mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-22 23:54:13 +08:00
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)
116 lines
4.1 KiB
Go
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)
|
|
}
|