llm-retry-kit 0.2.1 → 0.4.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 +274 -5
- package/dist/circuit-breaker.d.ts +17 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +63 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/global-budget.d.ts +13 -0
- package/dist/global-budget.d.ts.map +1 -0
- package/dist/global-budget.js +54 -0
- package/dist/global-budget.js.map +1 -0
- package/dist/hedging.d.ts +17 -0
- package/dist/hedging.d.ts.map +1 -0
- package/dist/hedging.js +105 -0
- package/dist/hedging.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/retry.d.ts +4 -0
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +281 -20
- package/dist/retry.js.map +1 -1
- package/dist/stream.d.ts +3 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +420 -0
- package/dist/stream.js.map +1 -0
- package/dist/types.d.ts +139 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
Small resilience layer for production LLM calls. `llm-retry-kit` gives you
|
|
11
11
|
provider-aware retries, fallback chains, jittered exponential backoff,
|
|
12
|
-
`Retry-After` handling,
|
|
13
|
-
|
|
12
|
+
`Retry-After` handling, streaming retries, circuit breakers, adaptive hedged
|
|
13
|
+
requests, rolling budget windows, cancellation, timeouts, and observability
|
|
14
|
+
hooks without runtime dependencies.
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
npm install llm-retry-kit
|
|
@@ -42,6 +43,15 @@ and `llm-retry-kit` manages the reliability policy around it.
|
|
|
42
43
|
`shouldFallback`.
|
|
43
44
|
- Track token usage and estimated cost.
|
|
44
45
|
- Use custom input/output token pricing through `costCalculator`.
|
|
46
|
+
- Wrap streaming responses with retry-before-first-chunk safety.
|
|
47
|
+
- Track partial stream token usage from provider events.
|
|
48
|
+
- Skip unhealthy providers with `CircuitBreaker`.
|
|
49
|
+
- Set timeout budgets per provider/model.
|
|
50
|
+
- Start hedged requests to reduce tail latency.
|
|
51
|
+
- Adapt hedge delays from recent provider latency with `AdaptiveHedgeDelay`.
|
|
52
|
+
- Enforce rolling cost windows with `GlobalBudgetTracker`.
|
|
53
|
+
- Await async stream chunk hooks while protecting the stream from hook errors.
|
|
54
|
+
- Pass request `meta` and `payload` through every context for logging.
|
|
45
55
|
- Abort long calls and retry sleeps with `AbortSignal` or `timeoutMs`.
|
|
46
56
|
- Observe attempts, retries, success, failure, and budget events.
|
|
47
57
|
- Strict TypeScript types.
|
|
@@ -169,6 +179,193 @@ console.log({
|
|
|
169
179
|
})
|
|
170
180
|
```
|
|
171
181
|
|
|
182
|
+
## Streaming
|
|
183
|
+
|
|
184
|
+
OpenAI and Anthropic both expose streaming APIs, but their event formats and
|
|
185
|
+
resume behavior are provider-specific. `llm-retry-kit` therefore keeps the
|
|
186
|
+
stream wrapper provider-agnostic and conservative:
|
|
187
|
+
|
|
188
|
+
- By default, it retries only if the stream fails before the first chunk.
|
|
189
|
+
- After a chunk has been yielded, retrying could duplicate output, so it stops
|
|
190
|
+
unless you explicitly set `retryMode: 'always'`.
|
|
191
|
+
- Token usage can be tracked from stream events with `getChunkUsage`.
|
|
192
|
+
- Use `chunkUsageMode: 'cumulative'` for providers that send cumulative usage
|
|
193
|
+
snapshots during a stream.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { llmRetryStream } from 'llm-retry-kit'
|
|
197
|
+
|
|
198
|
+
const result = llmRetryStream({
|
|
199
|
+
stream: async ({ signal }) => {
|
|
200
|
+
const stream = await openai.responses.create({
|
|
201
|
+
model: 'gpt-4o-mini',
|
|
202
|
+
input: 'Write a short incident summary.',
|
|
203
|
+
stream: true,
|
|
204
|
+
}, { signal })
|
|
205
|
+
|
|
206
|
+
return stream
|
|
207
|
+
},
|
|
208
|
+
retryMode: 'before-first-chunk',
|
|
209
|
+
getChunkUsage: (event) => {
|
|
210
|
+
if (!('usage' in event) || !event.usage) return undefined
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
promptTokens: event.usage.input_tokens ?? 0,
|
|
214
|
+
completionTokens: event.usage.output_tokens ?? 0,
|
|
215
|
+
totalTokens: event.usage.total_tokens ?? 0,
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
chunkUsageMode: 'cumulative',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
for await (const event of result.stream) {
|
|
222
|
+
// Send provider events to your UI, parser, or SSE response.
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(result.getStats())
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Advanced Production Controls
|
|
229
|
+
|
|
230
|
+
### Circuit Breaker
|
|
231
|
+
|
|
232
|
+
Keep one `CircuitBreaker` instance per provider/model at application scope. Do
|
|
233
|
+
not create the breaker inline inside a request handler; its state must survive
|
|
234
|
+
between calls. If the failure threshold is reached inside the time window,
|
|
235
|
+
later calls skip that provider until the cooldown expires.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { CircuitBreaker, llmRetry } from 'llm-retry-kit'
|
|
239
|
+
|
|
240
|
+
const openaiBreaker = new CircuitBreaker({
|
|
241
|
+
failureThreshold: 5,
|
|
242
|
+
windowMs: 60_000,
|
|
243
|
+
cooldownMs: 120_000,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
await llmRetry({
|
|
247
|
+
providers: [
|
|
248
|
+
{
|
|
249
|
+
name: 'openai:gpt-4o-mini',
|
|
250
|
+
fn: callOpenAI,
|
|
251
|
+
circuitBreaker: openaiBreaker,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'anthropic:claude-sonnet',
|
|
255
|
+
fn: callAnthropic,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Per-Provider Timeout
|
|
262
|
+
|
|
263
|
+
Use global `timeoutMs` for the whole workflow and provider `timeoutMs` for a
|
|
264
|
+
single attempt.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
await llmRetry({
|
|
268
|
+
providers: [
|
|
269
|
+
{ name: 'openai:fast', fn: callOpenAI, timeoutMs: 3_000, maxRetries: 1 },
|
|
270
|
+
{ name: 'anthropic:steady', fn: callAnthropic, timeoutMs: 10_000 },
|
|
271
|
+
],
|
|
272
|
+
timeoutMs: 30_000,
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Hedged Requests
|
|
277
|
+
|
|
278
|
+
Hedging starts the next provider in parallel if the current provider has not
|
|
279
|
+
answered after `hedgeDelayMs`. The first successful response wins and the
|
|
280
|
+
slower request is aborted through the context signal.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
await llmRetry({
|
|
284
|
+
providers: [
|
|
285
|
+
{ name: 'primary', fn: callPrimary },
|
|
286
|
+
{ name: 'hedge', fn: callBackup },
|
|
287
|
+
],
|
|
288
|
+
hedgeDelayMs: 750,
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Hedging is best for latency-sensitive read paths. It can increase provider
|
|
293
|
+
traffic, so pair it with budget tracking and conservative delay values.
|
|
294
|
+
|
|
295
|
+
### Adaptive Hedging
|
|
296
|
+
|
|
297
|
+
Use `AdaptiveHedgeDelay` when fixed hedge delays are too brittle. It records
|
|
298
|
+
recent latency samples per provider and uses the configured percentile as the
|
|
299
|
+
next hedge delay. Keep the instance at application scope so the latency history
|
|
300
|
+
survives between requests.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { AdaptiveHedgeDelay, llmRetry } from 'llm-retry-kit'
|
|
304
|
+
|
|
305
|
+
const adaptiveHedge = new AdaptiveHedgeDelay({
|
|
306
|
+
sampleSize: 100,
|
|
307
|
+
percentile: 0.95,
|
|
308
|
+
minSamples: 10,
|
|
309
|
+
minDelayMs: 250,
|
|
310
|
+
maxDelayMs: 5_000,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
await llmRetry({
|
|
314
|
+
providers: [
|
|
315
|
+
{ name: 'openai:gpt-4o-mini', fn: callOpenAI },
|
|
316
|
+
{ name: 'anthropic:claude-sonnet', fn: callAnthropic },
|
|
317
|
+
],
|
|
318
|
+
hedgeDelayStrategy: adaptiveHedge,
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
If there are not enough samples yet, no hedge is fired unless you set
|
|
323
|
+
`defaultDelayMs`.
|
|
324
|
+
|
|
325
|
+
### Rolling Global Budget
|
|
326
|
+
|
|
327
|
+
`maxCostUSD` limits a single retry workflow. `GlobalBudgetTracker` limits the
|
|
328
|
+
total spend across many calls inside a rolling time window. Keep one instance at
|
|
329
|
+
application scope.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { GlobalBudgetTracker, llmRetry } from 'llm-retry-kit'
|
|
333
|
+
|
|
334
|
+
const globalBudget = new GlobalBudgetTracker({
|
|
335
|
+
maxCostUSD: 5,
|
|
336
|
+
windowMs: 60_000,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
await llmRetry({
|
|
340
|
+
fn: callModel,
|
|
341
|
+
globalBudget,
|
|
342
|
+
costCalculator: calculateRealProviderCost,
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
When the rolling window is exhausted, new attempts fail before the provider is
|
|
347
|
+
called. In-flight non-streaming calls can still finish because final usage is
|
|
348
|
+
known only after the provider returns. Streaming calls are checked as chunk
|
|
349
|
+
usage is reported.
|
|
350
|
+
|
|
351
|
+
### Metadata And Payload Tracking
|
|
352
|
+
|
|
353
|
+
Attach request metadata once and it flows into provider calls and hooks.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
await llmRetry({
|
|
357
|
+
fn: callModel,
|
|
358
|
+
meta: { requestId: 'req_123', tenant: 'acme' },
|
|
359
|
+
payload: { prompt: 'Classify this ticket', userId: 'user_42' },
|
|
360
|
+
onAttempt: (context) => {
|
|
361
|
+
console.log(context.meta, context.payload)
|
|
362
|
+
},
|
|
363
|
+
onFailure: (error, context) => {
|
|
364
|
+
console.error(context.meta, error)
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
|
|
172
369
|
## Simple Fallback API
|
|
173
370
|
|
|
174
371
|
For smaller apps, `fn` plus `fallback` is still supported.
|
|
@@ -298,8 +495,8 @@ await llmRetry({
|
|
|
298
495
|
onSuccess: (context) => {
|
|
299
496
|
console.log(`Cost so far: $${context.totalCostUSD}`)
|
|
300
497
|
},
|
|
301
|
-
onFailure: (error) => {
|
|
302
|
-
console.error(error)
|
|
498
|
+
onFailure: (error, context) => {
|
|
499
|
+
console.error(context.meta, error)
|
|
303
500
|
},
|
|
304
501
|
})
|
|
305
502
|
```
|
|
@@ -315,18 +512,23 @@ await llmRetry({
|
|
|
315
512
|
| `providers` | `RetryProvider<T>[]` | optional | Explicit provider/model chain. |
|
|
316
513
|
| `maxRetries` | `number` | `3` | Retries after the first attempt. |
|
|
317
514
|
| `maxCostUSD` | `number` | optional | Maximum tracked cost before later attempts stop. |
|
|
515
|
+
| `globalBudget` | `GlobalBudgetTracker` | optional | Shared rolling cost budget across calls. |
|
|
318
516
|
| `costPer1kTokens` | `number` | `0.002` | Simple cost estimate. |
|
|
319
517
|
| `costCalculator` | `(usage, context) => number` | optional | Custom cost calculation. |
|
|
320
518
|
| `initialDelayMs` | `number` | `1000` | Initial retry delay. |
|
|
321
519
|
| `maxDelayMs` | `number` | `30000` | Maximum retry delay. |
|
|
322
520
|
| `timeoutMs` | `number` | optional | Abort wrapper after this time. |
|
|
521
|
+
| `hedgeDelayMs` | `number` | optional | Start the next provider after this delay if the current provider is still pending. |
|
|
522
|
+
| `hedgeDelayStrategy` | `AdaptiveHedgeDelay` | optional | Compute hedge delay from recent provider latency. |
|
|
323
523
|
| `signal` | `AbortSignal` | optional | External cancellation signal. |
|
|
524
|
+
| `meta` | `unknown` | optional | User metadata copied into attempt/failure contexts. |
|
|
525
|
+
| `payload` | `unknown` | optional | Request payload copied into attempt/failure contexts. |
|
|
324
526
|
| `shouldRetry` | `(error, context) => boolean \| Promise<boolean>` | optional | Override retry decisions. |
|
|
325
527
|
| `shouldFallback` | `(error, context) => boolean \| Promise<boolean>` | optional | Override provider fallback decisions. |
|
|
326
528
|
| `onAttempt` | `(context) => void` | optional | Called before each attempt. |
|
|
327
529
|
| `onRetry` | `(attempt, error, delayMs, context) => void` | optional | Called before retry wait. |
|
|
328
530
|
| `onSuccess` | `(context) => void` | optional | Called after a successful response. |
|
|
329
|
-
| `onFailure` | `(error) => void` | optional | Called before final failure is thrown. |
|
|
531
|
+
| `onFailure` | `(error, context) => void` | optional | Called before final failure is thrown. |
|
|
330
532
|
| `onBudgetExceeded` | `(spentUSD, limitUSD) => void` | optional | Called when budget is exhausted. |
|
|
331
533
|
|
|
332
534
|
### `RetryProvider<T>`
|
|
@@ -336,11 +538,76 @@ await llmRetry({
|
|
|
336
538
|
name: string
|
|
337
539
|
fn: (context: RetryAttemptContext) => Promise<LLMResponse<T>>
|
|
338
540
|
maxRetries?: number
|
|
541
|
+
timeoutMs?: number
|
|
542
|
+
hedgeDelayMs?: number
|
|
543
|
+
hedgeDelayStrategy?: AdaptiveHedgeDelay
|
|
544
|
+
circuitBreaker?: CircuitBreaker
|
|
339
545
|
costPer1kTokens?: number
|
|
340
546
|
costCalculator?: (usage, context) => number
|
|
341
547
|
}
|
|
342
548
|
```
|
|
343
549
|
|
|
550
|
+
### `llmRetryStream(options)`
|
|
551
|
+
|
|
552
|
+
Returns `{ stream, getStats }`. The request begins when the returned async
|
|
553
|
+
iterable is consumed.
|
|
554
|
+
|
|
555
|
+
| Option | Type | Default | Description |
|
|
556
|
+
| --- | --- | --- | --- |
|
|
557
|
+
| `stream` | `(context) => AsyncIterable<TChunk> \| Promise<AsyncIterable<TChunk>>` | optional | Primary stream call for the simple API. |
|
|
558
|
+
| `fallbackStream` | `(context) => AsyncIterable<TChunk> \| Promise<AsyncIterable<TChunk>>` | optional | Backup stream call. |
|
|
559
|
+
| `providers` | `StreamRetryProvider<TChunk>[]` | optional | Explicit stream provider chain. |
|
|
560
|
+
| `retryMode` | `'before-first-chunk' \| 'always' \| 'never'` | `'before-first-chunk'` | Controls whether interrupted streams are retried. |
|
|
561
|
+
| `getChunkUsage` | `(chunk, context) => TokenUsage \| undefined` | optional | Extract token usage from stream chunks/events. |
|
|
562
|
+
| `chunkUsageMode` | `'delta' \| 'cumulative'` | `'delta'` | Interpret chunk usage as incremental or cumulative. |
|
|
563
|
+
| `maxRetries` | `number` | `3` | Retries after the first attempt. |
|
|
564
|
+
| `timeoutMs` | `number` | optional | Abort the whole stream workflow after this time. |
|
|
565
|
+
| `globalBudget` | `GlobalBudgetTracker` | optional | Shared rolling cost budget across calls. |
|
|
566
|
+
| `meta` | `unknown` | optional | User metadata copied into contexts. |
|
|
567
|
+
| `payload` | `unknown` | optional | Request payload copied into contexts. |
|
|
568
|
+
| `onChunk` | `(chunk, context) => void \| Promise<void>` | optional | Called for each chunk before it is yielded. |
|
|
569
|
+
| `onChunkError` | `(error, chunk, context) => void \| Promise<void>` | optional | Called when `onChunk` fails. |
|
|
570
|
+
| `onChunkErrorMode` | `'ignore' \| 'throw'` | `'ignore'` | Decide whether `onChunk` failures should break the stream. |
|
|
571
|
+
|
|
572
|
+
### `CircuitBreaker`
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
new CircuitBreaker({
|
|
576
|
+
failureThreshold: 5,
|
|
577
|
+
windowMs: 60_000,
|
|
578
|
+
cooldownMs: 120_000,
|
|
579
|
+
})
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
`snapshot()` returns `{ state, failures, openedAt }`, where state is
|
|
583
|
+
`'closed'`, `'open'`, or `'half_open'`.
|
|
584
|
+
|
|
585
|
+
### `AdaptiveHedgeDelay`
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
new AdaptiveHedgeDelay({
|
|
589
|
+
sampleSize: 100,
|
|
590
|
+
percentile: 0.95,
|
|
591
|
+
minSamples: 5,
|
|
592
|
+
minDelayMs: 250,
|
|
593
|
+
maxDelayMs: 5_000,
|
|
594
|
+
defaultDelayMs: 750,
|
|
595
|
+
})
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`snapshot()` returns the current sample count and computed delay per provider.
|
|
599
|
+
|
|
600
|
+
### `GlobalBudgetTracker`
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
new GlobalBudgetTracker({
|
|
604
|
+
maxCostUSD: 5,
|
|
605
|
+
windowMs: 60_000,
|
|
606
|
+
})
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
`snapshot()` returns `{ spentUSD, limitUSD, windowMs, resetAt, entries }`.
|
|
610
|
+
|
|
344
611
|
### `RetryResult<T>`
|
|
345
612
|
|
|
346
613
|
```ts
|
|
@@ -377,6 +644,8 @@ await llmRetry({
|
|
|
377
644
|
| `initialDelayMs` | `1000` |
|
|
378
645
|
| `maxDelayMs` | `30000` |
|
|
379
646
|
| `costPer1kTokens` | `0.002` |
|
|
647
|
+
| stream retry mode | `before-first-chunk` |
|
|
648
|
+
| stream chunk hook error mode | `ignore` |
|
|
380
649
|
| fallback on client errors | `false` |
|
|
381
650
|
| fallback on transient errors | `true` |
|
|
382
651
|
| runtime dependencies | none |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CircuitBreakerOptions, CircuitBreakerSnapshot } from './types.js';
|
|
2
|
+
export declare class CircuitBreaker {
|
|
3
|
+
private readonly options;
|
|
4
|
+
private failureTimestamps;
|
|
5
|
+
private state;
|
|
6
|
+
private openedAt;
|
|
7
|
+
constructor(options: CircuitBreakerOptions);
|
|
8
|
+
canRequest(): boolean;
|
|
9
|
+
recordSuccess(): void;
|
|
10
|
+
recordFailure(): void;
|
|
11
|
+
snapshot(): CircuitBreakerSnapshot;
|
|
12
|
+
}
|
|
13
|
+
export declare class CircuitBreakerOpenError extends Error {
|
|
14
|
+
readonly provider: string;
|
|
15
|
+
constructor(provider: string);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,YAAY,CAAA;AAEnB,qBAAa,cAAc;IAKb,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,QAAQ,CAAsB;gBAET,OAAO,EAAE,qBAAqB;IAI3D,UAAU,IAAI,OAAO;IAcrB,aAAa,IAAI,IAAI;IAMrB,aAAa,IAAI,IAAI;IAiBrB,QAAQ,IAAI,sBAAsB;CAOnC;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aACpB,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAI7C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class CircuitBreaker {
|
|
2
|
+
options;
|
|
3
|
+
failureTimestamps = [];
|
|
4
|
+
state = 'closed';
|
|
5
|
+
openedAt = null;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
validateCircuitBreakerOptions(options);
|
|
9
|
+
}
|
|
10
|
+
canRequest() {
|
|
11
|
+
if (this.state !== 'open') {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
const openedAt = this.openedAt ?? 0;
|
|
15
|
+
if (Date.now() - openedAt >= this.options.cooldownMs) {
|
|
16
|
+
this.state = 'half_open';
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
recordSuccess() {
|
|
22
|
+
this.failureTimestamps = [];
|
|
23
|
+
this.state = 'closed';
|
|
24
|
+
this.openedAt = null;
|
|
25
|
+
}
|
|
26
|
+
recordFailure() {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const windowStart = now - this.options.windowMs;
|
|
29
|
+
this.failureTimestamps = [...this.failureTimestamps, now].filter((timestamp) => timestamp >= windowStart);
|
|
30
|
+
if (this.state === 'half_open' ||
|
|
31
|
+
this.failureTimestamps.length >= this.options.failureThreshold) {
|
|
32
|
+
this.state = 'open';
|
|
33
|
+
this.openedAt = now;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
snapshot() {
|
|
37
|
+
return {
|
|
38
|
+
state: this.state,
|
|
39
|
+
failures: this.failureTimestamps.length,
|
|
40
|
+
openedAt: this.openedAt,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class CircuitBreakerOpenError extends Error {
|
|
45
|
+
provider;
|
|
46
|
+
constructor(provider) {
|
|
47
|
+
super(`Circuit breaker is open for provider "${provider}"`);
|
|
48
|
+
this.provider = provider;
|
|
49
|
+
this.name = 'CircuitBreakerOpenError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function validateCircuitBreakerOptions(options) {
|
|
53
|
+
if (!Number.isInteger(options.failureThreshold) || options.failureThreshold <= 0) {
|
|
54
|
+
throw new Error('failureThreshold must be a positive integer');
|
|
55
|
+
}
|
|
56
|
+
if (options.windowMs < 0) {
|
|
57
|
+
throw new Error('windowMs must be greater than or equal to 0');
|
|
58
|
+
}
|
|
59
|
+
if (options.cooldownMs < 0) {
|
|
60
|
+
throw new Error('cooldownMs must be greater than or equal to 0');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,cAAc;IAKI;IAJrB,iBAAiB,GAAa,EAAE,CAAA;IAChC,KAAK,GAAwB,QAAQ,CAAA;IACrC,QAAQ,GAAkB,IAAI,CAAA;IAEtC,YAA6B,OAA8B;QAA9B,YAAO,GAAP,OAAO,CAAuB;QACzD,6BAA6B,CAAC,OAAO,CAAC,CAAA;IACxC,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAA;QACnC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,GAAG,WAAW,CAAA;YACxB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAED,aAAa;QACX,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAA;QAC3B,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAA;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACtB,CAAC;IAED,aAAa;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA;QAE/C,IAAI,CAAC,iBAAiB,GAAG,CAAC,GAAG,IAAI,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,MAAM,CAC9D,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,WAAW,CACxC,CAAA;QAED,IACE,IAAI,CAAC,KAAK,KAAK,WAAW;YAC1B,IAAI,CAAC,iBAAiB,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAC9D,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAA;YACnB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QACrB,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM;YACvC,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACpB;IAA5B,YAA4B,QAAgB;QAC1C,KAAK,CAAC,yCAAyC,QAAQ,GAAG,CAAC,CAAA;QADjC,aAAQ,GAAR,QAAQ,CAAQ;QAE1C,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;IACvC,CAAC;CACF;AAED,SAAS,6BAA6B,CAAC,OAA8B;IACnE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GlobalBudgetOptions, GlobalBudgetSnapshot } from './types.js';
|
|
2
|
+
export declare class GlobalBudgetTracker {
|
|
3
|
+
private readonly options;
|
|
4
|
+
private entries;
|
|
5
|
+
constructor(options: GlobalBudgetOptions);
|
|
6
|
+
add(costUSD: number): void;
|
|
7
|
+
isExceeded(): boolean;
|
|
8
|
+
get spent(): number;
|
|
9
|
+
get limit(): number;
|
|
10
|
+
snapshot(): GlobalBudgetSnapshot;
|
|
11
|
+
private prune;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=global-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-budget.d.ts","sourceRoot":"","sources":["../src/global-budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAO3E,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,OAAO,CAAoB;gBAEN,OAAO,EAAE,mBAAmB;IAIzD,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAc1B,UAAU,IAAI,OAAO;IAKrB,IAAI,KAAK,IAAI,MAAM,CAGlB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,QAAQ,IAAI,oBAAoB;IAchC,OAAO,CAAC,KAAK;CAId"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class GlobalBudgetTracker {
|
|
2
|
+
options;
|
|
3
|
+
entries = [];
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.options = options;
|
|
6
|
+
validateGlobalBudgetOptions(options);
|
|
7
|
+
}
|
|
8
|
+
add(costUSD) {
|
|
9
|
+
if (!Number.isFinite(costUSD) || costUSD < 0) {
|
|
10
|
+
throw new Error('costUSD must be greater than or equal to 0');
|
|
11
|
+
}
|
|
12
|
+
this.prune();
|
|
13
|
+
if (costUSD === 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
this.entries.push({ timestamp: Date.now(), costUSD });
|
|
17
|
+
}
|
|
18
|
+
isExceeded() {
|
|
19
|
+
this.prune();
|
|
20
|
+
return this.spent >= this.options.maxCostUSD;
|
|
21
|
+
}
|
|
22
|
+
get spent() {
|
|
23
|
+
this.prune();
|
|
24
|
+
return this.entries.reduce((total, entry) => total + entry.costUSD, 0);
|
|
25
|
+
}
|
|
26
|
+
get limit() {
|
|
27
|
+
return this.options.maxCostUSD;
|
|
28
|
+
}
|
|
29
|
+
snapshot() {
|
|
30
|
+
this.prune();
|
|
31
|
+
return {
|
|
32
|
+
spentUSD: this.spent,
|
|
33
|
+
limitUSD: this.options.maxCostUSD,
|
|
34
|
+
windowMs: this.options.windowMs,
|
|
35
|
+
resetAt: this.entries[0]
|
|
36
|
+
? this.entries[0].timestamp + this.options.windowMs
|
|
37
|
+
: null,
|
|
38
|
+
entries: this.entries.length,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
prune() {
|
|
42
|
+
const windowStart = Date.now() - this.options.windowMs;
|
|
43
|
+
this.entries = this.entries.filter((entry) => entry.timestamp > windowStart);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function validateGlobalBudgetOptions(options) {
|
|
47
|
+
if (options.maxCostUSD < 0) {
|
|
48
|
+
throw new Error('maxCostUSD must be greater than or equal to 0');
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isFinite(options.windowMs) || options.windowMs <= 0) {
|
|
51
|
+
throw new Error('windowMs must be greater than 0');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=global-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-budget.js","sourceRoot":"","sources":["../src/global-budget.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,mBAAmB;IAGD;IAFrB,OAAO,GAAkB,EAAE,CAAA;IAEnC,YAA6B,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;QACvD,2BAA2B,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,OAAe;QACjB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;QAC/D,CAAC;QAED,IAAI,CAAC,KAAK,EAAE,CAAA;QAEZ,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC;IAED,UAAU;QACR,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAA;IAC9C,CAAC;IAED,IAAI,KAAK;QACP,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACxE,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAA;IAChC,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,KAAK,EAAE,CAAA;QAEZ,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,KAAK;YACpB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACjC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC/B,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;gBACtB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ;gBACnD,CAAC,CAAC,IAAI;YACR,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;SAC7B,CAAA;IACH,CAAC;IAEO,KAAK;QACX,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,WAAW,CAAC,CAAA;IAC9E,CAAC;CACF;AAED,SAAS,2BAA2B,CAAC,OAA4B;IAC/D,IAAI,OAAO,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AdaptiveHedgeDelayOptions, AdaptiveHedgeDelaySnapshot, HedgeDelayRecord, RetryAttemptContext } from './types.js';
|
|
2
|
+
export declare class AdaptiveHedgeDelay {
|
|
3
|
+
private readonly samplesByProvider;
|
|
4
|
+
private readonly sampleSize;
|
|
5
|
+
private readonly percentile;
|
|
6
|
+
private readonly minSamples;
|
|
7
|
+
private readonly minDelayMs;
|
|
8
|
+
private readonly maxDelayMs;
|
|
9
|
+
private readonly defaultDelayMs;
|
|
10
|
+
private readonly recordFailures;
|
|
11
|
+
constructor(options?: AdaptiveHedgeDelayOptions);
|
|
12
|
+
getDelayMs(context: RetryAttemptContext): number | null;
|
|
13
|
+
recordLatency(record: HedgeDelayRecord): void;
|
|
14
|
+
snapshot(): AdaptiveHedgeDelaySnapshot;
|
|
15
|
+
private clamp;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=hedging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hedging.d.ts","sourceRoot":"","sources":["../src/hedging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAEnB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA8B;IAChE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAe;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,OAAO,GAAE,yBAA8B;IAmBnD,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI;IASvD,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAmB7C,QAAQ,IAAI,0BAA0B;IAmBtC,OAAO,CAAC,KAAK;CAad"}
|
package/dist/hedging.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export class AdaptiveHedgeDelay {
|
|
2
|
+
samplesByProvider = new Map();
|
|
3
|
+
sampleSize;
|
|
4
|
+
percentile;
|
|
5
|
+
minSamples;
|
|
6
|
+
minDelayMs;
|
|
7
|
+
maxDelayMs;
|
|
8
|
+
defaultDelayMs;
|
|
9
|
+
recordFailures;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.sampleSize = options.sampleSize ?? 100;
|
|
12
|
+
this.percentile = options.percentile ?? 0.95;
|
|
13
|
+
this.minSamples = options.minSamples ?? 5;
|
|
14
|
+
this.minDelayMs = options.minDelayMs ?? null;
|
|
15
|
+
this.maxDelayMs = options.maxDelayMs ?? null;
|
|
16
|
+
this.defaultDelayMs = options.defaultDelayMs ?? null;
|
|
17
|
+
this.recordFailures = options.recordFailures ?? false;
|
|
18
|
+
validateAdaptiveHedgeDelay({
|
|
19
|
+
sampleSize: this.sampleSize,
|
|
20
|
+
percentile: this.percentile,
|
|
21
|
+
minSamples: this.minSamples,
|
|
22
|
+
minDelayMs: this.minDelayMs,
|
|
23
|
+
maxDelayMs: this.maxDelayMs,
|
|
24
|
+
defaultDelayMs: this.defaultDelayMs,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
getDelayMs(context) {
|
|
28
|
+
const samples = this.samplesByProvider.get(context.provider) ?? [];
|
|
29
|
+
if (samples.length < this.minSamples) {
|
|
30
|
+
return this.defaultDelayMs;
|
|
31
|
+
}
|
|
32
|
+
return this.clamp(percentile(samples, this.percentile));
|
|
33
|
+
}
|
|
34
|
+
recordLatency(record) {
|
|
35
|
+
if (!Number.isFinite(record.latencyMs) || record.latencyMs < 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (record.outcome === 'failure' && !this.recordFailures) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const samples = this.samplesByProvider.get(record.provider) ?? [];
|
|
42
|
+
samples.push(record.latencyMs);
|
|
43
|
+
if (samples.length > this.sampleSize) {
|
|
44
|
+
samples.splice(0, samples.length - this.sampleSize);
|
|
45
|
+
}
|
|
46
|
+
this.samplesByProvider.set(record.provider, samples);
|
|
47
|
+
}
|
|
48
|
+
snapshot() {
|
|
49
|
+
const providers = {};
|
|
50
|
+
for (const [provider, samples] of this.samplesByProvider) {
|
|
51
|
+
providers[provider] = {
|
|
52
|
+
samples: samples.length,
|
|
53
|
+
delayMs: samples.length < this.minSamples
|
|
54
|
+
? this.defaultDelayMs
|
|
55
|
+
: this.clamp(percentile(samples, this.percentile)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
sampleSize: this.sampleSize,
|
|
60
|
+
percentile: this.percentile,
|
|
61
|
+
providers,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
clamp(delayMs) {
|
|
65
|
+
let clamped = delayMs;
|
|
66
|
+
if (this.minDelayMs !== null) {
|
|
67
|
+
clamped = Math.max(clamped, this.minDelayMs);
|
|
68
|
+
}
|
|
69
|
+
if (this.maxDelayMs !== null) {
|
|
70
|
+
clamped = Math.min(clamped, this.maxDelayMs);
|
|
71
|
+
}
|
|
72
|
+
return clamped;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function percentile(samples, value) {
|
|
76
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
77
|
+
const index = Math.ceil(value * sorted.length) - 1;
|
|
78
|
+
return sorted[Math.min(Math.max(index, 0), sorted.length - 1)] ?? 0;
|
|
79
|
+
}
|
|
80
|
+
function validateAdaptiveHedgeDelay(options) {
|
|
81
|
+
if (!Number.isInteger(options.sampleSize) || options.sampleSize <= 0) {
|
|
82
|
+
throw new Error('sampleSize must be a positive integer');
|
|
83
|
+
}
|
|
84
|
+
if (options.percentile <= 0 || options.percentile > 1) {
|
|
85
|
+
throw new Error('percentile must be greater than 0 and less than or equal to 1');
|
|
86
|
+
}
|
|
87
|
+
if (!Number.isInteger(options.minSamples) || options.minSamples < 0) {
|
|
88
|
+
throw new Error('minSamples must be a non-negative integer');
|
|
89
|
+
}
|
|
90
|
+
for (const [name, value] of [
|
|
91
|
+
['minDelayMs', options.minDelayMs],
|
|
92
|
+
['maxDelayMs', options.maxDelayMs],
|
|
93
|
+
['defaultDelayMs', options.defaultDelayMs],
|
|
94
|
+
]) {
|
|
95
|
+
if (value !== null && value < 0) {
|
|
96
|
+
throw new Error(`${name} must be greater than or equal to 0`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (options.minDelayMs !== null &&
|
|
100
|
+
options.maxDelayMs !== null &&
|
|
101
|
+
options.maxDelayMs < options.minDelayMs) {
|
|
102
|
+
throw new Error('maxDelayMs must be greater than or equal to minDelayMs');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=hedging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hedging.js","sourceRoot":"","sources":["../src/hedging.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { CircuitBreaker, CircuitBreakerOpenError } from './circuit-breaker.js';
|
|
2
|
+
export { BudgetExceededError, llmRetry, LLMRetryError, ProviderTimeoutError } from './retry.js';
|
|
3
|
+
export { llmRetryStream } from './stream.js';
|
|
2
4
|
export { BudgetTracker } from './budget.js';
|
|
5
|
+
export { GlobalBudgetTracker } from './global-budget.js';
|
|
6
|
+
export { AdaptiveHedgeDelay } from './hedging.js';
|
|
3
7
|
export { calculateBackoff, extractRetryAfter, isRetryableError } from './backoff.js';
|
|
4
|
-
export type { CostCalculator, FallbackDecisionContext, LLMCall, LLMResponse, RetryAttemptContext, RetryDecisionContext, RetryOptions, RetryProvider, RetryResult, RetrySuccessContext, RetryableError, ShouldFallback, ShouldRetry, TokenUsage, } from './types.js';
|
|
8
|
+
export type { AdaptiveHedgeDelayOptions, AdaptiveHedgeDelaySnapshot, CircuitBreakerLike, CircuitBreakerOptions, CircuitBreakerSnapshot, CircuitBreakerState, CostCalculator, FallbackDecisionContext, GlobalBudgetLike, GlobalBudgetOptions, GlobalBudgetSnapshot, HedgeDelayRecord, HedgeDelayStrategy, LLMCall, LLMResponse, RetryAttemptContext, RetryDecisionContext, RetryFailureContext, RetryOptions, RetryProvider, RetryResult, RetrySuccessContext, RetryableError, ShouldFallback, ShouldRetry, StreamLLMCall, StreamRetryMode, StreamRetryOptions, StreamRetryProvider, StreamRetryResult, StreamRetryStats, StreamUsageExtractor, StreamUsageMode, TokenUsage, } from './types.js';
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpF,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAA"}
|