fix: let direct models continue after tool execution failures

Return ordinary tool exceptions as paired error results so the model can decide how to recover. Preserve cancellation, existing recovery guidance, and failed tool activities without adding automatic retries or retry limits.

Validate all three text protocols with real process execution and cover bounded errors and cancellation. Focused tests: 161 passed, 1 skipped; typecheck and lint passed. Full suite: 6230 passed, 87 skipped, 1 Obsidian temporary-directory assertion failed.

Release note: Direct-model conversations can continue after a tool fails, allowing the model to use the reported error to choose its next step.
This commit is contained in:
mesalogo
2026-10-05 15:20:17 +08:00
parent 0eaaa85d4c
commit 56b8b10d46
5 changed files with 259 additions and 16 deletions
+19 -13
View File
@@ -882,14 +882,18 @@ function getToolResultPreview(parts: ModelToolResultPart[]): string {
.slice(0, 16_000)
}
function createRecoverableToolErrorResult(
error: RecoverableModelToolError
function createToolErrorResult(
error: unknown
): ModelToolResult {
const text = JSON.stringify({
ok: false,
recoverable: true,
error: error.message.slice(0, 1_000),
nextAction: error.nextAction.slice(0, 1_000)
...(error instanceof RecoverableModelToolError
? {
recoverable: true,
error: error.message.slice(0, 1_000),
nextAction: error.nextAction.slice(0, 1_000)
}
: { error: safeToolErrorDetail(error) ?? 'Tool execution failed' })
})
return {
parts: [{ type: 'text', text }],
@@ -3811,6 +3815,14 @@ export class ModelAgentRuntime implements AgentRuntime {
}
result = step.value
} catch (error) {
signal.throwIfAborted()
if (typeof error === 'object' && error !== null && (
('name' in error && error.name === 'AbortError') ||
('cause' in error && typeof error.cause === 'object' && error.cause !== null &&
'name' in error.cause && error.cause.name === 'AbortError')
)) {
throw error
}
const recoverable = error instanceof RecoverableModelToolError
const detail = safeToolErrorDetail(error)
if (showToolActivity) {
@@ -3828,14 +3840,8 @@ export class ModelAgentRuntime implements AgentRuntime {
...(detail ? { error: detail } : {})
}
}
if (recoverable) {
result = createRecoverableToolErrorResult(error)
toolFailed = true
} else {
throw new Error(`工具「${displayName}」执行失败`, {
cause: error
})
}
result = createToolErrorResult(error)
toolFailed = true
}
roundContextBytes += validateToolResult(result)
if (responses) {