Skip to content

Commit 21e93c0

Browse files
author
Zoo (VP)
committed
feat(error): add structured error presentation in assistant messages
1 parent 14ad8eb commit 21e93c0

1 file changed

Lines changed: 104 additions & 7 deletions

File tree

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,73 @@ import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../sha
1414
import { AskIgnoredError } from "../task/AskIgnoredError"
1515
import { Task } from "../task/Task"
1616

17+
/**
18+
* Structured error presentation for LLM-guided error recovery.
19+
* Provides WHAT/WHY/NEXT format wrapped in <error_details> XML tags.
20+
*/
21+
function formatStructuredError(
22+
details: {
23+
what: string
24+
why: string
25+
next: string[]
26+
retryable?: boolean
27+
pattern?: string
28+
occurrence?: number
29+
disposition?: string
30+
},
31+
byteLimit: number = 8000,
32+
): string {
33+
const version = "1.0"
34+
const status = "error"
35+
const category = details.pattern ? (details.pattern.split("/")[1] ?? "unknown") : "unknown"
36+
const type = details.pattern
37+
? details.pattern.startsWith("EI/")
38+
? details.pattern.replace("EI/", "guided_").toLowerCase().replace(/_/g, "_")
39+
: details.pattern.toLowerCase().replace(/_/g, "_")
40+
: "unclassified_error"
41+
const what = details.what
42+
const why = details.why
43+
const next = details.next ?? []
44+
const retryable = details.retryable ?? true
45+
const occurrence = Math.max(1, details.occurrence ?? 1)
46+
const patternId = details.pattern ?? "UNCLASSIFIED/000/000"
47+
const recoveryDisposition = details.disposition ?? "correct_once"
48+
49+
const payload = {
50+
version,
51+
status,
52+
type,
53+
category,
54+
what,
55+
why,
56+
next,
57+
retryable,
58+
occurrence,
59+
pattern_id: patternId,
60+
recovery_disposition: recoveryDisposition,
61+
}
62+
63+
let json = JSON.stringify(payload, null, 2)
64+
65+
if (json.length > byteLimit && next.length > 0) {
66+
// Trim Next items to fit within byte limit, preserving the first one
67+
const firstItem = next[0]
68+
next.length = 1
69+
const trimmed = {
70+
...payload,
71+
next: [firstItem],
72+
}
73+
json = JSON.stringify(trimmed, null, 2)
74+
}
75+
76+
if (json.length > byteLimit) {
77+
// Last resort: truncate the JSON string
78+
json = json.substring(0, byteLimit - 3) + "..."
79+
}
80+
81+
return `<error_details>\n${json}\n</error_details>`
82+
}
83+
1784
import { listFilesTool } from "../tools/ListFilesTool"
1885
import { readFileTool } from "../tools/ReadFileTool"
1986
import { readCommandOutputTool } from "../tools/ReadCommandOutputTool"
@@ -225,12 +292,28 @@ export async function presentAssistantMessage(cline: Task) {
225292
if (error instanceof AskIgnoredError) {
226293
return
227294
}
228-
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
295+
296+
// Structured error presentation with WHAT/WHY/NEXT format
297+
const serializedError = serializeError(error)
298+
const structuredErrorContent = formatStructuredError({
299+
what: `An error occurred during ${action}.`,
300+
why: error.message || serializedError.message || "An unexpected error occurred.",
301+
next: [
302+
`Retry the ${action} operation with corrected parameters if applicable.`,
303+
`If the error persists, report this issue to the development team with the error details below.`,
304+
],
305+
pattern: "TOOL_EXECUTION/ERROR_EXECUTION/001",
306+
retryable: true,
307+
occurrence: 1,
308+
disposition: "correct_once",
309+
})
310+
311+
pushToolResult(structuredErrorContent)
312+
229313
await cline.say(
230314
"error",
231-
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
315+
`[${action}] Error during execution:\n${error.message ?? JSON.stringify(serializedError, null, 2)}\n\n${structuredErrorContent}`,
232316
)
233-
pushToolResult(formatResponse.toolError(errorString))
234317
}
235318

236319
if (!mcpBlock.partial) {
@@ -543,14 +626,28 @@ export async function presentAssistantMessage(cline: Task) {
543626
if (error instanceof AskIgnoredError) {
544627
return
545628
}
546-
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
629+
630+
// Structured error presentation with WHAT/WHY/NEXT format
631+
const serializedError = serializeError(error)
632+
const structuredErrorContent = formatStructuredError({
633+
what: `An error occurred during ${action}.`,
634+
why: error.message || serializedError.message || "An unexpected error occurred.",
635+
next: [
636+
`Review the error details and retry the ${action} operation with corrected parameters.`,
637+
`If the error persists, report this issue to the development team.`,
638+
],
639+
pattern: "TOOL_EXECUTION/ERROR_EXECUTION/002",
640+
retryable: true,
641+
occurrence: 1,
642+
disposition: "correct_once",
643+
})
644+
645+
pushToolResult(structuredErrorContent)
547646

548647
await cline.say(
549648
"error",
550-
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
649+
`[${action}] Error during execution:\n${error.message ?? JSON.stringify(serializedError, null, 2)}\n\n${structuredErrorContent}`,
551650
)
552-
553-
pushToolResult(formatResponse.toolError(errorString))
554651
}
555652

556653
if (!block.partial) {

0 commit comments

Comments
 (0)