mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix(iflow): replace placeholder model defaults to prevent 406
This commit is contained in:
1 parent
a30d55777f
commit
03bc4140a1
4 files changed
+162
-8
No files matched your search
@@ -2,9 +2,9 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/iflow",
|
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/iflow",
|
||||||
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
|
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
|
||||||
"ANTHROPIC_MODEL": "deepseek-v3.2",
|
"ANTHROPIC_MODEL": "qwen3-coder-plus",
|
||||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "kimi-k2-thinking",
|
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3-coder-plus",
|
||||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v3.2",
|
"ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3-coder-plus",
|
||||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "minimax-m2"
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3-coder-plus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,8 @@ const DEPRECATED_MODEL_PREFIX = 'gemini-claude-';
|
|||||||
/** Replacement prefix matching actual upstream model names */
|
/** Replacement prefix matching actual upstream model names */
|
||||||
const UPSTREAM_MODEL_PREFIX = 'claude-';
|
const UPSTREAM_MODEL_PREFIX = 'claude-';
|
||||||
const CODEX_EFFORT_SUFFIX_REGEX = /-(xhigh|high|medium)$/i;
|
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 PRESET_MODEL_KEYS = ['default', 'opus', 'sonnet', 'haiku'] as const;
|
||||||
const REQUIRED_PROVIDER_ENV_KEYS = [
|
const REQUIRED_PROVIDER_ENV_KEYS = [
|
||||||
'ANTHROPIC_BASE_URL',
|
'ANTHROPIC_BASE_URL',
|
||||||
@@ -136,6 +138,61 @@ function migrateCodexEffortSuffixes(
|
|||||||
return migrated;
|
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 */
|
/** Remote proxy configuration for URL rewriting */
|
||||||
export interface RemoteProxyRewriteConfig {
|
export interface RemoteProxyRewriteConfig {
|
||||||
host: string;
|
host: string;
|
||||||
@@ -355,6 +412,8 @@ export function getEffectiveEnvVars(
|
|||||||
migrateDeprecatedModelNames(expandedPath, settings);
|
migrateDeprecatedModelNames(expandedPath, settings);
|
||||||
// Migrate codex effort suffixes to canonical IDs if present
|
// Migrate codex effort suffixes to canonical IDs if present
|
||||||
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
||||||
|
// Migrate legacy iFlow placeholders to supported model IDs
|
||||||
|
migrateIFlowPlaceholderModel(expandedPath, provider, settings);
|
||||||
// Custom variant settings found - merge with global env
|
// Custom variant settings found - merge with global env
|
||||||
envVars = { ...globalEnv, ...settings.env };
|
envVars = { ...globalEnv, ...settings.env };
|
||||||
// Ensure required vars are present (fall back to defaults if missing)
|
// Ensure required vars are present (fall back to defaults if missing)
|
||||||
@@ -388,6 +447,8 @@ export function getEffectiveEnvVars(
|
|||||||
migrateDeprecatedModelNames(settingsPath, settings);
|
migrateDeprecatedModelNames(settingsPath, settings);
|
||||||
// Migrate codex effort suffixes to canonical IDs if present
|
// Migrate codex effort suffixes to canonical IDs if present
|
||||||
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
||||||
|
// Migrate legacy iFlow placeholders to supported model IDs
|
||||||
|
migrateIFlowPlaceholderModel(settingsPath, provider, settings);
|
||||||
// User override found - merge with global env
|
// User override found - merge with global env
|
||||||
envVars = { ...globalEnv, ...settings.env };
|
envVars = { ...globalEnv, ...settings.env };
|
||||||
// Ensure required vars are present (fall back to defaults if missing)
|
// 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') {
|
if (settings.env && typeof settings.env === 'object') {
|
||||||
migrateDeprecatedModelNames(expandedPath, settings);
|
migrateDeprecatedModelNames(expandedPath, settings);
|
||||||
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
migrateCodexEffortSuffixes(expandedPath, provider, settings);
|
||||||
|
migrateIFlowPlaceholderModel(expandedPath, provider, settings);
|
||||||
userEnvVars = settings.env as Record<string, string>;
|
userEnvVars = settings.env as Record<string, string>;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -544,6 +606,7 @@ export function getRemoteEnvVars(
|
|||||||
if (settings.env && typeof settings.env === 'object') {
|
if (settings.env && typeof settings.env === 'object') {
|
||||||
migrateDeprecatedModelNames(settingsPath, settings);
|
migrateDeprecatedModelNames(settingsPath, settings);
|
||||||
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
migrateCodexEffortSuffixes(settingsPath, provider, settings);
|
||||||
|
migrateIFlowPlaceholderModel(settingsPath, provider, settings);
|
||||||
userEnvVars = settings.env as Record<string, string>;
|
userEnvVars = settings.env as Record<string, string>;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -146,6 +146,51 @@ describe('getEffectiveEnvVars local provider URL normalization', () => {
|
|||||||
expect(persisted.presets[0]?.haiku).toBe('gpt-5-mini');
|
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', () => {
|
it('repairs existing provider settings files that are missing env keys', () => {
|
||||||
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');
|
||||||
|
|||||||
@@ -288,12 +288,58 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
|||||||
iflow: {
|
iflow: {
|
||||||
provider: 'iflow',
|
provider: 'iflow',
|
||||||
displayName: 'iFlow',
|
displayName: 'iFlow',
|
||||||
defaultModel: 'iflow-default',
|
defaultModel: 'qwen3-coder-plus',
|
||||||
models: [
|
models: [
|
||||||
{
|
{
|
||||||
id: 'iflow-default',
|
id: 'qwen3-coder-plus',
|
||||||
name: 'iFlow Default',
|
name: 'Qwen3 Coder Plus',
|
||||||
description: 'Default iFlow model',
|
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