ai-retry 0.5.1 → 0.7.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 CHANGED
@@ -40,7 +40,8 @@ import { createRetryable } from 'ai-retry';
40
40
 
41
41
  // Create a retryable model
42
42
  const retryableModel = createRetryable({
43
- model: openai('gpt-4-mini'), // Base model
43
+ // Base model
44
+ model: openai('gpt-4-mini'),
44
45
  retries: [
45
46
  // Retry strategies and fallbacks...
46
47
  ],
@@ -74,7 +75,8 @@ import { createRetryable } from 'ai-retry';
74
75
 
75
76
  // Create a retryable model
76
77
  const retryableModel = createRetryable({
77
- model: openai.textEmbedding('text-embedding-3-large'), // Base model
78
+ // Base model
79
+ model: openai.textEmbedding('text-embedding-3-large'),
78
80
  retries: [
79
81
  // Retry strategies and fallbacks...
80
82
  ],
@@ -89,6 +91,165 @@ const result = await embed({
89
91
  console.log(result.embedding);
90
92
  ```
91
93
 
94
+ ### Retryables
95
+
96
+ The objects passed to the `retries` are called retryables and control the retry behavior. We can distinguish between two types of retryables:
97
+
98
+ - **Static retryables** are simply models instances (language or embedding) that will always be used when an error occurs. This is also called a fallback model.
99
+ - **Dynamic retryables** are functions that receive the current attempt context (error/result and previous attempts) and decide whether to retry with a different model based on custom logic.
100
+
101
+ You can think of `retries` as a big `if-else` block, where each dynamic retryable is an `if` condition that can match a certain error/result condition, and static retryables are the `else` branches that match all other conditions. The analogy is not perfect, because the order of retryables matters because `retries` are evaluated in order until one matches:
102
+
103
+ ```typescript
104
+ import { openai } from '@ai-sdk/openai';
105
+ import { generateText, streamText } from 'ai';
106
+ import { createRetryable } from 'ai-retry';
107
+
108
+ const retryableModel = createRetryable({
109
+ // Base model
110
+ model: openai('gpt-4-mini'),
111
+ // Retryables are evaluated in order
112
+ retries: [
113
+ // Dynamic retryable that matches only certain errors
114
+ (context) => {
115
+ return context.current.error.statusCode === 429
116
+ ? { model: openai('gpt-3.5-turbo') } // Retry with this model
117
+ : undefined; // Skip to next retryable
118
+ },
119
+
120
+ // Static retryable that always matches (fallback)
121
+ anthropic('claude-3-haiku-20240307'),
122
+ // Same as:
123
+ // { model: anthropic('claude-3-haiku-20240307'), maxAttempts: 1 }
124
+ ],
125
+ });
126
+ ```
127
+
128
+ In this example, if the base model fails with a 429 error, it will retry with `gpt-4`. In any other error case, it will fallback to `gpt-3.5-turbo`. If the order would be reversed, the static retryable would catch all errors first, and the dynamic retryable would never be reached.
129
+
130
+ #### Fallbacks
131
+
132
+ If you don't need precise error matching with custom logic and just want to fallback to different models on any error, you can simply provide a list of models.
133
+
134
+ > [!NOTE]
135
+ > Use the object syntax `{ model: openai('gpt-4') }` if you need to provide additional options like `maxAttempts`, `delay`, etc.
136
+
137
+ ```typescript
138
+ import { openai } from '@ai-sdk/openai';
139
+ import { generateText, streamText } from 'ai';
140
+ import { createRetryable } from 'ai-retry';
141
+
142
+ const retryableModel = createRetryable({
143
+ // Base model
144
+ model: openai('gpt-4-mini'),
145
+ // List of fallback models
146
+ retries: [
147
+ openai('gpt-3.5-turbo'), // Fallback for first error
148
+ // Same as:
149
+ // { model: openai('gpt-3.5-turbo'), maxAttempts: 1 },
150
+
151
+ anthropic('claude-3-haiku-20240307'), // Fallback for second error
152
+ // Same as:
153
+ // { model: anthropic('claude-3-haiku-20240307'), maxAttempts: 1 },
154
+ ],
155
+ });
156
+ ```
157
+
158
+ In this example, if the base model fails, it will retry with `gpt-3.5-turbo`. If that also fails, it will retry with `claude-3-haiku-20240307`. If that fails again, the whole retry process stops and a `RetryError` is thrown.
159
+
160
+ #### Custom
161
+
162
+ If you need more control over when to retry and which model to use, you can create your own custom retryable. This function is called with a context object containing the current attempt (error or result) and all previous attempts and needs to return a retry model or `undefined` to skip to the next retryable. The object you return from the retryable function is the same as the one you provide in the `retries` array.
163
+
164
+ > [!NOTE]
165
+ > You can return additional options like `maxAttempts`, `delay`, etc. along with the model.
166
+
167
+ ```typescript
168
+ import { anthropic } from '@ai-sdk/anthropic';
169
+ import { openai } from '@ai-sdk/openai';
170
+ import { APICallError } from 'ai';
171
+ import { createRetryable, isErrorAttempt } from 'ai-retry';
172
+ import type { Retryable } from 'ai-retry';
173
+
174
+ // Custom retryable that retries on rate limit errors (429)
175
+ const rateLimitRetry: Retryable = (context) => {
176
+ // Only handle error attempts
177
+ if (isErrorAttempt(context.current)) {
178
+ // Get the error from the current attempt
179
+ const { error } = context.current;
180
+
181
+ // Check for rate limit error
182
+ if (APICallError.isInstance(error) && error.statusCode === 429) {
183
+ // Retry with a different model
184
+ return { model: anthropic('claude-3-haiku-20240307') };
185
+ }
186
+ }
187
+
188
+ // Skip to next retryable
189
+ return undefined;
190
+ };
191
+
192
+ const retryableModel = createRetryable({
193
+ // Base model
194
+ model: openai('gpt-4-mini'),
195
+ retries: [
196
+ // Use custom rate limit retryable
197
+ rateLimitRetry
198
+
199
+ // Other retryables...
200
+ ],
201
+ });
202
+ ```
203
+
204
+ In this example, if the base model fails with a 429 error, it will retry with `claude-3-haiku-20240307`. For any other error, it will skip to the next retryable (if any) or throw the original error.
205
+
206
+ #### All Retries Failed
207
+
208
+ If all retry attempts failed, a `RetryError` is thrown containing all individual errors.
209
+ If no retry was attempted (e.g. because all retryables returned `undefined`), the original error is thrown directly.
210
+
211
+ ```typescript
212
+ import { RetryError } from 'ai';
213
+
214
+ const retryableModel = createRetryable({
215
+ // Base model = first attempt
216
+ model: azure('gpt-4-mini'),
217
+ retries: [
218
+ // Fallback model 1 = Second attempt
219
+ openai('gpt-3.5-turbo'),
220
+ // Fallback model 2 = Third attempt
221
+ anthropic('claude-3-haiku-20240307')
222
+ ],
223
+ });
224
+
225
+ try {
226
+ const result = await generateText({
227
+ model: retryableModel,
228
+ prompt: 'Hello world!',
229
+ });
230
+ } catch (error) {
231
+ // RetryError is an official AI SDK error
232
+ if (error instanceof RetryError) {
233
+ console.error('All retry attempts failed:', error.errors);
234
+ } else {
235
+ console.error('Request failed:', error);
236
+ }
237
+ }
238
+ ```
239
+
240
+ Errors are tracked per unique model (provider + modelId). That means on the first error, it will retry with `gpt-3.5-turbo`. If that also fails, it will retry with `claude-3-haiku-20240307`. If that fails again, the whole retry process stops and a `RetryError` is thrown.
241
+
242
+ ### Built-in Retryables
243
+
244
+ There are several built-in dynamic retryables available for common use cases:
245
+
246
+ - [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
247
+ - [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
248
+ - [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
249
+ - [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
250
+ - [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
251
+ - Use this retryable to handle Anthropic's overloaded errors.
252
+
92
253
  #### Content Filter
93
254
 
94
255
  Automatically switch to a different model when content filtering blocks your request.
@@ -195,82 +356,6 @@ const retryableModel = createRetryable({
195
356
 
196
357
  By default, if a [`retry-after-ms`](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/provisioned-get-started#what-should--i-do-when-i-receive-a-429-response) or [`retry-after`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) header is present in the response, it will be prioritized over the configured delay. The delay from the header will be capped at 60 seconds for safety.
197
358
 
198
- #### Fallbacks
199
-
200
- If you always want to fallback to a different model on any error, you can simply provide a list of models.
201
-
202
- ```typescript
203
- const retryableModel = createRetryable({
204
- model: azure('gpt-4'),
205
- retries: [
206
- openai('gpt-4'),
207
- anthropic('claude-3-haiku-20240307')
208
- ],
209
- });
210
- ```
211
-
212
- #### Custom
213
-
214
- Create your own retryables for specific use cases:
215
-
216
- ```typescript
217
- import { anthropic } from '@ai-sdk/anthropic';
218
- import { openai } from '@ai-sdk/openai';
219
- import { APICallError } from 'ai';
220
- import { createRetryable, isErrorAttempt } from 'ai-retry';
221
- import type { Retryable } from 'ai-retry';
222
-
223
- const rateLimitRetry: Retryable = (context) => {
224
- if (isErrorAttempt(context.current)) {
225
- const { error } = context.current;
226
-
227
- if (APICallError.isInstance(error) && error.statusCode === 429) {
228
- return { model: anthropic('claude-3-haiku-20240307') };
229
- }
230
- }
231
-
232
- return undefined;
233
- };
234
-
235
- const retryableModel = createRetryable({
236
- model: openai('gpt-4'),
237
- retries: [
238
- rateLimitRetry
239
- ],
240
- });
241
- ```
242
-
243
- #### All Retries Failed
244
-
245
- If all retry attempts failed, a `RetryError` is thrown containing all individual errors.
246
- If no retry was attempted (e.g. because all retryables returned `undefined`), the original error is thrown directly.
247
-
248
- ```typescript
249
- import { RetryError } from 'ai';
250
-
251
- const retryableModel = createRetryable({
252
- model: azure('gpt-4'),
253
- retries: [
254
- openai('gpt-4'),
255
- anthropic('claude-3-haiku-20240307')
256
- ],
257
- });
258
-
259
- try {
260
- const result = await generateText({
261
- model: retryableModel,
262
- prompt: 'Hello world!',
263
- });
264
- } catch (error) {
265
- // RetryError is an official AI SDK error
266
- if (error instanceof RetryError) {
267
- console.error('All retry attempts failed:', error.errors);
268
- } else {
269
- console.error('Request failed:', error);
270
- }
271
- }
272
- ```
273
-
274
359
  ### Options
275
360
 
276
361
  #### Retry Delays
@@ -282,10 +367,10 @@ const retryableModel = createRetryable({
282
367
  model: openai('gpt-4'),
283
368
  retries: [
284
369
  // Retry model 3 times with fixed 2s delay
285
- () => ({ model: openai('gpt-4'), delay: 2000, maxAttempts: 3 }),
370
+ { model: openai('gpt-4'), delay: 2000, maxAttempts: 3 },
286
371
 
287
372
  // Or retry with exponential backoff (2s, 4s, 8s)
288
- () => ({ model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
373
+ { model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 },
289
374
  ],
290
375
  });
291
376
 
@@ -322,7 +407,7 @@ const retryableModel = createRetryable({
322
407
  // Try this once
323
408
  anthropic('claude-3-haiku-20240307'),
324
409
  // Try this one more time (initial + 1 retry)
325
- () => ({ model: openai('gpt-4'), maxAttempts: 2 }),
410
+ { model: openai('gpt-4'), maxAttempts: 2 },
326
411
  // Already tried, won't be retried again
327
412
  anthropic('claude-3-haiku-20240307')
328
413
  ],
@@ -331,6 +416,41 @@ const retryableModel = createRetryable({
331
416
 
332
417
  The attempts are counted per unique model (provider + modelId). That means if multiple retryables return the same model, it won't be retried again once the `maxAttempts` is reached.
333
418
 
419
+ #### Provider Options
420
+
421
+ You can override provider-specific options for each retry attempt. This is useful when you want to use different configurations for fallback models.
422
+
423
+ ```typescript
424
+ const retryableModel = createRetryable({
425
+ model: openai('gpt-5'),
426
+ retries: [
427
+ // Use different provider options for the retry
428
+ {
429
+ model: openai('gpt-4o-2024-08-06'),
430
+ providerOptions: {
431
+ openai: {
432
+ user: 'fallback-user',
433
+ structuredOutputs: false,
434
+ },
435
+ },
436
+ },
437
+ ],
438
+ });
439
+
440
+ // Original provider options are used for the first attempt
441
+ const result = await generateText({
442
+ model: retryableModel,
443
+ prompt: 'Write a story',
444
+ providerOptions: {
445
+ openai: {
446
+ user: 'primary-user',
447
+ },
448
+ },
449
+ });
450
+ ```
451
+
452
+ The retry's `providerOptions` will completely replace the original ones during retry attempts. This works for all model types (language and embedding) and all operations (generate, stream, embed).
453
+
334
454
  #### Logging
335
455
 
336
456
  You can use the following callbacks to log retry attempts and errors:
@@ -361,18 +481,6 @@ Errors during streaming requests can occur in two ways:
361
481
 
362
482
  In the second case, errors during stream processing will not always be retried, because the stream might have already emitted some actual content and the consumer might have processed it. Retrying will be stopped as soon as the first content chunk (e.g. types of `text-delta`, `tool-call`, etc.) is emitted. The type of chunks considered as content are the same as the ones that are passed to [onChunk()](https://github.com/vercel/ai/blob/1fe4bd4144bff927f5319d9d206e782a73979ccb/packages/ai/src/generate-text/stream-text.ts#L684-L697).
363
483
 
364
- ### Retryables
365
-
366
- A retryable is a function that receives the current attempt and determines whether to retry with a different model based on the error/result and any previous attempts.
367
- There are several built-in retryables:
368
-
369
- - [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
370
- - [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
371
- - [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
372
- - [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
373
- - [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
374
- - Use this retryable to handle Anthropic's overloaded errors.
375
-
376
484
  ### API Reference
377
485
 
378
486
  #### `createRetryable(options: RetryableModelOptions): LanguageModelV2 | EmbeddingModelV2`
@@ -401,14 +509,15 @@ type Retryable = (
401
509
 
402
510
  #### `Retry`
403
511
 
404
- A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `backoffFactor`.
512
+ A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay`, `backoffFactor`, and `providerOptions`.
405
513
 
406
514
  ```typescript
407
515
  interface Retry {
408
516
  model: LanguageModelV2 | EmbeddingModelV2;
409
- maxAttempts?: number; // Maximum retry attempts per model (default: 1)
410
- delay?: number; // Delay in milliseconds before retrying
411
- backoffFactor?: number; // Multiplier for exponential backoff
517
+ maxAttempts?: number; // Maximum retry attempts per model (default: 1)
518
+ delay?: number; // Delay in milliseconds before retrying
519
+ backoffFactor?: number; // Multiplier for exponential backoff
520
+ providerOptions?: ProviderOptions; // Provider-specific options for the retry
412
521
  }
413
522
  ```
414
523
 
@@ -417,6 +526,7 @@ interface Retry {
417
526
  - `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
418
527
  - `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
419
528
  - `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
529
+ - `providerOptions`: Provider-specific options that override the original request's provider options during retry attempts.
420
530
 
421
531
  #### `RetryContext`
422
532
 
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as LanguageModelV2Generate, c as Retry, d as RetryErrorAttempt, f as RetryResultAttempt, h as RetryableOptions, i as LanguageModelV2, l as RetryAttempt, m as RetryableModelOptions, n as EmbeddingModelV2CallOptions, o as LanguageModelV2Stream, p as Retryable, r as EmbeddingModelV2Embed, s as Retries, t as EmbeddingModelV2, u as RetryContext } from "./types-DqwAmcZS.js";
1
+ import { a as LanguageModelV2Generate, c as Retry, d as RetryErrorAttempt, f as RetryResultAttempt, h as RetryableOptions, i as LanguageModelV2, l as RetryAttempt, m as RetryableModelOptions, n as EmbeddingModelV2CallOptions, o as LanguageModelV2Stream, p as Retryable, r as EmbeddingModelV2Embed, s as Retries, t as EmbeddingModelV2, u as RetryContext } from "./types-CfE400mD.js";
2
2
  import * as _ai_sdk_provider0 from "@ai-sdk/provider";
3
3
  import { LanguageModelV2 as LanguageModelV2$1, LanguageModelV2StreamPart } from "@ai-sdk/provider";
4
4
 
5
5
  //#region src/create-retryable-model.d.ts
6
- declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
7
- declare function createRetryable<MODEL extends EmbeddingModelV2>(options: RetryableModelOptions<MODEL>): EmbeddingModelV2;
6
+ declare function createRetryable<MODEL$1 extends LanguageModelV2>(options: RetryableModelOptions<MODEL$1>): LanguageModelV2;
7
+ declare function createRetryable<MODEL$1 extends EmbeddingModelV2>(options: RetryableModelOptions<MODEL$1>): EmbeddingModelV2;
8
8
  //#endregion
9
9
  //#region src/get-model-key.d.ts
10
10
  /**
@@ -66,5 +66,9 @@ declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is
66
66
  type: "raw";
67
67
  rawValue: unknown;
68
68
  };
69
+ /**
70
+ * Type guard to check if a value is a Retry object (has a model property with a MODEL)
71
+ */
72
+ declare const isRetry: <MODEL extends LanguageModelV2$1 | EmbeddingModelV2>(value: unknown) => value is Retry<MODEL>;
69
73
  //#endregion
70
- export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
74
+ export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isRetry, isStreamContentPart, isStreamResult, isString };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as isModelV2, c as isStreamContentPart, i as isLanguageModelV2, l as isStreamResult, n as isErrorAttempt, o as isObject, r as isGenerateResult, s as isResultAttempt, t as isEmbeddingModelV2, u as isString } from "./utils-BlCGaP0E.js";
1
+ import { a as isModelV2, c as isRetry, d as isString, i as isLanguageModelV2, l as isStreamContentPart, n as isErrorAttempt, o as isObject, r as isGenerateResult, s as isResultAttempt, t as isEmbeddingModelV2, u as isStreamResult } from "./utils-DsvLGk6a.js";
2
2
  import { delay } from "@ai-sdk/provider-utils";
3
3
  import { getErrorMessage } from "@ai-sdk/provider";
4
4
  import { RetryError } from "ai";
@@ -35,15 +35,18 @@ function countModelAttempts(model, attempts) {
35
35
  async function findRetryModel(retries, context) {
36
36
  /**
37
37
  * Filter retryables based on attempt type:
38
- * - Result-based attempts: Only consider function retryables (skip plain models)
39
- * - Error-based attempts: Consider all retryables (functions + plain models)
38
+ * - Result-based attempts: Only consider function retryables (skip plain models and static Retry objects)
39
+ * - Error-based attempts: Consider all retryables (functions + plain models + static Retry objects)
40
40
  */
41
41
  const applicableRetries = isResultAttempt(context.current) ? retries.filter((retry) => typeof retry === "function") : retries;
42
42
  /**
43
43
  * Iterate through the applicable retryables to find a model to retry with
44
44
  */
45
45
  for (const retry of applicableRetries) {
46
- const retryModel = typeof retry === "function" ? await retry(context) : { model: retry };
46
+ let retryModel;
47
+ if (typeof retry === "function") retryModel = await retry(context);
48
+ else if (isRetry(retry)) retryModel = retry;
49
+ else retryModel = { model: retry };
47
50
  if (retryModel) {
48
51
  /**
49
52
  * The model key uniquely identifies a model instance (provider + modelId)
@@ -109,6 +112,10 @@ var RetryableEmbeddingModel = class {
109
112
  * Track all attempts.
110
113
  */
111
114
  const attempts = input.attempts ?? [];
115
+ /**
116
+ * Track current retry configuration.
117
+ */
118
+ let currentRetry;
112
119
  while (true) {
113
120
  /**
114
121
  * The previous attempt that triggered a retry, or undefined if this is the first attempt
@@ -130,7 +137,7 @@ var RetryableEmbeddingModel = class {
130
137
  }
131
138
  try {
132
139
  return {
133
- result: await input.fn(),
140
+ result: await input.fn(currentRetry),
134
141
  attempts
135
142
  };
136
143
  } catch (error) {
@@ -149,6 +156,7 @@ var RetryableEmbeddingModel = class {
149
156
  await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
150
157
  }
151
158
  this.currentModel = retryModel.model;
159
+ currentRetry = retryModel;
152
160
  }
153
161
  }
154
162
  }
@@ -190,7 +198,13 @@ var RetryableEmbeddingModel = class {
190
198
  */
191
199
  this.currentModel = this.baseModel;
192
200
  const { result } = await this.withRetry({
193
- fn: async () => await this.currentModel.doEmbed(options),
201
+ fn: async (currentRetry) => {
202
+ const callOptions = {
203
+ ...options,
204
+ providerOptions: currentRetry?.providerOptions ?? options.providerOptions
205
+ };
206
+ return this.currentModel.doEmbed(callOptions);
207
+ },
194
208
  abortSignal: options.abortSignal
195
209
  });
196
210
  return result;
@@ -226,6 +240,10 @@ var RetryableLanguageModel = class {
226
240
  * Track all attempts.
227
241
  */
228
242
  const attempts = input.attempts ?? [];
243
+ /**
244
+ * Track current retry configuration.
245
+ */
246
+ let currentRetry;
229
247
  while (true) {
230
248
  /**
231
249
  * The previous attempt that triggered a retry, or undefined if this is the first attempt
@@ -249,7 +267,7 @@ var RetryableLanguageModel = class {
249
267
  /**
250
268
  * Call the function that may need to be retried
251
269
  */
252
- const result = await input.fn();
270
+ const result = await input.fn(currentRetry);
253
271
  /**
254
272
  * Check if the result should trigger a retry (only for generate results, not streams)
255
273
  */
@@ -270,6 +288,7 @@ var RetryableLanguageModel = class {
270
288
  await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
271
289
  }
272
290
  this.currentModel = retryModel.model;
291
+ currentRetry = retryModel;
273
292
  /**
274
293
  * Continue to the next iteration to retry
275
294
  */
@@ -292,6 +311,7 @@ var RetryableLanguageModel = class {
292
311
  await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
293
312
  }
294
313
  this.currentModel = retryModel.model;
314
+ currentRetry = retryModel;
295
315
  }
296
316
  }
297
317
  }
@@ -351,7 +371,13 @@ var RetryableLanguageModel = class {
351
371
  */
352
372
  this.currentModel = this.baseModel;
353
373
  const { result } = await this.withRetry({
354
- fn: async () => await this.currentModel.doGenerate(options),
374
+ fn: async (currentRetry) => {
375
+ const callOptions = {
376
+ ...options,
377
+ providerOptions: currentRetry?.providerOptions ?? options.providerOptions
378
+ };
379
+ return this.currentModel.doGenerate(callOptions);
380
+ },
355
381
  abortSignal: options.abortSignal
356
382
  });
357
383
  return result;
@@ -365,7 +391,13 @@ var RetryableLanguageModel = class {
365
391
  * Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
366
392
  */
367
393
  let { result, attempts } = await this.withRetry({
368
- fn: async () => await this.currentModel.doStream(options),
394
+ fn: async (currentRetry) => {
395
+ const callOptions = {
396
+ ...options,
397
+ providerOptions: currentRetry?.providerOptions ?? options.providerOptions
398
+ };
399
+ return this.currentModel.doStream(callOptions);
400
+ },
369
401
  abortSignal: options.abortSignal
370
402
  });
371
403
  /**
@@ -421,7 +453,13 @@ var RetryableLanguageModel = class {
421
453
  * This will create a new stream.
422
454
  */
423
455
  const retriedResult = await this.withRetry({
424
- fn: async () => await this.currentModel.doStream(options),
456
+ fn: async () => {
457
+ const callOptions = {
458
+ ...options,
459
+ providerOptions: retryModel.providerOptions ?? options.providerOptions
460
+ };
461
+ return this.currentModel.doStream(callOptions);
462
+ },
425
463
  attempts,
426
464
  abortSignal: options.abortSignal
427
465
  });
@@ -450,4 +488,4 @@ function createRetryable(options) {
450
488
  }
451
489
 
452
490
  //#endregion
453
- export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
491
+ export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isRetry, isStreamContentPart, isStreamResult, isString };
@@ -1,4 +1,4 @@
1
- import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-DqwAmcZS.js";
1
+ import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-CfE400mD.js";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { n as isErrorAttempt, o as isObject, s as isResultAttempt, u as isString } from "../utils-BlCGaP0E.js";
1
+ import { d as isString, n as isErrorAttempt, o as isObject, s as isResultAttempt } from "../utils-DsvLGk6a.js";
2
2
  import { isAbortError } from "@ai-sdk/provider-utils";
3
3
  import { APICallError } from "ai";
4
4
 
@@ -1,3 +1,4 @@
1
+ import { ProviderOptions } from "@ai-sdk/provider-utils";
1
2
  import { EmbeddingModelV2, LanguageModelV2 as LanguageModelV2$1 } from "@ai-sdk/provider";
2
3
 
3
4
  //#region src/types.d.ts
@@ -54,12 +55,13 @@ type Retry<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
54
55
  maxAttempts?: number;
55
56
  delay?: number;
56
57
  backoffFactor?: number;
58
+ providerOptions?: ProviderOptions;
57
59
  };
58
60
  /**
59
61
  * A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
60
62
  */
61
63
  type Retryable<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = (context: RetryContext<MODEL>) => Retry<MODEL> | Promise<Retry<MODEL>> | undefined;
62
- type Retries<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | MODEL>;
64
+ type Retries<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | Retry<MODEL> | MODEL>;
63
65
  type RetryableOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Partial<Omit<Retry<MODEL>, 'model'>>;
64
66
  type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2$1['doGenerate']>>;
65
67
  type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2$1['doStream']>>;
@@ -26,6 +26,10 @@ function isResultAttempt(attempt) {
26
26
  const isStreamContentPart = (part) => {
27
27
  return part.type === "text-delta" || part.type === "reasoning-delta" || part.type === "source" || part.type === "tool-call" || part.type === "tool-result" || part.type === "tool-input-start" || part.type === "tool-input-delta" || part.type === "raw";
28
28
  };
29
+ /**
30
+ * Type guard to check if a value is a Retry object (has a model property with a MODEL)
31
+ */
32
+ const isRetry = (value) => isObject(value) && "model" in value && isModelV2(value.model);
29
33
 
30
34
  //#endregion
31
- export { isModelV2 as a, isStreamContentPart as c, isLanguageModelV2 as i, isStreamResult as l, isErrorAttempt as n, isObject as o, isGenerateResult as r, isResultAttempt as s, isEmbeddingModelV2 as t, isString as u };
35
+ export { isModelV2 as a, isRetry as c, isString as d, isLanguageModelV2 as i, isStreamContentPart as l, isErrorAttempt as n, isObject as o, isGenerateResult as r, isResultAttempt as s, isEmbeddingModelV2 as t, isStreamResult as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "AI SDK Retry",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "@ai-sdk/anthropic": "^2.0.18",
37
37
  "@ai-sdk/azure": "^2.0.30",
38
38
  "@ai-sdk/groq": "^2.0.24",
39
- "@ai-sdk/openai": "^2.0.30",
39
+ "@ai-sdk/openai": "^2.0.53",
40
40
  "@arethetypeswrong/cli": "^0.18.2",
41
41
  "@biomejs/biome": "^2.2.4",
42
42
  "@total-typescript/tsconfig": "^1.0.4",