From 3a26abac71e36d6f1d95333a47feb3c3cd08cce5 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Mon, 2 Mar 2026 21:19:29 +0700 Subject: [PATCH] fix(agy): harden self-migration across settings load/update paths --- src/cliproxy/config/env-builder.ts | 11 +++++++ src/cliproxy/model-config.ts | 22 +++++++------ src/cliproxy/services/variant-settings.ts | 7 ++-- .../cliproxy/env-builder-provider-url.test.ts | 33 +++++++++++++++++++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/cliproxy/config/env-builder.ts b/src/cliproxy/config/env-builder.ts index 15f03616..de906406 100644 --- a/src/cliproxy/config/env-builder.ts +++ b/src/cliproxy/config/env-builder.ts @@ -559,6 +559,17 @@ export function ensureProviderSettings(provider: CLIProxyProvider): void { } } + // Canonicalize provider-specific model aliases (e.g., AGY Sonnet 4.6 thinking legacy IDs). + for (const key of MODEL_ENV_VAR_KEYS) { + const current = mergedEnv[key]; + if (typeof current !== 'string' || current.trim().length === 0) continue; + const canonical = normalizeModelIdForProvider(current, provider); + if (canonical !== current) { + mergedEnv[key] = canonical; + mutated = true; + } + } + if (!mutated) { return; } diff --git a/src/cliproxy/model-config.ts b/src/cliproxy/model-config.ts index cb1ddb34..0400a54a 100644 --- a/src/cliproxy/model-config.ts +++ b/src/cliproxy/model-config.ts @@ -13,6 +13,7 @@ import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator'; import { CLIProxyProvider } from './types'; import { initUI, color, bold, dim, ok, info, header } from '../utils/ui'; import { getCcsDir } from '../utils/config-manager'; +import { normalizeModelIdForProvider } from './model-id-normalizer'; const CODEX_EFFORT_SUFFIX_REGEX = /-(xhigh|high|medium)$/i; @@ -21,9 +22,9 @@ function stripCodexEffortSuffix(model: string, provider: CLIProxyProvider): stri return model.replace(CODEX_EFFORT_SUFFIX_REGEX, ''); } -function normalizeCodexTierModel(provider: CLIProxyProvider, model: string): string { - if (provider !== 'codex') return model; - return stripCodexEffortSuffix(model, provider); +function canonicalizeModelForProvider(provider: CLIProxyProvider, model: string): string { + const withoutCodexSuffix = stripCodexEffortSuffix(model, provider); + return normalizeModelIdForProvider(withoutCodexSuffix, provider); } /** @@ -50,7 +51,8 @@ export function getCurrentModel( try { const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); - return settings.env?.ANTHROPIC_MODEL; + const model = settings.env?.ANTHROPIC_MODEL; + return typeof model === 'string' ? canonicalizeModelForProvider(provider, model) : model; } catch { return undefined; } @@ -134,7 +136,7 @@ export async function configureProviderModel( // Find default index - use current model if configured, otherwise catalog default const currentModel = getCurrentModel(provider, customSettingsPath); const targetModel = currentModel - ? stripCodexEffortSuffix(currentModel, provider) + ? canonicalizeModelForProvider(provider, currentModel) : catalog.defaultModel; const defaultIdx = catalog.models.findIndex((m) => m.id === targetModel); const safeDefaultIdx = defaultIdx >= 0 ? defaultIdx : 0; @@ -157,10 +159,10 @@ export async function configureProviderModel( // Get base env vars for defaults const baseEnv = getClaudeEnvVars(provider); - const selectedDefaultModel = normalizeCodexTierModel(provider, selectedModel); - const selectedOpusModel = normalizeCodexTierModel(provider, selectedModel); - const selectedSonnetModel = normalizeCodexTierModel(provider, selectedModel); - const selectedHaikuModel = normalizeCodexTierModel( + const selectedDefaultModel = canonicalizeModelForProvider(provider, selectedModel); + const selectedOpusModel = canonicalizeModelForProvider(provider, selectedModel); + const selectedSonnetModel = canonicalizeModelForProvider(provider, selectedModel); + const selectedHaikuModel = canonicalizeModelForProvider( provider, baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || selectedModel ); @@ -251,7 +253,7 @@ export async function showCurrentConfig(provider: CLIProxyProvider): Promise { expect(repaired.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBeDefined(); }); + it('migrates deprecated agy sonnet 4.6 thinking IDs during ensureProviderSettings', () => { + process.env.CCS_HOME = tempHome; + const agySettingsPath = path.join(tempHome, '.ccs', 'agy.settings.json'); + fs.mkdirSync(path.dirname(agySettingsPath), { recursive: true }); + fs.writeFileSync( + agySettingsPath, + JSON.stringify( + { + env: { + ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/agy', + ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', + ANTHROPIC_MODEL: 'claude-sonnet-4-6-thinking(8192)', + ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6-thinking', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4.6-thinking', + ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-5', + }, + }, + null, + 2 + ) + ); + + ensureProviderSettings('agy'); + + const repaired = JSON.parse(fs.readFileSync(agySettingsPath, 'utf-8')) as { + env?: Record; + }; + expect(repaired.env?.ANTHROPIC_MODEL).toBe('claude-sonnet-4-6(8192)'); + expect(repaired.env?.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('claude-opus-4-6-thinking'); + expect(repaired.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('claude-sonnet-4-6'); + expect(repaired.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('claude-sonnet-4-5'); + }); + it('recovers malformed provider settings files by writing defaults and backup copy', () => { process.env.CCS_HOME = tempHome; const agySettingsPath = path.join(tempHome, '.ccs', 'agy.settings.json');