Files
CLIProxyAPI/internal/runtime/executor/xai_executor.go
ggbdpq b467a83c0f fix(xai): bump pinned grok client version to 1.0.44 for chat-proxy
xAI raised the minimum accepted x-grok-client-version on
cli-chat-proxy.grok.com, so every xAI OAuth chat request through the CLI
chat-proxy failed with HTTP 426 ("Your Grok CLI version (0.2.120) is
outdated"). /v1/models is not gated, so discovery looked healthy while
inference was dead.

Bump the pin to the current stable Grok CLI version (1.0.44) and record
the observed server floor in the constant's comment. Both
x-grok-client-version and the derived User-Agent ship from this one
constant.

Regression tests lock three things: the pinned version meets the known
server floor (red against 0.2.120), the two identity headers stay
derived from the same constant, and the per-auth `header:*` custom
header override still wins over the pin - the documented operator
escape hatch when xAI ratchets the floor again.

Closes #6249

Generated-by: GLM-5.3-Flash (ZCode)
2026-10-01 06:14:37 +08:00

116 lines
4.1 KiB
Go

package executor
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
"github.com/router-for-me/CLIProxyAPI/v8/internal/util"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
)
var (
xaiDataTag = []byte("data:")
xaiEventTag = []byte("event:")
)
const (
xaiImageHandlerType = "openai-image"
xaiVideoHandlerType = "openai-video"
xaiCustomToolType = "custom"
xaiFunctionToolType = "function"
xaiImageGenerationToolType = "image_generation"
xaiNamespaceToolType = "namespace"
xaiToolSearchType = "tool_search"
xaiWebSearchToolType = "web_search"
xaiClientWebSearchAlias = "clientfn_web_search"
xaiXSearchToolType = "x_search"
xaiMaxTools = 200
// Codex Desktop injects codex_app.automation_update with a large oneOf+$ref
// schema. xAI's free/build Responses path accepts the HTTP request but never
// emits SSE when that schema is present, so Desktop hangs on "thinking".
xaiCodexAppNamespaceName = "codex_app"
xaiAutomationUpdateToolName = "automation_update"
// Permissive placeholder schema: keeps the tool callable without the hang.
xaiSafeFunctionParameters = `{"type":"object","properties":{},"additionalProperties":true}`
xaiImagesGenerationsPath = "/images/generations"
xaiImagesEditsPath = "/images/edits"
xaiDefaultImageEndpointPath = xaiImagesGenerationsPath
xaiVideosGenerationsPath = "/videos/generations"
xaiVideosEditsPath = "/videos/edits"
xaiVideosExtensionsPath = "/videos/extensions"
xaiVideosPath = "/videos"
xaiIdempotencyKeyMetaKey = "idempotency_key"
xaiComposerModelPrefix = "grok-composer-"
xaiTokenAuthHeader = "X-XAI-Token-Auth"
xaiTokenAuthValue = "xai-grok-cli"
xaiClientVersionHeader = "x-grok-client-version"
// Keep in sync with the current Grok CLI client version that chat-proxy
// expects. The server rejects older versions with HTTP 426; it required
// 1.0.13+ as of 2026-10-01 (#6249).
xaiClientVersionValue = "1.0.44"
xaiClientIdentifierHeader = "x-grok-client-identifier"
xaiClientIdentifierValue = "grok-shell"
xaiAuthenticateResponseHeader = "x-authenticateresponse"
xaiAuthenticateResponseValue = "authenticate-response"
// xaiUsingAPIAttr enables the official API path for HTTP chat and media.
xaiUsingAPIAttr = "using_api"
)
// xaiXSearchToolJSON is the native X Search tool injected when enabled by config.
// Internal subtool traces are still filtered downstream when this tool is present.
var xaiXSearchToolJSON = []byte(`{"type":"x_search"}`)
// XAIExecutor is a stateless executor for xAI Grok's Responses API.
type XAIExecutor struct {
cfg *config.Config
}
// NewXAIExecutor creates a new xAI executor.
func NewXAIExecutor(cfg *config.Config) *XAIExecutor {
return &XAIExecutor{cfg: cfg}
}
// Identifier returns the provider identifier.
func (e *XAIExecutor) Identifier() string {
return "xai"
}
// PrepareRequest injects xAI credentials into the outgoing HTTP request.
func (e *XAIExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth.Auth) error {
if req == nil {
return nil
}
token, _ := xaiCreds(auth)
if strings.TrimSpace(token) != "" {
req.Header.Set("Authorization", "Bearer "+token)
} else {
req.Header.Del("Authorization")
}
var attrs map[string]string
if auth != nil {
attrs = auth.Attributes
}
util.ApplyCustomHeadersFromAttrs(req, attrs)
return nil
}
// HttpRequest injects xAI credentials into the request and executes it.
func (e *XAIExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth, req *http.Request) (*http.Response, error) {
if req == nil {
return nil, fmt.Errorf("xai executor: request is nil")
}
if ctx == nil {
ctx = req.Context()
}
httpReq := req.WithContext(ctx)
if errPrepare := e.PrepareRequest(httpReq, auth); errPrepare != nil {
return nil, errPrepare
}
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
return httpClient.Do(httpReq)
}