ai 6.0.279 → 6.0.281

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.
@@ -26,11 +26,21 @@ import {
26
26
  imageMediaTypeSignatures,
27
27
  } from '../util/detect-media-type';
28
28
  import { prepareRetries } from '../util/prepare-retries';
29
+ import { RetryError } from '../util/retry-error';
29
30
  import { VERSION } from '../version';
30
31
  import type { GenerateImageResult } from './generate-image-result';
31
32
  import { convertDataContentToUint8Array } from '../prompt/data-content';
32
33
  import { splitDataUrl } from '../prompt/split-data-url';
33
34
 
35
+ class RetryableNoImageResultError extends Error {
36
+ constructor() {
37
+ super('No image generated.');
38
+ this.name = 'RetryableNoImageResultError';
39
+ }
40
+ }
41
+
42
+ type ImageModelV3Result = Awaited<ReturnType<ImageModelV3['doGenerate']>>;
43
+
34
44
  export type GenerateImagePrompt =
35
45
  | string
36
46
  | {
@@ -51,7 +61,7 @@ export type GenerateImagePrompt =
51
61
  * @param seed - Seed for the image generation.
52
62
  * @param providerOptions - Additional provider-specific options that are passed through to the provider
53
63
  * as body parameters.
54
- * @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
64
+ * @param maxRetries - Maximum number of retries per image model call, including retries after unclassified empty responses. Empty responses marked as not retryable by the provider are not retried. Set to 0 to disable retries. Default: 2.
55
65
  * @param abortSignal - An optional abort signal that can be used to cancel the call.
56
66
  * @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
57
67
  *
@@ -122,7 +132,9 @@ export async function generateImage({
122
132
  providerOptions?: ProviderOptions;
123
133
 
124
134
  /**
125
- * Maximum number of retries per image model call. Set to 0 to disable retries.
135
+ * Maximum number of retries per image model call, including retries after
136
+ * unclassified empty responses. Empty responses marked as not retryable by
137
+ * the provider are not retried. Set to 0 to disable retries.
126
138
  *
127
139
  * @default 2
128
140
  */
@@ -149,6 +161,8 @@ export async function generateImage({
149
161
  const { retry } = prepareRetries({
150
162
  maxRetries: maxRetriesArg,
151
163
  abortSignal,
164
+ additionalRetryableError: error =>
165
+ error instanceof RetryableNoImageResultError,
152
166
  });
153
167
 
154
168
  // default to 1 if the model has not specified limits on
@@ -167,26 +181,55 @@ export async function generateImage({
167
181
  return remainder === 0 ? maxImagesPerCallWithDefault : remainder;
168
182
  });
169
183
 
170
- const results = await Promise.all(
171
- callImageCounts.map(async callImageCount =>
172
- retry(() => {
173
- const { prompt, files, mask } = normalizePrompt(promptArg);
174
-
175
- return model.doGenerate({
176
- prompt,
177
- files,
178
- mask,
179
- n: callImageCount,
180
- abortSignal,
181
- headers: headersWithUserAgent,
182
- size,
183
- aspectRatio,
184
- seed,
185
- providerOptions: providerOptions ?? {},
184
+ const resultGroups = await Promise.all(
185
+ callImageCounts.map(async callImageCount => {
186
+ const callResults: Array<ImageModelV3Result> = [];
187
+
188
+ try {
189
+ await retry(async () => {
190
+ const { prompt, files, mask } = normalizePrompt(promptArg);
191
+
192
+ const result = await model.doGenerate({
193
+ prompt,
194
+ files,
195
+ mask,
196
+ n: callImageCount,
197
+ abortSignal,
198
+ headers: headersWithUserAgent,
199
+ size,
200
+ aspectRatio,
201
+ seed,
202
+ providerOptions: providerOptions ?? {},
203
+ });
204
+
205
+ callResults.push(result);
206
+
207
+ if (result.images.length === 0 && result.isRetryable !== false) {
208
+ throw new RetryableNoImageResultError();
209
+ }
210
+
211
+ return result;
186
212
  });
187
- }),
188
- ),
213
+
214
+ return callResults;
215
+ } catch (error) {
216
+ const noImageResultError =
217
+ error instanceof RetryableNoImageResultError
218
+ ? error
219
+ : RetryError.isInstance(error) &&
220
+ error.lastError instanceof RetryableNoImageResultError
221
+ ? error.lastError
222
+ : undefined;
223
+
224
+ if (noImageResultError != null) {
225
+ return callResults;
226
+ }
227
+
228
+ throw error;
229
+ }
230
+ }),
189
231
  );
232
+ const results = resultGroups.flat();
190
233
 
191
234
  // collect result images, warnings, and response metadata
192
235
  const images: Array<DefaultGeneratedFile> = [];
@@ -1,3 +1,8 @@
1
+ // atob needs to be invoked as a function call, not as a method call.
2
+ // Otherwise Cloudflare will throw a
3
+ // "TypeError: Illegal invocation: function called with incorrect this reference"
4
+ const { atob } = globalThis;
5
+
1
6
  /**
2
7
  * Converts a data URL of type text/* to a text string.
3
8
  */
@@ -10,7 +15,7 @@ export function getTextFromDataUrl(dataUrl: string): string {
10
15
  }
11
16
 
12
17
  try {
13
- return globalThis.atob(base64Content);
18
+ return atob(base64Content);
14
19
  } catch (error) {
15
20
  throw new Error(`Error decoding data URL`);
16
21
  }
@@ -1,5 +1,8 @@
1
1
  import { InvalidArgumentError } from '../error/invalid-argument-error';
2
- import type { RetryFunction } from '@ai-sdk/provider-utils';
2
+ import type {
3
+ RetryFunction,
4
+ ShouldRetryFunction,
5
+ } from '@ai-sdk/provider-utils';
3
6
  import { retryWithExponentialBackoffRespectingRetryHeaders } from '../util/retry-with-exponential-backoff';
4
7
 
5
8
  /**
@@ -8,9 +11,11 @@ import { retryWithExponentialBackoffRespectingRetryHeaders } from '../util/retry
8
11
  export function prepareRetries({
9
12
  maxRetries,
10
13
  abortSignal,
14
+ additionalRetryableError,
11
15
  }: {
12
16
  maxRetries: number | undefined;
13
17
  abortSignal: AbortSignal | undefined;
18
+ additionalRetryableError?: ShouldRetryFunction;
14
19
  }): {
15
20
  maxRetries: number;
16
21
  retry: RetryFunction;
@@ -40,6 +45,7 @@ export function prepareRetries({
40
45
  retry: retryWithExponentialBackoffRespectingRetryHeaders({
41
46
  maxRetries: maxRetriesResult,
42
47
  abortSignal,
48
+ additionalRetryableError,
43
49
  }),
44
50
  };
45
51
  }
@@ -3,6 +3,7 @@ import { GatewayError } from '@ai-sdk/gateway';
3
3
  import {
4
4
  retryWithExponentialBackoff,
5
5
  type RetryFunction,
6
+ type ShouldRetryFunction,
6
7
  } from '@ai-sdk/provider-utils';
7
8
  import { RetryError } from './retry-error';
8
9
 
@@ -66,21 +67,25 @@ export const retryWithExponentialBackoffRespectingRetryHeaders = ({
66
67
  initialDelayInMs = 2000,
67
68
  backoffFactor = 2,
68
69
  abortSignal,
70
+ additionalRetryableError,
69
71
  }: {
70
72
  maxRetries?: number;
71
73
  initialDelayInMs?: number;
72
74
  backoffFactor?: number;
73
75
  abortSignal?: AbortSignal;
76
+ additionalRetryableError?: ShouldRetryFunction;
74
77
  } = {}): RetryFunction =>
75
78
  retryWithExponentialBackoff({
76
79
  maxRetries,
77
80
  initialDelayInMs,
78
81
  backoffFactor,
79
82
  abortSignal,
80
- shouldRetry: error =>
81
- error instanceof Error &&
82
- ((APICallError.isInstance(error) && error.isRetryable === true) ||
83
- (GatewayError.isInstance(error) && error.isRetryable === true)),
83
+ shouldRetry: async error =>
84
+ (error instanceof Error &&
85
+ ((APICallError.isInstance(error) && error.isRetryable === true) ||
86
+ (GatewayError.isInstance(error) && error.isRetryable === true))) ||
87
+ (additionalRetryableError != null &&
88
+ (await additionalRetryableError(error))),
84
89
  getDelayInMs: ({ error, exponentialBackoffDelay }) =>
85
90
  getRetryDelayInMs({
86
91
  error: error as APICallError | GatewayError,