ai-retry 0.4.1 → 0.5.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
@@ -21,9 +21,6 @@ It supports two types of retries:
21
21
 
22
22
  This library only supports AI SDK v5.
23
23
 
24
- > [!WARNING]
25
- > `ai-retry` is in an early stage and the API may change in future releases.
26
-
27
24
  ```bash
28
25
  npm install ai-retry
29
26
  ```
@@ -197,11 +194,6 @@ const retryableModel = createRetryable({
197
194
  });
198
195
  ```
199
196
 
200
- **Options:**
201
- - `delay` (required): Delay in milliseconds before retrying
202
- - `backoffFactor` (optional): Multiplier for exponential backoff (delay × backoffFactor^attempt). If not provided, uses fixed delay.
203
- - `maxAttempts` (optional): Maximum number of retry attempts for this model
204
-
205
197
  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` 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. If no headers are present, the configured delay or exponential backoff will be used.
206
198
 
207
199
  #### Fallbacks
@@ -284,22 +276,17 @@ try {
284
276
 
285
277
  #### Retry Delays
286
278
 
287
- You can add delays before retrying to handle rate limiting or give services time to recover. The delay respects abort signals, so requests can still be cancelled during the delay period.
279
+ You can delay retries with an optional exponential backoff. The delay respects abort signals, so requests can still be cancelled during the delay period.
288
280
 
289
281
  ```typescript
290
282
  const retryableModel = createRetryable({
291
283
  model: openai('gpt-4'),
292
284
  retries: [
293
- // Wait 1 second before retrying
294
- () => ({
295
- model: openai('gpt-4'),
296
- delay: 1_000
297
- }),
298
- // Wait 2 seconds before trying a different provider
299
- () => ({
300
- model: anthropic('claude-3-haiku-20240307'),
301
- delay: 2_000
302
- }),
285
+ // Retry model 3 times with fixed 2s delay
286
+ () => ({ model: openai('gpt-4'), delay: 2000, maxAttempts: 3 }),
287
+
288
+ // Or retry with exponential backoff (2s, 4s, 8s)
289
+ () => ({ model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
303
290
  ],
304
291
  });
305
292
 
@@ -405,29 +392,32 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
405
392
  #### `Retryable`
406
393
 
407
394
  A `Retryable` is a function that receives a `RetryContext` with the current error or result and model and all previous attempts.
408
- It should evaluate the error/result and decide whether to retry by returning a `RetryModel` or to skip by returning `undefined`.
395
+ It should evaluate the error/result and decide whether to retry by returning a `Retry` or to skip by returning `undefined`.
409
396
 
410
397
  ```ts
411
398
  type Retryable = (
412
399
  context: RetryContext
413
- ) => RetryModel | Promise<RetryModel> | undefined;
400
+ ) => Retry | Promise<Retry> | undefined;
414
401
  ```
415
402
 
416
- #### `RetryModel`
403
+ #### `Retry`
417
404
 
418
- A `RetryModel` specifies the model to retry and optional settings like `maxAttempts` and `delay`.
405
+ A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `backoffFactor`.
419
406
 
420
407
  ```typescript
421
- interface RetryModel {
408
+ interface Retry {
422
409
  model: LanguageModelV2 | EmbeddingModelV2;
423
- maxAttempts?: number; // Maximum retry attempts per model (default: 1)
424
- delay?: number; // Delay in milliseconds before retrying
410
+ maxAttempts?: number; // Maximum retry attempts per model (default: 1)
411
+ delay?: number; // Delay in milliseconds before retrying
412
+ backoffFactor?: number; // Multiplier for exponential backoff
425
413
  }
426
414
  ```
427
415
 
428
416
  **Options:**
417
+ - `model`: The model to use for the retry attempt.
429
418
  - `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
430
- - `delay`: Delay in milliseconds to wait before retrying. Useful for rate limiting or giving services time to recover. The delay respects abort signals from the request.
419
+ - `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
420
+ - `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
431
421
 
432
422
  #### `RetryContext`
433
423
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions } from "./types-BrJaHkFh.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-DqwAmcZS.js";
2
2
  import * as _ai_sdk_provider0 from "@ai-sdk/provider";
3
- import { LanguageModelV2StreamPart } from "@ai-sdk/provider";
3
+ import { LanguageModelV2 as LanguageModelV2$1, LanguageModelV2StreamPart } from "@ai-sdk/provider";
4
4
 
5
5
  //#region src/create-retryable-model.d.ts
6
6
  declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
@@ -15,6 +15,9 @@ declare const getModelKey: (model: LanguageModelV2 | EmbeddingModelV2) => string
15
15
  //#region src/utils.d.ts
16
16
  declare const isObject: (value: unknown) => value is Record<string, unknown>;
17
17
  declare const isString: (value: unknown) => value is string;
18
+ declare const isModelV2: (model: unknown) => model is LanguageModelV2$1 | EmbeddingModelV2;
19
+ declare const isLanguageModelV2: (model: unknown) => model is LanguageModelV2$1;
20
+ declare const isEmbeddingModelV2: (model: unknown) => model is EmbeddingModelV2;
18
21
  declare const isStreamResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Stream;
19
22
  declare const isGenerateResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Generate;
20
23
  /**
@@ -64,4 +67,4 @@ declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is
64
67
  rawValue: unknown;
65
68
  };
66
69
  //#endregion
67
- export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions, createRetryable, getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
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 };
package/dist/index.js CHANGED
@@ -1,8 +1,33 @@
1
- import { getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString } from "./utils-Dojn0elD.js";
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";
2
2
  import { delay } from "@ai-sdk/provider-utils";
3
3
  import { getErrorMessage } from "@ai-sdk/provider";
4
4
  import { RetryError } from "ai";
5
5
 
6
+ //#region src/calculate-exponential-backoff.ts
7
+ /**
8
+ * Calculates the exponential backoff delay.
9
+ */
10
+ function calculateExponentialBackoff(baseDelay, backoffFactor = 1, attempts) {
11
+ return baseDelay * Math.max(backoffFactor, 1) ** attempts;
12
+ }
13
+
14
+ //#endregion
15
+ //#region src/get-model-key.ts
16
+ /**
17
+ * Generate a unique key for a LanguageModelV2 instance.
18
+ */
19
+ const getModelKey = (model) => {
20
+ return `${model.provider}/${model.modelId}`;
21
+ };
22
+
23
+ //#endregion
24
+ //#region src/count-model-attempts.ts
25
+ function countModelAttempts(model, attempts) {
26
+ const modelKey = getModelKey(model);
27
+ return attempts.filter((a) => getModelKey(a.model) === modelKey).length;
28
+ }
29
+
30
+ //#endregion
6
31
  //#region src/find-retry-model.ts
7
32
  /**
8
33
  * Find the next model to retry with based on the retry context
@@ -18,10 +43,7 @@ async function findRetryModel(retries, context) {
18
43
  * Iterate through the applicable retryables to find a model to retry with
19
44
  */
20
45
  for (const retry of applicableRetries) {
21
- const retryModel = typeof retry === "function" ? await retry(context) : {
22
- model: retry,
23
- maxAttempts: 1
24
- };
46
+ const retryModel = typeof retry === "function" ? await retry(context) : { model: retry };
25
47
  if (retryModel) {
26
48
  /**
27
49
  * The model key uniquely identifies a model instance (provider + modelId)
@@ -97,17 +119,12 @@ var RetryableEmbeddingModel = class {
97
119
  * Skip on the first attempt since no previous attempt exists yet.
98
120
  */
99
121
  if (previousAttempt) {
100
- const currentAttempt = {
101
- ...previousAttempt,
102
- model: this.currentModel
103
- };
104
- /**
105
- * Create a shallow copy of the attempts for testing purposes
106
- */
107
- const updatedAttempts = [...attempts];
108
122
  const context = {
109
- current: currentAttempt,
110
- attempts: updatedAttempts
123
+ current: {
124
+ ...previousAttempt,
125
+ model: this.currentModel
126
+ },
127
+ attempts: [...attempts]
111
128
  };
112
129
  this.options.onRetry?.(context);
113
130
  }
@@ -119,7 +136,18 @@ var RetryableEmbeddingModel = class {
119
136
  } catch (error) {
120
137
  const { retryModel, attempt } = await this.handleError(error, attempts);
121
138
  attempts.push(attempt);
122
- if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
139
+ if (retryModel.delay) {
140
+ /**
141
+ * Calculate exponential backoff delay based on the number of attempts for this specific model.
142
+ * The delay grows exponentially: baseDelay * backoffFactor^attempts
143
+ * Example: With delay=1000ms and backoffFactor=2:
144
+ * - Attempt 1: 1000ms
145
+ * - Attempt 2: 2000ms
146
+ * - Attempt 3: 4000ms
147
+ */
148
+ const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
149
+ await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
150
+ }
123
151
  this.currentModel = retryModel.model;
124
152
  }
125
153
  }
@@ -208,17 +236,12 @@ var RetryableLanguageModel = class {
208
236
  * Skip on the first attempt since no previous attempt exists yet.
209
237
  */
210
238
  if (previousAttempt) {
211
- const currentAttempt = {
212
- ...previousAttempt,
213
- model: this.currentModel
214
- };
215
- /**
216
- * Create a shallow copy of the attempts for testing purposes
217
- */
218
- const updatedAttempts = [...attempts];
219
239
  const context = {
220
- current: currentAttempt,
221
- attempts: updatedAttempts
240
+ current: {
241
+ ...previousAttempt,
242
+ model: this.currentModel
243
+ },
244
+ attempts: [...attempts]
222
245
  };
223
246
  this.options.onRetry?.(context);
224
247
  }
@@ -234,7 +257,18 @@ var RetryableLanguageModel = class {
234
257
  const { retryModel, attempt } = await this.handleResult(result, attempts);
235
258
  attempts.push(attempt);
236
259
  if (retryModel) {
237
- if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
260
+ if (retryModel.delay) {
261
+ /**
262
+ * Calculate exponential backoff delay based on the number of attempts for this specific model.
263
+ * The delay grows exponentially: baseDelay * backoffFactor^attempts
264
+ * Example: With delay=1000ms and backoffFactor=2:
265
+ * - Attempt 1: 1000ms
266
+ * - Attempt 2: 2000ms
267
+ * - Attempt 3: 4000ms
268
+ */
269
+ const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
270
+ await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
271
+ }
238
272
  this.currentModel = retryModel.model;
239
273
  /**
240
274
  * Continue to the next iteration to retry
@@ -249,7 +283,14 @@ var RetryableLanguageModel = class {
249
283
  } catch (error) {
250
284
  const { retryModel, attempt } = await this.handleError(error, attempts);
251
285
  attempts.push(attempt);
252
- if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
286
+ if (retryModel.delay) {
287
+ /**
288
+ * Calculate exponential backoff delay based on the number of attempts for this specific model.
289
+ * The delay grows exponentially: baseDelay * backoffFactor^attempts
290
+ */
291
+ const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
292
+ await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
293
+ }
253
294
  this.currentModel = retryModel.model;
254
295
  }
255
296
  }
@@ -263,13 +304,9 @@ var RetryableLanguageModel = class {
263
304
  result,
264
305
  model: this.currentModel
265
306
  };
266
- /**
267
- * Save the current attempt
268
- */
269
- const updatedAttempts = [...attempts, resultAttempt];
270
307
  const context = {
271
308
  current: resultAttempt,
272
- attempts: updatedAttempts
309
+ attempts: [...attempts, resultAttempt]
273
310
  };
274
311
  return {
275
312
  retryModel: await findRetryModel(this.options.retries, context),
@@ -370,7 +407,14 @@ var RetryableLanguageModel = class {
370
407
  * Save the attempt
371
408
  */
372
409
  attempts.push(attempt);
373
- if (retryModel.delay) await delay(retryModel.delay, { abortSignal: options.abortSignal });
410
+ if (retryModel.delay) {
411
+ /**
412
+ * Calculate exponential backoff delay based on the number of attempts for this specific model.
413
+ * The delay grows exponentially: baseDelay * backoffFactor^attempts
414
+ */
415
+ const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
416
+ await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: options.abortSignal });
417
+ }
374
418
  this.currentModel = retryModel.model;
375
419
  /**
376
420
  * Retry the request by calling doStream again.
@@ -406,4 +450,4 @@ function createRetryable(options) {
406
450
  }
407
451
 
408
452
  //#endregion
409
- export { createRetryable, getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
453
+ export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
@@ -1,17 +1,17 @@
1
- import { EmbeddingModelV2, LanguageModelV2, RetryModel, Retryable } from "../types-BrJaHkFh.js";
1
+ import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-DqwAmcZS.js";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
 
5
5
  /**
6
6
  * Fallback to a different model if the content filter was triggered.
7
7
  */
8
- declare function contentFilterTriggered<MODEL extends LanguageModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
8
+ declare function contentFilterTriggered<MODEL extends LanguageModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
9
9
  //#endregion
10
10
  //#region src/retryables/request-not-retryable.d.ts
11
11
  /**
12
12
  * Fallback to a different model if the error is non-retryable.
13
13
  */
14
- declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
14
+ declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
15
15
  //#endregion
16
16
  //#region src/retryables/request-timeout.d.ts
17
17
  /**
@@ -19,20 +19,16 @@ declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingMo
19
19
  * Use in combination with the `abortSignal` option.
20
20
  * Works with both `LanguageModelV2` and `EmbeddingModelV2`.
21
21
  */
22
- declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
22
+ declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
23
23
  //#endregion
24
24
  //#region src/retryables/retry-after-delay.d.ts
25
- type RetryAfterDelayOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2> = Omit<RetryModel<MODEL>, 'model' | 'delay'> & {
26
- delay: number;
27
- backoffFactor?: number;
28
- };
29
25
  /**
30
26
  * Retry with the same or a different model if the error is retryable with a delay.
31
27
  * Uses the `Retry-After` or `Retry-After-Ms` headers if present.
32
- * Otherwise uses the specified `delay`, or exponential backoff if `backoffFactor` is provided.
28
+ * Otherwise uses the specified `delay` with exponential backoff if `backoffFactor` is provided.
33
29
  */
34
- declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryAfterDelayOptions<MODEL>): Retryable<MODEL>;
35
- declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryAfterDelayOptions<MODEL>): Retryable<MODEL>;
30
+ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
31
+ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
36
32
  //#endregion
37
33
  //#region src/retryables/service-overloaded.d.ts
38
34
  /**
@@ -42,6 +38,6 @@ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV
42
38
  * - Response with `type: "overloaded_error"`
43
39
  * - Response with a `message` containing "overloaded"
44
40
  */
45
- declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: Omit<RetryModel<MODEL>, 'model'>): Retryable<MODEL>;
41
+ declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
46
42
  //#endregion
47
43
  export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
@@ -1,4 +1,4 @@
1
- import { getModelKey, isErrorAttempt, isObject, isResultAttempt, isString } from "../utils-Dojn0elD.js";
1
+ import { a as isModelV2, n as isErrorAttempt, o as isObject, s as isResultAttempt, u as isString } from "../utils-BlCGaP0E.js";
2
2
  import { isAbortError } from "@ai-sdk/provider-utils";
3
3
  import { APICallError } from "ai";
4
4
 
@@ -70,12 +70,20 @@ function requestTimeout(model, options) {
70
70
  }
71
71
 
72
72
  //#endregion
73
- //#region src/calculate-exponential-backoff.ts
73
+ //#region src/internal/resolve-retryable-options.ts
74
74
  /**
75
- * Calculates the exponential backoff delay.
75
+ * Helper to resolve `RetryableOptions` from either a model and/or options object.
76
+ * Used to support function overloads in retryables:
77
+ * - `retryable(model)`
78
+ * - `retryable(model, options)`
79
+ * - `retryable(options)`
76
80
  */
77
- function calculateExponentialBackoff(baseDelay, backoffFactor, attempts) {
78
- return baseDelay * backoffFactor ** attempts;
81
+ function resolveRetryableOptions(modelOrOptions, options) {
82
+ if (isModelV2(modelOrOptions)) return {
83
+ ...options,
84
+ model: modelOrOptions
85
+ };
86
+ return modelOrOptions;
79
87
  }
80
88
 
81
89
  //#endregion
@@ -101,30 +109,22 @@ function parseRetryHeaders(headers) {
101
109
  //#region src/retryables/retry-after-delay.ts
102
110
  const MAX_RETRY_AFTER_MS = 6e4;
103
111
  function retryAfterDelay(modelOrOptions, options) {
104
- const model = modelOrOptions && "delay" in modelOrOptions ? void 0 : modelOrOptions;
105
- const opts = modelOrOptions && "delay" in modelOrOptions ? modelOrOptions : options;
106
- if (!opts?.delay) throw new Error("retryAfterDelay: delay is required");
107
- const delay$1 = opts.delay;
108
- const backoffFactor = Math.max(opts.backoffFactor ?? 1, 1);
112
+ const resolvedOptions = resolveRetryableOptions(modelOrOptions, options);
109
113
  return (context) => {
110
- const { current, attempts } = context;
114
+ const { current } = context;
111
115
  if (isErrorAttempt(current)) {
112
116
  const { error } = current;
113
117
  if (APICallError.isInstance(error) && error.isRetryable === true) {
114
- const targetModel = model ?? current.model;
115
- const modelKey = getModelKey(targetModel);
116
- const modelAttempts = attempts.filter((a) => getModelKey(a.model) === modelKey);
118
+ const model = resolvedOptions.model ?? current.model;
117
119
  const headerDelay = parseRetryHeaders(error.responseHeaders);
118
120
  if (headerDelay !== null) return {
119
- model: targetModel,
120
- delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
121
- maxAttempts: opts.maxAttempts
121
+ model,
122
+ ...resolvedOptions,
123
+ delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS)
122
124
  };
123
- const calculatedDelay = calculateExponentialBackoff(delay$1, backoffFactor, modelAttempts.length);
124
125
  return {
125
- model: targetModel,
126
- delay: calculatedDelay,
127
- maxAttempts: opts.maxAttempts
126
+ model,
127
+ ...resolvedOptions
128
128
  };
129
129
  }
130
130
  }
@@ -1,11 +1,11 @@
1
- import { EmbeddingModelV2, LanguageModelV2 } from "@ai-sdk/provider";
1
+ import { EmbeddingModelV2, LanguageModelV2 as LanguageModelV2$1 } from "@ai-sdk/provider";
2
2
 
3
3
  //#region src/types.d.ts
4
4
  type EmbeddingModelV2$1<VALUE = any> = EmbeddingModelV2<VALUE>;
5
5
  /**
6
6
  * Options for creating a retryable model.
7
7
  */
8
- interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> {
8
+ interface RetryableModelOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> {
9
9
  model: MODEL;
10
10
  retries: Retries<MODEL>;
11
11
  onError?: (context: RetryContext<MODEL>) => void;
@@ -14,7 +14,7 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
14
14
  /**
15
15
  * The context provided to Retryables with the current attempt and all previous attempts.
16
16
  */
17
- type RetryContext<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
17
+ type RetryContext<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
18
18
  /**
19
19
  * Current attempt that caused the retry
20
20
  */
@@ -27,7 +27,7 @@ type RetryContext<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
27
27
  /**
28
28
  * A retry attempt with an error
29
29
  */
30
- type RetryErrorAttempt<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
30
+ type RetryErrorAttempt<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
31
31
  type: 'error';
32
32
  error: unknown;
33
33
  result?: undefined;
@@ -40,28 +40,30 @@ type RetryResultAttempt = {
40
40
  type: 'result';
41
41
  result: LanguageModelV2Generate;
42
42
  error?: undefined;
43
- model: LanguageModelV2;
43
+ model: LanguageModelV2$1;
44
44
  };
45
45
  /**
46
46
  * A retry attempt with either an error or a result and the model used
47
47
  */
48
- type RetryAttempt<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = RetryErrorAttempt<MODEL> | RetryResultAttempt;
48
+ type RetryAttempt<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = RetryErrorAttempt<MODEL> | RetryResultAttempt;
49
49
  /**
50
50
  * A model to retry with and the maximum number of attempts for that model.
51
51
  */
52
- type RetryModel<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
52
+ type Retry<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
53
53
  model: MODEL;
54
54
  maxAttempts?: number;
55
55
  delay?: number;
56
+ backoffFactor?: number;
56
57
  };
57
58
  /**
58
59
  * A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
59
60
  */
60
- type Retryable<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = (context: RetryContext<MODEL>) => RetryModel<MODEL> | Promise<RetryModel<MODEL>> | undefined;
61
- type Retries<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | MODEL>;
62
- type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;
63
- type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2['doStream']>>;
61
+ 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>;
63
+ type RetryableOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Partial<Omit<Retry<MODEL>, 'model'>>;
64
+ type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2$1['doGenerate']>>;
65
+ type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2$1['doStream']>>;
64
66
  type EmbeddingModelV2CallOptions<VALUE> = Parameters<EmbeddingModelV2$1<VALUE>['doEmbed']>[0];
65
67
  type EmbeddingModelV2Embed<VALUE> = Awaited<ReturnType<EmbeddingModelV2$1<VALUE>['doEmbed']>>;
66
68
  //#endregion
67
- export { EmbeddingModelV2$1 as EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, type LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions };
69
+ export { LanguageModelV2Generate as a, Retry as c, RetryErrorAttempt as d, RetryResultAttempt as f, RetryableOptions as h, LanguageModelV2$1 as i, RetryAttempt as l, RetryableModelOptions as m, EmbeddingModelV2CallOptions as n, LanguageModelV2Stream as o, Retryable as p, EmbeddingModelV2Embed as r, Retries as s, EmbeddingModelV2$1 as t, RetryContext as u };
@@ -1,15 +1,9 @@
1
- //#region src/get-model-key.ts
2
- /**
3
- * Generate a unique key for a LanguageModelV2 instance.
4
- */
5
- const getModelKey = (model) => {
6
- return `${model.provider}/${model.modelId}`;
7
- };
8
-
9
- //#endregion
10
1
  //#region src/utils.ts
11
2
  const isObject = (value) => typeof value === "object" && value !== null;
12
3
  const isString = (value) => typeof value === "string";
4
+ const isModelV2 = (model) => isLanguageModelV2(model) || isEmbeddingModelV2(model);
5
+ const isLanguageModelV2 = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && model.specificationVersion === "v2";
6
+ const isEmbeddingModelV2 = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && model.specificationVersion === "v2";
13
7
  const isStreamResult = (result) => "stream" in result;
14
8
  const isGenerateResult = (result) => "content" in result;
15
9
  /**
@@ -34,4 +28,4 @@ const isStreamContentPart = (part) => {
34
28
  };
35
29
 
36
30
  //#endregion
37
- export { getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "AI SDK Retry",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -35,6 +35,7 @@
35
35
  "devDependencies": {
36
36
  "@ai-sdk/anthropic": "^2.0.18",
37
37
  "@ai-sdk/azure": "^2.0.30",
38
+ "@ai-sdk/groq": "^2.0.24",
38
39
  "@ai-sdk/openai": "^2.0.30",
39
40
  "@arethetypeswrong/cli": "^0.18.2",
40
41
  "@biomejs/biome": "^2.2.4",
@@ -45,7 +46,7 @@
45
46
  "msw": "^2.11.2",
46
47
  "pkg-pr-new": "^0.0.60",
47
48
  "publint": "^0.3.13",
48
- "tsdown": "^0.15.2",
49
+ "tsdown": "^0.15.9",
49
50
  "tsx": "^4.20.5",
50
51
  "typescript": "^5.9.2",
51
52
  "unbuild": "^3.6.1",