ai-resilient 0.1.1 → 0.2.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/README.md +4 -2
- package/dist/index.cjs +41 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -11
- package/dist/index.d.ts +38 -11
- package/dist/index.js +41 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ai-resilient
|
|
2
2
|
|
|
3
|
-
Smart model fallback for the [Vercel AI SDK](https://ai-sdk.dev) (v5).
|
|
3
|
+
Smart model fallback for the [Vercel AI SDK](https://ai-sdk.dev) (v5 and v6).
|
|
4
4
|
|
|
5
5
|
`ai-resilient` wraps a chain of language models in a single `LanguageModelV2` that:
|
|
6
6
|
|
|
@@ -15,7 +15,9 @@ Works transparently with `generateText`, `streamText`, `generateObject`, and `st
|
|
|
15
15
|
npm install ai-resilient ai
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`ai` (v5) and `@ai-sdk/provider` are peer dependencies (`@ai-sdk/provider` ships with `ai`, so most package managers install it automatically). `ai-resilient` has zero runtime dependencies.
|
|
18
|
+
`ai` (v5 or v6) and `@ai-sdk/provider` are peer dependencies (`@ai-sdk/provider` ships with `ai`, so most package managers install it automatically). `ai-resilient` has zero runtime dependencies.
|
|
19
|
+
|
|
20
|
+
The returned model mirrors the specification version of the models it wraps — `LanguageModelV2` on ai v5, `LanguageModelV3` on ai v6 — so it plugs into `generateText`/`streamText`/`generateObject`/`streamObject` on either major. All models in one chain must come from the same SDK major; mixing throws at construction.
|
|
19
21
|
|
|
20
22
|
## Usage
|
|
21
23
|
|
package/dist/index.cjs
CHANGED
|
@@ -380,8 +380,25 @@ var AllModelsExhaustedError = class extends Error {
|
|
|
380
380
|
|
|
381
381
|
// src/resilient-model.ts
|
|
382
382
|
var PRELUDE_PART_TYPES = /* @__PURE__ */ new Set(["stream-start", "response-metadata"]);
|
|
383
|
+
function tokenCount(value) {
|
|
384
|
+
if (typeof value === "number") return value;
|
|
385
|
+
if (typeof value === "object" && value !== null) {
|
|
386
|
+
const total = value.total;
|
|
387
|
+
if (typeof total === "number") return total;
|
|
388
|
+
}
|
|
389
|
+
return void 0;
|
|
390
|
+
}
|
|
391
|
+
function extractTotalTokens(usage) {
|
|
392
|
+
if (typeof usage !== "object" || usage === null) return void 0;
|
|
393
|
+
const u = usage;
|
|
394
|
+
const total = tokenCount(u.totalTokens);
|
|
395
|
+
if (total !== void 0) return total;
|
|
396
|
+
const input = tokenCount(u.inputTokens);
|
|
397
|
+
const output = tokenCount(u.outputTokens);
|
|
398
|
+
if (input === void 0 && output === void 0) return void 0;
|
|
399
|
+
return (input ?? 0) + (output ?? 0);
|
|
400
|
+
}
|
|
383
401
|
var ResilientLanguageModel = class {
|
|
384
|
-
specificationVersion = "v2";
|
|
385
402
|
provider = "ai-resilient";
|
|
386
403
|
candidates;
|
|
387
404
|
tracker;
|
|
@@ -398,6 +415,14 @@ var ResilientLanguageModel = class {
|
|
|
398
415
|
if (options.models.length === 0) {
|
|
399
416
|
throw new Error("ai-resilient: at least one model is required");
|
|
400
417
|
}
|
|
418
|
+
const versions = new Set(
|
|
419
|
+
options.models.map((config) => config.model.specificationVersion)
|
|
420
|
+
);
|
|
421
|
+
if (versions.size > 1) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`ai-resilient: all models must implement the same specification version; got ${[...versions].sort().join(", ")}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
401
426
|
const seen = /* @__PURE__ */ new Map();
|
|
402
427
|
this.candidates = options.models.map((config) => {
|
|
403
428
|
const base = `${config.model.provider}:${config.model.modelId}`;
|
|
@@ -409,6 +434,14 @@ var ResilientLanguageModel = class {
|
|
|
409
434
|
this.onFallback = options.onFallback;
|
|
410
435
|
this.onError = options.onError;
|
|
411
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Reported dynamically from the wrapped models: hardcoding 'v2' would
|
|
439
|
+
* make ai v6 wrap this model in its v2→v3 compat adapter and
|
|
440
|
+
* double-convert the already-v3 results passed through unchanged.
|
|
441
|
+
*/
|
|
442
|
+
get specificationVersion() {
|
|
443
|
+
return this.primary.specificationVersion;
|
|
444
|
+
}
|
|
412
445
|
get modelId() {
|
|
413
446
|
return this.primary.modelId;
|
|
414
447
|
}
|
|
@@ -464,7 +497,7 @@ var ResilientLanguageModel = class {
|
|
|
464
497
|
pending.push({ from: modelId, reason: classification });
|
|
465
498
|
}
|
|
466
499
|
async recordSuccess(candidate, headers, usage) {
|
|
467
|
-
const totalTokens =
|
|
500
|
+
const totalTokens = extractTotalTokens(usage);
|
|
468
501
|
await this.tracker.recordSuccess(candidate.key, {
|
|
469
502
|
provider: candidate.config.model.provider,
|
|
470
503
|
headers,
|
|
@@ -485,7 +518,9 @@ var ResilientLanguageModel = class {
|
|
|
485
518
|
}
|
|
486
519
|
this.fireFallbacks(pending, modelId);
|
|
487
520
|
try {
|
|
488
|
-
const result = await candidate.config.model.doGenerate(
|
|
521
|
+
const result = await candidate.config.model.doGenerate(
|
|
522
|
+
options
|
|
523
|
+
);
|
|
489
524
|
this.queueRecordSuccess(
|
|
490
525
|
candidate,
|
|
491
526
|
result.response?.headers,
|
|
@@ -514,7 +549,9 @@ var ResilientLanguageModel = class {
|
|
|
514
549
|
}
|
|
515
550
|
this.fireFallbacks(pending, modelId);
|
|
516
551
|
try {
|
|
517
|
-
const result = await candidate.config.model.doStream(
|
|
552
|
+
const result = await candidate.config.model.doStream(
|
|
553
|
+
options
|
|
554
|
+
);
|
|
518
555
|
return { candidate, result };
|
|
519
556
|
} catch (error) {
|
|
520
557
|
await this.handleFailure(error, candidate, attempts, pending);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/header-parsers.ts","../src/limit-tracker.ts","../src/classify-error.ts","../src/errors.ts","../src/resilient-model.ts","../src/stores/memory.ts"],"sourcesContent":["import type { LanguageModelV2 } from '@ai-sdk/provider';\nimport { LimitTracker } from './limit-tracker';\nimport { ResilientLanguageModel } from './resilient-model';\nimport { memoryStore } from './stores/memory';\nimport type { ResilientOptions } from './types';\n\nexport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nexport { classifyError, getRetryAfterMs } from './classify-error';\nexport { memoryStore } from './stores/memory';\nexport { LimitTracker } from './limit-tracker';\nexport { parseRateLimitHeaders, type ParsedRateLimit } from './header-parsers';\nexport type {\n ErrorClassification,\n FallbackInfo,\n FallbackReason,\n Limits,\n ModelConfig,\n ResilientOptions,\n Store,\n} from './types';\n\nconst DEFAULT_THRESHOLD = 0.1;\nconst DEFAULT_COOLDOWN_MS = 60_000;\n\n/**\n * Create a resilient language model that transparently falls back across\n * the configured models on rate-limit/transient errors and proactively\n * switches away from models near a known rate limit.\n *\n * The returned object implements `LanguageModelV2`, so it works with\n * `generateText`, `streamText`, `generateObject`, and `streamObject`.\n */\nexport function createResilient(options: ResilientOptions): LanguageModelV2 {\n const tracker = new LimitTracker({\n store: options.store ?? memoryStore(),\n threshold: options.threshold ?? DEFAULT_THRESHOLD,\n cooldown: options.cooldown ?? DEFAULT_COOLDOWN_MS,\n });\n return new ResilientLanguageModel({\n models: options.models,\n tracker,\n ...(options.onFallback !== undefined\n ? { onFallback: options.onFallback }\n : {}),\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n });\n}\n","/**\n * Normalized rate-limit state parsed from provider response headers.\n */\nexport interface ParsedRateLimit {\n requestsRemaining?: number | undefined;\n requestsLimit?: number | undefined;\n /** Milliseconds until the request limit resets. */\n requestsResetMs?: number | undefined;\n tokensRemaining?: number | undefined;\n tokensLimit?: number | undefined;\n /** Milliseconds until the token limit resets. */\n tokensResetMs?: number | undefined;\n}\n\ntype Headers = Record<string, string>;\n\nfunction lower(headers: Headers): Headers {\n const out: Headers = {};\n for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;\n return out;\n}\n\nfunction num(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * Parse duration strings used in OpenAI/Groq reset headers, e.g.\n * `\"1s\"`, `\"7.66s\"`, `\"2m59.56s\"`, `\"6m0s\"`, `\"1h2m3s\"`, `\"123ms\"`,\n * or a plain number of seconds.\n */\nexport function parseDuration(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const plain = Number(value);\n if (Number.isFinite(plain)) return plain * 1000;\n\n const pattern = /(\\d+(?:\\.\\d+)?)(ms|h|m|s)/g;\n let ms = 0;\n let matched = false;\n for (const [, amount, unit] of value.matchAll(pattern)) {\n matched = true;\n const n = Number(amount);\n if (unit === 'h') ms += n * 3_600_000;\n else if (unit === 'm') ms += n * 60_000;\n else if (unit === 's') ms += n * 1000;\n else ms += n;\n }\n return matched ? Math.round(ms) : undefined;\n}\n\n/** Parse an RFC 3339 timestamp into ms-from-now. */\nfunction parseResetTimestamp(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const t = Date.parse(value);\n if (Number.isNaN(t)) return undefined;\n return Math.max(0, t - Date.now());\n}\n\n/** OpenAI and Groq use `x-ratelimit-{limit,remaining,reset}-{requests,tokens}`. */\nfunction parseOpenAIStyle(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['x-ratelimit-remaining-requests']),\n requestsLimit: num(h['x-ratelimit-limit-requests']),\n requestsResetMs: parseDuration(h['x-ratelimit-reset-requests']),\n tokensRemaining: num(h['x-ratelimit-remaining-tokens']),\n tokensLimit: num(h['x-ratelimit-limit-tokens']),\n tokensResetMs: parseDuration(h['x-ratelimit-reset-tokens']),\n };\n}\n\n/** Anthropic uses `anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset}`. */\nfunction parseAnthropic(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['anthropic-ratelimit-requests-remaining']),\n requestsLimit: num(h['anthropic-ratelimit-requests-limit']),\n requestsResetMs: parseResetTimestamp(\n h['anthropic-ratelimit-requests-reset'],\n ),\n tokensRemaining: num(h['anthropic-ratelimit-tokens-remaining']),\n tokensLimit: num(h['anthropic-ratelimit-tokens-limit']),\n tokensResetMs: parseResetTimestamp(h['anthropic-ratelimit-tokens-reset']),\n };\n}\n\n/**\n * `ratelimit-reset` is delta-seconds per the IETF draft, but some\n * implementations send a Unix epoch timestamp. Values that would put the\n * reset more than a year out are interpreted as epoch seconds.\n */\nfunction parseIetfReset(value: string | undefined): number | undefined {\n const reset = num(value);\n if (reset === undefined) return undefined;\n const YEAR_S = 31_536_000;\n if (reset > YEAR_S) {\n // Epoch seconds → delta from now, clamped to >= 0.\n return Math.max(0, reset * 1000 - Date.now());\n }\n return reset * 1000;\n}\n\n/**\n * Best-effort parser for providers without a documented scheme\n * (Google, Mistral): tries OpenAI-style headers, then the IETF\n * draft `ratelimit-*` headers.\n */\nfunction parseGeneric(h: Headers): ParsedRateLimit {\n const openai = parseOpenAIStyle(h);\n if (Object.values(openai).some((v) => v !== undefined)) return openai;\n const reset = parseIetfReset(h['ratelimit-reset']);\n return {\n requestsRemaining: num(h['ratelimit-remaining']),\n requestsLimit: num(h['ratelimit-limit']),\n ...(reset !== undefined ? { requestsResetMs: reset } : {}),\n };\n}\n\n/**\n * Parse provider rate-limit headers into a normalized shape.\n * The provider is matched by prefix of the model's `provider` string\n * (e.g. `\"openai.chat\"` → OpenAI). Unknown providers get the\n * best-effort generic parser.\n */\nexport function parseRateLimitHeaders(\n provider: string,\n headers: Headers | undefined,\n): ParsedRateLimit {\n if (headers === undefined) return {};\n const h = lower(headers);\n const p = provider.toLowerCase();\n\n if (p.startsWith('openai') || p.startsWith('groq')) {\n return parseOpenAIStyle(h);\n }\n if (p.startsWith('anthropic')) {\n return parseAnthropic(h);\n }\n // google, mistral, and unknown providers\n return parseGeneric(h);\n}\n","import { parseRateLimitHeaders } from './header-parsers';\nimport type { Limits, Store } from './types';\n\nconst MINUTE_MS = 60_000;\nconst DAY_MS = 86_400_000;\n/** How long a parsed header snapshot stays relevant without a reset hint. */\nconst DEFAULT_SNAPSHOT_TTL_MS = 60_000;\n/** Upper bound for header-snapshot lifetimes: garbage reset values must not pin a snapshot. */\nconst MAX_SNAPSHOT_TTL_MS = DAY_MS;\n\ninterface UsageState {\n /** Timestamps (ms) of requests within the last 24h. */\n requests: number[];\n /** `[timestamp, tokenCount]` pairs within the last minute. */\n tokens: [number, number][];\n}\n\ninterface DimensionSnapshot {\n remaining: number;\n limit?: number | undefined;\n /** After this timestamp the dimension has reset and is ignored. */\n expiresAt: number;\n}\n\ninterface HeaderSnapshot {\n requests?: DimensionSnapshot | undefined;\n tokens?: DimensionSnapshot | undefined;\n}\n\nexport interface RecordSuccessInput {\n provider: string;\n headers?: Record<string, string> | undefined;\n totalTokens?: number | undefined;\n limits?: Limits | undefined;\n}\n\n/**\n * Tracks per-model rate-limit state: cooldown benches after rate-limit\n * errors, provider-header snapshots, and sliding-window self-counted\n * usage against user-declared limits.\n *\n * All state goes through the pluggable `Store`; any store failure is\n * swallowed and treated as \"model available / nothing recorded\".\n */\nexport class LimitTracker {\n private readonly store: Store;\n private readonly threshold: number;\n private readonly cooldown: number;\n /** Per-key promise chains serializing usage counter updates. */\n private readonly usageLocks = new Map<string, Promise<void>>();\n\n constructor(options: { store: Store; threshold: number; cooldown: number }) {\n this.store = options.store;\n this.threshold = options.threshold;\n this.cooldown = options.cooldown;\n }\n\n private benchKey(modelKey: string): string {\n return `ai-resilient:bench:${modelKey}`;\n }\n private usageKey(modelKey: string): string {\n return `ai-resilient:usage:${modelKey}`;\n }\n private headersKey(modelKey: string): string {\n return `ai-resilient:headers:${modelKey}`;\n }\n\n /**\n * False if the model is benched (cooldown active) or usage is at or\n * beyond `(1 - threshold)` of any known limit.\n */\n async isAvailable(modelKey: string, limits?: Limits): Promise<boolean> {\n try {\n if (await this.isBenched(modelKey)) return false;\n if (await this.isNearHeaderLimit(modelKey)) return false;\n if (limits && (await this.isNearDeclaredLimit(modelKey, limits))) {\n return false;\n }\n return true;\n } catch {\n // Store failure: assume available.\n return true;\n }\n }\n\n private async isBenched(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.benchKey(modelKey));\n if (raw === null) return false;\n // Belt and braces for stores without real TTL support: the value is\n // the bench expiry timestamp. Unparseable values fail open (not\n // benched), consistent with the store-failure policy in isAvailable.\n const expiresAt = Number(raw);\n return Number.isFinite(expiresAt) && Date.now() < expiresAt;\n }\n\n private async isNearHeaderLimit(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.headersKey(modelKey));\n if (raw === null) return false;\n const snapshot = JSON.parse(raw) as HeaderSnapshot;\n const now = Date.now();\n // Each dimension keeps its own expiration so a fast-resetting,\n // non-limiting dimension can't wipe out a still-exhausted one.\n for (const dim of [snapshot.requests, snapshot.tokens]) {\n if (dim === undefined || now >= dim.expiresAt) continue;\n if (this.remainingBelowThreshold(dim.remaining, dim.limit)) return true;\n }\n return false;\n }\n\n private remainingBelowThreshold(\n remaining: number | undefined,\n limit: number | undefined,\n ): boolean {\n if (remaining === undefined) return false;\n if (remaining <= 0) return true;\n if (limit === undefined || limit <= 0) return false;\n return remaining / limit <= this.threshold;\n }\n\n private async isNearDeclaredLimit(\n modelKey: string,\n limits: Limits,\n ): Promise<boolean> {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n if (limits.requestsPerMinute !== undefined) {\n const used = usage.requests.filter((t) => now - t < MINUTE_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerMinute) return true;\n }\n if (limits.requestsPerDay !== undefined) {\n const used = usage.requests.filter((t) => now - t < DAY_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerDay) return true;\n }\n if (limits.tokensPerMinute !== undefined) {\n const used = usage.tokens\n .filter(([t]) => now - t < MINUTE_MS)\n .reduce((sum, [, n]) => sum + n, 0);\n if (used >= (1 - this.threshold) * limits.tokensPerMinute) return true;\n }\n return false;\n }\n\n /**\n * Record a successful call: parse provider rate-limit headers and\n * increment sliding-window counters for declared limits.\n */\n async recordSuccess(\n modelKey: string,\n input: RecordSuccessInput,\n ): Promise<void> {\n try {\n if (input.headers !== undefined) {\n await this.recordHeaders(modelKey, input.provider, input.headers);\n }\n if (input.limits !== undefined) {\n await this.recordUsage(modelKey, input.limits, input.totalTokens);\n }\n } catch {\n // Store failure: skip recording.\n }\n }\n\n private async recordHeaders(\n modelKey: string,\n provider: string,\n headers: Record<string, string>,\n ): Promise<void> {\n const parsed = parseRateLimitHeaders(provider, headers);\n const now = Date.now();\n const snapshot: HeaderSnapshot = {};\n if (parsed.requestsRemaining !== undefined) {\n snapshot.requests = {\n remaining: parsed.requestsRemaining,\n limit: parsed.requestsLimit,\n expiresAt:\n now +\n Math.min(\n parsed.requestsResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n if (parsed.tokensRemaining !== undefined) {\n snapshot.tokens = {\n remaining: parsed.tokensRemaining,\n limit: parsed.tokensLimit,\n expiresAt:\n now +\n Math.min(\n parsed.tokensResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n // Keep the snapshot around until the *slowest* dimension resets;\n // each dimension expires individually in isNearHeaderLimit.\n const ttl = Math.max(\n (snapshot.requests?.expiresAt ?? 0) - now,\n (snapshot.tokens?.expiresAt ?? 0) - now,\n );\n if (ttl <= 0) return;\n await this.store.set(\n this.headersKey(modelKey),\n JSON.stringify(snapshot),\n ttl,\n );\n }\n\n private recordUsage(\n modelKey: string,\n limits: Limits,\n totalTokens: number | undefined,\n ): Promise<void> {\n // Serialize read-modify-write per key so concurrent calls in this\n // process don't drop each other's counts. Distributed setups sharing\n // a store across processes should prefer a store with atomic\n // operations; self-counting is best-effort there.\n return this.serializeUsageUpdate(modelKey, async () => {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n const trackDay = limits.requestsPerDay !== undefined ? DAY_MS : MINUTE_MS;\n usage.requests = usage.requests.filter((t) => now - t < trackDay);\n usage.requests.push(now);\n\n usage.tokens = usage.tokens.filter(([t]) => now - t < MINUTE_MS);\n if (limits.tokensPerMinute !== undefined && totalTokens !== undefined) {\n usage.tokens.push([now, totalTokens]);\n }\n\n await this.store.set(\n this.usageKey(modelKey),\n JSON.stringify(usage),\n trackDay,\n );\n });\n }\n\n private serializeUsageUpdate(\n modelKey: string,\n fn: () => Promise<void>,\n ): Promise<void> {\n const prev = this.usageLocks.get(modelKey) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n this.usageLocks.set(modelKey, tail);\n void tail.then(() => {\n if (this.usageLocks.get(modelKey) === tail) {\n this.usageLocks.delete(modelKey);\n }\n });\n return next;\n }\n\n private async readUsage(modelKey: string): Promise<UsageState> {\n const raw = await this.store.get(this.usageKey(modelKey));\n if (raw === null) return { requests: [], tokens: [] };\n const parsed = JSON.parse(raw) as Partial<UsageState>;\n return {\n requests: Array.isArray(parsed.requests) ? parsed.requests : [],\n tokens: Array.isArray(parsed.tokens) ? parsed.tokens : [],\n };\n }\n\n /**\n * Bench a model after a rate-limit error, for `retryAfterMs` when the\n * provider sent one, otherwise for the configured cooldown.\n */\n async recordRateLimit(\n modelKey: string,\n retryAfterMs?: number,\n ): Promise<void> {\n const benchMs = retryAfterMs ?? this.cooldown;\n if (benchMs <= 0) return;\n try {\n await this.store.set(\n this.benchKey(modelKey),\n String(Date.now() + benchMs),\n benchMs,\n );\n } catch {\n // Store failure: skip recording.\n }\n }\n}\n","import type { ErrorClassification } from './types';\n\ninterface ErrorShape {\n statusCode?: number | undefined;\n responseHeaders?: Record<string, string> | undefined;\n isRetryable?: boolean | undefined;\n message?: string | undefined;\n}\n\n/**\n * Extract the shape of an AI SDK `APICallError` (statusCode,\n * responseHeaders, isRetryable) via duck typing, so we don't need a\n * runtime dependency on `@ai-sdk/provider`.\n */\nfunction getErrorShape(error: unknown): ErrorShape {\n if (typeof error !== 'object' || error === null) {\n return { message: String(error) };\n }\n const e = error as Record<string, unknown>;\n return {\n statusCode:\n typeof e['statusCode'] === 'number' ? e['statusCode'] : undefined,\n responseHeaders:\n typeof e['responseHeaders'] === 'object' && e['responseHeaders'] !== null\n ? (e['responseHeaders'] as Record<string, string>)\n : undefined,\n isRetryable:\n typeof e['isRetryable'] === 'boolean' ? e['isRetryable'] : undefined,\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n };\n}\n\nconst RATE_LIMIT_PATTERN =\n /rate.?limit|too many requests|quota|exhausted|billing|insufficient.?quota/i;\n\nconst TRANSIENT_PATTERN =\n /overloaded|timeout|timed.?out|econnreset|econnrefused|etimedout|socket hang up|network|fetch failed|service unavailable|internal server error|bad gateway/i;\n\nfunction getHeader(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Classify an error from a model call:\n *\n * - `rate-limit`: HTTP 429, quota/exhausted message patterns, or a\n * `retry-after` header — fall back and bench the model.\n * - `transient`: HTTP 5xx, overloaded/network/timeout errors — fall back\n * without benching.\n * - `fatal`: 400/401/403 and everything else — throw immediately.\n */\nexport function classifyError(error: unknown): ErrorClassification {\n const { statusCode, responseHeaders, isRetryable, message } =\n getErrorShape(error);\n const msg = message ?? '';\n\n if (statusCode === 429) return 'rate-limit';\n if (statusCode === 400 || statusCode === 401 || statusCode === 403) {\n return 'fatal';\n }\n if (statusCode !== undefined && statusCode >= 500) return 'transient';\n if (getHeader(responseHeaders, 'retry-after') !== undefined) {\n return 'rate-limit';\n }\n if (RATE_LIMIT_PATTERN.test(msg)) return 'rate-limit';\n if (isRetryable === true) return 'transient';\n if (TRANSIENT_PATTERN.test(msg)) return 'transient';\n return 'fatal';\n}\n\n/**\n * Parse a `retry-after` header from an error's response headers into\n * milliseconds. Supports delta-seconds and HTTP-date formats.\n */\nexport function getRetryAfterMs(error: unknown): number | undefined {\n const { responseHeaders } = getErrorShape(error);\n const raw = getHeader(responseHeaders, 'retry-after');\n if (raw === undefined) return undefined;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n return Math.max(0, date - Date.now());\n }\n return undefined;\n}\n","import type { ErrorClassification } from './types';\n\n/** One failed attempt against a model in the fallback chain. */\nexport interface ModelAttempt {\n modelId: string;\n error: unknown;\n classification: ErrorClassification;\n}\n\n/**\n * Thrown when every model in the fallback chain failed with a\n * rate-limit or transient error.\n */\nexport class AllModelsExhaustedError extends Error {\n override readonly name = 'AllModelsExhaustedError';\n readonly attempts: ModelAttempt[];\n\n constructor(attempts: ModelAttempt[]) {\n const summary = attempts\n .map((a) => `${a.modelId} (${a.classification})`)\n .join(', ');\n super(\n `All models exhausted after ${attempts.length} attempt(s): ${summary}`,\n );\n this.attempts = attempts;\n }\n\n static isInstance(error: unknown): error is AllModelsExhaustedError {\n return error instanceof Error && error.name === 'AllModelsExhaustedError';\n }\n}\n","import type {\n LanguageModelV2,\n LanguageModelV2CallOptions,\n LanguageModelV2StreamPart,\n LanguageModelV2Usage,\n} from '@ai-sdk/provider';\nimport { classifyError, getRetryAfterMs } from './classify-error';\nimport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nimport { LimitTracker } from './limit-tracker';\nimport type { FallbackReason, ModelConfig, ResilientOptions } from './types';\n\ntype DoGenerateResult = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;\ntype DoStreamResult = Awaited<ReturnType<LanguageModelV2['doStream']>>;\n\ninterface Candidate {\n config: ModelConfig;\n /** Unique store key: `provider:modelId` (+ index suffix on collision). */\n key: string;\n}\n\ninterface PlanEntry {\n candidate: Candidate;\n /** True when the model was proactively filtered by the LimitTracker. */\n skip: boolean;\n}\n\ninterface PendingFallback {\n from: string;\n reason: FallbackReason;\n}\n\nconst PRELUDE_PART_TYPES = new Set(['stream-start', 'response-metadata']);\n\n/**\n * A `LanguageModelV2` that transparently falls back across a chain of\n * models on rate-limit and transient errors, and proactively skips\n * models that are near a known rate limit.\n */\nexport class ResilientLanguageModel implements LanguageModelV2 {\n readonly specificationVersion = 'v2' as const;\n readonly provider = 'ai-resilient';\n\n private readonly candidates: Candidate[];\n private readonly tracker: LimitTracker;\n private readonly onFallback: ResilientOptions['onFallback'];\n private readonly onError: ResilientOptions['onError'];\n\n /**\n * Bookkeeping writes chained off the response path. buildPlan awaits\n * this so the next call still sees all prior usage before deciding\n * availability (read-your-writes), without success responses paying\n * store-write latency.\n */\n private pendingRecords: Promise<void> = Promise.resolve();\n\n constructor(options: {\n models: ModelConfig[];\n tracker: LimitTracker;\n onFallback?: ResilientOptions['onFallback'];\n onError?: ResilientOptions['onError'];\n }) {\n if (options.models.length === 0) {\n throw new Error('ai-resilient: at least one model is required');\n }\n const seen = new Map<string, number>();\n this.candidates = options.models.map((config) => {\n const base = `${config.model.provider}:${config.model.modelId}`;\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n return { config, key: count === 0 ? base : `${base}#${count}` };\n });\n this.tracker = options.tracker;\n this.onFallback = options.onFallback;\n this.onError = options.onError;\n }\n\n get modelId(): string {\n return this.primary.modelId;\n }\n\n get supportedUrls(): LanguageModelV2['supportedUrls'] {\n return this.primary.supportedUrls;\n }\n\n private get primary(): LanguageModelV2 {\n // candidates is non-empty by construction\n return this.candidates[0]!.config.model;\n }\n\n private queueRecordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: LanguageModelV2Usage | undefined,\n ): void {\n this.pendingRecords = this.pendingRecords\n .then(() => this.recordSuccess(candidate, headers, usage))\n .catch(() => {\n // Store bookkeeping failures must never surface to callers.\n });\n }\n\n /**\n * Order candidates and mark near-limit/benched ones as skipped. If\n * every model would be skipped, try all of them in configured order\n * anyway — attempting beats failing without trying.\n */\n private async buildPlan(): Promise<PlanEntry[]> {\n await this.pendingRecords;\n const plan: PlanEntry[] = [];\n for (const candidate of this.candidates) {\n const available = await this.tracker.isAvailable(\n candidate.key,\n candidate.config.limits,\n );\n plan.push({ candidate, skip: !available });\n }\n if (plan.every((entry) => entry.skip)) {\n for (const entry of plan) entry.skip = false;\n }\n return plan;\n }\n\n private fireFallbacks(pending: PendingFallback[], to: string): void {\n for (const { from, reason } of pending) {\n this.onFallback?.({ from, to, reason });\n }\n pending.length = 0;\n }\n\n /**\n * Handle a failed attempt: notify, classify, bench on rate-limit, and\n * queue the fallback notification. Rethrows fatal errors.\n */\n private async handleFailure(\n error: unknown,\n candidate: Candidate,\n attempts: ModelAttempt[],\n pending: PendingFallback[],\n ): Promise<void> {\n const modelId = candidate.config.model.modelId;\n this.onError?.(error, modelId);\n const classification = classifyError(error);\n if (classification === 'fatal') throw error;\n attempts.push({ modelId, error, classification });\n if (classification === 'rate-limit') {\n await this.tracker.recordRateLimit(candidate.key, getRetryAfterMs(error));\n }\n pending.push({ from: modelId, reason: classification });\n }\n\n private async recordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: LanguageModelV2Usage | undefined,\n ): Promise<void> {\n const totalTokens =\n usage?.totalTokens ??\n (usage?.inputTokens !== undefined || usage?.outputTokens !== undefined\n ? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)\n : undefined);\n await this.tracker.recordSuccess(candidate.key, {\n provider: candidate.config.model.provider,\n headers,\n totalTokens,\n limits: candidate.config.limits,\n });\n }\n\n async doGenerate(\n options: LanguageModelV2CallOptions,\n ): Promise<DoGenerateResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n\n for (const entry of plan) {\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = await candidate.config.model.doGenerate(options);\n this.queueRecordSuccess(\n candidate,\n result.response?.headers,\n result.usage,\n );\n return result;\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n }\n\n async doStream(options: LanguageModelV2CallOptions): Promise<DoStreamResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n let index = 0;\n\n const acquire = async (): Promise<{\n candidate: Candidate;\n result: DoStreamResult;\n }> => {\n while (index < plan.length) {\n const entry = plan[index++]!;\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = await candidate.config.model.doStream(options);\n return { candidate, result };\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n };\n\n // Try candidates until one produces content (or finishes cleanly)\n // before returning, so the returned request/response metadata always\n // belongs to the model that actually serves the stream.\n for (;;) {\n const { candidate, result } = await acquire();\n const reader = result.stream.getReader();\n const prelude: LanguageModelV2StreamPart[] = [];\n let firstContent: LanguageModelV2StreamPart | undefined;\n let usage: LanguageModelV2Usage | undefined;\n let done = false;\n\n try {\n for (;;) {\n const { done: readDone, value } = await reader.read();\n if (readDone) {\n done = true;\n break;\n }\n if (value.type === 'error') {\n // Pre-content stream error: treat like a failed call so we\n // can fall back to the next model.\n throw value.error;\n }\n if (value.type === 'finish') usage = value.usage;\n if (PRELUDE_PART_TYPES.has(value.type)) {\n prelude.push(value);\n continue;\n }\n firstContent = value;\n break;\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n // Rethrows fatal errors; otherwise fall through to next candidate.\n await this.handleFailure(error, candidate, attempts, pending);\n continue;\n }\n\n // Committed: first content part arrived (or the stream finished).\n // Errors from here on propagate to the consumer unchanged.\n let preludeIndex = 0;\n const stream = new ReadableStream<LanguageModelV2StreamPart>({\n pull: async (controller) => {\n if (preludeIndex < prelude.length) {\n controller.enqueue(prelude[preludeIndex++]);\n return;\n }\n if (firstContent !== undefined) {\n const part = firstContent;\n firstContent = undefined;\n controller.enqueue(part);\n return;\n }\n if (!done) {\n const { done: readDone, value } = await reader.read();\n if (!readDone) {\n if (value.type === 'finish') usage = value.usage;\n controller.enqueue(value);\n return;\n }\n done = true;\n }\n this.queueRecordSuccess(candidate, result.response?.headers, usage);\n controller.close();\n },\n cancel: (reason) => reader.cancel(reason),\n });\n\n return {\n stream,\n ...(result.request !== undefined ? { request: result.request } : {}),\n ...(result.response !== undefined ? { response: result.response } : {}),\n };\n }\n }\n}\n","import type { Store } from '../types';\n\ninterface Entry {\n value: string;\n expiresAt?: number;\n}\n\n/**\n * In-process `Store` backed by a `Map` with lazy TTL eviction.\n * Suitable for long-running servers; use a Redis/KV adapter for\n * serverless deployments.\n */\nexport function memoryStore(): Store {\n const map = new Map<string, Entry>();\n\n function evictIfExpired(key: string): Entry | undefined {\n const entry = map.get(key);\n if (entry === undefined) return undefined;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n return {\n get(key) {\n const entry = evictIfExpired(key);\n return Promise.resolve(entry?.value ?? null);\n },\n set(key, value, ttlMs) {\n map.set(key, {\n value,\n ...(ttlMs !== undefined ? { expiresAt: Date.now() + ttlMs } : {}),\n });\n return Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/header-parsers.ts","../src/limit-tracker.ts","../src/classify-error.ts","../src/errors.ts","../src/resilient-model.ts","../src/stores/memory.ts"],"sourcesContent":["import { LimitTracker } from './limit-tracker';\nimport { ResilientLanguageModel } from './resilient-model';\nimport { memoryStore } from './stores/memory';\nimport type {\n AnyLanguageModel,\n ResilientOptions,\n SpecificationVersion,\n} from './types';\n\nexport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nexport { classifyError, getRetryAfterMs } from './classify-error';\nexport { memoryStore } from './stores/memory';\nexport { LimitTracker } from './limit-tracker';\nexport { parseRateLimitHeaders, type ParsedRateLimit } from './header-parsers';\nexport type {\n AnyLanguageModel,\n ErrorClassification,\n FallbackInfo,\n FallbackReason,\n Limits,\n ModelConfig,\n ResilientOptions,\n SpecificationVersion,\n Store,\n} from './types';\n\nconst DEFAULT_THRESHOLD = 0.1;\nconst DEFAULT_COOLDOWN_MS = 60_000;\n\n/**\n * Create a resilient language model that transparently falls back across\n * the configured models on rate-limit/transient errors and proactively\n * switches away from models near a known rate limit.\n *\n * The returned model mirrors the specification version of the models it\n * wraps — `LanguageModelV2` for ai v5 models, `LanguageModelV3` for\n * ai v6 models — so it works with `generateText`, `streamText`,\n * `generateObject`, and `streamObject` on either SDK major. Mixing spec\n * versions in one chain throws.\n */\nexport function createResilient<Version extends SpecificationVersion>(\n options: ResilientOptions<Version>,\n): AnyLanguageModel<Version> {\n const tracker = new LimitTracker({\n store: options.store ?? memoryStore(),\n threshold: options.threshold ?? DEFAULT_THRESHOLD,\n cooldown: options.cooldown ?? DEFAULT_COOLDOWN_MS,\n });\n return new ResilientLanguageModel({\n models: options.models,\n tracker,\n ...(options.onFallback !== undefined\n ? { onFallback: options.onFallback }\n : {}),\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n }) as unknown as AnyLanguageModel<Version>;\n}\n","/**\n * Normalized rate-limit state parsed from provider response headers.\n */\nexport interface ParsedRateLimit {\n requestsRemaining?: number | undefined;\n requestsLimit?: number | undefined;\n /** Milliseconds until the request limit resets. */\n requestsResetMs?: number | undefined;\n tokensRemaining?: number | undefined;\n tokensLimit?: number | undefined;\n /** Milliseconds until the token limit resets. */\n tokensResetMs?: number | undefined;\n}\n\ntype Headers = Record<string, string>;\n\nfunction lower(headers: Headers): Headers {\n const out: Headers = {};\n for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;\n return out;\n}\n\nfunction num(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * Parse duration strings used in OpenAI/Groq reset headers, e.g.\n * `\"1s\"`, `\"7.66s\"`, `\"2m59.56s\"`, `\"6m0s\"`, `\"1h2m3s\"`, `\"123ms\"`,\n * or a plain number of seconds.\n */\nexport function parseDuration(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const plain = Number(value);\n if (Number.isFinite(plain)) return plain * 1000;\n\n const pattern = /(\\d+(?:\\.\\d+)?)(ms|h|m|s)/g;\n let ms = 0;\n let matched = false;\n for (const [, amount, unit] of value.matchAll(pattern)) {\n matched = true;\n const n = Number(amount);\n if (unit === 'h') ms += n * 3_600_000;\n else if (unit === 'm') ms += n * 60_000;\n else if (unit === 's') ms += n * 1000;\n else ms += n;\n }\n return matched ? Math.round(ms) : undefined;\n}\n\n/** Parse an RFC 3339 timestamp into ms-from-now. */\nfunction parseResetTimestamp(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const t = Date.parse(value);\n if (Number.isNaN(t)) return undefined;\n return Math.max(0, t - Date.now());\n}\n\n/** OpenAI and Groq use `x-ratelimit-{limit,remaining,reset}-{requests,tokens}`. */\nfunction parseOpenAIStyle(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['x-ratelimit-remaining-requests']),\n requestsLimit: num(h['x-ratelimit-limit-requests']),\n requestsResetMs: parseDuration(h['x-ratelimit-reset-requests']),\n tokensRemaining: num(h['x-ratelimit-remaining-tokens']),\n tokensLimit: num(h['x-ratelimit-limit-tokens']),\n tokensResetMs: parseDuration(h['x-ratelimit-reset-tokens']),\n };\n}\n\n/** Anthropic uses `anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset}`. */\nfunction parseAnthropic(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['anthropic-ratelimit-requests-remaining']),\n requestsLimit: num(h['anthropic-ratelimit-requests-limit']),\n requestsResetMs: parseResetTimestamp(\n h['anthropic-ratelimit-requests-reset'],\n ),\n tokensRemaining: num(h['anthropic-ratelimit-tokens-remaining']),\n tokensLimit: num(h['anthropic-ratelimit-tokens-limit']),\n tokensResetMs: parseResetTimestamp(h['anthropic-ratelimit-tokens-reset']),\n };\n}\n\n/**\n * `ratelimit-reset` is delta-seconds per the IETF draft, but some\n * implementations send a Unix epoch timestamp. Values that would put the\n * reset more than a year out are interpreted as epoch seconds.\n */\nfunction parseIetfReset(value: string | undefined): number | undefined {\n const reset = num(value);\n if (reset === undefined) return undefined;\n const YEAR_S = 31_536_000;\n if (reset > YEAR_S) {\n // Epoch seconds → delta from now, clamped to >= 0.\n return Math.max(0, reset * 1000 - Date.now());\n }\n return reset * 1000;\n}\n\n/**\n * Best-effort parser for providers without a documented scheme\n * (Google, Mistral): tries OpenAI-style headers, then the IETF\n * draft `ratelimit-*` headers.\n */\nfunction parseGeneric(h: Headers): ParsedRateLimit {\n const openai = parseOpenAIStyle(h);\n if (Object.values(openai).some((v) => v !== undefined)) return openai;\n const reset = parseIetfReset(h['ratelimit-reset']);\n return {\n requestsRemaining: num(h['ratelimit-remaining']),\n requestsLimit: num(h['ratelimit-limit']),\n ...(reset !== undefined ? { requestsResetMs: reset } : {}),\n };\n}\n\n/**\n * Parse provider rate-limit headers into a normalized shape.\n * The provider is matched by prefix of the model's `provider` string\n * (e.g. `\"openai.chat\"` → OpenAI). Unknown providers get the\n * best-effort generic parser.\n */\nexport function parseRateLimitHeaders(\n provider: string,\n headers: Headers | undefined,\n): ParsedRateLimit {\n if (headers === undefined) return {};\n const h = lower(headers);\n const p = provider.toLowerCase();\n\n if (p.startsWith('openai') || p.startsWith('groq')) {\n return parseOpenAIStyle(h);\n }\n if (p.startsWith('anthropic')) {\n return parseAnthropic(h);\n }\n // google, mistral, and unknown providers\n return parseGeneric(h);\n}\n","import { parseRateLimitHeaders } from './header-parsers';\nimport type { Limits, Store } from './types';\n\nconst MINUTE_MS = 60_000;\nconst DAY_MS = 86_400_000;\n/** How long a parsed header snapshot stays relevant without a reset hint. */\nconst DEFAULT_SNAPSHOT_TTL_MS = 60_000;\n/** Upper bound for header-snapshot lifetimes: garbage reset values must not pin a snapshot. */\nconst MAX_SNAPSHOT_TTL_MS = DAY_MS;\n\ninterface UsageState {\n /** Timestamps (ms) of requests within the last 24h. */\n requests: number[];\n /** `[timestamp, tokenCount]` pairs within the last minute. */\n tokens: [number, number][];\n}\n\ninterface DimensionSnapshot {\n remaining: number;\n limit?: number | undefined;\n /** After this timestamp the dimension has reset and is ignored. */\n expiresAt: number;\n}\n\ninterface HeaderSnapshot {\n requests?: DimensionSnapshot | undefined;\n tokens?: DimensionSnapshot | undefined;\n}\n\nexport interface RecordSuccessInput {\n provider: string;\n headers?: Record<string, string> | undefined;\n totalTokens?: number | undefined;\n limits?: Limits | undefined;\n}\n\n/**\n * Tracks per-model rate-limit state: cooldown benches after rate-limit\n * errors, provider-header snapshots, and sliding-window self-counted\n * usage against user-declared limits.\n *\n * All state goes through the pluggable `Store`; any store failure is\n * swallowed and treated as \"model available / nothing recorded\".\n */\nexport class LimitTracker {\n private readonly store: Store;\n private readonly threshold: number;\n private readonly cooldown: number;\n /** Per-key promise chains serializing usage counter updates. */\n private readonly usageLocks = new Map<string, Promise<void>>();\n\n constructor(options: { store: Store; threshold: number; cooldown: number }) {\n this.store = options.store;\n this.threshold = options.threshold;\n this.cooldown = options.cooldown;\n }\n\n private benchKey(modelKey: string): string {\n return `ai-resilient:bench:${modelKey}`;\n }\n private usageKey(modelKey: string): string {\n return `ai-resilient:usage:${modelKey}`;\n }\n private headersKey(modelKey: string): string {\n return `ai-resilient:headers:${modelKey}`;\n }\n\n /**\n * False if the model is benched (cooldown active) or usage is at or\n * beyond `(1 - threshold)` of any known limit.\n */\n async isAvailable(modelKey: string, limits?: Limits): Promise<boolean> {\n try {\n if (await this.isBenched(modelKey)) return false;\n if (await this.isNearHeaderLimit(modelKey)) return false;\n if (limits && (await this.isNearDeclaredLimit(modelKey, limits))) {\n return false;\n }\n return true;\n } catch {\n // Store failure: assume available.\n return true;\n }\n }\n\n private async isBenched(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.benchKey(modelKey));\n if (raw === null) return false;\n // Belt and braces for stores without real TTL support: the value is\n // the bench expiry timestamp. Unparseable values fail open (not\n // benched), consistent with the store-failure policy in isAvailable.\n const expiresAt = Number(raw);\n return Number.isFinite(expiresAt) && Date.now() < expiresAt;\n }\n\n private async isNearHeaderLimit(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.headersKey(modelKey));\n if (raw === null) return false;\n const snapshot = JSON.parse(raw) as HeaderSnapshot;\n const now = Date.now();\n // Each dimension keeps its own expiration so a fast-resetting,\n // non-limiting dimension can't wipe out a still-exhausted one.\n for (const dim of [snapshot.requests, snapshot.tokens]) {\n if (dim === undefined || now >= dim.expiresAt) continue;\n if (this.remainingBelowThreshold(dim.remaining, dim.limit)) return true;\n }\n return false;\n }\n\n private remainingBelowThreshold(\n remaining: number | undefined,\n limit: number | undefined,\n ): boolean {\n if (remaining === undefined) return false;\n if (remaining <= 0) return true;\n if (limit === undefined || limit <= 0) return false;\n return remaining / limit <= this.threshold;\n }\n\n private async isNearDeclaredLimit(\n modelKey: string,\n limits: Limits,\n ): Promise<boolean> {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n if (limits.requestsPerMinute !== undefined) {\n const used = usage.requests.filter((t) => now - t < MINUTE_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerMinute) return true;\n }\n if (limits.requestsPerDay !== undefined) {\n const used = usage.requests.filter((t) => now - t < DAY_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerDay) return true;\n }\n if (limits.tokensPerMinute !== undefined) {\n const used = usage.tokens\n .filter(([t]) => now - t < MINUTE_MS)\n .reduce((sum, [, n]) => sum + n, 0);\n if (used >= (1 - this.threshold) * limits.tokensPerMinute) return true;\n }\n return false;\n }\n\n /**\n * Record a successful call: parse provider rate-limit headers and\n * increment sliding-window counters for declared limits.\n */\n async recordSuccess(\n modelKey: string,\n input: RecordSuccessInput,\n ): Promise<void> {\n try {\n if (input.headers !== undefined) {\n await this.recordHeaders(modelKey, input.provider, input.headers);\n }\n if (input.limits !== undefined) {\n await this.recordUsage(modelKey, input.limits, input.totalTokens);\n }\n } catch {\n // Store failure: skip recording.\n }\n }\n\n private async recordHeaders(\n modelKey: string,\n provider: string,\n headers: Record<string, string>,\n ): Promise<void> {\n const parsed = parseRateLimitHeaders(provider, headers);\n const now = Date.now();\n const snapshot: HeaderSnapshot = {};\n if (parsed.requestsRemaining !== undefined) {\n snapshot.requests = {\n remaining: parsed.requestsRemaining,\n limit: parsed.requestsLimit,\n expiresAt:\n now +\n Math.min(\n parsed.requestsResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n if (parsed.tokensRemaining !== undefined) {\n snapshot.tokens = {\n remaining: parsed.tokensRemaining,\n limit: parsed.tokensLimit,\n expiresAt:\n now +\n Math.min(\n parsed.tokensResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n // Keep the snapshot around until the *slowest* dimension resets;\n // each dimension expires individually in isNearHeaderLimit.\n const ttl = Math.max(\n (snapshot.requests?.expiresAt ?? 0) - now,\n (snapshot.tokens?.expiresAt ?? 0) - now,\n );\n if (ttl <= 0) return;\n await this.store.set(\n this.headersKey(modelKey),\n JSON.stringify(snapshot),\n ttl,\n );\n }\n\n private recordUsage(\n modelKey: string,\n limits: Limits,\n totalTokens: number | undefined,\n ): Promise<void> {\n // Serialize read-modify-write per key so concurrent calls in this\n // process don't drop each other's counts. Distributed setups sharing\n // a store across processes should prefer a store with atomic\n // operations; self-counting is best-effort there.\n return this.serializeUsageUpdate(modelKey, async () => {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n const trackDay = limits.requestsPerDay !== undefined ? DAY_MS : MINUTE_MS;\n usage.requests = usage.requests.filter((t) => now - t < trackDay);\n usage.requests.push(now);\n\n usage.tokens = usage.tokens.filter(([t]) => now - t < MINUTE_MS);\n if (limits.tokensPerMinute !== undefined && totalTokens !== undefined) {\n usage.tokens.push([now, totalTokens]);\n }\n\n await this.store.set(\n this.usageKey(modelKey),\n JSON.stringify(usage),\n trackDay,\n );\n });\n }\n\n private serializeUsageUpdate(\n modelKey: string,\n fn: () => Promise<void>,\n ): Promise<void> {\n const prev = this.usageLocks.get(modelKey) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n this.usageLocks.set(modelKey, tail);\n void tail.then(() => {\n if (this.usageLocks.get(modelKey) === tail) {\n this.usageLocks.delete(modelKey);\n }\n });\n return next;\n }\n\n private async readUsage(modelKey: string): Promise<UsageState> {\n const raw = await this.store.get(this.usageKey(modelKey));\n if (raw === null) return { requests: [], tokens: [] };\n const parsed = JSON.parse(raw) as Partial<UsageState>;\n return {\n requests: Array.isArray(parsed.requests) ? parsed.requests : [],\n tokens: Array.isArray(parsed.tokens) ? parsed.tokens : [],\n };\n }\n\n /**\n * Bench a model after a rate-limit error, for `retryAfterMs` when the\n * provider sent one, otherwise for the configured cooldown.\n */\n async recordRateLimit(\n modelKey: string,\n retryAfterMs?: number,\n ): Promise<void> {\n const benchMs = retryAfterMs ?? this.cooldown;\n if (benchMs <= 0) return;\n try {\n await this.store.set(\n this.benchKey(modelKey),\n String(Date.now() + benchMs),\n benchMs,\n );\n } catch {\n // Store failure: skip recording.\n }\n }\n}\n","import type { ErrorClassification } from './types';\n\ninterface ErrorShape {\n statusCode?: number | undefined;\n responseHeaders?: Record<string, string> | undefined;\n isRetryable?: boolean | undefined;\n message?: string | undefined;\n}\n\n/**\n * Extract the shape of an AI SDK `APICallError` (statusCode,\n * responseHeaders, isRetryable) via duck typing, so we don't need a\n * runtime dependency on `@ai-sdk/provider`.\n */\nfunction getErrorShape(error: unknown): ErrorShape {\n if (typeof error !== 'object' || error === null) {\n return { message: String(error) };\n }\n const e = error as Record<string, unknown>;\n return {\n statusCode:\n typeof e['statusCode'] === 'number' ? e['statusCode'] : undefined,\n responseHeaders:\n typeof e['responseHeaders'] === 'object' && e['responseHeaders'] !== null\n ? (e['responseHeaders'] as Record<string, string>)\n : undefined,\n isRetryable:\n typeof e['isRetryable'] === 'boolean' ? e['isRetryable'] : undefined,\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n };\n}\n\nconst RATE_LIMIT_PATTERN =\n /rate.?limit|too many requests|quota|exhausted|billing|insufficient.?quota/i;\n\nconst TRANSIENT_PATTERN =\n /overloaded|timeout|timed.?out|econnreset|econnrefused|etimedout|socket hang up|network|fetch failed|service unavailable|internal server error|bad gateway/i;\n\nfunction getHeader(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Classify an error from a model call:\n *\n * - `rate-limit`: HTTP 429, quota/exhausted message patterns, or a\n * `retry-after` header — fall back and bench the model.\n * - `transient`: HTTP 5xx, overloaded/network/timeout errors — fall back\n * without benching.\n * - `fatal`: 400/401/403 and everything else — throw immediately.\n */\nexport function classifyError(error: unknown): ErrorClassification {\n const { statusCode, responseHeaders, isRetryable, message } =\n getErrorShape(error);\n const msg = message ?? '';\n\n if (statusCode === 429) return 'rate-limit';\n if (statusCode === 400 || statusCode === 401 || statusCode === 403) {\n return 'fatal';\n }\n if (statusCode !== undefined && statusCode >= 500) return 'transient';\n if (getHeader(responseHeaders, 'retry-after') !== undefined) {\n return 'rate-limit';\n }\n if (RATE_LIMIT_PATTERN.test(msg)) return 'rate-limit';\n if (isRetryable === true) return 'transient';\n if (TRANSIENT_PATTERN.test(msg)) return 'transient';\n return 'fatal';\n}\n\n/**\n * Parse a `retry-after` header from an error's response headers into\n * milliseconds. Supports delta-seconds and HTTP-date formats.\n */\nexport function getRetryAfterMs(error: unknown): number | undefined {\n const { responseHeaders } = getErrorShape(error);\n const raw = getHeader(responseHeaders, 'retry-after');\n if (raw === undefined) return undefined;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n return Math.max(0, date - Date.now());\n }\n return undefined;\n}\n","import type { ErrorClassification } from './types';\n\n/** One failed attempt against a model in the fallback chain. */\nexport interface ModelAttempt {\n modelId: string;\n error: unknown;\n classification: ErrorClassification;\n}\n\n/**\n * Thrown when every model in the fallback chain failed with a\n * rate-limit or transient error.\n */\nexport class AllModelsExhaustedError extends Error {\n override readonly name = 'AllModelsExhaustedError';\n readonly attempts: ModelAttempt[];\n\n constructor(attempts: ModelAttempt[]) {\n const summary = attempts\n .map((a) => `${a.modelId} (${a.classification})`)\n .join(', ');\n super(\n `All models exhausted after ${attempts.length} attempt(s): ${summary}`,\n );\n this.attempts = attempts;\n }\n\n static isInstance(error: unknown): error is AllModelsExhaustedError {\n return error instanceof Error && error.name === 'AllModelsExhaustedError';\n }\n}\n","import type {\n LanguageModelV2,\n LanguageModelV2CallOptions,\n LanguageModelV2StreamPart,\n} from '@ai-sdk/provider';\nimport { classifyError, getRetryAfterMs } from './classify-error';\nimport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nimport { LimitTracker } from './limit-tracker';\nimport type {\n AnyLanguageModel,\n FallbackReason,\n ModelConfig,\n ResilientOptions,\n SpecificationVersion,\n} from './types';\n\ntype DoGenerateResult = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;\ntype DoStreamResult = Awaited<ReturnType<LanguageModelV2['doStream']>>;\n\ninterface Candidate {\n config: ModelConfig;\n /** Unique store key: `provider:modelId` (+ index suffix on collision). */\n key: string;\n}\n\ninterface PlanEntry {\n candidate: Candidate;\n /** True when the model was proactively filtered by the LimitTracker. */\n skip: boolean;\n}\n\ninterface PendingFallback {\n from: string;\n reason: FallbackReason;\n}\n\nconst PRELUDE_PART_TYPES = new Set(['stream-start', 'response-metadata']);\n\n/**\n * Extract a token count from a usage field that may be a v2 flat number\n * or a v3 nested `{ total: number }` object.\n */\nfunction tokenCount(value: unknown): number | undefined {\n if (typeof value === 'number') return value;\n if (typeof value === 'object' && value !== null) {\n const total = (value as { total?: unknown }).total;\n if (typeof total === 'number') return total;\n }\n return undefined;\n}\n\n/**\n * Total token usage from either a `LanguageModelV2Usage` (flat numbers,\n * top-level `totalTokens`) or a `LanguageModelV3Usage` (nested\n * `inputTokens.total` / `outputTokens.total`, no top-level total).\n */\nfunction extractTotalTokens(usage: unknown): number | undefined {\n if (typeof usage !== 'object' || usage === null) return undefined;\n const u = usage as Record<string, unknown>;\n const total = tokenCount(u.totalTokens);\n if (total !== undefined) return total;\n const input = tokenCount(u.inputTokens);\n const output = tokenCount(u.outputTokens);\n if (input === undefined && output === undefined) return undefined;\n return (input ?? 0) + (output ?? 0);\n}\n\n/**\n * A resilient language model that transparently falls back across a\n * chain of models on rate-limit and transient errors, and proactively\n * skips models that are near a known rate limit. Mirrors the spec\n * version (v2 or v3) of the models it wraps.\n */\nexport class ResilientLanguageModel {\n readonly provider = 'ai-resilient';\n\n private readonly candidates: Candidate[];\n private readonly tracker: LimitTracker;\n private readonly onFallback: ResilientOptions['onFallback'];\n private readonly onError: ResilientOptions['onError'];\n\n /**\n * Bookkeeping writes chained off the response path. buildPlan awaits\n * this so the next call still sees all prior usage before deciding\n * availability (read-your-writes), without success responses paying\n * store-write latency.\n */\n private pendingRecords: Promise<void> = Promise.resolve();\n\n constructor(options: {\n models: ModelConfig[];\n tracker: LimitTracker;\n onFallback?: ResilientOptions['onFallback'];\n onError?: ResilientOptions['onError'];\n }) {\n if (options.models.length === 0) {\n throw new Error('ai-resilient: at least one model is required');\n }\n const versions = new Set(\n options.models.map((config) => config.model.specificationVersion),\n );\n if (versions.size > 1) {\n // The outer specificationVersion drives how the AI SDK adapts call\n // options and results, so all wrapped models must speak the same\n // spec — a mixed chain would corrupt the fallback path.\n throw new Error(\n `ai-resilient: all models must implement the same specification version; got ${[...versions].sort().join(', ')}`,\n );\n }\n const seen = new Map<string, number>();\n this.candidates = options.models.map((config) => {\n const base = `${config.model.provider}:${config.model.modelId}`;\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n return { config, key: count === 0 ? base : `${base}#${count}` };\n });\n this.tracker = options.tracker;\n this.onFallback = options.onFallback;\n this.onError = options.onError;\n }\n\n /**\n * Reported dynamically from the wrapped models: hardcoding 'v2' would\n * make ai v6 wrap this model in its v2→v3 compat adapter and\n * double-convert the already-v3 results passed through unchanged.\n */\n get specificationVersion(): SpecificationVersion {\n return this.primary.specificationVersion;\n }\n\n get modelId(): string {\n return this.primary.modelId;\n }\n\n get supportedUrls(): AnyLanguageModel['supportedUrls'] {\n return this.primary.supportedUrls;\n }\n\n private get primary(): AnyLanguageModel {\n // candidates is non-empty by construction\n return this.candidates[0]!.config.model;\n }\n\n private queueRecordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: unknown,\n ): void {\n this.pendingRecords = this.pendingRecords\n .then(() => this.recordSuccess(candidate, headers, usage))\n .catch(() => {\n // Store bookkeeping failures must never surface to callers.\n });\n }\n\n /**\n * Order candidates and mark near-limit/benched ones as skipped. If\n * every model would be skipped, try all of them in configured order\n * anyway — attempting beats failing without trying.\n */\n private async buildPlan(): Promise<PlanEntry[]> {\n await this.pendingRecords;\n const plan: PlanEntry[] = [];\n for (const candidate of this.candidates) {\n const available = await this.tracker.isAvailable(\n candidate.key,\n candidate.config.limits,\n );\n plan.push({ candidate, skip: !available });\n }\n if (plan.every((entry) => entry.skip)) {\n for (const entry of plan) entry.skip = false;\n }\n return plan;\n }\n\n private fireFallbacks(pending: PendingFallback[], to: string): void {\n for (const { from, reason } of pending) {\n this.onFallback?.({ from, to, reason });\n }\n pending.length = 0;\n }\n\n /**\n * Handle a failed attempt: notify, classify, bench on rate-limit, and\n * queue the fallback notification. Rethrows fatal errors.\n */\n private async handleFailure(\n error: unknown,\n candidate: Candidate,\n attempts: ModelAttempt[],\n pending: PendingFallback[],\n ): Promise<void> {\n const modelId = candidate.config.model.modelId;\n this.onError?.(error, modelId);\n const classification = classifyError(error);\n if (classification === 'fatal') throw error;\n attempts.push({ modelId, error, classification });\n if (classification === 'rate-limit') {\n await this.tracker.recordRateLimit(candidate.key, getRetryAfterMs(error));\n }\n pending.push({ from: modelId, reason: classification });\n }\n\n private async recordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: unknown,\n ): Promise<void> {\n const totalTokens = extractTotalTokens(usage);\n await this.tracker.recordSuccess(candidate.key, {\n provider: candidate.config.model.provider,\n headers,\n totalTokens,\n limits: candidate.config.limits,\n });\n }\n\n async doGenerate(\n options: LanguageModelV2CallOptions,\n ): Promise<DoGenerateResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n\n for (const entry of plan) {\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = (await candidate.config.model.doGenerate(\n options,\n )) as DoGenerateResult;\n this.queueRecordSuccess(\n candidate,\n result.response?.headers,\n result.usage,\n );\n return result;\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n }\n\n async doStream(options: LanguageModelV2CallOptions): Promise<DoStreamResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n let index = 0;\n\n const acquire = async (): Promise<{\n candidate: Candidate;\n result: DoStreamResult;\n }> => {\n while (index < plan.length) {\n const entry = plan[index++]!;\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = (await candidate.config.model.doStream(\n options,\n )) as DoStreamResult;\n return { candidate, result };\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n };\n\n // Try candidates until one produces content (or finishes cleanly)\n // before returning, so the returned request/response metadata always\n // belongs to the model that actually serves the stream.\n for (;;) {\n const { candidate, result } = await acquire();\n const reader = result.stream.getReader();\n const prelude: LanguageModelV2StreamPart[] = [];\n let firstContent: LanguageModelV2StreamPart | undefined;\n let usage: unknown;\n let done = false;\n\n try {\n for (;;) {\n const { done: readDone, value } = await reader.read();\n if (readDone) {\n done = true;\n break;\n }\n if (value.type === 'error') {\n // Pre-content stream error: treat like a failed call so we\n // can fall back to the next model.\n throw value.error;\n }\n if (value.type === 'finish') usage = value.usage;\n if (PRELUDE_PART_TYPES.has(value.type)) {\n prelude.push(value);\n continue;\n }\n firstContent = value;\n break;\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n // Rethrows fatal errors; otherwise fall through to next candidate.\n await this.handleFailure(error, candidate, attempts, pending);\n continue;\n }\n\n // Committed: first content part arrived (or the stream finished).\n // Errors from here on propagate to the consumer unchanged.\n let preludeIndex = 0;\n const stream = new ReadableStream<LanguageModelV2StreamPart>({\n pull: async (controller) => {\n if (preludeIndex < prelude.length) {\n controller.enqueue(prelude[preludeIndex++]);\n return;\n }\n if (firstContent !== undefined) {\n const part = firstContent;\n firstContent = undefined;\n controller.enqueue(part);\n return;\n }\n if (!done) {\n const { done: readDone, value } = await reader.read();\n if (!readDone) {\n if (value.type === 'finish') usage = value.usage;\n controller.enqueue(value);\n return;\n }\n done = true;\n }\n this.queueRecordSuccess(candidate, result.response?.headers, usage);\n controller.close();\n },\n cancel: (reason) => reader.cancel(reason),\n });\n\n return {\n stream,\n ...(result.request !== undefined ? { request: result.request } : {}),\n ...(result.response !== undefined ? { response: result.response } : {}),\n };\n }\n }\n}\n","import type { Store } from '../types';\n\ninterface Entry {\n value: string;\n expiresAt?: number;\n}\n\n/**\n * In-process `Store` backed by a `Map` with lazy TTL eviction.\n * Suitable for long-running servers; use a Redis/KV adapter for\n * serverless deployments.\n */\nexport function memoryStore(): Store {\n const map = new Map<string, Entry>();\n\n function evictIfExpired(key: string): Entry | undefined {\n const entry = map.get(key);\n if (entry === undefined) return undefined;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n return {\n get(key) {\n const entry = evictIfExpired(key);\n return Promise.resolve(entry?.value ?? null);\n },\n set(key, value, ttlMs) {\n map.set(key, {\n value,\n ...(ttlMs !== undefined ? { expiresAt: Date.now() + ttlMs } : {}),\n });\n return Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { LanguageModelV2 } from '@ai-sdk/provider';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* User-declared rate limits for a model, used for self-counted tracking
|
|
5
3
|
* when the provider exposes no rate-limit headers.
|
|
@@ -9,9 +7,31 @@ interface Limits {
|
|
|
9
7
|
requestsPerDay?: number;
|
|
10
8
|
tokensPerMinute?: number;
|
|
11
9
|
}
|
|
10
|
+
/** Language model specification versions the wrapper supports. */
|
|
11
|
+
type SpecificationVersion = 'v2' | 'v3';
|
|
12
|
+
/**
|
|
13
|
+
* Structural stand-in for a language model of either specification
|
|
14
|
+
* version (`LanguageModelV2` from ai v5 / provider v2, `LanguageModelV3`
|
|
15
|
+
* from ai v6 / provider v3).
|
|
16
|
+
*
|
|
17
|
+
* `LanguageModelV3` is deliberately not imported from `@ai-sdk/provider`:
|
|
18
|
+
* the type only exists in provider v3, so importing it would break
|
|
19
|
+
* type-checking for consumers still on provider v2. TypeScript's
|
|
20
|
+
* structural typing makes real models of both versions assignable to
|
|
21
|
+
* this shape, and values of this shape assignable to the concrete
|
|
22
|
+
* `LanguageModelV2` / `LanguageModelV3` interfaces.
|
|
23
|
+
*/
|
|
24
|
+
interface AnyLanguageModel<Version extends SpecificationVersion = SpecificationVersion> {
|
|
25
|
+
readonly specificationVersion: Version;
|
|
26
|
+
readonly provider: string;
|
|
27
|
+
readonly modelId: string;
|
|
28
|
+
readonly supportedUrls: PromiseLike<Record<string, RegExp[]>> | Record<string, RegExp[]>;
|
|
29
|
+
doGenerate(options: any): PromiseLike<any>;
|
|
30
|
+
doStream(options: any): PromiseLike<any>;
|
|
31
|
+
}
|
|
12
32
|
/** Configuration for a single model in the fallback chain. */
|
|
13
|
-
interface ModelConfig {
|
|
14
|
-
model:
|
|
33
|
+
interface ModelConfig<Version extends SpecificationVersion = SpecificationVersion> {
|
|
34
|
+
model: AnyLanguageModel<Version>;
|
|
15
35
|
limits?: Limits;
|
|
16
36
|
}
|
|
17
37
|
/** Result of classifying an error. */
|
|
@@ -38,9 +58,13 @@ interface Store {
|
|
|
38
58
|
set(key: string, value: string, ttlMs?: number): Promise<void>;
|
|
39
59
|
}
|
|
40
60
|
/** Options for {@link createResilient}. */
|
|
41
|
-
interface ResilientOptions {
|
|
42
|
-
/**
|
|
43
|
-
|
|
61
|
+
interface ResilientOptions<Version extends SpecificationVersion = SpecificationVersion> {
|
|
62
|
+
/**
|
|
63
|
+
* Models to try, in priority order. At least one is required. All
|
|
64
|
+
* models must implement the same specification version (mixing ai v5
|
|
65
|
+
* and ai v6 models is rejected at construction).
|
|
66
|
+
*/
|
|
67
|
+
models: ModelConfig<Version>[];
|
|
44
68
|
/** State store. Defaults to an in-process `memoryStore()`. */
|
|
45
69
|
store?: Store;
|
|
46
70
|
/**
|
|
@@ -179,9 +203,12 @@ declare function parseRateLimitHeaders(provider: string, headers: Headers | unde
|
|
|
179
203
|
* the configured models on rate-limit/transient errors and proactively
|
|
180
204
|
* switches away from models near a known rate limit.
|
|
181
205
|
*
|
|
182
|
-
* The returned
|
|
183
|
-
*
|
|
206
|
+
* The returned model mirrors the specification version of the models it
|
|
207
|
+
* wraps — `LanguageModelV2` for ai v5 models, `LanguageModelV3` for
|
|
208
|
+
* ai v6 models — so it works with `generateText`, `streamText`,
|
|
209
|
+
* `generateObject`, and `streamObject` on either SDK major. Mixing spec
|
|
210
|
+
* versions in one chain throws.
|
|
184
211
|
*/
|
|
185
|
-
declare function createResilient(options: ResilientOptions):
|
|
212
|
+
declare function createResilient<Version extends SpecificationVersion>(options: ResilientOptions<Version>): AnyLanguageModel<Version>;
|
|
186
213
|
|
|
187
|
-
export { AllModelsExhaustedError, type ErrorClassification, type FallbackInfo, type FallbackReason, LimitTracker, type Limits, type ModelAttempt, type ModelConfig, type ParsedRateLimit, type ResilientOptions, type Store, classifyError, createResilient, getRetryAfterMs, memoryStore, parseRateLimitHeaders };
|
|
214
|
+
export { AllModelsExhaustedError, type AnyLanguageModel, type ErrorClassification, type FallbackInfo, type FallbackReason, LimitTracker, type Limits, type ModelAttempt, type ModelConfig, type ParsedRateLimit, type ResilientOptions, type SpecificationVersion, type Store, classifyError, createResilient, getRetryAfterMs, memoryStore, parseRateLimitHeaders };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { LanguageModelV2 } from '@ai-sdk/provider';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* User-declared rate limits for a model, used for self-counted tracking
|
|
5
3
|
* when the provider exposes no rate-limit headers.
|
|
@@ -9,9 +7,31 @@ interface Limits {
|
|
|
9
7
|
requestsPerDay?: number;
|
|
10
8
|
tokensPerMinute?: number;
|
|
11
9
|
}
|
|
10
|
+
/** Language model specification versions the wrapper supports. */
|
|
11
|
+
type SpecificationVersion = 'v2' | 'v3';
|
|
12
|
+
/**
|
|
13
|
+
* Structural stand-in for a language model of either specification
|
|
14
|
+
* version (`LanguageModelV2` from ai v5 / provider v2, `LanguageModelV3`
|
|
15
|
+
* from ai v6 / provider v3).
|
|
16
|
+
*
|
|
17
|
+
* `LanguageModelV3` is deliberately not imported from `@ai-sdk/provider`:
|
|
18
|
+
* the type only exists in provider v3, so importing it would break
|
|
19
|
+
* type-checking for consumers still on provider v2. TypeScript's
|
|
20
|
+
* structural typing makes real models of both versions assignable to
|
|
21
|
+
* this shape, and values of this shape assignable to the concrete
|
|
22
|
+
* `LanguageModelV2` / `LanguageModelV3` interfaces.
|
|
23
|
+
*/
|
|
24
|
+
interface AnyLanguageModel<Version extends SpecificationVersion = SpecificationVersion> {
|
|
25
|
+
readonly specificationVersion: Version;
|
|
26
|
+
readonly provider: string;
|
|
27
|
+
readonly modelId: string;
|
|
28
|
+
readonly supportedUrls: PromiseLike<Record<string, RegExp[]>> | Record<string, RegExp[]>;
|
|
29
|
+
doGenerate(options: any): PromiseLike<any>;
|
|
30
|
+
doStream(options: any): PromiseLike<any>;
|
|
31
|
+
}
|
|
12
32
|
/** Configuration for a single model in the fallback chain. */
|
|
13
|
-
interface ModelConfig {
|
|
14
|
-
model:
|
|
33
|
+
interface ModelConfig<Version extends SpecificationVersion = SpecificationVersion> {
|
|
34
|
+
model: AnyLanguageModel<Version>;
|
|
15
35
|
limits?: Limits;
|
|
16
36
|
}
|
|
17
37
|
/** Result of classifying an error. */
|
|
@@ -38,9 +58,13 @@ interface Store {
|
|
|
38
58
|
set(key: string, value: string, ttlMs?: number): Promise<void>;
|
|
39
59
|
}
|
|
40
60
|
/** Options for {@link createResilient}. */
|
|
41
|
-
interface ResilientOptions {
|
|
42
|
-
/**
|
|
43
|
-
|
|
61
|
+
interface ResilientOptions<Version extends SpecificationVersion = SpecificationVersion> {
|
|
62
|
+
/**
|
|
63
|
+
* Models to try, in priority order. At least one is required. All
|
|
64
|
+
* models must implement the same specification version (mixing ai v5
|
|
65
|
+
* and ai v6 models is rejected at construction).
|
|
66
|
+
*/
|
|
67
|
+
models: ModelConfig<Version>[];
|
|
44
68
|
/** State store. Defaults to an in-process `memoryStore()`. */
|
|
45
69
|
store?: Store;
|
|
46
70
|
/**
|
|
@@ -179,9 +203,12 @@ declare function parseRateLimitHeaders(provider: string, headers: Headers | unde
|
|
|
179
203
|
* the configured models on rate-limit/transient errors and proactively
|
|
180
204
|
* switches away from models near a known rate limit.
|
|
181
205
|
*
|
|
182
|
-
* The returned
|
|
183
|
-
*
|
|
206
|
+
* The returned model mirrors the specification version of the models it
|
|
207
|
+
* wraps — `LanguageModelV2` for ai v5 models, `LanguageModelV3` for
|
|
208
|
+
* ai v6 models — so it works with `generateText`, `streamText`,
|
|
209
|
+
* `generateObject`, and `streamObject` on either SDK major. Mixing spec
|
|
210
|
+
* versions in one chain throws.
|
|
184
211
|
*/
|
|
185
|
-
declare function createResilient(options: ResilientOptions):
|
|
212
|
+
declare function createResilient<Version extends SpecificationVersion>(options: ResilientOptions<Version>): AnyLanguageModel<Version>;
|
|
186
213
|
|
|
187
|
-
export { AllModelsExhaustedError, type ErrorClassification, type FallbackInfo, type FallbackReason, LimitTracker, type Limits, type ModelAttempt, type ModelConfig, type ParsedRateLimit, type ResilientOptions, type Store, classifyError, createResilient, getRetryAfterMs, memoryStore, parseRateLimitHeaders };
|
|
214
|
+
export { AllModelsExhaustedError, type AnyLanguageModel, type ErrorClassification, type FallbackInfo, type FallbackReason, LimitTracker, type Limits, type ModelAttempt, type ModelConfig, type ParsedRateLimit, type ResilientOptions, type SpecificationVersion, type Store, classifyError, createResilient, getRetryAfterMs, memoryStore, parseRateLimitHeaders };
|
package/dist/index.js
CHANGED
|
@@ -348,8 +348,25 @@ var AllModelsExhaustedError = class extends Error {
|
|
|
348
348
|
|
|
349
349
|
// src/resilient-model.ts
|
|
350
350
|
var PRELUDE_PART_TYPES = /* @__PURE__ */ new Set(["stream-start", "response-metadata"]);
|
|
351
|
+
function tokenCount(value) {
|
|
352
|
+
if (typeof value === "number") return value;
|
|
353
|
+
if (typeof value === "object" && value !== null) {
|
|
354
|
+
const total = value.total;
|
|
355
|
+
if (typeof total === "number") return total;
|
|
356
|
+
}
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
function extractTotalTokens(usage) {
|
|
360
|
+
if (typeof usage !== "object" || usage === null) return void 0;
|
|
361
|
+
const u = usage;
|
|
362
|
+
const total = tokenCount(u.totalTokens);
|
|
363
|
+
if (total !== void 0) return total;
|
|
364
|
+
const input = tokenCount(u.inputTokens);
|
|
365
|
+
const output = tokenCount(u.outputTokens);
|
|
366
|
+
if (input === void 0 && output === void 0) return void 0;
|
|
367
|
+
return (input ?? 0) + (output ?? 0);
|
|
368
|
+
}
|
|
351
369
|
var ResilientLanguageModel = class {
|
|
352
|
-
specificationVersion = "v2";
|
|
353
370
|
provider = "ai-resilient";
|
|
354
371
|
candidates;
|
|
355
372
|
tracker;
|
|
@@ -366,6 +383,14 @@ var ResilientLanguageModel = class {
|
|
|
366
383
|
if (options.models.length === 0) {
|
|
367
384
|
throw new Error("ai-resilient: at least one model is required");
|
|
368
385
|
}
|
|
386
|
+
const versions = new Set(
|
|
387
|
+
options.models.map((config) => config.model.specificationVersion)
|
|
388
|
+
);
|
|
389
|
+
if (versions.size > 1) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`ai-resilient: all models must implement the same specification version; got ${[...versions].sort().join(", ")}`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
369
394
|
const seen = /* @__PURE__ */ new Map();
|
|
370
395
|
this.candidates = options.models.map((config) => {
|
|
371
396
|
const base = `${config.model.provider}:${config.model.modelId}`;
|
|
@@ -377,6 +402,14 @@ var ResilientLanguageModel = class {
|
|
|
377
402
|
this.onFallback = options.onFallback;
|
|
378
403
|
this.onError = options.onError;
|
|
379
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Reported dynamically from the wrapped models: hardcoding 'v2' would
|
|
407
|
+
* make ai v6 wrap this model in its v2→v3 compat adapter and
|
|
408
|
+
* double-convert the already-v3 results passed through unchanged.
|
|
409
|
+
*/
|
|
410
|
+
get specificationVersion() {
|
|
411
|
+
return this.primary.specificationVersion;
|
|
412
|
+
}
|
|
380
413
|
get modelId() {
|
|
381
414
|
return this.primary.modelId;
|
|
382
415
|
}
|
|
@@ -432,7 +465,7 @@ var ResilientLanguageModel = class {
|
|
|
432
465
|
pending.push({ from: modelId, reason: classification });
|
|
433
466
|
}
|
|
434
467
|
async recordSuccess(candidate, headers, usage) {
|
|
435
|
-
const totalTokens =
|
|
468
|
+
const totalTokens = extractTotalTokens(usage);
|
|
436
469
|
await this.tracker.recordSuccess(candidate.key, {
|
|
437
470
|
provider: candidate.config.model.provider,
|
|
438
471
|
headers,
|
|
@@ -453,7 +486,9 @@ var ResilientLanguageModel = class {
|
|
|
453
486
|
}
|
|
454
487
|
this.fireFallbacks(pending, modelId);
|
|
455
488
|
try {
|
|
456
|
-
const result = await candidate.config.model.doGenerate(
|
|
489
|
+
const result = await candidate.config.model.doGenerate(
|
|
490
|
+
options
|
|
491
|
+
);
|
|
457
492
|
this.queueRecordSuccess(
|
|
458
493
|
candidate,
|
|
459
494
|
result.response?.headers,
|
|
@@ -482,7 +517,9 @@ var ResilientLanguageModel = class {
|
|
|
482
517
|
}
|
|
483
518
|
this.fireFallbacks(pending, modelId);
|
|
484
519
|
try {
|
|
485
|
-
const result = await candidate.config.model.doStream(
|
|
520
|
+
const result = await candidate.config.model.doStream(
|
|
521
|
+
options
|
|
522
|
+
);
|
|
486
523
|
return { candidate, result };
|
|
487
524
|
} catch (error) {
|
|
488
525
|
await this.handleFailure(error, candidate, attempts, pending);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/header-parsers.ts","../src/limit-tracker.ts","../src/classify-error.ts","../src/errors.ts","../src/resilient-model.ts","../src/stores/memory.ts","../src/index.ts"],"sourcesContent":["/**\n * Normalized rate-limit state parsed from provider response headers.\n */\nexport interface ParsedRateLimit {\n requestsRemaining?: number | undefined;\n requestsLimit?: number | undefined;\n /** Milliseconds until the request limit resets. */\n requestsResetMs?: number | undefined;\n tokensRemaining?: number | undefined;\n tokensLimit?: number | undefined;\n /** Milliseconds until the token limit resets. */\n tokensResetMs?: number | undefined;\n}\n\ntype Headers = Record<string, string>;\n\nfunction lower(headers: Headers): Headers {\n const out: Headers = {};\n for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;\n return out;\n}\n\nfunction num(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * Parse duration strings used in OpenAI/Groq reset headers, e.g.\n * `\"1s\"`, `\"7.66s\"`, `\"2m59.56s\"`, `\"6m0s\"`, `\"1h2m3s\"`, `\"123ms\"`,\n * or a plain number of seconds.\n */\nexport function parseDuration(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const plain = Number(value);\n if (Number.isFinite(plain)) return plain * 1000;\n\n const pattern = /(\\d+(?:\\.\\d+)?)(ms|h|m|s)/g;\n let ms = 0;\n let matched = false;\n for (const [, amount, unit] of value.matchAll(pattern)) {\n matched = true;\n const n = Number(amount);\n if (unit === 'h') ms += n * 3_600_000;\n else if (unit === 'm') ms += n * 60_000;\n else if (unit === 's') ms += n * 1000;\n else ms += n;\n }\n return matched ? Math.round(ms) : undefined;\n}\n\n/** Parse an RFC 3339 timestamp into ms-from-now. */\nfunction parseResetTimestamp(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const t = Date.parse(value);\n if (Number.isNaN(t)) return undefined;\n return Math.max(0, t - Date.now());\n}\n\n/** OpenAI and Groq use `x-ratelimit-{limit,remaining,reset}-{requests,tokens}`. */\nfunction parseOpenAIStyle(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['x-ratelimit-remaining-requests']),\n requestsLimit: num(h['x-ratelimit-limit-requests']),\n requestsResetMs: parseDuration(h['x-ratelimit-reset-requests']),\n tokensRemaining: num(h['x-ratelimit-remaining-tokens']),\n tokensLimit: num(h['x-ratelimit-limit-tokens']),\n tokensResetMs: parseDuration(h['x-ratelimit-reset-tokens']),\n };\n}\n\n/** Anthropic uses `anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset}`. */\nfunction parseAnthropic(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['anthropic-ratelimit-requests-remaining']),\n requestsLimit: num(h['anthropic-ratelimit-requests-limit']),\n requestsResetMs: parseResetTimestamp(\n h['anthropic-ratelimit-requests-reset'],\n ),\n tokensRemaining: num(h['anthropic-ratelimit-tokens-remaining']),\n tokensLimit: num(h['anthropic-ratelimit-tokens-limit']),\n tokensResetMs: parseResetTimestamp(h['anthropic-ratelimit-tokens-reset']),\n };\n}\n\n/**\n * `ratelimit-reset` is delta-seconds per the IETF draft, but some\n * implementations send a Unix epoch timestamp. Values that would put the\n * reset more than a year out are interpreted as epoch seconds.\n */\nfunction parseIetfReset(value: string | undefined): number | undefined {\n const reset = num(value);\n if (reset === undefined) return undefined;\n const YEAR_S = 31_536_000;\n if (reset > YEAR_S) {\n // Epoch seconds → delta from now, clamped to >= 0.\n return Math.max(0, reset * 1000 - Date.now());\n }\n return reset * 1000;\n}\n\n/**\n * Best-effort parser for providers without a documented scheme\n * (Google, Mistral): tries OpenAI-style headers, then the IETF\n * draft `ratelimit-*` headers.\n */\nfunction parseGeneric(h: Headers): ParsedRateLimit {\n const openai = parseOpenAIStyle(h);\n if (Object.values(openai).some((v) => v !== undefined)) return openai;\n const reset = parseIetfReset(h['ratelimit-reset']);\n return {\n requestsRemaining: num(h['ratelimit-remaining']),\n requestsLimit: num(h['ratelimit-limit']),\n ...(reset !== undefined ? { requestsResetMs: reset } : {}),\n };\n}\n\n/**\n * Parse provider rate-limit headers into a normalized shape.\n * The provider is matched by prefix of the model's `provider` string\n * (e.g. `\"openai.chat\"` → OpenAI). Unknown providers get the\n * best-effort generic parser.\n */\nexport function parseRateLimitHeaders(\n provider: string,\n headers: Headers | undefined,\n): ParsedRateLimit {\n if (headers === undefined) return {};\n const h = lower(headers);\n const p = provider.toLowerCase();\n\n if (p.startsWith('openai') || p.startsWith('groq')) {\n return parseOpenAIStyle(h);\n }\n if (p.startsWith('anthropic')) {\n return parseAnthropic(h);\n }\n // google, mistral, and unknown providers\n return parseGeneric(h);\n}\n","import { parseRateLimitHeaders } from './header-parsers';\nimport type { Limits, Store } from './types';\n\nconst MINUTE_MS = 60_000;\nconst DAY_MS = 86_400_000;\n/** How long a parsed header snapshot stays relevant without a reset hint. */\nconst DEFAULT_SNAPSHOT_TTL_MS = 60_000;\n/** Upper bound for header-snapshot lifetimes: garbage reset values must not pin a snapshot. */\nconst MAX_SNAPSHOT_TTL_MS = DAY_MS;\n\ninterface UsageState {\n /** Timestamps (ms) of requests within the last 24h. */\n requests: number[];\n /** `[timestamp, tokenCount]` pairs within the last minute. */\n tokens: [number, number][];\n}\n\ninterface DimensionSnapshot {\n remaining: number;\n limit?: number | undefined;\n /** After this timestamp the dimension has reset and is ignored. */\n expiresAt: number;\n}\n\ninterface HeaderSnapshot {\n requests?: DimensionSnapshot | undefined;\n tokens?: DimensionSnapshot | undefined;\n}\n\nexport interface RecordSuccessInput {\n provider: string;\n headers?: Record<string, string> | undefined;\n totalTokens?: number | undefined;\n limits?: Limits | undefined;\n}\n\n/**\n * Tracks per-model rate-limit state: cooldown benches after rate-limit\n * errors, provider-header snapshots, and sliding-window self-counted\n * usage against user-declared limits.\n *\n * All state goes through the pluggable `Store`; any store failure is\n * swallowed and treated as \"model available / nothing recorded\".\n */\nexport class LimitTracker {\n private readonly store: Store;\n private readonly threshold: number;\n private readonly cooldown: number;\n /** Per-key promise chains serializing usage counter updates. */\n private readonly usageLocks = new Map<string, Promise<void>>();\n\n constructor(options: { store: Store; threshold: number; cooldown: number }) {\n this.store = options.store;\n this.threshold = options.threshold;\n this.cooldown = options.cooldown;\n }\n\n private benchKey(modelKey: string): string {\n return `ai-resilient:bench:${modelKey}`;\n }\n private usageKey(modelKey: string): string {\n return `ai-resilient:usage:${modelKey}`;\n }\n private headersKey(modelKey: string): string {\n return `ai-resilient:headers:${modelKey}`;\n }\n\n /**\n * False if the model is benched (cooldown active) or usage is at or\n * beyond `(1 - threshold)` of any known limit.\n */\n async isAvailable(modelKey: string, limits?: Limits): Promise<boolean> {\n try {\n if (await this.isBenched(modelKey)) return false;\n if (await this.isNearHeaderLimit(modelKey)) return false;\n if (limits && (await this.isNearDeclaredLimit(modelKey, limits))) {\n return false;\n }\n return true;\n } catch {\n // Store failure: assume available.\n return true;\n }\n }\n\n private async isBenched(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.benchKey(modelKey));\n if (raw === null) return false;\n // Belt and braces for stores without real TTL support: the value is\n // the bench expiry timestamp. Unparseable values fail open (not\n // benched), consistent with the store-failure policy in isAvailable.\n const expiresAt = Number(raw);\n return Number.isFinite(expiresAt) && Date.now() < expiresAt;\n }\n\n private async isNearHeaderLimit(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.headersKey(modelKey));\n if (raw === null) return false;\n const snapshot = JSON.parse(raw) as HeaderSnapshot;\n const now = Date.now();\n // Each dimension keeps its own expiration so a fast-resetting,\n // non-limiting dimension can't wipe out a still-exhausted one.\n for (const dim of [snapshot.requests, snapshot.tokens]) {\n if (dim === undefined || now >= dim.expiresAt) continue;\n if (this.remainingBelowThreshold(dim.remaining, dim.limit)) return true;\n }\n return false;\n }\n\n private remainingBelowThreshold(\n remaining: number | undefined,\n limit: number | undefined,\n ): boolean {\n if (remaining === undefined) return false;\n if (remaining <= 0) return true;\n if (limit === undefined || limit <= 0) return false;\n return remaining / limit <= this.threshold;\n }\n\n private async isNearDeclaredLimit(\n modelKey: string,\n limits: Limits,\n ): Promise<boolean> {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n if (limits.requestsPerMinute !== undefined) {\n const used = usage.requests.filter((t) => now - t < MINUTE_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerMinute) return true;\n }\n if (limits.requestsPerDay !== undefined) {\n const used = usage.requests.filter((t) => now - t < DAY_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerDay) return true;\n }\n if (limits.tokensPerMinute !== undefined) {\n const used = usage.tokens\n .filter(([t]) => now - t < MINUTE_MS)\n .reduce((sum, [, n]) => sum + n, 0);\n if (used >= (1 - this.threshold) * limits.tokensPerMinute) return true;\n }\n return false;\n }\n\n /**\n * Record a successful call: parse provider rate-limit headers and\n * increment sliding-window counters for declared limits.\n */\n async recordSuccess(\n modelKey: string,\n input: RecordSuccessInput,\n ): Promise<void> {\n try {\n if (input.headers !== undefined) {\n await this.recordHeaders(modelKey, input.provider, input.headers);\n }\n if (input.limits !== undefined) {\n await this.recordUsage(modelKey, input.limits, input.totalTokens);\n }\n } catch {\n // Store failure: skip recording.\n }\n }\n\n private async recordHeaders(\n modelKey: string,\n provider: string,\n headers: Record<string, string>,\n ): Promise<void> {\n const parsed = parseRateLimitHeaders(provider, headers);\n const now = Date.now();\n const snapshot: HeaderSnapshot = {};\n if (parsed.requestsRemaining !== undefined) {\n snapshot.requests = {\n remaining: parsed.requestsRemaining,\n limit: parsed.requestsLimit,\n expiresAt:\n now +\n Math.min(\n parsed.requestsResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n if (parsed.tokensRemaining !== undefined) {\n snapshot.tokens = {\n remaining: parsed.tokensRemaining,\n limit: parsed.tokensLimit,\n expiresAt:\n now +\n Math.min(\n parsed.tokensResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n // Keep the snapshot around until the *slowest* dimension resets;\n // each dimension expires individually in isNearHeaderLimit.\n const ttl = Math.max(\n (snapshot.requests?.expiresAt ?? 0) - now,\n (snapshot.tokens?.expiresAt ?? 0) - now,\n );\n if (ttl <= 0) return;\n await this.store.set(\n this.headersKey(modelKey),\n JSON.stringify(snapshot),\n ttl,\n );\n }\n\n private recordUsage(\n modelKey: string,\n limits: Limits,\n totalTokens: number | undefined,\n ): Promise<void> {\n // Serialize read-modify-write per key so concurrent calls in this\n // process don't drop each other's counts. Distributed setups sharing\n // a store across processes should prefer a store with atomic\n // operations; self-counting is best-effort there.\n return this.serializeUsageUpdate(modelKey, async () => {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n const trackDay = limits.requestsPerDay !== undefined ? DAY_MS : MINUTE_MS;\n usage.requests = usage.requests.filter((t) => now - t < trackDay);\n usage.requests.push(now);\n\n usage.tokens = usage.tokens.filter(([t]) => now - t < MINUTE_MS);\n if (limits.tokensPerMinute !== undefined && totalTokens !== undefined) {\n usage.tokens.push([now, totalTokens]);\n }\n\n await this.store.set(\n this.usageKey(modelKey),\n JSON.stringify(usage),\n trackDay,\n );\n });\n }\n\n private serializeUsageUpdate(\n modelKey: string,\n fn: () => Promise<void>,\n ): Promise<void> {\n const prev = this.usageLocks.get(modelKey) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n this.usageLocks.set(modelKey, tail);\n void tail.then(() => {\n if (this.usageLocks.get(modelKey) === tail) {\n this.usageLocks.delete(modelKey);\n }\n });\n return next;\n }\n\n private async readUsage(modelKey: string): Promise<UsageState> {\n const raw = await this.store.get(this.usageKey(modelKey));\n if (raw === null) return { requests: [], tokens: [] };\n const parsed = JSON.parse(raw) as Partial<UsageState>;\n return {\n requests: Array.isArray(parsed.requests) ? parsed.requests : [],\n tokens: Array.isArray(parsed.tokens) ? parsed.tokens : [],\n };\n }\n\n /**\n * Bench a model after a rate-limit error, for `retryAfterMs` when the\n * provider sent one, otherwise for the configured cooldown.\n */\n async recordRateLimit(\n modelKey: string,\n retryAfterMs?: number,\n ): Promise<void> {\n const benchMs = retryAfterMs ?? this.cooldown;\n if (benchMs <= 0) return;\n try {\n await this.store.set(\n this.benchKey(modelKey),\n String(Date.now() + benchMs),\n benchMs,\n );\n } catch {\n // Store failure: skip recording.\n }\n }\n}\n","import type { ErrorClassification } from './types';\n\ninterface ErrorShape {\n statusCode?: number | undefined;\n responseHeaders?: Record<string, string> | undefined;\n isRetryable?: boolean | undefined;\n message?: string | undefined;\n}\n\n/**\n * Extract the shape of an AI SDK `APICallError` (statusCode,\n * responseHeaders, isRetryable) via duck typing, so we don't need a\n * runtime dependency on `@ai-sdk/provider`.\n */\nfunction getErrorShape(error: unknown): ErrorShape {\n if (typeof error !== 'object' || error === null) {\n return { message: String(error) };\n }\n const e = error as Record<string, unknown>;\n return {\n statusCode:\n typeof e['statusCode'] === 'number' ? e['statusCode'] : undefined,\n responseHeaders:\n typeof e['responseHeaders'] === 'object' && e['responseHeaders'] !== null\n ? (e['responseHeaders'] as Record<string, string>)\n : undefined,\n isRetryable:\n typeof e['isRetryable'] === 'boolean' ? e['isRetryable'] : undefined,\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n };\n}\n\nconst RATE_LIMIT_PATTERN =\n /rate.?limit|too many requests|quota|exhausted|billing|insufficient.?quota/i;\n\nconst TRANSIENT_PATTERN =\n /overloaded|timeout|timed.?out|econnreset|econnrefused|etimedout|socket hang up|network|fetch failed|service unavailable|internal server error|bad gateway/i;\n\nfunction getHeader(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Classify an error from a model call:\n *\n * - `rate-limit`: HTTP 429, quota/exhausted message patterns, or a\n * `retry-after` header — fall back and bench the model.\n * - `transient`: HTTP 5xx, overloaded/network/timeout errors — fall back\n * without benching.\n * - `fatal`: 400/401/403 and everything else — throw immediately.\n */\nexport function classifyError(error: unknown): ErrorClassification {\n const { statusCode, responseHeaders, isRetryable, message } =\n getErrorShape(error);\n const msg = message ?? '';\n\n if (statusCode === 429) return 'rate-limit';\n if (statusCode === 400 || statusCode === 401 || statusCode === 403) {\n return 'fatal';\n }\n if (statusCode !== undefined && statusCode >= 500) return 'transient';\n if (getHeader(responseHeaders, 'retry-after') !== undefined) {\n return 'rate-limit';\n }\n if (RATE_LIMIT_PATTERN.test(msg)) return 'rate-limit';\n if (isRetryable === true) return 'transient';\n if (TRANSIENT_PATTERN.test(msg)) return 'transient';\n return 'fatal';\n}\n\n/**\n * Parse a `retry-after` header from an error's response headers into\n * milliseconds. Supports delta-seconds and HTTP-date formats.\n */\nexport function getRetryAfterMs(error: unknown): number | undefined {\n const { responseHeaders } = getErrorShape(error);\n const raw = getHeader(responseHeaders, 'retry-after');\n if (raw === undefined) return undefined;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n return Math.max(0, date - Date.now());\n }\n return undefined;\n}\n","import type { ErrorClassification } from './types';\n\n/** One failed attempt against a model in the fallback chain. */\nexport interface ModelAttempt {\n modelId: string;\n error: unknown;\n classification: ErrorClassification;\n}\n\n/**\n * Thrown when every model in the fallback chain failed with a\n * rate-limit or transient error.\n */\nexport class AllModelsExhaustedError extends Error {\n override readonly name = 'AllModelsExhaustedError';\n readonly attempts: ModelAttempt[];\n\n constructor(attempts: ModelAttempt[]) {\n const summary = attempts\n .map((a) => `${a.modelId} (${a.classification})`)\n .join(', ');\n super(\n `All models exhausted after ${attempts.length} attempt(s): ${summary}`,\n );\n this.attempts = attempts;\n }\n\n static isInstance(error: unknown): error is AllModelsExhaustedError {\n return error instanceof Error && error.name === 'AllModelsExhaustedError';\n }\n}\n","import type {\n LanguageModelV2,\n LanguageModelV2CallOptions,\n LanguageModelV2StreamPart,\n LanguageModelV2Usage,\n} from '@ai-sdk/provider';\nimport { classifyError, getRetryAfterMs } from './classify-error';\nimport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nimport { LimitTracker } from './limit-tracker';\nimport type { FallbackReason, ModelConfig, ResilientOptions } from './types';\n\ntype DoGenerateResult = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;\ntype DoStreamResult = Awaited<ReturnType<LanguageModelV2['doStream']>>;\n\ninterface Candidate {\n config: ModelConfig;\n /** Unique store key: `provider:modelId` (+ index suffix on collision). */\n key: string;\n}\n\ninterface PlanEntry {\n candidate: Candidate;\n /** True when the model was proactively filtered by the LimitTracker. */\n skip: boolean;\n}\n\ninterface PendingFallback {\n from: string;\n reason: FallbackReason;\n}\n\nconst PRELUDE_PART_TYPES = new Set(['stream-start', 'response-metadata']);\n\n/**\n * A `LanguageModelV2` that transparently falls back across a chain of\n * models on rate-limit and transient errors, and proactively skips\n * models that are near a known rate limit.\n */\nexport class ResilientLanguageModel implements LanguageModelV2 {\n readonly specificationVersion = 'v2' as const;\n readonly provider = 'ai-resilient';\n\n private readonly candidates: Candidate[];\n private readonly tracker: LimitTracker;\n private readonly onFallback: ResilientOptions['onFallback'];\n private readonly onError: ResilientOptions['onError'];\n\n /**\n * Bookkeeping writes chained off the response path. buildPlan awaits\n * this so the next call still sees all prior usage before deciding\n * availability (read-your-writes), without success responses paying\n * store-write latency.\n */\n private pendingRecords: Promise<void> = Promise.resolve();\n\n constructor(options: {\n models: ModelConfig[];\n tracker: LimitTracker;\n onFallback?: ResilientOptions['onFallback'];\n onError?: ResilientOptions['onError'];\n }) {\n if (options.models.length === 0) {\n throw new Error('ai-resilient: at least one model is required');\n }\n const seen = new Map<string, number>();\n this.candidates = options.models.map((config) => {\n const base = `${config.model.provider}:${config.model.modelId}`;\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n return { config, key: count === 0 ? base : `${base}#${count}` };\n });\n this.tracker = options.tracker;\n this.onFallback = options.onFallback;\n this.onError = options.onError;\n }\n\n get modelId(): string {\n return this.primary.modelId;\n }\n\n get supportedUrls(): LanguageModelV2['supportedUrls'] {\n return this.primary.supportedUrls;\n }\n\n private get primary(): LanguageModelV2 {\n // candidates is non-empty by construction\n return this.candidates[0]!.config.model;\n }\n\n private queueRecordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: LanguageModelV2Usage | undefined,\n ): void {\n this.pendingRecords = this.pendingRecords\n .then(() => this.recordSuccess(candidate, headers, usage))\n .catch(() => {\n // Store bookkeeping failures must never surface to callers.\n });\n }\n\n /**\n * Order candidates and mark near-limit/benched ones as skipped. If\n * every model would be skipped, try all of them in configured order\n * anyway — attempting beats failing without trying.\n */\n private async buildPlan(): Promise<PlanEntry[]> {\n await this.pendingRecords;\n const plan: PlanEntry[] = [];\n for (const candidate of this.candidates) {\n const available = await this.tracker.isAvailable(\n candidate.key,\n candidate.config.limits,\n );\n plan.push({ candidate, skip: !available });\n }\n if (plan.every((entry) => entry.skip)) {\n for (const entry of plan) entry.skip = false;\n }\n return plan;\n }\n\n private fireFallbacks(pending: PendingFallback[], to: string): void {\n for (const { from, reason } of pending) {\n this.onFallback?.({ from, to, reason });\n }\n pending.length = 0;\n }\n\n /**\n * Handle a failed attempt: notify, classify, bench on rate-limit, and\n * queue the fallback notification. Rethrows fatal errors.\n */\n private async handleFailure(\n error: unknown,\n candidate: Candidate,\n attempts: ModelAttempt[],\n pending: PendingFallback[],\n ): Promise<void> {\n const modelId = candidate.config.model.modelId;\n this.onError?.(error, modelId);\n const classification = classifyError(error);\n if (classification === 'fatal') throw error;\n attempts.push({ modelId, error, classification });\n if (classification === 'rate-limit') {\n await this.tracker.recordRateLimit(candidate.key, getRetryAfterMs(error));\n }\n pending.push({ from: modelId, reason: classification });\n }\n\n private async recordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: LanguageModelV2Usage | undefined,\n ): Promise<void> {\n const totalTokens =\n usage?.totalTokens ??\n (usage?.inputTokens !== undefined || usage?.outputTokens !== undefined\n ? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)\n : undefined);\n await this.tracker.recordSuccess(candidate.key, {\n provider: candidate.config.model.provider,\n headers,\n totalTokens,\n limits: candidate.config.limits,\n });\n }\n\n async doGenerate(\n options: LanguageModelV2CallOptions,\n ): Promise<DoGenerateResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n\n for (const entry of plan) {\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = await candidate.config.model.doGenerate(options);\n this.queueRecordSuccess(\n candidate,\n result.response?.headers,\n result.usage,\n );\n return result;\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n }\n\n async doStream(options: LanguageModelV2CallOptions): Promise<DoStreamResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n let index = 0;\n\n const acquire = async (): Promise<{\n candidate: Candidate;\n result: DoStreamResult;\n }> => {\n while (index < plan.length) {\n const entry = plan[index++]!;\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = await candidate.config.model.doStream(options);\n return { candidate, result };\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n };\n\n // Try candidates until one produces content (or finishes cleanly)\n // before returning, so the returned request/response metadata always\n // belongs to the model that actually serves the stream.\n for (;;) {\n const { candidate, result } = await acquire();\n const reader = result.stream.getReader();\n const prelude: LanguageModelV2StreamPart[] = [];\n let firstContent: LanguageModelV2StreamPart | undefined;\n let usage: LanguageModelV2Usage | undefined;\n let done = false;\n\n try {\n for (;;) {\n const { done: readDone, value } = await reader.read();\n if (readDone) {\n done = true;\n break;\n }\n if (value.type === 'error') {\n // Pre-content stream error: treat like a failed call so we\n // can fall back to the next model.\n throw value.error;\n }\n if (value.type === 'finish') usage = value.usage;\n if (PRELUDE_PART_TYPES.has(value.type)) {\n prelude.push(value);\n continue;\n }\n firstContent = value;\n break;\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n // Rethrows fatal errors; otherwise fall through to next candidate.\n await this.handleFailure(error, candidate, attempts, pending);\n continue;\n }\n\n // Committed: first content part arrived (or the stream finished).\n // Errors from here on propagate to the consumer unchanged.\n let preludeIndex = 0;\n const stream = new ReadableStream<LanguageModelV2StreamPart>({\n pull: async (controller) => {\n if (preludeIndex < prelude.length) {\n controller.enqueue(prelude[preludeIndex++]);\n return;\n }\n if (firstContent !== undefined) {\n const part = firstContent;\n firstContent = undefined;\n controller.enqueue(part);\n return;\n }\n if (!done) {\n const { done: readDone, value } = await reader.read();\n if (!readDone) {\n if (value.type === 'finish') usage = value.usage;\n controller.enqueue(value);\n return;\n }\n done = true;\n }\n this.queueRecordSuccess(candidate, result.response?.headers, usage);\n controller.close();\n },\n cancel: (reason) => reader.cancel(reason),\n });\n\n return {\n stream,\n ...(result.request !== undefined ? { request: result.request } : {}),\n ...(result.response !== undefined ? { response: result.response } : {}),\n };\n }\n }\n}\n","import type { Store } from '../types';\n\ninterface Entry {\n value: string;\n expiresAt?: number;\n}\n\n/**\n * In-process `Store` backed by a `Map` with lazy TTL eviction.\n * Suitable for long-running servers; use a Redis/KV adapter for\n * serverless deployments.\n */\nexport function memoryStore(): Store {\n const map = new Map<string, Entry>();\n\n function evictIfExpired(key: string): Entry | undefined {\n const entry = map.get(key);\n if (entry === undefined) return undefined;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n return {\n get(key) {\n const entry = evictIfExpired(key);\n return Promise.resolve(entry?.value ?? null);\n },\n set(key, value, ttlMs) {\n map.set(key, {\n value,\n ...(ttlMs !== undefined ? { expiresAt: Date.now() + ttlMs } : {}),\n });\n return Promise.resolve();\n },\n };\n}\n","import type { LanguageModelV2 } from '@ai-sdk/provider';\nimport { LimitTracker } from './limit-tracker';\nimport { ResilientLanguageModel } from './resilient-model';\nimport { memoryStore } from './stores/memory';\nimport type { ResilientOptions } from './types';\n\nexport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nexport { classifyError, getRetryAfterMs } from './classify-error';\nexport { memoryStore } from './stores/memory';\nexport { LimitTracker } from './limit-tracker';\nexport { parseRateLimitHeaders, type ParsedRateLimit } from './header-parsers';\nexport type {\n ErrorClassification,\n FallbackInfo,\n FallbackReason,\n Limits,\n ModelConfig,\n ResilientOptions,\n Store,\n} from './types';\n\nconst DEFAULT_THRESHOLD = 0.1;\nconst DEFAULT_COOLDOWN_MS = 60_000;\n\n/**\n * Create a resilient language model that transparently falls back across\n * the configured models on rate-limit/transient errors and proactively\n * switches away from models near a known rate limit.\n *\n * The returned object implements `LanguageModelV2`, so it works with\n * `generateText`, `streamText`, `generateObject`, and `streamObject`.\n */\nexport function createResilient(options: ResilientOptions): LanguageModelV2 {\n const tracker = new LimitTracker({\n store: options.store ?? memoryStore(),\n threshold: options.threshold ?? DEFAULT_THRESHOLD,\n cooldown: options.cooldown ?? DEFAULT_COOLDOWN_MS,\n });\n return new ResilientLanguageModel({\n models: options.models,\n tracker,\n ...(options.onFallback !== undefined\n ? { onFallback: options.onFallback }\n : {}),\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/header-parsers.ts","../src/limit-tracker.ts","../src/classify-error.ts","../src/errors.ts","../src/resilient-model.ts","../src/stores/memory.ts","../src/index.ts"],"sourcesContent":["/**\n * Normalized rate-limit state parsed from provider response headers.\n */\nexport interface ParsedRateLimit {\n requestsRemaining?: number | undefined;\n requestsLimit?: number | undefined;\n /** Milliseconds until the request limit resets. */\n requestsResetMs?: number | undefined;\n tokensRemaining?: number | undefined;\n tokensLimit?: number | undefined;\n /** Milliseconds until the token limit resets. */\n tokensResetMs?: number | undefined;\n}\n\ntype Headers = Record<string, string>;\n\nfunction lower(headers: Headers): Headers {\n const out: Headers = {};\n for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;\n return out;\n}\n\nfunction num(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * Parse duration strings used in OpenAI/Groq reset headers, e.g.\n * `\"1s\"`, `\"7.66s\"`, `\"2m59.56s\"`, `\"6m0s\"`, `\"1h2m3s\"`, `\"123ms\"`,\n * or a plain number of seconds.\n */\nexport function parseDuration(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const plain = Number(value);\n if (Number.isFinite(plain)) return plain * 1000;\n\n const pattern = /(\\d+(?:\\.\\d+)?)(ms|h|m|s)/g;\n let ms = 0;\n let matched = false;\n for (const [, amount, unit] of value.matchAll(pattern)) {\n matched = true;\n const n = Number(amount);\n if (unit === 'h') ms += n * 3_600_000;\n else if (unit === 'm') ms += n * 60_000;\n else if (unit === 's') ms += n * 1000;\n else ms += n;\n }\n return matched ? Math.round(ms) : undefined;\n}\n\n/** Parse an RFC 3339 timestamp into ms-from-now. */\nfunction parseResetTimestamp(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const t = Date.parse(value);\n if (Number.isNaN(t)) return undefined;\n return Math.max(0, t - Date.now());\n}\n\n/** OpenAI and Groq use `x-ratelimit-{limit,remaining,reset}-{requests,tokens}`. */\nfunction parseOpenAIStyle(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['x-ratelimit-remaining-requests']),\n requestsLimit: num(h['x-ratelimit-limit-requests']),\n requestsResetMs: parseDuration(h['x-ratelimit-reset-requests']),\n tokensRemaining: num(h['x-ratelimit-remaining-tokens']),\n tokensLimit: num(h['x-ratelimit-limit-tokens']),\n tokensResetMs: parseDuration(h['x-ratelimit-reset-tokens']),\n };\n}\n\n/** Anthropic uses `anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset}`. */\nfunction parseAnthropic(h: Headers): ParsedRateLimit {\n return {\n requestsRemaining: num(h['anthropic-ratelimit-requests-remaining']),\n requestsLimit: num(h['anthropic-ratelimit-requests-limit']),\n requestsResetMs: parseResetTimestamp(\n h['anthropic-ratelimit-requests-reset'],\n ),\n tokensRemaining: num(h['anthropic-ratelimit-tokens-remaining']),\n tokensLimit: num(h['anthropic-ratelimit-tokens-limit']),\n tokensResetMs: parseResetTimestamp(h['anthropic-ratelimit-tokens-reset']),\n };\n}\n\n/**\n * `ratelimit-reset` is delta-seconds per the IETF draft, but some\n * implementations send a Unix epoch timestamp. Values that would put the\n * reset more than a year out are interpreted as epoch seconds.\n */\nfunction parseIetfReset(value: string | undefined): number | undefined {\n const reset = num(value);\n if (reset === undefined) return undefined;\n const YEAR_S = 31_536_000;\n if (reset > YEAR_S) {\n // Epoch seconds → delta from now, clamped to >= 0.\n return Math.max(0, reset * 1000 - Date.now());\n }\n return reset * 1000;\n}\n\n/**\n * Best-effort parser for providers without a documented scheme\n * (Google, Mistral): tries OpenAI-style headers, then the IETF\n * draft `ratelimit-*` headers.\n */\nfunction parseGeneric(h: Headers): ParsedRateLimit {\n const openai = parseOpenAIStyle(h);\n if (Object.values(openai).some((v) => v !== undefined)) return openai;\n const reset = parseIetfReset(h['ratelimit-reset']);\n return {\n requestsRemaining: num(h['ratelimit-remaining']),\n requestsLimit: num(h['ratelimit-limit']),\n ...(reset !== undefined ? { requestsResetMs: reset } : {}),\n };\n}\n\n/**\n * Parse provider rate-limit headers into a normalized shape.\n * The provider is matched by prefix of the model's `provider` string\n * (e.g. `\"openai.chat\"` → OpenAI). Unknown providers get the\n * best-effort generic parser.\n */\nexport function parseRateLimitHeaders(\n provider: string,\n headers: Headers | undefined,\n): ParsedRateLimit {\n if (headers === undefined) return {};\n const h = lower(headers);\n const p = provider.toLowerCase();\n\n if (p.startsWith('openai') || p.startsWith('groq')) {\n return parseOpenAIStyle(h);\n }\n if (p.startsWith('anthropic')) {\n return parseAnthropic(h);\n }\n // google, mistral, and unknown providers\n return parseGeneric(h);\n}\n","import { parseRateLimitHeaders } from './header-parsers';\nimport type { Limits, Store } from './types';\n\nconst MINUTE_MS = 60_000;\nconst DAY_MS = 86_400_000;\n/** How long a parsed header snapshot stays relevant without a reset hint. */\nconst DEFAULT_SNAPSHOT_TTL_MS = 60_000;\n/** Upper bound for header-snapshot lifetimes: garbage reset values must not pin a snapshot. */\nconst MAX_SNAPSHOT_TTL_MS = DAY_MS;\n\ninterface UsageState {\n /** Timestamps (ms) of requests within the last 24h. */\n requests: number[];\n /** `[timestamp, tokenCount]` pairs within the last minute. */\n tokens: [number, number][];\n}\n\ninterface DimensionSnapshot {\n remaining: number;\n limit?: number | undefined;\n /** After this timestamp the dimension has reset and is ignored. */\n expiresAt: number;\n}\n\ninterface HeaderSnapshot {\n requests?: DimensionSnapshot | undefined;\n tokens?: DimensionSnapshot | undefined;\n}\n\nexport interface RecordSuccessInput {\n provider: string;\n headers?: Record<string, string> | undefined;\n totalTokens?: number | undefined;\n limits?: Limits | undefined;\n}\n\n/**\n * Tracks per-model rate-limit state: cooldown benches after rate-limit\n * errors, provider-header snapshots, and sliding-window self-counted\n * usage against user-declared limits.\n *\n * All state goes through the pluggable `Store`; any store failure is\n * swallowed and treated as \"model available / nothing recorded\".\n */\nexport class LimitTracker {\n private readonly store: Store;\n private readonly threshold: number;\n private readonly cooldown: number;\n /** Per-key promise chains serializing usage counter updates. */\n private readonly usageLocks = new Map<string, Promise<void>>();\n\n constructor(options: { store: Store; threshold: number; cooldown: number }) {\n this.store = options.store;\n this.threshold = options.threshold;\n this.cooldown = options.cooldown;\n }\n\n private benchKey(modelKey: string): string {\n return `ai-resilient:bench:${modelKey}`;\n }\n private usageKey(modelKey: string): string {\n return `ai-resilient:usage:${modelKey}`;\n }\n private headersKey(modelKey: string): string {\n return `ai-resilient:headers:${modelKey}`;\n }\n\n /**\n * False if the model is benched (cooldown active) or usage is at or\n * beyond `(1 - threshold)` of any known limit.\n */\n async isAvailable(modelKey: string, limits?: Limits): Promise<boolean> {\n try {\n if (await this.isBenched(modelKey)) return false;\n if (await this.isNearHeaderLimit(modelKey)) return false;\n if (limits && (await this.isNearDeclaredLimit(modelKey, limits))) {\n return false;\n }\n return true;\n } catch {\n // Store failure: assume available.\n return true;\n }\n }\n\n private async isBenched(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.benchKey(modelKey));\n if (raw === null) return false;\n // Belt and braces for stores without real TTL support: the value is\n // the bench expiry timestamp. Unparseable values fail open (not\n // benched), consistent with the store-failure policy in isAvailable.\n const expiresAt = Number(raw);\n return Number.isFinite(expiresAt) && Date.now() < expiresAt;\n }\n\n private async isNearHeaderLimit(modelKey: string): Promise<boolean> {\n const raw = await this.store.get(this.headersKey(modelKey));\n if (raw === null) return false;\n const snapshot = JSON.parse(raw) as HeaderSnapshot;\n const now = Date.now();\n // Each dimension keeps its own expiration so a fast-resetting,\n // non-limiting dimension can't wipe out a still-exhausted one.\n for (const dim of [snapshot.requests, snapshot.tokens]) {\n if (dim === undefined || now >= dim.expiresAt) continue;\n if (this.remainingBelowThreshold(dim.remaining, dim.limit)) return true;\n }\n return false;\n }\n\n private remainingBelowThreshold(\n remaining: number | undefined,\n limit: number | undefined,\n ): boolean {\n if (remaining === undefined) return false;\n if (remaining <= 0) return true;\n if (limit === undefined || limit <= 0) return false;\n return remaining / limit <= this.threshold;\n }\n\n private async isNearDeclaredLimit(\n modelKey: string,\n limits: Limits,\n ): Promise<boolean> {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n if (limits.requestsPerMinute !== undefined) {\n const used = usage.requests.filter((t) => now - t < MINUTE_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerMinute) return true;\n }\n if (limits.requestsPerDay !== undefined) {\n const used = usage.requests.filter((t) => now - t < DAY_MS).length;\n if (used >= (1 - this.threshold) * limits.requestsPerDay) return true;\n }\n if (limits.tokensPerMinute !== undefined) {\n const used = usage.tokens\n .filter(([t]) => now - t < MINUTE_MS)\n .reduce((sum, [, n]) => sum + n, 0);\n if (used >= (1 - this.threshold) * limits.tokensPerMinute) return true;\n }\n return false;\n }\n\n /**\n * Record a successful call: parse provider rate-limit headers and\n * increment sliding-window counters for declared limits.\n */\n async recordSuccess(\n modelKey: string,\n input: RecordSuccessInput,\n ): Promise<void> {\n try {\n if (input.headers !== undefined) {\n await this.recordHeaders(modelKey, input.provider, input.headers);\n }\n if (input.limits !== undefined) {\n await this.recordUsage(modelKey, input.limits, input.totalTokens);\n }\n } catch {\n // Store failure: skip recording.\n }\n }\n\n private async recordHeaders(\n modelKey: string,\n provider: string,\n headers: Record<string, string>,\n ): Promise<void> {\n const parsed = parseRateLimitHeaders(provider, headers);\n const now = Date.now();\n const snapshot: HeaderSnapshot = {};\n if (parsed.requestsRemaining !== undefined) {\n snapshot.requests = {\n remaining: parsed.requestsRemaining,\n limit: parsed.requestsLimit,\n expiresAt:\n now +\n Math.min(\n parsed.requestsResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n if (parsed.tokensRemaining !== undefined) {\n snapshot.tokens = {\n remaining: parsed.tokensRemaining,\n limit: parsed.tokensLimit,\n expiresAt:\n now +\n Math.min(\n parsed.tokensResetMs ?? DEFAULT_SNAPSHOT_TTL_MS,\n MAX_SNAPSHOT_TTL_MS,\n ),\n };\n }\n // Keep the snapshot around until the *slowest* dimension resets;\n // each dimension expires individually in isNearHeaderLimit.\n const ttl = Math.max(\n (snapshot.requests?.expiresAt ?? 0) - now,\n (snapshot.tokens?.expiresAt ?? 0) - now,\n );\n if (ttl <= 0) return;\n await this.store.set(\n this.headersKey(modelKey),\n JSON.stringify(snapshot),\n ttl,\n );\n }\n\n private recordUsage(\n modelKey: string,\n limits: Limits,\n totalTokens: number | undefined,\n ): Promise<void> {\n // Serialize read-modify-write per key so concurrent calls in this\n // process don't drop each other's counts. Distributed setups sharing\n // a store across processes should prefer a store with atomic\n // operations; self-counting is best-effort there.\n return this.serializeUsageUpdate(modelKey, async () => {\n const usage = await this.readUsage(modelKey);\n const now = Date.now();\n\n const trackDay = limits.requestsPerDay !== undefined ? DAY_MS : MINUTE_MS;\n usage.requests = usage.requests.filter((t) => now - t < trackDay);\n usage.requests.push(now);\n\n usage.tokens = usage.tokens.filter(([t]) => now - t < MINUTE_MS);\n if (limits.tokensPerMinute !== undefined && totalTokens !== undefined) {\n usage.tokens.push([now, totalTokens]);\n }\n\n await this.store.set(\n this.usageKey(modelKey),\n JSON.stringify(usage),\n trackDay,\n );\n });\n }\n\n private serializeUsageUpdate(\n modelKey: string,\n fn: () => Promise<void>,\n ): Promise<void> {\n const prev = this.usageLocks.get(modelKey) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n this.usageLocks.set(modelKey, tail);\n void tail.then(() => {\n if (this.usageLocks.get(modelKey) === tail) {\n this.usageLocks.delete(modelKey);\n }\n });\n return next;\n }\n\n private async readUsage(modelKey: string): Promise<UsageState> {\n const raw = await this.store.get(this.usageKey(modelKey));\n if (raw === null) return { requests: [], tokens: [] };\n const parsed = JSON.parse(raw) as Partial<UsageState>;\n return {\n requests: Array.isArray(parsed.requests) ? parsed.requests : [],\n tokens: Array.isArray(parsed.tokens) ? parsed.tokens : [],\n };\n }\n\n /**\n * Bench a model after a rate-limit error, for `retryAfterMs` when the\n * provider sent one, otherwise for the configured cooldown.\n */\n async recordRateLimit(\n modelKey: string,\n retryAfterMs?: number,\n ): Promise<void> {\n const benchMs = retryAfterMs ?? this.cooldown;\n if (benchMs <= 0) return;\n try {\n await this.store.set(\n this.benchKey(modelKey),\n String(Date.now() + benchMs),\n benchMs,\n );\n } catch {\n // Store failure: skip recording.\n }\n }\n}\n","import type { ErrorClassification } from './types';\n\ninterface ErrorShape {\n statusCode?: number | undefined;\n responseHeaders?: Record<string, string> | undefined;\n isRetryable?: boolean | undefined;\n message?: string | undefined;\n}\n\n/**\n * Extract the shape of an AI SDK `APICallError` (statusCode,\n * responseHeaders, isRetryable) via duck typing, so we don't need a\n * runtime dependency on `@ai-sdk/provider`.\n */\nfunction getErrorShape(error: unknown): ErrorShape {\n if (typeof error !== 'object' || error === null) {\n return { message: String(error) };\n }\n const e = error as Record<string, unknown>;\n return {\n statusCode:\n typeof e['statusCode'] === 'number' ? e['statusCode'] : undefined,\n responseHeaders:\n typeof e['responseHeaders'] === 'object' && e['responseHeaders'] !== null\n ? (e['responseHeaders'] as Record<string, string>)\n : undefined,\n isRetryable:\n typeof e['isRetryable'] === 'boolean' ? e['isRetryable'] : undefined,\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n };\n}\n\nconst RATE_LIMIT_PATTERN =\n /rate.?limit|too many requests|quota|exhausted|billing|insufficient.?quota/i;\n\nconst TRANSIENT_PATTERN =\n /overloaded|timeout|timed.?out|econnreset|econnrefused|etimedout|socket hang up|network|fetch failed|service unavailable|internal server error|bad gateway/i;\n\nfunction getHeader(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Classify an error from a model call:\n *\n * - `rate-limit`: HTTP 429, quota/exhausted message patterns, or a\n * `retry-after` header — fall back and bench the model.\n * - `transient`: HTTP 5xx, overloaded/network/timeout errors — fall back\n * without benching.\n * - `fatal`: 400/401/403 and everything else — throw immediately.\n */\nexport function classifyError(error: unknown): ErrorClassification {\n const { statusCode, responseHeaders, isRetryable, message } =\n getErrorShape(error);\n const msg = message ?? '';\n\n if (statusCode === 429) return 'rate-limit';\n if (statusCode === 400 || statusCode === 401 || statusCode === 403) {\n return 'fatal';\n }\n if (statusCode !== undefined && statusCode >= 500) return 'transient';\n if (getHeader(responseHeaders, 'retry-after') !== undefined) {\n return 'rate-limit';\n }\n if (RATE_LIMIT_PATTERN.test(msg)) return 'rate-limit';\n if (isRetryable === true) return 'transient';\n if (TRANSIENT_PATTERN.test(msg)) return 'transient';\n return 'fatal';\n}\n\n/**\n * Parse a `retry-after` header from an error's response headers into\n * milliseconds. Supports delta-seconds and HTTP-date formats.\n */\nexport function getRetryAfterMs(error: unknown): number | undefined {\n const { responseHeaders } = getErrorShape(error);\n const raw = getHeader(responseHeaders, 'retry-after');\n if (raw === undefined) return undefined;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds * 1000);\n }\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n return Math.max(0, date - Date.now());\n }\n return undefined;\n}\n","import type { ErrorClassification } from './types';\n\n/** One failed attempt against a model in the fallback chain. */\nexport interface ModelAttempt {\n modelId: string;\n error: unknown;\n classification: ErrorClassification;\n}\n\n/**\n * Thrown when every model in the fallback chain failed with a\n * rate-limit or transient error.\n */\nexport class AllModelsExhaustedError extends Error {\n override readonly name = 'AllModelsExhaustedError';\n readonly attempts: ModelAttempt[];\n\n constructor(attempts: ModelAttempt[]) {\n const summary = attempts\n .map((a) => `${a.modelId} (${a.classification})`)\n .join(', ');\n super(\n `All models exhausted after ${attempts.length} attempt(s): ${summary}`,\n );\n this.attempts = attempts;\n }\n\n static isInstance(error: unknown): error is AllModelsExhaustedError {\n return error instanceof Error && error.name === 'AllModelsExhaustedError';\n }\n}\n","import type {\n LanguageModelV2,\n LanguageModelV2CallOptions,\n LanguageModelV2StreamPart,\n} from '@ai-sdk/provider';\nimport { classifyError, getRetryAfterMs } from './classify-error';\nimport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nimport { LimitTracker } from './limit-tracker';\nimport type {\n AnyLanguageModel,\n FallbackReason,\n ModelConfig,\n ResilientOptions,\n SpecificationVersion,\n} from './types';\n\ntype DoGenerateResult = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;\ntype DoStreamResult = Awaited<ReturnType<LanguageModelV2['doStream']>>;\n\ninterface Candidate {\n config: ModelConfig;\n /** Unique store key: `provider:modelId` (+ index suffix on collision). */\n key: string;\n}\n\ninterface PlanEntry {\n candidate: Candidate;\n /** True when the model was proactively filtered by the LimitTracker. */\n skip: boolean;\n}\n\ninterface PendingFallback {\n from: string;\n reason: FallbackReason;\n}\n\nconst PRELUDE_PART_TYPES = new Set(['stream-start', 'response-metadata']);\n\n/**\n * Extract a token count from a usage field that may be a v2 flat number\n * or a v3 nested `{ total: number }` object.\n */\nfunction tokenCount(value: unknown): number | undefined {\n if (typeof value === 'number') return value;\n if (typeof value === 'object' && value !== null) {\n const total = (value as { total?: unknown }).total;\n if (typeof total === 'number') return total;\n }\n return undefined;\n}\n\n/**\n * Total token usage from either a `LanguageModelV2Usage` (flat numbers,\n * top-level `totalTokens`) or a `LanguageModelV3Usage` (nested\n * `inputTokens.total` / `outputTokens.total`, no top-level total).\n */\nfunction extractTotalTokens(usage: unknown): number | undefined {\n if (typeof usage !== 'object' || usage === null) return undefined;\n const u = usage as Record<string, unknown>;\n const total = tokenCount(u.totalTokens);\n if (total !== undefined) return total;\n const input = tokenCount(u.inputTokens);\n const output = tokenCount(u.outputTokens);\n if (input === undefined && output === undefined) return undefined;\n return (input ?? 0) + (output ?? 0);\n}\n\n/**\n * A resilient language model that transparently falls back across a\n * chain of models on rate-limit and transient errors, and proactively\n * skips models that are near a known rate limit. Mirrors the spec\n * version (v2 or v3) of the models it wraps.\n */\nexport class ResilientLanguageModel {\n readonly provider = 'ai-resilient';\n\n private readonly candidates: Candidate[];\n private readonly tracker: LimitTracker;\n private readonly onFallback: ResilientOptions['onFallback'];\n private readonly onError: ResilientOptions['onError'];\n\n /**\n * Bookkeeping writes chained off the response path. buildPlan awaits\n * this so the next call still sees all prior usage before deciding\n * availability (read-your-writes), without success responses paying\n * store-write latency.\n */\n private pendingRecords: Promise<void> = Promise.resolve();\n\n constructor(options: {\n models: ModelConfig[];\n tracker: LimitTracker;\n onFallback?: ResilientOptions['onFallback'];\n onError?: ResilientOptions['onError'];\n }) {\n if (options.models.length === 0) {\n throw new Error('ai-resilient: at least one model is required');\n }\n const versions = new Set(\n options.models.map((config) => config.model.specificationVersion),\n );\n if (versions.size > 1) {\n // The outer specificationVersion drives how the AI SDK adapts call\n // options and results, so all wrapped models must speak the same\n // spec — a mixed chain would corrupt the fallback path.\n throw new Error(\n `ai-resilient: all models must implement the same specification version; got ${[...versions].sort().join(', ')}`,\n );\n }\n const seen = new Map<string, number>();\n this.candidates = options.models.map((config) => {\n const base = `${config.model.provider}:${config.model.modelId}`;\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n return { config, key: count === 0 ? base : `${base}#${count}` };\n });\n this.tracker = options.tracker;\n this.onFallback = options.onFallback;\n this.onError = options.onError;\n }\n\n /**\n * Reported dynamically from the wrapped models: hardcoding 'v2' would\n * make ai v6 wrap this model in its v2→v3 compat adapter and\n * double-convert the already-v3 results passed through unchanged.\n */\n get specificationVersion(): SpecificationVersion {\n return this.primary.specificationVersion;\n }\n\n get modelId(): string {\n return this.primary.modelId;\n }\n\n get supportedUrls(): AnyLanguageModel['supportedUrls'] {\n return this.primary.supportedUrls;\n }\n\n private get primary(): AnyLanguageModel {\n // candidates is non-empty by construction\n return this.candidates[0]!.config.model;\n }\n\n private queueRecordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: unknown,\n ): void {\n this.pendingRecords = this.pendingRecords\n .then(() => this.recordSuccess(candidate, headers, usage))\n .catch(() => {\n // Store bookkeeping failures must never surface to callers.\n });\n }\n\n /**\n * Order candidates and mark near-limit/benched ones as skipped. If\n * every model would be skipped, try all of them in configured order\n * anyway — attempting beats failing without trying.\n */\n private async buildPlan(): Promise<PlanEntry[]> {\n await this.pendingRecords;\n const plan: PlanEntry[] = [];\n for (const candidate of this.candidates) {\n const available = await this.tracker.isAvailable(\n candidate.key,\n candidate.config.limits,\n );\n plan.push({ candidate, skip: !available });\n }\n if (plan.every((entry) => entry.skip)) {\n for (const entry of plan) entry.skip = false;\n }\n return plan;\n }\n\n private fireFallbacks(pending: PendingFallback[], to: string): void {\n for (const { from, reason } of pending) {\n this.onFallback?.({ from, to, reason });\n }\n pending.length = 0;\n }\n\n /**\n * Handle a failed attempt: notify, classify, bench on rate-limit, and\n * queue the fallback notification. Rethrows fatal errors.\n */\n private async handleFailure(\n error: unknown,\n candidate: Candidate,\n attempts: ModelAttempt[],\n pending: PendingFallback[],\n ): Promise<void> {\n const modelId = candidate.config.model.modelId;\n this.onError?.(error, modelId);\n const classification = classifyError(error);\n if (classification === 'fatal') throw error;\n attempts.push({ modelId, error, classification });\n if (classification === 'rate-limit') {\n await this.tracker.recordRateLimit(candidate.key, getRetryAfterMs(error));\n }\n pending.push({ from: modelId, reason: classification });\n }\n\n private async recordSuccess(\n candidate: Candidate,\n headers: Record<string, string> | undefined,\n usage: unknown,\n ): Promise<void> {\n const totalTokens = extractTotalTokens(usage);\n await this.tracker.recordSuccess(candidate.key, {\n provider: candidate.config.model.provider,\n headers,\n totalTokens,\n limits: candidate.config.limits,\n });\n }\n\n async doGenerate(\n options: LanguageModelV2CallOptions,\n ): Promise<DoGenerateResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n\n for (const entry of plan) {\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = (await candidate.config.model.doGenerate(\n options,\n )) as DoGenerateResult;\n this.queueRecordSuccess(\n candidate,\n result.response?.headers,\n result.usage,\n );\n return result;\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n }\n\n async doStream(options: LanguageModelV2CallOptions): Promise<DoStreamResult> {\n const plan = await this.buildPlan();\n const attempts: ModelAttempt[] = [];\n const pending: PendingFallback[] = [];\n let index = 0;\n\n const acquire = async (): Promise<{\n candidate: Candidate;\n result: DoStreamResult;\n }> => {\n while (index < plan.length) {\n const entry = plan[index++]!;\n const { candidate } = entry;\n const modelId = candidate.config.model.modelId;\n if (entry.skip) {\n pending.push({ from: modelId, reason: 'proactive' });\n continue;\n }\n this.fireFallbacks(pending, modelId);\n try {\n const result = (await candidate.config.model.doStream(\n options,\n )) as DoStreamResult;\n return { candidate, result };\n } catch (error) {\n await this.handleFailure(error, candidate, attempts, pending);\n }\n }\n throw new AllModelsExhaustedError(attempts);\n };\n\n // Try candidates until one produces content (or finishes cleanly)\n // before returning, so the returned request/response metadata always\n // belongs to the model that actually serves the stream.\n for (;;) {\n const { candidate, result } = await acquire();\n const reader = result.stream.getReader();\n const prelude: LanguageModelV2StreamPart[] = [];\n let firstContent: LanguageModelV2StreamPart | undefined;\n let usage: unknown;\n let done = false;\n\n try {\n for (;;) {\n const { done: readDone, value } = await reader.read();\n if (readDone) {\n done = true;\n break;\n }\n if (value.type === 'error') {\n // Pre-content stream error: treat like a failed call so we\n // can fall back to the next model.\n throw value.error;\n }\n if (value.type === 'finish') usage = value.usage;\n if (PRELUDE_PART_TYPES.has(value.type)) {\n prelude.push(value);\n continue;\n }\n firstContent = value;\n break;\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n // Rethrows fatal errors; otherwise fall through to next candidate.\n await this.handleFailure(error, candidate, attempts, pending);\n continue;\n }\n\n // Committed: first content part arrived (or the stream finished).\n // Errors from here on propagate to the consumer unchanged.\n let preludeIndex = 0;\n const stream = new ReadableStream<LanguageModelV2StreamPart>({\n pull: async (controller) => {\n if (preludeIndex < prelude.length) {\n controller.enqueue(prelude[preludeIndex++]);\n return;\n }\n if (firstContent !== undefined) {\n const part = firstContent;\n firstContent = undefined;\n controller.enqueue(part);\n return;\n }\n if (!done) {\n const { done: readDone, value } = await reader.read();\n if (!readDone) {\n if (value.type === 'finish') usage = value.usage;\n controller.enqueue(value);\n return;\n }\n done = true;\n }\n this.queueRecordSuccess(candidate, result.response?.headers, usage);\n controller.close();\n },\n cancel: (reason) => reader.cancel(reason),\n });\n\n return {\n stream,\n ...(result.request !== undefined ? { request: result.request } : {}),\n ...(result.response !== undefined ? { response: result.response } : {}),\n };\n }\n }\n}\n","import type { Store } from '../types';\n\ninterface Entry {\n value: string;\n expiresAt?: number;\n}\n\n/**\n * In-process `Store` backed by a `Map` with lazy TTL eviction.\n * Suitable for long-running servers; use a Redis/KV adapter for\n * serverless deployments.\n */\nexport function memoryStore(): Store {\n const map = new Map<string, Entry>();\n\n function evictIfExpired(key: string): Entry | undefined {\n const entry = map.get(key);\n if (entry === undefined) return undefined;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n return {\n get(key) {\n const entry = evictIfExpired(key);\n return Promise.resolve(entry?.value ?? null);\n },\n set(key, value, ttlMs) {\n map.set(key, {\n value,\n ...(ttlMs !== undefined ? { expiresAt: Date.now() + ttlMs } : {}),\n });\n return Promise.resolve();\n },\n };\n}\n","import { LimitTracker } from './limit-tracker';\nimport { ResilientLanguageModel } from './resilient-model';\nimport { memoryStore } from './stores/memory';\nimport type {\n AnyLanguageModel,\n ResilientOptions,\n SpecificationVersion,\n} from './types';\n\nexport { AllModelsExhaustedError, type ModelAttempt } from './errors';\nexport { classifyError, getRetryAfterMs } from './classify-error';\nexport { memoryStore } from './stores/memory';\nexport { LimitTracker } from './limit-tracker';\nexport { parseRateLimitHeaders, type ParsedRateLimit } from './header-parsers';\nexport type {\n AnyLanguageModel,\n ErrorClassification,\n FallbackInfo,\n FallbackReason,\n Limits,\n ModelConfig,\n ResilientOptions,\n SpecificationVersion,\n Store,\n} from './types';\n\nconst DEFAULT_THRESHOLD = 0.1;\nconst DEFAULT_COOLDOWN_MS = 60_000;\n\n/**\n * Create a resilient language model that transparently falls back across\n * the configured models on rate-limit/transient errors and proactively\n * switches away from models near a known rate limit.\n *\n * The returned model mirrors the specification version of the models it\n * wraps — `LanguageModelV2` for ai v5 models, `LanguageModelV3` for\n * ai v6 models — so it works with `generateText`, `streamText`,\n * `generateObject`, and `streamObject` on either SDK major. Mixing spec\n * versions in one chain throws.\n */\nexport function createResilient<Version extends SpecificationVersion>(\n options: ResilientOptions<Version>,\n): AnyLanguageModel<Version> {\n const tracker = new LimitTracker({\n store: options.store ?? memoryStore(),\n threshold: options.threshold ?? DEFAULT_THRESHOLD,\n cooldown: options.cooldown ?? DEFAULT_COOLDOWN_MS,\n });\n return new ResilientLanguageModel({\n models: options.models,\n tracker,\n ...(options.onFallback !== undefined\n ? { onFallback: options.onFallback }\n : {}),\n ...(options.onError !== undefined ? { onError: options.onError } : {}),\n }) as unknown as AnyLanguageModel<Version>;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-resilient",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Smart model fallback for the Vercel AI SDK: reactive fallback on rate-limit/transient errors and proactive switching before limits are hit.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"prepublishOnly": "npm run typecheck && npm run lint && npm run format:check && npm run test && npm run build"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@ai-sdk/provider": "^2.0.0",
|
|
34
|
-
"ai": "^5.0.0"
|
|
33
|
+
"@ai-sdk/provider": "^2.0.0 || ^3.0.0",
|
|
34
|
+
"ai": "^5.0.0 || ^6.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@ai-sdk/provider": "^2.0.0",
|