ai-retry 1.1.0 → 1.3.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 +133 -23
- package/dist/index.d.mts +7 -5
- package/dist/index.mjs +262 -44
- package/dist/retryables/index.d.mts +13 -7
- package/dist/retryables/index.mjs +22 -3
- package/dist/{types-Bty5BU37.d.mts → types-wEZKtEcH.d.mts} +35 -14
- package/dist/{utils-fYGELi1C.mjs → utils-CfnsSGrw.mjs} +4 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Create a retryable model by providing a base model and a list of retryables or f
|
|
|
41
41
|
When an error occurs, it will evaluate each retryable in order and use the first one that indicates a retry should be attempted with a different model.
|
|
42
42
|
|
|
43
43
|
> [!NOTE]
|
|
44
|
-
> `ai-retry` supports
|
|
44
|
+
> `ai-retry` supports language models, embedding models, and image models.
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
47
|
import { openai } from '@ai-sdk/openai';
|
|
@@ -101,6 +101,28 @@ const result = await embed({
|
|
|
101
101
|
console.log(result.embedding);
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
This also works with image models:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { openai } from '@ai-sdk/openai';
|
|
108
|
+
import { generateImage } from 'ai';
|
|
109
|
+
import { createRetryable } from 'ai-retry';
|
|
110
|
+
|
|
111
|
+
const retryableModel = createRetryable({
|
|
112
|
+
model: openai.image('dall-e-3'),
|
|
113
|
+
retries: [
|
|
114
|
+
// Retry strategies and fallbacks...
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await generateImage({
|
|
119
|
+
model: retryableModel,
|
|
120
|
+
prompt: 'A sunset over mountains',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log(result.images);
|
|
124
|
+
```
|
|
125
|
+
|
|
104
126
|
#### Vercel AI Gateway
|
|
105
127
|
|
|
106
128
|
You can use `ai-retry` with Vercel AI Gateway by providing the model as a string. Internally, the model will be resolved with the default `gateway` [provider instance](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway#provider-instance) from AI SDK.
|
|
@@ -356,9 +378,9 @@ There are several built-in dynamic retryables available for common use cases:
|
|
|
356
378
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
357
379
|
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
358
380
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
359
|
-
- Use this retryable to handle Anthropic's overloaded errors.
|
|
360
381
|
- [`serviceUnavailable`](./src/retryables/service-unavailable.ts): Response with status code 503 (service unavailable).
|
|
361
382
|
- [`schemaMismatch`](./src/retryables/schema-mismatch.ts): Response JSON doesn't match the expected schema from structured output modes (`Output.object()`, `Output.array()`, `Output.choice()`).
|
|
383
|
+
- [`noImageGenerated`](./src/retryables/no-image-generated.ts): Image generation failed with `NoImageGeneratedError`.
|
|
362
384
|
|
|
363
385
|
#### Content Filter
|
|
364
386
|
|
|
@@ -412,9 +434,6 @@ const result = await generateText({
|
|
|
412
434
|
|
|
413
435
|
Handle service overload errors (status code 529) by switching to a provider.
|
|
414
436
|
|
|
415
|
-
> [!NOTE]
|
|
416
|
-
> You can use this retryable to handle Anthropic's overloaded errors.
|
|
417
|
-
|
|
418
437
|
```typescript
|
|
419
438
|
import { serviceOverloaded } from 'ai-retry/retryables';
|
|
420
439
|
|
|
@@ -453,6 +472,29 @@ const retryableModel = createRetryable({
|
|
|
453
472
|
});
|
|
454
473
|
```
|
|
455
474
|
|
|
475
|
+
#### No Image Generated
|
|
476
|
+
|
|
477
|
+
Handle image generation failures by switching to a different model.
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { openai } from '@ai-sdk/openai';
|
|
481
|
+
import { generateImage } from 'ai';
|
|
482
|
+
import { createRetryable } from 'ai-retry';
|
|
483
|
+
import { noImageGenerated } from 'ai-retry/retryables';
|
|
484
|
+
|
|
485
|
+
const retryableModel = createRetryable({
|
|
486
|
+
model: openai.image('dall-e-3'),
|
|
487
|
+
retries: [
|
|
488
|
+
noImageGenerated(openai.image('dall-e-2')), // Fallback to DALL-E 2
|
|
489
|
+
],
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const result = await generateImage({
|
|
493
|
+
model: retryableModel,
|
|
494
|
+
prompt: 'A sunset over mountains',
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
456
498
|
#### Request Not Retryable
|
|
457
499
|
|
|
458
500
|
Handle cases where the base model fails with a non-retryable error.
|
|
@@ -736,6 +778,17 @@ The following options can be overridden:
|
|
|
736
778
|
| [`headers`](https://ai-sdk.dev/docs/reference/ai-sdk-core/embed#headers) | Additional HTTP headers |
|
|
737
779
|
| [`providerOptions`](https://ai-sdk.dev/docs/reference/ai-sdk-core/embed#provideroptions) | Provider-specific options |
|
|
738
780
|
|
|
781
|
+
##### Image Model Options
|
|
782
|
+
|
|
783
|
+
| Option | Description |
|
|
784
|
+
|--------|-------------|
|
|
785
|
+
| [`n`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#n) | Number of images to generate |
|
|
786
|
+
| [`size`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#size) | Size of generated images |
|
|
787
|
+
| [`aspectRatio`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#aspectratio) | Aspect ratio of generated images |
|
|
788
|
+
| [`seed`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#seed) | Random seed for reproducibility |
|
|
789
|
+
| [`headers`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#headers) | Additional HTTP headers |
|
|
790
|
+
| [`providerOptions`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image#provideroptions) | Provider-specific options |
|
|
791
|
+
|
|
739
792
|
#### Logging
|
|
740
793
|
|
|
741
794
|
You can use the following callbacks to log retry attempts and errors:
|
|
@@ -757,6 +810,46 @@ const retryableModel = createRetryable({
|
|
|
757
810
|
});
|
|
758
811
|
```
|
|
759
812
|
|
|
813
|
+
#### Reset
|
|
814
|
+
|
|
815
|
+
By default, every new request starts with the base model, even if a previous request was retried with a different model. The `reset` option changes this behavior by making the last successfully retried model **sticky**, that means subsequent requests will continue using that model instead of switching back to the base model. The reset value controls how long the retry model stays sticky before resetting back to the base model.
|
|
816
|
+
|
|
817
|
+
| Value | Description |
|
|
818
|
+
|-------|-------------|
|
|
819
|
+
| `after-request` | Reset immediately after the next request (default) |
|
|
820
|
+
| `after-N-requests` | Keep the retry model for the next **N** requests, then reset |
|
|
821
|
+
| `after-N-seconds` | Keep the retry model for **N** seconds, then reset |
|
|
822
|
+
|
|
823
|
+
##### Reset after each request (default)
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
const retryableModel = createRetryable({
|
|
827
|
+
model: openai('gpt-4o-mini'),
|
|
828
|
+
retries: [anthropic('claude-sonnet-4-20250514')],
|
|
829
|
+
reset: 'after-request', // default: always start with the base model
|
|
830
|
+
});
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
##### Keep the retry model for N requests
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
const retryableModel = createRetryable({
|
|
837
|
+
model: openai('gpt-4o-mini'),
|
|
838
|
+
retries: [anthropic('claude-sonnet-4-20250514')],
|
|
839
|
+
reset: 'after-5-requests', // use the retry model for 5 more requests before resetting
|
|
840
|
+
});
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
##### Keep the retry model for N seconds
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
const retryableModel = createRetryable({
|
|
847
|
+
model: openai('gpt-4o-mini'),
|
|
848
|
+
retries: [anthropic('claude-sonnet-4-20250514')],
|
|
849
|
+
reset: 'after-30-seconds', // use the retry model for 30 seconds before resetting
|
|
850
|
+
});
|
|
851
|
+
```
|
|
852
|
+
|
|
760
853
|
### Streaming
|
|
761
854
|
|
|
762
855
|
Errors during streaming requests can occur in two ways:
|
|
@@ -768,15 +861,16 @@ In the second case, errors during stream processing will not always be retried,
|
|
|
768
861
|
|
|
769
862
|
### API Reference
|
|
770
863
|
|
|
771
|
-
#### `createRetryable(options: RetryableModelOptions):
|
|
864
|
+
#### `createRetryable(options: RetryableModelOptions): LanguageModelV3 | EmbeddingModelV3 | ImageModelV3`
|
|
772
865
|
|
|
773
|
-
Creates a retryable model that works with
|
|
866
|
+
Creates a retryable model that works with language models, embedding models, and image models.
|
|
774
867
|
|
|
775
868
|
```ts
|
|
776
|
-
interface RetryableModelOptions<MODEL extends
|
|
869
|
+
interface RetryableModelOptions<MODEL extends LanguageModelV3 | EmbeddingModelV3 | ImageModelV3> {
|
|
777
870
|
model: MODEL;
|
|
778
871
|
retries: Array<Retryable<MODEL> | MODEL>;
|
|
779
872
|
disabled?: boolean | (() => boolean);
|
|
873
|
+
reset?: Reset;
|
|
780
874
|
onError?: (context: RetryContext<MODEL>) => void;
|
|
781
875
|
onRetry?: (context: RetryContext<MODEL>) => void;
|
|
782
876
|
}
|
|
@@ -786,9 +880,25 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
786
880
|
- `model`: The base model to use for the initial request.
|
|
787
881
|
- `retries`: Array of retryables (functions, models, or retry objects) to attempt on failure.
|
|
788
882
|
- `disabled`: Disable all retry logic. Can be a boolean or function returning boolean. Default: `false` (retries enabled).
|
|
883
|
+
- `reset`: Controls when to reset back to the base model after a successful retry. Default: `after-request`.
|
|
789
884
|
- `onError`: Callback invoked when an error occurs.
|
|
790
885
|
- `onRetry`: Callback invoked before attempting a retry.
|
|
791
886
|
|
|
887
|
+
#### `Reset`
|
|
888
|
+
|
|
889
|
+
Controls when the sticky model resets back to the base model after a successful retry.
|
|
890
|
+
|
|
891
|
+
```ts
|
|
892
|
+
type Reset =
|
|
893
|
+
| 'after-request'
|
|
894
|
+
| `after-${number}-requests`
|
|
895
|
+
| `after-${number}-seconds`;
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
- `after-request` — reset immediately after the next request (default).
|
|
899
|
+
- `after-N-requests` — keep the retry model for the next N requests, then reset.
|
|
900
|
+
- `after-N-seconds` — keep the retry model for N seconds, then reset.
|
|
901
|
+
|
|
792
902
|
#### `Retryable`
|
|
793
903
|
|
|
794
904
|
A `Retryable` is a function that receives a `RetryContext` with the current error or result and model and all previous attempts.
|
|
@@ -802,17 +912,17 @@ type Retryable = (
|
|
|
802
912
|
|
|
803
913
|
#### `Retry`
|
|
804
914
|
|
|
805
|
-
A `Retry` specifies the model to retry and optional settings. The available options depend on the model type (language model or
|
|
915
|
+
A `Retry` specifies the model to retry and optional settings. The available options depend on the model type (language model, embedding model, or image model).
|
|
806
916
|
|
|
807
917
|
```typescript
|
|
808
918
|
interface Retry {
|
|
809
|
-
model:
|
|
919
|
+
model: LanguageModelV3 | EmbeddingModelV3 | ImageModelV3;
|
|
810
920
|
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
811
921
|
delay?: number; // Delay in milliseconds before retrying
|
|
812
922
|
backoffFactor?: number; // Multiplier for exponential backoff
|
|
813
923
|
timeout?: number; // Timeout in milliseconds for the retry attempt
|
|
814
924
|
providerOptions?: ProviderOptions; // @deprecated - use options.providerOptions instead
|
|
815
|
-
options?:
|
|
925
|
+
options?: LanguageModelV3CallOptions | EmbeddingModelV3CallOptions | ImageModelV3CallOptions; // Call options to override for this retry
|
|
816
926
|
}
|
|
817
927
|
```
|
|
818
928
|
|
|
@@ -832,22 +942,22 @@ interface RetryContext {
|
|
|
832
942
|
A `RetryAttempt` represents a single attempt with a specific model, which can be either an error or a successful result that triggered a retry. Each attempt includes the call options that were used for that specific attempt. For retry attempts, this will reflect any overridden options from the retry configuration.
|
|
833
943
|
|
|
834
944
|
```typescript
|
|
835
|
-
// For
|
|
945
|
+
// For language, embedding, and image models
|
|
836
946
|
type RetryAttempt =
|
|
837
|
-
| {
|
|
838
|
-
type: 'error';
|
|
839
|
-
error: unknown;
|
|
840
|
-
model:
|
|
841
|
-
options:
|
|
947
|
+
| {
|
|
948
|
+
type: 'error';
|
|
949
|
+
error: unknown;
|
|
950
|
+
model: LanguageModelV3 | EmbeddingModelV3 | ImageModelV3;
|
|
951
|
+
options: LanguageModelV3CallOptions | EmbeddingModelV3CallOptions | ImageModelV3CallOptions;
|
|
842
952
|
}
|
|
843
|
-
| {
|
|
844
|
-
type: 'result';
|
|
845
|
-
result:
|
|
846
|
-
model:
|
|
847
|
-
options:
|
|
953
|
+
| {
|
|
954
|
+
type: 'result';
|
|
955
|
+
result: LanguageModelV3Generate;
|
|
956
|
+
model: LanguageModelV3;
|
|
957
|
+
options: LanguageModelV3CallOptions;
|
|
848
958
|
};
|
|
849
959
|
|
|
850
|
-
// Note: Result-based retries only apply to language models, not embedding models
|
|
960
|
+
// Note: Result-based retries only apply to language models, not embedding or image models
|
|
851
961
|
|
|
852
962
|
// Type guards for discriminating attempts
|
|
853
963
|
function isErrorAttempt(attempt: RetryAttempt): attempt is RetryErrorAttempt;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as RetryableModelOptions, S as
|
|
1
|
+
import { C as RetryAttempt, D as Retryable, E as RetryResultAttempt, O as RetryableModelOptions, S as Retry, T as RetryErrorAttempt, _ as Reset, a as GatewayLanguageModelId, b as ResolvedModel, c as ImageModelGenerate, d as LanguageModelCallOptions, f as LanguageModelGenerate, g as ProviderOptions, h as LanguageModelStreamPart, i as EmbeddingModelRetryCallOptions, k as RetryableOptions, l as ImageModelRetryCallOptions, m as LanguageModelStream, n as EmbeddingModelCallOptions, o as ImageModel, p as LanguageModelRetryCallOptions, r as EmbeddingModelEmbed, s as ImageModelCallOptions, t as EmbeddingModel, u as LanguageModel, v as ResolvableLanguageModel, w as RetryContext, x as Retries, y as ResolvableModel } from "./types-wEZKtEcH.mjs";
|
|
2
2
|
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
3
|
|
|
4
4
|
//#region src/create-retryable-model.d.ts
|
|
@@ -7,19 +7,21 @@ declare function createRetryable<MODEL extends LanguageModel>(options: Omit<Retr
|
|
|
7
7
|
}): LanguageModel;
|
|
8
8
|
declare function createRetryable<MODEL extends LanguageModel>(options: RetryableModelOptions<MODEL>): LanguageModel;
|
|
9
9
|
declare function createRetryable<MODEL extends EmbeddingModel>(options: RetryableModelOptions<MODEL>): EmbeddingModel;
|
|
10
|
+
declare function createRetryable<MODEL extends ImageModel>(options: RetryableModelOptions<MODEL>): ImageModel;
|
|
10
11
|
//#endregion
|
|
11
12
|
//#region src/get-model-key.d.ts
|
|
12
13
|
/**
|
|
13
|
-
* Generate a unique key for a
|
|
14
|
+
* Generate a unique key for a model instance.
|
|
14
15
|
*/
|
|
15
|
-
declare const getModelKey: (model: LanguageModel | EmbeddingModel) => string;
|
|
16
|
+
declare const getModelKey: (model: LanguageModel | EmbeddingModel | ImageModel) => string;
|
|
16
17
|
//#endregion
|
|
17
18
|
//#region src/utils.d.ts
|
|
18
19
|
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
19
20
|
declare const isString: (value: unknown) => value is string;
|
|
20
|
-
declare const isModel: (model: unknown) => model is LanguageModel | EmbeddingModel;
|
|
21
|
+
declare const isModel: (model: unknown) => model is LanguageModel | EmbeddingModel | ImageModel;
|
|
21
22
|
declare const isLanguageModel: (model: unknown) => model is LanguageModel;
|
|
22
23
|
declare const isEmbeddingModel: (model: unknown) => model is EmbeddingModel;
|
|
24
|
+
declare const isImageModel: (model: unknown) => model is ImageModel;
|
|
23
25
|
declare const isStreamResult: (result: LanguageModelGenerate | LanguageModelStream) => result is LanguageModelStream;
|
|
24
26
|
declare const isGenerateResult: (result: LanguageModelGenerate | LanguageModelStream) => result is LanguageModelGenerate;
|
|
25
27
|
/**
|
|
@@ -73,4 +75,4 @@ declare const isAbortError: (error: unknown) => boolean;
|
|
|
73
75
|
*/
|
|
74
76
|
declare const isTimeoutError: (error: unknown) => boolean;
|
|
75
77
|
//#endregion
|
|
76
|
-
export { EmbeddingModel, EmbeddingModelCallOptions, EmbeddingModelEmbed, EmbeddingModelRetryCallOptions, GatewayLanguageModelId, LanguageModel, LanguageModelCallOptions, LanguageModelGenerate, LanguageModelRetryCallOptions, LanguageModelStream, LanguageModelStreamPart, ProviderOptions, ResolvableLanguageModel, ResolvableModel, ResolvedModel, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isAbortError, isEmbeddingModel, isErrorAttempt, isGenerateResult, isLanguageModel, isModel, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString, isTimeoutError };
|
|
78
|
+
export { EmbeddingModel, EmbeddingModelCallOptions, EmbeddingModelEmbed, EmbeddingModelRetryCallOptions, GatewayLanguageModelId, ImageModel, ImageModelCallOptions, ImageModelGenerate, ImageModelRetryCallOptions, LanguageModel, LanguageModelCallOptions, LanguageModelGenerate, LanguageModelRetryCallOptions, LanguageModelStream, LanguageModelStreamPart, ProviderOptions, Reset, ResolvableLanguageModel, ResolvableModel, ResolvedModel, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isAbortError, isEmbeddingModel, isErrorAttempt, isGenerateResult, isImageModel, isLanguageModel, isModel, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString, isTimeoutError };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,88 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as isImageModel, c as isObject, d as isStreamResult, f as isString, i as isGenerateResult, l as isResultAttempt, n as isEmbeddingModel, o as isLanguageModel, p as isTimeoutError, r as isErrorAttempt, s as isModel, t as isAbortError, u as isStreamContentPart } from "./utils-CfnsSGrw.mjs";
|
|
2
2
|
import { RetryError, gateway } from "ai";
|
|
3
3
|
import { delay } from "@ai-sdk/provider-utils";
|
|
4
4
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
5
5
|
|
|
6
|
+
//#region src/parse-reset.ts
|
|
7
|
+
/**
|
|
8
|
+
* Parses a `Reset` string into a structured object.
|
|
9
|
+
*
|
|
10
|
+
* `'after-request'` is treated as `{ type: 'requests', count: 0 }`,
|
|
11
|
+
* meaning the sticky model expires immediately (default behavior).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* parseReset(`after-request`); // { type: 'requests', count: 0 }
|
|
15
|
+
* parseReset(`after-5-requests`); // { type: 'requests', count: 5 }
|
|
16
|
+
* parseReset(`after-30-seconds`); // { type: 'seconds', count: 30 }
|
|
17
|
+
*/
|
|
18
|
+
function parseReset(reset) {
|
|
19
|
+
if (reset === `after-request`) return {
|
|
20
|
+
type: `requests`,
|
|
21
|
+
count: 0
|
|
22
|
+
};
|
|
23
|
+
const requestsMatch = reset.match(/^after-(\d+)-requests$/);
|
|
24
|
+
if (requestsMatch) return {
|
|
25
|
+
type: `requests`,
|
|
26
|
+
count: Number.parseInt(requestsMatch[1], 10)
|
|
27
|
+
};
|
|
28
|
+
const secondsMatch = reset.match(/^after-(\d+)-seconds$/);
|
|
29
|
+
if (secondsMatch) return {
|
|
30
|
+
type: `seconds`,
|
|
31
|
+
count: Number.parseInt(secondsMatch[1], 10)
|
|
32
|
+
};
|
|
33
|
+
throw new Error(`Invalid reset option: ${reset}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/base-retryable-model.ts
|
|
38
|
+
var BaseRetryableModel = class {
|
|
39
|
+
baseModel;
|
|
40
|
+
currentModel;
|
|
41
|
+
options;
|
|
42
|
+
parsedReset;
|
|
43
|
+
/** The model that last succeeded via retry, used for subsequent requests. */
|
|
44
|
+
stickyState;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.options = options;
|
|
47
|
+
this.baseModel = options.model;
|
|
48
|
+
this.currentModel = options.model;
|
|
49
|
+
this.parsedReset = parseReset(options.reset ?? `after-request`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Determine which model to start the request with,
|
|
53
|
+
* considering the sticky model and reset policy.
|
|
54
|
+
*/
|
|
55
|
+
resolveStartModel() {
|
|
56
|
+
if (!this.stickyState) return this.baseModel;
|
|
57
|
+
if (this.parsedReset.type === `requests`) {
|
|
58
|
+
if (this.stickyState.requestsRemaining > 0) {
|
|
59
|
+
this.stickyState.requestsRemaining--;
|
|
60
|
+
return this.stickyState.model;
|
|
61
|
+
}
|
|
62
|
+
} else if (Date.now() - this.stickyState.setAt < this.parsedReset.count * 1e3) return this.stickyState.model;
|
|
63
|
+
this.stickyState = void 0;
|
|
64
|
+
return this.baseModel;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* After a successful request, update sticky model if a retry occurred.
|
|
68
|
+
*/
|
|
69
|
+
updateStickyModel(startModel) {
|
|
70
|
+
if (this.currentModel !== startModel) this.stickyState = {
|
|
71
|
+
model: this.currentModel,
|
|
72
|
+
setAt: Date.now(),
|
|
73
|
+
requestsRemaining: this.parsedReset.type === `requests` ? this.parsedReset.count : 0
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if retries are disabled
|
|
78
|
+
*/
|
|
79
|
+
isDisabled() {
|
|
80
|
+
if (this.options.disabled === void 0) return false;
|
|
81
|
+
return typeof this.options.disabled === `function` ? this.options.disabled() : this.options.disabled;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
6
86
|
//#region src/calculate-exponential-backoff.ts
|
|
7
87
|
/**
|
|
8
88
|
* Calculates the exponential backoff delay.
|
|
@@ -14,7 +94,7 @@ function calculateExponentialBackoff(baseDelay, backoffFactor = 1, attempts) {
|
|
|
14
94
|
//#endregion
|
|
15
95
|
//#region src/get-model-key.ts
|
|
16
96
|
/**
|
|
17
|
-
* Generate a unique key for a
|
|
97
|
+
* Generate a unique key for a model instance.
|
|
18
98
|
*/
|
|
19
99
|
const getModelKey = (model) => {
|
|
20
100
|
return `${model.provider}/${model.modelId}`;
|
|
@@ -30,7 +110,7 @@ function countModelAttempts(model, attempts) {
|
|
|
30
110
|
//#endregion
|
|
31
111
|
//#region src/resolve-model.ts
|
|
32
112
|
/**
|
|
33
|
-
* Resolve a model string via the AI SDK Gateway to a
|
|
113
|
+
* Resolve a model string via the AI SDK Gateway to a model instance
|
|
34
114
|
*/
|
|
35
115
|
function resolveModel(model) {
|
|
36
116
|
return isModel(model) ? model : gateway(model);
|
|
@@ -53,9 +133,20 @@ async function findRetryModel(retries, context) {
|
|
|
53
133
|
*/
|
|
54
134
|
for (const retry of applicableRetries) {
|
|
55
135
|
let retryModel;
|
|
56
|
-
if (typeof retry ===
|
|
57
|
-
|
|
58
|
-
|
|
136
|
+
if (typeof retry === `function`)
|
|
137
|
+
/**
|
|
138
|
+
* Function retryable - call it with context
|
|
139
|
+
* The function can be either Retryable<MODEL> or Retryable<ResolvableLanguageModel>
|
|
140
|
+
* At runtime, both work because the context is structurally compatible
|
|
141
|
+
* We use type assertion here because TypeScript can't prove the union type compatibility
|
|
142
|
+
*/
|
|
143
|
+
retryModel = await retry(context);
|
|
144
|
+
else if (isObject(retry) && `model` in retry)
|
|
145
|
+
/** Static Retry object */
|
|
146
|
+
retryModel = retry;
|
|
147
|
+
else
|
|
148
|
+
/** Plain model */
|
|
149
|
+
retryModel = { model: retry };
|
|
59
150
|
if (retryModel) {
|
|
60
151
|
/**
|
|
61
152
|
* The model can be string or an instance.
|
|
@@ -100,11 +191,8 @@ function prepareRetryError(error, attempts) {
|
|
|
100
191
|
|
|
101
192
|
//#endregion
|
|
102
193
|
//#region src/retryable-embedding-model.ts
|
|
103
|
-
var RetryableEmbeddingModel = class {
|
|
194
|
+
var RetryableEmbeddingModel = class extends BaseRetryableModel {
|
|
104
195
|
specificationVersion = "v3";
|
|
105
|
-
baseModel;
|
|
106
|
-
currentModel;
|
|
107
|
-
options;
|
|
108
196
|
get modelId() {
|
|
109
197
|
return this.currentModel.modelId;
|
|
110
198
|
}
|
|
@@ -117,18 +205,6 @@ var RetryableEmbeddingModel = class {
|
|
|
117
205
|
get supportsParallelCalls() {
|
|
118
206
|
return this.currentModel.supportsParallelCalls;
|
|
119
207
|
}
|
|
120
|
-
constructor(options) {
|
|
121
|
-
this.options = options;
|
|
122
|
-
this.baseModel = options.model;
|
|
123
|
-
this.currentModel = options.model;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Check if retries are disabled
|
|
127
|
-
*/
|
|
128
|
-
isDisabled() {
|
|
129
|
-
if (this.options.disabled === void 0) return false;
|
|
130
|
-
return typeof this.options.disabled === "function" ? this.options.disabled() : this.options.disabled;
|
|
131
|
-
}
|
|
132
208
|
/**
|
|
133
209
|
* Get the retry call options overrides from a retry configuration.
|
|
134
210
|
*/
|
|
@@ -238,9 +314,10 @@ var RetryableEmbeddingModel = class {
|
|
|
238
314
|
}
|
|
239
315
|
async doEmbed(callOptions) {
|
|
240
316
|
/**
|
|
241
|
-
*
|
|
317
|
+
* Resolve the starting model (base or sticky)
|
|
242
318
|
*/
|
|
243
|
-
|
|
319
|
+
const startModel = this.resolveStartModel();
|
|
320
|
+
this.currentModel = startModel;
|
|
244
321
|
/**
|
|
245
322
|
* If retries are disabled, bypass retry machinery entirely
|
|
246
323
|
*/
|
|
@@ -251,37 +328,169 @@ var RetryableEmbeddingModel = class {
|
|
|
251
328
|
},
|
|
252
329
|
callOptions
|
|
253
330
|
});
|
|
331
|
+
this.updateStickyModel(startModel);
|
|
254
332
|
return result;
|
|
255
333
|
}
|
|
256
334
|
};
|
|
257
335
|
|
|
258
336
|
//#endregion
|
|
259
|
-
//#region src/retryable-
|
|
260
|
-
var
|
|
337
|
+
//#region src/retryable-image-model.ts
|
|
338
|
+
var RetryableImageModel = class extends BaseRetryableModel {
|
|
261
339
|
specificationVersion = "v3";
|
|
262
|
-
baseModel;
|
|
263
|
-
currentModel;
|
|
264
|
-
options;
|
|
265
340
|
get modelId() {
|
|
266
341
|
return this.currentModel.modelId;
|
|
267
342
|
}
|
|
268
343
|
get provider() {
|
|
269
344
|
return this.currentModel.provider;
|
|
270
345
|
}
|
|
271
|
-
get
|
|
272
|
-
return this.currentModel.
|
|
346
|
+
get maxImagesPerCall() {
|
|
347
|
+
return this.currentModel.maxImagesPerCall;
|
|
273
348
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Get the retry call options overrides from a retry configuration.
|
|
351
|
+
*/
|
|
352
|
+
getRetryCallOptions(callOptions, currentRetry) {
|
|
353
|
+
const retryOptions = currentRetry?.options ?? {};
|
|
354
|
+
return {
|
|
355
|
+
...callOptions,
|
|
356
|
+
n: retryOptions.n ?? callOptions.n,
|
|
357
|
+
size: retryOptions.size ?? callOptions.size,
|
|
358
|
+
aspectRatio: retryOptions.aspectRatio ?? callOptions.aspectRatio,
|
|
359
|
+
seed: retryOptions.seed ?? callOptions.seed,
|
|
360
|
+
headers: retryOptions.headers ?? callOptions.headers,
|
|
361
|
+
providerOptions: retryOptions.providerOptions ?? currentRetry?.providerOptions ?? callOptions.providerOptions,
|
|
362
|
+
abortSignal: currentRetry?.timeout ? AbortSignal.timeout(currentRetry.timeout) : callOptions.abortSignal
|
|
363
|
+
};
|
|
278
364
|
}
|
|
279
365
|
/**
|
|
280
|
-
*
|
|
366
|
+
* Execute a function with retry logic for handling errors
|
|
281
367
|
*/
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
368
|
+
async withRetry(input) {
|
|
369
|
+
/**
|
|
370
|
+
* Track all attempts.
|
|
371
|
+
*/
|
|
372
|
+
const attempts = input.attempts ?? [];
|
|
373
|
+
/**
|
|
374
|
+
* Track current retry configuration.
|
|
375
|
+
*/
|
|
376
|
+
let currentRetry;
|
|
377
|
+
while (true) {
|
|
378
|
+
/**
|
|
379
|
+
* The previous attempt that triggered a retry, or undefined if this is the first attempt
|
|
380
|
+
*/
|
|
381
|
+
const previousAttempt = attempts.at(-1);
|
|
382
|
+
/**
|
|
383
|
+
* Call the onRetry handler if provided.
|
|
384
|
+
* Skip on the first attempt since no previous attempt exists yet.
|
|
385
|
+
*/
|
|
386
|
+
if (previousAttempt) {
|
|
387
|
+
const context = {
|
|
388
|
+
current: {
|
|
389
|
+
...previousAttempt,
|
|
390
|
+
model: this.currentModel
|
|
391
|
+
},
|
|
392
|
+
attempts: [...attempts]
|
|
393
|
+
};
|
|
394
|
+
this.options.onRetry?.(context);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get the retry call options overrides for this attempt
|
|
398
|
+
*/
|
|
399
|
+
const retryCallOptions = this.getRetryCallOptions(input.callOptions, currentRetry);
|
|
400
|
+
try {
|
|
401
|
+
return {
|
|
402
|
+
result: await input.fn(retryCallOptions),
|
|
403
|
+
attempts
|
|
404
|
+
};
|
|
405
|
+
} catch (error) {
|
|
406
|
+
/** Don't retry if user manually aborted the request. */
|
|
407
|
+
/** TimeoutError from AbortSignal.timeout() will still be handled by retry handlers. */
|
|
408
|
+
if (isAbortError(error)) throw error;
|
|
409
|
+
const { retryModel, attempt } = await this.handleError(error, attempts, retryCallOptions);
|
|
410
|
+
attempts.push(attempt);
|
|
411
|
+
if (retryModel.delay) {
|
|
412
|
+
/**
|
|
413
|
+
* Calculate exponential backoff delay based on the number of attempts for this specific model.
|
|
414
|
+
* The delay grows exponentially: baseDelay * backoffFactor^attempts
|
|
415
|
+
* Example: With delay=1000ms and backoffFactor=2:
|
|
416
|
+
* - Attempt 1: 1000ms
|
|
417
|
+
* - Attempt 2: 2000ms
|
|
418
|
+
* - Attempt 3: 4000ms
|
|
419
|
+
*/
|
|
420
|
+
const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
|
|
421
|
+
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: retryCallOptions.abortSignal });
|
|
422
|
+
}
|
|
423
|
+
this.currentModel = retryModel.model;
|
|
424
|
+
currentRetry = retryModel;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Handle an error and determine if a retry is needed
|
|
430
|
+
*/
|
|
431
|
+
async handleError(error, attempts, callOptions) {
|
|
432
|
+
const errorAttempt = {
|
|
433
|
+
type: "error",
|
|
434
|
+
error,
|
|
435
|
+
model: this.currentModel,
|
|
436
|
+
options: callOptions
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Save the current attempt
|
|
440
|
+
*/
|
|
441
|
+
const updatedAttempts = [...attempts, errorAttempt];
|
|
442
|
+
const context = {
|
|
443
|
+
current: errorAttempt,
|
|
444
|
+
attempts: updatedAttempts
|
|
445
|
+
};
|
|
446
|
+
this.options.onError?.(context);
|
|
447
|
+
const retryModel = await findRetryModel(this.options.retries, context);
|
|
448
|
+
/**
|
|
449
|
+
* Handler didn't return any models to try next, rethrow the error.
|
|
450
|
+
* If we retried the request, wrap the error into a `RetryError` for better visibility.
|
|
451
|
+
*/
|
|
452
|
+
if (!retryModel) {
|
|
453
|
+
if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
retryModel,
|
|
458
|
+
attempt: errorAttempt
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async doGenerate(callOptions) {
|
|
462
|
+
/**
|
|
463
|
+
* Resolve the starting model (base or sticky)
|
|
464
|
+
*/
|
|
465
|
+
const startModel = this.resolveStartModel();
|
|
466
|
+
this.currentModel = startModel;
|
|
467
|
+
/**
|
|
468
|
+
* If retries are disabled, bypass retry machinery entirely
|
|
469
|
+
*/
|
|
470
|
+
if (this.isDisabled()) return this.currentModel.doGenerate(callOptions);
|
|
471
|
+
const { result } = await this.withRetry({
|
|
472
|
+
fn: async (retryCallOptions) => {
|
|
473
|
+
return this.currentModel.doGenerate(retryCallOptions);
|
|
474
|
+
},
|
|
475
|
+
callOptions
|
|
476
|
+
});
|
|
477
|
+
this.updateStickyModel(startModel);
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/retryable-language-model.ts
|
|
484
|
+
var RetryableLanguageModel = class extends BaseRetryableModel {
|
|
485
|
+
specificationVersion = "v3";
|
|
486
|
+
get modelId() {
|
|
487
|
+
return this.currentModel.modelId;
|
|
488
|
+
}
|
|
489
|
+
get provider() {
|
|
490
|
+
return this.currentModel.provider;
|
|
491
|
+
}
|
|
492
|
+
get supportedUrls() {
|
|
493
|
+
return this.currentModel.supportedUrls;
|
|
285
494
|
}
|
|
286
495
|
/**
|
|
287
496
|
* Get the retry call options overrides from a retry configuration.
|
|
@@ -446,9 +655,10 @@ var RetryableLanguageModel = class {
|
|
|
446
655
|
}
|
|
447
656
|
async doGenerate(callOptions) {
|
|
448
657
|
/**
|
|
449
|
-
*
|
|
658
|
+
* Resolve the starting model (base or sticky)
|
|
450
659
|
*/
|
|
451
|
-
|
|
660
|
+
const startModel = this.resolveStartModel();
|
|
661
|
+
this.currentModel = startModel;
|
|
452
662
|
/**
|
|
453
663
|
* If retries are disabled, bypass retry machinery entirely
|
|
454
664
|
*/
|
|
@@ -459,13 +669,15 @@ var RetryableLanguageModel = class {
|
|
|
459
669
|
},
|
|
460
670
|
callOptions
|
|
461
671
|
});
|
|
672
|
+
this.updateStickyModel(startModel);
|
|
462
673
|
return result;
|
|
463
674
|
}
|
|
464
675
|
async doStream(callOptions) {
|
|
465
676
|
/**
|
|
466
|
-
*
|
|
677
|
+
* Resolve the starting model (base or sticky)
|
|
467
678
|
*/
|
|
468
|
-
|
|
679
|
+
const startModel = this.resolveStartModel();
|
|
680
|
+
this.currentModel = startModel;
|
|
469
681
|
/**
|
|
470
682
|
* If retries are disabled, bypass retry machinery entirely
|
|
471
683
|
*/
|
|
@@ -479,6 +691,7 @@ var RetryableLanguageModel = class {
|
|
|
479
691
|
},
|
|
480
692
|
callOptions
|
|
481
693
|
});
|
|
694
|
+
this.updateStickyModel(startModel);
|
|
482
695
|
/**
|
|
483
696
|
* Track the current retry model for computing call options in the stream handler
|
|
484
697
|
*/
|
|
@@ -554,6 +767,7 @@ var RetryableLanguageModel = class {
|
|
|
554
767
|
await reader?.cancel();
|
|
555
768
|
result = retriedResult.result;
|
|
556
769
|
attempts = retriedResult.attempts;
|
|
770
|
+
this.updateStickyModel(startModel);
|
|
557
771
|
} finally {
|
|
558
772
|
reader?.releaseLock();
|
|
559
773
|
}
|
|
@@ -573,6 +787,10 @@ function createRetryable(options) {
|
|
|
573
787
|
...options,
|
|
574
788
|
model
|
|
575
789
|
});
|
|
790
|
+
if (isImageModel(model)) return new RetryableImageModel({
|
|
791
|
+
...options,
|
|
792
|
+
model
|
|
793
|
+
});
|
|
576
794
|
return new RetryableLanguageModel({
|
|
577
795
|
...options,
|
|
578
796
|
model
|
|
@@ -580,4 +798,4 @@ function createRetryable(options) {
|
|
|
580
798
|
}
|
|
581
799
|
|
|
582
800
|
//#endregion
|
|
583
|
-
export { createRetryable, getModelKey, isAbortError, isEmbeddingModel, isErrorAttempt, isGenerateResult, isLanguageModel, isModel, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString, isTimeoutError };
|
|
801
|
+
export { createRetryable, getModelKey, isAbortError, isEmbeddingModel, isErrorAttempt, isGenerateResult, isImageModel, isLanguageModel, isModel, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString, isTimeoutError };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as Retryable, k as RetryableOptions, o as ImageModel, t as EmbeddingModel, v as ResolvableLanguageModel } from "../types-wEZKtEcH.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
/**
|
|
@@ -6,11 +6,17 @@ import { S as Retryable, p as ResolvableLanguageModel, t as EmbeddingModel, w as
|
|
|
6
6
|
*/
|
|
7
7
|
declare function contentFilterTriggered<MODEL extends ResolvableLanguageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
8
8
|
//#endregion
|
|
9
|
+
//#region src/retryables/no-image-generated.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* Fallback to a different model if image generation fails with NoImageGeneratedError.
|
|
12
|
+
*/
|
|
13
|
+
declare function noImageGenerated<MODEL extends ImageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
14
|
+
//#endregion
|
|
9
15
|
//#region src/retryables/request-not-retryable.d.ts
|
|
10
16
|
/**
|
|
11
17
|
* Fallback to a different model if the error is non-retryable.
|
|
12
18
|
*/
|
|
13
|
-
declare function requestNotRetryable<MODEL extends ResolvableLanguageModel | EmbeddingModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
19
|
+
declare function requestNotRetryable<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
14
20
|
//#endregion
|
|
15
21
|
//#region src/retryables/request-timeout.d.ts
|
|
16
22
|
/**
|
|
@@ -18,7 +24,7 @@ declare function requestNotRetryable<MODEL extends ResolvableLanguageModel | Emb
|
|
|
18
24
|
* Use in combination with the `abortSignal` option.
|
|
19
25
|
* If no timeout is specified, a default of 60 seconds is used.
|
|
20
26
|
*/
|
|
21
|
-
declare function requestTimeout<MODEL extends ResolvableLanguageModel | EmbeddingModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
27
|
+
declare function requestTimeout<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
22
28
|
//#endregion
|
|
23
29
|
//#region src/retryables/retry-after-delay.d.ts
|
|
24
30
|
/**
|
|
@@ -26,7 +32,7 @@ declare function requestTimeout<MODEL extends ResolvableLanguageModel | Embeddin
|
|
|
26
32
|
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
|
27
33
|
* Otherwise uses the specified `delay` and `backoffFactor` if provided.
|
|
28
34
|
*/
|
|
29
|
-
declare function retryAfterDelay<MODEL extends ResolvableLanguageModel | EmbeddingModel>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
35
|
+
declare function retryAfterDelay<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
30
36
|
//#endregion
|
|
31
37
|
//#region src/retryables/schema-mismatch.d.ts
|
|
32
38
|
/**
|
|
@@ -58,13 +64,13 @@ declare function schemaMismatch<MODEL extends ResolvableLanguageModel>(model: MO
|
|
|
58
64
|
* - Response with `type: "overloaded_error"`
|
|
59
65
|
* - Response with a `message` containing "overloaded"
|
|
60
66
|
*/
|
|
61
|
-
declare function serviceOverloaded<MODEL extends ResolvableLanguageModel | EmbeddingModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
67
|
+
declare function serviceOverloaded<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
62
68
|
//#endregion
|
|
63
69
|
//#region src/retryables/service-unavailable.d.ts
|
|
64
70
|
/**
|
|
65
71
|
* Fallback to a different model if the provider returns a service unavailable error.
|
|
66
72
|
* This retryable handles HTTP status code 503 (Service Unavailable).
|
|
67
73
|
*/
|
|
68
|
-
declare function serviceUnavailable<MODEL extends ResolvableLanguageModel | EmbeddingModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
74
|
+
declare function serviceUnavailable<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
69
75
|
//#endregion
|
|
70
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
|
76
|
+
export { contentFilterTriggered, noImageGenerated, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { APICallError } from "ai";
|
|
1
|
+
import { c as isObject, f as isString, l as isResultAttempt, p as isTimeoutError, r as isErrorAttempt } from "../utils-CfnsSGrw.mjs";
|
|
2
|
+
import { APICallError, NoImageGeneratedError } from "ai";
|
|
3
3
|
import { safeParseJSON } from "@ai-sdk/provider-utils";
|
|
4
4
|
import { fromJSONSchema } from "zod";
|
|
5
5
|
|
|
@@ -29,6 +29,25 @@ function contentFilterTriggered(model, options) {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/retryables/no-image-generated.ts
|
|
34
|
+
/**
|
|
35
|
+
* Fallback to a different model if image generation fails with NoImageGeneratedError.
|
|
36
|
+
*/
|
|
37
|
+
function noImageGenerated(model, options) {
|
|
38
|
+
return (context) => {
|
|
39
|
+
const { current } = context;
|
|
40
|
+
if (isErrorAttempt(current)) {
|
|
41
|
+
const { error } = current;
|
|
42
|
+
if (NoImageGeneratedError.isInstance(error)) return {
|
|
43
|
+
model,
|
|
44
|
+
maxAttempts: 1,
|
|
45
|
+
...options
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
//#endregion
|
|
33
52
|
//#region src/retryables/request-not-retryable.ts
|
|
34
53
|
/**
|
|
@@ -219,4 +238,4 @@ function serviceUnavailable(model, options) {
|
|
|
219
238
|
}
|
|
220
239
|
|
|
221
240
|
//#endregion
|
|
222
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
|
241
|
+
export { contentFilterTriggered, noImageGenerated, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { gateway } from "ai";
|
|
2
|
-
import { EmbeddingModelV3, LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart, SharedV3ProviderOptions } from "@ai-sdk/provider";
|
|
2
|
+
import { EmbeddingModelV3, ImageModelV3, ImageModelV3CallOptions, LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart, SharedV3ProviderOptions } from "@ai-sdk/provider";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type Literals<T> = T extends string ? string extends T ? never : T : never;
|
|
6
6
|
type LanguageModel = LanguageModelV3;
|
|
7
7
|
type EmbeddingModel = EmbeddingModelV3;
|
|
8
|
+
type ImageModel = ImageModelV3;
|
|
8
9
|
type LanguageModelCallOptions = LanguageModelV3CallOptions;
|
|
9
10
|
type LanguageModelStreamPart = LanguageModelV3StreamPart;
|
|
11
|
+
type ImageModelCallOptions = ImageModelV3CallOptions;
|
|
10
12
|
type ProviderOptions = SharedV3ProviderOptions;
|
|
11
13
|
type GatewayLanguageModelId = Parameters<(typeof gateway)['languageModel']>[0];
|
|
12
14
|
type ResolvableLanguageModel = LanguageModel | Literals<GatewayLanguageModelId>;
|
|
13
|
-
type ResolvableModel<MODEL extends LanguageModel | EmbeddingModel> = MODEL extends LanguageModel ? ResolvableLanguageModel : EmbeddingModel;
|
|
14
|
-
type ResolvedModel<MODEL extends ResolvableLanguageModel | EmbeddingModel> = MODEL extends ResolvableLanguageModel ? LanguageModel : EmbeddingModel;
|
|
15
|
+
type ResolvableModel<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = MODEL extends LanguageModel ? ResolvableLanguageModel : MODEL extends EmbeddingModel ? EmbeddingModel : ImageModel;
|
|
16
|
+
type ResolvedModel<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = MODEL extends ResolvableLanguageModel ? LanguageModel : MODEL extends EmbeddingModel ? EmbeddingModel : ImageModel;
|
|
15
17
|
/**
|
|
16
18
|
* Call options that can be overridden during retry for language models.
|
|
17
19
|
*/
|
|
@@ -20,10 +22,14 @@ type LanguageModelRetryCallOptions = Partial<Pick<LanguageModelCallOptions, 'pro
|
|
|
20
22
|
* Call options that can be overridden during retry for embedding models.
|
|
21
23
|
*/
|
|
22
24
|
type EmbeddingModelRetryCallOptions = Partial<Pick<EmbeddingModelCallOptions, 'values' | 'headers' | 'providerOptions'>>;
|
|
25
|
+
/**
|
|
26
|
+
* Call options that can be overridden during retry for image models.
|
|
27
|
+
*/
|
|
28
|
+
type ImageModelRetryCallOptions = Partial<Pick<ImageModelCallOptions, 'n' | 'size' | 'aspectRatio' | 'seed' | 'headers' | 'providerOptions'>>;
|
|
23
29
|
/**
|
|
24
30
|
* A retry attempt with an error
|
|
25
31
|
*/
|
|
26
|
-
type RetryErrorAttempt<MODEL extends LanguageModel | EmbeddingModel> = {
|
|
32
|
+
type RetryErrorAttempt<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = {
|
|
27
33
|
type: 'error';
|
|
28
34
|
error: unknown;
|
|
29
35
|
result?: undefined;
|
|
@@ -31,7 +37,7 @@ type RetryErrorAttempt<MODEL extends LanguageModel | EmbeddingModel> = {
|
|
|
31
37
|
/**
|
|
32
38
|
* The call options used for this attempt.
|
|
33
39
|
*/
|
|
34
|
-
options: MODEL extends LanguageModel ? LanguageModelCallOptions : EmbeddingModelCallOptions;
|
|
40
|
+
options: MODEL extends LanguageModel ? LanguageModelCallOptions : MODEL extends EmbeddingModel ? EmbeddingModelCallOptions : ImageModelCallOptions;
|
|
35
41
|
};
|
|
36
42
|
/**
|
|
37
43
|
* A retry attempt with a successful result
|
|
@@ -49,11 +55,11 @@ type RetryResultAttempt = {
|
|
|
49
55
|
/**
|
|
50
56
|
* A retry attempt with either an error or a result and the model used
|
|
51
57
|
*/
|
|
52
|
-
type RetryAttempt<MODEL extends LanguageModel | EmbeddingModel> = RetryErrorAttempt<MODEL> | RetryResultAttempt
|
|
58
|
+
type RetryAttempt<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = MODEL extends LanguageModel ? RetryErrorAttempt<MODEL> | RetryResultAttempt : RetryErrorAttempt<MODEL>;
|
|
53
59
|
/**
|
|
54
60
|
* The context provided to Retryables with the current attempt and all previous attempts.
|
|
55
61
|
*/
|
|
56
|
-
type RetryContext<MODEL extends ResolvableLanguageModel | EmbeddingModel> = {
|
|
62
|
+
type RetryContext<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = {
|
|
57
63
|
/**
|
|
58
64
|
* Current attempt that caused the retry
|
|
59
65
|
*/
|
|
@@ -66,10 +72,16 @@ type RetryContext<MODEL extends ResolvableLanguageModel | EmbeddingModel> = {
|
|
|
66
72
|
/**
|
|
67
73
|
* Options for creating a retryable model.
|
|
68
74
|
*/
|
|
69
|
-
interface RetryableModelOptions<MODEL extends LanguageModel | EmbeddingModel> {
|
|
75
|
+
interface RetryableModelOptions<MODEL extends LanguageModel | EmbeddingModel | ImageModel> {
|
|
70
76
|
model: MODEL;
|
|
71
77
|
retries: Retries<MODEL>;
|
|
72
78
|
disabled?: boolean | (() => boolean);
|
|
79
|
+
/**
|
|
80
|
+
* Controls when to reset back to the base model after a successful retry.
|
|
81
|
+
*
|
|
82
|
+
* @default 'after-request'
|
|
83
|
+
*/
|
|
84
|
+
reset?: Reset;
|
|
73
85
|
onError?: (context: RetryContext<MODEL>) => void;
|
|
74
86
|
onRetry?: (context: RetryContext<MODEL>) => void;
|
|
75
87
|
}
|
|
@@ -84,7 +96,7 @@ interface RetryableModelOptions<MODEL extends LanguageModel | EmbeddingModel> {
|
|
|
84
96
|
* This flexible approach allows retryable functions to return the exact model type
|
|
85
97
|
* they received without type assertions, while still supporting string-based gateway models.
|
|
86
98
|
*/
|
|
87
|
-
type Retry<MODEL extends ResolvableLanguageModel | EmbeddingModel> = {
|
|
99
|
+
type Retry<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = {
|
|
88
100
|
model: MODEL;
|
|
89
101
|
/**
|
|
90
102
|
* Maximum number of attempts for this model.
|
|
@@ -106,7 +118,7 @@ type Retry<MODEL extends ResolvableLanguageModel | EmbeddingModel> = {
|
|
|
106
118
|
/**
|
|
107
119
|
* Call options to override for this retry.
|
|
108
120
|
*/
|
|
109
|
-
options?: MODEL extends LanguageModel ? Partial<LanguageModelRetryCallOptions> : Partial<EmbeddingModelRetryCallOptions>;
|
|
121
|
+
options?: MODEL extends LanguageModel ? Partial<LanguageModelRetryCallOptions> : MODEL extends EmbeddingModel ? Partial<EmbeddingModelRetryCallOptions> : Partial<ImageModelRetryCallOptions>;
|
|
110
122
|
/**
|
|
111
123
|
* @deprecated Use `options.providerOptions` instead.
|
|
112
124
|
* Provider options to override for this retry.
|
|
@@ -118,12 +130,21 @@ type Retry<MODEL extends ResolvableLanguageModel | EmbeddingModel> = {
|
|
|
118
130
|
/**
|
|
119
131
|
* A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
|
|
120
132
|
*/
|
|
121
|
-
type Retryable<MODEL extends ResolvableLanguageModel | EmbeddingModel> = (context: RetryContext<MODEL>) => Retry<MODEL> | Promise<Retry<MODEL> | undefined> | undefined;
|
|
122
|
-
type Retries<MODEL extends LanguageModel | EmbeddingModel> = Array<Retryable<ResolvableModel<MODEL>> | Retry<ResolvableModel<MODEL>> | ResolvableModel<MODEL>>;
|
|
123
|
-
type RetryableOptions<MODEL extends ResolvableLanguageModel | EmbeddingModel> = Partial<Omit<Retry<MODEL>, 'model'>>;
|
|
133
|
+
type Retryable<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = (context: RetryContext<MODEL>) => Retry<MODEL> | Promise<Retry<MODEL> | undefined> | undefined;
|
|
134
|
+
type Retries<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = Array<Retryable<ResolvableModel<MODEL>> | Retry<ResolvableModel<MODEL>> | ResolvableModel<MODEL>>;
|
|
135
|
+
type RetryableOptions<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = Partial<Omit<Retry<MODEL>, 'model'>>;
|
|
136
|
+
/**
|
|
137
|
+
* Controls when to reset the sticky model back to the base model.
|
|
138
|
+
*
|
|
139
|
+
* - `'after-request'` — reset after each request (default, current behavior)
|
|
140
|
+
* - `` `after-${number}-requests` `` — use the retry model for the next N requests
|
|
141
|
+
* - `` `after-${number}-seconds` `` — use the retry model for the next N seconds
|
|
142
|
+
*/
|
|
143
|
+
type Reset = 'after-request' | `after-${number}-requests` | `after-${number}-seconds`;
|
|
124
144
|
type LanguageModelGenerate = Awaited<ReturnType<LanguageModel['doGenerate']>>;
|
|
125
145
|
type LanguageModelStream = Awaited<ReturnType<LanguageModel['doStream']>>;
|
|
126
146
|
type EmbeddingModelCallOptions = Parameters<EmbeddingModel['doEmbed']>[0];
|
|
127
147
|
type EmbeddingModelEmbed = Awaited<ReturnType<EmbeddingModel['doEmbed']>>;
|
|
148
|
+
type ImageModelGenerate = Awaited<ReturnType<ImageModel['doGenerate']>>;
|
|
128
149
|
//#endregion
|
|
129
|
-
export {
|
|
150
|
+
export { RetryAttempt as C, Retryable as D, RetryResultAttempt as E, RetryableModelOptions as O, Retry as S, RetryErrorAttempt as T, Reset as _, GatewayLanguageModelId as a, ResolvedModel as b, ImageModelGenerate as c, LanguageModelCallOptions as d, LanguageModelGenerate as f, ProviderOptions as g, LanguageModelStreamPart as h, EmbeddingModelRetryCallOptions as i, RetryableOptions as k, ImageModelRetryCallOptions as l, LanguageModelStream as m, EmbeddingModelCallOptions as n, ImageModel as o, LanguageModelRetryCallOptions as p, EmbeddingModelEmbed as r, ImageModelCallOptions as s, EmbeddingModel as t, LanguageModel as u, ResolvableLanguageModel as v, RetryContext as w, Retries as x, ResolvableModel as y };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
//#region src/utils.ts
|
|
2
2
|
const isObject = (value) => typeof value === "object" && value !== null;
|
|
3
3
|
const isString = (value) => typeof value === "string";
|
|
4
|
-
const isModel = (model) => isLanguageModel(model) || isEmbeddingModel(model);
|
|
5
|
-
const isLanguageModel = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && "doGenerate" in model && model.specificationVersion === "v3";
|
|
4
|
+
const isModel = (model) => isLanguageModel(model) || isEmbeddingModel(model) || isImageModel(model);
|
|
5
|
+
const isLanguageModel = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && "doGenerate" in model && "doStream" in model && model.specificationVersion === "v3";
|
|
6
6
|
const isEmbeddingModel = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && "doEmbed" in model && model.specificationVersion === "v3";
|
|
7
|
+
const isImageModel = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && "doGenerate" in model && model.specificationVersion === "v3" && !("doStream" in model) && !("doEmbed" in model);
|
|
7
8
|
const isStreamResult = (result) => "stream" in result;
|
|
8
9
|
const isGenerateResult = (result) => "content" in result;
|
|
9
10
|
/**
|
|
@@ -38,4 +39,4 @@ const isAbortError = (error) => error instanceof Error && error.name === "AbortE
|
|
|
38
39
|
const isTimeoutError = (error) => error instanceof Error && error.name === "TimeoutError";
|
|
39
40
|
|
|
40
41
|
//#endregion
|
|
41
|
-
export {
|
|
42
|
+
export { isImageModel as a, isObject as c, isStreamResult as d, isString as f, isGenerateResult as i, isResultAttempt as l, isEmbeddingModel as n, isLanguageModel as o, isTimeoutError as p, isErrorAttempt as r, isModel as s, isAbortError as t, isStreamContentPart as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI SDK
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Retry and fallback mechanisms for AI SDK",
|
|
5
5
|
"types": "./dist/index.d.mts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@ai-sdk/anthropic": "3.0.23",
|
|
35
35
|
"@ai-sdk/azure": "3.0.19",
|
|
36
36
|
"@ai-sdk/gateway": "3.0.23",
|
|
37
|
+
"@ai-sdk/google": "^3.0.33",
|
|
37
38
|
"@ai-sdk/groq": "3.0.15",
|
|
38
39
|
"@ai-sdk/openai": "3.0.19",
|
|
39
40
|
"@ai-sdk/test-server": "1.0.3",
|