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 =>
{role[0].toUpperCase() + role.slice(1)} onModelChange(role, value)} onRefresh={onRefresh} />
)}
; 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')} +