llm-errors 0.1.0
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 +25 -0
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/index.cjs +495 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +464 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
// src/internal.ts
|
|
2
|
+
function isObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null;
|
|
4
|
+
}
|
|
5
|
+
function firstString(...values) {
|
|
6
|
+
for (const value of values) {
|
|
7
|
+
if (typeof value === "string" && value.length > 0) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
function firstNumber(...values) {
|
|
14
|
+
for (const value of values) {
|
|
15
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
function getHeader(headers, name) {
|
|
22
|
+
if (!headers) {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
const lower = name.toLowerCase();
|
|
26
|
+
if (typeof headers.get === "function") {
|
|
27
|
+
const value = headers.get(name);
|
|
28
|
+
return typeof value === "string" ? value : void 0;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(headers)) {
|
|
31
|
+
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;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
if (isObject(headers)) {
|
|
39
|
+
for (const key of Object.keys(headers)) {
|
|
40
|
+
if (key.toLowerCase() === lower) {
|
|
41
|
+
const value = headers[key];
|
|
42
|
+
return typeof value === "string" ? value : void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function readStatus(error) {
|
|
49
|
+
if (!isObject(error)) {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
const response = isObject(error.response) ? error.response : void 0;
|
|
53
|
+
const inner = isObject(error.error) ? error.error : void 0;
|
|
54
|
+
return firstNumber(
|
|
55
|
+
error.status,
|
|
56
|
+
error.statusCode,
|
|
57
|
+
response?.status,
|
|
58
|
+
// Google encodes the status as `error.code` (a numeric HTTP status).
|
|
59
|
+
inner?.code
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function readHeaders(error) {
|
|
63
|
+
if (!isObject(error)) {
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
const response = isObject(error.response) ? error.response : void 0;
|
|
67
|
+
return error.headers ?? response?.headers;
|
|
68
|
+
}
|
|
69
|
+
function readErrorBody(error) {
|
|
70
|
+
if (!isObject(error)) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
const candidates = [
|
|
74
|
+
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
|
|
77
|
+
];
|
|
78
|
+
for (const candidate of candidates) {
|
|
79
|
+
if (isObject(candidate)) {
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function readMessage(error, body) {
|
|
86
|
+
const fromBody = body ? firstString(body.message) : void 0;
|
|
87
|
+
if (fromBody) {
|
|
88
|
+
return fromBody;
|
|
89
|
+
}
|
|
90
|
+
if (isObject(error)) {
|
|
91
|
+
const direct = firstString(error.message);
|
|
92
|
+
if (direct) {
|
|
93
|
+
return direct;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (typeof error === "string" && error.length > 0) {
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
return "Unknown error";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/classify.ts
|
|
103
|
+
function baseCategoryFromStatus(status) {
|
|
104
|
+
switch (status) {
|
|
105
|
+
case 401:
|
|
106
|
+
return "authentication";
|
|
107
|
+
case 403:
|
|
108
|
+
return "permission";
|
|
109
|
+
case 404:
|
|
110
|
+
return "not_found";
|
|
111
|
+
case 408:
|
|
112
|
+
return "timeout";
|
|
113
|
+
case 413:
|
|
114
|
+
return "request_too_large";
|
|
115
|
+
case 400:
|
|
116
|
+
case 422:
|
|
117
|
+
return "invalid_request";
|
|
118
|
+
case 429:
|
|
119
|
+
return "rate_limit";
|
|
120
|
+
case 500:
|
|
121
|
+
return "server_error";
|
|
122
|
+
case 502:
|
|
123
|
+
return "server_error";
|
|
124
|
+
case 503:
|
|
125
|
+
return "overloaded";
|
|
126
|
+
case 504:
|
|
127
|
+
return "timeout";
|
|
128
|
+
case 529:
|
|
129
|
+
return "overloaded";
|
|
130
|
+
default:
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
if (typeof status === "number") {
|
|
134
|
+
if (status >= 500) {
|
|
135
|
+
return "server_error";
|
|
136
|
+
}
|
|
137
|
+
if (status >= 400) {
|
|
138
|
+
return "invalid_request";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return "unknown";
|
|
142
|
+
}
|
|
143
|
+
var RETRYABLE = /* @__PURE__ */ new Set([
|
|
144
|
+
"rate_limit",
|
|
145
|
+
"server_error",
|
|
146
|
+
"overloaded",
|
|
147
|
+
"timeout"
|
|
148
|
+
]);
|
|
149
|
+
function isRetryableCategory(category) {
|
|
150
|
+
return RETRYABLE.has(category);
|
|
151
|
+
}
|
|
152
|
+
function firstHeader(headers, ...names) {
|
|
153
|
+
for (const name of names) {
|
|
154
|
+
const value = getHeader(headers, name);
|
|
155
|
+
if (value !== void 0) {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/network.ts
|
|
163
|
+
var RETRYABLE_CODES = {
|
|
164
|
+
ETIMEDOUT: "timeout",
|
|
165
|
+
ESOCKETTIMEDOUT: "timeout",
|
|
166
|
+
ECONNRESET: "server_error",
|
|
167
|
+
ECONNREFUSED: "server_error",
|
|
168
|
+
ECONNABORTED: "server_error",
|
|
169
|
+
EPIPE: "server_error",
|
|
170
|
+
ENOTFOUND: "server_error",
|
|
171
|
+
EAI_AGAIN: "server_error",
|
|
172
|
+
EHOSTUNREACH: "server_error",
|
|
173
|
+
ENETUNREACH: "server_error"
|
|
174
|
+
};
|
|
175
|
+
var NAME_PATTERNS = [
|
|
176
|
+
["timeout", "timeout"],
|
|
177
|
+
["aborterror", "timeout"],
|
|
178
|
+
["connectionerror", "server_error"],
|
|
179
|
+
["connection error", "server_error"],
|
|
180
|
+
["fetcherror", "server_error"]
|
|
181
|
+
];
|
|
182
|
+
function classifyNetworkError(error) {
|
|
183
|
+
if (!isObject(error)) {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
const code = firstString(error.code);
|
|
187
|
+
if (code && code in RETRYABLE_CODES) {
|
|
188
|
+
return { category: RETRYABLE_CODES[code], code };
|
|
189
|
+
}
|
|
190
|
+
const names = [
|
|
191
|
+
firstString(error.name),
|
|
192
|
+
firstString(error.constructor?.name)
|
|
193
|
+
];
|
|
194
|
+
for (const name of names) {
|
|
195
|
+
if (!name) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const haystack = name.toLowerCase();
|
|
199
|
+
for (const [pattern, category] of NAME_PATTERNS) {
|
|
200
|
+
if (haystack.includes(pattern)) {
|
|
201
|
+
return { category, code: name };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/retry.ts
|
|
209
|
+
function parseRetryAfter(value, unit = "s") {
|
|
210
|
+
if (value === void 0) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
const trimmed = value.trim();
|
|
214
|
+
if (trimmed === "") {
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
const numeric = Number(trimmed);
|
|
218
|
+
if (Number.isFinite(numeric)) {
|
|
219
|
+
const ms = unit === "ms" ? numeric : numeric * 1e3;
|
|
220
|
+
return ms >= 0 ? ms : void 0;
|
|
221
|
+
}
|
|
222
|
+
const date = Date.parse(trimmed);
|
|
223
|
+
if (Number.isFinite(date)) {
|
|
224
|
+
return Math.max(0, date - Date.now());
|
|
225
|
+
}
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
function parseGoogleRetryDelay(details) {
|
|
229
|
+
if (!Array.isArray(details)) {
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
for (const detail of details) {
|
|
233
|
+
if (!isObject(detail)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const type = detail["@type"];
|
|
237
|
+
if (typeof type === "string" && !type.includes("RetryInfo")) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const delay = detail.retryDelay;
|
|
241
|
+
if (typeof delay === "string") {
|
|
242
|
+
const seconds = Number(delay.replace(/s$/, ""));
|
|
243
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
244
|
+
return seconds * 1e3;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (isObject(delay)) {
|
|
248
|
+
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) {
|
|
251
|
+
return seconds * 1e3 + Math.round(nanos / 1e6);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
function getRetryDelayMs(error, attempt, options = {}) {
|
|
258
|
+
if (typeof error.retryAfterMs === "number") {
|
|
259
|
+
return error.retryAfterMs;
|
|
260
|
+
}
|
|
261
|
+
const baseMs = options.baseMs ?? 500;
|
|
262
|
+
const maxMs = options.maxMs ?? 6e4;
|
|
263
|
+
const jitter = options.jitter ?? "full";
|
|
264
|
+
const safeAttempt = Number.isFinite(attempt) && attempt > 0 ? attempt : 0;
|
|
265
|
+
const exponential = Math.min(maxMs, baseMs * 2 ** safeAttempt);
|
|
266
|
+
if (jitter === "none") {
|
|
267
|
+
return exponential;
|
|
268
|
+
}
|
|
269
|
+
return Math.random() * exponential;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/providers/anthropic.ts
|
|
273
|
+
var ANTHROPIC_TYPES = {
|
|
274
|
+
authentication_error: "authentication",
|
|
275
|
+
permission_error: "permission",
|
|
276
|
+
not_found_error: "not_found",
|
|
277
|
+
request_too_large: "request_too_large",
|
|
278
|
+
rate_limit_error: "rate_limit",
|
|
279
|
+
invalid_request_error: "invalid_request",
|
|
280
|
+
api_error: "server_error",
|
|
281
|
+
overloaded_error: "overloaded",
|
|
282
|
+
billing_error: "insufficient_quota",
|
|
283
|
+
timeout_error: "timeout"
|
|
284
|
+
};
|
|
285
|
+
function matches(ctx) {
|
|
286
|
+
if (firstHeader(
|
|
287
|
+
ctx.headers,
|
|
288
|
+
"anthropic-version",
|
|
289
|
+
"anthropic-ratelimit-requests-limit",
|
|
290
|
+
"anthropic-ratelimit-tokens-limit"
|
|
291
|
+
) !== void 0) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
const body = ctx.body;
|
|
295
|
+
if (!body) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
if ("param" in body) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const type = firstString(body.type);
|
|
302
|
+
return type !== void 0 && type in ANTHROPIC_TYPES;
|
|
303
|
+
}
|
|
304
|
+
function classify(ctx) {
|
|
305
|
+
const body = ctx.body ?? {};
|
|
306
|
+
const type = firstString(body.type);
|
|
307
|
+
const message = (firstString(body.message) ?? "").toLowerCase();
|
|
308
|
+
const mapped = type ? ANTHROPIC_TYPES[type] : void 0;
|
|
309
|
+
let category = mapped ?? baseCategoryFromStatus(ctx.status);
|
|
310
|
+
if (category === "invalid_request" && (message.includes("prompt is too long") || message.includes("maximum context") || message.includes("context window"))) {
|
|
311
|
+
category = "context_length_exceeded";
|
|
312
|
+
}
|
|
313
|
+
const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
314
|
+
return { category, code: type, retryAfterMs };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/providers/gemini.ts
|
|
318
|
+
var RPC_STATUS = {
|
|
319
|
+
UNAUTHENTICATED: "authentication",
|
|
320
|
+
PERMISSION_DENIED: "permission",
|
|
321
|
+
NOT_FOUND: "not_found",
|
|
322
|
+
INVALID_ARGUMENT: "invalid_request",
|
|
323
|
+
FAILED_PRECONDITION: "invalid_request",
|
|
324
|
+
OUT_OF_RANGE: "invalid_request",
|
|
325
|
+
UNIMPLEMENTED: "invalid_request",
|
|
326
|
+
RESOURCE_EXHAUSTED: "rate_limit",
|
|
327
|
+
INTERNAL: "server_error",
|
|
328
|
+
UNKNOWN: "server_error",
|
|
329
|
+
ABORTED: "server_error",
|
|
330
|
+
DATA_LOSS: "server_error",
|
|
331
|
+
UNAVAILABLE: "overloaded",
|
|
332
|
+
DEADLINE_EXCEEDED: "timeout",
|
|
333
|
+
CANCELLED: "timeout"
|
|
334
|
+
};
|
|
335
|
+
function rpcStatus(ctx) {
|
|
336
|
+
const status = firstString(ctx.body?.status);
|
|
337
|
+
if (status && /^[A-Z][A-Z_]+$/.test(status)) {
|
|
338
|
+
return status;
|
|
339
|
+
}
|
|
340
|
+
return void 0;
|
|
341
|
+
}
|
|
342
|
+
function matches2(ctx) {
|
|
343
|
+
if (rpcStatus(ctx) !== void 0) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
return typeof ctx.body?.code === "number" && Array.isArray(ctx.body?.details);
|
|
347
|
+
}
|
|
348
|
+
function classify2(ctx) {
|
|
349
|
+
const status = rpcStatus(ctx);
|
|
350
|
+
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"));
|
|
353
|
+
return { category, code: status, retryAfterMs };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/providers/openai.ts
|
|
357
|
+
function matches3(ctx) {
|
|
358
|
+
if (firstHeader(
|
|
359
|
+
ctx.headers,
|
|
360
|
+
"openai-organization",
|
|
361
|
+
"openai-version",
|
|
362
|
+
"openai-processing-ms"
|
|
363
|
+
) !== void 0) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
const body = ctx.body;
|
|
367
|
+
if (!body) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
if ("param" in body) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
const code = firstString(body.code);
|
|
374
|
+
return code === "context_length_exceeded" || code === "insufficient_quota" || code === "invalid_api_key";
|
|
375
|
+
}
|
|
376
|
+
function classify3(ctx) {
|
|
377
|
+
const body = ctx.body ?? {};
|
|
378
|
+
const type = firstString(body.type);
|
|
379
|
+
const code = firstString(body.code);
|
|
380
|
+
const identifier = `${type ?? ""} ${code ?? ""}`.toLowerCase();
|
|
381
|
+
let category = baseCategoryFromStatus(ctx.status);
|
|
382
|
+
if (identifier.includes("context_length") || identifier.includes("context window")) {
|
|
383
|
+
category = "context_length_exceeded";
|
|
384
|
+
} else if (identifier.includes("insufficient_quota")) {
|
|
385
|
+
category = "insufficient_quota";
|
|
386
|
+
} else if (identifier.includes("content_filter") || identifier.includes("content_policy")) {
|
|
387
|
+
category = "content_filter";
|
|
388
|
+
} else if (code === "invalid_api_key" || identifier.includes("authentication")) {
|
|
389
|
+
category = "authentication";
|
|
390
|
+
} else if (category === "unknown" && identifier.includes("rate_limit")) {
|
|
391
|
+
category = "rate_limit";
|
|
392
|
+
}
|
|
393
|
+
const retryAfterMs = parseRetryAfter(firstHeader(ctx.headers, "retry-after-ms"), "ms") ?? parseRetryAfter(firstHeader(ctx.headers, "retry-after"));
|
|
394
|
+
return { category, code: code ?? type, retryAfterMs };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/normalize.ts
|
|
398
|
+
var DETECTORS = [
|
|
399
|
+
{ name: "gemini", matches: matches2 },
|
|
400
|
+
{ name: "anthropic", matches },
|
|
401
|
+
{ name: "openai", matches: matches3 }
|
|
402
|
+
];
|
|
403
|
+
function detectProvider(ctx) {
|
|
404
|
+
for (const detector of DETECTORS) {
|
|
405
|
+
if (detector.matches(ctx)) {
|
|
406
|
+
return detector.name;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return "unknown";
|
|
410
|
+
}
|
|
411
|
+
function classifyFor(provider, ctx) {
|
|
412
|
+
switch (provider) {
|
|
413
|
+
case "openai":
|
|
414
|
+
return classify3(ctx);
|
|
415
|
+
case "anthropic":
|
|
416
|
+
return classify(ctx);
|
|
417
|
+
case "gemini":
|
|
418
|
+
return classify2(ctx);
|
|
419
|
+
default:
|
|
420
|
+
return {
|
|
421
|
+
category: baseCategoryFromStatus(ctx.status),
|
|
422
|
+
code: firstString(ctx.body?.type, ctx.body?.code)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function normalizeError(error, options = {}) {
|
|
427
|
+
const ctx = {
|
|
428
|
+
status: readStatus(error),
|
|
429
|
+
headers: readHeaders(error),
|
|
430
|
+
body: readErrorBody(error)
|
|
431
|
+
};
|
|
432
|
+
const provider = options.provider ?? detectProvider(ctx);
|
|
433
|
+
const classification = classifyFor(provider, ctx);
|
|
434
|
+
let category = classification.category;
|
|
435
|
+
let code = classification.code;
|
|
436
|
+
if (category === "unknown" && ctx.status === void 0) {
|
|
437
|
+
const network = classifyNetworkError(error);
|
|
438
|
+
if (network) {
|
|
439
|
+
category = network.category;
|
|
440
|
+
code = code ?? network.code;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
provider,
|
|
445
|
+
category,
|
|
446
|
+
message: readMessage(error, ctx.body),
|
|
447
|
+
status: ctx.status,
|
|
448
|
+
code,
|
|
449
|
+
retryable: isRetryableCategory(category),
|
|
450
|
+
retryAfterMs: classification.retryAfterMs,
|
|
451
|
+
raw: error
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function isRetryableError(error, options = {}) {
|
|
455
|
+
return normalizeError(error, options).retryable;
|
|
456
|
+
}
|
|
457
|
+
export {
|
|
458
|
+
getRetryDelayMs,
|
|
459
|
+
isRetryableError,
|
|
460
|
+
normalizeError,
|
|
461
|
+
parseGoogleRetryDelay,
|
|
462
|
+
parseRetryAfter
|
|
463
|
+
};
|
|
464
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":";AAOO,SAAS,SAAS,OAAkD;AACzE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAGO,SAAS,eAAe,QAAuC;AACpE,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,eAAe,QAAuC;AACpE,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,UAAU,SAAkB,MAAkC;AAC5E,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,KAAK,YAAY;AAG/B,MAAI,OAAQ,QAA8B,QAAQ,YAAY;AAC5D,UAAM,QAAS,QAA0C,IAAI,IAAI;AACjE,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAGA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,SAAS,SAAS;AAC3B,UACE,MAAM,QAAQ,KAAK,KACnB,OAAO,MAAM,CAAC,MAAM,YACpB,MAAM,CAAC,EAAE,YAAY,MAAM,OAC3B;AACA,eAAO,OAAO,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,IAAI;AAAA,MACnD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,OAAO,GAAG;AACrB,eAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,UAAI,IAAI,YAAY,MAAM,OAAO;AAC/B,cAAM,QAAQ,QAAQ,GAAG;AACzB,eAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,WAAW,OAAoC;AAC7D,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,SAAS,MAAM,QAAQ,IAAI,MAAM,WAAW;AAC7D,QAAM,QAAQ,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ;AACpD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,UAAU;AAAA;AAAA,IAEV,OAAO;AAAA,EACT;AACF;AAGO,SAAS,YAAY,OAAyB;AACnD,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,SAAS,MAAM,QAAQ,IAAI,MAAM,WAAW;AAC7D,SAAO,MAAM,WAAW,UAAU;AACpC;AAOO,SAAS,cACd,OACqC;AACrC,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AAIA,QAAM,aAAwB;AAAA,IAC5B,SAAS,MAAM,KAAK,KAAK,SAAS,MAAM,MAAM,KAAK,IAC/C,MAAM,MAAM,QACZ,MAAM;AAAA,IACV,SAAS,MAAM,IAAI,IACd,MAAM,KAAiC,QACxC;AAAA,IACJ,SAAS,MAAM,QAAQ,KAAK,SAAS,MAAM,SAAS,IAAI,IACnD,MAAM,SAAS,KAAiC,QACjD;AAAA,EACN;AAEA,aAAW,aAAa,YAAY;AAClC,QAAI,SAAS,SAAS,GAAG;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YACd,OACA,MACQ;AACR,QAAM,WAAW,OAAO,YAAY,KAAK,OAAO,IAAI;AACpD,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,MAAI,SAAS,KAAK,GAAG;AACnB,UAAM,SAAS,YAAY,MAAM,OAAO;AACxC,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC/HO,SAAS,uBAAuB,QAAgC;AACrE,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE;AAAA,EACJ;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,QAAI,UAAU,KAAK;AACjB,aAAO;AAAA,IACT;AACA,QAAI,UAAU,KAAK;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGA,IAAM,YAAwC,oBAAI,IAAmB;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,oBAAoB,UAAkC;AACpE,SAAO,UAAU,IAAI,QAAQ;AAC/B;AAGO,SAAS,YACd,YACG,OACiB;AACpB,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,UAAU,SAAS,IAAI;AACrC,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;ACjFA,IAAM,kBAAiD;AAAA,EACrD,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,cAAc;AAAA,EACd,OAAO;AAAA,EACP,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AACf;AAOA,IAAM,gBACJ;AAAA,EACE,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,cAAc,SAAS;AAAA,EACxB,CAAC,mBAAmB,cAAc;AAAA,EAClC,CAAC,oBAAoB,cAAc;AAAA,EACnC,CAAC,cAAc,cAAc;AAC/B;AAYK,SAAS,qBACd,OACmC;AACnC,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,YAAY,MAAM,IAAI;AACnC,MAAI,QAAQ,QAAQ,iBAAiB;AACnC,WAAO,EAAE,UAAU,gBAAgB,IAAI,GAAG,KAAK;AAAA,EACjD;AAKA,QAAM,QAAQ;AAAA,IACZ,YAAY,MAAM,IAAI;AAAA,IACtB,YAAa,MAAM,aAAgD,IAAI;AAAA,EACzE;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,UAAM,WAAW,KAAK,YAAY;AAClC,eAAW,CAAC,SAAS,QAAQ,KAAK,eAAe;AAC/C,UAAI,SAAS,SAAS,OAAO,GAAG;AAC9B,eAAO,EAAE,UAAU,MAAM,KAAK;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ACtEO,SAAS,gBACd,OACA,OAAmB,KACC;AACpB,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AACA,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,YAAY,IAAI;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,OAAO;AAC9B,MAAI,OAAO,SAAS,OAAO,GAAG;AAC5B,UAAM,KAAK,SAAS,OAAO,UAAU,UAAU;AAC/C,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB;AAEA,QAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,WAAO,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,EACtC;AAEA,SAAO;AACT;AAQO,SAAS,sBAAsB,SAAsC;AAC1E,MAAI,CAAC,MAAM,QAAQ,OAAO,GAAG;AAC3B,WAAO;AAAA,EACT;AACA,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,SAAS,MAAM,GAAG;AACrB;AAAA,IACF;AACA,UAAM,OAAO,OAAO,OAAO;AAC3B,QAAI,OAAO,SAAS,YAAY,CAAC,KAAK,SAAS,WAAW,GAAG;AAC3D;AAAA,IACF;AACA,UAAM,QAAQ,OAAO;AACrB,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,UAAU,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC;AAC9C,UAAI,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG;AAC5C,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AACA,QAAI,SAAS,KAAK,GAAG;AACnB,YAAM,UACJ,OAAO,MAAM,YAAY,WACrB,MAAM,UACN,OAAO,MAAM,OAAO;AAC1B,YAAM,QAAQ,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAC9D,UAAI,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG;AAC5C,eAAO,UAAU,MAAO,KAAK,MAAM,QAAQ,GAAG;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,gBACd,OACA,SACA,UAA6B,CAAC,GACtB;AACR,MAAI,OAAO,MAAM,iBAAiB,UAAU;AAC1C,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,SAAS,QAAQ,UAAU;AAEjC,QAAM,cAAc,OAAO,SAAS,OAAO,KAAK,UAAU,IAAI,UAAU;AACxE,QAAM,cAAc,KAAK,IAAI,OAAO,SAAS,KAAK,WAAW;AAE7D,MAAI,WAAW,QAAQ;AACrB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,OAAO,IAAI;AACzB;;;AChGA,IAAM,kBAAiD;AAAA,EACrD,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,eAAe;AACjB;AAGO,SAAS,QAAQ,KAA+B;AACrD,MACE;AAAA,IACE,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAAM,QACN;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,MAAM;AACnB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,YAAY,KAAK,IAAI;AAClC,SAAO,SAAS,UAAa,QAAQ;AACvC;AAEO,SAAS,SAAS,KAAsC;AAC7D,QAAM,OAAO,IAAI,QAAQ,CAAC;AAC1B,QAAM,OAAO,YAAY,KAAK,IAAI;AAClC,QAAM,WAAW,YAAY,KAAK,OAAO,KAAK,IAAI,YAAY;AAE9D,QAAM,SAAS,OAAO,gBAAgB,IAAI,IAAI;AAC9C,MAAI,WAA0B,UAAU,uBAAuB,IAAI,MAAM;AAIzE,MACE,aAAa,sBACZ,QAAQ,SAAS,oBAAoB,KACpC,QAAQ,SAAS,iBAAiB,KAClC,QAAQ,SAAS,gBAAgB,IACnC;AACA,eAAW;AAAA,EACb;AAEA,QAAM,eAAe,gBAAgB,YAAY,IAAI,SAAS,aAAa,CAAC;AAE5E,SAAO,EAAE,UAAU,MAAM,MAAM,aAAa;AAC9C;;;AC3DA,IAAM,aAA4C;AAAA,EAChD,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AACb;AAEA,SAAS,UAAU,KAA0C;AAC3D,QAAM,SAAS,YAAY,IAAI,MAAM,MAAM;AAC3C,MAAI,UAAU,iBAAiB,KAAK,MAAM,GAAG;AAC3C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAASA,SAAQ,KAA+B;AACrD,MAAI,UAAU,GAAG,MAAM,QAAW;AAChC,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,IAAI,MAAM,SAAS,YAAY,MAAM,QAAQ,IAAI,MAAM,OAAO;AAC9E;AAEO,SAASC,UAAS,KAAsC;AAC7D,QAAM,SAAS,UAAU,GAAG;AAE5B,QAAM,SAAS,SAAS,WAAW,MAAM,IAAI;AAC7C,QAAM,WAA0B,UAAU,uBAAuB,IAAI,MAAM;AAE3E,QAAM,eACJ,sBAAsB,IAAI,MAAM,OAAO,KACvC,gBAAgB,YAAY,IAAI,SAAS,aAAa,CAAC;AAEzD,SAAO,EAAE,UAAU,MAAM,QAAQ,aAAa;AAChD;;;AC9CO,SAASC,SAAQ,KAA+B;AACrD,MACE;AAAA,IACE,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAAM,QACN;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,MAAM;AACnB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,YAAY,KAAK,IAAI;AAClC,SACE,SAAS,6BACT,SAAS,wBACT,SAAS;AAEb;AAEO,SAASC,UAAS,KAAsC;AAC7D,QAAM,OAAO,IAAI,QAAQ,CAAC;AAC1B,QAAM,OAAO,YAAY,KAAK,IAAI;AAClC,QAAM,OAAO,YAAY,KAAK,IAAI;AAClC,QAAM,aAAa,GAAG,QAAQ,EAAE,IAAI,QAAQ,EAAE,GAAG,YAAY;AAE7D,MAAI,WAA0B,uBAAuB,IAAI,MAAM;AAE/D,MACE,WAAW,SAAS,gBAAgB,KACpC,WAAW,SAAS,gBAAgB,GACpC;AACA,eAAW;AAAA,EACb,WAAW,WAAW,SAAS,oBAAoB,GAAG;AACpD,eAAW;AAAA,EACb,WACE,WAAW,SAAS,gBAAgB,KACpC,WAAW,SAAS,gBAAgB,GACpC;AACA,eAAW;AAAA,EACb,WACE,SAAS,qBACT,WAAW,SAAS,gBAAgB,GACpC;AACA,eAAW;AAAA,EACb,WAAW,aAAa,aAAa,WAAW,SAAS,YAAY,GAAG;AACtE,eAAW;AAAA,EACb;AAEA,QAAM,eACJ,gBAAgB,YAAY,IAAI,SAAS,gBAAgB,GAAG,IAAI,KAChE,gBAAgB,YAAY,IAAI,SAAS,aAAa,CAAC;AAEzD,SAAO,EAAE,UAAU,MAAM,QAAQ,MAAM,aAAa;AACtD;;;AChDA,IAAM,YAGD;AAAA,EACH,EAAE,MAAM,UAAU,SAAgBC,SAAQ;AAAA,EAC1C,EAAE,MAAM,aAAa,QAA2B;AAAA,EAChD,EAAE,MAAM,UAAU,SAAgBA,SAAQ;AAC5C;AAEA,SAAS,eAAe,KAAgC;AACtD,aAAW,YAAY,WAAW;AAChC,QAAI,SAAS,QAAQ,GAAG,GAAG;AACzB,aAAO,SAAS;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,UAAoB,KAAsC;AAC7E,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAcC,UAAS,GAAG;AAAA,IAC5B,KAAK;AACH,aAAiB,SAAS,GAAG;AAAA,IAC/B,KAAK;AACH,aAAcA,UAAS,GAAG;AAAA,IAC5B;AACE,aAAO;AAAA,QACL,UAAU,uBAAuB,IAAI,MAAM;AAAA,QAC3C,MAAM,YAAY,IAAI,MAAM,MAAM,IAAI,MAAM,IAAI;AAAA,MAClD;AAAA,EACJ;AACF;AAoBO,SAAS,eACd,OACA,UAA4B,CAAC,GACZ;AACjB,QAAM,MAAuB;AAAA,IAC3B,QAAQ,WAAW,KAAK;AAAA,IACxB,SAAS,YAAY,KAAK;AAAA,IAC1B,MAAM,cAAc,KAAK;AAAA,EAC3B;AAEA,QAAM,WAAW,QAAQ,YAAY,eAAe,GAAG;AACvD,QAAM,iBAAiB,YAAY,UAAU,GAAG;AAEhD,MAAI,WAAW,eAAe;AAC9B,MAAI,OAAO,eAAe;AAI1B,MAAI,aAAa,aAAa,IAAI,WAAW,QAAW;AACtD,UAAM,UAAU,qBAAqB,KAAK;AAC1C,QAAI,SAAS;AACX,iBAAW,QAAQ;AACnB,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,YAAY,OAAO,IAAI,IAAI;AAAA,IACpC,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA,WAAW,oBAAoB,QAAQ;AAAA,IACvC,cAAc,eAAe;AAAA,IAC7B,KAAK;AAAA,EACP;AACF;AAMO,SAAS,iBACd,OACA,UAA4B,CAAC,GACpB;AACT,SAAO,eAAe,OAAO,OAAO,EAAE;AACxC;","names":["matches","classify","matches","classify","matches","classify"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-errors",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Normalize OpenAI, Anthropic and Gemini API errors into one shape: category, retryable flag and Retry-After delay. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openai",
|
|
7
|
+
"anthropic",
|
|
8
|
+
"claude",
|
|
9
|
+
"gemini",
|
|
10
|
+
"llm",
|
|
11
|
+
"error",
|
|
12
|
+
"errors",
|
|
13
|
+
"retry",
|
|
14
|
+
"rate-limit",
|
|
15
|
+
"backoff",
|
|
16
|
+
"retryable",
|
|
17
|
+
"provider",
|
|
18
|
+
"portability",
|
|
19
|
+
"ai",
|
|
20
|
+
"agents"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Sebastian Legarraga",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/slegarraga/llm-errors.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/slegarraga/llm-errors#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/slegarraga/llm-errors/issues"
|
|
31
|
+
},
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": false,
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"lint": "eslint .",
|
|
60
|
+
"format": "prettier --write .",
|
|
61
|
+
"format:check": "prettier --check .",
|
|
62
|
+
"prepublishOnly": "npm run build",
|
|
63
|
+
"prepare": "npm run build"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@eslint/js": "^10.0.1",
|
|
67
|
+
"@types/node": "^25.9.1",
|
|
68
|
+
"eslint": "^10.4.1",
|
|
69
|
+
"prettier": "^3.4.2",
|
|
70
|
+
"tsup": "^8.3.5",
|
|
71
|
+
"typescript": "^5.7.2",
|
|
72
|
+
"typescript-eslint": "^8.60.0",
|
|
73
|
+
"vitest": "^2.1.8"
|
|
74
|
+
}
|
|
75
|
+
}
|