From 97ba1051a2d5b4f151a2a6133adaa00cbd590d5a Mon Sep 17 00:00:00 2001
From: lzt404 <2596933790@qq.com>
Date: Thu, 8 Oct 2026 01:41:42 +0800
Subject: [PATCH] feat(claude-code): refine model mapping UI and defaults
- Add independent Fable model mapping
- Rename startup model to default model
- Default to the Opus mapping and preserve Subagent inheritance
- Replace 1M checkboxes with standard toggle switches
- Align form fields and simplify explanatory text
- Remove bulk role synchronization
- Update translations and regression tests
---
src-tauri/src/agents/commands.rs | 6 +-
src-tauri/src/agents/configuration.rs | 3 +-
src-tauri/src/agents/discovery.rs | 8 +-
src-tauri/src/main.rs | 6 +
src-tauri/src/tests/agent_configuration.rs | 25 ++++
.../claude-code/ClaudeCodeContextOptions.tsx | 2 +-
.../claude-code/ClaudeCodeModelInputs.css | 134 +++++++++++++-----
.../claude-code/ClaudeCodeModelInputs.tsx | 17 +--
.../claude-code/ClaudeCodeRoleRows.tsx | 18 +--
src/i18n/ja.ts | 11 +-
src/i18n/locales/en.ts | 11 +-
src/i18n/locales/zh-CN.ts | 11 +-
src/pages/AgentsPage.tsx | 38 ++---
src/services/agentConfigurationDraft.ts | 4 +
src/services/claudeCodeModels.ts | 4 +-
tests/claude-code-model-inputs-ui.cjs | 24 ++--
tests/claudeCodeModels.test.ts | 2 +-
17 files changed, 206 insertions(+), 118 deletions(-)
diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs
index daaf1eb..81d7c17 100644
--- a/src-tauri/src/agents/commands.rs
+++ b/src-tauri/src/agents/commands.rs
@@ -1038,6 +1038,8 @@ pub(crate) fn resolve_claude_desktop_model_mappings(
opus_1m: requested.opus_1m,
sonnet_1m: requested.sonnet_1m,
haiku_1m: requested.haiku_1m,
+ fable: resolve(if requested.fable.trim().is_empty() { &requested.sonnet } else { &requested.fable })?,
+ fable_1m: requested.fable_1m,
max_context_tokens: requested.max_context_tokens,
auto_compact_pct: requested.auto_compact_pct,
disable_auto_compact: requested.disable_auto_compact,
@@ -1067,7 +1069,7 @@ pub(crate) fn resolve_claude_code_model_mappings(
let explicit_1m = [&requested.startup_model, &requested.subagent_model]
.iter().any(|value| value.as_deref().is_some_and(|model|
model.trim().to_ascii_lowercase().ends_with("[1m]")));
- let max_context_tokens = if requested.opus_1m || requested.sonnet_1m || requested.haiku_1m || explicit_1m {
+ let max_context_tokens = if requested.opus_1m || requested.sonnet_1m || requested.haiku_1m || requested.fable_1m || explicit_1m {
CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW
} else {
requested.max_context_tokens
@@ -1087,6 +1089,8 @@ pub(crate) fn resolve_claude_code_model_mappings(
opus_1m: requested.opus_1m,
sonnet_1m: requested.sonnet_1m,
haiku_1m: requested.haiku_1m,
+ fable: resolve(if requested.fable.trim().is_empty() { &requested.sonnet } else { &requested.fable })?,
+ fable_1m: requested.fable_1m,
max_context_tokens,
auto_compact_pct: requested.auto_compact_pct,
disable_auto_compact: requested.disable_auto_compact,
diff --git a/src-tauri/src/agents/configuration.rs b/src-tauri/src/agents/configuration.rs
index 7cbe071..e8ca88e 100644
--- a/src-tauri/src/agents/configuration.rs
+++ b/src-tauri/src/agents/configuration.rs
@@ -609,7 +609,7 @@ pub(crate) fn build_claude_agent_config(
("ANTHROPIC_DEFAULT_OPUS_MODEL", model_settings.opus.as_str()),
(
"ANTHROPIC_DEFAULT_FABLE_MODEL",
- model_settings.sonnet.as_str(),
+ model_settings.fable.as_str(),
),
] {
env.insert(
@@ -684,6 +684,7 @@ pub(crate) fn build_claude_agent_config(
root.insert("autoCompactEnabled".into(), serde_json::Value::Bool(!mappings.disable_auto_compact));
let model_settings_json = ensure_json_object_entry(root, "modelSettings");
for (model_id, window) in [
+ (&mappings.fable, if mappings.fable_1m { CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW } else { mappings.max_context_tokens }),
(&mappings.opus, if mappings.opus_1m { CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW } else { mappings.max_context_tokens }),
(&mappings.sonnet, if mappings.sonnet_1m { CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW } else { mappings.max_context_tokens }),
(&mappings.haiku, if mappings.haiku_1m { CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW } else { mappings.max_context_tokens }),
diff --git a/src-tauri/src/agents/discovery.rs b/src-tauri/src/agents/discovery.rs
index 1883dab..139cb8c 100644
--- a/src-tauri/src/agents/discovery.rs
+++ b/src-tauri/src/agents/discovery.rs
@@ -2982,6 +2982,8 @@ pub(crate) fn inspect_claude_code_model_mappings(
opus_1m: opus_had_1m,
sonnet_1m: sonnet_had_1m,
haiku_1m: haiku_had_1m,
+ fable: read_model("ANTHROPIC_DEFAULT_FABLE_MODEL").map(|entry| entry.0).unwrap_or_default(),
+ fable_1m: read_model("ANTHROPIC_DEFAULT_FABLE_MODEL").is_some_and(|entry| entry.1),
max_context_tokens,
auto_compact_pct,
disable_auto_compact,
@@ -3129,6 +3131,8 @@ pub(crate) fn claude_code_model_settings(
opus_1m: mappings.opus_1m,
sonnet_1m: mappings.sonnet_1m,
haiku_1m: mappings.haiku_1m,
+ fable: claude_code_model_setting(if mappings.fable.is_empty() { &mappings.sonnet } else { &mappings.fable }, if mappings.fable.is_empty() { mappings.sonnet_1m } else { mappings.fable_1m }),
+ fable_1m: mappings.fable_1m,
max_context_tokens: mappings.max_context_tokens,
auto_compact_pct: mappings.auto_compact_pct,
disable_auto_compact: mappings.disable_auto_compact,
@@ -3196,8 +3200,8 @@ pub(crate) fn claude_code_model_presentation_environment(
);
let fable = claude_code_model_presentation(
models,
- &mappings.sonnet,
- mappings.sonnet_1m,
+ if mappings.fable.is_empty() { &mappings.sonnet } else { &mappings.fable },
+ mappings.fable_1m,
Some(mappings.max_context_tokens),
Some("Fable mapping"),
);
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 5225e6a..9b49ced 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -1144,6 +1144,10 @@ struct ClaudeDesktopModelMappings {
#[serde(default)]
haiku: String,
#[serde(default)]
+ fable: String,
+ #[serde(default)]
+ fable_1m: bool,
+ #[serde(default)]
opus_1m: bool,
#[serde(default)]
sonnet_1m: bool,
@@ -1225,6 +1229,8 @@ impl ClaudeDesktopModelMappings {
opus: model.to_string(),
sonnet: model.to_string(),
haiku: model.to_string(),
+ fable: model.to_string(),
+ fable_1m: false,
opus_1m: false,
sonnet_1m: false,
haiku_1m: false,
diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs
index 491f971..b42b492 100644
--- a/src-tauri/src/tests/agent_configuration.rs
+++ b/src-tauri/src/tests/agent_configuration.rs
@@ -222,6 +222,8 @@ fn claude_code_role_mappings_drive_settings() {
disable_auto_compact: false,
manage_default_model: true,
manage_subagent_model: true,
+ fable: String::new(),
+ fable_1m: false,
startup_model: None,
subagent_model: None,
};
@@ -342,6 +344,8 @@ fn claude_code_runtime_settings_keep_per_role_1m_suffixes() {
disable_auto_compact: true,
manage_default_model: true,
manage_subagent_model: true,
+ fable: String::new(),
+ fable_1m: false,
startup_model: None,
subagent_model: None,
};
@@ -2692,3 +2696,24 @@ fn windows_batch_agent_commands_use_call_without_embedded_quotes() {
);
assert_eq!(native.get_args().count(), 0);
}
+
+#[test]
+fn claude_code_fable_mapping_is_independent_and_round_trips() {
+ let directory = agent_test_home("fable-mapping");
+ let path = directory.join("settings.json");
+ let models = test_agent_models(&["route-sonnet", "route-fable"]);
+ let mut mappings = ClaudeDesktopModelMappings::all("route-sonnet");
+ mappings.fable = "route-fable".into();
+ mappings.fable_1m = true;
+ mappings.startup_model = Some("fable".into());
+ let resolved = resolve_claude_code_model_mappings(AgentClient::ClaudeCode, &models, "route-sonnet", Some(mappings)).unwrap().unwrap();
+ let rendered = build_claude_agent_config(None, "http://127.0.0.1:8317", "test-key", "route-sonnet", &models, Some(&resolved)).unwrap();
+ let value: serde_json::Value = serde_json::from_str(&rendered).unwrap();
+ assert_eq!(value["env"]["ANTHROPIC_DEFAULT_FABLE_MODEL"], "route-fable[1m]");
+ assert_eq!(value["env"]["ANTHROPIC_DEFAULT_SONNET_MODEL"], "route-sonnet");
+ assert_eq!(value["model"], "fable");
+ fs::write(&path, rendered).unwrap();
+ let inspected = inspect_claude_code_model_mappings(&path).unwrap().unwrap();
+ assert_eq!(inspected.fable, "route-fable");
+ assert!(inspected.fable_1m);
+}
diff --git a/src/components/claude-code/ClaudeCodeContextOptions.tsx b/src/components/claude-code/ClaudeCodeContextOptions.tsx
index d68a7d3..3c50a9b 100644
--- a/src/components/claude-code/ClaudeCodeContextOptions.tsx
+++ b/src/components/claude-code/ClaudeCodeContextOptions.tsx
@@ -6,7 +6,7 @@ type Props = {
startupModel: string;
subagentModel: string;
disabled: boolean;
- onRoleChange: (field: 'opus1m' | 'sonnet1m' | 'haiku1m', enabled: boolean) => void;
+ onRoleChange: (field: 'opus1m' | 'sonnet1m' | 'haiku1m' | 'fable1m', enabled: boolean) => void;
onModelChange: (field: 'startupModel' | 'subagentModel', value: string) => void;
};
diff --git a/src/components/claude-code/ClaudeCodeModelInputs.css b/src/components/claude-code/ClaudeCodeModelInputs.css
index 0d01f06..664cfff 100644
--- a/src/components/claude-code/ClaudeCodeModelInputs.css
+++ b/src/components/claude-code/ClaudeCodeModelInputs.css
@@ -1,53 +1,113 @@
-.claude-code-model-inputs {
- grid-column: 1 / -1;
+.claude-code-model-inputs,
+.claude-role-table {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ min-width: 0;
gap: 12px;
}
+.claude-code-model-inputs { grid-column: 1 / -1; }
+
.claude-code-model-field {
display: grid;
- align-content: start;
- gap: 8px;
+ grid-template-columns: 112px minmax(0, 1fr);
+ align-items: center;
+ gap: 6px 12px;
min-width: 0;
+}
+
+.claude-model-with-context {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 76px;
+ align-items: center;
+ gap: 12px;
+ min-width: 0;
+}
+
+.claude-role-table {
+ padding-top: 16px;
+ border-top: 1px solid var(--theme-e3e1db);
+}
+
+.claude-role-table > strong {
+ color: var(--theme-817b72);
+ font-size: var(--font-size-caption);
+ font-weight: 500;
+}
+
+.claude-role-row {
+ display: grid;
+ grid-template-columns: 112px minmax(0, 1fr) 76px;
+ align-items: center;
+ gap: 12px;
+ min-width: 0;
+}
+
+.claude-code-model-field > strong,
+.claude-role-row > strong {
+ font-size: var(--font-size-label);
+ font-weight: 500;
+ color: var(--theme-5f5a53);
+}
+
+.claude-code-model-inputs .agent-model-trigger,
+.claude-role-table .agent-model-trigger {
+ min-height: 40px;
+ height: 40px;
+ padding: 7px 11px;
+ border: 1px solid var(--theme-d5d2cb);
+ border-radius: 6px;
+ background: var(--theme-fff);
+ font-weight: 400;
+}
+
+.claude-role-table .agent-model-trigger strong { font-weight: 400; }
+.claude-code-model-field .agent-model-trigger input { min-width: 0; width: 100%; }
+.claude-code-model-field > small {
+ grid-column: 2;
+ color: var(--theme-817b72);
+ font-size: var(--font-size-caption);
+ line-height: 1.4;
overflow-wrap: anywhere;
}
-@container agent-panel (max-width: 560px) {
- .claude-code-model-inputs {
- grid-template-columns: 1fr;
- }
+.claude-inline-context {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ white-space: nowrap;
+ font-size: var(--font-size-caption);
+ color: var(--theme-5f5a53);
+}
+.claude-inline-context .switch-control input:focus-visible + .switch-track {
+ outline: 2px solid var(--theme-2d2a26);
+ outline-offset: 2px;
}
-.claude-code-model-field small { color: var(--theme-817b72); line-height: 1.5; }
-.claude-code-model-field .agent-model-trigger { width: 100%; border: 1px solid var(--theme-d3d0c9); border-radius: 7px; background: var(--theme-fff); }
-.claude-code-model-field .agent-model-trigger input { min-width: 0; width: 100%; }
-.claude-code-model-error { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px; border: 1px solid var(--theme-d3d0c9); border-radius: 8px; }
+.claude-code-context-settings {
+ margin-top: 4px;
+ border-top: 1px solid var(--theme-e3e1db);
+ padding-top: 12px;
+}
+.claude-code-context-settings summary {
+ cursor: pointer;
+ font-weight: 500;
+ font-size: var(--font-size-label);
+ padding: 4px 0;
+}
+.claude-code-model-error {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 12px;
+ border: 1px solid var(--theme-e3e1db);
+ border-radius: 6px;
+}
.claude-code-model-error p { margin: 4px 0 0; }
-.claude-code-direct-context { display: flex; align-items: center; gap: 8px; font-size: var(--font-size-caption); }
-.claude-code-selection-hint { grid-column: 1 / -1; color: var(--theme-817b72); line-height: 1.5; }
-.claude-code-model-inputs .claude-code-model-field .agent-model-trigger {
- border: 1px solid var(--theme-d3d0c9, #d3d0c9);
- padding: 6px 10px;
- border-radius: 7px;
-}
-
-.claude-code-context-settings { border-top: 1px solid var(--theme-d3d0c9, #d3d0c9); padding-top: 14px; }
-.claude-code-context-settings summary { cursor: pointer; font-weight: 600; padding: 6px 0; }
-.claude-code-context-settings > p, .claude-code-context-options p { font-size: var(--font-size-caption); line-height: 1.5; color: var(--theme-817b72); }
-.claude-code-context-options { margin: 14px 0 0; padding: 12px; border: 1px solid var(--theme-d3d0c9, #d3d0c9); border-radius: 8px; }
-.claude-code-context-options > div { display: flex; flex-wrap: wrap; gap: 14px 24px; }
-.claude-code-context-options label { display: flex; gap: 8px; align-items: center; }
-.claude-code-group-heading { margin-top: 4px; font-size: var(--font-size-label); }
-.claude-role-table { display: grid; gap: 10px; min-width: 0; }
-.claude-role-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
-.claude-role-table > p { margin: 0; font-size: var(--font-size-caption); color: var(--theme-817b72); line-height: 1.5; }
-.claude-role-row { display: grid; grid-template-columns: 80px minmax(0, 1fr) 65px; align-items: center; gap: 12px; padding: 10px; border: 1px solid var(--theme-d3d0c9); border-radius: 8px; }
-.claude-role-row .agent-model-trigger { width: 100%; }
-.claude-model-with-context { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px; }
-.claude-inline-context { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
-@container agent-panel (max-width: 420px) {
- .claude-role-row { grid-template-columns: minmax(0, 1fr) auto; }
+@container agent-panel (max-width: 460px) {
+ .claude-code-model-field { grid-template-columns: minmax(0, 1fr); }
+ .claude-code-model-field > small { grid-column: 1; }
+ .claude-role-row { grid-template-columns: minmax(0, 1fr) 76px; gap: 6px 12px; }
.claude-role-row > strong { grid-column: 1 / -1; }
}
diff --git a/src/components/claude-code/ClaudeCodeModelInputs.tsx b/src/components/claude-code/ClaudeCodeModelInputs.tsx
index 47ca50e..f977551 100644
--- a/src/components/claude-code/ClaudeCodeModelInputs.tsx
+++ b/src/components/claude-code/ClaudeCodeModelInputs.tsx
@@ -26,7 +26,7 @@ export function ClaudeCodeModelInputs({ startupModel, subagentModel, models, rou
return (
{(['startupModel', 'subagentModel'] as const).map((field) => {
- const value = field === 'startupModel' ? startupModel : subagentModel;
+ const value = field === 'startupModel' ? startupModel || 'opus' : subagentModel;
const preview = claudeCodeModelPreview(value, routes);
return (
@@ -35,27 +35,22 @@ export function ClaudeCodeModelInputs({ startupModel, subagentModel, models, rou
onChange(field, value)}
+ onChange={(value) => onChange(field, field === 'startupModel' ? value.trim() || 'opus' : value)}
disabled={disabled}
loading={loading}
allowCustomValue
- emptyOption={t(`agents.claudeCodeRuntime.${field}Placeholder`)}
+ emptyOption={field === 'subagentModel' ? t('agents.claudeCodeRuntime.subagentModelPlaceholder') : undefined}
editable={{ label: t(`agents.claudeCodeRuntime.${field}`),
placeholder: t(`agents.claudeCodeRuntime.${field}Placeholder`), maxLength: 240 }}
/>
-
- {value.trim() ? preview.role
- ? t('agents.claudeCodeRuntime.followRole', { role: preview.role, model: preview.model || '—' })
- : t('agents.claudeCodeRuntime.directModel', { model: preview.model })
- : t(`agents.claudeCodeRuntime.${field}Hint`)}
-
+ {preview.role &&
{preview.role} → {preview.model}}
{!loading && !isKnownClaudeCodeModel(value, models) ? (
{t('agents.claudeCodeRuntime.unverifiedModel')}
) : null}
diff --git a/src/components/claude-code/ClaudeCodeRoleRows.tsx b/src/components/claude-code/ClaudeCodeRoleRows.tsx
index 8a9c0fb..c475feb 100644
--- a/src/components/claude-code/ClaudeCodeRoleRows.tsx
+++ b/src/components/claude-code/ClaudeCodeRoleRows.tsx
@@ -14,26 +14,20 @@ type Props = {
onModelChange: (role: Role, value: string) => void;
onContextChange: (field: `${Role}1m`, value: boolean) => void;
onRefresh: () => void;
- onApplyAll: () => void;
};
-export function ClaudeCodeRoleRows({ routes, models, loading, error, disabled, onModelChange, onContextChange, onRefresh, onApplyAll }: Props) {
+export function ClaudeCodeRoleRows({ routes, models, loading, error, disabled, onModelChange, onContextChange, onRefresh }: Props) {
const { t } = useI18n();
- return
-
- {t('agents.claudeCodeRuntime.roleMappings')}
-
-
- {t('agents.claudeCodeRuntime.roleRowsHint')}
- {(['sonnet', 'opus', 'haiku'] as const).map(role =>
+ return
+ {t('agents.claudeCodeRuntime.modelMappings')}
+ {(['sonnet', 'opus', 'fable', 'haiku'] as const).map(role => )}
;
diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts
index 85fe17b..235f3fb 100644
--- a/src/i18n/ja.ts
+++ b/src/i18n/ja.ts
@@ -1474,23 +1474,24 @@ export const jaOverrides = {
'agents.claudeCodeRuntime.unverifiedModel': '現在の CPA 一覧にないモデルです。ID またはエイリアスが利用可能か確認してください。',
'agents.claudeCodeRuntime.selectionHint': 'ロール選択はロールの 1M 設定に従います。直接指定では個別に 1M を設定します。上流モデルの対応が必要です。',
'agents.claudeCodeRuntime.advancedContext': 'コンテキストと自動圧縮(詳細)',
- 'agents.claudeCodeRuntime.applySonnetToRoles': 'Sonnet を全ロールに適用',
+ 'agents.claudeCodeRuntime.roleMappings': 'モデルマッピング',
+ 'agents.claudeCodeRuntime.modelMappings': 'モデルマッピング',
'agents.claudeCodeRuntime.roleRowsHint': '各行でリクエストモデルと 1M を設定します。起動・サブエージェントでロールを選ぶと、その行の設定に従います。',
- 'agents.claudeCodeRuntime.roleMappings': 'ロールのマッピング',
'agents.claudeCodeRuntime.sharedRoleModel': '共通ロールモデル',
'agents.claudeCodeRuntime.sharedRoleModelHint': '通常は Opus、Sonnet、Haiku に同じ CPA モデルを使用します。個別ルートが必要な場合だけ展開してください。',
'agents.claudeCodeRuntime.showRoleMappings': 'ロールを個別設定',
'agents.claudeCodeRuntime.hideRoleMappings': '個別設定を閉じる',
'agents.claudeCodeRuntime.extendedContext': '拡張コンテキスト指定(1M)',
'agents.claudeCodeRuntime.sharedContextHint': 'ウィンドウ値は全体の上書きで、モデル別の制限ではありません。1M は拡張コンテキストの指定であり、上流モデルの容量を増やしません。',
- 'agents.claudeCodeRuntime.startupModel': '起動モデル',
- 'agents.claudeCodeRuntime.startupModelPlaceholder': 'Claude Code の既定モデルを使用',
- 'agents.claudeCodeRuntime.startupModelHint': 'CPA のモデルを選択するか ID を入力します。空欄では Claude Code の既定の選択を維持します。',
+ 'agents.claudeCodeRuntime.startupModel': 'デフォルトモデル',
+ 'agents.claudeCodeRuntime.startupModelPlaceholder': 'Opus のマッピングに従う',
+ 'agents.claudeCodeRuntime.startupModelHint': '既定では Opus のマッピングに従います。別のモデルも選択できます。',
'agents.claudeCodeRuntime.subagentModel': 'Subagent モデル',
'agents.claudeCodeRuntime.subagentModelPlaceholder': '既定の継承動作を使用',
'agents.claudeCodeRuntime.subagentModelHint': 'CPA のモデルを選択するか ID を入力します。空欄ではサブエージェントの既定動作を維持します。',
'agents.claudeDesktopMapping.customMapping': 'モデルエイリアスを使用',
'agents.claudeDesktopMapping.customMappingHint': '有効にするとエイリアスモデルのみ、無効にするとエイリアス未設定の元モデルのみ表示します。',
+ 'agents.claudeDesktopMapping.fable': 'Fable',
'agents.claudeDesktopMapping.opus': 'Opus',
'agents.claudeDesktopMapping.sonnet': 'Sonnet',
'agents.claudeDesktopMapping.haiku': 'Haiku',
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index db7d615..b6a6846 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -1440,23 +1440,24 @@ export const en: Record
= {
'agents.claudeCodeRuntime.unverifiedModel': 'This model is not in the current CPA list. Check that the ID or alias is available.',
'agents.claudeCodeRuntime.selectionHint': 'Role selections follow their role’s 1M setting. Direct models have a separate 1M setting. The upstream must support the context size.',
'agents.claudeCodeRuntime.advancedContext': 'Context and auto-compaction (advanced)',
- 'agents.claudeCodeRuntime.applySonnetToRoles': 'Apply Sonnet to all roles',
+ 'agents.claudeCodeRuntime.roleMappings': 'Model mappings',
+ 'agents.claudeCodeRuntime.modelMappings': 'Model mappings',
'agents.claudeCodeRuntime.roleRowsHint': 'Set each role’s request model and 1M flag together. Session and subagent role selections follow the corresponding row.',
- 'agents.claudeCodeRuntime.roleMappings': 'Role mappings',
'agents.claudeCodeRuntime.sharedRoleModel': 'Shared role model',
'agents.claudeCodeRuntime.sharedRoleModelHint': 'Use one CPA model for Opus, Sonnet, and Haiku by default. Expand this only when you need separate routes.',
'agents.claudeCodeRuntime.showRoleMappings': 'Configure roles separately',
'agents.claudeCodeRuntime.hideRoleMappings': 'Hide separate role settings',
'agents.claudeCodeRuntime.extendedContext': 'Extended context flag (1M)',
'agents.claudeCodeRuntime.sharedContextHint': 'The window value is a global override, not a per-model limit. The 1M options send an extended-context flag; they do not increase upstream capacity.',
- 'agents.claudeCodeRuntime.startupModel': 'Startup model',
- 'agents.claudeCodeRuntime.startupModelPlaceholder': 'Use Claude Code default',
- 'agents.claudeCodeRuntime.startupModelHint': 'Select a CPA model or enter a model ID. Leave empty to keep the Claude Code default.',
+ 'agents.claudeCodeRuntime.startupModel': 'Default model',
+ 'agents.claudeCodeRuntime.startupModelPlaceholder': 'Follow Opus mapping',
+ 'agents.claudeCodeRuntime.startupModelHint': 'Follows the Opus mapping by default; you can select another model.',
'agents.claudeCodeRuntime.subagentModel': 'Subagent model',
'agents.claudeCodeRuntime.subagentModelPlaceholder': 'Use default inheritance',
'agents.claudeCodeRuntime.subagentModelHint': 'Select a CPA model or enter a model ID. Leave empty to keep the default subagent behavior.',
'agents.claudeDesktopMapping.customMapping': 'Use Model Aliases',
'agents.claudeDesktopMapping.customMappingHint': 'When enabled, only aliased models are shown; when disabled, only original models without aliases are shown.',
+ 'agents.claudeDesktopMapping.fable': 'Fable',
'agents.claudeDesktopMapping.opus': 'Opus',
'agents.claudeDesktopMapping.sonnet': 'Sonnet',
'agents.claudeDesktopMapping.haiku': 'Haiku',
diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts
index a04307b..f9092f3 100644
--- a/src/i18n/locales/zh-CN.ts
+++ b/src/i18n/locales/zh-CN.ts
@@ -1439,23 +1439,24 @@ export const zhCN = {
'agents.claudeCodeRuntime.unverifiedModel': '当前 CPA 列表中未找到此模型,请确认 ID 或别名可用。',
'agents.claudeCodeRuntime.selectionHint': '跟随角色时使用该角色的 1M 设置;直接指定模型时单独设置 1M。上游须支持对应窗口。',
'agents.claudeCodeRuntime.advancedContext': '上下文与自动压缩(高级)',
- 'agents.claudeCodeRuntime.applySonnetToRoles': '将 Sonnet 应用到全部角色',
+ 'agents.claudeCodeRuntime.roleMappings': '模型映射',
+ 'agents.claudeCodeRuntime.modelMappings': '模型映射',
'agents.claudeCodeRuntime.roleRowsHint': '每行设置一个角色的请求模型和 1M 标记;启动模型或子代理选择角色时,跟随对应行的设置。',
- 'agents.claudeCodeRuntime.roleMappings': '角色映射',
'agents.claudeCodeRuntime.sharedRoleModel': '统一角色模型',
'agents.claudeCodeRuntime.sharedRoleModelHint': '默认让 Opus、Sonnet、Haiku 共用一个 CPA 模型;需要分流时再展开分别设置。',
'agents.claudeCodeRuntime.showRoleMappings': '分别设置角色',
'agents.claudeCodeRuntime.hideRoleMappings': '收起分别设置',
'agents.claudeCodeRuntime.extendedContext': '扩展上下文标记(1M)',
'agents.claudeCodeRuntime.sharedContextHint': '这里的窗口数值是全局覆盖,不是每个模型的独立窗口。1M 选项发送扩展上下文标记;它不会增加上游模型的实际容量。',
- 'agents.claudeCodeRuntime.startupModel': '启动模型',
- 'agents.claudeCodeRuntime.startupModelPlaceholder': '使用 Claude Code 默认模型',
- 'agents.claudeCodeRuntime.startupModelHint': '选择 CPA 模型或输入模型 ID;留空保留 Claude Code 的默认选择。',
+ 'agents.claudeCodeRuntime.startupModel': '默认模型',
+ 'agents.claudeCodeRuntime.startupModelPlaceholder': '跟随 Opus 映射',
+ 'agents.claudeCodeRuntime.startupModelHint': '默认跟随 Opus 映射,也可选择其他模型。',
'agents.claudeCodeRuntime.subagentModel': 'Subagent 模型',
'agents.claudeCodeRuntime.subagentModelPlaceholder': '使用默认继承行为',
'agents.claudeCodeRuntime.subagentModelHint': '选择 CPA 模型或输入模型 ID;留空不覆盖子代理的默认模型。',
'agents.claudeDesktopMapping.customMapping': '使用模型别名',
'agents.claudeDesktopMapping.customMappingHint': '开启后只显示别名模型;关闭后只显示未设置别名的原始模型。',
+ 'agents.claudeDesktopMapping.fable': 'Fable',
'agents.claudeDesktopMapping.opus': 'Opus',
'agents.claudeDesktopMapping.sonnet': 'Sonnet',
'agents.claudeDesktopMapping.haiku': 'Haiku',
diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx
index 529251d..2b61b6c 100644
--- a/src/pages/AgentsPage.tsx
+++ b/src/pages/AgentsPage.tsx
@@ -153,6 +153,8 @@ type ClaudeModelMappings = {
opus: string;
sonnet: string;
haiku: string;
+ fable: string;
+ fable1m: boolean;
opus1m: boolean;
sonnet1m: boolean;
haiku1m: boolean;
@@ -187,6 +189,8 @@ const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
opus: model,
sonnet: model,
haiku: model,
+ fable: model,
+ fable1m: false,
opus1m: false,
sonnet1m: false,
haiku1m: false,
@@ -194,9 +198,9 @@ const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
autoCompactPct: DEFAULT_CLAUDE_AUTO_COMPACT_PCT,
disableAutoCompact: false,
manageDefaultModel: true,
- manageSubagentModel: true,
- startupModel: model,
- subagentModel: model,
+ manageSubagentModel: false,
+ startupModel: 'opus',
+ subagentModel: '',
});
const createClaudeModelMappingsByClient = (): Record<
@@ -219,6 +223,7 @@ let codexOauthConfigurationDraftCache: boolean | null = null;
let agentFormEditBaselineCache: Partial> = {};
const claudeMappingRoles = [
+ { key: 'fable', contextKey: 'fable1m', labelKey: 'agents.claudeDesktopMapping.fable' },
{
key: 'opus',
contextKey: 'opus1m',
@@ -903,13 +908,15 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
opus1m: Boolean(source.opus1m),
sonnet1m: Boolean(source.sonnet1m),
haiku1m: Boolean(source.haiku1m),
+ fable: source.fable || source.sonnet || selectedModel,
+ fable1m: Boolean(source.fable1m),
maxContextTokens: source.maxContextTokens ?? DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS,
autoCompactPct: source.autoCompactPct ?? DEFAULT_CLAUDE_AUTO_COMPACT_PCT,
disableAutoCompact: Boolean(source.disableAutoCompact),
manageDefaultModel: source.manageDefaultModel !== false,
manageSubagentModel: source.manageSubagentModel !== false,
- startupModel: source.startupModel ?? (source.manageDefaultModel === false ? '' : source.sonnet || ''),
- subagentModel: source.subagentModel ?? (source.manageSubagentModel === false ? '' : source.haiku || ''),
+ startupModel: source.startupModel?.trim() || 'opus',
+ subagentModel: source.subagentModel ?? '',
};
return sameAgentModelMappings(currentClientDraft, next)
? current
@@ -1079,7 +1086,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
};
const selectClaudeModelMapping = (
- role: 'opus' | 'sonnet' | 'haiku',
+ role: 'opus' | 'sonnet' | 'haiku' | 'fable',
value: string,
) => {
const model = findAgentModel(models, value);
@@ -1089,14 +1096,14 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
};
const changeClaude1mPreference = (
- preference: 'opus1m' | 'sonnet1m' | 'haiku1m',
+ preference: 'opus1m' | 'sonnet1m' | 'haiku1m' | 'fable1m',
enabled: boolean,
) => {
if (!isClaudeModelMappingClient) return;
editClaudeModelMappings((current) => {
const next = { ...current, [preference]: enabled };
if (selected === 'claude-code') {
- const any1mEnabled = next.opus1m || next.sonnet1m || next.haiku1m
+ const any1mEnabled = next.opus1m || next.sonnet1m || next.haiku1m || next.fable1m
|| [next.startupModel, next.subagentModel].some(value => /\[1m\]$/i.test(value.trim()));
next.maxContextTokens = any1mEnabled
? 1_000_000
@@ -1179,7 +1186,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
resolved.maxContextTokens = claudeModelMappingsDraft.maxContextTokens;
resolved.autoCompactPct = claudeModelMappingsDraft.autoCompactPct;
resolved.disableAutoCompact = claudeModelMappingsDraft.disableAutoCompact;
- resolved.startupModel = claudeModelMappingsDraft.startupModel.trim();
+ resolved.startupModel = claudeModelMappingsDraft.startupModel.trim() || 'opus';
resolved.subagentModel = claudeModelMappingsDraft.subagentModel.trim();
resolved.manageDefaultModel = resolved.startupModel.length > 0;
resolved.manageSubagentModel = resolved.subagentModel.length > 0;
@@ -2036,9 +2043,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
{t(selected === 'claude-code'
? 'agents.claudeCodeMapping.title'
: 'agents.claudeDesktopMapping.title')}
- {t(selected === 'claude-code'
- ? 'agents.claudeCodeMapping.description'
- : 'agents.claudeDesktopMapping.description')}
+