ai-retry 1.5.0 → 1.6.1

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
@@ -288,6 +288,9 @@ If you need more control over when to retry and which model to use, you can crea
288
288
  > [!NOTE]
289
289
  > You can return additional options like `maxAttempts`, `delay`, etc. along with the model.
290
290
 
291
+ > [!TIP]
292
+ > If you'd like the same flexibility with a typed, composable condition system, see [Experimental: Composable Conditions](#experimental-composable-conditions).
293
+
291
294
  ```typescript
292
295
  import { anthropic } from '@ai-sdk/anthropic';
293
296
  import { openai } from '@ai-sdk/openai';
@@ -370,6 +373,9 @@ There are several built-in dynamic retryables available for common use cases:
370
373
  > [!TIP]
371
374
  > You are missing a retryable for your use case? [Open an issue](https://github.com/zirkelc/ai-retry/issues/new) and let's discuss it!
372
375
 
376
+ > [!NOTE]
377
+ > Looking for a composable alternative? See [Experimental: Composable Conditions](#experimental-composable-conditions) for a `condition().action()` API that builds on small primitives.
378
+
373
379
  - [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
374
380
  - [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
375
381
  - [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
@@ -578,6 +584,130 @@ const result = await generateText({
578
584
  console.log(result.object); // { name: "Alice", age: 30 }
579
585
  ```
580
586
 
587
+ ### Experimental: Composable Conditions
588
+
589
+ > [!WARNING]
590
+ > This API is experimental and may change. It is not exported from the package root; opt in via the deep import:
591
+ >
592
+ > ```ts
593
+ > import { ... } from 'ai-retry/retryables/experimental';
594
+ > ```
595
+
596
+ A `condition().action()` API for retryables. Conditions are built from small primitives (`error(fn)`, `result(fn)`), composed with `and` / `or` / `not`, and turned into a `Retryable` by one of two terminal actions: `.switch({ model })` or `.retry({ delay })`. The result drops into the same `retries: [...]` array as the stable helpers, so you can mix the two styles freely.
597
+
598
+ ```typescript
599
+ import { anthropic } from '@ai-sdk/anthropic';
600
+ import { openai } from '@ai-sdk/openai';
601
+ import { generateText } from 'ai';
602
+ import { createRetryable } from 'ai-retry';
603
+ import {
604
+ error,
605
+ finishReason,
606
+ httpStatus,
607
+ } from 'ai-retry/retryables/experimental';
608
+
609
+ const retryableModel = createRetryable({
610
+ model: openai('gpt-4'),
611
+ retries: [
612
+ // Switch on 529 or any "overloaded" message
613
+ httpStatus(529, 'overloaded').switch({
614
+ model: anthropic('claude-3-haiku-20240307'),
615
+ }),
616
+
617
+ // Switch when the response was content-filtered
618
+ finishReason('content-filter').switch({ model: openai('gpt-4o') }),
619
+
620
+ // Retry the same model with exponential backoff on retryable errors
621
+ error.isRetryable(true).retry({ delay: 1_000, backoffFactor: 2 }),
622
+ ],
623
+ });
624
+ ```
625
+
626
+ #### High-level helpers
627
+
628
+ These cover the common cases. Each returns a `Condition` that you finalize with `.switch(...)` or `.retry(...)`.
629
+
630
+ | Helper | Matches when |
631
+ | ------------------------------ | -------------------------------------------------------------------------------------------------- |
632
+ | `httpStatus(...patterns)` | Numbers match the status code; strings match the message (substring); regex matches either |
633
+ | `timeout()` | `Error.name === 'TimeoutError'` (`AbortSignal.timeout()` fired) |
634
+ | `aborted()` | `Error.name === 'AbortError'` (manual `controller.abort()`) |
635
+ | `noImage()` | The image model threw `NoImageGeneratedError` |
636
+ | `finishReason(...reasons)` | The result's `finishReason.unified` matches one of the given values |
637
+ | `schemaInvalid()` | The result text fails JSON-schema validation against the call's `responseFormat` |
638
+
639
+ #### Actions
640
+
641
+ Every `Condition` exposes two terminal actions that turn it into a `Retryable`:
642
+
643
+ - **`.switch({ model, ...options })`** falls back to a different model when the condition matches. Optional fields (`maxAttempts`, `delay`, `backoffFactor`, `timeout`, `options`) are the same as on a normal `Retry` object.
644
+ - **`.retry({ delay?, backoffFactor?, ... })`** retries the current model when the condition matches. Honors `Retry-After` and `Retry-After-Ms` response headers when present, capped at 60 seconds.
645
+
646
+ #### Combinators
647
+
648
+ Compose conditions with the free functions or the methods on `Condition`:
649
+
650
+ ```typescript
651
+ import {
652
+ and,
653
+ error,
654
+ httpStatus,
655
+ not,
656
+ or,
657
+ } from 'ai-retry/retryables/experimental';
658
+
659
+ or(httpStatus(429), error.message('overloaded'));
660
+ and(httpStatus(503), error.message('temporary'));
661
+ not(error.isRetryable(true));
662
+
663
+ // Method form
664
+ httpStatus(429).or(error.message('overloaded'));
665
+ ```
666
+
667
+ #### Primitives
668
+
669
+ The two lowest-level builders. Reach for them when no helper covers your case:
670
+
671
+ | Primitive | Matches when |
672
+ | ------------------ | ----------------------------------------------------------------------------- |
673
+ | `error(predicate)` | The current attempt failed and `predicate(err, ctx)` returns true |
674
+ | `result(predicate)`| The current attempt succeeded and `predicate(res, ctx)` returns true (language models only) |
675
+
676
+ ```typescript
677
+ import { APICallError } from 'ai';
678
+ import { error } from 'ai-retry/retryables/experimental';
679
+
680
+ error<MODEL, APICallError>(
681
+ (e) => APICallError.isInstance(e) && e.statusCode === 418,
682
+ ).switch({ model: fallback });
683
+ ```
684
+
685
+ A few common error fields have ready-made matchers on the `error` namespace:
686
+
687
+ | Helper | Matches when |
688
+ | ------------------------------- | ------------------------------------------------------------------------------------- |
689
+ | `error.isRetryable(flag)` | `APICallError.isRetryable === flag` (default `true`) |
690
+ | `error.statusCode(...patterns)` | Numbers match exactly; regex matches the stringified code (e.g. `/^5\d\d$/` for 5xx) |
691
+ | `error.message(...patterns)` | Substring (case-insensitive) or regex match against the error message |
692
+
693
+ #### Mapping from Built-in retryables
694
+
695
+ Each stable retryable has an equivalent in the new shape:
696
+
697
+ | Built-in | Composable form |
698
+ | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
699
+ | `contentFilterTriggered(m)` | `or(error(/* check e.data.error.code === 'content_filter' */), finishReason('content-filter')).switch({ model: m })` |
700
+ | `requestTimeout(m)` | `timeout().switch({ model: m, timeout: 60_000 })` |
701
+ | `requestNotRetryable(m)` | `error.isRetryable(false).switch({ model: m })` |
702
+ | `schemaMismatch(m)` | `schemaInvalid().switch({ model: m })` |
703
+ | `serviceOverloaded(m)` | `httpStatus(529, 'overloaded').switch({ model: m })` |
704
+ | `serviceUnavailable(m)` | `error.statusCode(503).switch({ model: m })` |
705
+ | `noImageGenerated(m)` | `noImage().switch({ model: m })` |
706
+ | `retryAfterDelay({ delay, backoffFactor })` | `error.isRetryable(true).retry({ delay, backoffFactor })` |
707
+
708
+ > [!NOTE]
709
+ > `error.isRetryable(true)` matches whatever the AI SDK's `APICallError` marks retryable. By default that's status codes 408, 409, 429, and any 5xx, plus network errors and provider-specific overrides (e.g. Anthropic flips it on `error.type === 'overloaded_error'`). It picks up more cases than a manual status-code list.
710
+
581
711
  ### Options
582
712
 
583
713
  #### Disabling Retries
@@ -652,7 +782,9 @@ const retryableModel = createRetryable({
652
782
 
653
783
  #### Timeouts
654
784
 
655
- When a retry specifies a `timeout` value, a fresh `AbortSignal.timeout()` is created for that retry attempt, replacing any existing abort signal. This is essential when retrying after timeout errors, as the original abort signal would already be in an aborted state.
785
+ When a retry specifies a `timeout` value, a fresh `AbortSignal.timeout()` is created for that retry attempt. If the original `abortSignal` is still alive, the fresh deadline is composed with it via `AbortSignal.any()` so user cancellation still works mid-retry. If the original signal is already aborted (for example it carried a request-level deadline that already fired), it is dropped so the retry runs against the fresh deadline alone.
786
+
787
+ If the original `abortSignal` is already aborted at the time of retry and the chosen retry does **not** supply a `timeout`, ai-retry rethrows the original error rather than firing a misleading retry against the dead signal. `onError` still fires for observability, but `onRetry` is skipped. Setting `retry.timeout` is the explicit opt-in for retrying past an aborted signal.
656
788
 
657
789
  ```typescript
658
790
  const retryableModel = createRetryable({
@@ -795,7 +927,7 @@ The following options can be overridden:
795
927
 
796
928
  #### Dynamic Call Options
797
929
 
798
- You can also override call options dynamically from inside the `onRetry` callback, instead of declaring them statically on the retry object. This is useful when the override depends on something only known at runtime, like the prompt that just failed, the model that's about to be tried next, or the error that triggered the retry. The overrides apply to the upcoming retry attempt only, and can change the same fields as the static `options` on a retry plus the request `timeout`. The callback may also be `async` if computing the override needs to do work (e.g. fetching a fresh credential).
930
+ You can also override call options dynamically from inside the `onRetry` callback, instead of declaring them statically on the retry object. This is useful when the override depends on something only known at runtime, like the prompt that just failed, the model that's about to be tried next, or the error that triggered the retry. The overrides apply to the upcoming retry attempt only, and can change the same fields as the static `options` on a retry. The callback may also be `async` if computing the override needs to do work (e.g. fetching a fresh credential).
799
931
 
800
932
  A common use case is sanitizing provider-scoped metadata when falling back to a different provider, for example stripping `providerOptions.azure.itemId` references from the previous prompt before retrying on OpenAI:
801
933
 
@@ -815,10 +947,7 @@ const retryableModel = createRetryable({
815
947
  // Strip provider-scoped metadata from the prompt before retrying on a different provider
816
948
  return {
817
949
  options: {
818
- prompt: sanitizePromptForProvider(
819
- previous.options.prompt,
820
- current.model.provider,
821
- ),
950
+ prompt: stripProviderMetadata(current.options.prompt),
822
951
  },
823
952
  };
824
953
  }
@@ -826,6 +955,8 @@ const retryableModel = createRetryable({
826
955
  });
827
956
  ```
828
957
 
958
+ Inside the `onRetry` callback, `context.current.model` is the model that's about to be tried next, while `context.current.options` and `context.current.error` describe the failed attempt that triggered the retry. The previous model is available at `context.attempts.at(-1).model`.
959
+
829
960
  `onRetry` may also be `async`, which is useful if computing the override needs to do work (e.g. fetching a fresh credential):
830
961
 
831
962
  ```typescript
@@ -844,7 +975,7 @@ const retryableModel = createRetryable({
844
975
  **Precedence** for the upcoming retry attempt (highest to lowest):
845
976
 
846
977
  1. The value returned from `onRetry`
847
- 2. `Retry.options` (declared on the retryable)
978
+ 2. The `options` returned from the retryable
848
979
  3. The original call options from the request
849
980
 
850
981
  #### Logging
@@ -961,7 +1092,7 @@ interface RetryableModelOptions<
961
1092
  - `disabled`: Disable all retry logic. Can be a boolean or function returning boolean. Default: `false` (retries enabled).
962
1093
  - `reset`: Controls when to reset back to the base model after a successful retry. Default: `after-request`.
963
1094
  - `onError`: Callback invoked when an error occurs.
964
- - `onRetry`: Callback invoked before attempting a retry. May optionally return an `OnRetryOverrides` object (or a `Promise` of one) to override `options.*` and `timeout` for the upcoming attempt only. See [Dynamic Call Options via `onRetry`](#dynamic-call-options-via-onretry).
1095
+ - `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).
965
1096
  - `onSuccess`: Callback invoked after a successful request. Receives the model that handled the request and all previous attempts.
966
1097
 
967
1098
  #### `Reset`
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as RetryResultAttempt, C as Result, D as RetryCallOptions, E as RetryAttempt, F as SuccessContext, M as RetryableModelOptions, N as RetryableOptions, O as RetryContext, P as SuccessAttempt, S as ResolvedModel, T as Retry, _ as OnRetryOverrides, a as EmbeddingModelRetryCallOptions, b as ResolvableLanguageModel, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStreamPart, h as LanguageModelStream, i as EmbeddingModelEmbed, j as Retryable, k as RetryErrorAttempt, 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 ProviderOptions, w as Retries, x as ResolvableModel, y as Reset } from "./types-wrgO_vOH.mjs";
1
+ import { A as RetryResultAttempt, C as Result, D as RetryCallOptions, E as RetryAttempt, F as SuccessContext, M as RetryableModelOptions, N as RetryableOptions, O as RetryContext, P as SuccessAttempt, S as ResolvedModel, T as Retry, _ as OnRetryOverrides, a as EmbeddingModelRetryCallOptions, b as ResolvableLanguageModel, c as ImageModelCallOptions, d as LanguageModel, f as LanguageModelCallOptions, g as LanguageModelStreamPart, h as LanguageModelStream, i as EmbeddingModelEmbed, j as Retryable, k as RetryErrorAttempt, 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 ProviderOptions, w as Retries, x as ResolvableModel, y as Reset } from "./types-CRKV-hdW.mjs";
2
2
  import * as _ai_sdk_provider0 from "@ai-sdk/provider";
3
3
 
4
4
  //#region src/create-retryable-model.d.ts
package/dist/index.mjs CHANGED
@@ -197,14 +197,18 @@ function resolveProviderOptions(base, currentRetry, onRetryOverrides) {
197
197
  /**
198
198
  * Resolve `abortSignal` for the upcoming attempt.
199
199
  *
200
- * If either `onRetryOverrides.timeout` or `currentRetry.timeout` is set, a
201
- * fresh `AbortSignal.timeout(...)` is created (override wins). Otherwise
202
- * the base `abortSignal` is preserved unchanged.
200
+ * If `currentRetry.timeout` is set, a fresh `AbortSignal.timeout(...)` is
201
+ * created. When the base signal is still alive, the fresh deadline is
202
+ * composed with it via `AbortSignal.any` so the user can still cancel
203
+ * mid-retry. When the base is already aborted, it is dropped so the retry
204
+ * runs against the fresh deadline alone. Without a retry timeout, the base
205
+ * is preserved unchanged.
203
206
  */
204
- function resolveAbortSignal(base, currentRetry, onRetryOverrides) {
205
- if (onRetryOverrides?.timeout !== void 0) return AbortSignal.timeout(onRetryOverrides.timeout);
206
- if (currentRetry?.timeout !== void 0) return AbortSignal.timeout(currentRetry.timeout);
207
- return base;
207
+ function resolveAbortSignal(base, currentRetry) {
208
+ if (currentRetry?.timeout === void 0) return base;
209
+ const fresh = AbortSignal.timeout(currentRetry.timeout);
210
+ if (base !== void 0 && !base.aborted) return AbortSignal.any([base, fresh]);
211
+ return fresh;
208
212
  }
209
213
  /**
210
214
  * Merge call options for the upcoming language model retry attempt.
@@ -231,7 +235,7 @@ function mergeLanguageModelCallOptions(input) {
231
235
  seed: overrideOptions.seed ?? retryOptions.seed ?? callOptions.seed,
232
236
  headers: overrideOptions.headers ?? retryOptions.headers ?? callOptions.headers,
233
237
  providerOptions: resolveProviderOptions(callOptions.providerOptions, currentRetry, onRetryOverrides),
234
- abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry, onRetryOverrides)
238
+ abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry)
235
239
  };
236
240
  }
237
241
  /**
@@ -246,7 +250,7 @@ function mergeEmbeddingModelCallOptions(input) {
246
250
  values: overrideOptions.values ?? retryOptions.values ?? callOptions.values,
247
251
  headers: overrideOptions.headers ?? retryOptions.headers ?? callOptions.headers,
248
252
  providerOptions: resolveProviderOptions(callOptions.providerOptions, currentRetry, onRetryOverrides),
249
- abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry, onRetryOverrides)
253
+ abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry)
250
254
  };
251
255
  }
252
256
  /**
@@ -264,7 +268,7 @@ function mergeImageModelCallOptions(input) {
264
268
  seed: overrideOptions.seed ?? retryOptions.seed ?? callOptions.seed,
265
269
  headers: overrideOptions.headers ?? retryOptions.headers ?? callOptions.headers,
266
270
  providerOptions: resolveProviderOptions(callOptions.providerOptions, currentRetry, onRetryOverrides),
267
- abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry, onRetryOverrides)
271
+ abortSignal: resolveAbortSignal(callOptions.abortSignal, currentRetry)
268
272
  };
269
273
  }
270
274
 
@@ -331,9 +335,15 @@ var RetryableEmbeddingModel = class extends BaseRetryableModel {
331
335
  callOptions: retryCallOptions
332
336
  };
333
337
  } catch (error) {
334
- if (isAbortError(error)) throw error;
335
338
  const { retryModel, attempt } = await this.handleError(error, attempts, retryCallOptions);
336
339
  attempts.push(attempt);
340
+ /**
341
+ * If the inbound abort signal is already aborted and the chosen
342
+ * retry does not supply a fresh deadline, the retry would die
343
+ * instantly with the same abort. Rethrow rather than fire a
344
+ * misleading retry against a dead signal.
345
+ */
346
+ if (input.callOptions.abortSignal?.aborted && retryModel.timeout === void 0) throw error;
337
347
  if (retryModel.delay) {
338
348
  /**
339
349
  * Calculate exponential backoff delay based on the number of attempts for this specific model.
@@ -474,11 +484,15 @@ var RetryableImageModel = class extends BaseRetryableModel {
474
484
  callOptions: retryCallOptions
475
485
  };
476
486
  } catch (error) {
477
- /** Don't retry if user manually aborted the request. */
478
- /** TimeoutError from AbortSignal.timeout() will still be handled by retry handlers. */
479
- if (isAbortError(error)) throw error;
480
487
  const { retryModel, attempt } = await this.handleError(error, attempts, retryCallOptions);
481
488
  attempts.push(attempt);
489
+ /**
490
+ * If the inbound abort signal is already aborted and the chosen
491
+ * retry does not supply a fresh deadline, the retry would die
492
+ * instantly with the same abort. Rethrow rather than fire a
493
+ * misleading retry against a dead signal.
494
+ */
495
+ if (input.callOptions.abortSignal?.aborted && retryModel.timeout === void 0) throw error;
482
496
  if (retryModel.delay) {
483
497
  /**
484
498
  * Calculate exponential backoff delay based on the number of attempts for this specific model.
@@ -650,9 +664,15 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
650
664
  callOptions: retryCallOptions
651
665
  };
652
666
  } catch (error) {
653
- if (isAbortError(error)) throw error;
654
667
  const { retryModel, attempt } = await this.handleError(error, attempts, retryCallOptions);
655
668
  attempts.push(attempt);
669
+ /**
670
+ * If the inbound abort signal is already aborted and the chosen
671
+ * retry does not supply a fresh deadline, the retry would die
672
+ * instantly with the same abort. Rethrow rather than fire a
673
+ * misleading retry against a dead signal.
674
+ */
675
+ if (input.callOptions.abortSignal?.aborted && retryModel.timeout === void 0) throw error;
656
676
  if (retryModel.delay) {
657
677
  /**
658
678
  * Calculate exponential backoff delay based on the number of attempts for this specific model.
@@ -825,6 +845,13 @@ var RetryableLanguageModel = class extends BaseRetryableModel {
825
845
  * Save the attempt
826
846
  */
827
847
  attempts.push(attempt);
848
+ /**
849
+ * If the inbound abort signal is already aborted and the chosen
850
+ * retry does not supply a fresh deadline, the retry would die
851
+ * instantly with the same abort. Rethrow rather than fire a
852
+ * misleading retry against a dead signal.
853
+ */
854
+ if (callOptions.abortSignal?.aborted && retryModel.timeout === void 0) throw error;
828
855
  if (retryModel.delay) {
829
856
  /**
830
857
  * Calculate exponential backoff delay based on the number of attempts for this specific model.
@@ -0,0 +1,26 @@
1
+ //#region src/parse-retry-headers.ts
2
+ /**
3
+ * Upper bound for `Retry-After` / `Retry-After-Ms` honored by retry actions.
4
+ * Servers can technically request very long delays; cap to keep retries
5
+ * responsive.
6
+ */
7
+ const MAX_RETRY_AFTER_MS = 6e4;
8
+ function parseRetryHeaders(headers) {
9
+ if (!headers) return null;
10
+ const retryAfterMs = headers["retry-after-ms"];
11
+ if (retryAfterMs) {
12
+ const delayMs = Number.parseFloat(retryAfterMs);
13
+ if (!Number.isNaN(delayMs) && delayMs >= 0) return delayMs;
14
+ }
15
+ const retryAfter = headers["retry-after"];
16
+ if (retryAfter) {
17
+ const seconds = Number.parseFloat(retryAfter);
18
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
19
+ const date = Date.parse(retryAfter);
20
+ if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
21
+ }
22
+ return null;
23
+ }
24
+
25
+ //#endregion
26
+ export { parseRetryHeaders as n, MAX_RETRY_AFTER_MS as t };
@@ -0,0 +1,248 @@
1
+ import { O as RetryContext, T as Retry, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, p as LanguageModelGenerate, s as ImageModel } from "../../types-CRKV-hdW.mjs";
2
+
3
+ //#region src/retryables/experimental/condition.d.ts
4
+ /**
5
+ * Any model the retryable system supports.
6
+ */
7
+ type AnyModel = ResolvableLanguageModel | EmbeddingModel | ImageModel;
8
+ /**
9
+ * Predicate over a `RetryContext`. May be sync or async.
10
+ */
11
+ type Predicate<MODEL extends AnyModel> = (ctx: RetryContext<MODEL>) => boolean | Promise<boolean>;
12
+ /**
13
+ * Argument shape for `Condition.switch`. The target `model` is required;
14
+ * all other `Retry` fields are optional.
15
+ */
16
+ type SwitchTarget<MODEL extends AnyModel> = {
17
+ model: MODEL;
18
+ } & Omit<Retry<MODEL>, 'model'>;
19
+ /**
20
+ * Argument shape for `Condition.retry`. Same as `Retry` without `model`,
21
+ * since retry reuses the current model.
22
+ */
23
+ type RetryOptions<MODEL extends AnyModel> = Omit<Retry<MODEL>, 'model'>;
24
+ /**
25
+ * A predicate over a `RetryContext` paired with two terminal actions
26
+ * (`switch`, `retry`) that turn it into a `Retryable<MODEL>`. Compose
27
+ * conditions with `and`, `or`, `not`.
28
+ *
29
+ * @example
30
+ * const cond = httpStatus(429, 503);
31
+ * cond.switch({ model: fallback });
32
+ * cond.retry({ delay: 1000 });
33
+ */
34
+ declare class Condition<MODEL extends AnyModel> {
35
+ private readonly predicate;
36
+ constructor(predicate: Predicate<MODEL>);
37
+ /**
38
+ * Run the predicate against a context and resolve to a boolean.
39
+ */
40
+ evaluate(ctx: RetryContext<MODEL>): Promise<boolean>;
41
+ /**
42
+ * Switch to a different model when the condition matches.
43
+ *
44
+ * @example
45
+ * httpStatus(529).switch({ model: fallback })
46
+ */
47
+ switch(target: SwitchTarget<MODEL>): Retryable<MODEL>;
48
+ /**
49
+ * Retry the same model when the condition matches. Honors
50
+ * `Retry-After` and `Retry-After-Ms` response headers when present,
51
+ * capped at 60 seconds, overriding any provided `delay`.
52
+ *
53
+ * @example
54
+ * error.isRetryable(true).retry({ delay: 1000, backoffFactor: 2 })
55
+ */
56
+ retry(options?: RetryOptions<MODEL>): Retryable<MODEL>;
57
+ /**
58
+ * Combine with another condition; matches when both match.
59
+ *
60
+ * @example
61
+ * httpStatus(429).and(error.message('overloaded'))
62
+ */
63
+ and(other: Condition<MODEL>): Condition<MODEL>;
64
+ /**
65
+ * Combine with another condition; matches when either matches.
66
+ *
67
+ * @example
68
+ * httpStatus(429).or(error.message('overloaded'))
69
+ */
70
+ or(other: Condition<MODEL>): Condition<MODEL>;
71
+ /**
72
+ * Invert the condition.
73
+ *
74
+ * @example
75
+ * error.isRetryable(true).not()
76
+ */
77
+ not(): Condition<MODEL>;
78
+ }
79
+ //#endregion
80
+ //#region src/retryables/experimental/aborted.d.ts
81
+ /**
82
+ * Match a manual abort: an `Error` with `name === 'AbortError'`, which
83
+ * `controller.abort()` produces. Distinct from `timeout()`, which
84
+ * matches `AbortSignal.timeout()` firing.
85
+ *
86
+ * @example
87
+ * aborted().switch({ model: fallback })
88
+ */
89
+ declare function aborted<MODEL extends AnyModel = AnyModel>(): Condition<MODEL>;
90
+ //#endregion
91
+ //#region src/retryables/experimental/and.d.ts
92
+ /**
93
+ * Match only when all of the given conditions match. Evaluates left to
94
+ * right and stops on the first miss.
95
+ *
96
+ * @example
97
+ * and(httpStatus(429), error.message('overloaded'))
98
+ */
99
+ declare function and<MODEL extends AnyModel>(...conditions: Array<Condition<MODEL>>): Condition<MODEL>;
100
+ //#endregion
101
+ //#region src/retryables/experimental/error.d.ts
102
+ /**
103
+ * Build a condition from a predicate over the current error. The
104
+ * predicate runs only when the current attempt failed with an error;
105
+ * result attempts return false.
106
+ *
107
+ * @example
108
+ * error<MODEL, APICallError>(
109
+ * (e) => APICallError.isInstance(e) && e.statusCode === 418,
110
+ * )
111
+ */
112
+ declare function error<MODEL extends AnyModel = AnyModel, E = unknown>(predicate: (err: E, ctx: RetryContext<MODEL>) => boolean | Promise<boolean>): Condition<MODEL>;
113
+ /**
114
+ * Higher-level matchers for common error fields. Drop down to `error(fn)`
115
+ * for anything not covered.
116
+ *
117
+ * `Error.name` is intentionally not exposed here because the property
118
+ * name would clash with `Function.prototype.name`. Use `timeout()` or
119
+ * `aborted()` for the common cases, or `error(fn)` for custom names.
120
+ */
121
+ declare namespace error {
122
+ /**
123
+ * Match when the error explicitly carries `isRetryable === flag`.
124
+ *
125
+ * @example
126
+ * error.isRetryable(true).retry({ delay: 1000 })
127
+ * error.isRetryable(false).switch({ model: fallback })
128
+ */
129
+ function isRetryable<MODEL extends AnyModel = AnyModel>(flag?: boolean): Condition<MODEL>;
130
+ /**
131
+ * Match by HTTP status code. Numbers match exactly; regular expressions
132
+ * match against the stringified code, useful for range checks.
133
+ *
134
+ * @example
135
+ * error.statusCode(429, 503)
136
+ * error.statusCode(/^5\d\d$/)
137
+ */
138
+ function statusCode<MODEL extends AnyModel = AnyModel>(...patterns: Array<number | RegExp>): Condition<MODEL>;
139
+ /**
140
+ * Match the error message against substrings or regular expressions.
141
+ * Substring matching is case-insensitive: both the pattern and the
142
+ * message are lowercased before matching. Regular expressions match
143
+ * as written; use the `i` flag for case-insensitive regex matching.
144
+ *
145
+ * @example
146
+ * error.message('overloaded')
147
+ * error.message(/rate.?limit/i)
148
+ * error.message('overloaded', /rate.?limit/i)
149
+ */
150
+ function message<MODEL extends AnyModel = AnyModel>(...patterns: Array<string | RegExp>): Condition<MODEL>;
151
+ }
152
+ //#endregion
153
+ //#region src/retryables/experimental/finish-reason.d.ts
154
+ /**
155
+ * Match the result's finish reason against one of the given values.
156
+ *
157
+ * @example
158
+ * finishReason('content-filter')
159
+ * finishReason('content-filter', 'length')
160
+ */
161
+ declare function finishReason<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(...reasons: Array<string>): Condition<MODEL>;
162
+ //#endregion
163
+ //#region src/retryables/experimental/http-status.d.ts
164
+ /**
165
+ * A pattern accepted by `httpStatus`. Numbers match the response status
166
+ * code; strings match the error message as a substring; regular
167
+ * expressions match against both the stringified status code and the
168
+ * error message.
169
+ */
170
+ type StatusPattern = number | string | RegExp;
171
+ /**
172
+ * Match an `APICallError` by status code, message substring, or regular
173
+ * expression. Numbers match the status code; strings match the message;
174
+ * regular expressions match either the stringified status code or the
175
+ * message. Mix any combination in a single call; matches when any
176
+ * pattern matches.
177
+ *
178
+ * @example
179
+ * httpStatus(529)
180
+ * httpStatus(529, 'overloaded')
181
+ * httpStatus(/^5\d\d$/)
182
+ * httpStatus(529, 'overloaded', /rate.?limit/i)
183
+ */
184
+ declare function httpStatus<MODEL extends AnyModel = AnyModel>(...patterns: Array<StatusPattern>): Condition<MODEL>;
185
+ //#endregion
186
+ //#region src/retryables/experimental/no-image.d.ts
187
+ /**
188
+ * Match when image generation produced no images
189
+ * (`NoImageGeneratedError`).
190
+ *
191
+ * @example
192
+ * noImage().switch({ model: fallback })
193
+ */
194
+ declare function noImage<MODEL extends ImageModel = ImageModel>(): Condition<MODEL>;
195
+ //#endregion
196
+ //#region src/retryables/experimental/not.d.ts
197
+ /**
198
+ * Invert a condition.
199
+ *
200
+ * @example
201
+ * not(error.isRetryable(true))
202
+ */
203
+ declare function not<MODEL extends AnyModel>(condition: Condition<MODEL>): Condition<MODEL>;
204
+ //#endregion
205
+ //#region src/retryables/experimental/or.d.ts
206
+ /**
207
+ * Match when any of the given conditions match. Evaluates left to right
208
+ * and stops on the first match.
209
+ *
210
+ * @example
211
+ * or(httpStatus(429), error.message('overloaded'))
212
+ */
213
+ declare function or<MODEL extends AnyModel>(...conditions: Array<Condition<MODEL>>): Condition<MODEL>;
214
+ //#endregion
215
+ //#region src/retryables/experimental/result.d.ts
216
+ /**
217
+ * Build a condition from a predicate over the current generate result.
218
+ * Available for language models only. The predicate runs only when the
219
+ * current attempt succeeded; error attempts return false.
220
+ *
221
+ * @example
222
+ * result<MODEL>((res) => res.finishReason.unified === 'length')
223
+ */
224
+ declare function result<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(predicate: (res: LanguageModelGenerate, ctx: RetryContext<MODEL>) => boolean | Promise<boolean>): Condition<MODEL>;
225
+ //#endregion
226
+ //#region src/retryables/experimental/schema-invalid.d.ts
227
+ /**
228
+ * Match when the result text fails JSON schema validation. The schema is
229
+ * read from the call's `responseFormat`, which `Output.object()` sets
230
+ * automatically. No-op when no schema is configured.
231
+ *
232
+ * @example
233
+ * schemaInvalid().switch({ model: fallback })
234
+ */
235
+ declare function schemaInvalid<MODEL extends ResolvableLanguageModel = ResolvableLanguageModel>(): Condition<MODEL>;
236
+ //#endregion
237
+ //#region src/retryables/experimental/timeout.d.ts
238
+ /**
239
+ * Match a timeout error: an `Error` with `name === 'TimeoutError'`,
240
+ * which `AbortSignal.timeout()` produces when the timeout fires.
241
+ * Distinct from `aborted()`, which matches manual aborts.
242
+ *
243
+ * @example
244
+ * timeout().switch({ model: fallback, timeout: 60_000 })
245
+ */
246
+ declare function timeout<MODEL extends AnyModel = AnyModel>(): Condition<MODEL>;
247
+ //#endregion
248
+ export { AnyModel, Condition, Predicate, RetryOptions, StatusPattern, SwitchTarget, aborted, and, error, finishReason, httpStatus, noImage, not, or, result, schemaInvalid, timeout };
@@ -0,0 +1,310 @@
1
+ import { l as isResultAttempt, r as isErrorAttempt } from "../../utils-CfnsSGrw.mjs";
2
+ import { n as parseRetryHeaders, t as MAX_RETRY_AFTER_MS } from "../../parse-retry-headers-DIPVbwW5.mjs";
3
+ import { APICallError, NoImageGeneratedError } from "ai";
4
+ import { safeParseJSON } from "@ai-sdk/provider-utils";
5
+ import { fromJSONSchema } from "zod";
6
+
7
+ //#region src/retryables/experimental/condition.ts
8
+ /**
9
+ * A predicate over a `RetryContext` paired with two terminal actions
10
+ * (`switch`, `retry`) that turn it into a `Retryable<MODEL>`. Compose
11
+ * conditions with `and`, `or`, `not`.
12
+ *
13
+ * @example
14
+ * const cond = httpStatus(429, 503);
15
+ * cond.switch({ model: fallback });
16
+ * cond.retry({ delay: 1000 });
17
+ */
18
+ var Condition = class Condition {
19
+ constructor(predicate) {
20
+ this.predicate = predicate;
21
+ }
22
+ /**
23
+ * Run the predicate against a context and resolve to a boolean.
24
+ */
25
+ async evaluate(ctx) {
26
+ return this.predicate(ctx);
27
+ }
28
+ /**
29
+ * Switch to a different model when the condition matches.
30
+ *
31
+ * @example
32
+ * httpStatus(529).switch({ model: fallback })
33
+ */
34
+ switch(target) {
35
+ return async (ctx) => {
36
+ if (!await this.evaluate(ctx)) return void 0;
37
+ return {
38
+ maxAttempts: 1,
39
+ ...target
40
+ };
41
+ };
42
+ }
43
+ /**
44
+ * Retry the same model when the condition matches. Honors
45
+ * `Retry-After` and `Retry-After-Ms` response headers when present,
46
+ * capped at 60 seconds, overriding any provided `delay`.
47
+ *
48
+ * @example
49
+ * error.isRetryable(true).retry({ delay: 1000, backoffFactor: 2 })
50
+ */
51
+ retry(options) {
52
+ return async (ctx) => {
53
+ if (!await this.evaluate(ctx)) return void 0;
54
+ const model = ctx.current.model;
55
+ if (isErrorAttempt(ctx.current)) {
56
+ const { error: err } = ctx.current;
57
+ if (APICallError.isInstance(err)) {
58
+ const headerDelay = parseRetryHeaders(err.responseHeaders);
59
+ if (headerDelay !== null) return {
60
+ model,
61
+ ...options,
62
+ delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
63
+ backoffFactor: 1
64
+ };
65
+ }
66
+ }
67
+ return {
68
+ model,
69
+ ...options
70
+ };
71
+ };
72
+ }
73
+ /**
74
+ * Combine with another condition; matches when both match.
75
+ *
76
+ * @example
77
+ * httpStatus(429).and(error.message('overloaded'))
78
+ */
79
+ and(other) {
80
+ return new Condition(async (ctx) => await this.evaluate(ctx) && await other.evaluate(ctx));
81
+ }
82
+ /**
83
+ * Combine with another condition; matches when either matches.
84
+ *
85
+ * @example
86
+ * httpStatus(429).or(error.message('overloaded'))
87
+ */
88
+ or(other) {
89
+ return new Condition(async (ctx) => await this.evaluate(ctx) || await other.evaluate(ctx));
90
+ }
91
+ /**
92
+ * Invert the condition.
93
+ *
94
+ * @example
95
+ * error.isRetryable(true).not()
96
+ */
97
+ not() {
98
+ return new Condition(async (ctx) => !await this.evaluate(ctx));
99
+ }
100
+ };
101
+
102
+ //#endregion
103
+ //#region src/retryables/experimental/error.ts
104
+ /**
105
+ * Build a condition from a predicate over the current error. The
106
+ * predicate runs only when the current attempt failed with an error;
107
+ * result attempts return false.
108
+ *
109
+ * @example
110
+ * error<MODEL, APICallError>(
111
+ * (e) => APICallError.isInstance(e) && e.statusCode === 418,
112
+ * )
113
+ */
114
+ function error(predicate) {
115
+ return new Condition(async (ctx) => {
116
+ if (!isErrorAttempt(ctx.current)) return false;
117
+ return predicate(ctx.current.error, ctx);
118
+ });
119
+ }
120
+ (function(_error) {
121
+ function isRetryable(flag = true) {
122
+ return error((e) => APICallError.isInstance(e) && e.isRetryable === flag);
123
+ }
124
+ _error.isRetryable = isRetryable;
125
+ function statusCode(...patterns) {
126
+ return error((e) => {
127
+ if (!APICallError.isInstance(e)) return false;
128
+ const code = e.statusCode;
129
+ if (code === void 0) return false;
130
+ return patterns.some((p) => typeof p === "number" ? p === code : p.test(String(code)));
131
+ });
132
+ }
133
+ _error.statusCode = statusCode;
134
+ function message(...patterns) {
135
+ return error((e) => {
136
+ if (!(e instanceof Error)) return false;
137
+ const lower = e.message.toLowerCase();
138
+ return patterns.some((p) => typeof p === "string" ? lower.includes(p.toLowerCase()) : p.test(e.message));
139
+ });
140
+ }
141
+ _error.message = message;
142
+ })(error || (error = {}));
143
+
144
+ //#endregion
145
+ //#region src/retryables/experimental/aborted.ts
146
+ /**
147
+ * Match a manual abort: an `Error` with `name === 'AbortError'`, which
148
+ * `controller.abort()` produces. Distinct from `timeout()`, which
149
+ * matches `AbortSignal.timeout()` firing.
150
+ *
151
+ * @example
152
+ * aborted().switch({ model: fallback })
153
+ */
154
+ function aborted() {
155
+ return error((err) => err instanceof Error && err.name === "AbortError");
156
+ }
157
+
158
+ //#endregion
159
+ //#region src/retryables/experimental/and.ts
160
+ /**
161
+ * Match only when all of the given conditions match. Evaluates left to
162
+ * right and stops on the first miss.
163
+ *
164
+ * @example
165
+ * and(httpStatus(429), error.message('overloaded'))
166
+ */
167
+ function and(...conditions) {
168
+ return new Condition(async (ctx) => {
169
+ for (const c of conditions) if (!await c.evaluate(ctx)) return false;
170
+ return true;
171
+ });
172
+ }
173
+
174
+ //#endregion
175
+ //#region src/retryables/experimental/result.ts
176
+ /**
177
+ * Build a condition from a predicate over the current generate result.
178
+ * Available for language models only. The predicate runs only when the
179
+ * current attempt succeeded; error attempts return false.
180
+ *
181
+ * @example
182
+ * result<MODEL>((res) => res.finishReason.unified === 'length')
183
+ */
184
+ function result(predicate) {
185
+ return new Condition(async (ctx) => {
186
+ if (!isResultAttempt(ctx.current)) return false;
187
+ return predicate(ctx.current.result, ctx);
188
+ });
189
+ }
190
+
191
+ //#endregion
192
+ //#region src/retryables/experimental/finish-reason.ts
193
+ /**
194
+ * Match the result's finish reason against one of the given values.
195
+ *
196
+ * @example
197
+ * finishReason('content-filter')
198
+ * finishReason('content-filter', 'length')
199
+ */
200
+ function finishReason(...reasons) {
201
+ return result((res) => reasons.includes(res.finishReason.unified));
202
+ }
203
+
204
+ //#endregion
205
+ //#region src/retryables/experimental/or.ts
206
+ /**
207
+ * Match when any of the given conditions match. Evaluates left to right
208
+ * and stops on the first match.
209
+ *
210
+ * @example
211
+ * or(httpStatus(429), error.message('overloaded'))
212
+ */
213
+ function or(...conditions) {
214
+ return new Condition(async (ctx) => {
215
+ for (const c of conditions) if (await c.evaluate(ctx)) return true;
216
+ return false;
217
+ });
218
+ }
219
+
220
+ //#endregion
221
+ //#region src/retryables/experimental/http-status.ts
222
+ /**
223
+ * Match an `APICallError` by status code, message substring, or regular
224
+ * expression. Numbers match the status code; strings match the message;
225
+ * regular expressions match either the stringified status code or the
226
+ * message. Mix any combination in a single call; matches when any
227
+ * pattern matches.
228
+ *
229
+ * @example
230
+ * httpStatus(529)
231
+ * httpStatus(529, 'overloaded')
232
+ * httpStatus(/^5\d\d$/)
233
+ * httpStatus(529, 'overloaded', /rate.?limit/i)
234
+ */
235
+ function httpStatus(...patterns) {
236
+ const numbers = patterns.filter((p) => typeof p === "number");
237
+ const strings = patterns.filter((p) => typeof p === "string");
238
+ const regexes = patterns.filter((p) => p instanceof RegExp);
239
+ const conditions = [];
240
+ if (numbers.length || regexes.length) conditions.push(error.statusCode(...numbers, ...regexes));
241
+ if (strings.length || regexes.length) conditions.push(error.message(...strings, ...regexes));
242
+ return or(...conditions);
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/retryables/experimental/no-image.ts
247
+ /**
248
+ * Match when image generation produced no images
249
+ * (`NoImageGeneratedError`).
250
+ *
251
+ * @example
252
+ * noImage().switch({ model: fallback })
253
+ */
254
+ function noImage() {
255
+ return error((err) => NoImageGeneratedError.isInstance(err));
256
+ }
257
+
258
+ //#endregion
259
+ //#region src/retryables/experimental/not.ts
260
+ /**
261
+ * Invert a condition.
262
+ *
263
+ * @example
264
+ * not(error.isRetryable(true))
265
+ */
266
+ function not(condition) {
267
+ return condition.not();
268
+ }
269
+
270
+ //#endregion
271
+ //#region src/retryables/experimental/schema-invalid.ts
272
+ /**
273
+ * Match when the result text fails JSON schema validation. The schema is
274
+ * read from the call's `responseFormat`, which `Output.object()` sets
275
+ * automatically. No-op when no schema is configured.
276
+ *
277
+ * @example
278
+ * schemaInvalid().switch({ model: fallback })
279
+ */
280
+ function schemaInvalid() {
281
+ return result(async (res, ctx) => {
282
+ if (!isResultAttempt(ctx.current)) return false;
283
+ const callOptions = ctx.current.options;
284
+ const text = res.content.filter((part) => part.type === "text").map((part) => part.text).join("");
285
+ if (!text) return false;
286
+ const responseFormat = callOptions.responseFormat;
287
+ if (responseFormat?.type !== "json" || !responseFormat.schema) return false;
288
+ return !(await safeParseJSON({
289
+ text,
290
+ schema: fromJSONSchema(responseFormat.schema)
291
+ })).success;
292
+ });
293
+ }
294
+
295
+ //#endregion
296
+ //#region src/retryables/experimental/timeout.ts
297
+ /**
298
+ * Match a timeout error: an `Error` with `name === 'TimeoutError'`,
299
+ * which `AbortSignal.timeout()` produces when the timeout fires.
300
+ * Distinct from `aborted()`, which matches manual aborts.
301
+ *
302
+ * @example
303
+ * timeout().switch({ model: fallback, timeout: 60_000 })
304
+ */
305
+ function timeout() {
306
+ return error((err) => err instanceof Error && err.name === "TimeoutError");
307
+ }
308
+
309
+ //#endregion
310
+ export { Condition, aborted, and, error, finishReason, httpStatus, noImage, not, or, result, schemaInvalid, timeout };
@@ -1,4 +1,4 @@
1
- import { N as RetryableOptions, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, s as ImageModel } from "../types-wrgO_vOH.mjs";
1
+ import { N as RetryableOptions, b as ResolvableLanguageModel, j as Retryable, n as EmbeddingModel, s as ImageModel } from "../types-CRKV-hdW.mjs";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
  /**
@@ -1,4 +1,5 @@
1
1
  import { c as isObject, f as isString, l as isResultAttempt, p as isTimeoutError, r as isErrorAttempt } from "../utils-CfnsSGrw.mjs";
2
+ import { n as parseRetryHeaders, t as MAX_RETRY_AFTER_MS } from "../parse-retry-headers-DIPVbwW5.mjs";
2
3
  import { APICallError, NoImageGeneratedError } from "ai";
3
4
  import { safeParseJSON } from "@ai-sdk/provider-utils";
4
5
  import { fromJSONSchema } from "zod";
@@ -91,28 +92,8 @@ function requestTimeout(model, options) {
91
92
  };
92
93
  }
93
94
 
94
- //#endregion
95
- //#region src/parse-retry-headers.ts
96
- function parseRetryHeaders(headers) {
97
- if (!headers) return null;
98
- const retryAfterMs = headers["retry-after-ms"];
99
- if (retryAfterMs) {
100
- const delayMs = Number.parseFloat(retryAfterMs);
101
- if (!Number.isNaN(delayMs) && delayMs >= 0) return delayMs;
102
- }
103
- const retryAfter = headers["retry-after"];
104
- if (retryAfter) {
105
- const seconds = Number.parseFloat(retryAfter);
106
- if (!Number.isNaN(seconds)) return seconds * 1e3;
107
- const date = Date.parse(retryAfter);
108
- if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
109
- }
110
- return null;
111
- }
112
-
113
95
  //#endregion
114
96
  //#region src/retryables/retry-after-delay.ts
115
- const MAX_RETRY_AFTER_MS = 6e4;
116
97
  /**
117
98
  * Retry the current failed attempt with the same model, if the error is retryable.
118
99
  * Uses the `Retry-After` or `Retry-After-Ms` headers if present.
@@ -34,7 +34,7 @@ type RetryCallOptions<MODEL extends LanguageModel | EmbeddingModel | ImageModel>
34
34
  /**
35
35
  * Override returned by `onRetry` to influence the upcoming retry attempt.
36
36
  */
37
- type OnRetryOverrides<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = Pick<Retry<MODEL>, 'options' | 'timeout'>;
37
+ type OnRetryOverrides<MODEL extends LanguageModel | EmbeddingModel | ImageModel> = Pick<Retry<MODEL>, 'options'>;
38
38
  /**
39
39
  * Maps a model type to its call options type.
40
40
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Retry and fallback mechanisms for AI SDK",
5
5
  "keywords": [
6
6
  "ai",
@@ -22,6 +22,7 @@
22
22
  "exports": {
23
23
  ".": "./dist/index.mjs",
24
24
  "./retryables": "./dist/retryables/index.mjs",
25
+ "./retryables/experimental": "./dist/retryables/experimental/index.mjs",
25
26
  "./package.json": "./package.json"
26
27
  },
27
28
  "publishConfig": {