fix(iflow): replace placeholder model defaults to prevent 406

This commit is contained in:
Tam Nhu Tran
2026-02-26 14:04:32 +07:00
parent a30d55777f
commit 03bc4140a1
4 changed files with 162 additions and 8 deletions
+4 -4
View File
@@ -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"
}
}
+63
View File
@@ -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');
+50 -4
View File
@@ -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',
},
],
},