fix(agy): harden preset canonicalization and migration coverage

This commit is contained in:
Tam Nhu Tran committed 2026-03-03 00:39:16 +07:00
1 parent 8ddd3596e9
commit 747c706369
9 files changed
+338 -26

No files matched your search

@@ -800,5 +800,35 @@ oauth-model-alias:
'Should remove deprecated gemini-claude alias after regeneration'
);
});
it('normalizes dotted deprecated sonnet thinking aliases during regeneration', () => {
const cliproxyDir = path.join(testDir, '.ccs', 'cliproxy');
fs.mkdirSync(cliproxyDir, { recursive: true });
const initialConfig = `# CLIProxyAPI config generated by CCS v11
port: 8317
api-keys:
- "ccs-internal-managed"
auth-dir: "${cliproxyDir.replace(/\\/g, '/')}/auth"
oauth-model-alias:
antigravity:
- name: claude-sonnet-4-6-thinking
alias: claude-sonnet-4.6-thinking
fork: true
`;
fs.writeFileSync(path.join(cliproxyDir, 'config.yaml'), initialConfig);
regenerateConfig();
const newConfig = fs.readFileSync(path.join(cliproxyDir, 'config.yaml'), 'utf-8');
assert(
newConfig.includes('alias: claude-sonnet-4-6'),
'Should migrate dotted deprecated sonnet thinking alias to canonical model ID'
);
assert(
!newConfig.includes('alias: claude-sonnet-4.6-thinking'),
'Should remove dotted deprecated sonnet thinking alias after regeneration'
);
});
});
});
@@ -245,6 +245,15 @@ describe('getEffectiveEnvVars local provider URL normalization', () => {
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4.6-thinking',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-5',
},
presets: [
{
name: 'legacy',
default: 'claude-sonnet-4-6-thinking',
opus: 'claude-opus-4.6-thinking',
sonnet: 'claude-sonnet-4.6-thinking',
haiku: 'claude-sonnet-4-5',
},
],
},
null,
2
@@ -255,11 +264,63 @@ describe('getEffectiveEnvVars local provider URL normalization', () => {
const repaired = JSON.parse(fs.readFileSync(agySettingsPath, 'utf-8')) as {
env?: Record<string, string>;
presets?: Array<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');
expect(repaired.presets?.[0]?.default).toBe('claude-sonnet-4-6');
expect(repaired.presets?.[0]?.opus).toBe('claude-opus-4-6-thinking');
expect(repaired.presets?.[0]?.sonnet).toBe('claude-sonnet-4-6');
expect(repaired.presets?.[0]?.haiku).toBe('claude-sonnet-4-5');
});
it('migrates codex effort-suffixed preset IDs during ensureProviderSettings', () => {
process.env.CCS_HOME = tempHome;
const codexSettingsPath = path.join(tempHome, '.ccs', 'codex.settings.json');
fs.mkdirSync(path.dirname(codexSettingsPath), { recursive: true });
fs.writeFileSync(
codexSettingsPath,
JSON.stringify(
{
env: {
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/codex',
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
ANTHROPIC_MODEL: ' gpt-5.3-codex-xhigh ',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex-xhigh',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.3-codex-high',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5-mini-medium',
},
presets: [
{
name: 'legacy',
default: 'gpt-5.3-codex-xhigh',
opus: 'gpt-5.3-codex-xhigh',
sonnet: 'gpt-5.3-codex-high',
haiku: 'gpt-5-mini-medium',
},
],
},
null,
2
)
);
ensureProviderSettings('codex');
const repaired = JSON.parse(fs.readFileSync(codexSettingsPath, 'utf-8')) as {
env?: Record<string, string>;
presets?: Array<Record<string, string>>;
};
expect(repaired.env?.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env?.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex');
expect(repaired.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5-mini');
expect(repaired.presets?.[0]?.default).toBe('gpt-5.3-codex');
expect(repaired.presets?.[0]?.opus).toBe('gpt-5.3-codex');
expect(repaired.presets?.[0]?.sonnet).toBe('gpt-5.3-codex');
expect(repaired.presets?.[0]?.haiku).toBe('gpt-5-mini');
});
it('recovers malformed provider settings files by writing defaults and backup copy', () => {
@@ -14,4 +14,12 @@ describe('model-catalog compatibility lookups', () => {
expect(supportsThinking('agy', 'claude-opus-4.6-thinking')).toBe(true);
expect(supportsThinking('agy', 'claude-sonnet-4.5')).toBe(false);
});
it('maps legacy sonnet 4.6 thinking aliases to canonical agy model', () => {
const dottedLegacy = findModel('agy', 'claude-sonnet-4.6-thinking');
const hyphenLegacy = findModel('agy', 'claude-sonnet-4-6-thinking');
expect(dottedLegacy?.id).toBe('claude-sonnet-4-6');
expect(hyphenLegacy?.id).toBe('claude-sonnet-4-6');
});
});
@@ -78,6 +78,21 @@ describe('model-id-normalizer', () => {
'claude-sonnet-4-6-thinking'
);
});
it('trims and canonicalizes provider model IDs with surrounding whitespace', () => {
expect(canonicalizeModelIdForProvider(' gpt-5.3-codex-high ', 'codex')).toBe(
'gpt-5.3-codex'
);
expect(canonicalizeModelIdForProvider(' claude-sonnet-4.6-thinking ', 'agy')).toBe(
'claude-sonnet-4-6'
);
expect(canonicalizeModelIdForProvider(' claude-sonnet-4-6-thinking ', 'claude')).toBe(
'claude-sonnet-4-6-thinking'
);
expect(normalizeModelIdForRouting(' claude-sonnet-4.6-thinking ', null)).toBe(
'claude-sonnet-4-6'
);
});
});
describe('env normalization', () => {