ai-retry 1.9.0 → 1.10.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
@@ -1026,6 +1026,7 @@ You can use the following callbacks to log retry attempts and errors:
1026
1026
  - `onError` is invoked if an error occurs.
1027
1027
  - `onRetry` is invoked before attempting a retry.
1028
1028
  - `onSuccess` is invoked after a successful request with the model that handled it.
1029
+ - `onFailure` is invoked when the request ultimately fails and no retry could recover it.
1029
1030
 
1030
1031
  ```typescript
1031
1032
  const retryableModel = createRetryable({
@@ -1049,9 +1050,17 @@ const retryableModel = createRetryable({
1049
1050
  `Request handled by ${context.current.model.provider}/${context.current.model.modelId}`,
1050
1051
  );
1051
1052
  },
1053
+ onFailure: (context) => {
1054
+ console.error(
1055
+ `Request failed after ${context.attempts.length} attempts:`,
1056
+ context.error,
1057
+ );
1058
+ },
1052
1059
  });
1053
1060
  ```
1054
1061
 
1062
+ `onSuccess` and `onFailure` are counterparts: exactly one of them is invoked per request once its final outcome is known. `onFailure` fires when the error could not be recovered by a retry, whether because no retryable matched, all retries were exhausted, or the retry itself failed. `context.error` is the error surfaced to the caller (a [`RetryError`](#all-retries-failed) wrapping every attempt error when more than one attempt was made, otherwise the original error), and `context.current` is the final failed attempt. Neither callback fires when retries are disabled.
1063
+
1055
1064
  #### Reset
1056
1065
 
1057
1066
  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.
@@ -1183,6 +1192,13 @@ In the second case, errors during stream processing will not always be retried,
1183
1192
  > [!IMPORTANT]
1184
1193
  > **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`.
1185
1194
 
1195
+ #### Preamble buffering
1196
+
1197
+ Every stream begins with a non-content preamble (`stream-start`, then optionally `response-metadata` and `text-start` / `reasoning-start`) that providers emit as soon as the response headers arrive, before any content flows. Because a retry can still happen during this window, `ai-retry` does not forward the preamble immediately. It buffers the leading non-content parts and flushes them only when the first content chunk arrives (or when the stream finishes with no content). If a retry fires before any content, the buffered preamble is discarded and replaced by the fallback's, so the consumer always sees exactly one preamble — the one belonging to the model that actually produced the output, with its own `warnings` and `response-metadata`. Without this, a fallback's `stream-start` would be emitted a second time after the primary's, which some consumers (e.g. `streamText`) reject.
1198
+
1199
+ > [!NOTE]
1200
+ > One side effect: the consumer's "stream started" signal now arrives at first-content time rather than when the response headers arrive (typically a sub-second difference). For UIs that show a typing indicator off `stream-start` this is negligible.
1201
+
1186
1202
  ### API Reference
1187
1203
 
1188
1204
  #### `createRetryable(options: RetryableModelOptions): LanguageModelV3 | EmbeddingModelV3 | ImageModelV3`
@@ -1203,6 +1219,7 @@ interface RetryableModelOptions<
1203
1219
  context: RetryContext<MODEL>,
1204
1220
  ) => void | OnRetryOverrides<MODEL> | Promise<void | OnRetryOverrides<MODEL>>;
1205
1221
  onSuccess?: (context: SuccessContext<MODEL>) => void;
1222
+ onFailure?: (context: FailureContext<MODEL>) => void;
1206
1223
  }
1207
1224
  ```
1208
1225
 
@@ -1216,6 +1233,7 @@ interface RetryableModelOptions<
1216
1233
  - `onError`: Callback invoked when an error occurs.
1217
1234
  - `onRetry`: Callback invoked before attempting a retry. May optionally return an `OnRetryOverrides` object (or a `Promise` of one) to override `options.*` for the upcoming attempt only. See [Dynamic Call Options via `onRetry`](#dynamic-call-options-via-onretry).
1218
1235
  - `onSuccess`: Callback invoked after a successful request. Receives the model that handled the request and all previous attempts.
1236
+ - `onFailure`: Callback invoked when the request ultimately fails and no retry could recover it (no retryable matched, all retries exhausted, or the retry itself failed).
1219
1237
 
1220
1238
  #### `Reset`
1221
1239
 
@@ -1302,6 +1320,18 @@ interface SuccessAttempt {
1302
1320
  }
1303
1321
  ```
1304
1322
 
1323
+ #### `FailureContext`
1324
+
1325
+ The `FailureContext` object is passed to the `onFailure` callback when a request ultimately fails. `current` is the final failed attempt (an error attempt, see [`RetryAttempt`](#retryattempt)) and `error` is the error surfaced to the caller, a [`RetryError`](#all-retries-failed) wrapping every attempt error when more than one attempt was made, otherwise the original error.
1326
+
1327
+ ```typescript
1328
+ interface FailureContext {
1329
+ current: RetryErrorAttempt;
1330
+ attempts: Array<RetryAttempt>;
1331
+ error: unknown;
1332
+ }
1333
+ ```
1334
+
1305
1335
  #### `RetryAttempt`
1306
1336
 
1307
1337
  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.
@@ -515,14 +515,13 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
515
515
  callOptions: retryCallOptions
516
516
  };
517
517
  } catch (error) {
518
+ const { retryModel, attempt, finalError } = await this.handleError(error, attempts, retryCallOptions);
519
+ attempts.push(attempt);
518
520
  /**
519
- * `handleError` throws when no retry matched. Record the attempt as
520
- * failed before that error propagates.
521
+ * No retry matched. Record the attempt as failed and throw the
522
+ * surfaced error.
521
523
  */
522
- let decision;
523
- try {
524
- decision = await this.handleError(error, attempts, retryCallOptions);
525
- } catch (finalError) {
524
+ if (!retryModel) {
526
525
  input.recorder?.endAttempt({
527
526
  attempt: attemptNumber,
528
527
  outcome: "failure",
@@ -530,8 +529,6 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
530
529
  });
531
530
  throw finalError;
532
531
  }
533
- const { retryModel, attempt } = decision;
534
- attempts.push(attempt);
535
532
  /**
536
533
  * If the inbound abort signal is already aborted and the chosen
537
534
  * retry does not supply a fresh deadline, the retry would die
@@ -587,19 +584,26 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
587
584
  };
588
585
  this.options.onError?.(context);
589
586
  const retryModel = await findRetryModel(this.options.retries, context);
590
- /**
591
- * Handler didn't return any models to try next, rethrow the error.
592
- * If we retried the request, wrap the error into a `RetryError` for better visibility.
593
- */
594
- if (!retryModel) {
595
- if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
596
- throw error;
597
- }
598
587
  return {
599
588
  retryModel,
600
- attempt: errorAttempt
589
+ attempt: errorAttempt,
590
+ finalError: retryModel ? void 0 : updatedAttempts.length > 1 ? prepareRetryError(error, updatedAttempts) : error
601
591
  };
602
592
  }
593
+ /**
594
+ * Fire the `onFailure` callback for a terminally failed operation. The
595
+ * final attempt (last entry of `attempts`) is surfaced as `current`.
596
+ */
597
+ emitFailure(attempts, error) {
598
+ if (!this.options.onFailure) return;
599
+ const current = attempts.at(-1);
600
+ if (!current) return;
601
+ this.options.onFailure({
602
+ current,
603
+ attempts,
604
+ error
605
+ });
606
+ }
603
607
  async doEmbed(callOptions) {
604
608
  /**
605
609
  * Resolve the starting model (base or sticky)
@@ -616,13 +620,19 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
616
620
  provider: startModel.provider,
617
621
  modelId: startModel.modelId
618
622
  });
623
+ /**
624
+ * Shared attempts array, threaded into `withRetry` so it stays populated
625
+ * (including the final failed attempt) when the retry loop throws.
626
+ */
627
+ const attempts = [];
619
628
  let operationError;
620
629
  try {
621
- const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
630
+ const { result, callOptions: finalCallOptions } = await this.withRetry({
622
631
  fn: async (retryCallOptions) => {
623
632
  return this.currentModel.doEmbed(retryCallOptions);
624
633
  },
625
634
  callOptions,
635
+ attempts,
626
636
  recorder
627
637
  });
628
638
  this.updateStickyModel(startModel);
@@ -638,6 +648,7 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
638
648
  return result;
639
649
  } catch (error) {
640
650
  operationError = error;
651
+ this.emitFailure(attempts, error);
641
652
  throw error;
642
653
  } finally {
643
654
  recorder?.endOperation({
@@ -729,14 +740,13 @@ var RetryableImageModel = class extends BaseRetryableModel {
729
740
  callOptions: retryCallOptions
730
741
  };
731
742
  } catch (error) {
743
+ const { retryModel, attempt, finalError } = await this.handleError(error, attempts, retryCallOptions);
744
+ attempts.push(attempt);
732
745
  /**
733
- * `handleError` throws when no retry matched. Record the attempt as
734
- * failed before that error propagates.
746
+ * No retry matched. Record the attempt as failed and throw the
747
+ * surfaced error.
735
748
  */
736
- let decision;
737
- try {
738
- decision = await this.handleError(error, attempts, retryCallOptions);
739
- } catch (finalError) {
749
+ if (!retryModel) {
740
750
  input.recorder?.endAttempt({
741
751
  attempt: attemptNumber,
742
752
  outcome: "failure",
@@ -744,8 +754,6 @@ var RetryableImageModel = class extends BaseRetryableModel {
744
754
  });
745
755
  throw finalError;
746
756
  }
747
- const { retryModel, attempt } = decision;
748
- attempts.push(attempt);
749
757
  /**
750
758
  * If the inbound abort signal is already aborted and the chosen
751
759
  * retry does not supply a fresh deadline, the retry would die
@@ -801,19 +809,26 @@ var RetryableImageModel = class extends BaseRetryableModel {
801
809
  };
802
810
  this.options.onError?.(context);
803
811
  const retryModel = await findRetryModel(this.options.retries, context);
804
- /**
805
- * Handler didn't return any models to try next, rethrow the error.
806
- * If we retried the request, wrap the error into a `RetryError` for better visibility.
807
- */
808
- if (!retryModel) {
809
- if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
810
- throw error;
811
- }
812
812
  return {
813
813
  retryModel,
814
- attempt: errorAttempt
814
+ attempt: errorAttempt,
815
+ finalError: retryModel ? void 0 : updatedAttempts.length > 1 ? prepareRetryError(error, updatedAttempts) : error
815
816
  };
816
817
  }
818
+ /**
819
+ * Fire the `onFailure` callback for a terminally failed operation. The
820
+ * final attempt (last entry of `attempts`) is surfaced as `current`.
821
+ */
822
+ emitFailure(attempts, error) {
823
+ if (!this.options.onFailure) return;
824
+ const current = attempts.at(-1);
825
+ if (!current) return;
826
+ this.options.onFailure({
827
+ current,
828
+ attempts,
829
+ error
830
+ });
831
+ }
817
832
  async doGenerate(callOptions) {
818
833
  /**
819
834
  * Resolve the starting model (base or sticky)
@@ -830,13 +845,19 @@ var RetryableImageModel = class extends BaseRetryableModel {
830
845
  provider: startModel.provider,
831
846
  modelId: startModel.modelId
832
847
  });
848
+ /**
849
+ * Shared attempts array, threaded into `withRetry` so it stays populated
850
+ * (including the final failed attempt) when the retry loop throws.
851
+ */
852
+ const attempts = [];
833
853
  let operationError;
834
854
  try {
835
- const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
855
+ const { result, callOptions: finalCallOptions } = await this.withRetry({
836
856
  fn: async (retryCallOptions) => {
837
857
  return this.currentModel.doGenerate(retryCallOptions);
838
858
  },
839
859
  callOptions,
860
+ attempts,
840
861
  recorder
841
862
  });
842
863
  this.updateStickyModel(startModel);
@@ -852,6 +873,7 @@ var RetryableImageModel = class extends BaseRetryableModel {
852
873
  return result;
853
874
  } catch (error) {
854
875
  operationError = error;
876
+ this.emitFailure(attempts, error);
855
877
  throw error;
856
878
  } finally {
857
879
  recorder?.endOperation({
@@ -1080,6 +1102,20 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1080
1102
  finalError: retryModel ? void 0 : updatedAttempts.length > 1 ? prepareRetryError(error, updatedAttempts) : error
1081
1103
  };
1082
1104
  }
1105
+ /**
1106
+ * Fire the `onFailure` callback for a terminally failed operation. The
1107
+ * final attempt (last entry of `attempts`) is surfaced as `current`.
1108
+ */
1109
+ emitFailure(attempts, error) {
1110
+ if (!this.options.onFailure) return;
1111
+ const current = attempts.at(-1);
1112
+ if (!current || !isErrorAttempt(current)) return;
1113
+ this.options.onFailure({
1114
+ current,
1115
+ attempts,
1116
+ error
1117
+ });
1118
+ }
1083
1119
  async doGenerate(callOptions) {
1084
1120
  /**
1085
1121
  * Resolve the starting model (base or sticky)
@@ -1096,13 +1132,19 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1096
1132
  provider: startModel.provider,
1097
1133
  modelId: startModel.modelId
1098
1134
  });
1135
+ /**
1136
+ * Shared attempts array, threaded into `withRetry` so it stays populated
1137
+ * (including the final failed attempt) when the retry loop throws.
1138
+ */
1139
+ const attempts = [];
1099
1140
  let operationError;
1100
1141
  try {
1101
- const { result, attempts, callOptions: finalCallOptions } = await this.withRetry({
1142
+ const { result, callOptions: finalCallOptions } = await this.withRetry({
1102
1143
  fn: async (retryCallOptions) => {
1103
1144
  return this.currentModel.doGenerate(retryCallOptions);
1104
1145
  },
1105
1146
  callOptions,
1147
+ attempts,
1106
1148
  recorder
1107
1149
  });
1108
1150
  this.updateStickyModel(startModel);
@@ -1118,6 +1160,7 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1118
1160
  return result;
1119
1161
  } catch (error) {
1120
1162
  operationError = error;
1163
+ this.emitFailure(attempts, error);
1121
1164
  throw error;
1122
1165
  } finally {
1123
1166
  recorder?.endOperation({
@@ -1147,7 +1190,11 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1147
1190
  * Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
1148
1191
  */
1149
1192
  let result;
1150
- let attempts;
1193
+ /**
1194
+ * Shared attempts array, threaded into `withRetry` so it stays populated
1195
+ * (including the final failed attempt) when the retry loop throws.
1196
+ */
1197
+ let attempts = [];
1151
1198
  let finalCallOptions;
1152
1199
  /**
1153
1200
  * The open attempt span for the stream currently being consumed, closed
@@ -1160,6 +1207,7 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1160
1207
  return this.currentModel.doStream(retryCallOptions);
1161
1208
  },
1162
1209
  callOptions,
1210
+ attempts,
1163
1211
  recorder
1164
1212
  });
1165
1213
  result = initial.result;
@@ -1171,6 +1219,7 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1171
1219
  * Every pre-stream attempt failed; record the operation failure before
1172
1220
  * the error propagates to the caller.
1173
1221
  */
1222
+ this.emitFailure(attempts, error);
1174
1223
  recorder?.endOperation({
1175
1224
  provider: this.currentModel.provider,
1176
1225
  modelId: this.currentModel.modelId,
@@ -1202,6 +1251,16 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1202
1251
  let capturedWarnings = [];
1203
1252
  let capturedResponseMetadata = {};
1204
1253
  /**
1254
+ * Buffer for the leading non-content parts (`stream-start`,
1255
+ * `response-metadata`, `text-start`, `reasoning-start`, …) of this
1256
+ * attempt. While no content has been forwarded the preamble is held
1257
+ * here rather than enqueued, so a pre-content retry can discard it
1258
+ * and the consumer sees exactly one preamble — the one belonging to
1259
+ * the model that actually produced the output. Reset per attempt;
1260
+ * flushed on the first content part or at completion.
1261
+ */
1262
+ let preambleBuffer = [];
1263
+ /**
1205
1264
  * Set when a `finish` part triggers a retry decision. Causes the
1206
1265
  * inner read loop to exit without enqueuing the finish part, and
1207
1266
  * the outer loop to re-stream against the next model.
@@ -1270,11 +1329,25 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1270
1329
  }
1271
1330
  }
1272
1331
  /**
1273
- * Mark that streaming has started once we receive actual content
1332
+ * Mark that streaming has started once we receive actual
1333
+ * content. On the first content part, flush this attempt's
1334
+ * buffered preamble (in order) ahead of the content, then
1335
+ * forward normally from here on.
1274
1336
  */
1275
- if (isStreamContentPart(value)) isStreaming = true;
1276
- /**
1277
- * Enqueue the chunk to the consumer of the stream
1337
+ if (isStreamContentPart(value)) {
1338
+ isStreaming = true;
1339
+ for (const buffered of preambleBuffer) controller.enqueue(buffered);
1340
+ preambleBuffer = [];
1341
+ controller.enqueue(value);
1342
+ } else if (!isStreaming)
1343
+ /**
1344
+ * Pre-content part: buffer it so a pre-content retry can
1345
+ * replace it with the next attempt's preamble.
1346
+ */
1347
+ preambleBuffer.push(value);
1348
+ else
1349
+ /**
1350
+ * Content already flowing: forward directly.
1278
1351
  */
1279
1352
  controller.enqueue(value);
1280
1353
  }
@@ -1306,7 +1379,13 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1306
1379
  currentRetry,
1307
1380
  recorder
1308
1381
  });
1309
- await reader?.cancel();
1382
+ /**
1383
+ * Cancelling a reader whose stream has already errored (e.g.
1384
+ * a mid-stream `controller.error`) rejects with that stored
1385
+ * error. Swallow it: the retry already succeeded and that
1386
+ * rejection must not abort the wrapped stream.
1387
+ */
1388
+ await reader?.cancel().catch(() => {});
1310
1389
  result = retriedResult.result;
1311
1390
  attempts = retriedResult.attempts;
1312
1391
  finalCallOptions = retriedResult.callOptions;
@@ -1318,6 +1397,13 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1318
1397
  outcome: "success",
1319
1398
  finishReason: streamFinishReason
1320
1399
  });
1400
+ /**
1401
+ * A stream that completes with no content part still has its
1402
+ * preamble buffered. Flush it so a zero-content completion emits
1403
+ * its `stream-start` (and any metadata/finish) before closing.
1404
+ */
1405
+ for (const buffered of preambleBuffer) controller.enqueue(buffered);
1406
+ preambleBuffer = [];
1321
1407
  controller.close();
1322
1408
  break;
1323
1409
  } catch (error) {
@@ -1349,6 +1435,7 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1349
1435
  error
1350
1436
  });
1351
1437
  operationError = finalError;
1438
+ this.emitFailure(attempts, finalError);
1352
1439
  controller.enqueue({
1353
1440
  type: "error",
1354
1441
  error: finalError
@@ -1369,6 +1456,7 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1369
1456
  error
1370
1457
  });
1371
1458
  operationError = error;
1459
+ this.emitFailure(attempts, error);
1372
1460
  controller.enqueue({
1373
1461
  type: "error",
1374
1462
  error
@@ -1408,9 +1496,13 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
1408
1496
  recorder
1409
1497
  });
1410
1498
  /**
1411
- * Cancel the previous reader and stream if we are retrying
1499
+ * Cancel the previous reader and stream if we are retrying.
1500
+ * Cancelling a reader whose stream has already errored (e.g. a
1501
+ * mid-stream `controller.error`) rejects with that stored
1502
+ * error. Swallow it: the retry already succeeded and that
1503
+ * rejection must not abort the wrapped stream.
1412
1504
  */
1413
- await reader?.cancel();
1505
+ await reader?.cancel().catch(() => {});
1414
1506
  result = retriedResult.result;
1415
1507
  attempts = retriedResult.attempts;
1416
1508
  finalCallOptions = retriedResult.callOptions;
@@ -1,4 +1,4 @@
1
- import { E as Retry, N as Retryable, k as RetryContext, n as EmbeddingModel, s as ImageModel, x as ResolvableLanguageModel } from "./types-ydxC71oc.mjs";
1
+ import { A as RetryContext, D as Retry, P as Retryable, S as ResolvableLanguageModel, c as ImageModel, n as EmbeddingModel } from "./types-Dik-mH20.mjs";
2
2
 
3
3
  //#region src/experimental/internal/condition.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { P as RetryableModelOptions, n as EmbeddingModel } from "../../types-ydxC71oc.mjs";
2
- import "../../error-CoF13EJO.mjs";
1
+ import { F as RetryableModelOptions, n as EmbeddingModel } from "../../types-Dik-mH20.mjs";
2
+ import "../../error-B-rjhfG_.mjs";
3
3
  import { aborted, error, httpStatus, timeout } from "./retryables/index.mjs";
4
4
 
5
5
  //#region src/experimental/embedding-model/index.d.ts
@@ -1,4 +1,4 @@
1
- import { t as createRetryable$1 } from "../../create-retryable-model-VgywojZt.mjs";
1
+ import { t as createRetryable$1 } from "../../create-retryable-model-D36IQyOQ.mjs";
2
2
  import "../../error-CaTT-xX8.mjs";
3
3
  import { aborted, error, httpStatus, timeout } from "./retryables/index.mjs";
4
4
 
@@ -1,5 +1,5 @@
1
- import { k as RetryContext } from "../../../types-ydxC71oc.mjs";
2
- import { n as Condition, t as StatusPattern } from "../../../error-CoF13EJO.mjs";
1
+ import { A as RetryContext } from "../../../types-Dik-mH20.mjs";
2
+ import { n as Condition, t as StatusPattern } from "../../../error-B-rjhfG_.mjs";
3
3
  import * as _ai_sdk_provider0 from "@ai-sdk/provider";
4
4
 
5
5
  //#region src/experimental/embedding-model/retryables/index.d.ts
@@ -1,6 +1,6 @@
1
- import { P as RetryableModelOptions, s as ImageModel } from "../../types-ydxC71oc.mjs";
2
- import "../../error-CoF13EJO.mjs";
3
- import { a as noImage, i as timeout, n as error, r as httpStatus, t as aborted } from "../../index-D4w-HLk-.mjs";
1
+ import { F as RetryableModelOptions, c as ImageModel } from "../../types-Dik-mH20.mjs";
2
+ import "../../error-B-rjhfG_.mjs";
3
+ import { a as noImage, i as timeout, n as error, r as httpStatus, t as aborted } from "../../index-ewZ5T6B2.mjs";
4
4
 
5
5
  //#region src/experimental/image-model/index.d.ts
6
6
  declare function createRetryable<MODEL extends ImageModel>(options: RetryableModelOptions<MODEL>): ImageModel;
@@ -1,4 +1,4 @@
1
- import { t as createRetryable$1 } from "../../create-retryable-model-VgywojZt.mjs";
1
+ import { t as createRetryable$1 } from "../../create-retryable-model-D36IQyOQ.mjs";
2
2
  import "../../error-CaTT-xX8.mjs";
3
3
  import { a as noImage, i as timeout, n as error, r as httpStatus, t as aborted } from "../../retryables-CPAbu_M3.mjs";
4
4
 
@@ -1,4 +1,4 @@
1
- import "../../../types-ydxC71oc.mjs";
2
- import "../../../error-CoF13EJO.mjs";
3
- import { a as noImage, i as timeout, n as error, r as httpStatus, t as aborted } from "../../../index-D4w-HLk-.mjs";
1
+ import "../../../types-Dik-mH20.mjs";
2
+ import "../../../error-B-rjhfG_.mjs";
3
+ import { a as noImage, i as timeout, n as error, r as httpStatus, t as aborted } from "../../../index-ewZ5T6B2.mjs";
4
4
  export { aborted, error, httpStatus, noImage, timeout };
@@ -1,6 +1,6 @@
1
- import { P as RetryableModelOptions, d as LanguageModel, o as GatewayLanguageModelId } from "../../types-ydxC71oc.mjs";
2
- import "../../error-CoF13EJO.mjs";
3
- import { a as result, i as httpStatus, n as error, o as schemaInvalid, r as finishReason, s as timeout, t as aborted } from "../../index-K7qV8HD9.mjs";
1
+ import { F as RetryableModelOptions, f as LanguageModel, s as GatewayLanguageModelId } from "../../types-Dik-mH20.mjs";
2
+ import "../../error-B-rjhfG_.mjs";
3
+ import { a as result, i as httpStatus, n as error, o as schemaInvalid, r as finishReason, s as timeout, t as aborted } from "../../index-DaJrd4dN.mjs";
4
4
 
5
5
  //#region src/experimental/language-model/index.d.ts
6
6
  declare function createRetryable(options: Omit<RetryableModelOptions<LanguageModel>, 'model'> & {
@@ -1,4 +1,4 @@
1
- import { t as createRetryable$1 } from "../../create-retryable-model-VgywojZt.mjs";
1
+ import { t as createRetryable$1 } from "../../create-retryable-model-D36IQyOQ.mjs";
2
2
  import "../../error-CaTT-xX8.mjs";
3
3
  import { a as result, i as httpStatus, n as error, o as schemaInvalid, r as finishReason, s as timeout, t as aborted } from "../../retryables-M5l_6w9k.mjs";
4
4
 
@@ -1,4 +1,4 @@
1
- import "../../../types-ydxC71oc.mjs";
2
- import "../../../error-CoF13EJO.mjs";
3
- import { a as result, i as httpStatus, n as error, o as schemaInvalid, r as finishReason, s as timeout, t as aborted } from "../../../index-K7qV8HD9.mjs";
1
+ import "../../../types-Dik-mH20.mjs";
2
+ import "../../../error-B-rjhfG_.mjs";
3
+ import { a as result, i as httpStatus, n as error, o as schemaInvalid, r as finishReason, s as timeout, t as aborted } from "../../../index-DaJrd4dN.mjs";
4
4
  export { aborted, error, finishReason, httpStatus, result, schemaInvalid, timeout };
@@ -1,5 +1,5 @@
1
- import { k as RetryContext, m as LanguageModelResult, x as ResolvableLanguageModel } from "./types-ydxC71oc.mjs";
2
- import { n as Condition, t as StatusPattern } from "./error-CoF13EJO.mjs";
1
+ import { A as RetryContext, S as ResolvableLanguageModel, h as LanguageModelResult } from "./types-Dik-mH20.mjs";
2
+ import { n as Condition, t as StatusPattern } from "./error-B-rjhfG_.mjs";
3
3
 
4
4
  //#region src/experimental/internal/result.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { k as RetryContext, s as ImageModel } from "./types-ydxC71oc.mjs";
2
- import { n as Condition, t as StatusPattern } from "./error-CoF13EJO.mjs";
1
+ import { A as RetryContext, c as ImageModel } from "./types-Dik-mH20.mjs";
2
+ import { n as Condition, t as StatusPattern } from "./error-B-rjhfG_.mjs";
3
3
  import * as _ai_sdk_provider13 from "@ai-sdk/provider";
4
4
 
5
5
  //#region src/experimental/internal/no-image.d.ts
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as RetryErrorAttempt, C as ResolvedModel, D as RetryAttempt, E as Retry, F as RetryableOptions, I as SuccessAttempt, L as SuccessContext, M as RetryTelemetrySettings, N as Retryable, O as RetryCallOptions, P as RetryableModelOptions, S as ResolvableModel, T as Retries, _ as LanguageModelStreamPart, a as EmbeddingModelRetryCallOptions, b as Reset, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStream, h as LanguageModelRetryCallOptions, i as EmbeddingModelEmbed, j as RetryResultAttempt, k as RetryContext, l as ImageModelGenerate, m as LanguageModelResult, n as EmbeddingModel, o as GatewayLanguageModelId, p as LanguageModelGenerate, r as EmbeddingModelCallOptions, s as ImageModel, t as CallOptions, u as ImageModelRetryCallOptions, v as OnRetryOverrides, w as Result, x as ResolvableLanguageModel, y as ProviderOptions } from "./types-ydxC71oc.mjs";
1
+ import { A as RetryContext, C as ResolvableModel, D as Retry, E as Retries, F as RetryableModelOptions, I as RetryableOptions, L as SuccessAttempt, M as RetryResultAttempt, N as RetryTelemetrySettings, O as RetryAttempt, P as Retryable, R as SuccessContext, S as ResolvableLanguageModel, T as Result, _ as LanguageModelStream, a as EmbeddingModelRetryCallOptions, b as ProviderOptions, c as ImageModel, d as ImageModelRetryCallOptions, f as LanguageModel, g as LanguageModelRetryCallOptions, h as LanguageModelResult, i as EmbeddingModelEmbed, j as RetryErrorAttempt, k as RetryCallOptions, l as ImageModelCallOptions, m as LanguageModelGenerate, n as EmbeddingModel, o as FailureContext, p as LanguageModelCallOptions, r as EmbeddingModelCallOptions, s as GatewayLanguageModelId, t as CallOptions, u as ImageModelGenerate, v as LanguageModelStreamPart, w as ResolvedModel, x as Reset, y as OnRetryOverrides } from "./types-Dik-mH20.mjs";
2
2
 
3
3
  //#region src/create-retryable-model.d.ts
4
4
  declare function createRetryable<MODEL extends LanguageModel>(options: Omit<RetryableModelOptions<LanguageModel>, 'model'> & {
@@ -24,4 +24,4 @@ declare function isErrorAttempt(attempt: RetryAttempt<any>): attempt is RetryErr
24
24
  */
25
25
  declare function isResultAttempt(attempt: RetryAttempt<any>): attempt is RetryResultAttempt;
26
26
  //#endregion
27
- export { CallOptions, EmbeddingModel, EmbeddingModelCallOptions, EmbeddingModelEmbed, EmbeddingModelRetryCallOptions, GatewayLanguageModelId, ImageModel, ImageModelCallOptions, ImageModelGenerate, ImageModelRetryCallOptions, LanguageModel, LanguageModelCallOptions, LanguageModelGenerate, LanguageModelResult, LanguageModelRetryCallOptions, LanguageModelStream, LanguageModelStreamPart, OnRetryOverrides, ProviderOptions, Reset, ResolvableLanguageModel, ResolvableModel, ResolvedModel, Result, Retries, Retry, RetryAttempt, RetryCallOptions, RetryContext, RetryErrorAttempt, RetryResultAttempt, RetryTelemetrySettings, Retryable, RetryableModelOptions, RetryableOptions, SuccessAttempt, SuccessContext, createRetryable, getModelKey, isErrorAttempt, isResultAttempt };
27
+ export { CallOptions, EmbeddingModel, EmbeddingModelCallOptions, EmbeddingModelEmbed, EmbeddingModelRetryCallOptions, FailureContext, GatewayLanguageModelId, ImageModel, ImageModelCallOptions, ImageModelGenerate, ImageModelRetryCallOptions, LanguageModel, LanguageModelCallOptions, LanguageModelGenerate, LanguageModelResult, LanguageModelRetryCallOptions, LanguageModelStream, LanguageModelStreamPart, OnRetryOverrides, ProviderOptions, Reset, ResolvableLanguageModel, ResolvableModel, ResolvedModel, Result, Retries, Retry, RetryAttempt, RetryCallOptions, RetryContext, RetryErrorAttempt, RetryResultAttempt, RetryTelemetrySettings, Retryable, RetryableModelOptions, RetryableOptions, SuccessAttempt, SuccessContext, createRetryable, getModelKey, isErrorAttempt, isResultAttempt };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as getModelKey, t as createRetryable } from "./create-retryable-model-VgywojZt.mjs";
1
+ import { n as getModelKey, t as createRetryable } from "./create-retryable-model-D36IQyOQ.mjs";
2
2
  import { n as isErrorAttempt, s as isResultAttempt } from "./guards-D8UJtxDK.mjs";
3
3
 
4
4
  export { createRetryable, getModelKey, isErrorAttempt, isResultAttempt };
@@ -1,4 +1,4 @@
1
- import { F as RetryableOptions, N as Retryable, n as EmbeddingModel, s as ImageModel, x as ResolvableLanguageModel } from "../types-ydxC71oc.mjs";
1
+ import { I as RetryableOptions, P as Retryable, S as ResolvableLanguageModel, c as ImageModel, n as EmbeddingModel } from "../types-Dik-mH20.mjs";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
  /**
@@ -113,6 +113,26 @@ type SuccessContext<MODEL extends ResolvableLanguageModel | EmbeddingModel | Ima
113
113
  */
114
114
  attempts: Array<RetryAttempt<ResolvedModel<MODEL>>>;
115
115
  };
116
+ /**
117
+ * The context provided to onFailure when an operation terminally fails
118
+ * (no retry matched, retries exhausted, or the retry itself failed).
119
+ */
120
+ type FailureContext<MODEL extends ResolvableLanguageModel | EmbeddingModel | ImageModel> = {
121
+ /**
122
+ * The final attempt that failed.
123
+ */
124
+ current: RetryErrorAttempt<ResolvedModel<MODEL>>;
125
+ /**
126
+ * All attempts made, including the final failed one.
127
+ */
128
+ attempts: Array<RetryAttempt<ResolvedModel<MODEL>>>;
129
+ /**
130
+ * The error surfaced to the caller. When more than one attempt was made,
131
+ * this is a `RetryError` wrapping every attempt error; otherwise the raw
132
+ * error.
133
+ */
134
+ error: unknown;
135
+ };
116
136
  /**
117
137
  * Telemetry configuration for retry instrumentation.
118
138
  *
@@ -180,6 +200,14 @@ interface RetryableModelOptions<MODEL extends LanguageModel | EmbeddingModel | I
180
200
  */
181
201
  onRetry?: (context: RetryContext<MODEL>) => void | OnRetryOverrides<MODEL> | Promise<void | OnRetryOverrides<MODEL>>;
182
202
  onSuccess?: (context: SuccessContext<MODEL>) => void;
203
+ /**
204
+ * Called once when an operation terminally fails and the error could not
205
+ * be recovered by a retry: no retry matched, all retries were exhausted,
206
+ * or the retry itself failed. The counterpart to `onSuccess`.
207
+ *
208
+ * Not called when retries are disabled.
209
+ */
210
+ onFailure?: (context: FailureContext<MODEL>) => void;
183
211
  }
184
212
  /**
185
213
  * A model to retry with and the maximum number of attempts for that model.
@@ -243,4 +271,4 @@ type EmbeddingModelCallOptions = Parameters<EmbeddingModel['doEmbed']>[0];
243
271
  type EmbeddingModelEmbed = Awaited<ReturnType<EmbeddingModel['doEmbed']>>;
244
272
  type ImageModelGenerate = Awaited<ReturnType<ImageModel['doGenerate']>>;
245
273
  //#endregion
246
- export { RetryErrorAttempt as A, ResolvedModel as C, RetryAttempt as D, Retry as E, RetryableOptions as F, SuccessAttempt as I, SuccessContext as L, RetryTelemetrySettings as M, Retryable as N, RetryCallOptions as O, RetryableModelOptions as P, ResolvableModel as S, Retries as T, LanguageModelStreamPart as _, EmbeddingModelRetryCallOptions as a, Reset as b, ImageModelCallOptions as c, LanguageModel as d, LanguageModelCallOptions as f, LanguageModelStream as g, LanguageModelRetryCallOptions as h, EmbeddingModelEmbed as i, RetryResultAttempt as j, RetryContext as k, ImageModelGenerate as l, LanguageModelResult as m, EmbeddingModel as n, GatewayLanguageModelId as o, LanguageModelGenerate as p, EmbeddingModelCallOptions as r, ImageModel as s, CallOptions as t, ImageModelRetryCallOptions as u, OnRetryOverrides as v, Result as w, ResolvableLanguageModel as x, ProviderOptions as y };
274
+ export { RetryContext as A, ResolvableModel as C, Retry as D, Retries as E, RetryableModelOptions as F, RetryableOptions as I, SuccessAttempt as L, RetryResultAttempt as M, RetryTelemetrySettings as N, RetryAttempt as O, Retryable as P, SuccessContext as R, ResolvableLanguageModel as S, Result as T, LanguageModelStream as _, EmbeddingModelRetryCallOptions as a, ProviderOptions as b, ImageModel as c, ImageModelRetryCallOptions as d, LanguageModel as f, LanguageModelRetryCallOptions as g, LanguageModelResult as h, EmbeddingModelEmbed as i, RetryErrorAttempt as j, RetryCallOptions as k, ImageModelCallOptions as l, LanguageModelGenerate as m, EmbeddingModel as n, FailureContext as o, LanguageModelCallOptions as p, EmbeddingModelCallOptions as r, GatewayLanguageModelId as s, CallOptions as t, ImageModelGenerate as u, LanguageModelStreamPart as v, ResolvedModel as w, Reset as x, OnRetryOverrides as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Retry and fallback mechanisms for AI SDK",
5
5
  "keywords": [
6
6
  "ai",