feat(settings): add reasoning effort level for Claude Code

Add an Effort Level dropdown under the Claude Code model inputs with
options Low / Medium / High / Max (Opus only). The selection writes an
"effortLevel" key at the root of the Claude Code config JSON, and is
removed when the default is chosen. Manual edits to the native JSON
textarea stay in sync with the dropdown.
This commit is contained in:
xintaofei
2026-04-15 16:38:39 +08:00
parent 503b149d03
commit 2da2378ae3
11 changed files with 170 additions and 10 deletions

View File

@@ -127,6 +127,7 @@ interface AgentDraft {
claudeDefaultHaikuModel: string claudeDefaultHaikuModel: string
claudeDefaultSonnetModel: string claudeDefaultSonnetModel: string
claudeDefaultOpusModel: string claudeDefaultOpusModel: string
claudeEffortLevel: ClaudeEffortLevel
codexAuthJsonText: string codexAuthJsonText: string
codexConfigTomlText: string codexConfigTomlText: string
openCodeAuthJsonText: string openCodeAuthJsonText: string
@@ -251,6 +252,28 @@ const CLAUDE_MODEL_ENV_KEYS = {
claudeDefaultOpusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL", claudeDefaultOpusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
} as const } as const
const CLAUDE_EFFORT_LEVEL_CONFIG_KEY = "effortLevel"
type ClaudeEffortLevel = "" | "low" | "medium" | "high" | "max"
const CLAUDE_EFFORT_LEVEL_VALUES: ReadonlyArray<
Exclude<ClaudeEffortLevel, "">
> = ["low", "medium", "high", "max"]
function normalizeClaudeEffortLevel(value: unknown): ClaudeEffortLevel {
if (typeof value !== "string") return ""
const normalized = value.trim().toLowerCase()
if (
normalized === "low" ||
normalized === "medium" ||
normalized === "high" ||
normalized === "max"
) {
return normalized
}
return ""
}
const GEMINI_AUTH_MODES = [ const GEMINI_AUTH_MODES = [
"custom", "custom",
"login_google", "login_google",
@@ -473,6 +496,7 @@ function extractImportantConfigValues(
claudeDefaultHaikuModel: string claudeDefaultHaikuModel: string
claudeDefaultSonnetModel: string claudeDefaultSonnetModel: string
claudeDefaultOpusModel: string claudeDefaultOpusModel: string
claudeEffortLevel: ClaudeEffortLevel
configError: string | null configError: string | null
} { } {
const parseResult = parseConfigJsonText(configText) const parseResult = parseConfigJsonText(configText)
@@ -507,6 +531,11 @@ function extractImportantConfigValues(
CLAUDE_MODEL_ENV_KEYS.claudeDefaultOpusModel, CLAUDE_MODEL_ENV_KEYS.claudeDefaultOpusModel,
]) ])
const claudeEffortLevel: ClaudeEffortLevel =
agentType === "claude_code"
? normalizeClaudeEffortLevel(config[CLAUDE_EFFORT_LEVEL_CONFIG_KEY])
: ""
return { return {
apiBaseUrl: apiBaseUrl ?? "", apiBaseUrl: apiBaseUrl ?? "",
apiKey: apiKey ?? "", apiKey: apiKey ?? "",
@@ -520,6 +549,7 @@ function extractImportantConfigValues(
agentType === "claude_code" ? claudeDefaultSonnetModel : "", agentType === "claude_code" ? claudeDefaultSonnetModel : "",
claudeDefaultOpusModel: claudeDefaultOpusModel:
agentType === "claude_code" ? claudeDefaultOpusModel : "", agentType === "claude_code" ? claudeDefaultOpusModel : "",
claudeEffortLevel,
configError: parseResult.error, configError: parseResult.error,
} }
} }
@@ -2318,6 +2348,7 @@ function buildAgentDraft(agent: AcpAgentInfo): AgentDraft {
claudeDefaultHaikuModel: important.claudeDefaultHaikuModel, claudeDefaultHaikuModel: important.claudeDefaultHaikuModel,
claudeDefaultSonnetModel: important.claudeDefaultSonnetModel, claudeDefaultSonnetModel: important.claudeDefaultSonnetModel,
claudeDefaultOpusModel: important.claudeDefaultOpusModel, claudeDefaultOpusModel: important.claudeDefaultOpusModel,
claudeEffortLevel: important.claudeEffortLevel,
codexAuthJsonText, codexAuthJsonText,
codexConfigTomlText, codexConfigTomlText,
openCodeAuthJsonText, openCodeAuthJsonText,
@@ -3569,6 +3600,7 @@ export function AcpAgentSettings() {
claudeDefaultHaikuModel: important.claudeDefaultHaikuModel, claudeDefaultHaikuModel: important.claudeDefaultHaikuModel,
claudeDefaultSonnetModel: important.claudeDefaultSonnetModel, claudeDefaultSonnetModel: important.claudeDefaultSonnetModel,
claudeDefaultOpusModel: important.claudeDefaultOpusModel, claudeDefaultOpusModel: important.claudeDefaultOpusModel,
claudeEffortLevel: important.claudeEffortLevel,
})) }))
}, },
[selectedAgent, selectedDraft, updateSelectedDraft] [selectedAgent, selectedDraft, updateSelectedDraft]
@@ -3607,6 +3639,41 @@ export function AcpAgentSettings() {
[selectedAgent, selectedDraft, t, updateSelectedDraft] [selectedAgent, selectedDraft, t, updateSelectedDraft]
) )
const handleClaudeEffortLevelChange = useCallback(
(nextValue: ClaudeEffortLevel) => {
if (
!selectedAgent ||
!selectedDraft ||
selectedAgent.agent_type !== "claude_code"
)
return
const parsed = parseConfigJsonText(selectedDraft.configText)
if (parsed.error) {
toast.warning(t("warnings.nativeJsonRecoveredStructured"))
}
const config: Record<string, unknown> = parsed.error
? {}
: { ...parsed.config }
if (nextValue) {
config[CLAUDE_EFFORT_LEVEL_CONFIG_KEY] = nextValue
} else {
delete config[CLAUDE_EFFORT_LEVEL_CONFIG_KEY]
}
const nextConfigText =
Object.keys(config).length === 0 ? "" : JSON.stringify(config, null, 2)
setConfigErrors((prev) => ({
...prev,
[selectedAgent.agent_type]: null,
}))
updateSelectedDraft((current) => ({
...current,
claudeEffortLevel: nextValue,
configText: nextConfigText,
}))
},
[selectedAgent, selectedDraft, t, updateSelectedDraft]
)
const handleClaudeAuthModeChange = useCallback( const handleClaudeAuthModeChange = useCallback(
(nextMode: ClaudeAuthMode) => { (nextMode: ClaudeAuthMode) => {
if ( if (
@@ -7111,6 +7178,39 @@ supports_websockets = true`}
<p className="text-[11px] text-muted-foreground"> <p className="text-[11px] text-muted-foreground">
{t("modelHintDefault")} {t("modelHintDefault")}
</p> </p>
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
{t("claude.effortLevel")}
</label>
<Select
value={selectedDraft.claudeEffortLevel || "default"}
onValueChange={(nextValue) => {
handleClaudeEffortLevelChange(
nextValue === "default"
? ""
: (nextValue as ClaudeEffortLevel)
)
}}
>
<SelectTrigger className="w-full">
<SelectValue
placeholder={t("claude.effortLevelDefault")}
/>
</SelectTrigger>
<SelectContent align="start">
<SelectItem value="default">
{t("claude.effortLevelDefault")}
</SelectItem>
{CLAUDE_EFFORT_LEVEL_VALUES.map((value) => (
<SelectItem key={value} value={value}>
{value === "max"
? t("claude.effortLevelMax")
: t(`claude.effortLevel_${value}`)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div> </div>
) : ( ) : (
<div className="space-y-1.5"> <div className="space-y-1.5">

View File

@@ -626,7 +626,13 @@
"reasoningModel": "نموذج الاستدلال (thinking)", "reasoningModel": "نموذج الاستدلال (thinking)",
"haikuDefaultModel": "نموذج Haiku الافتراضي", "haikuDefaultModel": "نموذج Haiku الافتراضي",
"sonnetDefaultModel": "نموذج Sonnet الافتراضي", "sonnetDefaultModel": "نموذج Sonnet الافتراضي",
"opusDefaultModel": "نموذج Opus الافتراضي" "opusDefaultModel": "نموذج Opus الافتراضي",
"effortLevel": "مستوى الاستدلال",
"effortLevelDefault": "المستوى الافتراضي",
"effortLevel_low": "منخفض",
"effortLevel_medium": "متوسط",
"effortLevel_high": "مرتفع",
"effortLevelMax": "الأقصى (Opus فقط)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "حذف المزود {providerId}؟", "confirmDeleteProvider": "حذف المزود {providerId}؟",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "Reasoning-Modell (thinking)", "reasoningModel": "Reasoning-Modell (thinking)",
"haikuDefaultModel": "Standard-Haiku-Modell", "haikuDefaultModel": "Standard-Haiku-Modell",
"sonnetDefaultModel": "Standard-Sonnet-Modell", "sonnetDefaultModel": "Standard-Sonnet-Modell",
"opusDefaultModel": "Standard-Opus-Modell" "opusDefaultModel": "Standard-Opus-Modell",
"effortLevel": "Reasoning-Stufe",
"effortLevelDefault": "Standardstufe",
"effortLevel_low": "Niedrig",
"effortLevel_medium": "Mittel",
"effortLevel_high": "Hoch",
"effortLevelMax": "Maximum (nur Opus)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Provider {providerId} löschen?", "confirmDeleteProvider": "Provider {providerId} löschen?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "Reasoning Model (thinking)", "reasoningModel": "Reasoning Model (thinking)",
"haikuDefaultModel": "Default Haiku Model", "haikuDefaultModel": "Default Haiku Model",
"sonnetDefaultModel": "Default Sonnet Model", "sonnetDefaultModel": "Default Sonnet Model",
"opusDefaultModel": "Default Opus Model" "opusDefaultModel": "Default Opus Model",
"effortLevel": "Reasoning Effort Level",
"effortLevelDefault": "Default Level",
"effortLevel_low": "Low",
"effortLevel_medium": "Medium",
"effortLevel_high": "High",
"effortLevelMax": "Max (Opus only)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Delete Provider {providerId}?", "confirmDeleteProvider": "Delete Provider {providerId}?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "Modelo de razonamiento (thinking)", "reasoningModel": "Modelo de razonamiento (thinking)",
"haikuDefaultModel": "Modelo Haiku predeterminado", "haikuDefaultModel": "Modelo Haiku predeterminado",
"sonnetDefaultModel": "Modelo Sonnet predeterminado", "sonnetDefaultModel": "Modelo Sonnet predeterminado",
"opusDefaultModel": "Modelo Opus predeterminado" "opusDefaultModel": "Modelo Opus predeterminado",
"effortLevel": "Nivel de razonamiento",
"effortLevelDefault": "Nivel predeterminado",
"effortLevel_low": "Bajo",
"effortLevel_medium": "Medio",
"effortLevel_high": "Alto",
"effortLevelMax": "Máximo (solo Opus)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "¿Eliminar proveedor {providerId}?", "confirmDeleteProvider": "¿Eliminar proveedor {providerId}?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "Modèle de raisonnement (thinking)", "reasoningModel": "Modèle de raisonnement (thinking)",
"haikuDefaultModel": "Modèle Haiku par défaut", "haikuDefaultModel": "Modèle Haiku par défaut",
"sonnetDefaultModel": "Modèle Sonnet par défaut", "sonnetDefaultModel": "Modèle Sonnet par défaut",
"opusDefaultModel": "Modèle Opus par défaut" "opusDefaultModel": "Modèle Opus par défaut",
"effortLevel": "Niveau de raisonnement",
"effortLevelDefault": "Niveau par défaut",
"effortLevel_low": "Bas",
"effortLevel_medium": "Moyen",
"effortLevel_high": "Élevé",
"effortLevelMax": "Maximum (Opus uniquement)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Supprimer le provider {providerId} ?", "confirmDeleteProvider": "Supprimer le provider {providerId} ?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "推論モデルthinking", "reasoningModel": "推論モデルthinking",
"haikuDefaultModel": "デフォルト Haiku モデル", "haikuDefaultModel": "デフォルト Haiku モデル",
"sonnetDefaultModel": "デフォルト Sonnet モデル", "sonnetDefaultModel": "デフォルト Sonnet モデル",
"opusDefaultModel": "デフォルト Opus モデル" "opusDefaultModel": "デフォルト Opus モデル",
"effortLevel": "推論レベル",
"effortLevelDefault": "デフォルトレベル",
"effortLevel_low": "低",
"effortLevel_medium": "中",
"effortLevel_high": "高",
"effortLevelMax": "最大Opus のみ対応)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Provider {providerId} を削除しますか?", "confirmDeleteProvider": "Provider {providerId} を削除しますか?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "추론 모델 (thinking)", "reasoningModel": "추론 모델 (thinking)",
"haikuDefaultModel": "기본 Haiku 모델", "haikuDefaultModel": "기본 Haiku 모델",
"sonnetDefaultModel": "기본 Sonnet 모델", "sonnetDefaultModel": "기본 Sonnet 모델",
"opusDefaultModel": "기본 Opus 모델" "opusDefaultModel": "기본 Opus 모델",
"effortLevel": "추론 수준",
"effortLevelDefault": "기본 수준",
"effortLevel_low": "낮음",
"effortLevel_medium": "중간",
"effortLevel_high": "높음",
"effortLevelMax": "최고 (Opus만 지원)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Provider {providerId}를 삭제하시겠습니까?", "confirmDeleteProvider": "Provider {providerId}를 삭제하시겠습니까?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "Modelo de raciocínio (thinking)", "reasoningModel": "Modelo de raciocínio (thinking)",
"haikuDefaultModel": "Modelo Haiku padrão", "haikuDefaultModel": "Modelo Haiku padrão",
"sonnetDefaultModel": "Modelo Sonnet padrão", "sonnetDefaultModel": "Modelo Sonnet padrão",
"opusDefaultModel": "Modelo Opus padrão" "opusDefaultModel": "Modelo Opus padrão",
"effortLevel": "Nível de raciocínio",
"effortLevelDefault": "Nível padrão",
"effortLevel_low": "Baixo",
"effortLevel_medium": "Médio",
"effortLevel_high": "Alto",
"effortLevelMax": "Máximo (apenas Opus)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "Excluir o provedor {providerId}?", "confirmDeleteProvider": "Excluir o provedor {providerId}?",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "推理模型thinking", "reasoningModel": "推理模型thinking",
"haikuDefaultModel": "Haiku 默认模型", "haikuDefaultModel": "Haiku 默认模型",
"sonnetDefaultModel": "Sonnet 默认模型", "sonnetDefaultModel": "Sonnet 默认模型",
"opusDefaultModel": "Opus 默认模型" "opusDefaultModel": "Opus 默认模型",
"effortLevel": "推理级别",
"effortLevelDefault": "默认级别",
"effortLevel_low": "低",
"effortLevel_medium": "中",
"effortLevel_high": "高",
"effortLevelMax": "最高(仅 Opus 支持)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "确认删除 Provider {providerId}", "confirmDeleteProvider": "确认删除 Provider {providerId}",

View File

@@ -626,7 +626,13 @@
"reasoningModel": "推理模型thinking", "reasoningModel": "推理模型thinking",
"haikuDefaultModel": "Haiku 預設模型", "haikuDefaultModel": "Haiku 預設模型",
"sonnetDefaultModel": "Sonnet 預設模型", "sonnetDefaultModel": "Sonnet 預設模型",
"opusDefaultModel": "Opus 預設模型" "opusDefaultModel": "Opus 預設模型",
"effortLevel": "推理等級",
"effortLevelDefault": "預設等級",
"effortLevel_low": "低",
"effortLevel_medium": "中",
"effortLevel_high": "高",
"effortLevelMax": "最高(僅 Opus 支援)"
}, },
"dialogs": { "dialogs": {
"confirmDeleteProvider": "確認刪除 Provider {providerId}", "confirmDeleteProvider": "確認刪除 Provider {providerId}",