diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index f0e3a0bc..6c932ae2 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -226,6 +226,9 @@ type GatewayConfig struct { MaxBodySize int64 `mapstructure:"max_body_size"` // ConnectionPoolIsolation: 上游连接池隔离策略(proxy/account/account_proxy) ConnectionPoolIsolation string `mapstructure:"connection_pool_isolation"` + // ForceCodexCLI: 强制将 OpenAI `/v1/responses` 请求按 Codex CLI 处理。 + // 用于网关未透传/改写 User-Agent 时的兼容兜底(默认关闭,避免影响其他客户端)。 + ForceCodexCLI bool `mapstructure:"force_codex_cli"` // HTTP 上游连接池配置(性能优化:支持高并发场景调优) // MaxIdleConns: 所有主机的最大空闲连接总数 @@ -882,6 +885,7 @@ func setDefaults() { viper.SetDefault("gateway.failover_on_400", false) viper.SetDefault("gateway.max_account_switches", 10) viper.SetDefault("gateway.max_account_switches_gemini", 3) + viper.SetDefault("gateway.force_codex_cli", false) viper.SetDefault("gateway.antigravity_fallback_cooldown_minutes", 1) viper.SetDefault("gateway.max_body_size", int64(100*1024*1024)) viper.SetDefault("gateway.connection_pool_isolation", ConnectionPoolIsolationAccountProxy) diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index 1dcb163b..13b3703e 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -28,6 +28,7 @@ type OpenAIGatewayHandler struct { errorPassthroughService *service.ErrorPassthroughService concurrencyHelper *ConcurrencyHelper maxAccountSwitches int + cfg *config.Config } // NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler @@ -54,6 +55,7 @@ func NewOpenAIGatewayHandler( errorPassthroughService: errorPassthroughService, concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatComment, pingInterval), maxAccountSwitches: maxAccountSwitches, + cfg: cfg, } } @@ -109,7 +111,8 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { } userAgent := c.GetHeader("User-Agent") - if !openai.IsCodexCLIRequest(userAgent) { + isCodexCLI := openai.IsCodexCLIRequest(userAgent) || (h.cfg != nil && h.cfg.Gateway.ForceCodexCLI) + if !isCodexCLI { existingInstructions, _ := reqBody["instructions"].(string) if strings.TrimSpace(existingInstructions) == "" { if instructions := strings.TrimSpace(service.GetOpenCodeInstructions()); instructions != "" { diff --git a/backend/internal/service/openai_codex_transform.go b/backend/internal/service/openai_codex_transform.go index ba3b1b7b..1c7f3e82 100644 --- a/backend/internal/service/openai_codex_transform.go +++ b/backend/internal/service/openai_codex_transform.go @@ -342,8 +342,8 @@ func GetCodexCLIInstructions() string { } // applyInstructions 处理 instructions 字段 -// isCodexCLI=true: 仅补充缺失的 instructions(使用 opencode 指令) -// isCodexCLI=false: 优先使用 opencode 指令覆盖 +// isCodexCLI=true: 仅补充缺失的 instructions(使用内置 Codex CLI 指令) +// isCodexCLI=false: 优先使用 opencode 指令覆盖(不可用时回退到内置 Codex CLI 指令) func applyInstructions(reqBody map[string]any, isCodexCLI bool) bool { if isCodexCLI { return applyCodexCLIInstructions(reqBody) @@ -352,13 +352,13 @@ func applyInstructions(reqBody map[string]any, isCodexCLI bool) bool { } // applyCodexCLIInstructions 为 Codex CLI 请求补充缺失的 instructions -// 仅在 instructions 为空时添加 opencode 指令 +// 仅在 instructions 为空时添加内置 Codex CLI 指令(不依赖 opencode 缓存/回源) func applyCodexCLIInstructions(reqBody map[string]any) bool { if !isInstructionsEmpty(reqBody) { return false // 已有有效 instructions,不修改 } - instructions := strings.TrimSpace(getOpenCodeCodexHeader()) + instructions := strings.TrimSpace(getCodexCLIInstructions()) if instructions != "" { reqBody["instructions"] = instructions return true diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index 69c8aa9f..76e8abd4 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -765,7 +765,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco bodyModified := false originalModel := reqModel - isCodexCLI := openai.IsCodexCLIRequest(c.GetHeader("User-Agent")) + isCodexCLI := openai.IsCodexCLIRequest(c.GetHeader("User-Agent")) || (s.cfg != nil && s.cfg.Gateway.ForceCodexCLI) // 对所有请求执行模型映射(包含 Codex CLI)。 mappedModel := account.GetMappedModel(reqModel) @@ -1053,6 +1053,12 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin. req.Header.Set("user-agent", customUA) } + // 若开启 ForceCodexCLI,则强制将上游 User-Agent 伪装为 Codex CLI。 + // 用于网关未透传/改写 User-Agent 时,仍能命中 Codex 侧识别逻辑。 + if s.cfg != nil && s.cfg.Gateway.ForceCodexCLI { + req.Header.Set("user-agent", "codex_cli_rs/0.98.0") + } + // Ensure required headers exist if req.Header.Get("content-type") == "" { req.Header.Set("content-type", "application/json") diff --git a/deploy/.env.example b/deploy/.env.example index 6581afd4..44821064 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -169,6 +169,13 @@ RATE_LIMIT_OVERLOAD_COOLDOWN_MINUTES=10 # Gateway Scheduling (Optional) # 调度缓存与受控回源配置(缓存就绪且命中时不读 DB) # ----------------------------------------------------------------------------- +# Force Codex CLI mode: treat all /openai/v1/responses requests as Codex CLI. +# 强制按 Codex CLI 处理 /openai/v1/responses 请求(用于网关未透传/改写 User-Agent 的兜底)。 +# +# 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。 +# +# 默认:false +GATEWAY_FORCE_CODEX_CLI=false # 上游连接池:每主机最大连接数(默认 1024;流式/HTTP1.1 场景可调大,如 2400/4096) GATEWAY_MAX_CONNS_PER_HOST=2048 # 上游连接池:最大空闲连接总数(默认 2560;账号/代理隔离 + 高并发场景可调大) diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml index 941bc03e..27fe6ad8 100644 --- a/deploy/config.example.yaml +++ b/deploy/config.example.yaml @@ -151,6 +151,11 @@ gateway: # - account_proxy: Isolate by account+proxy combination (default, finest granularity) # - account_proxy: 按账户+代理组合隔离(默认,最细粒度) connection_pool_isolation: "account_proxy" + # Force Codex CLI mode: treat all /openai/v1/responses requests as Codex CLI. + # 强制按 Codex CLI 处理 /openai/v1/responses 请求(用于网关未透传/改写 User-Agent 的兜底)。 + # + # 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。 + force_codex_cli: false # HTTP upstream connection pool settings (HTTP/2 + multi-proxy scenario defaults) # HTTP 上游连接池配置(HTTP/2 + 多代理场景默认值) # Max idle connections across all hosts diff --git a/deploy/docker-compose-aicodex.yml b/deploy/docker-compose-aicodex.yml index 017903dd..44ff6ddd 100644 --- a/deploy/docker-compose-aicodex.yml +++ b/deploy/docker-compose-aicodex.yml @@ -66,6 +66,7 @@ services: # ======================================================================= # Gateway Configuration # ======================================================================= + - GATEWAY_FORCE_CODEX_CLI=${GATEWAY_FORCE_CODEX_CLI:-false} - GATEWAY_MAX_IDLE_CONNS=${GATEWAY_MAX_IDLE_CONNS:-2560} - GATEWAY_MAX_IDLE_CONNS_PER_HOST=${GATEWAY_MAX_IDLE_CONNS_PER_HOST:-120} - GATEWAY_MAX_CONNS_PER_HOST=${GATEWAY_MAX_CONNS_PER_HOST:-8192}