mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
fix(iflow): replace placeholder model defaults to prevent 406
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/iflow",
|
||||
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
|
||||
"ANTHROPIC_MODEL": "deepseek-v3.2",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "kimi-k2-thinking",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v3.2",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "minimax-m2"
|
||||
"ANTHROPIC_MODEL": "qwen3-coder-plus",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3-coder-plus",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3-coder-plus",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3-coder-plus"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ const DEPRECATED_MODEL_PREFIX = 'gemini-claude-';
|
||||
/** Replacement prefix matching actual upstream model names */
|
||||
const UPSTREAM_MODEL_PREFIX = 'claude-';
|
||||
const CODEX_EFFORT_SUFFIX_REGEX = /-(xhigh|high|medium)$/i;
|
||||
const IFLOW_PLACEHOLDER_MODEL = 'iflow-default';
|
||||
const IFLOW_DEFAULT_MODEL = 'qwen3-coder-plus';
|
||||
const PRESET_MODEL_KEYS = ['default', 'opus', 'sonnet', 'haiku'] as const;
|
||||
const REQUIRED_PROVIDER_ENV_KEYS = [
|
||||
'ANTHROPIC_BASE_URL',
|
||||
@@ -136,6 +138,61 @@ function migrateCodexEffortSuffixes(
|
||||
return migrated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate legacy iFlow placeholder model IDs to a real default model.
|
||||
* Example: iflow-default -> qwen3-coder-plus
|
||||
*/
|
||||
function migrateIFlowPlaceholderModel(
|
||||
settingsPath: string,
|
||||
provider: CLIProxyProvider,
|
||||
settings: ProviderSettings
|
||||
): boolean {
|
||||
if (provider !== 'iflow') return false;
|
||||
if (!settings.env || typeof settings.env !== 'object') return false;
|
||||
|
||||
let migrated = false;
|
||||
const normalize = (value: string): string => value.trim().toLowerCase();
|
||||
const replaceIfPlaceholder = (value: string): string =>
|
||||
normalize(value) === IFLOW_PLACEHOLDER_MODEL ? IFLOW_DEFAULT_MODEL : value;
|
||||
|
||||
for (const key of MODEL_ENV_VAR_KEYS) {
|
||||
const value = settings.env[key];
|
||||
if (typeof value !== 'string') continue;
|
||||
const replaced = replaceIfPlaceholder(value);
|
||||
if (replaced !== value) {
|
||||
settings.env[key] = replaced;
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(settings.presets)) {
|
||||
for (const preset of settings.presets) {
|
||||
if (!preset || typeof preset !== 'object') continue;
|
||||
const presetRecord = preset as Record<string, unknown>;
|
||||
|
||||
for (const key of PRESET_MODEL_KEYS) {
|
||||
const value = presetRecord[key];
|
||||
if (typeof value !== 'string') continue;
|
||||
const replaced = replaceIfPlaceholder(value);
|
||||
if (replaced !== value) {
|
||||
presetRecord[key] = replaced;
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
try {
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', { mode: 0o600 });
|
||||
} catch {
|
||||
// Best-effort migration — don't block startup if write fails
|
||||
}
|
||||
}
|
||||
|
||||
return migrated;
|
||||
}
|
||||
|
||||
/** Remote proxy configuration for URL rewriting */
|
||||
export interface RemoteProxyRewriteConfig {
|
||||
host: string;
|
||||
@@ -355,6 +412,8 @@ export function getEffectiveEnvVars(
|
||||
migrateDeprecatedModelNames(expandedPath, settings);
|
||||
// Migrate codex effort suffixes to canonical IDs if present
|
||||
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
||||
// Migrate legacy iFlow placeholders to supported model IDs
|
||||
migrateIFlowPlaceholderModel(expandedPath, provider, settings);
|
||||
// Custom variant settings found - merge with global env
|
||||
envVars = { ...globalEnv, ...settings.env };
|
||||
// Ensure required vars are present (fall back to defaults if missing)
|
||||
@@ -388,6 +447,8 @@ export function getEffectiveEnvVars(
|
||||
migrateDeprecatedModelNames(settingsPath, settings);
|
||||
// Migrate codex effort suffixes to canonical IDs if present
|
||||
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
||||
// Migrate legacy iFlow placeholders to supported model IDs
|
||||
migrateIFlowPlaceholderModel(settingsPath, provider, settings);
|
||||
// User override found - merge with global env
|
||||
envVars = { ...globalEnv, ...settings.env };
|
||||
// Ensure required vars are present (fall back to defaults if missing)
|
||||
@@ -525,6 +586,7 @@ export function getRemoteEnvVars(
|
||||
if (settings.env && typeof settings.env === 'object') {
|
||||
migrateDeprecatedModelNames(expandedPath, settings);
|
||||
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
||||
migrateIFlowPlaceholderModel(expandedPath, provider, settings);
|
||||
userEnvVars = settings.env as Record<string, string>;
|
||||
}
|
||||
} catch {
|
||||
@@ -544,6 +606,7 @@ export function getRemoteEnvVars(
|
||||
if (settings.env && typeof settings.env === 'object') {
|
||||
migrateDeprecatedModelNames(settingsPath, settings);
|
||||
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
||||
migrateIFlowPlaceholderModel(settingsPath, provider, settings);
|
||||
userEnvVars = settings.env as Record<string, string>;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -146,6 +146,51 @@ describe('getEffectiveEnvVars local provider URL normalization', () => {
|
||||
expect(persisted.presets[0]?.haiku).toBe('gpt-5-mini');
|
||||
});
|
||||
|
||||
it('migrates iflow placeholder model IDs to a supported default', () => {
|
||||
const iflowSettingsPath = path.join(tempHome, 'iflow.settings.json');
|
||||
writeSettings(
|
||||
iflowSettingsPath,
|
||||
{
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/iflow',
|
||||
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
|
||||
ANTHROPIC_MODEL: 'iflow-default',
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: 'iflow-default',
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: 'iflow-default',
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'iflow-default',
|
||||
},
|
||||
{
|
||||
presets: [
|
||||
{
|
||||
name: 'legacy-iflow',
|
||||
default: 'iflow-default',
|
||||
opus: 'iflow-default',
|
||||
sonnet: 'iflow-default',
|
||||
haiku: 'iflow-default',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const env = getEffectiveEnvVars('iflow', 8317, iflowSettingsPath);
|
||||
expect(env.ANTHROPIC_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('qwen3-coder-plus');
|
||||
|
||||
const persisted = JSON.parse(fs.readFileSync(iflowSettingsPath, 'utf-8')) as {
|
||||
env: Record<string, string>;
|
||||
presets: Array<Record<string, string>>;
|
||||
};
|
||||
expect(persisted.env.ANTHROPIC_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(persisted.env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(persisted.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(persisted.env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('qwen3-coder-plus');
|
||||
expect(persisted.presets[0]?.default).toBe('qwen3-coder-plus');
|
||||
expect(persisted.presets[0]?.opus).toBe('qwen3-coder-plus');
|
||||
expect(persisted.presets[0]?.sonnet).toBe('qwen3-coder-plus');
|
||||
expect(persisted.presets[0]?.haiku).toBe('qwen3-coder-plus');
|
||||
});
|
||||
|
||||
it('repairs existing provider settings files that are missing env keys', () => {
|
||||
process.env.CCS_HOME = tempHome;
|
||||
const agySettingsPath = path.join(tempHome, '.ccs', 'agy.settings.json');
|
||||
|
||||
@@ -288,12 +288,58 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
||||
iflow: {
|
||||
provider: 'iflow',
|
||||
displayName: 'iFlow',
|
||||
defaultModel: 'iflow-default',
|
||||
defaultModel: 'qwen3-coder-plus',
|
||||
models: [
|
||||
{
|
||||
id: 'iflow-default',
|
||||
name: 'iFlow Default',
|
||||
description: 'Default iFlow model',
|
||||
id: 'qwen3-coder-plus',
|
||||
name: 'Qwen3 Coder Plus',
|
||||
description: 'Recommended default for iFlow accounts',
|
||||
presetMapping: {
|
||||
default: 'qwen3-coder-plus',
|
||||
opus: 'qwen3-coder-plus',
|
||||
sonnet: 'qwen3-coder-plus',
|
||||
haiku: 'qwen3-coder-plus',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'qwen3-max',
|
||||
name: 'Qwen3 Max',
|
||||
description: 'Flagship Qwen model via iFlow',
|
||||
},
|
||||
{
|
||||
id: 'kimi-k2.5',
|
||||
name: 'Kimi K2.5',
|
||||
description: 'Latest Kimi model via iFlow',
|
||||
},
|
||||
{
|
||||
id: 'kimi-k2',
|
||||
name: 'Kimi K2',
|
||||
description: 'Kimi general model',
|
||||
},
|
||||
{
|
||||
id: 'deepseek-v3.2-chat',
|
||||
name: 'DeepSeek V3.2 Chat',
|
||||
description: 'Stable DeepSeek chat model',
|
||||
},
|
||||
{
|
||||
id: 'deepseek-r1',
|
||||
name: 'DeepSeek R1',
|
||||
description: 'Reasoning-focused DeepSeek model',
|
||||
},
|
||||
{
|
||||
id: 'glm-4.7',
|
||||
name: 'GLM 4.7',
|
||||
description: 'Zhipu GLM 4.7 via iFlow',
|
||||
},
|
||||
{
|
||||
id: 'minimax-m2.5',
|
||||
name: 'MiniMax M2.5',
|
||||
description: 'MiniMax M2.5 via iFlow',
|
||||
},
|
||||
{
|
||||
id: 'qwen3-vl-plus',
|
||||
name: 'Qwen3 VL Plus',
|
||||
description: 'Vision-language model',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user