@providerkit/core 0.7.1 → 0.8.1
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/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -1
- package/dist/fallback.d.ts +86 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +190 -0
- package/dist/fallback.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +2 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +3 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/factory.d.ts +2 -1
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +16 -4
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +3 -1
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +3 -1
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/responses.d.ts +2 -1
- package/dist/providers/responses.d.ts.map +1 -1
- package/dist/providers/responses.js +3 -1
- package/dist/providers/responses.js.map +1 -1
- package/dist/rate-limit.d.ts +3 -0
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +16 -0
- package/dist/rate-limit.js.map +1 -1
- package/dist/retry.d.ts +2 -0
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +13 -5
- package/dist/retry.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +5 -2
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +19 -0
- package/src/fallback.ts +254 -0
- package/src/index.ts +1 -0
- package/src/providers/anthropic.ts +4 -2
- package/src/providers/factory.ts +19 -5
- package/src/providers/gemini.ts +5 -2
- package/src/providers/openai.ts +5 -2
- package/src/providers/responses.ts +5 -2
- package/src/rate-limit.ts +22 -0
- package/src/retry.ts +14 -5
- package/src/transport.ts +5 -2
- package/src/types.ts +9 -1
package/dist/types.d.ts
CHANGED
|
@@ -112,6 +112,12 @@ export declare const EMPTY_USAGE: TokenUsage;
|
|
|
112
112
|
/** Normalized streaming chunk — every provider's shape collapses into this. */
|
|
113
113
|
export interface ProviderChunk {
|
|
114
114
|
type: "delta" | "usage" | "finish";
|
|
115
|
+
/** The selected endpoint and model when a fallback provider emits this chunk.
|
|
116
|
+
* Per-call metadata, never shared mutable state on the provider object. */
|
|
117
|
+
source?: {
|
|
118
|
+
provider: string;
|
|
119
|
+
model: string;
|
|
120
|
+
};
|
|
115
121
|
content?: string;
|
|
116
122
|
reasoning?: string;
|
|
117
123
|
/** OpenRouter's normalized reasoning payload — hand it back on the next
|
|
@@ -195,6 +201,8 @@ export interface Provider {
|
|
|
195
201
|
}
|
|
196
202
|
export interface Completion {
|
|
197
203
|
text: string;
|
|
204
|
+
/** Present when the stream identifies the endpoint that answered. */
|
|
205
|
+
provider?: string;
|
|
198
206
|
reasoning: string;
|
|
199
207
|
/** Present only where the provider sent one. See ChatMessage.reasoningDetails. */
|
|
200
208
|
reasoningDetails?: unknown[];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -37,7 +37,10 @@ export async function drainStream(stream, model) {
|
|
|
37
37
|
let reasoningDetails;
|
|
38
38
|
let usage = EMPTY_USAGE;
|
|
39
39
|
let finishReason = null;
|
|
40
|
+
let source;
|
|
40
41
|
for await (const chunk of stream) {
|
|
42
|
+
if (chunk.source)
|
|
43
|
+
source = chunk.source;
|
|
41
44
|
if (chunk.type === "delta") {
|
|
42
45
|
if (chunk.content)
|
|
43
46
|
text += chunk.content;
|
|
@@ -59,7 +62,8 @@ export async function drainStream(stream, model) {
|
|
|
59
62
|
...(reasoningDetails ? { reasoningDetails } : {}),
|
|
60
63
|
usage,
|
|
61
64
|
finishReason,
|
|
62
|
-
model,
|
|
65
|
+
model: source?.model ?? model,
|
|
66
|
+
...(source ? { provider: source.provider } : {}),
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
/**
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,0BAA0B;AAE1B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AAGzE,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AACpD,CAAC;AA4GD,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW,EAAE,CAAC;IACd,iBAAiB,EAAE,CAAC;IACpB,YAAY,EAAE,CAAC;CAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// read before the status — status alone gives the wrong advice ("retry" for an
|
|
11
11
|
// empty balance, "check your key" for a plan that never included the API).
|
|
12
12
|
|
|
13
|
+
import { parseUsageLimitBody, type RateLimitWindow } from "./rate-limit.ts";
|
|
14
|
+
|
|
13
15
|
/** What kind of failure this is, named by what actually fixes it. */
|
|
14
16
|
export type ErrorKind =
|
|
15
17
|
/** Our own cancel — the caller pressed Stop. Never retried. */
|
|
@@ -67,6 +69,9 @@ export class ProviderError extends Error {
|
|
|
67
69
|
/** Honoured when the provider said how long to wait (Retry-After, or
|
|
68
70
|
* Gemini's RetryInfo.retryDelay). */
|
|
69
71
|
readonly retryAfterMs?: number;
|
|
72
|
+
/** Absolute reset of the binding limit. Can be days beyond Retry-After. */
|
|
73
|
+
readonly resetAtMs?: number;
|
|
74
|
+
readonly window?: RateLimitWindow;
|
|
70
75
|
/** The provider's own response body, truncated — the actual reason, which is
|
|
71
76
|
* otherwise lost behind "400 status code (no body)". */
|
|
72
77
|
readonly body?: string;
|
|
@@ -79,6 +84,8 @@ export class ProviderError extends Error {
|
|
|
79
84
|
status?: number;
|
|
80
85
|
code?: string;
|
|
81
86
|
retryAfterMs?: number;
|
|
87
|
+
resetAtMs?: number;
|
|
88
|
+
window?: RateLimitWindow;
|
|
82
89
|
body?: string;
|
|
83
90
|
cause?: unknown;
|
|
84
91
|
} = {},
|
|
@@ -90,6 +97,8 @@ export class ProviderError extends Error {
|
|
|
90
97
|
this.status = opts.status;
|
|
91
98
|
this.code = opts.code;
|
|
92
99
|
this.retryAfterMs = opts.retryAfterMs;
|
|
100
|
+
this.resetAtMs = opts.resetAtMs;
|
|
101
|
+
this.window = opts.window;
|
|
93
102
|
this.body = opts.body;
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -113,6 +122,7 @@ export class ProviderError extends Error {
|
|
|
113
122
|
status,
|
|
114
123
|
code,
|
|
115
124
|
retryAfterMs: parseRetryAfterMs(err, body),
|
|
125
|
+
...parseUsageLimitBody(body),
|
|
116
126
|
body: body.slice(0, 2_000) || undefined,
|
|
117
127
|
cause: err,
|
|
118
128
|
});
|
|
@@ -411,7 +421,13 @@ export function classify(err: unknown, status?: number, body?: string): ErrorKin
|
|
|
411
421
|
* backoff shorter than the window just burns an attempt.
|
|
412
422
|
*/
|
|
413
423
|
export function parseRetryAfterMs(err: unknown, body?: string): number | undefined {
|
|
424
|
+
// Adapters already normalized the response headers. Re-parsing only the body
|
|
425
|
+
// discarded this property and made retries ignore their own Retry-After.
|
|
426
|
+
const normalized = readNumber(err, "retryAfterMs");
|
|
427
|
+
if (normalized !== undefined && Number.isFinite(normalized) && normalized >= 0) return normalized;
|
|
414
428
|
const text = body ?? bodyTextOf(err);
|
|
429
|
+
const usageLimit = parseUsageLimitBody(text);
|
|
430
|
+
if (usageLimit.retryAfterMs !== undefined) return usageLimit.retryAfterMs;
|
|
415
431
|
const delay = text.match(/"?retryDelay"?\s*[:=]\s*"?(\d+(?:\.\d+)?)s"?/i);
|
|
416
432
|
if (delay?.[1]) return Math.round(parseFloat(delay[1]) * 1000);
|
|
417
433
|
|
|
@@ -467,6 +483,7 @@ export function streamError(provider: string, error: unknown): ProviderError {
|
|
|
467
483
|
...(status !== undefined ? { status } : {}),
|
|
468
484
|
...(code ? { code } : {}),
|
|
469
485
|
retryAfterMs: parseRetryAfterMs(error, body),
|
|
486
|
+
...parseUsageLimitBody(body),
|
|
470
487
|
body: body.slice(0, 2_000),
|
|
471
488
|
},
|
|
472
489
|
);
|
|
@@ -482,6 +499,8 @@ export function describeProviderError(err: unknown): Record<string, unknown> {
|
|
|
482
499
|
status: err.status,
|
|
483
500
|
code: err.code,
|
|
484
501
|
retryAfterMs: err.retryAfterMs,
|
|
502
|
+
resetAtMs: err.resetAtMs,
|
|
503
|
+
window: err.window,
|
|
485
504
|
error: err.message,
|
|
486
505
|
body: err.body,
|
|
487
506
|
};
|
package/src/fallback.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { classify, parseRetryAfterMs, ProviderError, type ErrorKind } from "./errors.ts";
|
|
2
|
+
import type { RateLimitWindow } from "./rate-limit.ts";
|
|
3
|
+
import type { ProviderPresetId } from "./presets.ts";
|
|
4
|
+
import type { Provider, ProviderChunk } from "./types.ts";
|
|
5
|
+
import { createPresetProvider, type PresetProviderConfig } from "./providers/factory.ts";
|
|
6
|
+
|
|
7
|
+
/** Retry intervals when the server supplies no deadline, not estimates of when
|
|
8
|
+
* it will recover. A server deadline always wins over these defaults. */
|
|
9
|
+
export const DEFAULT_FALLBACK_COOLDOWNS: Readonly<Partial<Record<ErrorKind, number>>> = Object.freeze({
|
|
10
|
+
rate: 60_000,
|
|
11
|
+
quota: 3_600_000,
|
|
12
|
+
timeout: 30_000,
|
|
13
|
+
network: 30_000,
|
|
14
|
+
overload: 30_000,
|
|
15
|
+
auth: 3_600_000,
|
|
16
|
+
entitlement: 3_600_000,
|
|
17
|
+
model: 3_600_000,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export interface FallbackOptions<T> {
|
|
21
|
+
/** Per-kind intervals in ms; null stops fallback for that kind. Adding a kind
|
|
22
|
+
* opts it into fallback. Cancellation is never overridable. */
|
|
23
|
+
cooldownMs?: Partial<Record<ErrorKind, number | null>>;
|
|
24
|
+
onCooldown?: (info: {
|
|
25
|
+
candidate: T;
|
|
26
|
+
error: unknown;
|
|
27
|
+
kind: ErrorKind;
|
|
28
|
+
retryAtMs: number;
|
|
29
|
+
window?: RateLimitWindow;
|
|
30
|
+
}) => void;
|
|
31
|
+
/** Inject a clock for deterministic tests. */
|
|
32
|
+
now?: () => number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A fallback named by PRESET id rather than hand-built: the endpoint, auth
|
|
37
|
+
* style, effort dialect and every measured quirk come from the presets table,
|
|
38
|
+
* so a consumer writes neither a baseUrl nor a model-spelling rule. A
|
|
39
|
+
* hand-built `Provider` is still accepted wherever a spec is.
|
|
40
|
+
*/
|
|
41
|
+
export interface FallbackSpec extends PresetProviderConfig {
|
|
42
|
+
preset: ProviderPresetId;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type FallbackCandidate = Provider | FallbackSpec;
|
|
46
|
+
|
|
47
|
+
function resolveFallback(candidate: FallbackCandidate): Provider {
|
|
48
|
+
if (typeof candidate === "object" && candidate !== null && "createStream" in candidate) {
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
51
|
+
const { preset, ...config } = candidate;
|
|
52
|
+
return createPresetProvider(preset, config);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Standard options that any provider config can accept to configure fallbacks inline. */
|
|
56
|
+
export interface ProviderFallbackConfig {
|
|
57
|
+
/** Secondary and tertiary providers to try if this provider fails/exhausts:
|
|
58
|
+
* hand-built `Provider`s, preset-id `FallbackSpec`s, or both. */
|
|
59
|
+
fallbacks?: readonly FallbackCandidate[];
|
|
60
|
+
/** Custom cooldown or telemetry options for the fallback pool. */
|
|
61
|
+
fallbackOptions?: FallbackOptions<Provider>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Wrap a primary provider with its fallbacks if any are specified.
|
|
66
|
+
* Returns the primary provider untouched when fallbacks is empty or omitted.
|
|
67
|
+
*/
|
|
68
|
+
export function withConfiguredFallbacks(
|
|
69
|
+
primary: Provider,
|
|
70
|
+
config?: ProviderFallbackConfig,
|
|
71
|
+
): Provider {
|
|
72
|
+
if (!config?.fallbacks || config.fallbacks.length === 0) return primary;
|
|
73
|
+
return withFallbackProviders(
|
|
74
|
+
[primary, ...config.fallbacks.map(resolveFallback)],
|
|
75
|
+
config.fallbackOptions,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface Cooldown {
|
|
80
|
+
retryAtMs: number;
|
|
81
|
+
kind: ErrorKind;
|
|
82
|
+
window?: RateLimitWindow;
|
|
83
|
+
probing: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The whole chain is unavailable. Fail promptly instead of holding a request
|
|
87
|
+
* open for hours. `retryAtMs` names its next opportunity, not a sleep to cap. */
|
|
88
|
+
export class NoAvailableProviderError extends ProviderError {
|
|
89
|
+
readonly retryAtMs: number;
|
|
90
|
+
|
|
91
|
+
constructor(retryAtMs: number, now: number) {
|
|
92
|
+
super("fallback", "rate", "All configured providers are unavailable. Retry after the cooldown.", {
|
|
93
|
+
resetAtMs: retryAtMs,
|
|
94
|
+
retryAfterMs: Math.max(0, retryAtMs - now),
|
|
95
|
+
});
|
|
96
|
+
this.name = "NoAvailableProviderError";
|
|
97
|
+
this.retryAtMs = retryAtMs;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Ordered candidates with cooldowns shared across calls. Keep one pool alive
|
|
102
|
+
* per credential set: rebuilding it per request forgets a weekly lockout.
|
|
103
|
+
* State is in-memory and process-local; `status` and `reset` expose it without
|
|
104
|
+
* a background timer, vendor SDK, database or global singleton. */
|
|
105
|
+
export class FallbackPool<T> {
|
|
106
|
+
private readonly candidates: readonly T[];
|
|
107
|
+
private readonly cooldowns = new Map<T, Cooldown>();
|
|
108
|
+
private readonly now: () => number;
|
|
109
|
+
|
|
110
|
+
constructor(candidates: readonly T[], private readonly options: FallbackOptions<T> = {}) {
|
|
111
|
+
if (candidates.length === 0) throw new Error("providerkit: supply a primary before its backups");
|
|
112
|
+
if (new Set(candidates).size !== candidates.length) {
|
|
113
|
+
throw new Error("providerkit: each fallback candidate must appear only once");
|
|
114
|
+
}
|
|
115
|
+
for (const value of Object.values(options.cooldownMs ?? {})) {
|
|
116
|
+
if (value !== null && value !== undefined && (!Number.isFinite(value) || value < 0)) {
|
|
117
|
+
throw new Error("providerkit: fallback cooldowns must be finite, non-negative milliseconds");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.candidates = [...candidates];
|
|
121
|
+
this.now = options.now ?? Date.now;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Clear a cooldown after replacing a key or fixing configuration. Omit the
|
|
125
|
+
* candidate to clear all. Does not interrupt calls already in flight. */
|
|
126
|
+
reset(...selection: [] | [T]): void {
|
|
127
|
+
if (selection.length === 0) this.cooldowns.clear();
|
|
128
|
+
else this.cooldowns.delete(selection[0]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
status(): Array<{ candidate: T; retryAtMs: number; probing: boolean; kind?: ErrorKind; window?: RateLimitWindow }> {
|
|
132
|
+
return this.candidates.map((candidate) => ({
|
|
133
|
+
candidate,
|
|
134
|
+
retryAtMs: 0,
|
|
135
|
+
probing: false,
|
|
136
|
+
...this.cooldowns.get(candidate),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Buffered calls only: do not wrap an agent turn that has run side effects. */
|
|
141
|
+
async with<R>(attempt: (candidate: T, signal: AbortSignal) => Promise<R>, signal?: AbortSignal): Promise<R> {
|
|
142
|
+
for await (const result of this.stream(async function* (candidate, inner) {
|
|
143
|
+
yield await attempt(candidate, inner);
|
|
144
|
+
}, signal)) return result;
|
|
145
|
+
throw new Error("providerkit: fallback attempt ended without a result");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Only a pre-output failure can switch provider. A later failure still
|
|
149
|
+
* cools the endpoint for FUTURE calls but never restarts the current answer. */
|
|
150
|
+
async *stream<R>(
|
|
151
|
+
attempt: (candidate: T, signal: AbortSignal) => AsyncIterable<R>,
|
|
152
|
+
signal?: AbortSignal,
|
|
153
|
+
): AsyncGenerator<R> {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
let hasFailed = false;
|
|
156
|
+
let lastError: unknown;
|
|
157
|
+
for (const candidate of this.candidates) {
|
|
158
|
+
signal?.throwIfAborted();
|
|
159
|
+
const previous = this.cooldowns.get(candidate);
|
|
160
|
+
if (previous && (previous.retryAtMs > this.now() || previous.probing)) continue;
|
|
161
|
+
// One recovery probe after expiry. Healthy endpoints still allow normal
|
|
162
|
+
// concurrency; only a known failed endpoint gets a single probe.
|
|
163
|
+
if (previous) previous.probing = true;
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const inner = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
166
|
+
let emitted = false;
|
|
167
|
+
let attemptFailed = false;
|
|
168
|
+
const startedWithCooldown = previous !== undefined;
|
|
169
|
+
try {
|
|
170
|
+
for await (const chunk of attempt(candidate, inner)) {
|
|
171
|
+
inner.throwIfAborted();
|
|
172
|
+
emitted = true;
|
|
173
|
+
// Only a call that was admitted as a recovery probe (started while on cooldown)
|
|
174
|
+
// is authorized to clear that cooldown upon succeeding.
|
|
175
|
+
if (startedWithCooldown && this.cooldowns.get(candidate) === previous) {
|
|
176
|
+
this.cooldowns.delete(candidate);
|
|
177
|
+
}
|
|
178
|
+
yield chunk;
|
|
179
|
+
}
|
|
180
|
+
inner.throwIfAborted();
|
|
181
|
+
if (startedWithCooldown && this.cooldowns.get(candidate) === previous) {
|
|
182
|
+
this.cooldowns.delete(candidate);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
signal?.throwIfAborted();
|
|
187
|
+
const kind = classify(error);
|
|
188
|
+
if (kind === "aborted") throw error;
|
|
189
|
+
const configured = this.options.cooldownMs?.[kind];
|
|
190
|
+
const interval = configured === undefined ? DEFAULT_FALLBACK_COOLDOWNS[kind] : configured;
|
|
191
|
+
if (interval === undefined || interval === null) throw error;
|
|
192
|
+
const now = this.now();
|
|
193
|
+
const after = parseRetryAfterMs(error);
|
|
194
|
+
const reset = error instanceof ProviderError ? error.resetAtMs : undefined;
|
|
195
|
+
const hints = [reset, after === undefined ? undefined : now + after].filter(
|
|
196
|
+
(value): value is number => value !== undefined && Number.isFinite(value),
|
|
197
|
+
);
|
|
198
|
+
const retryAtMs = Math.max(
|
|
199
|
+
now,
|
|
200
|
+
...(hints.length ? hints : [now + interval]),
|
|
201
|
+
this.cooldowns.get(candidate)?.retryAtMs ?? 0,
|
|
202
|
+
);
|
|
203
|
+
const window = error instanceof ProviderError ? error.window : undefined;
|
|
204
|
+
this.cooldowns.set(candidate, { retryAtMs, kind, window, probing: false });
|
|
205
|
+
this.options.onCooldown?.({ candidate, error, kind, retryAtMs, window });
|
|
206
|
+
attemptFailed = true;
|
|
207
|
+
hasFailed = true;
|
|
208
|
+
lastError = error;
|
|
209
|
+
if (emitted) throw error;
|
|
210
|
+
} finally {
|
|
211
|
+
controller.abort();
|
|
212
|
+
const current = this.cooldowns.get(candidate);
|
|
213
|
+
if (current && current.probing && !attemptFailed) {
|
|
214
|
+
current.probing = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (hasFailed) throw lastError;
|
|
219
|
+
// If all expired candidates are already being probed, the next opportunity
|
|
220
|
+
// is now. Other callers need not wait for those probes or start duplicates.
|
|
221
|
+
const retryAtMs = Math.min(...this.status().map((entry) => Math.max(this.now(), entry.retryAtMs)));
|
|
222
|
+
throw new NoAvailableProviderError(retryAtMs, this.now());
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface FallbackProvider extends Provider {
|
|
227
|
+
readonly fallbacks: FallbackPool<Provider>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Compose bound provider/model pairs. Identity names the configured primary;
|
|
231
|
+
* chunk.source names the endpoint that answered. Per-call model overrides
|
|
232
|
+
* apply ONLY to the primary because model ids are endpoint-specific.
|
|
233
|
+
* Keep this provider alive across calls so its cooldowns survive. */
|
|
234
|
+
export function withFallbackProviders(
|
|
235
|
+
providers: readonly Provider[],
|
|
236
|
+
options: FallbackOptions<Provider> = {},
|
|
237
|
+
): FallbackProvider {
|
|
238
|
+
const fallbacks = new FallbackPool(providers, options);
|
|
239
|
+
const primary = providers[0]!;
|
|
240
|
+
return {
|
|
241
|
+
id: primary.id,
|
|
242
|
+
model: primary.model,
|
|
243
|
+
fallbacks,
|
|
244
|
+
createStream(messages, tools, opts = {}) {
|
|
245
|
+
return fallbacks.stream(async function* (provider, signal): AsyncGenerator<ProviderChunk> {
|
|
246
|
+
const { model: override, ...rest } = opts;
|
|
247
|
+
const model = provider === primary ? (override ?? provider.model) : provider.model;
|
|
248
|
+
for await (const chunk of provider.createStream(messages, tools, { ...rest, model, signal })) {
|
|
249
|
+
yield { ...chunk, source: chunk.source ?? { provider: provider.id, model } };
|
|
250
|
+
}
|
|
251
|
+
}, opts.signal);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { streamError } from "../errors.ts";
|
|
|
3
3
|
import { schemaPrompt } from "../schema.ts";
|
|
4
4
|
import { parseToolArgs } from "../tool-args.ts";
|
|
5
5
|
import { streamSse, apiUrl } from "../transport.ts";
|
|
6
|
+
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
6
7
|
import type {
|
|
7
8
|
JsonOutput,
|
|
8
9
|
ChatMessage,
|
|
@@ -15,7 +16,7 @@ import type {
|
|
|
15
16
|
ToolDefinition,
|
|
16
17
|
} from "../types.ts";
|
|
17
18
|
|
|
18
|
-
export interface AnthropicConfig {
|
|
19
|
+
export interface AnthropicConfig extends ProviderFallbackConfig {
|
|
19
20
|
apiKey: string;
|
|
20
21
|
model: string;
|
|
21
22
|
/** Any endpoint speaking the Anthropic Messages dialect — a proxy or gateway.
|
|
@@ -219,7 +220,7 @@ export function createAnthropicProvider(config: AnthropicConfig): Provider {
|
|
|
219
220
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
220
221
|
const id = config.id ?? "anthropic";
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
const provider: Provider = {
|
|
223
224
|
id,
|
|
224
225
|
model: config.model,
|
|
225
226
|
|
|
@@ -382,4 +383,5 @@ export function createAnthropicProvider(config: AnthropicConfig): Provider {
|
|
|
382
383
|
}
|
|
383
384
|
},
|
|
384
385
|
};
|
|
386
|
+
return withConfiguredFallbacks(provider, config);
|
|
385
387
|
}
|
package/src/providers/factory.ts
CHANGED
|
@@ -21,8 +21,9 @@ import {
|
|
|
21
21
|
type ProviderPresetId,
|
|
22
22
|
} from "../presets.ts";
|
|
23
23
|
import type { Effort, Provider } from "../types.ts";
|
|
24
|
+
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
24
25
|
|
|
25
|
-
export interface PresetProviderConfig {
|
|
26
|
+
export interface PresetProviderConfig extends ProviderFallbackConfig {
|
|
26
27
|
/** The credential: an API key for `key`/`bearer` presets, an ACCESS TOKEN
|
|
27
28
|
* for `oauth` ones. Acquiring the token is the caller's job. */
|
|
28
29
|
apiKey: string;
|
|
@@ -58,11 +59,15 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
58
59
|
}
|
|
59
60
|
const headers = { ...preset.headers, ...config.headers };
|
|
60
61
|
|
|
62
|
+
const { fallbacks: _fallbacks, fallbackOptions: _fallbackOptions, ...baseConfig } = config;
|
|
63
|
+
|
|
64
|
+
let provider: Provider;
|
|
61
65
|
switch (preset.shape) {
|
|
62
66
|
case "anthropic":
|
|
63
67
|
// `key` rides Anthropic's native x-api-key; coding plans and OAuth
|
|
64
68
|
// tokens read Bearer (measured across the family's coding endpoints).
|
|
65
|
-
|
|
69
|
+
provider = createAnthropicProvider({
|
|
70
|
+
...baseConfig,
|
|
66
71
|
apiKey: config.apiKey,
|
|
67
72
|
model,
|
|
68
73
|
effort: config.effort,
|
|
@@ -76,8 +81,10 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
76
81
|
...(preset.explicitNone ? { explicitNone: true } : {}),
|
|
77
82
|
fetchImpl: config.fetchImpl,
|
|
78
83
|
});
|
|
84
|
+
break;
|
|
79
85
|
case "openai":
|
|
80
|
-
|
|
86
|
+
provider = createOpenAIProvider({
|
|
87
|
+
...baseConfig,
|
|
81
88
|
id,
|
|
82
89
|
apiKey: config.apiKey,
|
|
83
90
|
model,
|
|
@@ -88,8 +95,10 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
88
95
|
...(config.providerOrder ? { providerOrder: config.providerOrder } : {}),
|
|
89
96
|
fetchImpl: config.fetchImpl,
|
|
90
97
|
});
|
|
98
|
+
break;
|
|
91
99
|
case "responses":
|
|
92
|
-
|
|
100
|
+
provider = createResponsesProvider({
|
|
101
|
+
...baseConfig,
|
|
93
102
|
apiKey: config.apiKey,
|
|
94
103
|
model,
|
|
95
104
|
effort: config.effort,
|
|
@@ -99,8 +108,10 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
99
108
|
headers,
|
|
100
109
|
fetchImpl: config.fetchImpl,
|
|
101
110
|
});
|
|
111
|
+
break;
|
|
102
112
|
case "gemini":
|
|
103
|
-
|
|
113
|
+
provider = createGeminiProvider({
|
|
114
|
+
...baseConfig,
|
|
104
115
|
apiKey: config.apiKey,
|
|
105
116
|
model,
|
|
106
117
|
effort: config.effort,
|
|
@@ -109,5 +120,8 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
109
120
|
headers,
|
|
110
121
|
fetchImpl: config.fetchImpl,
|
|
111
122
|
});
|
|
123
|
+
break;
|
|
112
124
|
}
|
|
125
|
+
|
|
126
|
+
return withConfiguredFallbacks(provider, config);
|
|
113
127
|
}
|
package/src/providers/gemini.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { streamError } from "../errors.ts";
|
|
|
9
9
|
import { schemaPrompt } from "../schema.ts";
|
|
10
10
|
import { parseToolArgs } from "../tool-args.ts";
|
|
11
11
|
import { streamSse, apiUrl } from "../transport.ts";
|
|
12
|
+
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
12
13
|
import type {
|
|
13
14
|
ChatMessage,
|
|
14
15
|
Effort,
|
|
@@ -22,7 +23,7 @@ import type {
|
|
|
22
23
|
ToolDefinition,
|
|
23
24
|
} from "../types.ts";
|
|
24
25
|
|
|
25
|
-
export interface GeminiConfig {
|
|
26
|
+
export interface GeminiConfig extends ProviderFallbackConfig {
|
|
26
27
|
apiKey: string;
|
|
27
28
|
model: string;
|
|
28
29
|
/** Any endpoint speaking the Generative Language REST dialect — a proxy or
|
|
@@ -247,7 +248,7 @@ export function createGeminiProvider(config: GeminiConfig): Provider {
|
|
|
247
248
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
248
249
|
const id = config.id ?? "gemini";
|
|
249
250
|
|
|
250
|
-
|
|
251
|
+
const provider: Provider = {
|
|
251
252
|
id,
|
|
252
253
|
model: config.model,
|
|
253
254
|
|
|
@@ -409,4 +410,6 @@ export function createGeminiProvider(config: GeminiConfig): Provider {
|
|
|
409
410
|
}
|
|
410
411
|
},
|
|
411
412
|
};
|
|
413
|
+
|
|
414
|
+
return withConfiguredFallbacks(provider, config);
|
|
412
415
|
}
|
package/src/providers/openai.ts
CHANGED
|
@@ -18,8 +18,9 @@ import type {
|
|
|
18
18
|
} from "../types.ts";
|
|
19
19
|
import { toDataUri } from "../types.ts";
|
|
20
20
|
import { isStrictSchema, schemaPrompt } from "../schema.ts";
|
|
21
|
+
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
21
22
|
|
|
22
|
-
export interface OpenAIConfig {
|
|
23
|
+
export interface OpenAIConfig extends ProviderFallbackConfig {
|
|
23
24
|
apiKey: string;
|
|
24
25
|
model: string;
|
|
25
26
|
/** Any OpenAI-compatible endpoint. Defaults to OpenAI itself. */
|
|
@@ -305,7 +306,7 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
|
|
|
305
306
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
306
307
|
const id = config.id ?? "openai";
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
const provider: Provider = {
|
|
309
310
|
id,
|
|
310
311
|
model: config.model,
|
|
311
312
|
|
|
@@ -489,4 +490,6 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
|
|
|
489
490
|
}
|
|
490
491
|
},
|
|
491
492
|
};
|
|
493
|
+
|
|
494
|
+
return withConfiguredFallbacks(provider, config);
|
|
492
495
|
}
|
|
@@ -25,8 +25,9 @@ import type {
|
|
|
25
25
|
} from "../types.ts";
|
|
26
26
|
import { toDataUri } from "../types.ts";
|
|
27
27
|
import { isStrictSchema } from "../schema.ts";
|
|
28
|
+
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
28
29
|
|
|
29
|
-
export interface ResponsesConfig {
|
|
30
|
+
export interface ResponsesConfig extends ProviderFallbackConfig {
|
|
30
31
|
apiKey: string;
|
|
31
32
|
model: string;
|
|
32
33
|
/** Any endpoint speaking the Responses format. Defaults to OpenAI itself. */
|
|
@@ -226,7 +227,7 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
226
227
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
227
228
|
const id = config.id ?? "openai-responses";
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
const provider: Provider = {
|
|
230
231
|
id,
|
|
231
232
|
model: config.model,
|
|
232
233
|
|
|
@@ -463,4 +464,6 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
463
464
|
// included, was already yielded as it arrived.
|
|
464
465
|
},
|
|
465
466
|
};
|
|
467
|
+
|
|
468
|
+
return withConfiguredFallbacks(provider, config);
|
|
466
469
|
}
|
package/src/rate-limit.ts
CHANGED
|
@@ -215,3 +215,25 @@ export function parseUsageLimitBody(bodyText: string, now = Date.now()): RateLim
|
|
|
215
215
|
}
|
|
216
216
|
return result;
|
|
217
217
|
}
|
|
218
|
+
|
|
219
|
+
/** Read both places an endpoint can report its reset. A short Retry-After is
|
|
220
|
+
* not evidence that a longer subscription window has lifted. */
|
|
221
|
+
export function parseRateLimitResponse(
|
|
222
|
+
headers: Headers,
|
|
223
|
+
bodyText: string,
|
|
224
|
+
now = Date.now(),
|
|
225
|
+
): RateLimitReset {
|
|
226
|
+
const header = parseRateLimitReset(headers, now);
|
|
227
|
+
const body = parseUsageLimitBody(bodyText, now);
|
|
228
|
+
const resetAtMs = Math.max(header.resetAtMs ?? 0, body.resetAtMs ?? 0);
|
|
229
|
+
const retryAfterMs = header.retryAfterMs ?? body.retryAfterMs;
|
|
230
|
+
const window =
|
|
231
|
+
body.resetAtMs !== undefined && body.resetAtMs > (header.resetAtMs ?? 0)
|
|
232
|
+
? body.window
|
|
233
|
+
: (header.window ?? body.window);
|
|
234
|
+
return {
|
|
235
|
+
...(resetAtMs > 0 ? { resetAtMs } : {}),
|
|
236
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
237
|
+
...(window ? { window } : {}),
|
|
238
|
+
};
|
|
239
|
+
}
|