feat: implement bounded LLM request recovery
This commit is contained in:
@@ -44,6 +44,7 @@ import {
|
||||
} from '@agentclientprotocol/sdk'
|
||||
import type { ContentBlock, LlmCallConfig, LlmModelInfo, LlmProviderInfo } from '@deepseek-ai/dsh-llm'
|
||||
import { assertNever, CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
// Side-effect type import: resolves `ctx.get('permission')` to the service.
|
||||
@@ -481,7 +482,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
|
||||
reason: TurnEndReason,
|
||||
): void => {
|
||||
if (reason.kind === 'error') {
|
||||
inflight.reject(internalError(`turn failed: ${reason.message}`))
|
||||
inflight.reject(internalError(`turn failed: ${'failure' in reason ? reason.failure.message : reason.message}`))
|
||||
} else {
|
||||
inflight.resolve(turnEndToStopReason(reason))
|
||||
}
|
||||
@@ -1035,6 +1036,7 @@ function validateMcpServers(params: { mcpServers?: unknown[] }): void {
|
||||
* identical update stream from the same event log.
|
||||
*
|
||||
* - `assistant/chunk` text-delta/reasoning-delta → message/thought chunks
|
||||
* - `llm/retry` and terminal model failure → visible discarded-attempt markers
|
||||
* - `user/message` → `user_message_chunk` during load replay only — so a
|
||||
* loaded transcript reconstructs the USER side of each turn without echoing
|
||||
* a live `session/prompt` back to the client
|
||||
@@ -1081,6 +1083,13 @@ export function streamSessionEventUpdate(
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'llm/retry': {
|
||||
const text = '\n\n[Previous model attempt discarded; retrying '
|
||||
+ `${event.data.retry}/${event.data.maxRetries} in ${event.data.delayMs}ms: `
|
||||
+ `${event.data.failure.message}]\n\n`
|
||||
notify({ sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } })
|
||||
return
|
||||
}
|
||||
case 'user/message': {
|
||||
if (!includeUserMessages) return
|
||||
// Replay the user's prompt so a loaded session shows both sides of each
|
||||
@@ -1108,7 +1117,16 @@ export function streamSessionEventUpdate(
|
||||
notify({ sessionId, update: { sessionUpdate: 'plan', ...todosToPlan(event.data.todos) } })
|
||||
return
|
||||
}
|
||||
// turn/step boundaries, context/message, steering,
|
||||
case 'turn/end': {
|
||||
if (event.data.reason.kind !== 'error') return
|
||||
const message = 'failure' in event.data.reason
|
||||
? event.data.reason.failure.message
|
||||
: event.data.reason.message
|
||||
const text = `\n\n[Model attempt failed; any partial output above is discarded: ${message}]\n\n`
|
||||
notify({ sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } })
|
||||
return
|
||||
}
|
||||
// non-error turn/step boundaries, context/message, steering,
|
||||
// assistant/message — no direct ACP client update.
|
||||
default:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user