ai-retry 1.5.0 → 1.6.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 +134 -5
- package/dist/index.d.mts +1 -1
- package/dist/parse-retry-headers-DIPVbwW5.mjs +26 -0
- package/dist/retryables/experimental/index.d.mts +248 -0
- package/dist/retryables/experimental/index.mjs +310 -0
- package/dist/retryables/index.d.mts +1 -1
- package/dist/retryables/index.mjs +1 -20
- package/package.json +2 -1
- /package/dist/{types-wrgO_vOH.d.mts → types-pGdkwtOE.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -288,6 +288,9 @@ If you need more control over when to retry and which model to use, you can crea
|
|
|
288
288
|
> [!NOTE]
|
|
289
289
|
> You can return additional options like `maxAttempts`, `delay`, etc. along with the model.
|
|
290
290
|
|
|
291
|
+
> [!TIP]
|
|
292
|
+
> If you'd like the same flexibility with a typed, composable condition system, see [Experimental: Composable Conditions](#experimental-composable-conditions).
|
|
293
|
+
|
|
291
294
|
```typescript
|
|
292
295
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
293
296
|
import { openai } from '@ai-sdk/openai';
|
|
@@ -370,6 +373,9 @@ There are several built-in dynamic retryables available for common use cases:
|
|
|
370
373
|
> [!TIP]
|
|
371
374
|
> You are missing a retryable for your use case? [Open an issue](https://github.com/zirkelc/ai-retry/issues/new) and let's discuss it!
|
|
372
375
|
|
|
376
|
+
> [!NOTE]
|
|
377
|
+
> Looking for a composable alternative? See [Experimental: Composable Conditions](#experimental-composable-conditions) for a `condition().action()` API that builds on small primitives.
|
|
378
|
+
|
|
373
379
|
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
374
380
|
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
375
381
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
@@ -578,6 +584,130 @@ const result = await generateText({
|
|
|
578
584
|
console.log(result.object); // { name: "Alice", age: 30 }
|
|
579
585
|
```
|
|
580
586
|
|
|
587
|
+
### Experimental: Composable Conditions
|
|
588
|
+
|
|
589
|
+
> [!WARNING]
|
|
590
|
+
> This API is experimental and may change. It is not exported from the package root; opt in via the deep import:
|
|
591
|
+
>
|
|
592
|
+
> ```ts
|
|
593
|
+
> import { ... } from 'ai-retry/retryables/experimental';
|
|
594
|
+
> ```
|
|
595
|
+
|
|
596
|
+
A `condition().action()` API for retryables. Conditions are built from small primitives (`error(fn)`, `result(fn)`), composed with `and` / `or` / `not`, and turned into a `Retryable` by one of two terminal actions: `.switch({ model })` or `.retry({ delay })`. The result drops into the same `retries: [...]` array as the stable helpers, so you can mix the two styles freely.
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
600
|
+
import { openai } from '@ai-sdk/openai';
|
|
601
|
+
import { generateText } from 'ai';
|
|
602
|
+
import { createRetryable } from 'ai-retry';
|
|
603
|
+
import {
|
|
604
|
+
error,
|
|
605
|
+
finishReason,
|
|
606
|
+
httpStatus,
|
|
607
|
+
} from 'ai-retry/retryables/experimental';
|
|
608
|
+
|
|
609
|
+
const retryableModel = createRetryable({
|
|
610
|
+
model: openai('gpt-4'),
|
|
611
|
+
retries: [
|
|
612
|
+
// Switch on 529 or any "overloaded" message
|
|
613
|
+
httpStatus(529, 'overloaded').switch({
|
|
614
|
+
model: anthropic('claude-3-haiku-20240307'),
|
|
615
|
+
}),
|
|
616
|
+
|
|
617
|
+
// Switch when the response was content-filtered
|
|
618
|
+
finishReason('content-filter').switch({ model: openai('gpt-4o') }),
|
|
619
|
+
|
|
620
|
+
// Retry the same model with exponential backoff on retryable errors
|
|
621
|
+
error.isRetryable(true).retry({ delay: 1_000, backoffFactor: 2 }),
|
|
622
|
+
],
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### High-level helpers
|
|
627
|
+
|
|
628
|
+
These cover the common cases. Each returns a `Condition` that you finalize with `.switch(...)` or `.retry(...)`.
|
|
629
|
+
|
|
630
|
+
| Helper | Matches when |
|
|
631
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
632
|
+
| `httpStatus(...patterns)` | Numbers match the status code; strings match the message (substring); regex matches either |
|
|
633
|
+
| `timeout()` | `Error.name === 'TimeoutError'` (`AbortSignal.timeout()` fired) |
|
|
634
|
+
| `aborted()` | `Error.name === 'AbortError'` (manual `controller.abort()`) |
|
|
635
|
+
| `noImage()` | The image model threw `NoImageGeneratedError` |
|
|
636
|
+
| `finishReason(...reasons)` | The result's `finishReason.unified` matches one of the given values |
|
|
637
|
+
| `schemaInvalid()` | The result text fails JSON-schema validation against the call's `responseFormat` |
|
|
638
|
+
|
|
639
|
+
#### Actions
|
|
640
|
+
|
|
641
|
+
Every `Condition` exposes two terminal actions that turn it into a `Retryable`:
|
|
642
|
+
|
|
643
|
+
- **`.switch({ model, ...options })`** falls back to a different model when the condition matches. Optional fields (`maxAttempts`, `delay`, `backoffFactor`, `timeout`, `options`) are the same as on a normal `Retry` object.
|
|
644
|
+
- **`.retry({ delay?, backoffFactor?, ... })`** retries the current model when the condition matches. Honors `Retry-After` and `Retry-After-Ms` response headers when present, capped at 60 seconds.
|
|
645
|
+
|
|
646
|
+
#### Combinators
|
|
647
|
+
|
|
648
|
+
Compose conditions with the free functions or the methods on `Condition`:
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
import {
|
|
652
|
+
and,
|
|
653
|
+
error,
|
|
654
|
+
httpStatus,
|
|
655
|
+
not,
|
|
656
|
+
or,
|
|
657
|
+
} from 'ai-retry/retryables/experimental';
|
|
658
|
+
|
|
659
|
+
or(httpStatus(429), error.message('overloaded'));
|
|
660
|
+
and(httpStatus(503), error.message('temporary'));
|
|
661
|
+
not(error.isRetryable(true));
|
|
662
|
+
|
|
663
|
+
// Method form
|
|
664
|
+
httpStatus(429).or(error.message('overloaded'));
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### Primitives
|
|
668
|
+
|
|
669
|
+
The two lowest-level builders. Reach for them when no helper covers your case:
|
|
670
|
+
|
|
671
|
+
| Primitive | Matches when |
|
|
672
|
+
| ------------------ | ----------------------------------------------------------------------------- |
|
|
673
|
+
| `error(predicate)` | The current attempt failed and `predicate(err, ctx)` returns true |
|
|
674
|
+
| `result(predicate)`| The current attempt succeeded and `predicate(res, ctx)` returns true (language models only) |
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
import { APICallError } from 'ai';
|
|
678
|
+
import { error } from 'ai-retry/retryables/experimental';
|
|
679
|
+
|
|
680
|
+
error<MODEL, APICallError>(
|
|
681
|
+
(e) => APICallError.isInstance(e) && e.statusCode === 418,
|
|
682
|
+
).switch({ model: fallback });
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
A few common error fields have ready-made matchers on the `error` namespace:
|
|
686
|
+
|
|
687
|
+
| Helper | Matches when |
|
|
688
|
+
| ------------------------------- | ------------------------------------------------------------------------------------- |
|
|
689
|
+
| `error.isRetryable(flag)` | `APICallError.isRetryable === flag` (default `true`) |
|
|
690
|
+
| `error.statusCode(...patterns)` | Numbers match exactly; regex matches the stringified code (e.g. `/^5\d\d$/` for 5xx) |
|
|
691
|
+
| `error.message(...patterns)` | Substring (case-insensitive) or regex match against the error message |
|
|
692
|
+
|
|
693
|
+
#### Mapping from Built-in retryables
|
|
694
|
+
|
|
695
|
+
Each stable retryable has an equivalent in the new shape:
|
|
696
|
+
|
|
697
|
+
| Built-in | Composable form |
|
|
698
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
699
|
+
| `contentFilterTriggered(m)` | `or(error(/* check e.data.error.code === 'content_filter' */), finishReason('content-filter')).switch({ model: m })` |
|
|
700
|
+
| `requestTimeout(m)` | `timeout().switch({ model: m, timeout: 60_000 })` |
|
|
701
|
+
| `requestNotRetryable(m)` | `error.isRetryable(false).switch({ model: m })` |
|
|
702
|
+
| `schemaMismatch(m)` | `schemaInvalid().switch({ model: m })` |
|
|
703
|
+
| `serviceOverloaded(m)` | `httpStatus(529, 'overloaded').switch({ model: m })` |
|
|
704
|
+
| `serviceUnavailable(m)` | `error.statusCode(503).switch({ model: m })` |
|
|
705
|
+
| `noImageGenerated(m)` | `noImage().switch({ model: m })` |
|
|
706
|
+
| `retryAfterDelay({ delay, backoffFactor })` | `error.isRetryable(true).retry({ delay, backoffFactor })` |
|
|
707
|
+
|
|
708
|
+
> [!NOTE]
|
|
709
|
+
> `error.isRetryable(true)` matches whatever the AI SDK's `APICallError` marks retryable. By default that's status codes 408, 409, 429, and any 5xx, plus network errors and provider-specific overrides (e.g. Anthropic flips it on `error.type === 'overloaded_error'`). It picks up more cases than a manual status-code list.
|
|
710
|
+
|
|
581
711
|
### Options
|
|
582
712
|
|
|
583
713
|
#### Disabling Retries
|
|
@@ -815,10 +945,7 @@ const retryableModel = createRetryable({
|
|
|
815
945
|
// Strip provider-scoped metadata from the prompt before retrying on a different provider
|
|
816
946
|
return {
|
|
817
947
|
options: {
|
|
818
|
-
prompt:
|
|
819
|
-
previous.options.prompt,
|
|
820
|
-
current.model.provider,
|
|
821
|
-
),
|
|
948
|
+
prompt: stripProviderMetadata(current.options.prompt),
|
|
822
949
|
},
|
|
823
950
|
};
|
|
824
951
|
}
|
|
@@ -826,6 +953,8 @@ const retryableModel = createRetryable({
|
|
|
826
953
|
});
|
|
827
954
|
```
|
|
828
955
|
|
|
956
|
+
Inside the `onRetry` callback, `context.current.model` is the model that's about to be tried next, while `context.current.options` and `context.current.error` describe the failed attempt that triggered the retry. The previous model is available at `context.attempts.at(-1).model`.
|
|
957
|
+
|
|
829
958
|
`onRetry` may also be `async`, which is useful if computing the override needs to do work (e.g. fetching a fresh credential):
|
|
830
959
|
|
|
831
960
|
```typescript
|
|
@@ -844,7 +973,7 @@ const retryableModel = createRetryable({
|
|
|
844
973
|
**Precedence** for the upcoming retry attempt (highest to lowest):
|
|
845
974
|
|
|
846
975
|
1. The value returned from `onRetry`
|
|
847
|
-
2. `
|
|
976
|
+
2. The `options` returned from the retryable
|
|
848
977
|
3. The original call options from the request
|
|
849
978
|
|
|
850
979
|
#### Logging
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as RetryResultAttempt, C as Result, D as RetryCallOptions, E as RetryAttempt, F as SuccessContext, M as RetryableModelOptions, N as RetryableOptions, O as RetryContext, P as SuccessAttempt, S as ResolvedModel, T as Retry, _ as OnRetryOverrides, a as EmbeddingModelRetryCallOptions, b as ResolvableLanguageModel, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStreamPart, h as LanguageModelStream, i as EmbeddingModelEmbed, j as Retryable, k as RetryErrorAttempt, l as ImageModelGenerate, m as LanguageModelRetryCallOptions, n as EmbeddingModel, o as GatewayLanguageModelId, p as LanguageModelGenerate, r as EmbeddingModelCallOptions, s as ImageModel, t as CallOptions, u as ImageModelRetryCallOptions, v as ProviderOptions, w as Retries, x as ResolvableModel, y as Reset } from "./types-
|
|
1
|
+
import { A as RetryResultAttempt, C as Result, D as RetryCallOptions, E as RetryAttempt, F as SuccessContext, M as RetryableModelOptions, N as RetryableOptions, O as RetryContext, P as SuccessAttempt, S as ResolvedModel, T as Retry, _ as OnRetryOverrides, a as EmbeddingModelRetryCallOptions, b as ResolvableLanguageModel, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStreamPart, h as LanguageModelStream, i as EmbeddingModelEmbed, j as Retryable, k as RetryErrorAttempt, l as ImageModelGenerate, m as LanguageModelRetryCallOptions, n as EmbeddingModel, o as GatewayLanguageModelId, p as LanguageModelGenerate, r as EmbeddingModelCallOptions, s as ImageModel, t as CallOptions, u as ImageModelRetryCallOptions, v as ProviderOptions, w as Retries, x as ResolvableModel, y as Reset } from "./types-pGdkwtOE.mjs";
|
|
2
2
|
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
3
|
|
|
4
4
|
//#region src/create-retryable-model.d.ts
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/parse-retry-headers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Upper bound for `Retry-After` / `Retry-After-Ms` honored by retry actions.
|
|
4
|
+
* Servers can technically request very long delays; cap to keep retries
|
|
5
|
+
* responsive.
|
|
6
|
+
*/
|
|
7
|
+
const MAX_RETRY_AFTER_MS = 6e4;
|
|
8
|
+
function parseRetryHeaders(headers) {
|
|
9
|
+
if (!headers) return null;
|
|
10
|
+
const retryAfterMs = headers["retry-after-ms"];
|
|
11
|
+
if (retryAfterMs) {
|
|
12
|
+
const delayMs = Number.parseFloat(retryAfterMs);
|
|
13
|
+
if (!Number.isNaN(delayMs) && delayMs >= 0) return delayMs;
|
|
14
|
+
}
|
|
15
|
+
const retryAfter = headers["retry-after"];
|
|
16
|
+
if (retryAfter) {
|
|
17
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
18
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
19
|
+
const date = Date.parse(retryAfter);
|
|
20
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { parseRetryHeaders as n, MAX_RETRY_AFTER_MS as t };
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { O as RetryContext, T as Retry, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, p as LanguageModelGenerate, s as ImageModel } from "../../types-pGdkwtOE.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/retryables/experimental/condition.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Any model the retryable system supports.
|
|
6
|
+
*/
|
|
7
|
+
type AnyModel = ResolvableLanguageModel | EmbeddingModel | ImageModel;
|
|
8
|
+
/**
|
|
9
|
+
* Predicate over a `RetryContext`. May be sync or async.
|
|
10
|
+
*/
|
|
11
|
+
type Predicate<MODEL extends AnyModel> = (ctx: RetryContext<MODEL>) => boolean | Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Argument shape for `Condition.switch`. The target `model` is required;
|
|
14
|
+
* all other `Retry` fields are optional.
|
|
15
|
+
*/
|
|
16
|
+
type SwitchTarget<MODEL extends AnyModel> = {
|
|
17
|
+
model: MODEL;
|
|
18
|
+
} & Omit<Retry<MODEL>, 'model'>;
|
|
19
|
+
/**
|
|
20
|
+
* Argument shape for `Condition.retry`. Same as `Retry` without `model`,
|
|
21
|
+
* since retry reuses the current model.
|
|
22
|
+
*/
|
|
23
|
+
type RetryOptions<MODEL extends AnyModel> = Omit<Retry<MODEL>, 'model'>;
|
|
24
|
+
/**
|
|
25
|
+
* A predicate over a `RetryContext` paired with two terminal actions
|
|
26
|
+
* (`switch`, `retry`) that turn it into a `Retryable<MODEL>`. Compose
|
|
27
|
+
* conditions with `and`, `or`, `not`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const cond = httpStatus(429, 503);
|
|
31
|
+
* cond.switch({ model: fallback });
|
|
32
|
+
* cond.retry({ delay: 1000 });
|
|
33
|
+
*/
|
|
34
|
+
declare class Condition<MODEL extends AnyModel> {
|
|
35
|
+
private readonly predicate;
|
|
36
|
+
constructor(predicate: Predicate<MODEL>);
|
|
37
|
+
/**
|
|
38
|
+
* Run the predicate against a context and resolve to a boolean.
|
|
39
|
+
*/
|
|
40
|
+
evaluate(ctx: RetryContext<MODEL>): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Switch to a different model when the condition matches.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* httpStatus(529).switch({ model: fallback })
|
|
46
|
+
*/
|
|
47
|
+
switch(target: SwitchTarget<MODEL>): Retryable<MODEL>;
|
|
48
|
+
/**
|
|
49
|
+
* Retry the same model when the condition matches. Honors
|
|
50
|
+
* `Retry-After` and `Retry-After-Ms` response headers when present,
|
|
51
|
+
* capped at 60 seconds, overriding any provided `delay`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* error.isRetryable(true).retry({ delay: 1000, backoffFactor: 2 })
|
|
55
|
+
*/
|
|
56
|
+
retry(options?: RetryOptions<MODEL>): Retryable<MODEL>;
|
|
57
|
+
/**
|
|
58
|
+
* Combine with another condition; matches when both match.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* httpStatus(429).and(error.message('overloaded'))
|
|
62
|
+
*/
|
|
63
|
+
and(other: Condition<MODEL>): Condition<MODEL>;
|
|
64
|
+
/**
|
|
65
|
+
* Combine with another condition; matches when either matches.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* httpStatus(429).or(error.message('overloaded'))
|
|
69
|
+
*/
|
|
70
|
+
or(other: Condition<MODEL>): Condition<MODEL>;
|
|
71
|
+
/**
|
|
72
|
+
* Invert the condition.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* error.isRetryable(true).not()
|
|
76
|
+
*/
|
|
77
|
+
not(): Condition<MODEL>;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/retryables/experimental/aborted.d.ts
|
|
81
|
+
/**
|
|
82
|
+
* Match a manual abort: an `Error` with `name === 'AbortError'`, which
|
|
83
|
+
* `controller.abort()` produces. Distinct from `timeout()`, which
|
|
84
|
+
* matches `AbortSignal.timeout()` firing.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* aborted().switch({ model: fallback })
|
|
88
|
+
*/
|
|
89
|
+
declare function aborted<MODEL extends AnyModel = AnyModel>(): Condition<MODEL>;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/retryables/experimental/and.d.ts
|
|
92
|
+
/**
|
|
93
|
+
* Match only when all of the given conditions match. Evaluates left to
|
|
94
|
+
* right and stops on the first miss.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* and(httpStatus(429), error.message('overloaded'))
|
|
98
|
+
*/
|
|
99
|
+
declare function and<MODEL extends AnyModel>(...conditions: Array<Condition<MODEL>>): Condition<MODEL>;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/retryables/experimental/error.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Build a condition from a predicate over the current error. The
|
|
104
|
+
* predicate runs only when the current attempt failed with an error;
|
|
105
|
+
* result attempts return false.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* error<MODEL, APICallError>(
|
|
109
|
+
* (e) => APICallError.isInstance(e) && e.statusCode === 418,
|
|
110
|
+
* )
|
|
111
|
+
*/
|
|
112
|
+
declare function error<MODEL extends AnyModel = AnyModel, E = unknown>(predicate: (err: E, ctx: RetryContext<MODEL>) => boolean | Promise<boolean>): Condition<MODEL>;
|
|
113
|
+
/**
|
|
114
|
+
* Higher-level matchers for common error fields. Drop down to `error(fn)`
|
|
115
|
+
* for anything not covered.
|
|
116
|
+
*
|
|
117
|
+
* `Error.name` is intentionally not exposed here because the property
|
|
118
|
+
* name would clash with `Function.prototype.name`. Use `timeout()` or
|
|
119
|
+
* `aborted()` for the common cases, or `error(fn)` for custom names.
|
|
120
|
+
*/
|
|
121
|
+
declare namespace error {
|
|
122
|
+
/**
|
|
123
|
+
* Match when the error explicitly carries `isRetryable === flag`.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* error.isRetryable(true).retry({ delay: 1000 })
|
|
127
|
+
* error.isRetryable(false).switch({ model: fallback })
|
|
128
|
+
*/
|
|
129
|
+
function isRetryable<MODEL extends AnyModel = AnyModel>(flag?: boolean): Condition<MODEL>;
|
|
130
|
+
/**
|
|
131
|
+
* Match by HTTP status code. Numbers match exactly; regular expressions
|
|
132
|
+
* match against the stringified code, useful for range checks.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* error.statusCode(429, 503)
|
|
136
|
+
* error.statusCode(/^5\d\d$/)
|
|
137
|
+
*/
|
|
138
|
+
function statusCode<MODEL extends AnyModel = AnyModel>(...patterns: Array<number | RegExp>): Condition<MODEL>;
|
|
139
|
+
/**
|
|
140
|
+
* Match the error message against substrings or regular expressions.
|
|
141
|
+
* Substring matching is case-insensitive: both the pattern and the
|
|
142
|
+
* message are lowercased before matching. Regular expressions match
|
|
143
|
+
* as written; use the `i` flag for case-insensitive regex matching.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* error.message('overloaded')
|
|
147
|
+
* error.message(/rate.?limit/i)
|
|
148
|
+
* error.message('overloaded', /rate.?limit/i)
|
|
149
|
+
*/
|
|
150
|
+
function message<MODEL extends AnyModel = AnyModel>(...patterns: Array<string | RegExp>): Condition<MODEL>;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/retryables/experimental/finish-reason.d.ts
|
|
154
|
+
/**
|
|
155
|
+
* Match the result's finish reason against one of the given values.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* finishReason('content-filter')
|
|
159
|
+
* finishReason('content-filter', 'length')
|
|
160
|
+
*/
|
|
161
|
+
declare function finishReason<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(...reasons: Array<string>): Condition<MODEL>;
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/retryables/experimental/http-status.d.ts
|
|
164
|
+
/**
|
|
165
|
+
* A pattern accepted by `httpStatus`. Numbers match the response status
|
|
166
|
+
* code; strings match the error message as a substring; regular
|
|
167
|
+
* expressions match against both the stringified status code and the
|
|
168
|
+
* error message.
|
|
169
|
+
*/
|
|
170
|
+
type StatusPattern = number | string | RegExp;
|
|
171
|
+
/**
|
|
172
|
+
* Match an `APICallError` by status code, message substring, or regular
|
|
173
|
+
* expression. Numbers match the status code; strings match the message;
|
|
174
|
+
* regular expressions match either the stringified status code or the
|
|
175
|
+
* message. Mix any combination in a single call; matches when any
|
|
176
|
+
* pattern matches.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* httpStatus(529)
|
|
180
|
+
* httpStatus(529, 'overloaded')
|
|
181
|
+
* httpStatus(/^5\d\d$/)
|
|
182
|
+
* httpStatus(529, 'overloaded', /rate.?limit/i)
|
|
183
|
+
*/
|
|
184
|
+
declare function httpStatus<MODEL extends AnyModel = AnyModel>(...patterns: Array<StatusPattern>): Condition<MODEL>;
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/retryables/experimental/no-image.d.ts
|
|
187
|
+
/**
|
|
188
|
+
* Match when image generation produced no images
|
|
189
|
+
* (`NoImageGeneratedError`).
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* noImage().switch({ model: fallback })
|
|
193
|
+
*/
|
|
194
|
+
declare function noImage<MODEL extends ImageModel = ImageModel>(): Condition<MODEL>;
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/retryables/experimental/not.d.ts
|
|
197
|
+
/**
|
|
198
|
+
* Invert a condition.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* not(error.isRetryable(true))
|
|
202
|
+
*/
|
|
203
|
+
declare function not<MODEL extends AnyModel>(condition: Condition<MODEL>): Condition<MODEL>;
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/retryables/experimental/or.d.ts
|
|
206
|
+
/**
|
|
207
|
+
* Match when any of the given conditions match. Evaluates left to right
|
|
208
|
+
* and stops on the first match.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* or(httpStatus(429), error.message('overloaded'))
|
|
212
|
+
*/
|
|
213
|
+
declare function or<MODEL extends AnyModel>(...conditions: Array<Condition<MODEL>>): Condition<MODEL>;
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/retryables/experimental/result.d.ts
|
|
216
|
+
/**
|
|
217
|
+
* Build a condition from a predicate over the current generate result.
|
|
218
|
+
* Available for language models only. The predicate runs only when the
|
|
219
|
+
* current attempt succeeded; error attempts return false.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* result<MODEL>((res) => res.finishReason.unified === 'length')
|
|
223
|
+
*/
|
|
224
|
+
declare function result<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(predicate: (res: LanguageModelGenerate, ctx: RetryContext<MODEL>) => boolean | Promise<boolean>): Condition<MODEL>;
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/retryables/experimental/schema-invalid.d.ts
|
|
227
|
+
/**
|
|
228
|
+
* Match when the result text fails JSON schema validation. The schema is
|
|
229
|
+
* read from the call's `responseFormat`, which `Output.object()` sets
|
|
230
|
+
* automatically. No-op when no schema is configured.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* schemaInvalid().switch({ model: fallback })
|
|
234
|
+
*/
|
|
235
|
+
declare function schemaInvalid<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(): Condition<MODEL>;
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/retryables/experimental/timeout.d.ts
|
|
238
|
+
/**
|
|
239
|
+
* Match a timeout error: an `Error` with `name === 'TimeoutError'`,
|
|
240
|
+
* which `AbortSignal.timeout()` produces when the timeout fires.
|
|
241
|
+
* Distinct from `aborted()`, which matches manual aborts.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* timeout().switch({ model: fallback, timeout: 60_000 })
|
|
245
|
+
*/
|
|
246
|
+
declare function timeout<MODEL extends AnyModel = AnyModel>(): Condition<MODEL>;
|
|
247
|
+
//#endregion
|
|
248
|
+
export { AnyModel, Condition, Predicate, RetryOptions, StatusPattern, SwitchTarget, aborted, and, error, finishReason, httpStatus, noImage, not, or, result, schemaInvalid, timeout };
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { l as isResultAttempt, r as isErrorAttempt } from "../../utils-CfnsSGrw.mjs";
|
|
2
|
+
import { n as parseRetryHeaders, t as MAX_RETRY_AFTER_MS } from "../../parse-retry-headers-DIPVbwW5.mjs";
|
|
3
|
+
import { APICallError, NoImageGeneratedError } from "ai";
|
|
4
|
+
import { safeParseJSON } from "@ai-sdk/provider-utils";
|
|
5
|
+
import { fromJSONSchema } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/retryables/experimental/condition.ts
|
|
8
|
+
/**
|
|
9
|
+
* A predicate over a `RetryContext` paired with two terminal actions
|
|
10
|
+
* (`switch`, `retry`) that turn it into a `Retryable<MODEL>`. Compose
|
|
11
|
+
* conditions with `and`, `or`, `not`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const cond = httpStatus(429, 503);
|
|
15
|
+
* cond.switch({ model: fallback });
|
|
16
|
+
* cond.retry({ delay: 1000 });
|
|
17
|
+
*/
|
|
18
|
+
var Condition = class Condition {
|
|
19
|
+
constructor(predicate) {
|
|
20
|
+
this.predicate = predicate;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run the predicate against a context and resolve to a boolean.
|
|
24
|
+
*/
|
|
25
|
+
async evaluate(ctx) {
|
|
26
|
+
return this.predicate(ctx);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Switch to a different model when the condition matches.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* httpStatus(529).switch({ model: fallback })
|
|
33
|
+
*/
|
|
34
|
+
switch(target) {
|
|
35
|
+
return async (ctx) => {
|
|
36
|
+
if (!await this.evaluate(ctx)) return void 0;
|
|
37
|
+
return {
|
|
38
|
+
maxAttempts: 1,
|
|
39
|
+
...target
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Retry the same model when the condition matches. Honors
|
|
45
|
+
* `Retry-After` and `Retry-After-Ms` response headers when present,
|
|
46
|
+
* capped at 60 seconds, overriding any provided `delay`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* error.isRetryable(true).retry({ delay: 1000, backoffFactor: 2 })
|
|
50
|
+
*/
|
|
51
|
+
retry(options) {
|
|
52
|
+
return async (ctx) => {
|
|
53
|
+
if (!await this.evaluate(ctx)) return void 0;
|
|
54
|
+
const model = ctx.current.model;
|
|
55
|
+
if (isErrorAttempt(ctx.current)) {
|
|
56
|
+
const { error: err } = ctx.current;
|
|
57
|
+
if (APICallError.isInstance(err)) {
|
|
58
|
+
const headerDelay = parseRetryHeaders(err.responseHeaders);
|
|
59
|
+
if (headerDelay !== null) return {
|
|
60
|
+
model,
|
|
61
|
+
...options,
|
|
62
|
+
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
|
|
63
|
+
backoffFactor: 1
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
model,
|
|
69
|
+
...options
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Combine with another condition; matches when both match.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* httpStatus(429).and(error.message('overloaded'))
|
|
78
|
+
*/
|
|
79
|
+
and(other) {
|
|
80
|
+
return new Condition(async (ctx) => await this.evaluate(ctx) && await other.evaluate(ctx));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Combine with another condition; matches when either matches.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* httpStatus(429).or(error.message('overloaded'))
|
|
87
|
+
*/
|
|
88
|
+
or(other) {
|
|
89
|
+
return new Condition(async (ctx) => await this.evaluate(ctx) || await other.evaluate(ctx));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Invert the condition.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* error.isRetryable(true).not()
|
|
96
|
+
*/
|
|
97
|
+
not() {
|
|
98
|
+
return new Condition(async (ctx) => !await this.evaluate(ctx));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/retryables/experimental/error.ts
|
|
104
|
+
/**
|
|
105
|
+
* Build a condition from a predicate over the current error. The
|
|
106
|
+
* predicate runs only when the current attempt failed with an error;
|
|
107
|
+
* result attempts return false.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* error<MODEL, APICallError>(
|
|
111
|
+
* (e) => APICallError.isInstance(e) && e.statusCode === 418,
|
|
112
|
+
* )
|
|
113
|
+
*/
|
|
114
|
+
function error(predicate) {
|
|
115
|
+
return new Condition(async (ctx) => {
|
|
116
|
+
if (!isErrorAttempt(ctx.current)) return false;
|
|
117
|
+
return predicate(ctx.current.error, ctx);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
(function(_error) {
|
|
121
|
+
function isRetryable(flag = true) {
|
|
122
|
+
return error((e) => APICallError.isInstance(e) && e.isRetryable === flag);
|
|
123
|
+
}
|
|
124
|
+
_error.isRetryable = isRetryable;
|
|
125
|
+
function statusCode(...patterns) {
|
|
126
|
+
return error((e) => {
|
|
127
|
+
if (!APICallError.isInstance(e)) return false;
|
|
128
|
+
const code = e.statusCode;
|
|
129
|
+
if (code === void 0) return false;
|
|
130
|
+
return patterns.some((p) => typeof p === "number" ? p === code : p.test(String(code)));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
_error.statusCode = statusCode;
|
|
134
|
+
function message(...patterns) {
|
|
135
|
+
return error((e) => {
|
|
136
|
+
if (!(e instanceof Error)) return false;
|
|
137
|
+
const lower = e.message.toLowerCase();
|
|
138
|
+
return patterns.some((p) => typeof p === "string" ? lower.includes(p.toLowerCase()) : p.test(e.message));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
_error.message = message;
|
|
142
|
+
})(error || (error = {}));
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/retryables/experimental/aborted.ts
|
|
146
|
+
/**
|
|
147
|
+
* Match a manual abort: an `Error` with `name === 'AbortError'`, which
|
|
148
|
+
* `controller.abort()` produces. Distinct from `timeout()`, which
|
|
149
|
+
* matches `AbortSignal.timeout()` firing.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* aborted().switch({ model: fallback })
|
|
153
|
+
*/
|
|
154
|
+
function aborted() {
|
|
155
|
+
return error((err) => err instanceof Error && err.name === "AbortError");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/retryables/experimental/and.ts
|
|
160
|
+
/**
|
|
161
|
+
* Match only when all of the given conditions match. Evaluates left to
|
|
162
|
+
* right and stops on the first miss.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* and(httpStatus(429), error.message('overloaded'))
|
|
166
|
+
*/
|
|
167
|
+
function and(...conditions) {
|
|
168
|
+
return new Condition(async (ctx) => {
|
|
169
|
+
for (const c of conditions) if (!await c.evaluate(ctx)) return false;
|
|
170
|
+
return true;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/retryables/experimental/result.ts
|
|
176
|
+
/**
|
|
177
|
+
* Build a condition from a predicate over the current generate result.
|
|
178
|
+
* Available for language models only. The predicate runs only when the
|
|
179
|
+
* current attempt succeeded; error attempts return false.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* result<MODEL>((res) => res.finishReason.unified === 'length')
|
|
183
|
+
*/
|
|
184
|
+
function result(predicate) {
|
|
185
|
+
return new Condition(async (ctx) => {
|
|
186
|
+
if (!isResultAttempt(ctx.current)) return false;
|
|
187
|
+
return predicate(ctx.current.result, ctx);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/retryables/experimental/finish-reason.ts
|
|
193
|
+
/**
|
|
194
|
+
* Match the result's finish reason against one of the given values.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* finishReason('content-filter')
|
|
198
|
+
* finishReason('content-filter', 'length')
|
|
199
|
+
*/
|
|
200
|
+
function finishReason(...reasons) {
|
|
201
|
+
return result((res) => reasons.includes(res.finishReason.unified));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/retryables/experimental/or.ts
|
|
206
|
+
/**
|
|
207
|
+
* Match when any of the given conditions match. Evaluates left to right
|
|
208
|
+
* and stops on the first match.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* or(httpStatus(429), error.message('overloaded'))
|
|
212
|
+
*/
|
|
213
|
+
function or(...conditions) {
|
|
214
|
+
return new Condition(async (ctx) => {
|
|
215
|
+
for (const c of conditions) if (await c.evaluate(ctx)) return true;
|
|
216
|
+
return false;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/retryables/experimental/http-status.ts
|
|
222
|
+
/**
|
|
223
|
+
* Match an `APICallError` by status code, message substring, or regular
|
|
224
|
+
* expression. Numbers match the status code; strings match the message;
|
|
225
|
+
* regular expressions match either the stringified status code or the
|
|
226
|
+
* message. Mix any combination in a single call; matches when any
|
|
227
|
+
* pattern matches.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* httpStatus(529)
|
|
231
|
+
* httpStatus(529, 'overloaded')
|
|
232
|
+
* httpStatus(/^5\d\d$/)
|
|
233
|
+
* httpStatus(529, 'overloaded', /rate.?limit/i)
|
|
234
|
+
*/
|
|
235
|
+
function httpStatus(...patterns) {
|
|
236
|
+
const numbers = patterns.filter((p) => typeof p === "number");
|
|
237
|
+
const strings = patterns.filter((p) => typeof p === "string");
|
|
238
|
+
const regexes = patterns.filter((p) => p instanceof RegExp);
|
|
239
|
+
const conditions = [];
|
|
240
|
+
if (numbers.length || regexes.length) conditions.push(error.statusCode(...numbers, ...regexes));
|
|
241
|
+
if (strings.length || regexes.length) conditions.push(error.message(...strings, ...regexes));
|
|
242
|
+
return or(...conditions);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/retryables/experimental/no-image.ts
|
|
247
|
+
/**
|
|
248
|
+
* Match when image generation produced no images
|
|
249
|
+
* (`NoImageGeneratedError`).
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* noImage().switch({ model: fallback })
|
|
253
|
+
*/
|
|
254
|
+
function noImage() {
|
|
255
|
+
return error((err) => NoImageGeneratedError.isInstance(err));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/retryables/experimental/not.ts
|
|
260
|
+
/**
|
|
261
|
+
* Invert a condition.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* not(error.isRetryable(true))
|
|
265
|
+
*/
|
|
266
|
+
function not(condition) {
|
|
267
|
+
return condition.not();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/retryables/experimental/schema-invalid.ts
|
|
272
|
+
/**
|
|
273
|
+
* Match when the result text fails JSON schema validation. The schema is
|
|
274
|
+
* read from the call's `responseFormat`, which `Output.object()` sets
|
|
275
|
+
* automatically. No-op when no schema is configured.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* schemaInvalid().switch({ model: fallback })
|
|
279
|
+
*/
|
|
280
|
+
function schemaInvalid() {
|
|
281
|
+
return result(async (res, ctx) => {
|
|
282
|
+
if (!isResultAttempt(ctx.current)) return false;
|
|
283
|
+
const callOptions = ctx.current.options;
|
|
284
|
+
const text = res.content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
285
|
+
if (!text) return false;
|
|
286
|
+
const responseFormat = callOptions.responseFormat;
|
|
287
|
+
if (responseFormat?.type !== "json" || !responseFormat.schema) return false;
|
|
288
|
+
return !(await safeParseJSON({
|
|
289
|
+
text,
|
|
290
|
+
schema: fromJSONSchema(responseFormat.schema)
|
|
291
|
+
})).success;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/retryables/experimental/timeout.ts
|
|
297
|
+
/**
|
|
298
|
+
* Match a timeout error: an `Error` with `name === 'TimeoutError'`,
|
|
299
|
+
* which `AbortSignal.timeout()` produces when the timeout fires.
|
|
300
|
+
* Distinct from `aborted()`, which matches manual aborts.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* timeout().switch({ model: fallback, timeout: 60_000 })
|
|
304
|
+
*/
|
|
305
|
+
function timeout() {
|
|
306
|
+
return error((err) => err instanceof Error && err.name === "TimeoutError");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
export { Condition, aborted, and, error, finishReason, httpStatus, noImage, not, or, result, schemaInvalid, timeout };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as RetryableOptions, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, s as ImageModel } from "../types-
|
|
1
|
+
import { N as RetryableOptions, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, s as ImageModel } from "../types-pGdkwtOE.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { c as isObject, f as isString, l as isResultAttempt, p as isTimeoutError, r as isErrorAttempt } from "../utils-CfnsSGrw.mjs";
|
|
2
|
+
import { n as parseRetryHeaders, t as MAX_RETRY_AFTER_MS } from "../parse-retry-headers-DIPVbwW5.mjs";
|
|
2
3
|
import { APICallError, NoImageGeneratedError } from "ai";
|
|
3
4
|
import { safeParseJSON } from "@ai-sdk/provider-utils";
|
|
4
5
|
import { fromJSONSchema } from "zod";
|
|
@@ -91,28 +92,8 @@ function requestTimeout(model, options) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/parse-retry-headers.ts
|
|
96
|
-
function parseRetryHeaders(headers) {
|
|
97
|
-
if (!headers) return null;
|
|
98
|
-
const retryAfterMs = headers["retry-after-ms"];
|
|
99
|
-
if (retryAfterMs) {
|
|
100
|
-
const delayMs = Number.parseFloat(retryAfterMs);
|
|
101
|
-
if (!Number.isNaN(delayMs) && delayMs >= 0) return delayMs;
|
|
102
|
-
}
|
|
103
|
-
const retryAfter = headers["retry-after"];
|
|
104
|
-
if (retryAfter) {
|
|
105
|
-
const seconds = Number.parseFloat(retryAfter);
|
|
106
|
-
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
107
|
-
const date = Date.parse(retryAfter);
|
|
108
|
-
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
95
|
//#endregion
|
|
114
96
|
//#region src/retryables/retry-after-delay.ts
|
|
115
|
-
const MAX_RETRY_AFTER_MS = 6e4;
|
|
116
97
|
/**
|
|
117
98
|
* Retry the current failed attempt with the same model, if the error is retryable.
|
|
118
99
|
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Retry and fallback mechanisms for AI SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./dist/index.mjs",
|
|
24
24
|
"./retryables": "./dist/retryables/index.mjs",
|
|
25
|
+
"./retryables/experimental": "./dist/retryables/experimental/index.mjs",
|
|
25
26
|
"./package.json": "./package.json"
|
|
26
27
|
},
|
|
27
28
|
"publishConfig": {
|
|
File without changes
|