mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 12:19:35 +00:00
fix(agy): harden self-migration across settings load/update paths
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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('');
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user