llm-errors 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +14 -5
- package/dist/index.cjs +133 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +133 -31
- package/dist/index.js.map +1 -1
- package/fixtures/README.md +5 -2
- package/fixtures/cases/anthropic/rate-limit-credit-balance.json +17 -0
- package/fixtures/cases/anthropic/sdk-billing-error.json +14 -0
- package/fixtures/cases/gemini/plain-unavailable-body.json +9 -0
- package/fixtures/cases/gemini/rpc-quota-exhausted-billing.json +11 -0
- package/fixtures/cases/gemini/rpc-quota-exhausted-rate-bucket.json +11 -0
- package/fixtures/cases/generic/http-400-retry-after.json +15 -0
- package/fixtures/cases/generic/http-503-retry-after.json +15 -0
- package/fixtures/cases/openai/plain-rate-limit-body.json +10 -0
- package/fixtures/cases/openai/sdk-billing-hard-limit.json +12 -0
- package/fixtures/expected/anthropic/rate-limit-credit-balance.json +8 -0
- package/fixtures/expected/anthropic/sdk-billing-error.json +8 -0
- package/fixtures/expected/gemini/plain-unavailable-body.json +8 -0
- package/fixtures/expected/gemini/rpc-quota-exhausted-billing.json +8 -0
- package/fixtures/expected/gemini/rpc-quota-exhausted-rate-bucket.json +8 -0
- package/fixtures/expected/generic/http-400-retry-after.json +7 -0
- package/fixtures/expected/generic/http-503-retry-after.json +8 -0
- package/fixtures/expected/openai/plain-rate-limit-body.json +7 -0
- package/fixtures/expected/openai/sdk-billing-hard-limit.json +7 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -61,8 +61,9 @@ interface NormalizedError {
|
|
|
61
61
|
retryable: boolean;
|
|
62
62
|
/**
|
|
63
63
|
* Suggested delay in milliseconds before retrying, derived from the provider
|
|
64
|
-
* (`Retry-After` header, Google `RetryInfo`, etc.)
|
|
65
|
-
* provider did not specify one
|
|
64
|
+
* (`Retry-After` header, Google `RetryInfo`, etc.) for retryable errors.
|
|
65
|
+
* `undefined` when the provider did not specify one, or when the normalized
|
|
66
|
+
* category is not retryable — use {@link getRetryDelayMs} for a fallback.
|
|
66
67
|
*/
|
|
67
68
|
retryAfterMs?: number;
|
|
68
69
|
/** The original value passed to {@link normalizeError}, untouched. */
|
|
@@ -89,7 +90,8 @@ interface RetryDelayOptions {
|
|
|
89
90
|
/**
|
|
90
91
|
* Jitter strategy applied to the exponential delay. `'full'` picks a random
|
|
91
92
|
* value in `[0, delay]`, `'none'` disables jitter. Default `'full'`.
|
|
92
|
-
* Ignored when the provider supplied an explicit `retryAfterMs
|
|
93
|
+
* Ignored when the provider supplied an explicit `retryAfterMs`, or when the
|
|
94
|
+
* error is not retryable.
|
|
93
95
|
*/
|
|
94
96
|
jitter?: 'full' | 'none';
|
|
95
97
|
}
|
|
@@ -137,8 +139,9 @@ declare function parseGoogleRetryDelay(details: unknown): number | undefined;
|
|
|
137
139
|
/**
|
|
138
140
|
* Suggested delay before retrying, in milliseconds.
|
|
139
141
|
*
|
|
140
|
-
* When the provider supplied an explicit
|
|
141
|
-
*
|
|
142
|
+
* Non-retryable errors return `0`. When the provider supplied an explicit
|
|
143
|
+
* valid delay (`error.retryAfterMs`) it is respected. Otherwise this falls
|
|
144
|
+
* back to exponential backoff:
|
|
142
145
|
* `baseMs * 2 ** attempt`, capped at `maxMs`, with optional full jitter.
|
|
143
146
|
*
|
|
144
147
|
* @param error A {@link NormalizedError}.
|
package/dist/index.js
CHANGED
|
@@ -10,14 +10,45 @@ function firstString(...values) {
|
|
|
10
10
|
}
|
|
11
11
|
return void 0;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function httpStatus(value) {
|
|
14
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && /^[1-5]\d{2}$/.test(value.trim()) ? Number(value) : void 0;
|
|
15
|
+
if (typeof numeric === "number" && Number.isInteger(numeric) && numeric >= 100 && numeric <= 599) {
|
|
16
|
+
return numeric;
|
|
17
|
+
}
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
function firstHttpStatus(...values) {
|
|
14
21
|
for (const value of values) {
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
const status = httpStatus(value);
|
|
23
|
+
if (status !== void 0) {
|
|
24
|
+
return status;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
function headerValueToString(value) {
|
|
30
|
+
if (typeof value === "string") {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
34
|
+
return String(value);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
for (const entry of value) {
|
|
38
|
+
const stringValue = headerValueToString(entry);
|
|
39
|
+
if (stringValue !== void 0) {
|
|
40
|
+
return stringValue;
|
|
41
|
+
}
|
|
17
42
|
}
|
|
18
43
|
}
|
|
19
44
|
return void 0;
|
|
20
45
|
}
|
|
46
|
+
function headerPairValue(entry, lower) {
|
|
47
|
+
if (Array.isArray(entry) && typeof entry[0] === "string" && entry[0].toLowerCase() === lower) {
|
|
48
|
+
return headerValueToString(entry[1]);
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
21
52
|
function getHeader(headers, name) {
|
|
22
53
|
if (!headers) {
|
|
23
54
|
return void 0;
|
|
@@ -25,36 +56,44 @@ function getHeader(headers, name) {
|
|
|
25
56
|
const lower = name.toLowerCase();
|
|
26
57
|
if (typeof headers.get === "function") {
|
|
27
58
|
const value = headers.get(name);
|
|
28
|
-
|
|
59
|
+
const stringValue = headerValueToString(value);
|
|
60
|
+
if (stringValue !== void 0) {
|
|
61
|
+
return stringValue;
|
|
62
|
+
}
|
|
29
63
|
}
|
|
30
|
-
if (
|
|
64
|
+
if (typeof headers[Symbol.iterator] === "function") {
|
|
31
65
|
for (const entry of headers) {
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
const value = headerPairValue(entry, lower);
|
|
67
|
+
if (value !== void 0) {
|
|
68
|
+
return value;
|
|
34
69
|
}
|
|
35
70
|
}
|
|
36
|
-
return void 0;
|
|
37
71
|
}
|
|
38
72
|
if (isObject(headers)) {
|
|
39
73
|
for (const key of Object.keys(headers)) {
|
|
40
74
|
if (key.toLowerCase() === lower) {
|
|
41
75
|
const value = headers[key];
|
|
42
|
-
return
|
|
76
|
+
return headerValueToString(value);
|
|
43
77
|
}
|
|
44
78
|
}
|
|
45
79
|
}
|
|
46
80
|
return void 0;
|
|
47
81
|
}
|
|
82
|
+
function looksLikeProviderErrorBody(value) {
|
|
83
|
+
return typeof value.type === "string" || typeof value.code === "string" || typeof value.code === "number" && typeof value.status === "string" || typeof value.status === "string" || Array.isArray(value.details) || "param" in value;
|
|
84
|
+
}
|
|
48
85
|
function readStatus(error) {
|
|
49
86
|
if (!isObject(error)) {
|
|
50
87
|
return void 0;
|
|
51
88
|
}
|
|
52
89
|
const response = isObject(error.response) ? error.response : void 0;
|
|
53
90
|
const inner = isObject(error.error) ? error.error : void 0;
|
|
54
|
-
return
|
|
91
|
+
return firstHttpStatus(
|
|
55
92
|
error.status,
|
|
56
93
|
error.statusCode,
|
|
94
|
+
looksLikeProviderErrorBody(error) ? error.code : void 0,
|
|
57
95
|
response?.status,
|
|
96
|
+
response?.statusCode,
|
|
58
97
|
// Google encodes the status as `error.code` (a numeric HTTP status).
|
|
59
98
|
inner?.code
|
|
60
99
|
);
|
|
@@ -70,10 +109,15 @@ function readErrorBody(error) {
|
|
|
70
109
|
if (!isObject(error)) {
|
|
71
110
|
return void 0;
|
|
72
111
|
}
|
|
112
|
+
const body = isObject(error.body) ? error.body : void 0;
|
|
113
|
+
const responseData = isObject(error.response) && isObject(error.response.data) ? error.response.data : void 0;
|
|
73
114
|
const candidates = [
|
|
74
115
|
isObject(error.error) && isObject(error.error.error) ? error.error.error : error.error,
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
body?.error,
|
|
117
|
+
responseData?.error,
|
|
118
|
+
body && looksLikeProviderErrorBody(body) ? body : void 0,
|
|
119
|
+
responseData && looksLikeProviderErrorBody(responseData) ? responseData : void 0,
|
|
120
|
+
looksLikeProviderErrorBody(error) ? error : void 0
|
|
77
121
|
];
|
|
78
122
|
for (const candidate of candidates) {
|
|
79
123
|
if (isObject(candidate)) {
|
|
@@ -206,6 +250,9 @@ function classifyNetworkError(error) {
|
|
|
206
250
|
}
|
|
207
251
|
|
|
208
252
|
// src/retry.ts
|
|
253
|
+
function nonNegativeFinite(value) {
|
|
254
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
255
|
+
}
|
|
209
256
|
function parseRetryAfter(value, unit = "s") {
|
|
210
257
|
if (value === void 0) {
|
|
211
258
|
return void 0;
|
|
@@ -219,6 +266,9 @@ function parseRetryAfter(value, unit = "s") {
|
|
|
219
266
|
const ms = unit === "ms" ? numeric : numeric * 1e3;
|
|
220
267
|
return ms >= 0 ? ms : void 0;
|
|
221
268
|
}
|
|
269
|
+
if (unit === "ms") {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
222
272
|
const date = Date.parse(trimmed);
|
|
223
273
|
if (Number.isFinite(date)) {
|
|
224
274
|
return Math.max(0, date - Date.now());
|
|
@@ -246,8 +296,8 @@ function parseGoogleRetryDelay(details) {
|
|
|
246
296
|
}
|
|
247
297
|
if (isObject(delay)) {
|
|
248
298
|
const seconds = typeof delay.seconds === "number" ? delay.seconds : Number(delay.seconds);
|
|
249
|
-
const nanos = typeof delay.nanos === "number" ? delay.nanos :
|
|
250
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
299
|
+
const nanos = delay.nanos === void 0 ? 0 : typeof delay.nanos === "number" ? delay.nanos : Number(delay.nanos);
|
|
300
|
+
if (Number.isFinite(seconds) && seconds >= 0 && Number.isFinite(nanos) && nanos >= 0 && nanos < 1e9) {
|
|
251
301
|
return seconds * 1e3 + Math.round(nanos / 1e6);
|
|
252
302
|
}
|
|
253
303
|
}
|
|
@@ -255,13 +305,17 @@ function parseGoogleRetryDelay(details) {
|
|
|
255
305
|
return void 0;
|
|
256
306
|
}
|
|
257
307
|
function getRetryDelayMs(error, attempt, options = {}) {
|
|
258
|
-
if (
|
|
259
|
-
return
|
|
308
|
+
if (!error.retryable) {
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
const explicitDelay = nonNegativeFinite(error.retryAfterMs);
|
|
312
|
+
if (explicitDelay !== void 0) {
|
|
313
|
+
return explicitDelay;
|
|
260
314
|
}
|
|
261
|
-
const baseMs = options.baseMs ?? 500;
|
|
262
|
-
const maxMs = options.maxMs ?? 6e4;
|
|
315
|
+
const baseMs = nonNegativeFinite(options.baseMs) ?? 500;
|
|
316
|
+
const maxMs = nonNegativeFinite(options.maxMs) ?? 6e4;
|
|
263
317
|
const jitter = options.jitter ?? "full";
|
|
264
|
-
const safeAttempt = Number.isFinite(attempt) && attempt > 0 ? attempt : 0;
|
|
318
|
+
const safeAttempt = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
|
|
265
319
|
const exponential = Math.min(maxMs, baseMs * 2 ** safeAttempt);
|
|
266
320
|
if (jitter === "none") {
|
|
267
321
|
return exponential;
|
|
@@ -309,8 +363,10 @@ function classify(ctx) {
|
|
|
309
363
|
let category = mapped ?? baseCategoryFromStatus(ctx.status);
|
|
310
364
|
if (category === "invalid_request" && (message.includes("prompt is too long") || message.includes("maximum context") || message.includes("context window"))) {
|
|
311
365
|
category = "context_length_exceeded";
|
|
366
|
+
} else if (message.includes("credit balance") || message.includes("billing") || message.includes("insufficient quota")) {
|
|
367
|
+
category = "insufficient_quota";
|
|
312
368
|
}
|
|
313
|
-
const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
369
|
+
const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, "retry-after-ms"), "ms") ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
314
370
|
return { category, code: type, retryAfterMs };
|
|
315
371
|
}
|
|
316
372
|
|
|
@@ -339,6 +395,10 @@ function rpcStatus(ctx) {
|
|
|
339
395
|
}
|
|
340
396
|
return void 0;
|
|
341
397
|
}
|
|
398
|
+
function isQuotaExhaustedMessage(message) {
|
|
399
|
+
const lower = message.toLowerCase();
|
|
400
|
+
return lower.includes("quota") && (lower.includes("billing") || lower.includes("paid plan") || lower.includes("free tier") || lower.includes("check your plan") || lower.includes("upgrade"));
|
|
401
|
+
}
|
|
342
402
|
function matches2(ctx) {
|
|
343
403
|
if (rpcStatus(ctx) !== void 0) {
|
|
344
404
|
return true;
|
|
@@ -347,13 +407,31 @@ function matches2(ctx) {
|
|
|
347
407
|
}
|
|
348
408
|
function classify2(ctx) {
|
|
349
409
|
const status = rpcStatus(ctx);
|
|
410
|
+
const message = firstString(ctx.body?.message) ?? "";
|
|
411
|
+
const retryAfterMs = parseGoogleRetryDelay(ctx.body?.details) ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after-ms"), "ms") ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
350
412
|
const mapped = status ? RPC_STATUS[status] : void 0;
|
|
351
|
-
|
|
352
|
-
|
|
413
|
+
let category = mapped ?? baseCategoryFromStatus(ctx.status);
|
|
414
|
+
if (status === "RESOURCE_EXHAUSTED" && retryAfterMs === void 0 && isQuotaExhaustedMessage(message)) {
|
|
415
|
+
category = "insufficient_quota";
|
|
416
|
+
}
|
|
353
417
|
return { category, code: status, retryAfterMs };
|
|
354
418
|
}
|
|
355
419
|
|
|
356
420
|
// src/providers/openai.ts
|
|
421
|
+
var OPENAI_CODES = /* @__PURE__ */ new Set([
|
|
422
|
+
"billing_hard_limit_reached",
|
|
423
|
+
"billing_not_active",
|
|
424
|
+
"context_length_exceeded",
|
|
425
|
+
"content_filter",
|
|
426
|
+
"content_policy_violation",
|
|
427
|
+
"insufficient_quota",
|
|
428
|
+
"invalid_api_key",
|
|
429
|
+
"model_not_found",
|
|
430
|
+
"rate_limit_exceeded"
|
|
431
|
+
]);
|
|
432
|
+
function includesAny(haystack, needles) {
|
|
433
|
+
return needles.some((needle) => haystack.includes(needle));
|
|
434
|
+
}
|
|
357
435
|
function matches3(ctx) {
|
|
358
436
|
if (firstHeader(
|
|
359
437
|
ctx.headers,
|
|
@@ -371,24 +449,46 @@ function matches3(ctx) {
|
|
|
371
449
|
return true;
|
|
372
450
|
}
|
|
373
451
|
const code = firstString(body.code);
|
|
374
|
-
return code
|
|
452
|
+
return code !== void 0 && OPENAI_CODES.has(code);
|
|
375
453
|
}
|
|
376
454
|
function classify3(ctx) {
|
|
377
455
|
const body = ctx.body ?? {};
|
|
378
456
|
const type = firstString(body.type);
|
|
379
457
|
const code = firstString(body.code);
|
|
380
|
-
const
|
|
458
|
+
const message = firstString(body.message) ?? "";
|
|
459
|
+
const identifier = `${type ?? ""} ${code ?? ""} ${message}`.toLowerCase();
|
|
381
460
|
let category = baseCategoryFromStatus(ctx.status);
|
|
382
461
|
if (identifier.includes("context_length") || identifier.includes("context window")) {
|
|
383
462
|
category = "context_length_exceeded";
|
|
384
|
-
} else if (identifier
|
|
463
|
+
} else if (includesAny(identifier, [
|
|
464
|
+
"insufficient_quota",
|
|
465
|
+
"billing_hard_limit",
|
|
466
|
+
"billing_not_active",
|
|
467
|
+
"exceeded your current quota"
|
|
468
|
+
])) {
|
|
385
469
|
category = "insufficient_quota";
|
|
386
|
-
} else if (
|
|
470
|
+
} else if (includesAny(identifier, [
|
|
471
|
+
"content_filter",
|
|
472
|
+
"content_policy",
|
|
473
|
+
"safety_policy"
|
|
474
|
+
])) {
|
|
387
475
|
category = "content_filter";
|
|
388
|
-
} else if (code === "invalid_api_key" || identifier
|
|
476
|
+
} else if (code === "invalid_api_key" || includesAny(identifier, ["authentication", "unauthorized"])) {
|
|
389
477
|
category = "authentication";
|
|
390
|
-
} else if (
|
|
478
|
+
} else if (includesAny(identifier, ["permission", "forbidden"])) {
|
|
479
|
+
category = "permission";
|
|
480
|
+
} else if (includesAny(identifier, ["not_found", "model_not_found"])) {
|
|
481
|
+
category = "not_found";
|
|
482
|
+
} else if (includesAny(identifier, ["timeout", "timed out"])) {
|
|
483
|
+
category = "timeout";
|
|
484
|
+
} else if (includesAny(identifier, ["overload", "unavailable"])) {
|
|
485
|
+
category = "overloaded";
|
|
486
|
+
} else if (includesAny(identifier, ["server_error", "api_error"])) {
|
|
487
|
+
category = "server_error";
|
|
488
|
+
} else if (identifier.includes("rate_limit")) {
|
|
391
489
|
category = "rate_limit";
|
|
490
|
+
} else if (category === "unknown" && identifier.includes("invalid_request")) {
|
|
491
|
+
category = "invalid_request";
|
|
392
492
|
}
|
|
393
493
|
const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, "retry-after-ms"), "ms") ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
394
494
|
return { category, code: code ?? type, retryAfterMs };
|
|
@@ -419,7 +519,8 @@ function classifyFor(provider, ctx) {
|
|
|
419
519
|
default:
|
|
420
520
|
return {
|
|
421
521
|
category: baseCategoryFromStatus(ctx.status),
|
|
422
|
-
code: firstString(ctx.body?.type, ctx.body?.code)
|
|
522
|
+
code: firstString(ctx.body?.type, ctx.body?.code),
|
|
523
|
+
retryAfterMs: parseRetryAfter(firstHeader(ctx.headers, "retry-after-ms"), "ms") ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"))
|
|
423
524
|
};
|
|
424
525
|
}
|
|
425
526
|
}
|
|
@@ -440,14 +541,15 @@ function normalizeError(error, options = {}) {
|
|
|
440
541
|
code = code ?? network.code;
|
|
441
542
|
}
|
|
442
543
|
}
|
|
544
|
+
const retryable = isRetryableCategory(category);
|
|
443
545
|
return {
|
|
444
546
|
provider,
|
|
445
547
|
category,
|
|
446
548
|
message: readMessage(error, ctx.body),
|
|
447
549
|
status: ctx.status,
|
|
448
550
|
code,
|
|
449
|
-
retryable
|
|
450
|
-
retryAfterMs: classification.retryAfterMs,
|
|
551
|
+
retryable,
|
|
552
|
+
retryAfterMs: retryable ? classification.retryAfterMs : void 0,
|
|
451
553
|
raw: error
|
|
452
554
|
};
|
|
453
555
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal.ts","../src/classify.ts","../src/network.ts","../src/retry.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/normalize.ts"],"sourcesContent":["/**\n * Internal probing helpers. These intentionally accept `unknown` and never\n * throw: error objects arrive in many shapes (SDK error classes, raw `fetch`\n * responses, plain JSON) and the library must degrade gracefully on any of\n * them.\n */\n\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Return the first argument that is a non-empty string. */\nexport function firstString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === 'string' && value.length > 0) {\n return value;\n }\n }\n return undefined;\n}\n\n/** Return the first argument that is a finite number. */\nexport function firstNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n }\n return undefined;\n}\n\n/**\n * Read a header by name from the many container shapes an error may carry:\n * a `Headers` instance, a plain object, a `Map`, or an array of `[k, v]`\n * pairs. Lookup is case-insensitive.\n */\nexport function getHeader(headers: unknown, name: string): string | undefined {\n if (!headers) {\n return undefined;\n }\n const lower = name.toLowerCase();\n\n // `Headers` (fetch) or `Map`-like: has a `.get` method.\n if (typeof (headers as { get?: unknown }).get === 'function') {\n const value = (headers as { get(key: string): unknown }).get(name);\n return typeof value === 'string' ? value : undefined;\n }\n\n // Array of [key, value] pairs.\n if (Array.isArray(headers)) {\n for (const entry of headers) {\n if (\n Array.isArray(entry) &&\n typeof entry[0] === 'string' &&\n entry[0].toLowerCase() === lower\n ) {\n return typeof entry[1] === 'string' ? entry[1] : undefined;\n }\n }\n return undefined;\n }\n\n // Plain object: case-insensitive key scan.\n if (isObject(headers)) {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lower) {\n const value = headers[key];\n return typeof value === 'string' ? value : undefined;\n }\n }\n }\n\n return undefined;\n}\n\n/** Extract an HTTP status code from common error shapes. */\nexport function readStatus(error: unknown): number | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n const response = isObject(error.response) ? error.response : undefined;\n const inner = isObject(error.error) ? error.error : undefined;\n return firstNumber(\n error.status,\n error.statusCode,\n response?.status,\n // Google encodes the status as `error.code` (a numeric HTTP status).\n inner?.code,\n );\n}\n\n/** Extract the header container from common error shapes. */\nexport function readHeaders(error: unknown): unknown {\n if (!isObject(error)) {\n return undefined;\n }\n const response = isObject(error.response) ? error.response : undefined;\n return error.headers ?? response?.headers;\n}\n\n/**\n * Extract the provider error body — the object that holds `type` / `code` /\n * `message`. SDK error classes expose it at `error.error`; raw responses may\n * nest it under `error.body.error` or `error.response.data.error`.\n */\nexport function readErrorBody(\n error: unknown,\n): Record<string, unknown> | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n\n // Anthropic wraps as `{ type: 'error', error: {...} }`; OpenAI/Gemini SDK\n // error objects expose the inner payload directly at `.error`.\n const candidates: unknown[] = [\n isObject(error.error) && isObject(error.error.error)\n ? error.error.error\n : error.error,\n isObject(error.body)\n ? (error.body as Record<string, unknown>).error\n : undefined,\n isObject(error.response) && isObject(error.response.data)\n ? (error.response.data as Record<string, unknown>).error\n : undefined,\n ];\n\n for (const candidate of candidates) {\n if (isObject(candidate)) {\n return candidate;\n }\n }\n return undefined;\n}\n\n/** Best-effort human-readable message from an error of any shape. */\nexport function readMessage(\n error: unknown,\n body: Record<string, unknown> | undefined,\n): string {\n const fromBody = body ? firstString(body.message) : undefined;\n if (fromBody) {\n return fromBody;\n }\n if (isObject(error)) {\n const direct = firstString(error.message);\n if (direct) {\n return direct;\n }\n }\n if (typeof error === 'string' && error.length > 0) {\n return error;\n }\n return 'Unknown error';\n}\n","import { getHeader } from './internal.ts';\nimport type { ErrorCategory } from './types.ts';\n\n/**\n * The pre-parsed pieces of an error that every provider classifier and the\n * detector operate on. Kept internal to the package.\n */\nexport interface ProviderContext {\n status?: number;\n /** The provider error body (`{ type, code, message, ... }`), if found. */\n body: Record<string, unknown> | undefined;\n /** The raw header container (`Headers`, object, pairs), if found. */\n headers: unknown;\n}\n\n/** The result a provider classifier contributes to the normalized error. */\nexport interface Classification {\n category: ErrorCategory;\n code?: string;\n retryAfterMs?: number;\n}\n\n/**\n * Map an HTTP status code to a category, ignoring provider specifics. Provider\n * classifiers start here and then refine using their `code` / `type` strings.\n */\nexport function baseCategoryFromStatus(status?: number): ErrorCategory {\n switch (status) {\n case 401:\n return 'authentication';\n case 403:\n return 'permission';\n case 404:\n return 'not_found';\n case 408:\n return 'timeout';\n case 413:\n return 'request_too_large';\n case 400:\n case 422:\n return 'invalid_request';\n case 429:\n return 'rate_limit';\n case 500:\n return 'server_error';\n case 502:\n return 'server_error';\n case 503:\n return 'overloaded';\n case 504:\n return 'timeout';\n case 529:\n return 'overloaded';\n default:\n break;\n }\n if (typeof status === 'number') {\n if (status >= 500) {\n return 'server_error';\n }\n if (status >= 400) {\n return 'invalid_request';\n }\n }\n return 'unknown';\n}\n\n/** Categories that are safe to retry after a delay. */\nconst RETRYABLE: ReadonlySet<ErrorCategory> = new Set<ErrorCategory>([\n 'rate_limit',\n 'server_error',\n 'overloaded',\n 'timeout',\n]);\n\n/** Whether a category represents a transient, retryable condition. */\nexport function isRetryableCategory(category: ErrorCategory): boolean {\n return RETRYABLE.has(category);\n}\n\n/** Read the first present header from a list of candidate names. */\nexport function firstHeader(\n headers: unknown,\n ...names: string[]\n): string | undefined {\n for (const name of names) {\n const value = getHeader(headers, name);\n if (value !== undefined) {\n return value;\n }\n }\n return undefined;\n}\n","import { firstString, isObject } from './internal.ts';\nimport type { ErrorCategory } from './types.ts';\n\n/**\n * Transport-level failures never reach an HTTP response, so they carry no\n * status code or provider body — yet most of them (timeouts, dropped\n * connections, DNS hiccups) are very much worth retrying. This recognizes\n * them from the Node `code`, the `name`, or the SDK error class name.\n */\n\n/** Node `error.code` values that mean \"the connection failed; try again\". */\nconst RETRYABLE_CODES: Record<string, ErrorCategory> = {\n ETIMEDOUT: 'timeout',\n ESOCKETTIMEDOUT: 'timeout',\n ECONNRESET: 'server_error',\n ECONNREFUSED: 'server_error',\n ECONNABORTED: 'server_error',\n EPIPE: 'server_error',\n ENOTFOUND: 'server_error',\n EAI_AGAIN: 'server_error',\n EHOSTUNREACH: 'server_error',\n ENETUNREACH: 'server_error',\n};\n\n/**\n * Error `name` / constructor names, matched case-insensitively as substrings,\n * mapped to a category. Covers `AbortError`, the Fetch `TimeoutError`, and the\n * OpenAI / Anthropic SDK connection error classes.\n */\nconst NAME_PATTERNS: ReadonlyArray<[pattern: string, category: ErrorCategory]> =\n [\n ['timeout', 'timeout'],\n ['aborterror', 'timeout'],\n ['connectionerror', 'server_error'],\n ['connection error', 'server_error'],\n ['fetcherror', 'server_error'],\n ];\n\nexport interface NetworkClassification {\n category: ErrorCategory;\n code?: string;\n}\n\n/**\n * Try to classify a transport-level error. Returns `undefined` when the value\n * does not look like a network failure, so the caller can fall back to its\n * normal (HTTP-based) classification.\n */\nexport function classifyNetworkError(\n error: unknown,\n): NetworkClassification | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n\n const code = firstString(error.code);\n if (code && code in RETRYABLE_CODES) {\n return { category: RETRYABLE_CODES[code], code };\n }\n\n // Check both the instance `name` and the constructor name: a subclass of\n // `Error` that doesn't override `name` still reports `\"Error\"`, so the\n // distinguishing signal lives on `constructor.name`.\n const names = [\n firstString(error.name),\n firstString((error.constructor as { name?: unknown } | undefined)?.name),\n ];\n for (const name of names) {\n if (!name) {\n continue;\n }\n const haystack = name.toLowerCase();\n for (const [pattern, category] of NAME_PATTERNS) {\n if (haystack.includes(pattern)) {\n return { category, code: name };\n }\n }\n }\n\n return undefined;\n}\n","import { isObject } from './internal.ts';\nimport type { NormalizedError, RetryDelayOptions } from './types.ts';\n\n/**\n * Parse a `Retry-After` header value into milliseconds.\n *\n * Per RFC 7231 the value is either a number of seconds (`\"30\"`) or an\n * HTTP date (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Some providers also send a\n * fractional `retry-after-ms` value, which is accepted when `unit` is `'ms'`.\n */\nexport function parseRetryAfter(\n value: string | undefined,\n unit: 's' | 'ms' = 's',\n): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed === '') {\n return undefined;\n }\n\n const numeric = Number(trimmed);\n if (Number.isFinite(numeric)) {\n const ms = unit === 'ms' ? numeric : numeric * 1000;\n return ms >= 0 ? ms : undefined;\n }\n\n const date = Date.parse(trimmed);\n if (Number.isFinite(date)) {\n return Math.max(0, date - Date.now());\n }\n\n return undefined;\n}\n\n/**\n * Parse a Google `RetryInfo.retryDelay` value into milliseconds.\n *\n * Google encodes it either as a duration string (`\"30s\"`, `\"1.5s\"`) or as a\n * `{ seconds, nanos }` object inside `error.details[]`.\n */\nexport function parseGoogleRetryDelay(details: unknown): number | undefined {\n if (!Array.isArray(details)) {\n return undefined;\n }\n for (const detail of details) {\n if (!isObject(detail)) {\n continue;\n }\n const type = detail['@type'];\n if (typeof type === 'string' && !type.includes('RetryInfo')) {\n continue;\n }\n const delay = detail.retryDelay;\n if (typeof delay === 'string') {\n const seconds = Number(delay.replace(/s$/, ''));\n if (Number.isFinite(seconds) && seconds >= 0) {\n return seconds * 1000;\n }\n }\n if (isObject(delay)) {\n const seconds =\n typeof delay.seconds === 'number'\n ? delay.seconds\n : Number(delay.seconds);\n const nanos = typeof delay.nanos === 'number' ? delay.nanos : 0;\n if (Number.isFinite(seconds) && seconds >= 0) {\n return seconds * 1000 + Math.round(nanos / 1e6);\n }\n }\n }\n return undefined;\n}\n\n/**\n * Suggested delay before retrying, in milliseconds.\n *\n * When the provider supplied an explicit delay (`error.retryAfterMs`) it is\n * always respected. Otherwise this falls back to exponential backoff:\n * `baseMs * 2 ** attempt`, capped at `maxMs`, with optional full jitter.\n *\n * @param error A {@link NormalizedError}.\n * @param attempt Zero-based retry attempt number (0 for the first retry).\n * @param options Backoff tuning. See {@link RetryDelayOptions}.\n */\nexport function getRetryDelayMs(\n error: NormalizedError,\n attempt: number,\n options: RetryDelayOptions = {},\n): number {\n if (typeof error.retryAfterMs === 'number') {\n return error.retryAfterMs;\n }\n\n const baseMs = options.baseMs ?? 500;\n const maxMs = options.maxMs ?? 60000;\n const jitter = options.jitter ?? 'full';\n\n const safeAttempt = Number.isFinite(attempt) && attempt > 0 ? attempt : 0;\n const exponential = Math.min(maxMs, baseMs * 2 ** safeAttempt);\n\n if (jitter === 'none') {\n return exponential;\n }\n return Math.random() * exponential;\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\nconst ANTHROPIC_TYPES: Record<string, ErrorCategory> = {\n authentication_error: 'authentication',\n permission_error: 'permission',\n not_found_error: 'not_found',\n request_too_large: 'request_too_large',\n rate_limit_error: 'rate_limit',\n invalid_request_error: 'invalid_request',\n api_error: 'server_error',\n overloaded_error: 'overloaded',\n billing_error: 'insufficient_quota',\n timeout_error: 'timeout',\n};\n\n/** Heuristic: does this error look like it came from the Anthropic API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (\n firstHeader(\n ctx.headers,\n 'anthropic-version',\n 'anthropic-ratelimit-requests-limit',\n 'anthropic-ratelimit-tokens-limit',\n ) !== undefined\n ) {\n return true;\n }\n const body = ctx.body;\n if (!body) {\n return false;\n }\n // `param` is an OpenAI-only field; its presence rules Anthropic out even\n // though both providers share type strings like `invalid_request_error`.\n if ('param' in body) {\n return false;\n }\n const type = firstString(body.type);\n return type !== undefined && type in ANTHROPIC_TYPES;\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const body = ctx.body ?? {};\n const type = firstString(body.type);\n const message = (firstString(body.message) ?? '').toLowerCase();\n\n const mapped = type ? ANTHROPIC_TYPES[type] : undefined;\n let category: ErrorCategory = mapped ?? baseCategoryFromStatus(ctx.status);\n\n // Anthropic reports an over-long prompt as `invalid_request_error` with a\n // \"prompt is too long\" message rather than a dedicated type.\n if (\n category === 'invalid_request' &&\n (message.includes('prompt is too long') ||\n message.includes('maximum context') ||\n message.includes('context window'))\n ) {\n category = 'context_length_exceeded';\n }\n\n const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n return { category, code: type, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseGoogleRetryDelay, parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\n/** Canonical google.rpc.Code names → provider-agnostic categories. */\nconst RPC_STATUS: Record<string, ErrorCategory> = {\n UNAUTHENTICATED: 'authentication',\n PERMISSION_DENIED: 'permission',\n NOT_FOUND: 'not_found',\n INVALID_ARGUMENT: 'invalid_request',\n FAILED_PRECONDITION: 'invalid_request',\n OUT_OF_RANGE: 'invalid_request',\n UNIMPLEMENTED: 'invalid_request',\n RESOURCE_EXHAUSTED: 'rate_limit',\n INTERNAL: 'server_error',\n UNKNOWN: 'server_error',\n ABORTED: 'server_error',\n DATA_LOSS: 'server_error',\n UNAVAILABLE: 'overloaded',\n DEADLINE_EXCEEDED: 'timeout',\n CANCELLED: 'timeout',\n};\n\nfunction rpcStatus(ctx: ProviderContext): string | undefined {\n const status = firstString(ctx.body?.status);\n if (status && /^[A-Z][A-Z_]+$/.test(status)) {\n return status;\n }\n return undefined;\n}\n\n/** Heuristic: does this error look like it came from the Gemini API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (rpcStatus(ctx) !== undefined) {\n return true;\n }\n // A numeric `code` alongside a `details` array is the Google error envelope.\n return typeof ctx.body?.code === 'number' && Array.isArray(ctx.body?.details);\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const status = rpcStatus(ctx);\n\n const mapped = status ? RPC_STATUS[status] : undefined;\n const category: ErrorCategory = mapped ?? baseCategoryFromStatus(ctx.status);\n\n const retryAfterMs =\n parseGoogleRetryDelay(ctx.body?.details) ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n return { category, code: status, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\n/** Heuristic: does this error look like it came from the OpenAI API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (\n firstHeader(\n ctx.headers,\n 'openai-organization',\n 'openai-version',\n 'openai-processing-ms',\n ) !== undefined\n ) {\n return true;\n }\n const body = ctx.body;\n if (!body) {\n return false;\n }\n // OpenAI error objects carry a `param` field; Anthropic and Gemini do not.\n if ('param' in body) {\n return true;\n }\n const code = firstString(body.code);\n return (\n code === 'context_length_exceeded' ||\n code === 'insufficient_quota' ||\n code === 'invalid_api_key'\n );\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const body = ctx.body ?? {};\n const type = firstString(body.type);\n const code = firstString(body.code);\n const identifier = `${type ?? ''} ${code ?? ''}`.toLowerCase();\n\n let category: ErrorCategory = baseCategoryFromStatus(ctx.status);\n\n if (\n identifier.includes('context_length') ||\n identifier.includes('context window')\n ) {\n category = 'context_length_exceeded';\n } else if (identifier.includes('insufficient_quota')) {\n category = 'insufficient_quota';\n } else if (\n identifier.includes('content_filter') ||\n identifier.includes('content_policy')\n ) {\n category = 'content_filter';\n } else if (\n code === 'invalid_api_key' ||\n identifier.includes('authentication')\n ) {\n category = 'authentication';\n } else if (category === 'unknown' && identifier.includes('rate_limit')) {\n category = 'rate_limit';\n }\n\n const retryAfterMs =\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after-ms'), 'ms') ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n return { category, code: code ?? type, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n isRetryableCategory,\n type Classification,\n type ProviderContext,\n} from './classify.ts';\nimport {\n firstString,\n readErrorBody,\n readHeaders,\n readMessage,\n readStatus,\n} from './internal.ts';\nimport { classifyNetworkError } from './network.ts';\nimport * as anthropic from './providers/anthropic.ts';\nimport * as gemini from './providers/gemini.ts';\nimport * as openai from './providers/openai.ts';\nimport type { NormalizedError, NormalizeOptions, Provider } from './types.ts';\n\n/**\n * Detection order is deliberate: Gemini's canonical RPC status string is the\n * most distinctive signal, then Anthropic's typed errors / headers, then\n * OpenAI (whose `param` field and headers are the remaining tell).\n */\nconst DETECTORS: ReadonlyArray<{\n name: Provider;\n matches: (ctx: ProviderContext) => boolean;\n}> = [\n { name: 'gemini', matches: gemini.matches },\n { name: 'anthropic', matches: anthropic.matches },\n { name: 'openai', matches: openai.matches },\n];\n\nfunction detectProvider(ctx: ProviderContext): Provider {\n for (const detector of DETECTORS) {\n if (detector.matches(ctx)) {\n return detector.name;\n }\n }\n return 'unknown';\n}\n\nfunction classifyFor(provider: Provider, ctx: ProviderContext): Classification {\n switch (provider) {\n case 'openai':\n return openai.classify(ctx);\n case 'anthropic':\n return anthropic.classify(ctx);\n case 'gemini':\n return gemini.classify(ctx);\n default:\n return {\n category: baseCategoryFromStatus(ctx.status),\n code: firstString(ctx.body?.type, ctx.body?.code),\n };\n }\n}\n\n/**\n * Normalize an error thrown by an OpenAI, Anthropic or Gemini client into a\n * single consistent {@link NormalizedError} shape.\n *\n * Accepts SDK error objects, raw `fetch` responses with a parsed body, or\n * plain JSON. It never throws: anything unrecognized comes back as\n * `{ provider: 'unknown', category: 'unknown', retryable: false }`.\n *\n * @example\n * ```ts\n * try {\n * await client.messages.create(params);\n * } catch (err) {\n * const e = normalizeError(err);\n * if (e.retryable) await sleep(e.retryAfterMs ?? 1000);\n * }\n * ```\n */\nexport function normalizeError(\n error: unknown,\n options: NormalizeOptions = {},\n): NormalizedError {\n const ctx: ProviderContext = {\n status: readStatus(error),\n headers: readHeaders(error),\n body: readErrorBody(error),\n };\n\n const provider = options.provider ?? detectProvider(ctx);\n const classification = classifyFor(provider, ctx);\n\n let category = classification.category;\n let code = classification.code;\n\n // No HTTP response reached us: this may be a transport-level failure\n // (timeout, dropped connection) that is retryable despite having no status.\n if (category === 'unknown' && ctx.status === undefined) {\n const network = classifyNetworkError(error);\n if (network) {\n category = network.category;\n code = code ?? network.code;\n }\n }\n\n return {\n provider,\n category,\n message: readMessage(error, ctx.body),\n status: ctx.status,\n code,\n retryable: isRetryableCategory(category),\n retryAfterMs: classification.retryAfterMs,\n raw: error,\n };\n}\n\n/**\n * Convenience wrapper around {@link normalizeError} that returns only whether\n * the error is worth retrying.\n */\nexport function isRetryableError(\n error: unknown,\n options: NormalizeOptions = {},\n): boolean {\n return normalizeError(error, options).retryable;\n}\n"],"mappings":"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","names":["matches","classify","matches","classify","matches","classify"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal.ts","../src/classify.ts","../src/network.ts","../src/retry.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/normalize.ts"],"sourcesContent":["/**\n * Internal probing helpers. These intentionally accept `unknown` and never\n * throw: error objects arrive in many shapes (SDK error classes, raw `fetch`\n * responses, plain JSON) and the library must degrade gracefully on any of\n * them.\n */\n\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Return the first argument that is a non-empty string. */\nexport function firstString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === 'string' && value.length > 0) {\n return value;\n }\n }\n return undefined;\n}\n\n/** Return the first argument that is a finite number. */\nexport function firstNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction httpStatus(value: unknown): number | undefined {\n const numeric =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && /^[1-5]\\d{2}$/.test(value.trim())\n ? Number(value)\n : undefined;\n\n if (\n typeof numeric === 'number' &&\n Number.isInteger(numeric) &&\n numeric >= 100 &&\n numeric <= 599\n ) {\n return numeric;\n }\n return undefined;\n}\n\nfunction firstHttpStatus(...values: unknown[]): number | undefined {\n for (const value of values) {\n const status = httpStatus(value);\n if (status !== undefined) {\n return status;\n }\n }\n return undefined;\n}\n\nfunction headerValueToString(value: unknown): string | undefined {\n if (typeof value === 'string') {\n return value;\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n if (Array.isArray(value)) {\n for (const entry of value) {\n const stringValue = headerValueToString(entry);\n if (stringValue !== undefined) {\n return stringValue;\n }\n }\n }\n return undefined;\n}\n\nfunction headerPairValue(entry: unknown, lower: string): string | undefined {\n if (\n Array.isArray(entry) &&\n typeof entry[0] === 'string' &&\n entry[0].toLowerCase() === lower\n ) {\n return headerValueToString(entry[1]);\n }\n return undefined;\n}\n\n/**\n * Read a header by name from the many container shapes an error may carry:\n * a `Headers` instance, a plain object, a `Map`, or an array of `[k, v]`\n * pairs. Lookup is case-insensitive and accepts common Node-style values such\n * as numbers or string arrays.\n */\nexport function getHeader(headers: unknown, name: string): string | undefined {\n if (!headers) {\n return undefined;\n }\n const lower = name.toLowerCase();\n\n // `Headers` (fetch) or `Map`-like: has a `.get` method.\n if (typeof (headers as { get?: unknown }).get === 'function') {\n const value = (headers as { get(key: string): unknown }).get(name);\n const stringValue = headerValueToString(value);\n if (stringValue !== undefined) {\n return stringValue;\n }\n }\n\n // Iterable containers (`Map`, `Headers`, arrays of [key, value] pairs).\n if (\n typeof (headers as { [Symbol.iterator]?: unknown })[Symbol.iterator] ===\n 'function'\n ) {\n for (const entry of headers as Iterable<unknown>) {\n const value = headerPairValue(entry, lower);\n if (value !== undefined) {\n return value;\n }\n }\n }\n\n // Plain object: case-insensitive key scan.\n if (isObject(headers)) {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lower) {\n const value = headers[key];\n return headerValueToString(value);\n }\n }\n }\n\n return undefined;\n}\n\nfunction looksLikeProviderErrorBody(value: Record<string, unknown>): boolean {\n return (\n typeof value.type === 'string' ||\n typeof value.code === 'string' ||\n (typeof value.code === 'number' && typeof value.status === 'string') ||\n typeof value.status === 'string' ||\n Array.isArray(value.details) ||\n 'param' in value\n );\n}\n\n/** Extract an HTTP status code from common error shapes. */\nexport function readStatus(error: unknown): number | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n const response = isObject(error.response) ? error.response : undefined;\n const inner = isObject(error.error) ? error.error : undefined;\n return firstHttpStatus(\n error.status,\n error.statusCode,\n looksLikeProviderErrorBody(error) ? error.code : undefined,\n response?.status,\n response?.statusCode,\n // Google encodes the status as `error.code` (a numeric HTTP status).\n inner?.code,\n );\n}\n\n/** Extract the header container from common error shapes. */\nexport function readHeaders(error: unknown): unknown {\n if (!isObject(error)) {\n return undefined;\n }\n const response = isObject(error.response) ? error.response : undefined;\n return error.headers ?? response?.headers;\n}\n\n/**\n * Extract the provider error body — the object that holds `type` / `code` /\n * `message`. SDK error classes expose it at `error.error`; raw responses may\n * nest it under `error.body.error` or `error.response.data.error`.\n */\nexport function readErrorBody(\n error: unknown,\n): Record<string, unknown> | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n const body = isObject(error.body) ? error.body : undefined;\n const responseData =\n isObject(error.response) && isObject(error.response.data)\n ? error.response.data\n : undefined;\n\n // Anthropic wraps as `{ type: 'error', error: {...} }`; OpenAI/Gemini SDK\n // error objects expose the inner payload directly at `.error`.\n const candidates: unknown[] = [\n isObject(error.error) && isObject(error.error.error)\n ? error.error.error\n : error.error,\n body?.error,\n responseData?.error,\n body && looksLikeProviderErrorBody(body) ? body : undefined,\n responseData && looksLikeProviderErrorBody(responseData)\n ? responseData\n : undefined,\n looksLikeProviderErrorBody(error) ? error : undefined,\n ];\n\n for (const candidate of candidates) {\n if (isObject(candidate)) {\n return candidate;\n }\n }\n return undefined;\n}\n\n/** Best-effort human-readable message from an error of any shape. */\nexport function readMessage(\n error: unknown,\n body: Record<string, unknown> | undefined,\n): string {\n const fromBody = body ? firstString(body.message) : undefined;\n if (fromBody) {\n return fromBody;\n }\n if (isObject(error)) {\n const direct = firstString(error.message);\n if (direct) {\n return direct;\n }\n }\n if (typeof error === 'string' && error.length > 0) {\n return error;\n }\n return 'Unknown error';\n}\n","import { getHeader } from './internal.ts';\nimport type { ErrorCategory } from './types.ts';\n\n/**\n * The pre-parsed pieces of an error that every provider classifier and the\n * detector operate on. Kept internal to the package.\n */\nexport interface ProviderContext {\n status?: number;\n /** The provider error body (`{ type, code, message, ... }`), if found. */\n body: Record<string, unknown> | undefined;\n /** The raw header container (`Headers`, object, pairs), if found. */\n headers: unknown;\n}\n\n/** The result a provider classifier contributes to the normalized error. */\nexport interface Classification {\n category: ErrorCategory;\n code?: string;\n retryAfterMs?: number;\n}\n\n/**\n * Map an HTTP status code to a category, ignoring provider specifics. Provider\n * classifiers start here and then refine using their `code` / `type` strings.\n */\nexport function baseCategoryFromStatus(status?: number): ErrorCategory {\n switch (status) {\n case 401:\n return 'authentication';\n case 403:\n return 'permission';\n case 404:\n return 'not_found';\n case 408:\n return 'timeout';\n case 413:\n return 'request_too_large';\n case 400:\n case 422:\n return 'invalid_request';\n case 429:\n return 'rate_limit';\n case 500:\n return 'server_error';\n case 502:\n return 'server_error';\n case 503:\n return 'overloaded';\n case 504:\n return 'timeout';\n case 529:\n return 'overloaded';\n default:\n break;\n }\n if (typeof status === 'number') {\n if (status >= 500) {\n return 'server_error';\n }\n if (status >= 400) {\n return 'invalid_request';\n }\n }\n return 'unknown';\n}\n\n/** Categories that are safe to retry after a delay. */\nconst RETRYABLE: ReadonlySet<ErrorCategory> = new Set<ErrorCategory>([\n 'rate_limit',\n 'server_error',\n 'overloaded',\n 'timeout',\n]);\n\n/** Whether a category represents a transient, retryable condition. */\nexport function isRetryableCategory(category: ErrorCategory): boolean {\n return RETRYABLE.has(category);\n}\n\n/** Read the first present header from a list of candidate names. */\nexport function firstHeader(\n headers: unknown,\n ...names: string[]\n): string | undefined {\n for (const name of names) {\n const value = getHeader(headers, name);\n if (value !== undefined) {\n return value;\n }\n }\n return undefined;\n}\n","import { firstString, isObject } from './internal.ts';\nimport type { ErrorCategory } from './types.ts';\n\n/**\n * Transport-level failures never reach an HTTP response, so they carry no\n * status code or provider body — yet most of them (timeouts, dropped\n * connections, DNS hiccups) are very much worth retrying. This recognizes\n * them from the Node `code`, the `name`, or the SDK error class name.\n */\n\n/** Node `error.code` values that mean \"the connection failed; try again\". */\nconst RETRYABLE_CODES: Record<string, ErrorCategory> = {\n ETIMEDOUT: 'timeout',\n ESOCKETTIMEDOUT: 'timeout',\n ECONNRESET: 'server_error',\n ECONNREFUSED: 'server_error',\n ECONNABORTED: 'server_error',\n EPIPE: 'server_error',\n ENOTFOUND: 'server_error',\n EAI_AGAIN: 'server_error',\n EHOSTUNREACH: 'server_error',\n ENETUNREACH: 'server_error',\n};\n\n/**\n * Error `name` / constructor names, matched case-insensitively as substrings,\n * mapped to a category. Covers `AbortError`, the Fetch `TimeoutError`, and the\n * OpenAI / Anthropic SDK connection error classes.\n */\nconst NAME_PATTERNS: ReadonlyArray<[pattern: string, category: ErrorCategory]> =\n [\n ['timeout', 'timeout'],\n ['aborterror', 'timeout'],\n ['connectionerror', 'server_error'],\n ['connection error', 'server_error'],\n ['fetcherror', 'server_error'],\n ];\n\nexport interface NetworkClassification {\n category: ErrorCategory;\n code?: string;\n}\n\n/**\n * Try to classify a transport-level error. Returns `undefined` when the value\n * does not look like a network failure, so the caller can fall back to its\n * normal (HTTP-based) classification.\n */\nexport function classifyNetworkError(\n error: unknown,\n): NetworkClassification | undefined {\n if (!isObject(error)) {\n return undefined;\n }\n\n const code = firstString(error.code);\n if (code && code in RETRYABLE_CODES) {\n return { category: RETRYABLE_CODES[code], code };\n }\n\n // Check both the instance `name` and the constructor name: a subclass of\n // `Error` that doesn't override `name` still reports `\"Error\"`, so the\n // distinguishing signal lives on `constructor.name`.\n const names = [\n firstString(error.name),\n firstString((error.constructor as { name?: unknown } | undefined)?.name),\n ];\n for (const name of names) {\n if (!name) {\n continue;\n }\n const haystack = name.toLowerCase();\n for (const [pattern, category] of NAME_PATTERNS) {\n if (haystack.includes(pattern)) {\n return { category, code: name };\n }\n }\n }\n\n return undefined;\n}\n","import { isObject } from './internal.ts';\nimport type { NormalizedError, RetryDelayOptions } from './types.ts';\n\nfunction nonNegativeFinite(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? value\n : undefined;\n}\n\n/**\n * Parse a `Retry-After` header value into milliseconds.\n *\n * Per RFC 7231 the value is either a number of seconds (`\"30\"`) or an\n * HTTP date (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Some providers also send a\n * fractional `retry-after-ms` value, which is accepted when `unit` is `'ms'`.\n */\nexport function parseRetryAfter(\n value: string | undefined,\n unit: 's' | 'ms' = 's',\n): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed === '') {\n return undefined;\n }\n\n const numeric = Number(trimmed);\n if (Number.isFinite(numeric)) {\n const ms = unit === 'ms' ? numeric : numeric * 1000;\n return ms >= 0 ? ms : undefined;\n }\n\n if (unit === 'ms') {\n return undefined;\n }\n\n const date = Date.parse(trimmed);\n if (Number.isFinite(date)) {\n return Math.max(0, date - Date.now());\n }\n\n return undefined;\n}\n\n/**\n * Parse a Google `RetryInfo.retryDelay` value into milliseconds.\n *\n * Google encodes it either as a duration string (`\"30s\"`, `\"1.5s\"`) or as a\n * `{ seconds, nanos }` object inside `error.details[]`.\n */\nexport function parseGoogleRetryDelay(details: unknown): number | undefined {\n if (!Array.isArray(details)) {\n return undefined;\n }\n for (const detail of details) {\n if (!isObject(detail)) {\n continue;\n }\n const type = detail['@type'];\n if (typeof type === 'string' && !type.includes('RetryInfo')) {\n continue;\n }\n const delay = detail.retryDelay;\n if (typeof delay === 'string') {\n const seconds = Number(delay.replace(/s$/, ''));\n if (Number.isFinite(seconds) && seconds >= 0) {\n return seconds * 1000;\n }\n }\n if (isObject(delay)) {\n const seconds =\n typeof delay.seconds === 'number'\n ? delay.seconds\n : Number(delay.seconds);\n const nanos =\n delay.nanos === undefined\n ? 0\n : typeof delay.nanos === 'number'\n ? delay.nanos\n : Number(delay.nanos);\n if (\n Number.isFinite(seconds) &&\n seconds >= 0 &&\n Number.isFinite(nanos) &&\n nanos >= 0 &&\n nanos < 1e9\n ) {\n return seconds * 1000 + Math.round(nanos / 1e6);\n }\n }\n }\n return undefined;\n}\n\n/**\n * Suggested delay before retrying, in milliseconds.\n *\n * Non-retryable errors return `0`. When the provider supplied an explicit\n * valid delay (`error.retryAfterMs`) it is respected. Otherwise this falls\n * back to exponential backoff:\n * `baseMs * 2 ** attempt`, capped at `maxMs`, with optional full jitter.\n *\n * @param error A {@link NormalizedError}.\n * @param attempt Zero-based retry attempt number (0 for the first retry).\n * @param options Backoff tuning. See {@link RetryDelayOptions}.\n */\nexport function getRetryDelayMs(\n error: NormalizedError,\n attempt: number,\n options: RetryDelayOptions = {},\n): number {\n if (!error.retryable) {\n return 0;\n }\n\n const explicitDelay = nonNegativeFinite(error.retryAfterMs);\n if (explicitDelay !== undefined) {\n return explicitDelay;\n }\n\n const baseMs = nonNegativeFinite(options.baseMs) ?? 500;\n const maxMs = nonNegativeFinite(options.maxMs) ?? 60000;\n const jitter = options.jitter ?? 'full';\n\n const safeAttempt =\n Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;\n const exponential = Math.min(maxMs, baseMs * 2 ** safeAttempt);\n\n if (jitter === 'none') {\n return exponential;\n }\n return Math.random() * exponential;\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\nconst ANTHROPIC_TYPES: Record<string, ErrorCategory> = {\n authentication_error: 'authentication',\n permission_error: 'permission',\n not_found_error: 'not_found',\n request_too_large: 'request_too_large',\n rate_limit_error: 'rate_limit',\n invalid_request_error: 'invalid_request',\n api_error: 'server_error',\n overloaded_error: 'overloaded',\n billing_error: 'insufficient_quota',\n timeout_error: 'timeout',\n};\n\n/** Heuristic: does this error look like it came from the Anthropic API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (\n firstHeader(\n ctx.headers,\n 'anthropic-version',\n 'anthropic-ratelimit-requests-limit',\n 'anthropic-ratelimit-tokens-limit',\n ) !== undefined\n ) {\n return true;\n }\n const body = ctx.body;\n if (!body) {\n return false;\n }\n // `param` is an OpenAI-only field; its presence rules Anthropic out even\n // though both providers share type strings like `invalid_request_error`.\n if ('param' in body) {\n return false;\n }\n const type = firstString(body.type);\n return type !== undefined && type in ANTHROPIC_TYPES;\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const body = ctx.body ?? {};\n const type = firstString(body.type);\n const message = (firstString(body.message) ?? '').toLowerCase();\n\n const mapped = type ? ANTHROPIC_TYPES[type] : undefined;\n let category: ErrorCategory = mapped ?? baseCategoryFromStatus(ctx.status);\n\n // Anthropic reports an over-long prompt as `invalid_request_error` with a\n // \"prompt is too long\" message rather than a dedicated type.\n if (\n category === 'invalid_request' &&\n (message.includes('prompt is too long') ||\n message.includes('maximum context') ||\n message.includes('context window'))\n ) {\n category = 'context_length_exceeded';\n } else if (\n message.includes('credit balance') ||\n message.includes('billing') ||\n message.includes('insufficient quota')\n ) {\n category = 'insufficient_quota';\n }\n\n const retryAfterMs =\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after-ms'), 'ms') ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n return { category, code: type, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseGoogleRetryDelay, parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\n/** Canonical google.rpc.Code names → provider-agnostic categories. */\nconst RPC_STATUS: Record<string, ErrorCategory> = {\n UNAUTHENTICATED: 'authentication',\n PERMISSION_DENIED: 'permission',\n NOT_FOUND: 'not_found',\n INVALID_ARGUMENT: 'invalid_request',\n FAILED_PRECONDITION: 'invalid_request',\n OUT_OF_RANGE: 'invalid_request',\n UNIMPLEMENTED: 'invalid_request',\n RESOURCE_EXHAUSTED: 'rate_limit',\n INTERNAL: 'server_error',\n UNKNOWN: 'server_error',\n ABORTED: 'server_error',\n DATA_LOSS: 'server_error',\n UNAVAILABLE: 'overloaded',\n DEADLINE_EXCEEDED: 'timeout',\n CANCELLED: 'timeout',\n};\n\nfunction rpcStatus(ctx: ProviderContext): string | undefined {\n const status = firstString(ctx.body?.status);\n if (status && /^[A-Z][A-Z_]+$/.test(status)) {\n return status;\n }\n return undefined;\n}\n\nfunction isQuotaExhaustedMessage(message: string): boolean {\n const lower = message.toLowerCase();\n return (\n lower.includes('quota') &&\n (lower.includes('billing') ||\n lower.includes('paid plan') ||\n lower.includes('free tier') ||\n lower.includes('check your plan') ||\n lower.includes('upgrade'))\n );\n}\n\n/** Heuristic: does this error look like it came from the Gemini API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (rpcStatus(ctx) !== undefined) {\n return true;\n }\n // A numeric `code` alongside a `details` array is the Google error envelope.\n return typeof ctx.body?.code === 'number' && Array.isArray(ctx.body?.details);\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const status = rpcStatus(ctx);\n const message = firstString(ctx.body?.message) ?? '';\n const retryAfterMs =\n parseGoogleRetryDelay(ctx.body?.details) ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after-ms'), 'ms') ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n const mapped = status ? RPC_STATUS[status] : undefined;\n let category: ErrorCategory = mapped ?? baseCategoryFromStatus(ctx.status);\n if (\n status === 'RESOURCE_EXHAUSTED' &&\n retryAfterMs === undefined &&\n isQuotaExhaustedMessage(message)\n ) {\n category = 'insufficient_quota';\n }\n\n return { category, code: status, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n type Classification,\n type ProviderContext,\n} from '../classify.ts';\nimport { firstString } from '../internal.ts';\nimport { parseRetryAfter } from '../retry.ts';\nimport type { ErrorCategory } from '../types.ts';\n\nconst OPENAI_CODES = new Set([\n 'billing_hard_limit_reached',\n 'billing_not_active',\n 'context_length_exceeded',\n 'content_filter',\n 'content_policy_violation',\n 'insufficient_quota',\n 'invalid_api_key',\n 'model_not_found',\n 'rate_limit_exceeded',\n]);\n\nfunction includesAny(\n haystack: string,\n needles: ReadonlyArray<string>,\n): boolean {\n return needles.some((needle) => haystack.includes(needle));\n}\n\n/** Heuristic: does this error look like it came from the OpenAI API? */\nexport function matches(ctx: ProviderContext): boolean {\n if (\n firstHeader(\n ctx.headers,\n 'openai-organization',\n 'openai-version',\n 'openai-processing-ms',\n ) !== undefined\n ) {\n return true;\n }\n const body = ctx.body;\n if (!body) {\n return false;\n }\n // OpenAI error objects carry a `param` field; Anthropic and Gemini do not.\n if ('param' in body) {\n return true;\n }\n const code = firstString(body.code);\n return code !== undefined && OPENAI_CODES.has(code);\n}\n\nexport function classify(ctx: ProviderContext): Classification {\n const body = ctx.body ?? {};\n const type = firstString(body.type);\n const code = firstString(body.code);\n const message = firstString(body.message) ?? '';\n const identifier = `${type ?? ''} ${code ?? ''} ${message}`.toLowerCase();\n\n let category: ErrorCategory = baseCategoryFromStatus(ctx.status);\n\n if (\n identifier.includes('context_length') ||\n identifier.includes('context window')\n ) {\n category = 'context_length_exceeded';\n } else if (\n includesAny(identifier, [\n 'insufficient_quota',\n 'billing_hard_limit',\n 'billing_not_active',\n 'exceeded your current quota',\n ])\n ) {\n category = 'insufficient_quota';\n } else if (\n includesAny(identifier, [\n 'content_filter',\n 'content_policy',\n 'safety_policy',\n ])\n ) {\n category = 'content_filter';\n } else if (\n code === 'invalid_api_key' ||\n includesAny(identifier, ['authentication', 'unauthorized'])\n ) {\n category = 'authentication';\n } else if (includesAny(identifier, ['permission', 'forbidden'])) {\n category = 'permission';\n } else if (includesAny(identifier, ['not_found', 'model_not_found'])) {\n category = 'not_found';\n } else if (includesAny(identifier, ['timeout', 'timed out'])) {\n category = 'timeout';\n } else if (includesAny(identifier, ['overload', 'unavailable'])) {\n category = 'overloaded';\n } else if (includesAny(identifier, ['server_error', 'api_error'])) {\n category = 'server_error';\n } else if (identifier.includes('rate_limit')) {\n category = 'rate_limit';\n } else if (category === 'unknown' && identifier.includes('invalid_request')) {\n category = 'invalid_request';\n }\n\n const retryAfterMs =\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after-ms'), 'ms') ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after'));\n\n return { category, code: code ?? type, retryAfterMs };\n}\n","import {\n baseCategoryFromStatus,\n firstHeader,\n isRetryableCategory,\n type Classification,\n type ProviderContext,\n} from './classify.ts';\nimport {\n firstString,\n readErrorBody,\n readHeaders,\n readMessage,\n readStatus,\n} from './internal.ts';\nimport { classifyNetworkError } from './network.ts';\nimport * as anthropic from './providers/anthropic.ts';\nimport * as gemini from './providers/gemini.ts';\nimport * as openai from './providers/openai.ts';\nimport { parseRetryAfter } from './retry.ts';\nimport type { NormalizedError, NormalizeOptions, Provider } from './types.ts';\n\n/**\n * Detection order is deliberate: Gemini's canonical RPC status string is the\n * most distinctive signal, then Anthropic's typed errors / headers, then\n * OpenAI (whose `param` field and headers are the remaining tell).\n */\nconst DETECTORS: ReadonlyArray<{\n name: Provider;\n matches: (ctx: ProviderContext) => boolean;\n}> = [\n { name: 'gemini', matches: gemini.matches },\n { name: 'anthropic', matches: anthropic.matches },\n { name: 'openai', matches: openai.matches },\n];\n\nfunction detectProvider(ctx: ProviderContext): Provider {\n for (const detector of DETECTORS) {\n if (detector.matches(ctx)) {\n return detector.name;\n }\n }\n return 'unknown';\n}\n\nfunction classifyFor(provider: Provider, ctx: ProviderContext): Classification {\n switch (provider) {\n case 'openai':\n return openai.classify(ctx);\n case 'anthropic':\n return anthropic.classify(ctx);\n case 'gemini':\n return gemini.classify(ctx);\n default:\n return {\n category: baseCategoryFromStatus(ctx.status),\n code: firstString(ctx.body?.type, ctx.body?.code),\n retryAfterMs:\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after-ms'), 'ms') ??\n parseRetryAfter(firstHeader(ctx.headers, 'retry-after')),\n };\n }\n}\n\n/**\n * Normalize an error thrown by an OpenAI, Anthropic or Gemini client into a\n * single consistent {@link NormalizedError} shape.\n *\n * Accepts SDK error objects, raw `fetch` responses with a parsed body, or\n * plain JSON. It never throws: anything unrecognized comes back as\n * `{ provider: 'unknown', category: 'unknown', retryable: false }`.\n *\n * @example\n * ```ts\n * try {\n * await client.messages.create(params);\n * } catch (err) {\n * const e = normalizeError(err);\n * if (e.retryable) await sleep(e.retryAfterMs ?? 1000);\n * }\n * ```\n */\nexport function normalizeError(\n error: unknown,\n options: NormalizeOptions = {},\n): NormalizedError {\n const ctx: ProviderContext = {\n status: readStatus(error),\n headers: readHeaders(error),\n body: readErrorBody(error),\n };\n\n const provider = options.provider ?? detectProvider(ctx);\n const classification = classifyFor(provider, ctx);\n\n let category = classification.category;\n let code = classification.code;\n\n // No HTTP response reached us: this may be a transport-level failure\n // (timeout, dropped connection) that is retryable despite having no status.\n if (category === 'unknown' && ctx.status === undefined) {\n const network = classifyNetworkError(error);\n if (network) {\n category = network.category;\n code = code ?? network.code;\n }\n }\n\n const retryable = isRetryableCategory(category);\n\n return {\n provider,\n category,\n message: readMessage(error, ctx.body),\n status: ctx.status,\n code,\n retryable,\n retryAfterMs: retryable ? classification.retryAfterMs : undefined,\n raw: error,\n };\n}\n\n/**\n * Convenience wrapper around {@link normalizeError} that returns only whether\n * the error is worth retrying.\n */\nexport function isRetryableError(\n error: unknown,\n options: NormalizeOptions = {},\n): boolean {\n return normalizeError(error, options).retryable;\n}\n"],"mappings":"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","names":["matches","classify","matches","classify","matches","classify"]}
|
package/fixtures/README.md
CHANGED
|
@@ -16,9 +16,12 @@ For example, `cases/openai/sdk-rate-limit.json` is paired with
|
|
|
16
16
|
|
|
17
17
|
The corpus covers:
|
|
18
18
|
|
|
19
|
-
- OpenAI SDK `APIError`-style objects
|
|
19
|
+
- OpenAI SDK `APIError`-style objects, parsed fetch responses and direct
|
|
20
|
+
provider error bodies.
|
|
20
21
|
- Anthropic SDK envelopes and parsed fetch responses.
|
|
21
|
-
- Gemini / Google RPC error envelopes.
|
|
22
|
+
- Gemini / Google RPC error envelopes and direct RPC status bodies.
|
|
23
|
+
- Generic HTTP errors with status codes and retry headers, including
|
|
24
|
+
non-retryable statuses where retry hints are intentionally ignored.
|
|
22
25
|
- Transport failures such as Node timeout codes and browser abort errors.
|
|
23
26
|
|
|
24
27
|
The fixtures are not recordings of private traffic and do not contain API keys,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Anthropic rate-limit type with credit balance message",
|
|
3
|
+
"shape": "sdk-error",
|
|
4
|
+
"error": {
|
|
5
|
+
"status": 429,
|
|
6
|
+
"headers": {
|
|
7
|
+
"Retry-After": "30"
|
|
8
|
+
},
|
|
9
|
+
"error": {
|
|
10
|
+
"type": "error",
|
|
11
|
+
"error": {
|
|
12
|
+
"type": "rate_limit_error",
|
|
13
|
+
"message": "Your credit balance is too low."
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Anthropic SDK billing error",
|
|
3
|
+
"shape": "anthropic-sdk-error-envelope",
|
|
4
|
+
"error": {
|
|
5
|
+
"status": 429,
|
|
6
|
+
"error": {
|
|
7
|
+
"type": "error",
|
|
8
|
+
"error": {
|
|
9
|
+
"type": "billing_error",
|
|
10
|
+
"message": "Your credit balance is too low."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Gemini Google RPC quota exhausted billing",
|
|
3
|
+
"shape": "google-rpc-error-envelope",
|
|
4
|
+
"error": {
|
|
5
|
+
"error": {
|
|
6
|
+
"code": 429,
|
|
7
|
+
"message": "You exceeded your current quota, please check your plan and billing details.",
|
|
8
|
+
"status": "RESOURCE_EXHAUSTED"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Generic HTTP 503 with Retry-After",
|
|
3
|
+
"shape": "fetch-like-http-error",
|
|
4
|
+
"error": {
|
|
5
|
+
"statusCode": 503,
|
|
6
|
+
"headers": {
|
|
7
|
+
"Retry-After": "6"
|
|
8
|
+
},
|
|
9
|
+
"body": {
|
|
10
|
+
"error": {
|
|
11
|
+
"message": "Service temporarily unavailable"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|