mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(cliproxy): normalize codex effort aliases without reasoning proxy
This commit is contained in:
@@ -24,7 +24,7 @@ import { stripClaudeCodeEnv } from '../../utils/shell-executor';
|
||||
import { CodexReasoningProxy } from '../codex-reasoning-proxy';
|
||||
import { ToolSanitizationProxy } from '../tool-sanitization-proxy';
|
||||
import { HttpsTunnelProxy } from '../https-tunnel-proxy';
|
||||
import { normalizeModelIdForProvider } from '../model-id-normalizer';
|
||||
import { MODEL_ENV_VAR_KEYS, normalizeModelIdForProvider } from '../model-id-normalizer';
|
||||
|
||||
export interface RemoteProxyConfig {
|
||||
host: string;
|
||||
@@ -61,6 +61,38 @@ export interface ProxyChainConfig {
|
||||
compositeDefaultTier?: 'opus' | 'sonnet' | 'haiku';
|
||||
}
|
||||
|
||||
const CODEX_EFFORT_SUFFIX_REGEX = /^(.*)-(xhigh|high|medium)$/i;
|
||||
const EXTENDED_CONTEXT_SUFFIX_REGEX = /\[1m\]$/i;
|
||||
|
||||
function normalizeCodexModelForDirectUpstream(model: string): string {
|
||||
const withoutExtendedContext = model.trim().replace(EXTENDED_CONTEXT_SUFFIX_REGEX, '').trim();
|
||||
if (!withoutExtendedContext) return withoutExtendedContext;
|
||||
|
||||
const effortMatch = withoutExtendedContext.match(CODEX_EFFORT_SUFFIX_REGEX);
|
||||
if (!effortMatch?.[1] || !effortMatch[2]) {
|
||||
return withoutExtendedContext;
|
||||
}
|
||||
|
||||
return `${effortMatch[1].trim()}(${effortMatch[2].toLowerCase()})`;
|
||||
}
|
||||
|
||||
function normalizeCodexEnvForDirectUpstream(envVars: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
let nextEnv: NodeJS.ProcessEnv | null = null;
|
||||
|
||||
for (const key of MODEL_ENV_VAR_KEYS) {
|
||||
const value = envVars[key];
|
||||
if (typeof value !== 'string' || value.trim().length === 0) continue;
|
||||
|
||||
const normalizedValue = normalizeCodexModelForDirectUpstream(value);
|
||||
if (normalizedValue === value) continue;
|
||||
|
||||
if (!nextEnv) nextEnv = { ...envVars };
|
||||
nextEnv[key] = normalizedValue;
|
||||
}
|
||||
|
||||
return nextEnv ?? envVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build final environment variables for Claude CLI execution
|
||||
* Handles proxy chain ordering and integration with hooks
|
||||
@@ -190,6 +222,14 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
// Auto-enabled for Gemini, opt-in for Claude (--1m flag)
|
||||
applyExtendedContextConfig(envVars, provider, extendedContextOverride);
|
||||
|
||||
// Fallback compatibility for Codex:
|
||||
// CLIProxyAPI provider lookup recognizes model(level) suffix form, while CCS stores codex
|
||||
// effort aliases as model-level (e.g., gpt-5.3-codex-high). If codex reasoning proxy is
|
||||
// unavailable, normalize to model(level) to avoid "unknown provider for model ..." failures.
|
||||
if (provider === 'codex' && !isComposite && !codexReasoningPort) {
|
||||
envVars = normalizeCodexEnvForDirectUpstream(envVars);
|
||||
}
|
||||
|
||||
// Determine the final ANTHROPIC_BASE_URL based on active proxies
|
||||
// Chain order: Claude CLI → [CodexReasoningProxy] → [ToolSanitizationProxy] → CLIProxy
|
||||
let finalBaseUrl = envVars.ANTHROPIC_BASE_URL;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { afterEach, describe, expect, it } from 'bun:test';
|
||||
import { buildClaudeEnvironment } from '../../../src/cliproxy/executor/env-resolver';
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createCodexSettingsFile(models: {
|
||||
defaultModel: string;
|
||||
opusModel: string;
|
||||
sonnetModel: string;
|
||||
haikuModel: string;
|
||||
}): string {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-codex-fallback-'));
|
||||
tempDirs.push(tempDir);
|
||||
|
||||
const settingsPath = path.join(tempDir, 'codex-test.settings.json');
|
||||
fs.writeFileSync(
|
||||
settingsPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/codex',
|
||||
ANTHROPIC_AUTH_TOKEN: 'test-token',
|
||||
ANTHROPIC_MODEL: models.defaultModel,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: models.opusModel,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: models.sonnetModel,
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: models.haikuModel,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
) + '\n'
|
||||
);
|
||||
|
||||
return settingsPath;
|
||||
}
|
||||
|
||||
describe('buildClaudeEnvironment codex fallback normalization', () => {
|
||||
afterEach(() => {
|
||||
while (tempDirs.length > 0) {
|
||||
const tempDir = tempDirs.pop();
|
||||
if (tempDir) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes codex effort aliases when reasoning proxy is unavailable', () => {
|
||||
const settingsPath = createCodexSettingsFile({
|
||||
defaultModel: 'gpt-5.3-codex-high',
|
||||
opusModel: 'gpt-5.3-codex-xhigh',
|
||||
sonnetModel: 'gpt-5.3-codex-high',
|
||||
haikuModel: 'gpt-5-mini-medium',
|
||||
});
|
||||
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
customSettingsPath: settingsPath,
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex(high)');
|
||||
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex(xhigh)');
|
||||
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex(high)');
|
||||
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5-mini(medium)');
|
||||
});
|
||||
|
||||
it('keeps codex effort aliases when reasoning proxy is active', () => {
|
||||
const settingsPath = createCodexSettingsFile({
|
||||
defaultModel: 'gpt-5.3-codex-high',
|
||||
opusModel: 'gpt-5.3-codex-xhigh',
|
||||
sonnetModel: 'gpt-5.3-codex-high',
|
||||
haikuModel: 'gpt-5-mini-medium',
|
||||
});
|
||||
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
customSettingsPath: settingsPath,
|
||||
codexReasoningPort: 9444,
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex-high');
|
||||
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex-xhigh');
|
||||
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex-high');
|
||||
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5-mini-medium');
|
||||
expect(env.ANTHROPIC_BASE_URL).toBe('http://127.0.0.1:9444/api/provider/codex');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user