feat(claude): add effort setting parity with model across stack (#353)

Co-authored-by: Xiaoyi <xiaoyizhang@microsoft.com>
This commit is contained in:
xyzhang626
2026-03-24 21:15:48 +08:00
committed by GitHub
co-authored by Xiaoyi
parent 30265fdc25
commit a200fe9628
50 changed files with 715 additions and 52 deletions
@@ -0,0 +1,37 @@
import type { AgentType, ClaudeEffort } from './types'
import { CLAUDE_EFFORT_OPTIONS } from './types'
import { useTranslation } from '@/lib/use-translation'
export function ClaudeEffortSelector(props: {
agent: AgentType
effort: ClaudeEffort
isDisabled: boolean
onEffortChange: (value: ClaudeEffort) => void
}) {
const { t } = useTranslation()
if (props.agent !== 'claude') {
return null
}
return (
<div className="flex flex-col gap-1.5 px-3 py-3">
<label className="text-xs font-medium text-[var(--app-hint)]">
{t('newSession.effort')}{' '}
<span className="font-normal">({t('newSession.model.optional')})</span>
</label>
<select
value={props.effort}
onChange={(e) => props.onEffortChange(e.target.value as ClaudeEffort)}
disabled={props.isDisabled}
className="w-full px-3 py-2 text-sm rounded-lg border border-[var(--app-divider)] bg-[var(--app-bg)] text-[var(--app-text)] focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
>
{CLAUDE_EFFORT_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
)
}
+12 -1
View File
@@ -9,12 +9,13 @@ import { useActiveSuggestions, type Suggestion } from '@/hooks/useActiveSuggesti
import { useDirectorySuggestions } from '@/hooks/useDirectorySuggestions'
import { useRecentPaths } from '@/hooks/useRecentPaths'
import { useTranslation } from '@/lib/use-translation'
import type { AgentType, CodexReasoningEffort, SessionType } from './types'
import type { AgentType, ClaudeEffort, CodexReasoningEffort, SessionType } from './types'
import { ActionButtons } from './ActionButtons'
import { AgentSelector } from './AgentSelector'
import { DirectorySection } from './DirectorySection'
import { MachineSelector } from './MachineSelector'
import { ModelSelector } from './ModelSelector'
import { ClaudeEffortSelector } from './ClaudeEffortSelector'
import { ReasoningEffortSelector } from './ReasoningEffortSelector'
import {
loadPreferredAgent,
@@ -46,6 +47,7 @@ export function NewSession(props: {
const [isDirectoryFocused, setIsDirectoryFocused] = useState(false)
const [agent, setAgent] = useState<AgentType>(loadPreferredAgent)
const [model, setModel] = useState('auto')
const [effort, setEffort] = useState<ClaudeEffort>('auto')
const [modelReasoningEffort, setModelReasoningEffort] = useState<CodexReasoningEffort>('default')
const [yoloMode, setYoloMode] = useState(loadPreferredYoloMode)
const [sessionType, setSessionType] = useState<SessionType>('simple')
@@ -62,6 +64,7 @@ export function NewSession(props: {
useEffect(() => {
setModel('auto')
setEffort('auto')
}, [agent])
useEffect(() => {
@@ -244,6 +247,7 @@ export function NewSession(props: {
}
const resolvedModel = model !== 'auto' && agent !== 'opencode' ? model : undefined
const resolvedEffort = agent === 'claude' && effort !== 'auto' ? effort : undefined
const resolvedModelReasoningEffort = agent === 'codex' && modelReasoningEffort !== 'default'
? modelReasoningEffort
: undefined
@@ -252,6 +256,7 @@ export function NewSession(props: {
directory: trimmedDirectory,
agent,
model: resolvedModel,
effort: resolvedEffort,
modelReasoningEffort: resolvedModelReasoningEffort,
yolo: yoloMode,
sessionType,
@@ -324,6 +329,12 @@ export function NewSession(props: {
isDisabled={isFormDisabled}
onModelChange={setModel}
/>
<ClaudeEffortSelector
agent={agent}
effort={effort}
isDisabled={isFormDisabled}
onEffortChange={setEffort}
/>
<ReasoningEffortSelector
agent={agent}
value={modelReasoningEffort}
+12 -1
View File
@@ -1,6 +1,6 @@
import { CLAUDE_MODEL_PRESETS, getClaudeModelLabel } from '@hapi/protocol'
import { describe, expect, it } from 'vitest'
import { MODEL_OPTIONS } from './types'
import { CLAUDE_EFFORT_OPTIONS, MODEL_OPTIONS } from './types'
describe('Claude model options', () => {
it('includes 1m model options in the expected order', () => {
@@ -19,3 +19,14 @@ describe('Claude model options', () => {
expect(getClaudeModelLabel('opus[1m]')).toBe('Opus 1M')
})
})
describe('Claude effort options', () => {
it('matches supported effort presets in expected order', () => {
expect(CLAUDE_EFFORT_OPTIONS).toEqual([
{ value: 'auto', label: 'Auto' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
{ value: 'max', label: 'Max' },
])
})
})
+8
View File
@@ -1,6 +1,7 @@
export type AgentType = 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode'
export type SessionType = 'simple' | 'worktree'
export type CodexReasoningEffort = 'default' | 'low' | 'medium' | 'high' | 'xhigh'
export type ClaudeEffort = 'auto' | 'medium' | 'high' | 'max'
export const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]> = {
claude: [
@@ -36,3 +37,10 @@ export const CODEX_REASONING_EFFORT_OPTIONS: { value: CodexReasoningEffort; labe
{ value: 'high', label: 'High' },
{ value: 'xhigh', label: 'XHigh' },
]
export const CLAUDE_EFFORT_OPTIONS: { value: ClaudeEffort; label: string }[] = [
{ value: 'auto', label: 'Auto' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
{ value: 'max', label: 'Max' },
]