ai-retry 0.8.0 → 0.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
@@ -251,12 +251,16 @@ Errors are tracked per unique model (provider + modelId). That means on the firs
251
251
 
252
252
  There are several built-in dynamic retryables available for common use cases:
253
253
 
254
+ > [!TIP]
255
+ > 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!
256
+
254
257
  - [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
255
258
  - [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
256
259
  - [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
257
260
  - [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
258
261
  - [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
259
262
  - Use this retryable to handle Anthropic's overloaded errors.
263
+ - [`serviceUnavailable`](./src/retryables/service-unavailable.ts): Response with status code 503 (service unavailable).
260
264
 
261
265
  #### Content Filter
262
266
 
@@ -324,6 +328,21 @@ const retryableModel = createRetryable({
324
328
  });
325
329
  ```
326
330
 
331
+ #### Service Unavailable
332
+
333
+ Handle service unavailable errors (status code 503) by switching to a different provider.
334
+
335
+ ```typescript
336
+ import { serviceUnavailable } from 'ai-retry/retryables';
337
+
338
+ const retryableModel = createRetryable({
339
+ model: azure('gpt-4'),
340
+ retries: [
341
+ serviceUnavailable(openai('gpt-4')), // Switch to OpenAI if Azure is unavailable
342
+ ],
343
+ });
344
+ ```
345
+
327
346
  #### Request Not Retryable
328
347
 
329
348
  Handle cases where the base model fails with a non-retryable error.
@@ -372,6 +391,32 @@ By default, if a [`retry-after-ms`](https://learn.microsoft.com/en-us/azure/ai-f
372
391
 
373
392
  ### Options
374
393
 
394
+ #### Disabling Retries
395
+
396
+ You can disable retries entirely, which is useful for testing or specific environments. When disabled, the base model will execute directly without any retry logic.
397
+
398
+ ```typescript
399
+ const retryableModel = createRetryable({
400
+ model: openai('gpt-4'), // Base model
401
+ retries: [/* ... */],
402
+ disabled: true, // Retries are completely disabled
403
+ });
404
+
405
+ // Or disable based on environment
406
+ const retryableModel = createRetryable({
407
+ model: openai('gpt-4'), // Base model
408
+ retries: [/* ... */],
409
+ disabled: process.env.NODE_ENV === 'test', // Disable in test environment
410
+ });
411
+
412
+ // Or use a function for dynamic control
413
+ const retryableModel = createRetryable({
414
+ model: openai('gpt-4'), // Base model
415
+ retries: [/* ... */],
416
+ disabled: () => !featureFlags.isEnabled('ai-retries'), // Check feature flag
417
+ });
418
+ ```
419
+
375
420
  #### Retry Delays
376
421
 
377
422
  You can delay retries with an optional exponential backoff. The delay respects abort signals, so requests can still be cancelled during the delay period.
@@ -529,11 +574,19 @@ Creates a retryable model that works with both language models and embedding mod
529
574
  interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2> {
530
575
  model: MODEL;
531
576
  retries: Array<Retryable<MODEL> | MODEL>;
577
+ disabled?: boolean | (() => boolean);
532
578
  onError?: (context: RetryContext<MODEL>) => void;
533
579
  onRetry?: (context: RetryContext<MODEL>) => void;
534
580
  }
535
581
  ```
536
582
 
583
+ **Options:**
584
+ - `model`: The base model to use for the initial request.
585
+ - `retries`: Array of retryables (functions, models, or retry objects) to attempt on failure.
586
+ - `disabled`: Disable all retry logic. Can be a boolean or function returning boolean. Default: `false` (retries enabled).
587
+ - `onError`: Callback invoked when an error occurs.
588
+ - `onRetry`: Callback invoked before attempting a retry.
589
+
537
590
  #### `Retryable`
538
591
 
539
592
  A `Retryable` is a function that receives a `RetryContext` with the current error or result and model and all previous attempts.
package/dist/index.js CHANGED
@@ -105,6 +105,13 @@ var RetryableEmbeddingModel = class {
105
105
  this.currentModel = options.model;
106
106
  }
107
107
  /**
108
+ * Check if retries are disabled
109
+ */
110
+ isDisabled() {
111
+ if (this.options.disabled === void 0) return false;
112
+ return typeof this.options.disabled === "function" ? this.options.disabled() : this.options.disabled;
113
+ }
114
+ /**
108
115
  * Execute a function with retry logic for handling errors
109
116
  */
110
117
  async withRetry(input) {
@@ -197,6 +204,10 @@ var RetryableEmbeddingModel = class {
197
204
  * Always start with the original model
198
205
  */
199
206
  this.currentModel = this.baseModel;
207
+ /**
208
+ * If retries are disabled, bypass retry machinery entirely
209
+ */
210
+ if (this.isDisabled()) return this.currentModel.doEmbed(options);
200
211
  const { result } = await this.withRetry({
201
212
  fn: async (currentRetry) => {
202
213
  const callOptions = {
@@ -234,6 +245,13 @@ var RetryableLanguageModel = class {
234
245
  this.currentModel = options.model;
235
246
  }
236
247
  /**
248
+ * Check if retries are disabled
249
+ */
250
+ isDisabled() {
251
+ if (this.options.disabled === void 0) return false;
252
+ return typeof this.options.disabled === "function" ? this.options.disabled() : this.options.disabled;
253
+ }
254
+ /**
237
255
  * Execute a function with retry logic for handling errors
238
256
  */
239
257
  async withRetry(input) {
@@ -371,6 +389,10 @@ var RetryableLanguageModel = class {
371
389
  * Always start with the original model
372
390
  */
373
391
  this.currentModel = this.baseModel;
392
+ /**
393
+ * If retries are disabled, bypass retry machinery entirely
394
+ */
395
+ if (this.isDisabled()) return this.currentModel.doGenerate(options);
374
396
  const { result } = await this.withRetry({
375
397
  fn: async (currentRetry) => {
376
398
  const callOptions = {
@@ -390,6 +412,10 @@ var RetryableLanguageModel = class {
390
412
  */
391
413
  this.currentModel = this.baseModel;
392
414
  /**
415
+ * If retries are disabled, bypass retry machinery entirely
416
+ */
417
+ if (this.isDisabled()) return this.currentModel.doStream(options);
418
+ /**
393
419
  * Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
394
420
  */
395
421
  let { result, attempts } = await this.withRetry({
@@ -39,4 +39,11 @@ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV
39
39
  */
40
40
  declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
41
41
  //#endregion
42
- export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
42
+ //#region src/retryables/service-unavailable.d.ts
43
+ /**
44
+ * Fallback to a different model if the provider returns a service unavailable error.
45
+ * This retryable handles HTTP status code 503 (Service Unavailable).
46
+ */
47
+ declare function serviceUnavailable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
48
+ //#endregion
49
+ export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
@@ -152,4 +152,24 @@ function serviceOverloaded(model, options) {
152
152
  }
153
153
 
154
154
  //#endregion
155
- export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
155
+ //#region src/retryables/service-unavailable.ts
156
+ /**
157
+ * Fallback to a different model if the provider returns a service unavailable error.
158
+ * This retryable handles HTTP status code 503 (Service Unavailable).
159
+ */
160
+ function serviceUnavailable(model, options) {
161
+ return (context) => {
162
+ const { current } = context;
163
+ if (isErrorAttempt(current)) {
164
+ const { error } = current;
165
+ if (APICallError.isInstance(error) && error.statusCode === 503) return {
166
+ model,
167
+ maxAttempts: 1,
168
+ ...options
169
+ };
170
+ }
171
+ };
172
+ }
173
+
174
+ //#endregion
175
+ export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "AI SDK Retry",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",