package executor import ( "context" "fmt" "net/http" "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/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" xaiXSearchToolType = "x_search" // 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. xaiClientVersionValue = "0.2.120" xaiClientIdentifierHeader = "x-grok-client-identifier" xaiClientIdentifierValue = "grok-shell" xaiAuthenticateResponseHeader = "x-authenticateresponse" xaiAuthenticateResponseValue = "authenticate-response" // xaiUsingAPIAttr enables the official API path for non-media HTTP chat. 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) }