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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user