mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-08 07:17:39 +00:00
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:
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getOpencodeReasoningEffortRefetchInterval, shouldRetryOpencodeReasoningEffortQuery } from './useOpencodeReasoningEffortOptions'
|
||||
|
||||
describe('useOpencodeReasoningEffortOptions retry policy', () => {
|
||||
it('retries transient failures up to three times', () => {
|
||||
expect(shouldRetryOpencodeReasoningEffortQuery(0)).toBe(true)
|
||||
expect(shouldRetryOpencodeReasoningEffortQuery(2)).toBe(true)
|
||||
expect(shouldRetryOpencodeReasoningEffortQuery(3)).toBe(false)
|
||||
})
|
||||
|
||||
it('polls until options are available', () => {
|
||||
expect(getOpencodeReasoningEffortRefetchInterval(true, undefined, 0)).toBe(1000)
|
||||
expect(getOpencodeReasoningEffortRefetchInterval(true, { success: false, error: 'not ready' }, 2)).toBe(1000)
|
||||
expect(getOpencodeReasoningEffortRefetchInterval(true, {
|
||||
success: true,
|
||||
options: [{ value: 'low', name: 'Low' }]
|
||||
}, 1)).toBe(false)
|
||||
})
|
||||
|
||||
it('stops polling when disabled or after the max poll count', () => {
|
||||
expect(getOpencodeReasoningEffortRefetchInterval(false, undefined, 0)).toBe(false)
|
||||
expect(getOpencodeReasoningEffortRefetchInterval(true, undefined, 10)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { OpencodeReasoningEffortResponse } from '@hapi/protocol/apiTypes'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
|
||||
export function shouldRetryOpencodeReasoningEffortQuery(failureCount: number): boolean {
|
||||
return failureCount < 3
|
||||
}
|
||||
|
||||
const MAX_OPENCODE_REASONING_EFFORT_DISCOVERY_POLLS = 10
|
||||
|
||||
export function getOpencodeReasoningEffortRefetchInterval(
|
||||
enabled: boolean,
|
||||
data: OpencodeReasoningEffortResponse | undefined,
|
||||
pollCount: number
|
||||
): 1000 | false {
|
||||
if (!enabled || pollCount >= MAX_OPENCODE_REASONING_EFFORT_DISCOVERY_POLLS) {
|
||||
return false
|
||||
}
|
||||
if (!data) {
|
||||
return 1000
|
||||
}
|
||||
if (data.success === false) {
|
||||
return 1000
|
||||
}
|
||||
return (data.options?.length ?? 0) > 0 ? false : 1000
|
||||
}
|
||||
|
||||
export function useOpencodeReasoningEffortOptions(args: {
|
||||
api: ApiClient | null
|
||||
sessionId?: string | null
|
||||
enabled?: boolean
|
||||
}): {
|
||||
options: Array<{ value: string; name?: string }>
|
||||
currentValue: string | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
} {
|
||||
const { api, sessionId } = args
|
||||
const enabled = Boolean(args.enabled && api && sessionId)
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: sessionId
|
||||
? queryKeys.sessionOpencodeReasoningEffortOptions(sessionId)
|
||||
: ['session-opencode-reasoning-effort-options', 'unknown'] as const,
|
||||
queryFn: async () => {
|
||||
if (!api) {
|
||||
throw new Error('API unavailable')
|
||||
}
|
||||
if (!sessionId) {
|
||||
throw new Error('OpenCode reasoning effort target unavailable')
|
||||
}
|
||||
return await api.getSessionOpencodeReasoningEffortOptions(sessionId)
|
||||
},
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
retry: (failureCount) => shouldRetryOpencodeReasoningEffortQuery(failureCount),
|
||||
refetchInterval: (query) => getOpencodeReasoningEffortRefetchInterval(
|
||||
enabled,
|
||||
query.state.data as OpencodeReasoningEffortResponse | undefined,
|
||||
query.state.dataUpdateCount + query.state.errorUpdateCount
|
||||
),
|
||||
})
|
||||
|
||||
return {
|
||||
options: query.data?.options ?? [],
|
||||
currentValue: query.data?.currentValue ?? null,
|
||||
isLoading: query.isLoading,
|
||||
error: query.data?.success === false
|
||||
? (query.data.error ?? 'Failed to load OpenCode reasoning effort options')
|
||||
: query.error instanceof Error
|
||||
? query.error.message
|
||||
: query.error
|
||||
? 'Failed to load OpenCode reasoning effort options'
|
||||
: null,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user