@@ -6,15 +6,17 @@ export function isAbortError(raw: unknown): boolean {
66 return raw instanceof Error && ( raw . name === 'AbortError' || / \b a b o r t / i. test ( raw . message ) ) ;
77}
88
9- function readStatus ( raw : unknown ) : number | undefined {
9+ /**
10+ * Reads a numeric status from `raw.status` or `raw.cause.status` only.
11+ * No text parsing — structured fields only.
12+ */
13+ function structuredStatus ( raw : unknown ) : number | undefined {
1014 const obj = raw as { status ?: unknown ; cause ?: { status ?: unknown } } | null ;
1115 const direct = typeof obj ?. status === 'number' ? obj . status : undefined ;
1216 const viaCause = typeof obj ?. cause ?. status === 'number' ? obj ! . cause ! . status : undefined ;
1317 if ( direct !== undefined ) return direct ;
1418 if ( viaCause !== undefined ) return viaCause ;
15- const msg = raw instanceof Error ? raw . message : typeof raw === 'string' ? raw : '' ;
16- const m = / \b ( \d { 3 } ) \b / . exec ( msg ) ;
17- return m ? Number ( m [ 1 ] ) : undefined ;
19+ return undefined ;
1820}
1921
2022function isConnectionError ( raw : unknown ) : boolean {
@@ -24,23 +26,60 @@ function isConnectionError(raw: unknown): boolean {
2426 ) ;
2527}
2628
29+ /**
30+ * Extracts an HTTP status code from a message string, but ONLY when the token
31+ * is unambiguously HTTP-shaped:
32+ * - `HTTP/502`, `HTTP 404`, `HTTP404`
33+ * - `status: 503`, `status=503`, `code: 404`
34+ *
35+ * Bare 3-digit numbers (e.g. model version strings like "gpt-500") are NOT matched.
36+ */
37+ function httpStatusFromMessage ( raw : unknown ) : number | undefined {
38+ const msg =
39+ raw instanceof Error ? raw . message : typeof raw === 'string' ? raw : '' ;
40+ if ( ! msg ) return undefined ;
41+
42+ // "HTTP 500", "HTTP/500", "HTTP500"
43+ const httpToken = / \b H T T P [ / ] ? ( \d { 3 } ) \b / i. exec ( msg ) ;
44+ if ( httpToken ) return Number ( httpToken [ 1 ] ) ;
45+
46+ // "status: 503", "status=503", "status 503" (up to 4 non-digit chars between keyword and digits)
47+ // Also matches "code: 404", "code=404", etc.
48+ const prefixed = / \b (?: s t a t u s | c o d e ) \b \D { 0 , 4 } ( \d { 3 } ) \b / i. exec ( msg ) ;
49+ if ( prefixed ) return Number ( prefixed [ 1 ] ) ;
50+
51+ return undefined ;
52+ }
53+
2754function make ( kind : AgentErrorKind , retryable : boolean , raw : unknown , status ?: number , message ?: string ) : AgentError {
2855 return new AgentError ( { kind, retryable, status, cause : raw , message : message ?? AGENT_ERROR_MESSAGES [ kind ] } ) ;
2956}
3057
58+ function classifyByStatus ( status : number , raw : unknown ) : AgentError {
59+ if ( status === 401 || status === 403 ) return make ( 'auth' , false , raw , status ) ;
60+ if ( status >= 500 ) return make ( 'server' , true , raw , status ) ;
61+ if ( status >= 400 ) return make ( 'server' , false , raw , status , `The request was rejected (HTTP ${ status } ).` ) ;
62+ // Stray 2xx/3xx from a status field — treat as unknown transient failure, no status.
63+ return make ( 'server' , true , raw , undefined , 'Something went wrong. You can try again.' ) ;
64+ }
65+
3166/** Classify any raw error into a structured {@link AgentError}. Idempotent. */
3267export function toAgentError ( raw : unknown ) : AgentError {
3368 if ( raw instanceof AgentError ) return raw ;
3469 if ( isAbortError ( raw ) ) return make ( 'aborted' , false , raw ) ;
3570
36- const status = readStatus ( raw ) ;
37- if ( status !== undefined ) {
38- if ( status === 401 || status === 403 ) return make ( 'auth' , false , raw , status ) ;
39- if ( status >= 500 ) return make ( 'server' , true , raw , status ) ;
40- if ( status >= 400 ) return make ( 'server' , false , raw , status , `The request was rejected (HTTP ${ status } ).` ) ;
41- }
71+ // 1. Structured status (authoritative): raw.status or raw.cause.status.
72+ const structured = structuredStatus ( raw ) ;
73+ if ( structured !== undefined ) return classifyByStatus ( structured , raw ) ;
74+
75+ // 2. Network/connection markers are definitive — before any loose text parsing.
4276 if ( isConnectionError ( raw ) ) return make ( 'connection' , true , raw ) ;
4377
78+ // 3. Best-effort: only an HTTP-shaped status token in the message counts.
79+ const httpStatus = httpStatusFromMessage ( raw ) ;
80+ if ( httpStatus !== undefined ) return classifyByStatus ( httpStatus , raw ) ;
81+
82+ // 4. Fallback: unknown failure, assume transient.
4483 const msg = raw instanceof Error && raw . message ? raw . message : 'Something went wrong. You can try again.' ;
45- return make ( 'server' , true , raw , status , msg ) ;
84+ return make ( 'server' , true , raw , undefined , msg ) ;
4685}
0 commit comments