mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-08 07:17:39 +00:00
feat(opencode): add plan mode, reasoning effort, and status telemetry (#688)
* feat(opencode): support plan mode * feat(opencode): support reasoning effort * feat(opencode): surface context usage in web Bridge OpenCode ACP usage updates into the existing token-count pipeline so the web status bar can show live context and cache information without a separate UI path. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): restrict plan mode to remote, rollback reasoning effort on failure - Block local OpenCode plan startup (tools not enforced in local path) - Allow remote OpenCode plan only (ACP permission handler denies tools) - Guard web /permission-mode endpoint for local OpenCode plan sessions - Rollback session reasoning effort when OpenCode rejects set_config_option - Wire rollback callback through opencodeLoop to runOpencode closure - Add tests: local plan rejected, remote plan allowed, web guard, effort rollback * fix(web): auto-retry OpenCode models query to populate model selector without refresh - Retry early failures (RPC may still be registering on new sessions) - Poll briefly until availableModels is non-empty - Stop polling once model options are discovered - Add tests for retry/poll/stop policy * fix(opencode): cap model discovery polling --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -106,11 +106,11 @@ export function useSessionActions(
|
||||
if (!api || !sessionId) {
|
||||
throw new Error('Session unavailable')
|
||||
}
|
||||
if (agentFlavor !== 'codex') {
|
||||
throw new Error('Model reasoning effort is only supported for Codex sessions')
|
||||
if (agentFlavor !== 'codex' && agentFlavor !== 'opencode') {
|
||||
throw new Error('Model reasoning effort is only supported for Codex and OpenCode sessions')
|
||||
}
|
||||
if (!codexCollaborationModeSupported) {
|
||||
throw new Error('Model reasoning effort is only supported for remote Codex sessions')
|
||||
if (agentFlavor === 'codex' && !codexCollaborationModeSupported) {
|
||||
throw new Error('Model reasoning effort is only supported for remote sessions')
|
||||
}
|
||||
await api.setModelReasoningEffort(sessionId, modelReasoningEffort)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getOpencodeModelsRefetchInterval, shouldRetryOpencodeModelsQuery } from './useOpencodeModels'
|
||||
|
||||
describe('useOpencodeModels retry policy', () => {
|
||||
it('retries early failures while the session model RPC may still be registering', () => {
|
||||
expect(shouldRetryOpencodeModelsQuery(0)).toBe(true)
|
||||
expect(shouldRetryOpencodeModelsQuery(2)).toBe(true)
|
||||
expect(shouldRetryOpencodeModelsQuery(3)).toBe(false)
|
||||
})
|
||||
|
||||
it('polls briefly until OpenCode session models are discovered', () => {
|
||||
expect(getOpencodeModelsRefetchInterval(true, undefined, 0)).toBe(1000)
|
||||
expect(getOpencodeModelsRefetchInterval(true, { success: true, availableModels: [] }, 1)).toBe(1000)
|
||||
expect(getOpencodeModelsRefetchInterval(true, { success: false, error: 'not ready' }, 2)).toBe(1000)
|
||||
})
|
||||
|
||||
it('stops polling once model options are available or the query is disabled', () => {
|
||||
expect(getOpencodeModelsRefetchInterval(true, {
|
||||
success: true,
|
||||
availableModels: [{ modelId: 'provider/model', name: 'Provider Model' }],
|
||||
currentModelId: 'provider/model'
|
||||
}, 1)).toBe(false)
|
||||
expect(getOpencodeModelsRefetchInterval(false, undefined, 0)).toBe(false)
|
||||
})
|
||||
|
||||
it('stops polling after the discovery poll cap', () => {
|
||||
expect(getOpencodeModelsRefetchInterval(true, undefined, 10)).toBe(false)
|
||||
expect(getOpencodeModelsRefetchInterval(true, { success: true, availableModels: [] }, 10)).toBe(false)
|
||||
expect(getOpencodeModelsRefetchInterval(true, { success: false, error: 'not ready' }, 10)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,32 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { OpencodeModelsResponse } from '@hapi/protocol/apiTypes'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import type { OpencodeModelSummary } from '@/types/api'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
|
||||
export function shouldRetryOpencodeModelsQuery(failureCount: number): boolean {
|
||||
return failureCount < 3
|
||||
}
|
||||
|
||||
const MAX_OPENCODE_MODEL_DISCOVERY_POLLS = 10
|
||||
|
||||
export function getOpencodeModelsRefetchInterval(
|
||||
enabled: boolean,
|
||||
data: OpencodeModelsResponse | undefined,
|
||||
pollCount: number
|
||||
): 1000 | false {
|
||||
if (!enabled || pollCount >= MAX_OPENCODE_MODEL_DISCOVERY_POLLS) {
|
||||
return false
|
||||
}
|
||||
if (!data) {
|
||||
return 1000
|
||||
}
|
||||
if (data.success === false) {
|
||||
return 1000
|
||||
}
|
||||
return (data.availableModels?.length ?? 0) > 0 ? false : 1000
|
||||
}
|
||||
|
||||
export function useOpencodeModels(args: {
|
||||
api: ApiClient | null
|
||||
sessionId?: string | null
|
||||
@@ -31,7 +55,12 @@ export function useOpencodeModels(args: {
|
||||
},
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
retry: false,
|
||||
retry: (failureCount) => shouldRetryOpencodeModelsQuery(failureCount),
|
||||
refetchInterval: (query) => getOpencodeModelsRefetchInterval(
|
||||
enabled,
|
||||
query.state.data as OpencodeModelsResponse | undefined,
|
||||
query.state.dataUpdateCount + query.state.errorUpdateCount
|
||||
),
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user