fix(codex): normalize native cliproxy tuning aliases (#1254)

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-15 09:51:42 -04:00
committed by GitHub
parent 53730c0165
commit 609bed0c39
5 changed files with 174 additions and 14 deletions
@@ -12,6 +12,7 @@ import {
normalizeModelIdForProvider,
normalizeModelIdForRouting,
normalizeModelEnvVarsForProvider,
parseCodexModelTuningAlias,
} from '../model-id-normalizer';
describe('model-id-normalizer', () => {
@@ -135,6 +136,20 @@ describe('model-id-normalizer', () => {
'gpt-5.4-high-fast'
);
});
it('parses codex model tuning suffixes', () => {
expect(parseCodexModelTuningAlias('gpt-5.5-high')).toEqual({
baseModel: 'gpt-5.5',
effort: 'high',
serviceTier: null,
});
expect(parseCodexModelTuningAlias('gpt-5.5-fast-high')).toEqual({
baseModel: 'gpt-5.5',
effort: 'high',
serviceTier: 'fast',
});
expect(parseCodexModelTuningAlias('gpt-5.5[1m]')).toBeNull();
});
});
describe('env normalization', () => {
@@ -45,6 +45,15 @@ const IFLOW_LEGACY_MODEL_ALIASES: Readonly<Record<string, string>> = Object.free
'minimax-m2.5': 'qwen3-coder-plus',
});
export type CodexModelTuningEffort = 'medium' | 'high' | 'xhigh';
export type CodexModelServiceTier = 'fast';
export interface CodexModelTuningAlias {
baseModel: string;
effort: CodexModelTuningEffort | null;
serviceTier: CodexModelServiceTier | null;
}
function trimModelId(model: string): string {
return model.trim();
}
@@ -93,6 +102,23 @@ function splitCodexTuningSuffix(model: string): { baseModel: string; suffix: str
};
}
export function parseCodexModelTuningAlias(model: string): CodexModelTuningAlias | null {
const trimmed = trimModelId(model);
const { baseModel, suffix } = splitBaseModelAndSuffix(trimmed);
if (suffix) return null;
const parsed = splitCodexTuningSuffix(baseModel);
if (!parsed.suffix) return null;
const tokens = parsed.suffix.slice(1).split('-');
const effort = tokens.find((token) => token !== 'fast') as CodexModelTuningEffort | undefined;
return {
baseModel: parsed.baseModel.trim(),
effort: effort ?? null,
serviceTier: tokens.includes('fast') ? 'fast' : null,
};
}
/**
* Extract provider segment from `/api/provider/{provider}` request paths.
*
+42 -14
View File
@@ -6,11 +6,14 @@ import {
stringifyTomlObject,
writeTomlFileAtomic,
} from '../web-server/services/compatible-cli-toml-file-service';
import { getModelMaxLevel } from '../cliproxy/model-catalog';
import { parseCodexModelTuningAlias } from '../cliproxy/ai-providers/model-id-normalizer';
export const CCSXP_CLIPROXY_SHORTCUT_ENV = 'CCSXP_CLIPROXY_SHORTCUT';
export const CODEX_CLIPROXY_PROVIDER_ID = 'cliproxy';
export const CODEX_CLIPROXY_PROVIDER_ENV_KEY = 'CLIPROXY_API_KEY';
export const CODEX_CLIPROXY_PROVIDER_NAME = 'CLIProxy Codex';
const CODEX_FAST_SERVICE_TIER = 'priority';
export interface CodexCliproxyProviderRepairResult {
changed: boolean;
@@ -99,18 +102,28 @@ function buildProviderConfig(baseUrl: string, envKey: string): Record<string, un
};
}
function buildProviderBlock(baseUrl: string): string {
return stringifyTomlObject({
function appendProviderBlock(rawText: string, baseUrl: string): string {
const prefix = rawText.trimEnd();
const providerBlock = stringifyTomlObject({
model_providers: {
[CODEX_CLIPROXY_PROVIDER_ID]: buildProviderConfig(baseUrl, CODEX_CLIPROXY_PROVIDER_ENV_KEY),
},
});
}).trimEnd();
return prefix ? `${prefix}\n\n${providerBlock}\n` : `${providerBlock}\n`;
}
function appendProviderBlock(rawText: string, baseUrl: string): string {
const prefix = rawText.trimEnd();
const providerBlock = buildProviderBlock(baseUrl).trimEnd();
return prefix ? `${prefix}\n\n${providerBlock}\n` : `${providerBlock}\n`;
function normalizeTopLevelCodexModelAlias(config: Record<string, unknown>): boolean {
const model = config.model;
if (typeof model !== 'string') return false;
const parsed = parseCodexModelTuningAlias(model);
if (!parsed || (!parsed.effort && !parsed.serviceTier)) return false;
if (!parsed.baseModel || getModelMaxLevel('codex', parsed.baseModel) === undefined) return false;
config.model = parsed.baseModel;
if (parsed.effort) config.model_reasoning_effort = parsed.effort;
if (parsed.serviceTier) config.service_tier = CODEX_FAST_SERVICE_TIER;
return true;
}
export async function ensureCodexCliproxyProviderConfig(
@@ -131,6 +144,7 @@ export async function ensureCodexCliproxyProviderConfig(
const modelProvidersValue = config.model_providers;
const providers = asObject(modelProvidersValue);
const expectedBaseUrl = buildCodexCliproxyProviderBaseUrl(port);
const normalizedModelAlias = normalizeTopLevelCodexModelAlias(config);
if (modelProvidersValue !== undefined && !providers) {
throw new Error(`Cannot repair ${displayPath}: [model_providers] must be a table.`);
@@ -139,7 +153,18 @@ export async function ensureCodexCliproxyProviderConfig(
if (!providers || !Object.prototype.hasOwnProperty.call(providers, CODEX_CLIPROXY_PROVIDER_ID)) {
await writeTomlFileAtomic({
filePath: configPath,
rawText: appendProviderBlock(fileProbe.rawText, expectedBaseUrl),
rawText: normalizedModelAlias
? stringifyTomlObject({
...config,
model_providers: {
...(providers ?? {}),
[CODEX_CLIPROXY_PROVIDER_ID]: buildProviderConfig(
expectedBaseUrl,
CODEX_CLIPROXY_PROVIDER_ENV_KEY
),
},
})
: appendProviderBlock(fileProbe.rawText, expectedBaseUrl),
expectedMtime: fileProbe.diagnostics.mtimeMs ?? undefined,
fileLabel: 'config.toml',
});
@@ -154,15 +179,18 @@ export async function ensureCodexCliproxyProviderConfig(
}
const envKey = resolveProviderEnvKey(currentProvider);
const providerReady = isProviderReady(currentProvider, expectedBaseUrl, envKey);
if (isProviderReady(currentProvider, expectedBaseUrl, envKey)) {
return { changed: false, configPath, displayPath, envKey };
if (!providerReady) {
providers[CODEX_CLIPROXY_PROVIDER_ID] = {
...currentProvider,
...buildProviderConfig(expectedBaseUrl, envKey),
};
}
providers[CODEX_CLIPROXY_PROVIDER_ID] = {
...currentProvider,
...buildProviderConfig(expectedBaseUrl, envKey),
};
if (providerReady && !normalizedModelAlias) {
return { changed: false, configPath, displayPath, envKey };
}
await writeTomlFileAtomic({
filePath: configPath,
@@ -136,4 +136,65 @@ supports_websockets = false
expect(result.envKey).toBe('CLIPROXY_API_KEY');
expect(fs.readFileSync(configPath, 'utf8')).toBe(rawText);
});
it('normalizes a ready native Codex tuning alias before requests reach cliproxy', async () => {
fs.mkdirSync(codexHome, { recursive: true });
fs.writeFileSync(
configPath,
`model = "gpt-5.5-high-fast"
[model_providers.cliproxy]
name = "CLIProxy Codex"
base_url = "http://localhost:8317/api/provider/codex"
env_key = "CLIPROXY_API_KEY"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false
`,
'utf8'
);
const result = await ensureCodexCliproxyProviderConfig(8317, env);
expect(result.changed).toBe(true);
const rawText = fs.readFileSync(configPath, 'utf8');
expect(rawText).toContain('model = "gpt-5.5"');
expect(rawText).toContain('model_reasoning_effort = "high"');
expect(rawText).toContain('service_tier = "priority"');
expect(rawText).toContain('[model_providers.cliproxy]');
expect(rawText).not.toContain('gpt-5.5-high-fast');
});
it('normalizes a native Codex effort alias when adding the missing provider', async () => {
fs.mkdirSync(codexHome, { recursive: true });
fs.writeFileSync(configPath, 'model = "gpt-5.5-xhigh"\n', 'utf8');
const result = await ensureCodexCliproxyProviderConfig(8317, env);
expect(result.changed).toBe(true);
const rawText = fs.readFileSync(configPath, 'utf8');
expect(rawText).toContain('model = "gpt-5.5"');
expect(rawText).toContain('model_reasoning_effort = "xhigh"');
expect(rawText).toContain('[model_providers.cliproxy]');
});
it('does not strip unknown top-level models that happen to end with effort tokens', async () => {
fs.mkdirSync(codexHome, { recursive: true });
const rawText = `model = "literal-high"
[model_providers.cliproxy]
name = "CLIProxy Codex"
base_url = "http://localhost:8317/api/provider/codex"
env_key = "CLIPROXY_API_KEY"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false
`;
fs.writeFileSync(configPath, rawText, 'utf8');
const result = await ensureCodexCliproxyProviderConfig(8317, env);
expect(result.changed).toBe(false);
expect(fs.readFileSync(configPath, 'utf8')).toBe(rawText);
});
});
@@ -747,6 +747,36 @@ process.exit(0);
]);
});
it('normalizes ccsxp native Codex tuning aliases in config.toml', () => {
if (process.platform === 'win32') return;
const codexHome = path.join(tmpHome, '.codex');
fs.mkdirSync(codexHome, { recursive: true });
fs.writeFileSync(path.join(codexHome, 'config.toml'), 'model = "gpt-5.5-high-fast"\n');
const result = runCcsxpAlias(['fix failing tests'], {
...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_ENV_OUT: codexEnvLogPath,
});
expect(result.status).toBe(0);
expect(readLoggedCodexCalls(codexArgsLogPath)).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"');
expect(codexConfig).toContain('model_reasoning_effort = "high"');
expect(codexConfig).toContain('service_tier = "priority"');
expect(codexConfig).toContain('[model_providers.cliproxy]');
expect(codexConfig).not.toContain('gpt-5.5-high-fast');
});
it('loads the configured cliproxy provider env_key for ccsxp launches', () => {
if (process.platform === 'win32') return;