ai-retry 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -478,6 +478,7 @@ Handle image generation failures by switching to a different model.
478
478
 
479
479
  ```typescript
480
480
  import { openai } from '@ai-sdk/openai';
481
+ import { google } from '@ai-sdk/google';
481
482
  import { generateImage } from 'ai';
482
483
  import { createRetryable } from 'ai-retry';
483
484
  import { noImageGenerated } from 'ai-retry/retryables';
@@ -485,7 +486,7 @@ import { noImageGenerated } from 'ai-retry/retryables';
485
486
  const retryableModel = createRetryable({
486
487
  model: openai.image('dall-e-3'),
487
488
  retries: [
488
- noImageGenerated(openai.image('dall-e-2')), // Fallback to DALL-E 2
489
+ noImageGenerated(google.image('gemini-3-pro-image-preview')), // Switch to Gemini if DALL-E fails to generate an image
489
490
  ],
490
491
  });
491
492
 
@@ -792,21 +793,25 @@ The following options can be overridden:
792
793
  #### Logging
793
794
 
794
795
  You can use the following callbacks to log retry attempts and errors:
795
- - `onError` is invoked if an error occurs.
796
+ - `onError` is invoked if an error occurs.
796
797
  - `onRetry` is invoked before attempting a retry.
798
+ - `onSuccess` is invoked after a successful request with the model that handled it.
797
799
 
798
800
  ```typescript
799
801
  const retryableModel = createRetryable({
800
802
  model: openai('gpt-4-mini'),
801
803
  retries: [/* your retryables */],
802
804
  onError: (context) => {
803
- console.error(`Attempt ${context.attempts.length} with ${context.current.model.provider}/${context.current.model.modelId} failed:`,
805
+ console.error(`Attempt ${context.attempts.length} with ${context.current.model.provider}/${context.current.model.modelId} failed:`,
804
806
  context.current.error
805
807
  );
806
808
  },
807
809
  onRetry: (context) => {
808
810
  console.log(`Retrying attempt ${context.attempts.length + 1} with model ${context.current.model.provider}/${context.current.model.modelId}...`);
809
811
  },
812
+ onSuccess: (context) => {
813
+ console.log(`Request handled by ${context.current.model.provider}/${context.current.model.modelId}`);
814
+ },
810
815
  });
811
816
  ```
812
817
 
@@ -859,6 +864,9 @@ Errors during streaming requests can occur in two ways:
859
864
 
860
865
  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).
861
866
 
867
+ > [!IMPORTANT]
868
+ > **Streaming limitation:** Retries and fallbacks only apply before the first content chunk is emitted. Once streaming begins delivering content, the response is committed to the current model. Mid-stream errors will propagate to the caller rather than triggering a fallback. If reliable retries are critical for your use case, consider using `generateText` instead of `streamText`.
869
+
862
870
  ### API Reference
863
871
 
864
872
  #### `createRetryable(options: RetryableModelOptions): LanguageModelV3 | EmbeddingModelV3 | ImageModelV3`
@@ -873,6 +881,7 @@ interface RetryableModelOptions<MODEL extends LanguageModelV3 | EmbeddingModelV3
873
881
  reset?: Reset;
874
882
  onError?: (context: RetryContext<MODEL>) => void;
875
883
  onRetry?: (context: RetryContext<MODEL>) => void;
884
+ onSuccess?: (context: SuccessContext<MODEL>) => void;
876
885
  }
877
886
  ```
878
887
 
@@ -883,6 +892,7 @@ interface RetryableModelOptions<MODEL extends LanguageModelV3 | EmbeddingModelV3
883
892
  - `reset`: Controls when to reset back to the base model after a successful retry. Default: `after-request`.
884
893
  - `onError`: Callback invoked when an error occurs.
885
894
  - `onRetry`: Callback invoked before attempting a retry.
895
+ - `onSuccess`: Callback invoked after a successful request. Receives the model that handled the request and all previous attempts.
886
896
 
887
897
  #### `Reset`
888
898
 
@@ -937,6 +947,30 @@ interface RetryContext {
937
947
  }
938
948
  ```
939
949
 
950
+ #### `SuccessContext`
951
+
952
+ The `SuccessContext` object is passed to the `onSuccess` callback after a successful request.
953
+
954
+ ```typescript
955
+ interface SuccessContext {
956
+ current: SuccessAttempt;
957
+ attempts: Array<RetryAttempt>;
958
+ }
959
+ ```
960
+
961
+ #### `SuccessAttempt`
962
+
963
+ A `SuccessAttempt` represents the successful attempt with the model, result, and call options used. The `result` type depends on the model type.
964
+
965
+ ```typescript
966
+ interface SuccessAttempt {
967
+ type: 'success';
968
+ model: LanguageModelV3 | EmbeddingModelV3 | ImageModelV3;
969
+ result: LanguageModelGenerate | LanguageModelStream | EmbeddingModelEmbed | ImageModelGenerate;
970
+ options: LanguageModelV3CallOptions | EmbeddingModelV3CallOptions | ImageModelV3CallOptions;
971
+ }
972
+ ```
973
+
940
974
  #### `RetryAttempt`
941
975
 
942
976
  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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
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";
1
+ import { A as RetryableModelOptions, C as Retries, D as RetryErrorAttempt, E as RetryContext, M as SuccessAttempt, N as SuccessContext, O as RetryResultAttempt, S as Result, T as RetryAttempt, _ as ProviderOptions, a as EmbeddingModelRetryCallOptions, b as ResolvableModel, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStreamPart, h as LanguageModelStream, i as EmbeddingModelEmbed, j as RetryableOptions, k as Retryable, l as ImageModelGenerate, m as LanguageModelRetryCallOptions, n as EmbeddingModel, o as GatewayLanguageModelId, p as LanguageModelGenerate, r as EmbeddingModelCallOptions, s as ImageModel, t as CallOptions, u as ImageModelRetryCallOptions, v as Reset, w as Retry, x as ResolvedModel, y as ResolvableLanguageModel } from "./types-CqvBIDad.mjs";
2
2
  import * as _ai_sdk_provider0 from "@ai-sdk/provider";
3
3
 
4
4
  //#region src/create-retryable-model.d.ts
@@ -75,4 +75,4 @@ declare const isAbortError: (error: unknown) => boolean;
75
75
  */
76
76
  declare const isTimeoutError: (error: unknown) => boolean;
77
77
  //#endregion
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 };
78
+ export { CallOptions, EmbeddingModel, EmbeddingModelCallOptions, EmbeddingModelEmbed, EmbeddingModelRetryCallOptions, GatewayLanguageModelId, ImageModel, ImageModelCallOptions, ImageModelGenerate, ImageModelRetryCallOptions, LanguageModel, LanguageModelCallOptions, LanguageModelGenerate, LanguageModelRetryCallOptions, LanguageModelStream, LanguageModelStreamPart, ProviderOptions, Reset, ResolvableLanguageModel, ResolvableModel, ResolvedModel, Result, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, SuccessAttempt, SuccessContext, createRetryable, getModelKey, isAbortError, isEmbeddingModel, isErrorAttempt, isGenerateResult, isImageModel, isLanguageModel, isModel, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString, isTimeoutError };
package/dist/index.mjs CHANGED
@@ -256,7 +256,8 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
256
256
  try {
257
257
  return {
258
258
  result: await input.fn(retryCallOptions),
259
- attempts
259
+ attempts,
260
+ callOptions: retryCallOptions
260
261
  };
261
262
  } catch (error) {
262
263
  if (isAbortError(error)) throw error;
@@ -322,13 +323,22 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
322
323
  * If retries are disabled, bypass retry machinery entirely
323
324
  */
324
325
  if (this.isDisabled()) return this.currentModel.doEmbed(callOptions);
325
- const { result } = await this.withRetry({
326
+ const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
326
327
  fn: async (retryCallOptions) => {
327
328
  return this.currentModel.doEmbed(retryCallOptions);
328
329
  },
329
330
  callOptions
330
331
  });
331
332
  this.updateStickyModel(startModel);
333
+ this.options.onSuccess?.({
334
+ current: {
335
+ type: "success",
336
+ model: this.currentModel,
337
+ result,
338
+ options: finalCallOptions
339
+ },
340
+ attempts
341
+ });
332
342
  return result;
333
343
  }
334
344
  };
@@ -400,7 +410,8 @@ var RetryableImageModel = class extends BaseRetryableModel {
400
410
  try {
401
411
  return {
402
412
  result: await input.fn(retryCallOptions),
403
- attempts
413
+ attempts,
414
+ callOptions: retryCallOptions
404
415
  };
405
416
  } catch (error) {
406
417
  /** Don't retry if user manually aborted the request. */
@@ -468,13 +479,22 @@ var RetryableImageModel = class extends BaseRetryableModel {
468
479
  * If retries are disabled, bypass retry machinery entirely
469
480
  */
470
481
  if (this.isDisabled()) return this.currentModel.doGenerate(callOptions);
471
- const { result } = await this.withRetry({
482
+ const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
472
483
  fn: async (retryCallOptions) => {
473
484
  return this.currentModel.doGenerate(retryCallOptions);
474
485
  },
475
486
  callOptions
476
487
  });
477
488
  this.updateStickyModel(startModel);
489
+ this.options.onSuccess?.({
490
+ current: {
491
+ type: "success",
492
+ model: this.currentModel,
493
+ result,
494
+ options: finalCallOptions
495
+ },
496
+ attempts
497
+ });
478
498
  return result;
479
499
  }
480
500
  };
@@ -582,7 +602,8 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
582
602
  }
583
603
  return {
584
604
  result,
585
- attempts
605
+ attempts,
606
+ callOptions: retryCallOptions
586
607
  };
587
608
  } catch (error) {
588
609
  if (isAbortError(error)) throw error;
@@ -663,13 +684,22 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
663
684
  * If retries are disabled, bypass retry machinery entirely
664
685
  */
665
686
  if (this.isDisabled()) return this.currentModel.doGenerate(callOptions);
666
- const { result } = await this.withRetry({
687
+ const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
667
688
  fn: async (retryCallOptions) => {
668
689
  return this.currentModel.doGenerate(retryCallOptions);
669
690
  },
670
691
  callOptions
671
692
  });
672
693
  this.updateStickyModel(startModel);
694
+ this.options.onSuccess?.({
695
+ current: {
696
+ type: "success",
697
+ model: this.currentModel,
698
+ result,
699
+ options: finalCallOptions
700
+ },
701
+ attempts
702
+ });
673
703
  return result;
674
704
  }
675
705
  async doStream(callOptions) {
@@ -685,13 +715,22 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
685
715
  /**
686
716
  * Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
687
717
  */
688
- let { result, attempts } = await this.withRetry({
718
+ let { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
689
719
  fn: async (retryCallOptions) => {
690
720
  return this.currentModel.doStream(retryCallOptions);
691
721
  },
692
722
  callOptions
693
723
  });
694
724
  this.updateStickyModel(startModel);
725
+ this.options.onSuccess?.({
726
+ current: {
727
+ type: "success",
728
+ model: this.currentModel,
729
+ result,
730
+ options: finalCallOptions
731
+ },
732
+ attempts
733
+ });
695
734
  /**
696
735
  * Track the current retry model for computing call options in the stream handler
697
736
  */
@@ -1,4 +1,4 @@
1
- import { D as Retryable, k as RetryableOptions, o as ImageModel, t as EmbeddingModel, v as ResolvableLanguageModel } from "../types-wEZKtEcH.mjs";
1
+ import { j as RetryableOptions, k as Retryable, n as EmbeddingModel, s as ImageModel, y as ResolvableLanguageModel } from "../types-CqvBIDad.mjs";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
  /**
@@ -26,6 +26,14 @@ type EmbeddingModelRetryCallOptions = Partial<Pick<EmbeddingModelCallOptions, 'v
26
26
  * Call options that can be overridden during retry for image models.
27
27
  */
28
28
  type ImageModelRetryCallOptions = Partial<Pick<ImageModelCallOptions, 'n' | 'size' | 'aspectRatio' | 'seed' | 'headers' | 'providerOptions'>>;
29
+ /**
30
+ * Maps a model type to its call options type.
31
+ */
32
+ type CallOptions<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = MODEL extends LanguageModel ? LanguageModelCallOptions : MODEL extends EmbeddingModel ? EmbeddingModelCallOptions : ImageModelCallOptions;
33
+ /**
34
+ * Maps a model type to its result type.
35
+ */
36
+ type Result<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = MODEL extends LanguageModel ? LanguageModelGenerate | LanguageModelStream : MODEL extends EmbeddingModel ? EmbeddingModelEmbed : ImageModelGenerate;
29
37
  /**
30
38
  * A retry attempt with an error
31
39
  */
@@ -37,7 +45,7 @@ type RetryErrorAttempt<MODEL extends LanguageModel | EmbeddingModel | ImageModel
37
45
  /**
38
46
  * The call options used for this attempt.
39
47
  */
40
- options: MODEL extends LanguageModel ? LanguageModelCallOptions : MODEL extends EmbeddingModel ? EmbeddingModelCallOptions : ImageModelCallOptions;
48
+ options: CallOptions<MODEL>;
41
49
  };
42
50
  /**
43
51
  * A retry attempt with a successful result
@@ -69,6 +77,28 @@ type RetryContext<MODEL extends ResolvableLanguageModel | EmbeddingModel | Image
69
77
  */
70
78
  attempts: Array<RetryAttempt<ResolvedModel<MODEL>>>;
71
79
  };
80
+ /**
81
+ * A successful attempt with the result
82
+ */
83
+ type SuccessAttempt<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = {
84
+ type: 'success';
85
+ model: MODEL;
86
+ result: Result<MODEL>;
87
+ options: CallOptions<MODEL>;
88
+ };
89
+ /**
90
+ * The context provided to onSuccess with the successful attempt and all previous attempts.
91
+ */
92
+ type SuccessContext<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = {
93
+ /**
94
+ * The successful attempt
95
+ */
96
+ current: SuccessAttempt<ResolvedModel<MODEL>>;
97
+ /**
98
+ * All attempts made so far, including the current one
99
+ */
100
+ attempts: Array<RetryAttempt<ResolvedModel<MODEL>>>;
101
+ };
72
102
  /**
73
103
  * Options for creating a retryable model.
74
104
  */
@@ -84,6 +114,7 @@ interface RetryableModelOptions<MODEL extends LanguageModel | EmbeddingModel | I
84
114
  reset?: Reset;
85
115
  onError?: (context: RetryContext<MODEL>) => void;
86
116
  onRetry?: (context: RetryContext<MODEL>) => void;
117
+ onSuccess?: (context: SuccessContext<MODEL>) => void;
87
118
  }
88
119
  /**
89
120
  * A model to retry with and the maximum number of attempts for that model.
@@ -147,4 +178,4 @@ type EmbeddingModelCallOptions = Parameters<EmbeddingModel['doEmbed']>[0];
147
178
  type EmbeddingModelEmbed = Awaited<ReturnType<EmbeddingModel['doEmbed']>>;
148
179
  type ImageModelGenerate = Awaited<ReturnType<ImageModel['doGenerate']>>;
149
180
  //#endregion
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 };
181
+ export { RetryableModelOptions as A, Retries as C, RetryErrorAttempt as D, RetryContext as E, SuccessAttempt as M, SuccessContext as N, RetryResultAttempt as O, Result as S, RetryAttempt as T, ProviderOptions as _, EmbeddingModelRetryCallOptions as a, ResolvableModel as b, ImageModelCallOptions as c, LanguageModel as d, LanguageModelCallOptions as f, LanguageModelStreamPart as g, LanguageModelStream as h, EmbeddingModelEmbed as i, RetryableOptions as j, Retryable as k, ImageModelGenerate as l, LanguageModelRetryCallOptions as m, EmbeddingModel as n, GatewayLanguageModelId as o, LanguageModelGenerate as p, EmbeddingModelCallOptions as r, ImageModel as s, CallOptions as t, ImageModelRetryCallOptions as u, Reset as v, Retry as w, ResolvedModel as x, ResolvableLanguageModel as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Retry and fallback mechanisms for AI SDK",
5
5
  "types": "./dist/index.d.mts",
6
6
  "type": "module",
@@ -28,7 +28,10 @@
28
28
  "url": "git+https://github.com/zirkelc/ai-retry.git"
29
29
  },
30
30
  "peerDependencies": {
31
- "ai": "6.x"
31
+ "@ai-sdk/provider": "^3.0.0",
32
+ "@ai-sdk/provider-utils": "^4.0.0",
33
+ "ai": "6.x",
34
+ "zod": "^4.0.0"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@ai-sdk/anthropic": "3.0.23",
@@ -37,6 +40,8 @@
37
40
  "@ai-sdk/google": "^3.0.33",
38
41
  "@ai-sdk/groq": "3.0.15",
39
42
  "@ai-sdk/openai": "3.0.19",
43
+ "@ai-sdk/provider": "3.0.5",
44
+ "@ai-sdk/provider-utils": "4.0.9",
40
45
  "@ai-sdk/test-server": "1.0.3",
41
46
  "@arethetypeswrong/cli": "^0.18.2",
42
47
  "@biomejs/biome": "^2.3.13",
@@ -50,11 +55,7 @@
50
55
  "tsdown": "^0.20.1",
51
56
  "tsx": "^4.21.0",
52
57
  "typescript": "^5.9.3",
53
- "vitest": "^4.0.18"
54
- },
55
- "dependencies": {
56
- "@ai-sdk/provider": "3.0.5",
57
- "@ai-sdk/provider-utils": "4.0.9",
58
+ "vitest": "^4.0.18",
58
59
  "zod": "^4.3.6"
59
60
  },
60
61
  "scripts": {