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.
Files changed (28) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +14 -5
  3. package/dist/index.cjs +133 -31
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +8 -5
  6. package/dist/index.d.ts +8 -5
  7. package/dist/index.js +133 -31
  8. package/dist/index.js.map +1 -1
  9. package/fixtures/README.md +5 -2
  10. package/fixtures/cases/anthropic/rate-limit-credit-balance.json +17 -0
  11. package/fixtures/cases/anthropic/sdk-billing-error.json +14 -0
  12. package/fixtures/cases/gemini/plain-unavailable-body.json +9 -0
  13. package/fixtures/cases/gemini/rpc-quota-exhausted-billing.json +11 -0
  14. package/fixtures/cases/gemini/rpc-quota-exhausted-rate-bucket.json +11 -0
  15. package/fixtures/cases/generic/http-400-retry-after.json +15 -0
  16. package/fixtures/cases/generic/http-503-retry-after.json +15 -0
  17. package/fixtures/cases/openai/plain-rate-limit-body.json +10 -0
  18. package/fixtures/cases/openai/sdk-billing-hard-limit.json +12 -0
  19. package/fixtures/expected/anthropic/rate-limit-credit-balance.json +8 -0
  20. package/fixtures/expected/anthropic/sdk-billing-error.json +8 -0
  21. package/fixtures/expected/gemini/plain-unavailable-body.json +8 -0
  22. package/fixtures/expected/gemini/rpc-quota-exhausted-billing.json +8 -0
  23. package/fixtures/expected/gemini/rpc-quota-exhausted-rate-bucket.json +8 -0
  24. package/fixtures/expected/generic/http-400-retry-after.json +7 -0
  25. package/fixtures/expected/generic/http-503-retry-after.json +8 -0
  26. package/fixtures/expected/openai/plain-rate-limit-body.json +7 -0
  27. package/fixtures/expected/openai/sdk-billing-hard-limit.json +7 -0
  28. 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.). `undefined` when the
65
- * provider did not specify one use {@link getRetryDelayMs} for a fallback.
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 delay (`error.retryAfterMs`) it is
141
- * always respected. Otherwise this falls back to exponential backoff:
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 firstNumber(...values) {
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
- if (typeof value === "number" && Number.isFinite(value)) {
16
- return value;
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
- return typeof value === "string" ? value : void 0;
59
+ const stringValue = headerValueToString(value);
60
+ if (stringValue !== void 0) {
61
+ return stringValue;
62
+ }
29
63
  }
30
- if (Array.isArray(headers)) {
64
+ if (typeof headers[Symbol.iterator] === "function") {
31
65
  for (const entry of headers) {
32
- if (Array.isArray(entry) && typeof entry[0] === "string" && entry[0].toLowerCase() === lower) {
33
- return typeof entry[1] === "string" ? entry[1] : void 0;
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 typeof value === "string" ? value : void 0;
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 firstNumber(
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
- isObject(error.body) ? error.body.error : void 0,
76
- isObject(error.response) && isObject(error.response.data) ? error.response.data.error : void 0
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 : 0;
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 (typeof error.retryAfterMs === "number") {
259
- return error.retryAfterMs;
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
- const category = mapped ?? baseCategoryFromStatus(ctx.status);
352
- const retryAfterMs = parseGoogleRetryDelay(ctx.body?.details) ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
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 === "context_length_exceeded" || code === "insufficient_quota" || code === "invalid_api_key";
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 identifier = `${type ?? ""} ${code ?? ""}`.toLowerCase();
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.includes("insufficient_quota")) {
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 (identifier.includes("content_filter") || identifier.includes("content_policy")) {
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.includes("authentication")) {
476
+ } else if (code === "invalid_api_key" || includesAny(identifier, ["authentication", "unauthorized"])) {
389
477
  category = "authentication";
390
- } else if (category === "unknown" && identifier.includes("rate_limit")) {
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: isRetryableCategory(category),
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"]}
@@ -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 and parsed fetch responses.
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,9 @@
1
+ {
2
+ "name": "Gemini direct plain unavailable body",
3
+ "shape": "plain-provider-error-body",
4
+ "error": {
5
+ "code": 503,
6
+ "message": "The model is overloaded. Please try again later.",
7
+ "status": "UNAVAILABLE"
8
+ }
9
+ }
@@ -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,11 @@
1
+ {
2
+ "name": "Gemini RESOURCE_EXHAUSTED rate bucket without billing signal",
3
+ "shape": "google-rpc-error-body",
4
+ "error": {
5
+ "error": {
6
+ "code": 429,
7
+ "message": "Quota exceeded for requests per minute.",
8
+ "status": "RESOURCE_EXHAUSTED"
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "Generic HTTP 400 with ignored Retry-After",
3
+ "shape": "fetch-like-http-error",
4
+ "error": {
5
+ "statusCode": 400,
6
+ "headers": {
7
+ "Retry-After": "60"
8
+ },
9
+ "body": {
10
+ "error": {
11
+ "message": "Bad request"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -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
+ }