From e09e930c38ef17dfecf000804504ab6ed4f38367 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 30 May 2026 14:56:21 -0400 Subject: [PATCH] fix(codex): gate ccsxp config overrides --- src/cliproxy/quota/quota-manager.ts | 10 +- src/management/checks/image-analysis-check.ts | 5 +- src/targets/codex-adapter.ts | 13 +- tests/unit/bin/ccsxp-runtime.test.ts | 15 +- tests/unit/targets/codex-adapter.test.ts | 29 ++- .../targets/codex-runtime-integration.test.ts | 209 ++++++++++-------- 6 files changed, 170 insertions(+), 111 deletions(-) diff --git a/src/cliproxy/quota/quota-manager.ts b/src/cliproxy/quota/quota-manager.ts index 8574b079..041e2748 100644 --- a/src/cliproxy/quota/quota-manager.ts +++ b/src/cliproxy/quota/quota-manager.ts @@ -483,9 +483,8 @@ export async function reconcileExhaustedRotationAccounts( return []; } - const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import( - '../accounts/account-safety' - ); + const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = + await import('../accounts/account-safety'); restoreExpiredQuotaPauses(); const config = loadOrCreateUnifiedConfig(); @@ -593,9 +592,8 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise * Fix image analysis configuration issues */ export async function fixImageAnalysisConfig(): Promise { - const { updateConfig, loadOrCreateUnifiedConfig } = await import( - '../../config/config-loader-facade' - ); + const { updateConfig, loadOrCreateUnifiedConfig } = + await import('../../config/config-loader-facade'); const config = loadOrCreateUnifiedConfig(); let fixed = false; diff --git a/src/targets/codex-adapter.ts b/src/targets/codex-adapter.ts index 333dda86..c1a02bed 100644 --- a/src/targets/codex-adapter.ts +++ b/src/targets/codex-adapter.ts @@ -289,20 +289,19 @@ export class CodexAdapter implements TargetAdapter { const runtimeConfigOverrides = creds?.runtimeConfigOverrides ?? []; if (profileType === 'default') { - const modelFlagNormalization = isCcsxpCliproxyShortcut() + const isCcsxpShortcut = isCcsxpCliproxyShortcut(); + const modelFlagNormalization = isCcsxpShortcut ? normalizeCcsxpCodexModelFlagAliases(userArgs) : { args: userArgs, overrides: [] }; const overrides = [...runtimeConfigOverrides, ...modelFlagNormalization.overrides]; if (reasoningOverride) { overrides.push(`model_reasoning_effort=${formatTomlString(reasoningOverride)}`); } - if (overrides.length === 0) { - return modelFlagNormalization.args; + const needsConfigOverrideSupport = isCcsxpShortcut || overrides.length > 0; + if (needsConfigOverrideSupport && !codexBinarySupportsConfigOverrides(options?.binaryInfo)) { + throw buildConfigOverrideSupportError(hydrateCodexBinaryVersion(options?.binaryInfo)); } - if (!codexBinarySupportsConfigOverrides(options?.binaryInfo)) { - if (reasoningOverride || modelFlagNormalization.overrides.length > 0) { - throw buildConfigOverrideSupportError(hydrateCodexBinaryVersion(options?.binaryInfo)); - } + if (overrides.length === 0) { return modelFlagNormalization.args; } return [...buildConfigOverrideArgs(overrides), ...modelFlagNormalization.args]; diff --git a/tests/unit/bin/ccsxp-runtime.test.ts b/tests/unit/bin/ccsxp-runtime.test.ts index f853f114..b2a7a5a9 100644 --- a/tests/unit/bin/ccsxp-runtime.test.ts +++ b/tests/unit/bin/ccsxp-runtime.test.ts @@ -1,16 +1,18 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import * as os from 'os'; import * as path from 'path'; +import { CCSXP_CLIPROXY_SHORTCUT_ENV } from '../../../src/targets/codex-cliproxy-provider-config'; const wrapperPath = require.resolve('../../../src/bin/ccsxp-runtime.ts'); const ccsPath = require.resolve('../../../src/ccs.ts'); describe('ccsxp runtime wrapper', () => { const originalArgv = process.argv; - const originalEntryTarget = process.env.CCS_INTERNAL_ENTRY_TARGET; - const originalCodexHome = process.env.CODEX_HOME; - const originalCcsCodexProfile = process.env.CCS_CODEX_PROFILE; - const originalCcsxpCodexHome = process.env.CCSXP_CODEX_HOME; + const originalEntryTarget = process.env.CCS_INTERNAL_ENTRY_TARGET; + const originalShortcut = process.env[CCSXP_CLIPROXY_SHORTCUT_ENV]; + const originalCodexHome = process.env.CODEX_HOME; + const originalCcsCodexProfile = process.env.CCS_CODEX_PROFILE; + const originalCcsxpCodexHome = process.env.CCSXP_CODEX_HOME; beforeEach(() => { delete require.cache[wrapperPath]; @@ -25,6 +27,11 @@ describe('ccsxp runtime wrapper', () => { } else { process.env.CCS_INTERNAL_ENTRY_TARGET = originalEntryTarget; } + if (originalShortcut === undefined) { + delete process.env[CCSXP_CLIPROXY_SHORTCUT_ENV]; + } else { + process.env[CCSXP_CLIPROXY_SHORTCUT_ENV] = originalShortcut; + } if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { diff --git a/tests/unit/targets/codex-adapter.test.ts b/tests/unit/targets/codex-adapter.test.ts index 5a48af16..6d4927d9 100644 --- a/tests/unit/targets/codex-adapter.test.ts +++ b/tests/unit/targets/codex-adapter.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from 'bun:test'; import { CodexAdapter } from '../../../src/targets/codex-adapter'; +import { CCSXP_CLIPROXY_SHORTCUT_ENV } from '../../../src/targets/codex-cliproxy-provider-config'; import { buildCodexBrowserMcpOverrides, getCodexBrowserMcpServerName, @@ -97,6 +98,30 @@ describe('CodexAdapter', () => { ).toThrow(/does not advertise --config overrides/); }); + test('rejects ccsxp default launches when codex lacks config override support', () => { + const originalShortcut = process.env[CCSXP_CLIPROXY_SHORTCUT_ENV]; + process.env[CCSXP_CLIPROXY_SHORTCUT_ENV] = '1'; + try { + expect(() => + adapter.buildArgs('default', ['--config', 'model_provider="cliproxy"', '--version'], { + profileType: 'default', + binaryInfo: { + path: '/tmp/codex', + needsShell: false, + version: 'codex-cli 0.1.0', + features: [], + }, + }) + ).toThrow(/does not advertise --config overrides/); + } finally { + if (originalShortcut === undefined) { + delete process.env[CCSXP_CLIPROXY_SHORTCUT_ENV]; + } else { + process.env[CCSXP_CLIPROXY_SHORTCUT_ENV] = originalShortcut; + } + } + }); + test('injects transient config overrides for CCS-backed launches', () => { const runtimeConfigOverrides = buildCodexBrowserMcpOverrides(); const args = adapter.buildArgs('codex', ['--search'], { @@ -120,7 +145,9 @@ describe('CodexAdapter', () => { expect(args).toContain('model_provider="ccs_runtime"'); expect(args).toContain('model_providers.ccs_runtime.env_key="CCS_CODEX_API_KEY"'); expect(args).toContain('model="gpt-5.4"'); - expect(args).toContain(`mcp_servers.${getCodexBrowserMcpServerName()}.command=${JSON.stringify(process.platform === 'win32' ? 'npx.cmd' : 'npx')}`); + expect(args).toContain( + `mcp_servers.${getCodexBrowserMcpServerName()}.command=${JSON.stringify(process.platform === 'win32' ? 'npx.cmd' : 'npx')}` + ); expect(args).toContain('model_reasoning_effort="high"'); expect(args[args.length - 1]).toBe('--search'); }); diff --git a/tests/unit/targets/codex-runtime-integration.test.ts b/tests/unit/targets/codex-runtime-integration.test.ts index a14dfed4..75e28197 100644 --- a/tests/unit/targets/codex-runtime-integration.test.ts +++ b/tests/unit/targets/codex-runtime-integration.test.ts @@ -714,8 +714,10 @@ process.exit(0); expect(result.status).toBe(0); expect(result.stdout).toContain('codex-cli 9.9.9-test'); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', '--version'], + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + '--version', ]); }); @@ -737,19 +739,17 @@ process.exit(0); }); expect(result.status).toBe(0); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: path.join(tmpHome, '.codex'), - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CLIPROXY_API_KEY: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, - ]); + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: path.join(tmpHome, '.codex'), + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CLIPROXY_API_KEY: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); }); it('routes default ccsxp launches through native Codex with the cliproxy provider override', () => { @@ -768,24 +768,50 @@ process.exit(0); }); expect(result.status).toBe(0); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', 'fix failing tests'], + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + 'fix failing tests', ]); const codexConfig = fs.readFileSync(path.join(tmpHome, '.codex', 'config.toml'), 'utf8'); expect(codexConfig).toContain('[model_providers.cliproxy]'); expect(codexConfig).toContain('env_key = "CLIPROXY_API_KEY"'); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: path.join(tmpHome, '.codex'), - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CLIPROXY_API_KEY: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: path.join(tmpHome, '.codex'), + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CLIPROXY_API_KEY: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); + }); + + it('fails fast when ccsxp needs unsupported Codex config overrides', () => { + if (process.platform === 'win32') return; + + const result = runCcsxpAlias(['--version'], { + ...process.env, + CI: '1', + NO_COLOR: '1', + HOME: tmpHome, + CCS_HOME: tmpHome, + CCS_CODEX_PATH: fakeCodexPath, + CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, + CCS_TEST_CODEX_CONFIG_OVERRIDE_STATUS: 'unsupported', + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', + CCS_TEST_CODEX_HELP: ' -p, --profile \n', + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain('Codex CLI (codex-cli 9.9.9-test)'); + expect(result.stderr).toContain('does not advertise --config overrides'); + expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ + ['-c', 'model="gpt-5"', '--version'], + ['--help'], + ['--version'], ]); }); @@ -808,8 +834,10 @@ process.exit(0); }); expect(result.status).toBe(0); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', 'fix failing tests'], + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + 'fix failing tests', ]); const codexConfig = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8'); expect(codexConfig).toContain('model = "gpt-5.5"'); @@ -903,24 +931,24 @@ supports_websockets = false }); expect(result.status).toBe(0); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', 'fix failing tests'], + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + 'fix failing tests', ]); const codexConfig = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8'); expect(codexConfig).toContain('env_key = "CCS_CUSTOM_CLIPROXY_TOKEN"'); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: codexHome, - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CCS_CUSTOM_CLIPROXY_TOKEN: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, - ]); + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: codexHome, + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CCS_CUSTOM_CLIPROXY_TOKEN: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); }); it('preserves a custom cliproxy provider base_url for ccsxp launches', () => { @@ -954,27 +982,27 @@ supports_websockets = false }); expect(result.status).toBe(0); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', 'fix failing tests'], + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + 'fix failing tests', ]); const codexConfig = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8'); expect(codexConfig).toContain( 'base_url = "https://cliproxy.example.com/api/provider/codex/responses"' ); expect(codexConfig).toContain('env_key = "CCS_REMOTE_CLIPROXY_TOKEN"'); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: codexHome, - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CCS_REMOTE_CLIPROXY_TOKEN: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, - ]); + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: codexHome, + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CCS_REMOTE_CLIPROXY_TOKEN: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); }); it('keeps ccsxp native when the CCS default profile is a Claude account', () => { @@ -1005,22 +1033,22 @@ supports_websockets = false }); expect(result.status).toBe(0); - expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ - ['--config', 'model_provider="cliproxy"', 'fix failing tests'], - ]); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: path.join(tmpHome, '.codex'), - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CLIPROXY_API_KEY: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, + expect(readLoggedCodexCalls(codexArgsLogPath).at(-1)).toEqual([ + '--config', + 'model_provider="cliproxy"', + 'fix failing tests', ]); + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: path.join(tmpHome, '.codex'), + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CLIPROXY_API_KEY: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); }); it('keeps implicit ccs --target codex launches native when the CCS default is a Claude account', () => { @@ -1045,6 +1073,7 @@ supports_websockets = false HOME: tmpHome, CCS_HOME: tmpHome, CCS_CODEX_PATH: fakeCodexPath, + CODEX_HOME: undefined, CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath, CCS_THINKING: '8192', @@ -1135,7 +1164,9 @@ supports_websockets = false }); expect(result.status).toBe(0); - expect(result.stderr).not.toContain('Codex CLI does not support Claude account-based profiles.'); + expect(result.stderr).not.toContain( + 'Codex CLI does not support Claude account-based profiles.' + ); expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['fix failing tests']]); expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ { @@ -1205,19 +1236,17 @@ supports_websockets = false }); expect(result.status).toBe(0); - expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ - { - CODEX_HOME: explicitCodexHome, - CODEX_CI: undefined, - CODEX_MANAGED_BY_BUN: undefined, - CODEX_THREAD_ID: undefined, - ANTHROPIC_BASE_URL: undefined, - CLIPROXY_API_KEY: 'ccs-internal-managed', - CCS_BROWSER_USER_DATA_DIR: undefined, - CCS_BROWSER_PROFILE_DIR: undefined, - CCS_BROWSER_DEVTOOLS_WS_URL: undefined, - }, - ]); + expect(readLoggedCodexEnv(codexEnvLogPath).at(-1)).toEqual({ + CODEX_HOME: explicitCodexHome, + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CLIPROXY_API_KEY: 'ccs-internal-managed', + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }); }); it('fails with a clean CLI error when ccsxp receives a malformed --target flag', () => {