| package executor |
|
|
| import ( |
| "bytes" |
| "fmt" |
| "net/http" |
| "strings" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/google/uuid" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
| cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" |
| "github.com/tidwall/gjson" |
| "github.com/tidwall/sjson" |
| ) |
|
|
| const ( |
| codexClientVersion = "0.101.0" |
| codexUserAgent = "codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464" |
| codexDefaultURL = "https://chatgpt.com/backend-api/codex" |
| ) |
|
|
| var dataTag = []byte("data:") |
|
|
| |
| type CodexProvider struct { |
| cfg *config.Config |
| } |
|
|
| func NewCodexProvider(cfg *config.Config) *CodexProvider { |
| return &CodexProvider{cfg: cfg} |
| } |
|
|
| func (p *CodexProvider) GetIdentifier() string { |
| return "codex" |
| } |
|
|
| func (p *CodexProvider) GetCredentials(auth *cliproxyauth.Auth) (apiKey, baseURL string) { |
| if auth == nil { |
| return "", codexDefaultURL |
| } |
|
|
| if auth.Attributes != nil { |
| apiKey = auth.Attributes["api_key"] |
| baseURL = auth.Attributes["base_url"] |
| } |
|
|
| if apiKey == "" && auth.Metadata != nil { |
| if v, ok := auth.Metadata["access_token"].(string); ok { |
| apiKey = v |
| } |
| } |
|
|
| if baseURL == "" { |
| baseURL = codexDefaultURL |
| } |
|
|
| return apiKey, baseURL |
| } |
|
|
| func (p *CodexProvider) GetEndpoint(baseURL, model, action string, stream bool) string { |
| return strings.TrimSuffix(baseURL, "/") + "/responses" |
| } |
|
|
| func (p *CodexProvider) ApplyHeaders(req *http.Request, auth *cliproxyauth.Auth, apiKey string, stream bool) { |
| req.Header.Set("Content-Type", "application/json") |
| req.Header.Set("Authorization", "Bearer "+apiKey) |
|
|
| var ginHeaders http.Header |
| if ginCtx, ok := req.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { |
| ginHeaders = ginCtx.Request.Header |
| } |
|
|
| misc.EnsureHeader(req.Header, ginHeaders, "Version", codexClientVersion) |
| misc.EnsureHeader(req.Header, ginHeaders, "Session_id", uuid.NewString()) |
| misc.EnsureHeader(req.Header, ginHeaders, "User-Agent", codexUserAgent) |
| misc.EnsureHeader(req.Header, ginHeaders, "x-codex-beta-features", "") |
| misc.EnsureHeader(req.Header, ginHeaders, "x-codex-turn-state", "") |
| misc.EnsureHeader(req.Header, ginHeaders, "x-codex-turn-metadata", "") |
| misc.EnsureHeader(req.Header, ginHeaders, "x-responsesapi-include-timing-metrics", "") |
|
|
| if stream { |
| req.Header.Set("Accept", "text/event-stream") |
| } else { |
| req.Header.Set("Accept", "application/json") |
| } |
| req.Header.Set("Connection", "Keep-Alive") |
|
|
| |
| isAPIKey := false |
| if auth != nil && auth.Attributes != nil { |
| if v := strings.TrimSpace(auth.Attributes["api_key"]); v != "" { |
| isAPIKey = true |
| } |
| } |
|
|
| if !isAPIKey { |
| req.Header.Set("Originator", "codex_cli_rs") |
| if auth != nil && auth.Metadata != nil { |
| if accountID, ok := auth.Metadata["account_id"].(string); ok { |
| req.Header.Set("Chatgpt-Account-Id", accountID) |
| } |
| } |
| } |
|
|
| |
| var attrs map[string]string |
| if auth != nil { |
| attrs = auth.Attributes |
| } |
| util.ApplyCustomHeadersFromAttrs(req, attrs) |
| } |
|
|
| func (p *CodexProvider) GetTranslatorFormat() string { |
| return "codex" |
| } |
|
|
| func (p *CodexProvider) TransformRequestBody(body []byte, model string, stream bool) ([]byte, error) { |
| return normalizeCodexRequestBody(body, model, stream) |
| } |
|
|
| func normalizeCodexRequestBody(body []byte, model string, stream bool) ([]byte, error) { |
| var err error |
|
|
| |
| body, err = sjson.SetBytes(body, "model", model) |
| if err != nil { |
| return body, fmt.Errorf("codex executor: failed to set model in payload: %w", err) |
| } |
|
|
| |
| body, _ = sjson.SetBytes(body, "stream", stream) |
|
|
| |
| if !gjson.GetBytes(body, "reasoning.effort").Exists() { |
| if effort := strings.TrimSpace(gjson.GetBytes(body, "reasoning_effort").String()); effort != "" { |
| body, _ = sjson.SetBytes(body, "reasoning.effort", effort) |
| } |
| } |
| body, _ = sjson.DeleteBytes(body, "reasoning_effort") |
| body = applyCodexReasoningProfile(body) |
| body, _ = sjson.DeleteBytes(body, "_cliproxy") |
| body, _ = sjson.DeleteBytes(body, "agent_mode") |
|
|
| |
| body, _ = sjson.DeleteBytes(body, "previous_response_id") |
| body, _ = sjson.DeleteBytes(body, "prompt_cache_retention") |
| body, _ = sjson.DeleteBytes(body, "safety_identifier") |
|
|
| |
| if !gjson.GetBytes(body, "instructions").Exists() { |
| body, _ = sjson.SetBytes(body, "instructions", "") |
| } |
|
|
| return body, nil |
| } |
|
|
| func (p *CodexProvider) TransformResponseBody(body []byte) []byte { |
| |
| return body |
| } |
|
|
| func (p *CodexProvider) ParseUsage(data []byte, stream bool) usageDetail { |
| |
| if payload, ok := codexCompletedEventPayload(data); ok { |
| if detail, ok := parseCodexUsage(payload); ok { |
| return detail |
| } |
| } |
| return usageDetail{} |
| } |
|
|
| func codexCompletedEventPayload(data []byte) ([]byte, bool) { |
| if !bytes.HasPrefix(data, dataTag) { |
| return nil, false |
| } |
| payload := bytes.TrimSpace(data[len(dataTag):]) |
| if gjson.GetBytes(payload, "type").String() != "response.completed" { |
| return nil, false |
| } |
| return payload, true |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func applyCodexReasoningProfile(body []byte) []byte { |
| if len(body) == 0 || !gjson.ValidBytes(body) { |
| return body |
| } |
|
|
| profile := strings.TrimSpace(gjson.GetBytes(body, "_cliproxy.reasoning_profile").String()) |
| custom := strings.TrimSpace(gjson.GetBytes(body, "_cliproxy.reasoning_prompt").String()) |
| force := gjson.GetBytes(body, "_cliproxy.force_reasoning_profile").Bool() |
|
|
| if profile == "" && custom == "" { |
| return body |
| } |
| if !force && !codexReasoningEnabled(body) { |
| body, _ = sjson.DeleteBytes(body, "_cliproxy") |
| return body |
| } |
|
|
| snippet := buildCodexReasoningProfilePrompt(profile, custom) |
| if strings.TrimSpace(snippet) == "" { |
| body, _ = sjson.DeleteBytes(body, "_cliproxy") |
| return body |
| } |
|
|
| currentInstructions := strings.TrimSpace(gjson.GetBytes(body, "instructions").String()) |
| if currentInstructions == "" { |
| body, _ = sjson.SetBytes(body, "instructions", snippet) |
| } else { |
| body, _ = sjson.SetBytes(body, "instructions", currentInstructions+"\n\n"+snippet) |
| } |
|
|
| body, _ = sjson.DeleteBytes(body, "_cliproxy") |
| return body |
| } |
|
|
| func codexReasoningEnabled(body []byte) bool { |
| for _, path := range []string{"reasoning.effort", "reasoning_effort"} { |
| if effort := gjson.GetBytes(body, path); effort.Exists() { |
| v := strings.ToLower(strings.TrimSpace(effort.String())) |
| if v != "" && v != "none" { |
| return true |
| } |
| } |
| } |
| return false |
| } |
|
|
| func buildCodexReasoningProfilePrompt(profile, custom string) string { |
| var sections []string |
| switch strings.ToLower(strings.TrimSpace(profile)) { |
| case "", "none", "off", "disabled": |
| |
| case "deep", "deep_engineering", "engineering": |
| sections = append(sections, `Response profile: |
| - Prefer thoroughness over brevity. |
| - Use multi-lens analysis (user intent/cognitive load, technical tradeoffs/performance, accessibility, scalability/maintenance). |
| - Avoid shallow conclusions; justify decisions concretely. |
| - Provide a visible rationale summary instead of hidden internal reasoning. |
| - Structure the answer into: |
| 1. Architectural/Design Rationale |
| 2. Edge Cases and Failure Prevention |
| 3. Production-Ready Implementation`) |
| sections = append(sections, codexDeepEngineeringStandardsPrompt()) |
| sections = append(sections, codexNormalResponseFormatPrompt()) |
| default: |
| |
| } |
| if strings.TrimSpace(custom) != "" { |
| sections = append(sections, custom) |
| } |
| return strings.TrimSpace(strings.Join(sections, "\n\n")) |
| } |
|
|
| func codexDeepEngineeringStandardsPrompt() string { |
| return strings.TrimSpace(`Engineering depth policy: |
| - Override Brevity: Immediately suspend the "Zero Fluff" rule when rigor is required. |
| - Maximum Depth: Engage in exhaustive, deep-level reasoning before writing a single line when implementation is requested. |
| - Prohibition: Never use surface-level logic. If the reasoning feels easy, dig deeper until the logic is irrefutable. |
| |
| Multi-Dimensional Analysis (apply when relevant): |
| - Architectural: Separation of concerns, modularity, dependency direction, coupling. |
| - Performance: Time/space complexity, memory layout, I/O costs, concurrency pitfalls, hot-path optimization. |
| - Reliability: Error handling strategy, edge cases, failure modes, defensive programming. |
| - Scalability: Will the design hold at 10x load/scope, and what is the long-term maintenance burden. |
| - Security: Input validation, injection vectors, privilege boundaries, secrets management. |
| - Ecosystem Fit: Prefer solutions native to the language/framework/community. |
| |
| Coding standards (all languages): |
| - Library & Framework Discipline (critical): If a library/framework/engine is active in the project, use it. Do not rebuild utilities the ecosystem already provides, and do not introduce redundant overlapping dependencies. Exception: wrappers/extensions are fine when the underlying primitive stays project-native. |
| - Language-specific awareness: |
| - Python: Type hints, pathlib over os.path, f-strings, dataclasses/Pydantic when appropriate, async for I/O-bound work. |
| - Lua: Respect table-driven design, metatables over class emulation unless already used, 1-based indexing, and target runtime differences. |
| - JavaScript/TypeScript: Strict TypeScript where possible, framework conventions (React/Vue/Svelte), ESM over CJS. |
| - Systems (Rust/Go/C/C++): Ownership/lifetime clarity, avoid unnecessary hot-path allocations, respect the language concurrency model. |
| - Shell/Bash: Prefer POSIX when portability matters, use set -euo pipefail, quote variables. |
| - SQL: Always parameterize queries unless a documented exception exists. |
| - Universal standards: |
| - Error Handling: Never swallow errors silently; use the idiomatic error model. |
| - Naming: Descriptive and consistent, following language/project conventions. |
| - Structure: Logical module organization; no god files; avoid oversized functions. |
| - Comments: Explain why, not what.`) |
| } |
|
|
| func codexNormalResponseFormatPrompt() string { |
| return strings.TrimSpace(`Response format (normal mode): |
| 1. Rationale: 1-2 sentences on the approach and why. |
| 2. The Code: production-ready code using project-native libraries/frameworks. |
| 3. Edge Cases: what can fail and how the implementation defends against it. |
| |
| When describing reasoning, provide a visible rationale summary. Do not expose hidden chain-of-thought.`) |
| } |
|
|