Repository navigation
feat(agents): expose bounded terminal-error hints #2382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -185,15 +185,34 @@ function keepTail(text: string, max: number): string { | |
| return text.length <= max ? text : `${ELLIPSIS}${text.slice(text.length - max + 1)}`; | ||
| } | ||
|
|
||
| const TERMINAL_ERROR_SCAN_MAX = 2_048; | ||
| const TERMINAL_ERROR_HINTS = [ | ||
| ["quota", /insufficient_quota|quota_exceeded|usage_limit_reached|usage limit|quota exceeded/], | ||
| ["rate_limit", /rate[_ -]?limit|too many requests/], | ||
| ["authentication", /invalid_api_key|authentication failed|authentication error|unauthorized/], | ||
| ["model_unavailable", /model_not_found|unknown model|unsupported model|model (?:not found|unavailable)/], | ||
| ["timeout", /timeout|timed out/], | ||
| ["transport", /websocket|econnreset|econnrefused|enotfound|fetch failed|connection (?:closed|reset|refused)/], | ||
| ] as const; | ||
|
|
||
| function terminalErrorDiagnostic(errorMessage: unknown): string { | ||
| const generic = "assistant reported an error"; | ||
| if (typeof errorMessage !== "string") return generic; | ||
| // Inspect a bounded prefix, but retain only a fixed label: no provider text, | ||
| // URLs, credentials or request identifiers may enter task state or output. | ||
| const excerpt = errorMessage.slice(0, TERMINAL_ERROR_SCAN_MAX).toLowerCase(); | ||
| const hint = TERMINAL_ERROR_HINTS.find(([, pattern]) => pattern.test(excerpt))?.[0]; | ||
| return hint ? `${generic} [hint: ${hint}]` : generic; | ||
| } | ||
|
Comment on lines
+188
to
+206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Add tests for the changed logic in The PR adds tests for the main categories. The cases in
The documented precedence is a public claim. A test that matches two categories would lock it in. As per path instructions: "Behavior changes here must ship with their tests in the same PR. Flag changed logic without updated tests." 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| function terminalAssistant(messages: unknown): Omit<AgentEndEvent, "type"> { | ||
| if (!Array.isArray(messages)) return { text: "", outcome: "empty", diagnostic: "assistant returned no final report" }; | ||
| for (let index = messages.length - 1; index >= 0; index -= 1) { | ||
| const message = messages[index] as { role?: string; content?: unknown; stopReason?: unknown }; | ||
| const message = messages[index] as { role?: string; content?: unknown; stopReason?: unknown; errorMessage?: unknown }; | ||
| if (message?.role !== "assistant") continue; | ||
| const stopReason = clean(message.stopReason).toLowerCase(); | ||
| // Do not preserve unbounded provider error payloads. The terminal reason is | ||
| // enough for an operator to distinguish failure from an empty report. | ||
| if (stopReason === "error") return { text: "", outcome: "error", diagnostic: "assistant reported an error" }; | ||
| // Preserve the generic prefix and only a bounded, allowlisted hint. | ||
| if (stopReason === "error") return { text: "", outcome: "error", diagnostic: terminalErrorDiagnostic(message.errorMessage) }; | ||
| if (stopReason === "aborted") return { text: "", outcome: "aborted", diagnostic: "assistant aborted" }; | ||
| const text = contentText(message.content); | ||
| return text.length > 0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
Fix the hint patterns: they match broad substrings and can give wrong classifications.
The patterns have no word boundaries. They also include very common tokens. Some examples:
/timeout|timed out/matches any text that contains "timeout". A provider error that carries a "timeout" parameter name is one case./unauthorized/can match a 403 message about an unrelated resource./websocket/makestransportthe hint for any error that mentions WebSocket.First-match precedence then picks the first category in array order. A message such as "rate limit ... timeout" always becomes
rate_limit.The docs state that hints are best-effort. However, a misleading hint in operator output can still send an operator to the wrong cause. Tighten the patterns, or add tests that show the intended precedence for messages that match more than one category.
🤖 Prompt for AI Agents