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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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":["export { normalizeError, isRetryableError } from './normalize.ts';\nexport {\n getRetryDelayMs,\n parseRetryAfter,\n parseGoogleRetryDelay,\n} from './retry.ts';\nexport type {\n Provider,\n ErrorCategory,\n NormalizedError,\n NormalizeOptions,\n RetryDelayOptions,\n} from './types.ts';\n","/**\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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The LLM provider an error originated from.
|
|
3
|
+
*
|
|
4
|
+
* `'unknown'` is used when the provider cannot be determined from the error
|
|
5
|
+
* shape and no explicit hint was given.
|
|
6
|
+
*/
|
|
7
|
+
type Provider = 'openai' | 'anthropic' | 'gemini' | 'unknown';
|
|
8
|
+
/**
|
|
9
|
+
* A provider-agnostic classification of an API error.
|
|
10
|
+
*
|
|
11
|
+
* Categories are chosen so that two different providers reporting the same
|
|
12
|
+
* underlying problem map to the same value, letting callers write a single
|
|
13
|
+
* `switch` instead of one branch per provider.
|
|
14
|
+
*/
|
|
15
|
+
type ErrorCategory =
|
|
16
|
+
/** Missing or invalid API key / credentials (usually HTTP 401). */
|
|
17
|
+
'authentication'
|
|
18
|
+
/** Authenticated but not allowed to use this resource (usually HTTP 403). */
|
|
19
|
+
| 'permission'
|
|
20
|
+
/** Too many requests in a window; safe to retry after a delay (HTTP 429). */
|
|
21
|
+
| 'rate_limit'
|
|
22
|
+
/** Billing quota or credits exhausted; retrying soon will not help (HTTP 429). */
|
|
23
|
+
| 'insufficient_quota'
|
|
24
|
+
/** The prompt plus completion exceeds the model context window (HTTP 400). */
|
|
25
|
+
| 'context_length_exceeded'
|
|
26
|
+
/** The request payload itself is too large (e.g. HTTP 413). */
|
|
27
|
+
| 'request_too_large'
|
|
28
|
+
/** Malformed or invalid request that will fail again unchanged (HTTP 400/422). */
|
|
29
|
+
| 'invalid_request'
|
|
30
|
+
/** The requested model or resource does not exist (HTTP 404). */
|
|
31
|
+
| 'not_found'
|
|
32
|
+
/** Blocked by a provider content / safety policy. */
|
|
33
|
+
| 'content_filter'
|
|
34
|
+
/** The request or upstream model timed out (e.g. HTTP 504). */
|
|
35
|
+
| 'timeout'
|
|
36
|
+
/** Generic upstream failure; usually transient and retryable (HTTP 500). */
|
|
37
|
+
| 'server_error'
|
|
38
|
+
/** The provider is temporarily overloaded; retryable (HTTP 503/529). */
|
|
39
|
+
| 'overloaded'
|
|
40
|
+
/** Could not be classified into any of the above. */
|
|
41
|
+
| 'unknown';
|
|
42
|
+
/**
|
|
43
|
+
* A single error normalized into one consistent shape across providers.
|
|
44
|
+
*/
|
|
45
|
+
interface NormalizedError {
|
|
46
|
+
/** Which provider the error came from, or `'unknown'`. */
|
|
47
|
+
provider: Provider;
|
|
48
|
+
/** Provider-agnostic category, suitable for branching logic. */
|
|
49
|
+
category: ErrorCategory;
|
|
50
|
+
/** A human-readable message extracted from the provider payload. */
|
|
51
|
+
message: string;
|
|
52
|
+
/** The HTTP status code, when one is available. */
|
|
53
|
+
status?: number;
|
|
54
|
+
/** The provider-specific error code or type string, when available. */
|
|
55
|
+
code?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Whether retrying the same request later may succeed. `true` for transient
|
|
58
|
+
* conditions (rate limits, server errors, overload, timeouts); `false` for
|
|
59
|
+
* deterministic failures (bad request, auth, context length, content filter).
|
|
60
|
+
*/
|
|
61
|
+
retryable: boolean;
|
|
62
|
+
/**
|
|
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.
|
|
66
|
+
*/
|
|
67
|
+
retryAfterMs?: number;
|
|
68
|
+
/** The original value passed to {@link normalizeError}, untouched. */
|
|
69
|
+
raw: unknown;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Options for {@link normalizeError}.
|
|
73
|
+
*/
|
|
74
|
+
interface NormalizeOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Force the provider instead of auto-detecting it. Useful when you already
|
|
77
|
+
* know which client threw, or when the error shape is ambiguous.
|
|
78
|
+
*/
|
|
79
|
+
provider?: Provider;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Options for {@link getRetryDelayMs} exponential-backoff fallback.
|
|
83
|
+
*/
|
|
84
|
+
interface RetryDelayOptions {
|
|
85
|
+
/** Base delay in milliseconds for the first attempt. Default `500`. */
|
|
86
|
+
baseMs?: number;
|
|
87
|
+
/** Upper bound for the computed delay in milliseconds. Default `60000`. */
|
|
88
|
+
maxMs?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Jitter strategy applied to the exponential delay. `'full'` picks a random
|
|
91
|
+
* value in `[0, delay]`, `'none'` disables jitter. Default `'full'`.
|
|
92
|
+
* Ignored when the provider supplied an explicit `retryAfterMs`.
|
|
93
|
+
*/
|
|
94
|
+
jitter?: 'full' | 'none';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Normalize an error thrown by an OpenAI, Anthropic or Gemini client into a
|
|
99
|
+
* single consistent {@link NormalizedError} shape.
|
|
100
|
+
*
|
|
101
|
+
* Accepts SDK error objects, raw `fetch` responses with a parsed body, or
|
|
102
|
+
* plain JSON. It never throws: anything unrecognized comes back as
|
|
103
|
+
* `{ provider: 'unknown', category: 'unknown', retryable: false }`.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* try {
|
|
108
|
+
* await client.messages.create(params);
|
|
109
|
+
* } catch (err) {
|
|
110
|
+
* const e = normalizeError(err);
|
|
111
|
+
* if (e.retryable) await sleep(e.retryAfterMs ?? 1000);
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function normalizeError(error: unknown, options?: NormalizeOptions): NormalizedError;
|
|
116
|
+
/**
|
|
117
|
+
* Convenience wrapper around {@link normalizeError} that returns only whether
|
|
118
|
+
* the error is worth retrying.
|
|
119
|
+
*/
|
|
120
|
+
declare function isRetryableError(error: unknown, options?: NormalizeOptions): boolean;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse a `Retry-After` header value into milliseconds.
|
|
124
|
+
*
|
|
125
|
+
* Per RFC 7231 the value is either a number of seconds (`"30"`) or an
|
|
126
|
+
* HTTP date (`"Wed, 21 Oct 2026 07:28:00 GMT"`). Some providers also send a
|
|
127
|
+
* fractional `retry-after-ms` value, which is accepted when `unit` is `'ms'`.
|
|
128
|
+
*/
|
|
129
|
+
declare function parseRetryAfter(value: string | undefined, unit?: 's' | 'ms'): number | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Parse a Google `RetryInfo.retryDelay` value into milliseconds.
|
|
132
|
+
*
|
|
133
|
+
* Google encodes it either as a duration string (`"30s"`, `"1.5s"`) or as a
|
|
134
|
+
* `{ seconds, nanos }` object inside `error.details[]`.
|
|
135
|
+
*/
|
|
136
|
+
declare function parseGoogleRetryDelay(details: unknown): number | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Suggested delay before retrying, in milliseconds.
|
|
139
|
+
*
|
|
140
|
+
* When the provider supplied an explicit delay (`error.retryAfterMs`) it is
|
|
141
|
+
* always respected. Otherwise this falls back to exponential backoff:
|
|
142
|
+
* `baseMs * 2 ** attempt`, capped at `maxMs`, with optional full jitter.
|
|
143
|
+
*
|
|
144
|
+
* @param error A {@link NormalizedError}.
|
|
145
|
+
* @param attempt Zero-based retry attempt number (0 for the first retry).
|
|
146
|
+
* @param options Backoff tuning. See {@link RetryDelayOptions}.
|
|
147
|
+
*/
|
|
148
|
+
declare function getRetryDelayMs(error: NormalizedError, attempt: number, options?: RetryDelayOptions): number;
|
|
149
|
+
|
|
150
|
+
export { type ErrorCategory, type NormalizeOptions, type NormalizedError, type Provider, type RetryDelayOptions, getRetryDelayMs, isRetryableError, normalizeError, parseGoogleRetryDelay, parseRetryAfter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The LLM provider an error originated from.
|
|
3
|
+
*
|
|
4
|
+
* `'unknown'` is used when the provider cannot be determined from the error
|
|
5
|
+
* shape and no explicit hint was given.
|
|
6
|
+
*/
|
|
7
|
+
type Provider = 'openai' | 'anthropic' | 'gemini' | 'unknown';
|
|
8
|
+
/**
|
|
9
|
+
* A provider-agnostic classification of an API error.
|
|
10
|
+
*
|
|
11
|
+
* Categories are chosen so that two different providers reporting the same
|
|
12
|
+
* underlying problem map to the same value, letting callers write a single
|
|
13
|
+
* `switch` instead of one branch per provider.
|
|
14
|
+
*/
|
|
15
|
+
type ErrorCategory =
|
|
16
|
+
/** Missing or invalid API key / credentials (usually HTTP 401). */
|
|
17
|
+
'authentication'
|
|
18
|
+
/** Authenticated but not allowed to use this resource (usually HTTP 403). */
|
|
19
|
+
| 'permission'
|
|
20
|
+
/** Too many requests in a window; safe to retry after a delay (HTTP 429). */
|
|
21
|
+
| 'rate_limit'
|
|
22
|
+
/** Billing quota or credits exhausted; retrying soon will not help (HTTP 429). */
|
|
23
|
+
| 'insufficient_quota'
|
|
24
|
+
/** The prompt plus completion exceeds the model context window (HTTP 400). */
|
|
25
|
+
| 'context_length_exceeded'
|
|
26
|
+
/** The request payload itself is too large (e.g. HTTP 413). */
|
|
27
|
+
| 'request_too_large'
|
|
28
|
+
/** Malformed or invalid request that will fail again unchanged (HTTP 400/422). */
|
|
29
|
+
| 'invalid_request'
|
|
30
|
+
/** The requested model or resource does not exist (HTTP 404). */
|
|
31
|
+
| 'not_found'
|
|
32
|
+
/** Blocked by a provider content / safety policy. */
|
|
33
|
+
| 'content_filter'
|
|
34
|
+
/** The request or upstream model timed out (e.g. HTTP 504). */
|
|
35
|
+
| 'timeout'
|
|
36
|
+
/** Generic upstream failure; usually transient and retryable (HTTP 500). */
|
|
37
|
+
| 'server_error'
|
|
38
|
+
/** The provider is temporarily overloaded; retryable (HTTP 503/529). */
|
|
39
|
+
| 'overloaded'
|
|
40
|
+
/** Could not be classified into any of the above. */
|
|
41
|
+
| 'unknown';
|
|
42
|
+
/**
|
|
43
|
+
* A single error normalized into one consistent shape across providers.
|
|
44
|
+
*/
|
|
45
|
+
interface NormalizedError {
|
|
46
|
+
/** Which provider the error came from, or `'unknown'`. */
|
|
47
|
+
provider: Provider;
|
|
48
|
+
/** Provider-agnostic category, suitable for branching logic. */
|
|
49
|
+
category: ErrorCategory;
|
|
50
|
+
/** A human-readable message extracted from the provider payload. */
|
|
51
|
+
message: string;
|
|
52
|
+
/** The HTTP status code, when one is available. */
|
|
53
|
+
status?: number;
|
|
54
|
+
/** The provider-specific error code or type string, when available. */
|
|
55
|
+
code?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Whether retrying the same request later may succeed. `true` for transient
|
|
58
|
+
* conditions (rate limits, server errors, overload, timeouts); `false` for
|
|
59
|
+
* deterministic failures (bad request, auth, context length, content filter).
|
|
60
|
+
*/
|
|
61
|
+
retryable: boolean;
|
|
62
|
+
/**
|
|
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.
|
|
66
|
+
*/
|
|
67
|
+
retryAfterMs?: number;
|
|
68
|
+
/** The original value passed to {@link normalizeError}, untouched. */
|
|
69
|
+
raw: unknown;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Options for {@link normalizeError}.
|
|
73
|
+
*/
|
|
74
|
+
interface NormalizeOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Force the provider instead of auto-detecting it. Useful when you already
|
|
77
|
+
* know which client threw, or when the error shape is ambiguous.
|
|
78
|
+
*/
|
|
79
|
+
provider?: Provider;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Options for {@link getRetryDelayMs} exponential-backoff fallback.
|
|
83
|
+
*/
|
|
84
|
+
interface RetryDelayOptions {
|
|
85
|
+
/** Base delay in milliseconds for the first attempt. Default `500`. */
|
|
86
|
+
baseMs?: number;
|
|
87
|
+
/** Upper bound for the computed delay in milliseconds. Default `60000`. */
|
|
88
|
+
maxMs?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Jitter strategy applied to the exponential delay. `'full'` picks a random
|
|
91
|
+
* value in `[0, delay]`, `'none'` disables jitter. Default `'full'`.
|
|
92
|
+
* Ignored when the provider supplied an explicit `retryAfterMs`.
|
|
93
|
+
*/
|
|
94
|
+
jitter?: 'full' | 'none';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Normalize an error thrown by an OpenAI, Anthropic or Gemini client into a
|
|
99
|
+
* single consistent {@link NormalizedError} shape.
|
|
100
|
+
*
|
|
101
|
+
* Accepts SDK error objects, raw `fetch` responses with a parsed body, or
|
|
102
|
+
* plain JSON. It never throws: anything unrecognized comes back as
|
|
103
|
+
* `{ provider: 'unknown', category: 'unknown', retryable: false }`.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* try {
|
|
108
|
+
* await client.messages.create(params);
|
|
109
|
+
* } catch (err) {
|
|
110
|
+
* const e = normalizeError(err);
|
|
111
|
+
* if (e.retryable) await sleep(e.retryAfterMs ?? 1000);
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function normalizeError(error: unknown, options?: NormalizeOptions): NormalizedError;
|
|
116
|
+
/**
|
|
117
|
+
* Convenience wrapper around {@link normalizeError} that returns only whether
|
|
118
|
+
* the error is worth retrying.
|
|
119
|
+
*/
|
|
120
|
+
declare function isRetryableError(error: unknown, options?: NormalizeOptions): boolean;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse a `Retry-After` header value into milliseconds.
|
|
124
|
+
*
|
|
125
|
+
* Per RFC 7231 the value is either a number of seconds (`"30"`) or an
|
|
126
|
+
* HTTP date (`"Wed, 21 Oct 2026 07:28:00 GMT"`). Some providers also send a
|
|
127
|
+
* fractional `retry-after-ms` value, which is accepted when `unit` is `'ms'`.
|
|
128
|
+
*/
|
|
129
|
+
declare function parseRetryAfter(value: string | undefined, unit?: 's' | 'ms'): number | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Parse a Google `RetryInfo.retryDelay` value into milliseconds.
|
|
132
|
+
*
|
|
133
|
+
* Google encodes it either as a duration string (`"30s"`, `"1.5s"`) or as a
|
|
134
|
+
* `{ seconds, nanos }` object inside `error.details[]`.
|
|
135
|
+
*/
|
|
136
|
+
declare function parseGoogleRetryDelay(details: unknown): number | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Suggested delay before retrying, in milliseconds.
|
|
139
|
+
*
|
|
140
|
+
* When the provider supplied an explicit delay (`error.retryAfterMs`) it is
|
|
141
|
+
* always respected. Otherwise this falls back to exponential backoff:
|
|
142
|
+
* `baseMs * 2 ** attempt`, capped at `maxMs`, with optional full jitter.
|
|
143
|
+
*
|
|
144
|
+
* @param error A {@link NormalizedError}.
|
|
145
|
+
* @param attempt Zero-based retry attempt number (0 for the first retry).
|
|
146
|
+
* @param options Backoff tuning. See {@link RetryDelayOptions}.
|
|
147
|
+
*/
|
|
148
|
+
declare function getRetryDelayMs(error: NormalizedError, attempt: number, options?: RetryDelayOptions): number;
|
|
149
|
+
|
|
150
|
+
export { type ErrorCategory, type NormalizeOptions, type NormalizedError, type Provider, type RetryDelayOptions, getRetryDelayMs, isRetryableError, normalizeError, parseGoogleRetryDelay, parseRetryAfter };
|