fix(opencode): use ACP-reported reasoning effort options (#853)

* fix(opencode): use ACP-reported reasoning effort options

Expose thought_level options from OpenCode ACP to the web UI via RPC/API
instead of hardcoded presets, and validate effort values before setConfigOption.

Fixes #852

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(opencode): sync hub effort after coerced setConfigOption

When resolveThoughtLevelEffort falls back to a different supported value,
roll back session state after a successful ACP update so keepalive and the
web UI do not keep advertising the rejected effort.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SSU-WEI HUANG
2026-06-10 13:57:03 +08:00
committed by GitHub
co-authored by Cursor
parent 3473a88d67
commit cad58cfa0b
18 changed files with 486 additions and 35 deletions
+15 -1
View File
@@ -51,6 +51,7 @@ import {
} from '@/lib/sessionChatCursorModel'
import { buildCursorEffortPickerOptions, resolveCursorVariantOptions } from '@/lib/cursorModelOptions'
import { useOpencodeModels } from '@/hooks/queries/useOpencodeModels'
import { useOpencodeReasoningEffortOptions } from '@/hooks/queries/useOpencodeReasoningEffortOptions'
import { useVoiceOptional } from '@/lib/voice-context'
import { VoiceBackendSession, registerSessionStore, registerVoiceHooksStore, voiceHooks } from '@/realtime'
import { isRemoteTerminalSupported } from '@/utils/terminalSupport'
@@ -411,6 +412,11 @@ function SessionChatInner(props: SessionChatProps) {
sessionId: props.session.id,
enabled: agentFlavor === 'opencode' && props.session.active
})
const opencodeReasoningEffortState = useOpencodeReasoningEffortOptions({
api: props.api,
sessionId: props.session.id,
enabled: agentFlavor === 'opencode' && props.session.active
})
const opencodeModelOptions = useMemo(() => {
if (agentFlavor !== 'opencode') {
return undefined
@@ -1082,6 +1088,11 @@ function SessionChatInner(props: SessionChatProps) {
? opencodeModelOptions
: undefined
}
availableModelReasoningEffortOptions={
agentFlavor === 'opencode' && opencodeReasoningEffortState.options.length > 0
? opencodeReasoningEffortState.options
: undefined
}
active={props.session.active}
allowSendWhenInactive
thinking={props.session.thinking}
@@ -1140,7 +1151,10 @@ function SessionChatInner(props: SessionChatProps) {
: undefined
}
onModelReasoningEffortChange={
(agentFlavor === 'codex' || agentFlavor === 'opencode') && props.session.active && !controlledByUser
(agentFlavor === 'codex' || agentFlavor === 'opencode')
&& props.session.active
&& !controlledByUser
&& (agentFlavor !== 'opencode' || opencodeReasoningEffortState.options.length > 0)
? handleModelReasoningEffortChange
: undefined
}