fix(grok): use official CLI user agent

This commit is contained in:
shaw
2026-08-24 22:14:45 +08:00
parent 07931bbb18
commit 9fb260439f
9 changed files with 45 additions and 48 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ const (
// repository and service layers build their own client identity from it, so // repository and service layers build their own client identity from it, so
// one bump here covers OAuth traffic and billing probes together. // one bump here covers OAuth traffic and billing probes together.
// Keep in sync with https://x.ai/cli/stable. // Keep in sync with https://x.ai/cli/stable.
CLIClientVersion = "0.2.114" CLIClientVersion = "0.2.120"
// billingCLIUserAgent is the legacy pager/shell UA used by billing probes. // billingCLIUserAgent is the legacy pager/shell UA used by billing probes.
// Distinct from CLIUserAgent() in cli_identity.go (workspace-style UA). // Distinct from CLIUserAgent() in cli_identity.go (workspace-style UA).
billingCLIUserAgent = "grok-pager/" + CLIClientVersion + " grok-shell/" + CLIClientVersion + " (macos; aarch64)" billingCLIUserAgent = "grok-pager/" + CLIClientVersion + " grok-shell/" + CLIClientVersion + " (macos; aarch64)"
@@ -41,7 +41,7 @@ func TestApplyCLIProxyHeaders(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("User-Agent", "sub2api-grok/1.0") req.Header.Set("User-Agent", "legacy-client/1.0")
ApplyCLIProxyHeaders(req) ApplyCLIProxyHeaders(req)
@@ -56,12 +56,12 @@ func TestApplyCLIProxyHeadersLeavesAPIHostUnchanged(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "https://api.x.ai/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://api.x.ai/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("User-Agent", "sub2api-grok/1.0") req.Header.Set("User-Agent", "direct-api-client/1.0")
ApplyCLIProxyHeaders(req) ApplyCLIProxyHeaders(req)
require.Empty(t, req.Header.Get("x-grok-client-version")) require.Empty(t, req.Header.Get("x-grok-client-version"))
require.Empty(t, req.Header.Get("x-grok-client-identifier")) require.Empty(t, req.Header.Get("x-grok-client-identifier"))
require.Empty(t, req.Header.Get("X-XAI-Token-Auth")) require.Empty(t, req.Header.Get("X-XAI-Token-Auth"))
require.Equal(t, "sub2api-grok/1.0", req.Header.Get("User-Agent")) require.Equal(t, "direct-api-client/1.0", req.Header.Get("User-Agent"))
} }
@@ -17,6 +17,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
"github.com/Wei-Shaw/sub2api/internal/service" "github.com/Wei-Shaw/sub2api/internal/service"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@@ -228,16 +229,16 @@ func TestHTTPUpstreamDoAppliesGrokCLIIdentityBeforeOAuthRoundTrip(t *testing.T)
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/"+endpoint, nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/"+endpoint, nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("User-Agent", "sub2api-grok/1.0") req.Header.Set("User-Agent", "legacy-client/1.0")
resp, err := svc.Do(req, "", accountID, 1) resp, err := svc.Do(req, "", accountID, 1)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close()) require.NoError(t, resp.Body.Close())
require.Equal(t, "0.2.114", capturedHeaders.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, capturedHeaders.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-cli", capturedHeaders.Get("X-XAI-Token-Auth")) require.Equal(t, "xai-grok-cli", capturedHeaders.Get("X-XAI-Token-Auth"))
require.Equal(t, "xai-grok-workspace/0.2.114", capturedHeaders.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), capturedHeaders.Get("User-Agent"))
}) })
} }
} }
@@ -454,67 +455,67 @@ func TestApplyGrokCLIProxyHeaders(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "") t.Setenv("XAI_GROK_CLI_VERSION", "")
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("User-Agent", "sub2api-grok/1.0") req.Header.Set("User-Agent", "legacy-client/1.0")
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, req.Header.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-cli", req.Header.Get("X-XAI-Token-Auth")) require.Equal(t, "xai-grok-cli", req.Header.Get("X-XAI-Token-Auth"))
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), req.Header.Get("User-Agent"))
}) })
t.Run("accepts a valid operator override", func(t *testing.T) { t.Run("accepts a valid operator override", func(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.115-alpha.1") t.Setenv("XAI_GROK_CLI_VERSION", "0.2.121-alpha.1")
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/chat/completions", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/chat/completions", nil)
require.NoError(t, err) require.NoError(t, err)
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.115-alpha.1", req.Header.Get("x-grok-client-version")) require.Equal(t, "0.2.121-alpha.1", req.Header.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-workspace/0.2.115-alpha.1", req.Header.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent("0.2.121-alpha.1"), req.Header.Get("User-Agent"))
}) })
t.Run("rejects an unsafe override", func(t *testing.T) { t.Run("rejects an unsafe override", func(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.115\r\nX-Injected: true") t.Setenv("XAI_GROK_CLI_VERSION", "0.2.121\r\nX-Injected: true")
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, req.Header.Get("x-grok-client-version"))
require.Empty(t, req.Header.Get("X-Injected")) require.Empty(t, req.Header.Get("X-Injected"))
}) })
t.Run("rejects an override below the supported minimum", func(t *testing.T) { t.Run("rejects an override below the supported minimum", func(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.113") t.Setenv("XAI_GROK_CLI_VERSION", "0.2.119")
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, req.Header.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), req.Header.Get("User-Agent"))
}) })
t.Run("rejects a prerelease override at the minimum version", func(t *testing.T) { t.Run("rejects a prerelease override at the minimum version", func(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.114-beta.1") t.Setenv("XAI_GROK_CLI_VERSION", "0.2.120-beta.1")
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, req.Header.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), req.Header.Get("User-Agent"))
}) })
// Every entry sits above the pinned minimum, so a rejection here can only be // Every entry sits above the pinned minimum, so a rejection here can only be
// caused by the malformed semver and never by the version being too old. // caused by the malformed semver and never by the version being too old.
for _, version := range []string{ for _, version := range []string{
"0.2.0115", "0.2.0121",
"0.2.115-alpha..1", "0.2.121-alpha..1",
"0.3", "0.3",
"1", "1",
"0.2.115+build.1", "0.2.121+build.1",
} { } {
t.Run("rejects invalid semver "+version, func(t *testing.T) { t.Run("rejects invalid semver "+version, func(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", version) t.Setenv("XAI_GROK_CLI_VERSION", version)
@@ -523,8 +524,8 @@ func TestApplyGrokCLIProxyHeaders(t *testing.T) {
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, req.Header.Get("x-grok-client-version"))
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent")) require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), req.Header.Get("User-Agent"))
}) })
} }
@@ -532,13 +533,13 @@ func TestApplyGrokCLIProxyHeaders(t *testing.T) {
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.95") t.Setenv("XAI_GROK_CLI_VERSION", "0.2.95")
req, err := http.NewRequest(http.MethodPost, "https://api.x.ai/v1/responses", nil) req, err := http.NewRequest(http.MethodPost, "https://api.x.ai/v1/responses", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("User-Agent", "sub2api-grok/1.0") req.Header.Set("User-Agent", "direct-api-client/1.0")
applyGrokCLIProxyHeaders(req) applyGrokCLIProxyHeaders(req)
require.Empty(t, req.Header.Get("x-grok-client-version")) require.Empty(t, req.Header.Get("x-grok-client-version"))
require.Empty(t, req.Header.Get("X-XAI-Token-Auth")) require.Empty(t, req.Header.Get("X-XAI-Token-Auth"))
require.Equal(t, "sub2api-grok/1.0", req.Header.Get("User-Agent")) require.Equal(t, "direct-api-client/1.0", req.Header.Get("User-Agent"))
}) })
} }
@@ -92,7 +92,7 @@ func (s *GrokQuotaService) syncGrokObservedModels(ctx context.Context, account *
} }
req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", grokUpstreamUserAgent) req.Header.Set("User-Agent", defaultGrokUpstreamUserAgent())
if account.IsGrokOAuth() { if account.IsGrokOAuth() {
applyGrokCLIHeaders(req.Header) applyGrokCLIHeaders(req.Header)
if isGrokCLIProxyTarget(req.URL.String()) { if isGrokCLIProxyTarget(req.URL.String()) {
@@ -133,8 +133,9 @@ func TestSyncGrokObservedModelsUsesCLIIdentityAndAccountHeaders(t *testing.T) {
require.NoError(t, svc.syncGrokObservedModels(context.Background(), account)) require.NoError(t, svc.syncGrokObservedModels(context.Background(), account))
require.Equal(t, xai.DefaultCLIBaseURL+"/models", upstream.lastReq.URL.String()) require.Equal(t, xai.DefaultCLIBaseURL+"/models", upstream.lastReq.URL.String())
require.NotEmpty(t, upstream.lastReq.Header.Get("x-grok-client-version")) require.Equal(t, xai.CLIClientVersion, upstream.lastReq.Header.Get("x-grok-client-version"))
require.Equal(t, xai.CLIClientIdentifier, upstream.lastReq.Header.Get("x-grok-client-identifier")) require.Equal(t, xai.CLIClientIdentifier, upstream.lastReq.Header.Get("x-grok-client-identifier"))
require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), upstream.lastReq.Header.Get("User-Agent"))
require.Equal(t, "interactive", upstream.lastReq.Header.Get("X-Grok-Client-Mode")) require.Equal(t, "interactive", upstream.lastReq.Header.Get("X-Grok-Client-Mode"))
require.Equal(t, "user-902", upstream.lastReq.Header.Get("X-UserID")) require.Equal(t, "user-902", upstream.lastReq.Header.Get("X-UserID"))
require.Equal(t, "user902@example.test", upstream.lastReq.Header.Get("X-Email")) require.Equal(t, "user902@example.test", upstream.lastReq.Header.Get("X-Email"))
@@ -10,10 +10,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/xai" "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
) )
// grokUpstreamUserAgent is kept for compatibility with older Grok request
// tests. Current requests use the pinned default UA from this package.
const grokUpstreamUserAgent = "sub2api-grok/1.0"
// Fixed CLI identity aliases — single source of truth is internal/pkg/xai. // Fixed CLI identity aliases — single source of truth is internal/pkg/xai.
const ( const (
grokClientVersionHeader = xai.CLIStableVersion grokClientVersionHeader = xai.CLIStableVersion
@@ -175,7 +175,7 @@ func (s *OpenAIGatewayService) forwardAsRawChatCompletions(
SetActualOpenAIUpstreamEndpoint(c, grokChatRawEndpoint) SetActualOpenAIUpstreamEndpoint(c, grokChatRawEndpoint)
customUA := account.GetOpenAIUserAgent() customUA := account.GetOpenAIUserAgent()
if customUA == "" && account.IsGrokOAuth() { if customUA == "" && account.IsGrokOAuth() {
customUA = "sub2api-grok/1.0" customUA = defaultGrokUpstreamUserAgent()
} }
resp, err := s.sendCCUpstreamRequest(ctx, c, account, targetURL, upstreamBody, clientStream, token, customUA, grokCacheIdentity) resp, err := s.sendCCUpstreamRequest(ctx, c, account, targetURL, upstreamBody, clientStream, token, customUA, grokCacheIdentity)
if err != nil { if err != nil {
@@ -22,14 +22,13 @@ import (
const ( const (
grokComposerImageBridgeVisionModel = "grok-build-0.1" grokComposerImageBridgeVisionModel = "grok-build-0.1"
grokComposerImageBridgeMaxOutputTokens = 512 grokComposerImageBridgeMaxOutputTokens = 512
// grokUpstreamUserAgent lives in grok_upstream_headers.go (shared with TLS header helpers). grokCLIVersion = xai.CLIClientVersion
grokCLIVersion = xai.CLIClientVersion grokDefaultResponsesModel = "grok-4.5"
grokDefaultResponsesModel = "grok-4.5" grokRateLimitFallbackCooldown = 2 * time.Minute
grokRateLimitFallbackCooldown = 2 * time.Minute grokRateLimitRepeatCooldown = 10 * time.Minute
grokRateLimitRepeatCooldown = 10 * time.Minute grokRateLimitSustainedCooldown = 30 * time.Minute
grokRateLimitSustainedCooldown = 30 * time.Minute grokRateLimitMaxAdaptiveCooldown = time.Hour
grokRateLimitMaxAdaptiveCooldown = time.Hour grokRateLimitBackoffQuietPeriod = time.Hour
grokRateLimitBackoffQuietPeriod = time.Hour
) )
func (s *OpenAIGatewayService) forwardGrokResponses( func (s *OpenAIGatewayService) forwardGrokResponses(
@@ -828,7 +828,7 @@ func TestBuildGrokResponsesRequestAllowsPublicAPIKeyBaseURLByDefault(t *testing.
require.Equal(t, "https://grok.example.test/v1/responses", req.URL.String()) require.Equal(t, "https://grok.example.test/v1/responses", req.URL.String())
require.Equal(t, "Bearer api-key", req.Header.Get("Authorization")) require.Equal(t, "Bearer api-key", req.Header.Get("Authorization"))
require.Empty(t, req.Header.Get("X-Grok-Client-Version")) require.Empty(t, req.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, req.Header.Get("User-Agent")) require.NotEqual(t, defaultGrokUpstreamUserAgent(), req.Header.Get("User-Agent"))
} }
func TestBuildGrokResponsesRequestHonorsOAuthOfficialEndpointSwitch(t *testing.T) { func TestBuildGrokResponsesRequestHonorsOAuthOfficialEndpointSwitch(t *testing.T) {
@@ -1132,7 +1132,7 @@ func TestForwardGrokMediaImagesGenerationNormalizesImagineAlias(t *testing.T) {
require.Equal(t, "Bearer api-key", upstream.lastReq.Header.Get("Authorization")) require.Equal(t, "Bearer api-key", upstream.lastReq.Header.Get("Authorization"))
require.Equal(t, "application/json", upstream.lastReq.Header.Get("Content-Type")) require.Equal(t, "application/json", upstream.lastReq.Header.Get("Content-Type"))
require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version")) require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, upstream.lastReq.Header.Get("User-Agent")) require.NotEqual(t, defaultGrokUpstreamUserAgent(), upstream.lastReq.Header.Get("User-Agent"))
require.JSONEq(t, `{"model":"grok-imagine-image-quality","prompt":"draw a cat"}`, string(upstream.lastBody)) require.JSONEq(t, `{"model":"grok-imagine-image-quality","prompt":"draw a cat"}`, string(upstream.lastBody))
require.Equal(t, http.StatusOK, recorder.Code) require.Equal(t, http.StatusOK, recorder.Code)
require.JSONEq(t, `{"data":[{"url":"https://images.test/cat.png"}]}`, recorder.Body.String()) require.JSONEq(t, `{"data":[{"url":"https://images.test/cat.png"}]}`, recorder.Body.String())
@@ -1646,7 +1646,7 @@ func TestForwardGrokMediaVideoStatusUsesGETWithoutBody(t *testing.T) {
require.Equal(t, http.MethodGet, upstream.lastReq.Method) require.Equal(t, http.MethodGet, upstream.lastReq.Method)
require.Equal(t, "Bearer api-key", upstream.lastReq.Header.Get("Authorization")) require.Equal(t, "Bearer api-key", upstream.lastReq.Header.Get("Authorization"))
require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version")) require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, upstream.lastReq.Header.Get("User-Agent")) require.NotEqual(t, defaultGrokUpstreamUserAgent(), upstream.lastReq.Header.Get("User-Agent"))
require.Empty(t, upstream.lastReq.Header.Get("Content-Type")) require.Empty(t, upstream.lastReq.Header.Get("Content-Type"))
require.Empty(t, upstream.lastBody) require.Empty(t, upstream.lastBody)
require.Equal(t, http.StatusOK, recorder.Code) require.Equal(t, http.StatusOK, recorder.Code)
@@ -1982,7 +1982,7 @@ func TestForwardGrokResponsesAPIKeyUsesXAIResponses(t *testing.T) {
require.Equal(t, "https://api.x.ai/v1/responses", upstream.lastReq.URL.String()) require.Equal(t, "https://api.x.ai/v1/responses", upstream.lastReq.URL.String())
require.Equal(t, "Bearer xai-test-key", upstream.lastReq.Header.Get("Authorization")) require.Equal(t, "Bearer xai-test-key", upstream.lastReq.Header.Get("Authorization"))
require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version")) require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, upstream.lastReq.Header.Get("User-Agent")) require.NotEqual(t, defaultGrokUpstreamUserAgent(), upstream.lastReq.Header.Get("User-Agent"))
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String()) require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "resp_grok_api_key", result.ResponseID) require.Equal(t, "resp_grok_api_key", result.ResponseID)
require.Equal(t, 2, result.Usage.InputTokens) require.Equal(t, 2, result.Usage.InputTokens)
@@ -2259,7 +2259,7 @@ func TestForwardAsChatCompletionsForGrokAPIKeyUsesConfiguredRawEndpointWithoutOA
require.Equal(t, "https://grok.example.test/v1/chat/completions", upstream.lastReq.URL.String()) require.Equal(t, "https://grok.example.test/v1/chat/completions", upstream.lastReq.URL.String())
require.Equal(t, "Bearer third-party-key", upstream.lastReq.Header.Get("Authorization")) require.Equal(t, "Bearer third-party-key", upstream.lastReq.Header.Get("Authorization"))
require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version")) require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, upstream.lastReq.Header.Get("User-Agent")) require.NotEqual(t, defaultGrokUpstreamUserAgent(), upstream.lastReq.Header.Get("User-Agent"))
} }
func TestForwardAsChatCompletionsForGrokAPIKeyRejectsNonStreamingResponseWithoutUsage(t *testing.T) { func TestForwardAsChatCompletionsForGrokAPIKeyRejectsNonStreamingResponseWithoutUsage(t *testing.T) {