Skip to content

Commit c37f011

Browse files
authored
Merge branch 'main' into claude/typescript-dx-pass
2 parents fa8e657 + d62cc35 commit c37f011

3 files changed

Lines changed: 84 additions & 19 deletions

File tree

apps/website/content/docs/chat/api/api-docs.json

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,7 @@
14291429
{
14301430
"name": "AgentError",
14311431
"kind": "class",
1432-
"description": "Structured, classified failure surfaced on `Agent.error`. Extends `Error`\n so existing `.message` / `instanceof Error` reads keep working.",
1432+
"description": "Structured, classified failure surfaced on `Agent.error`. Extends `Error`, so\nexisting `.message` / `instanceof Error` reads keep working — but adds a\nmachine-readable AgentErrorKind, a `retryable` flag, an optional HTTP\n`status`, and the original `cause`.\n\nYou rarely construct one yourself; adapters normalize raw failures via\ntoAgentError. Read it off the agent to render legible, cause-specific UI:",
14331433
"params": [
14341434
{
14351435
"name": "init",
@@ -1438,18 +1438,20 @@
14381438
"optional": false
14391439
}
14401440
],
1441-
"examples": [],
1441+
"examples": [
1442+
"```ts\nconst err = agent.error(); // AgentError | undefined\nif (err) {\n console.warn(err.message); // legible, per-kind copy\n if (err.kind === 'auth') showApiKeyHelp();\n if (err.retryable) showRetryButton(); // → agent.retry()\n}\n```"
1443+
],
14421444
"properties": [
14431445
{
14441446
"name": "cause",
14451447
"type": "unknown",
1446-
"description": "",
1448+
"description": "The original raw error this was classified from, preserved for debugging/telemetry.",
14471449
"optional": false
14481450
},
14491451
{
14501452
"name": "kind",
14511453
"type": "AgentErrorKind",
1452-
"description": "",
1454+
"description": "The classified failure type. See AgentErrorKind.",
14531455
"optional": false
14541456
},
14551457
{
@@ -1467,7 +1469,7 @@
14671469
{
14681470
"name": "retryable",
14691471
"type": "boolean",
1470-
"description": "connection | server | interrupted → true; auth | aborted | non-auth-4xx → false.",
1472+
"description": "Whether retrying the same request could plausibly succeed:\n `connection` | `server` (5xx) | `interrupted` → true; `auth` | `aborted` | non-auth `4xx` → false.",
14711473
"optional": false
14721474
},
14731475
{
@@ -1479,7 +1481,7 @@
14791481
{
14801482
"name": "status",
14811483
"type": "number",
1482-
"description": "",
1484+
"description": "The HTTP status code when the failure came from an HTTP response.",
14831485
"optional": true
14841486
}
14851487
],
@@ -7162,7 +7164,7 @@
71627164
{
71637165
"name": "AgentErrorKind",
71647166
"kind": "type",
7165-
"description": "",
7167+
"description": "The failure class of an AgentError, used to drive UI and retry logic:\n\n- `connection` — offline / DNS / connection refused / `fetch` failed. Retryable.\n- `auth` — `401` / `403`; credentials or API key are wrong. Not retryable.\n- `server` — a `5xx` (retryable) or a non-auth `4xx` like `400`/`404`/`429` (not retryable).\n- `interrupted` — the stream closed mid-response after a run had started. Retryable.\n- `aborted` — the user pressed stop; treated as a graceful idle, not surfaced as an error.",
71667168
"signature": "\"connection\" | \"auth\" | \"server\" | \"interrupted\" | \"aborted\"",
71677169
"examples": []
71687170
},
@@ -7351,7 +7353,7 @@
73517353
{
73527354
"name": "AGENT_ERROR_MESSAGES",
73537355
"kind": "const",
7354-
"description": "Default human-facing copy per kind.",
7356+
"description": "Default, human-facing copy per AgentErrorKind. Used as the message when\na classified error has no better text. Override by mapping `error.kind` to your\nown strings in a custom error component.",
73557357
"signature": "Record<AgentErrorKind, string>",
73567358
"examples": []
73577359
},
@@ -7838,13 +7840,13 @@
78387840
{
78397841
"name": "isAbortError",
78407842
"kind": "function",
7841-
"description": "True when `raw` represents a user-requested abort. Shared by adapters + classifier.",
7843+
"description": "Whether `raw` represents an abort (a `DOMException`/`Error` named `AbortError`,\nor an abort-ish message). Shared by the runtime adapters and toAgentError\nso a user-requested stop settles to idle instead of surfacing as an error.",
78427844
"signature": "isAbortError(raw: unknown): boolean",
78437845
"params": [
78447846
{
78457847
"name": "raw",
78467848
"type": "unknown",
7847-
"description": "",
7849+
"description": "Any thrown/rejected value.",
78487850
"optional": false
78497851
}
78507852
],
@@ -8162,21 +8164,23 @@
81628164
{
81638165
"name": "toAgentError",
81648166
"kind": "function",
8165-
"description": "Classify any raw error into a structured AgentError. Idempotent.",
8167+
"description": "Classify any raw error into a structured AgentError.\n\nResolution order (first match wins): an existing `AgentError` is returned\nunchanged (idempotent) → a user abort → a structured `status`/`cause.status`\n→ network/connection markers → an HTTP-shaped status in the message → a\n`server` + retryable fallback. The original error is always preserved on\n`cause`. Runtime adapters call this before setting `Agent.error`; custom\nbackends can call it too (or throw an `AgentError` directly).",
81668168
"signature": "toAgentError(raw: unknown): AgentError<>",
81678169
"params": [
81688170
{
81698171
"name": "raw",
81708172
"type": "unknown",
8171-
"description": "",
8173+
"description": "Any thrown/rejected value — an `Error`, a `{ status }` object, a string, etc.",
81728174
"optional": false
81738175
}
81748176
],
81758177
"returns": {
81768178
"type": "AgentError<>",
81778179
"description": ""
81788180
},
8179-
"examples": []
8181+
"examples": [
8182+
"```ts\nconst e = toAgentError(new Error('HTTP 500: Internal Server Error'));\ne.kind; // 'server'\ne.retryable; // true\ne.status; // 500\n```"
8183+
]
81808184
},
81818185
{
81828186
"name": "toClientToolSpecs",

libs/chat/src/lib/agent/agent-error.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
11
// SPDX-License-Identifier: MIT
2+
3+
/**
4+
* The failure class of an {@link AgentError}, used to drive UI and retry logic:
5+
*
6+
* - `connection` — offline / DNS / connection refused / `fetch` failed. Retryable.
7+
* - `auth` — `401` / `403`; credentials or API key are wrong. Not retryable.
8+
* - `server` — a `5xx` (retryable) or a non-auth `4xx` like `400`/`404`/`429` (not retryable).
9+
* - `interrupted` — the stream closed mid-response after a run had started. Retryable.
10+
* - `aborted` — the user pressed stop; treated as a graceful idle, not surfaced as an error.
11+
*/
212
export type AgentErrorKind = 'connection' | 'auth' | 'server' | 'interrupted' | 'aborted';
313

4-
/** Structured, classified failure surfaced on `Agent.error`. Extends `Error`
5-
* so existing `.message` / `instanceof Error` reads keep working. */
14+
/**
15+
* Structured, classified failure surfaced on `Agent.error`. Extends `Error`, so
16+
* existing `.message` / `instanceof Error` reads keep working — but adds a
17+
* machine-readable {@link AgentErrorKind}, a `retryable` flag, an optional HTTP
18+
* `status`, and the original `cause`.
19+
*
20+
* You rarely construct one yourself; adapters normalize raw failures via
21+
* {@link toAgentError}. Read it off the agent to render legible, cause-specific UI:
22+
*
23+
* @example
24+
* ```ts
25+
* const err = agent.error(); // AgentError | undefined
26+
* if (err) {
27+
* console.warn(err.message); // legible, per-kind copy
28+
* if (err.kind === 'auth') showApiKeyHelp();
29+
* if (err.retryable) showRetryButton(); // → agent.retry()
30+
* }
31+
* ```
32+
*/
633
export class AgentError extends Error {
34+
/** The classified failure type. See {@link AgentErrorKind}. */
735
readonly kind: AgentErrorKind;
8-
/** connection | server | interrupted → true; auth | aborted | non-auth-4xx → false. */
36+
/** Whether retrying the same request could plausibly succeed:
37+
* `connection` | `server` (5xx) | `interrupted` → true; `auth` | `aborted` | non-auth `4xx` → false. */
938
readonly retryable: boolean;
39+
/** The HTTP status code when the failure came from an HTTP response. */
1040
readonly status?: number;
41+
/** The original raw error this was classified from, preserved for debugging/telemetry. */
1142
override readonly cause: unknown;
1243

1344
constructor(init: { kind: AgentErrorKind; message: string; retryable: boolean; status?: number; cause?: unknown }) {
@@ -20,7 +51,11 @@ export class AgentError extends Error {
2051
}
2152
}
2253

23-
/** Default human-facing copy per kind. */
54+
/**
55+
* Default, human-facing copy per {@link AgentErrorKind}. Used as the message when
56+
* a classified error has no better text. Override by mapping `error.kind` to your
57+
* own strings in a custom error component.
58+
*/
2459
export const AGENT_ERROR_MESSAGES: Record<AgentErrorKind, string> = {
2560
connection: "Can't reach the server. Check your connection and try again.",
2661
auth: 'Authentication failed. Check your API key or credentials.',

libs/chat/src/lib/agent/to-agent-error.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
// SPDX-License-Identifier: MIT
22
import { AgentError, AGENT_ERROR_MESSAGES, type AgentErrorKind } from './agent-error';
33

4-
/** True when `raw` represents a user-requested abort. Shared by adapters + classifier. */
4+
/**
5+
* Whether `raw` represents an abort (a `DOMException`/`Error` named `AbortError`,
6+
* or an abort-ish message). Shared by the runtime adapters and {@link toAgentError}
7+
* so a user-requested stop settles to idle instead of surfacing as an error.
8+
*
9+
* @param raw Any thrown/rejected value.
10+
* @returns `true` if it looks like an abort.
11+
*/
512
export function isAbortError(raw: unknown): boolean {
613
return raw instanceof Error && (raw.name === 'AbortError' || /\babort/i.test(raw.message));
714
}
@@ -63,7 +70,26 @@ function classifyByStatus(status: number, raw: unknown): AgentError {
6370
return make('server', true, raw, undefined, 'Something went wrong. You can try again.');
6471
}
6572

66-
/** Classify any raw error into a structured {@link AgentError}. Idempotent. */
73+
/**
74+
* Classify any raw error into a structured {@link AgentError}.
75+
*
76+
* Resolution order (first match wins): an existing `AgentError` is returned
77+
* unchanged (idempotent) → a user abort → a structured `status`/`cause.status`
78+
* → network/connection markers → an HTTP-shaped status in the message → a
79+
* `server` + retryable fallback. The original error is always preserved on
80+
* `cause`. Runtime adapters call this before setting `Agent.error`; custom
81+
* backends can call it too (or throw an `AgentError` directly).
82+
*
83+
* @param raw Any thrown/rejected value — an `Error`, a `{ status }` object, a string, etc.
84+
* @returns The classified {@link AgentError} (kind, retryable, status?, cause).
85+
* @example
86+
* ```ts
87+
* const e = toAgentError(new Error('HTTP 500: Internal Server Error'));
88+
* e.kind; // 'server'
89+
* e.retryable; // true
90+
* e.status; // 500
91+
* ```
92+
*/
6793
export function toAgentError(raw: unknown): AgentError {
6894
if (raw instanceof AgentError) return raw;
6995
if (isAbortError(raw)) return make('aborted', false, raw);

0 commit comments

Comments
 (0)