mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 11:43:23 +08:00
fix(grok): bump pinned Grok CLI version to 0.2.114
`cli-chat-proxy.grok.com` turns away requests that do not name a supported client version — the failure mode #3952 was opened for. The pin has sat at 0.2.93 since 2026-07-17, while https://x.ai/cli/stable and `npm view @xai-official/grok version` both report 0.2.114. The version was written out in three places, which is how they came to drift: - `repository/http_upstream.go` (OAuth traffic through the CLI proxy) - `pkg/xai/billing.go` (billing and quota probes) - `service/openai_gateway_grok.go` (gateway request headers) `xai.CLIClientVersion` is now the single source, and the other two build their own client identity from it, so the next bump is one line. `internal/pkg/xai` is a leaf package both layers already import, so this adds no cycle. `CLIUserAgent` derives from the same constant. Checked against the real 0.2.114 binary rather than the version feed alone: the `x-grok-client-version` header name, the `xai-grok-cli` token-auth value, and the version string all match what the CLI sends. The pinned value is also the floor an operator override must clear, so some fixtures carried a meaning relative to it rather than a fixed number. Those were moved, not search-and-replaced: - the "valid override" case now uses 0.2.115-alpha.1; at 0.2.95-alpha.1 it would fall below the new floor, be dropped, and stop testing acceptance at all - the prerelease-at-the-minimum case tracks the pin to 0.2.114-beta.1, or it becomes a copy of the below-minimum case - three of the five malformed-semver entries sat below the new floor and would have been turned away for being old, not for being malformed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -743,7 +743,7 @@ The Grok OAuth flow uses PKCE and does not require committing private secrets. T
|
||||
| `XAI_OAUTH_AUTHORIZE_URL` | `https://auth.x.ai/oauth2/authorize` |
|
||||
| `XAI_OAUTH_TOKEN_URL` | `https://auth.x.ai/oauth2/token` |
|
||||
| `XAI_BASE_URL` | `https://api.x.ai/v1`; runtime-diagnostics override (account `base_url` controls request forwarding) |
|
||||
| `XAI_GROK_CLI_VERSION` | `0.2.93`; optional override for the client identity sent to `cli-chat-proxy.grok.com` |
|
||||
| `XAI_GROK_CLI_VERSION` | `0.2.114`; optional override for the client identity sent to `cli-chat-proxy.grok.com`. The pinned value is also the floor: an override below it is dropped |
|
||||
|
||||
Administrators can create Grok OAuth or API-key accounts from the dashboard. OAuth authorization and reauthorization are also available through the admin API:
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@ const (
|
||||
CLITokenAuthHeader = "x-xai-token-auth"
|
||||
CLITokenAuthValue = "xai-grok-cli"
|
||||
CLIClientVersionHeader = "x-grok-client-version"
|
||||
// CLIClientVersion is the one place the pinned Grok CLI version lives. The
|
||||
// repository and service layers build their own client identity from it, so
|
||||
// one bump here covers OAuth traffic and billing probes together.
|
||||
// Keep in sync with https://x.ai/cli/stable.
|
||||
CLIClientVersion = "0.2.93"
|
||||
CLIClientVersion = "0.2.114"
|
||||
CLIUserAgent = "grok-pager/" + CLIClientVersion + " grok-shell/" + CLIClientVersion + " (macos; aarch64)"
|
||||
|
||||
BillingWeeklyPath = "/billing?format=credits"
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/proxyutil"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/servertiming"
|
||||
"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/util/urlvalidator"
|
||||
"golang.org/x/mod/semver"
|
||||
@@ -76,7 +77,7 @@ const (
|
||||
// allowing operators to bump it without waiting for a Sub2API release.
|
||||
grokCLIProxyHost = "cli-chat-proxy.grok.com"
|
||||
grokOfficialAPIHost = "api.x.ai"
|
||||
grokCLIStableVersion = "0.2.93"
|
||||
grokCLIStableVersion = xai.CLIClientVersion
|
||||
grokCLIVersionOverride = "XAI_GROK_CLI_VERSION"
|
||||
grokFallbackBodyLimit = 64 << 10
|
||||
)
|
||||
|
||||
@@ -235,9 +235,9 @@ func TestHTTPUpstreamDoAppliesGrokCLIIdentityBeforeOAuthRoundTrip(t *testing.T)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
require.Equal(t, "0.2.93", capturedHeaders.Get("x-grok-client-version"))
|
||||
require.Equal(t, "0.2.114", capturedHeaders.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-cli", capturedHeaders.Get("X-XAI-Token-Auth"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.93", capturedHeaders.Get("User-Agent"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.114", capturedHeaders.Get("User-Agent"))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -458,61 +458,63 @@ func TestApplyGrokCLIProxyHeaders(t *testing.T) {
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.93", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "0.2.114", 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-workspace/0.2.93", req.Header.Get("User-Agent"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent"))
|
||||
})
|
||||
|
||||
t.Run("accepts a valid operator override", func(t *testing.T) {
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.95-alpha.1")
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.115-alpha.1")
|
||||
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/chat/completions", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.95-alpha.1", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.95-alpha.1", req.Header.Get("User-Agent"))
|
||||
require.Equal(t, "0.2.115-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"))
|
||||
})
|
||||
|
||||
t.Run("rejects an unsafe override", func(t *testing.T) {
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.95\r\nX-Injected: true")
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.115\r\nX-Injected: true")
|
||||
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.93", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version"))
|
||||
require.Empty(t, req.Header.Get("X-Injected"))
|
||||
})
|
||||
|
||||
t.Run("rejects an override below the supported minimum", func(t *testing.T) {
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.92")
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.113")
|
||||
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.93", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.93", req.Header.Get("User-Agent"))
|
||||
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent"))
|
||||
})
|
||||
|
||||
t.Run("rejects a prerelease override at the minimum version", func(t *testing.T) {
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.93-beta.1")
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", "0.2.114-beta.1")
|
||||
req, err := http.NewRequest(http.MethodPost, "https://cli-chat-proxy.grok.com/v1/responses", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.93", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.93", req.Header.Get("User-Agent"))
|
||||
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent"))
|
||||
})
|
||||
|
||||
// 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.
|
||||
for _, version := range []string{
|
||||
"0.2.093",
|
||||
"0.2.94-alpha..1",
|
||||
"0.2.0115",
|
||||
"0.2.115-alpha..1",
|
||||
"0.3",
|
||||
"1",
|
||||
"0.2.95+build.1",
|
||||
"0.2.115+build.1",
|
||||
} {
|
||||
t.Run("rejects invalid semver "+version, func(t *testing.T) {
|
||||
t.Setenv("XAI_GROK_CLI_VERSION", version)
|
||||
@@ -521,8 +523,8 @@ func TestApplyGrokCLIProxyHeaders(t *testing.T) {
|
||||
|
||||
applyGrokCLIProxyHeaders(req)
|
||||
|
||||
require.Equal(t, "0.2.93", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.93", req.Header.Get("User-Agent"))
|
||||
require.Equal(t, "0.2.114", req.Header.Get("x-grok-client-version"))
|
||||
require.Equal(t, "xai-grok-workspace/0.2.114", req.Header.Get("User-Agent"))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const (
|
||||
grokComposerImageBridgeVisionModel = "grok-build-0.1"
|
||||
grokComposerImageBridgeMaxOutputTokens = 512
|
||||
grokUpstreamUserAgent = "sub2api-grok/1.0"
|
||||
grokCLIVersion = "0.2.93"
|
||||
grokCLIVersion = xai.CLIClientVersion
|
||||
grokDefaultResponsesModel = "grok-4.5"
|
||||
grokRateLimitFallbackCooldown = 2 * time.Minute
|
||||
grokRateLimitRepeatCooldown = 10 * time.Minute
|
||||
|
||||
Reference in New Issue
Block a user