From a1a565177e5118bb06f1e159ee1dcd887c8ed19e Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 24 Jun 2026 00:08:02 -0400 Subject: [PATCH] fix(codex): route codex CLI to /backend-api/codex on original CLIProxy backend The Codex CLI provider config uses wire_api = "responses", so the Codex CLI appends "/responses" to its base_url. buildCodexCliproxyProviderBaseUrl derived the base URL from the backend-aware route helper, which returns the bare root for the original backend. Codex then called http://127.0.0.1:/responses, which the original CLIProxy binary does not serve, producing a 404 on every request. The original backend serves the Codex Responses API only at /v1/responses and the chatgpt_base_url-compatible alias /backend-api/codex/responses. Use that alias for the original backend; keep the provider-scoped alias for the Plus backend. Closes #1597 --- src/targets/codex-cliproxy-provider-config.ts | 21 +++++++++++++-- .../codex-cliproxy-provider-config.test.ts | 27 ++++++++++++++----- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/targets/codex-cliproxy-provider-config.ts b/src/targets/codex-cliproxy-provider-config.ts index e7525696..a2c93252 100644 --- a/src/targets/codex-cliproxy-provider-config.ts +++ b/src/targets/codex-cliproxy-provider-config.ts @@ -8,7 +8,11 @@ import { } from '../web-server/services/compatible-cli-toml-file-service'; import { getModelMaxLevel } from '../cliproxy/model-catalog'; import { parseCodexModelTuningAlias } from '../cliproxy/ai-providers/model-id-normalizer'; -import { buildLocalProviderBaseUrl } from '../cliproxy/config/provider-route'; +import { + buildLocalProviderBaseUrl, + getConfiguredCliproxyBackend, + usesScopedProviderRoutes, +} from '../cliproxy/config/provider-route'; import { ConfigError } from '../errors/error-types'; export const CCSXP_CLIPROXY_SHORTCUT_ENV = 'CCSXP_CLIPROXY_SHORTCUT'; @@ -39,7 +43,20 @@ function resolveCodexConfigPath(env: NodeJS.ProcessEnv = process.env): { } export function buildCodexCliproxyProviderBaseUrl(port: number): string { - return buildLocalProviderBaseUrl('codex', port); + // The Codex CLI provider uses wire_api = "responses", so the Codex CLI appends + // "/responses" to this base_url. The local CLIProxy backends do NOT serve the + // Codex Responses API at the bare root: + // - original backend: only "/v1/responses" and "/backend-api/codex/responses" + // - plus backend: additionally "/api/provider/codex/responses" + // Returning the root makes Codex call "http://127.0.0.1:/responses" -> 404 + // (issue #1597). Use the chatgpt_base_url-compatible "/backend-api/codex" alias, + // which both backends serve; keep the provider-scoped alias for the Plus backend + // to preserve its existing per-provider routing. + const backend = getConfiguredCliproxyBackend(); + if (usesScopedProviderRoutes(backend)) { + return buildLocalProviderBaseUrl('codex', port, backend); + } + return `http://127.0.0.1:${port}/backend-api/codex`; } export function isCcsxpCliproxyShortcut(env: NodeJS.ProcessEnv = process.env): boolean { diff --git a/tests/unit/targets/codex-cliproxy-provider-config.test.ts b/tests/unit/targets/codex-cliproxy-provider-config.test.ts index 62792f81..9b1b99d2 100644 --- a/tests/unit/targets/codex-cliproxy-provider-config.test.ts +++ b/tests/unit/targets/codex-cliproxy-provider-config.test.ts @@ -50,8 +50,21 @@ describe('codex cliproxy provider config repair', () => { clearConfigCache(); } - it('uses the original backend root URL by default', () => { - expect(buildCodexCliproxyProviderBaseUrl(8317)).toBe('http://127.0.0.1:8317'); + it('uses the original backend chatgpt_base_url alias by default', () => { + // Codex CLI (wire_api = "responses") appends "/responses" to base_url. The + // original CLIProxy backend serves the Codex Responses API only under the + // "/backend-api/codex" alias, never at the bare root. Returning the root here + // makes Codex call "http://127.0.0.1:8317/responses" -> 404 (issue #1597). + expect(buildCodexCliproxyProviderBaseUrl(8317)).toBe( + 'http://127.0.0.1:8317/backend-api/codex' + ); + }); + + it('produces a Codex Responses endpoint the original backend actually serves (regression #1597)', () => { + const baseUrl = buildCodexCliproxyProviderBaseUrl(8317); + const responsesEndpoint = `${baseUrl.replace(/\/+$/, '')}/responses`; + expect(responsesEndpoint).toBe('http://127.0.0.1:8317/backend-api/codex/responses'); + expect(responsesEndpoint).not.toBe('http://127.0.0.1:8317/responses'); }); it('uses the plus backend scoped Codex URL when configured', () => { @@ -69,7 +82,7 @@ describe('codex cliproxy provider config repair', () => { const rawText = fs.readFileSync(configPath, 'utf8'); expect(rawText).toContain('[model_providers.cliproxy]'); expect(rawText).toContain('name = "CLIProxy Codex"'); - expect(rawText).toContain('base_url = "http://127.0.0.1:8317"'); + expect(rawText).toContain('base_url = "http://127.0.0.1:8317/backend-api/codex"'); expect(rawText).toContain('env_key = "CLIPROXY_API_KEY"'); expect(rawText).not.toContain('model_provider = "cliproxy"'); }); @@ -116,7 +129,7 @@ wire_api = "responses" expect(result.changed).toBe(true); expect(result.envKey).toBe('CLIPROXY_API_KEY'); const rawText = fs.readFileSync(configPath, 'utf8'); - expect(rawText).toContain('base_url = "http://127.0.0.1:9321"'); + expect(rawText).toContain('base_url = "http://127.0.0.1:9321/backend-api/codex"'); expect(rawText).toContain('env_key = "CLIPROXY_API_KEY"'); expect(rawText).toContain('requires_openai_auth = false'); expect(rawText).toContain('supports_websockets = false'); @@ -180,7 +193,7 @@ supports_websockets = false expect(fs.readFileSync(configPath, 'utf8')).toBe(rawText); }); - it('repairs a stale ready localhost provider to the original backend root URL', async () => { + it('repairs a stale ready localhost provider to the original backend codex alias', async () => { fs.mkdirSync(codexHome, { recursive: true }); const rawText = `[model_providers.cliproxy] name = "CLIProxy Codex" @@ -197,7 +210,7 @@ supports_websockets = false expect(result.changed).toBe(true); expect(result.envKey).toBe('CLIPROXY_API_KEY'); const repairedText = fs.readFileSync(configPath, 'utf8'); - expect(repairedText).toContain('base_url = "http://127.0.0.1:8317"'); + expect(repairedText).toContain('base_url = "http://127.0.0.1:8317/backend-api/codex"'); expect(repairedText).not.toContain('/api/provider/codex'); }); @@ -299,7 +312,7 @@ supports_websockets = false [model_providers.cliproxy] name = "CLIProxy Codex" -base_url = "http://127.0.0.1:8317" +base_url = "http://127.0.0.1:8317/backend-api/codex" env_key = "CLIPROXY_API_KEY" wire_api = "responses" requires_openai_auth = false