fix(agy): harden self-migration across settings load/update paths

This commit is contained in:
Tam Nhu Tran
2026-03-02 21:19:29 +07:00
parent 8a2b55449b
commit 3a26abac71
4 changed files with 61 additions and 12 deletions
+11
View File
@@ -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) { if (!mutated) {
return; return;
} }
+12 -10
View File
@@ -13,6 +13,7 @@ import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
import { CLIProxyProvider } from './types'; import { CLIProxyProvider } from './types';
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui'; import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
import { getCcsDir } from '../utils/config-manager'; import { getCcsDir } from '../utils/config-manager';
import { normalizeModelIdForProvider } from './model-id-normalizer';
const CODEX_EFFORT_SUFFIX_REGEX = /-(xhigh|high|medium)$/i; 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, ''); return model.replace(CODEX_EFFORT_SUFFIX_REGEX, '');
} }
function normalizeCodexTierModel(provider: CLIProxyProvider, model: string): string { function canonicalizeModelForProvider(provider: CLIProxyProvider, model: string): string {
if (provider !== 'codex') return model; const withoutCodexSuffix = stripCodexEffortSuffix(model, provider);
return stripCodexEffortSuffix(model, provider); return normalizeModelIdForProvider(withoutCodexSuffix, provider);
} }
/** /**
@@ -50,7 +51,8 @@ export function getCurrentModel(
try { try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); 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 { } catch {
return undefined; return undefined;
} }
@@ -134,7 +136,7 @@ export async function configureProviderModel(
// Find default index - use current model if configured, otherwise catalog default // Find default index - use current model if configured, otherwise catalog default
const currentModel = getCurrentModel(provider, customSettingsPath); const currentModel = getCurrentModel(provider, customSettingsPath);
const targetModel = currentModel const targetModel = currentModel
? stripCodexEffortSuffix(currentModel, provider) ? canonicalizeModelForProvider(provider, currentModel)
: catalog.defaultModel; : catalog.defaultModel;
const defaultIdx = catalog.models.findIndex((m) => m.id === targetModel); const defaultIdx = catalog.models.findIndex((m) => m.id === targetModel);
const safeDefaultIdx = defaultIdx >= 0 ? defaultIdx : 0; const safeDefaultIdx = defaultIdx >= 0 ? defaultIdx : 0;
@@ -157,10 +159,10 @@ export async function configureProviderModel(
// Get base env vars for defaults // Get base env vars for defaults
const baseEnv = getClaudeEnvVars(provider); const baseEnv = getClaudeEnvVars(provider);
const selectedDefaultModel = normalizeCodexTierModel(provider, selectedModel); const selectedDefaultModel = canonicalizeModelForProvider(provider, selectedModel);
const selectedOpusModel = normalizeCodexTierModel(provider, selectedModel); const selectedOpusModel = canonicalizeModelForProvider(provider, selectedModel);
const selectedSonnetModel = normalizeCodexTierModel(provider, selectedModel); const selectedSonnetModel = canonicalizeModelForProvider(provider, selectedModel);
const selectedHaikuModel = normalizeCodexTierModel( const selectedHaikuModel = canonicalizeModelForProvider(
provider, provider,
baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || selectedModel baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || selectedModel
); );
@@ -251,7 +253,7 @@ export async function showCurrentConfig(provider: CLIProxyProvider): Promise<voi
const currentModel = getCurrentModel(provider); const currentModel = getCurrentModel(provider);
const settingsPath = getProviderSettingsPath(provider); const settingsPath = getProviderSettingsPath(provider);
const normalizedCurrentModel = currentModel const normalizedCurrentModel = currentModel
? stripCodexEffortSuffix(currentModel, provider) ? canonicalizeModelForProvider(provider, currentModel)
: undefined; : undefined;
console.error(''); console.error('');
+5 -2
View File
@@ -18,6 +18,7 @@ import { ensureProfileHooks } from '../../utils/websearch/profile-hook-injector'
import { ensureProfileHooks as ensureImageAnalyzerHooks } from '../../utils/hooks/image-analyzer-profile-hook-injector'; import { ensureProfileHooks as ensureImageAnalyzerHooks } from '../../utils/hooks/image-analyzer-profile-hook-injector';
import { getEffectiveApiKey } from '../auth-token-manager'; import { getEffectiveApiKey } from '../auth-token-manager';
import { warn } from '../../utils/ui'; import { warn } from '../../utils/ui';
import { normalizeModelIdForProvider } from '../model-id-normalizer';
/** Environment settings structure */ /** Environment settings structure */
interface SettingsEnv { interface SettingsEnv {
@@ -41,8 +42,10 @@ function canonicalizeModelForProvider(
provider: CLIProxyProfileName | undefined, provider: CLIProxyProfileName | undefined,
model: string model: string
): string { ): string {
if (provider !== 'codex') return model; const withoutCodexSuffix =
return model.replace(CODEX_EFFORT_SUFFIX_REGEX, ''); provider === 'codex' ? model.replace(CODEX_EFFORT_SUFFIX_REGEX, '') : model;
if (!provider) return withoutCodexSuffix;
return normalizeModelIdForProvider(withoutCodexSuffix, provider);
} }
/** /**
@@ -229,6 +229,39 @@ describe('getEffectiveEnvVars local provider URL normalization', () => {
expect(repaired.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBeDefined(); 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<string, string>;
};
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', () => { it('recovers malformed provider settings files by writing defaults and backup copy', () => {
process.env.CCS_HOME = tempHome; process.env.CCS_HOME = tempHome;
const agySettingsPath = path.join(tempHome, '.ccs', 'agy.settings.json'); const agySettingsPath = path.join(tempHome, '.ccs', 'agy.settings.json');