ai 7.0.84 → 7.0.86

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.
@@ -1,9 +1,40 @@
1
+ import type { ImageModelV4ProviderMetadata } from '@ai-sdk/provider';
1
2
  import type { GeneratedFile } from '../generate-text';
2
3
  import type { ImageModelProviderMetadata } from '../types/image-model';
3
4
  import type { ImageModelResponseMetadata } from '../types/image-model-response-metadata';
4
5
  import type { ImageModelUsage } from '../types/usage';
5
6
  import type { Warning } from '../types/warning';
6
7
 
8
+ /**
9
+ * The result of one underlying image model call.
10
+ */
11
+ export interface GenerateImageCall {
12
+ /**
13
+ * The images generated by this call.
14
+ */
15
+ readonly images: Array<GeneratedFile>;
16
+
17
+ /**
18
+ * Provider-specific metadata for this call.
19
+ */
20
+ readonly providerMetadata?: ImageModelV4ProviderMetadata;
21
+
22
+ /**
23
+ * Response metadata from the provider.
24
+ */
25
+ readonly response: ImageModelResponseMetadata;
26
+
27
+ /**
28
+ * Warnings for this call, e.g. unsupported settings.
29
+ */
30
+ readonly warnings: Array<Warning>;
31
+
32
+ /**
33
+ * Token usage for this call, if reported by the provider.
34
+ */
35
+ readonly usage?: ImageModelUsage;
36
+ }
37
+
7
38
  /**
8
39
  * The result of a `generateImage` call.
9
40
  * It contains the images and additional information.
@@ -19,6 +50,11 @@ export interface GenerateImageResult {
19
50
  */
20
51
  readonly images: Array<GeneratedFile>;
21
52
 
53
+ /**
54
+ * The results of the underlying image model calls.
55
+ */
56
+ readonly calls: Array<GenerateImageCall>;
57
+
22
58
  /**
23
59
  * Warnings for the call, e.g. unsupported settings.
24
60
  */
@@ -26,12 +62,16 @@ export interface GenerateImageResult {
26
62
 
27
63
  /**
28
64
  * Response metadata from the provider. There may be multiple responses if we made multiple calls to the model.
65
+ *
66
+ * @deprecated Use `calls` to preserve each response with its corresponding images, metadata, warnings, and usage.
29
67
  */
30
68
  readonly responses: Array<ImageModelResponseMetadata>;
31
69
 
32
70
  /**
33
71
  * Provider-specific metadata. They are passed through from the provider to the AI SDK and enable provider-specific
34
72
  * results that can be fully encapsulated in the provider.
73
+ *
74
+ * @deprecated Use the provider metadata in `calls` or on individual `images` to preserve its scope.
35
75
  */
36
76
  readonly providerMetadata: ImageModelProviderMetadata;
37
77
 
@@ -1,8 +1,10 @@
1
- import type {
2
- ImageModelV4,
3
- ImageModelV4CallOptions,
4
- ImageModelV4File,
5
- ImageModelV4ProviderMetadata,
1
+ import {
2
+ isJSONObject,
3
+ type ImageModelV4,
4
+ type ImageModelV4CallOptions,
5
+ type ImageModelV4File,
6
+ type ImageModelV4ProviderMetadata,
7
+ type JSONObject,
6
8
  } from '@ai-sdk/provider';
7
9
  import {
8
10
  convertBase64ToUint8Array,
@@ -24,10 +26,27 @@ import { addImageModelUsage, type ImageModelUsage } from '../types/usage';
24
26
  import type { Warning } from '../types/warning';
25
27
  import { prepareRetries } from '../util/prepare-retries';
26
28
  import { VERSION } from '../version';
27
- import type { GenerateImageResult } from './generate-image-result';
29
+ import type {
30
+ GenerateImageCall,
31
+ GenerateImageResult,
32
+ } from './generate-image-result';
28
33
  import { convertDataContentToUint8Array } from '../prompt/data-content';
29
34
  import { splitDataUrl } from '../prompt/split-data-url';
30
35
 
36
+ const gatewayCostMetadataKeys = [
37
+ 'cost',
38
+ 'gatewayCost',
39
+ 'inferenceCost',
40
+ 'inputInferenceCost',
41
+ 'marketCost',
42
+ 'outputInferenceCost',
43
+ 'surchargeCost',
44
+ ] as const;
45
+
46
+ type GatewayCostMetadata = {
47
+ [key in (typeof gatewayCostMetadataKeys)[number]]?: unknown;
48
+ };
49
+
31
50
  export type GenerateImagePrompt =
32
51
  | string
33
52
  | {
@@ -187,7 +206,8 @@ export async function generateImage({
187
206
  );
188
207
 
189
208
  // collect result images, warnings, and response metadata
190
- const images: Array<DefaultGeneratedFile> = [];
209
+ const images: Array<GeneratedFile> = [];
210
+ const calls: Array<GenerateImageCall> = [];
191
211
  const warnings: Array<Warning> = [];
192
212
  const responses: Array<ImageModelResponseMetadata> = [];
193
213
  const providerMetadata: ImageModelV4ProviderMetadata = {};
@@ -197,19 +217,29 @@ export async function generateImage({
197
217
  totalTokens: undefined,
198
218
  };
199
219
  for (const result of results) {
200
- images.push(
201
- ...result.images.map(
202
- image =>
203
- new DefaultGeneratedFile({
204
- data: image,
205
- mediaType:
206
- detectMediaType({
207
- data: image,
208
- topLevelType: 'image',
209
- }) ?? 'image/png',
210
- }),
211
- ),
220
+ const callImages = result.images.map(
221
+ (image, index) =>
222
+ new DefaultGeneratedFile({
223
+ data: image,
224
+ mediaType:
225
+ detectMediaType({
226
+ data: image,
227
+ topLevelType: 'image',
228
+ }) ?? 'image/png',
229
+ providerMetadata: getImageProviderMetadata(
230
+ result.providerMetadata,
231
+ index,
232
+ ),
233
+ }),
212
234
  );
235
+ images.push(...callImages);
236
+ calls.push({
237
+ images: callImages,
238
+ providerMetadata: result.providerMetadata,
239
+ response: result.response,
240
+ warnings: result.warnings,
241
+ usage: result.usage,
242
+ });
213
243
  warnings.push(...result.warnings);
214
244
 
215
245
  if (result.usage != null) {
@@ -217,19 +247,33 @@ export async function generateImage({
217
247
  }
218
248
 
219
249
  if (result.providerMetadata) {
220
- for (const [providerName, metadata] of Object.entries<{
221
- images: unknown;
222
- }>(result.providerMetadata)) {
250
+ for (const [providerName, metadata] of Object.entries(
251
+ result.providerMetadata,
252
+ )) {
223
253
  if (providerName === 'gateway') {
224
254
  const currentEntry = providerMetadata[providerName];
225
255
  if (currentEntry != null && typeof currentEntry === 'object') {
256
+ const currentGatewayMetadata = currentEntry as GatewayCostMetadata;
257
+ const newGatewayMetadata = metadata as GatewayCostMetadata;
258
+
226
259
  providerMetadata[providerName] = {
227
260
  ...(currentEntry as object),
228
- ...metadata,
261
+ ...(metadata as object),
262
+ ...Object.fromEntries(
263
+ gatewayCostMetadataKeys.flatMap(key => {
264
+ const total = addDecimalStrings(
265
+ currentGatewayMetadata[key],
266
+ newGatewayMetadata[key],
267
+ );
268
+
269
+ return total == null ? [] : [[key, total]];
270
+ }),
271
+ ),
229
272
  } as ImageModelV4ProviderMetadata[string];
230
273
  } else {
231
- providerMetadata[providerName] =
232
- metadata as ImageModelV4ProviderMetadata[string];
274
+ providerMetadata[providerName] = {
275
+ ...(metadata as object),
276
+ } as ImageModelV4ProviderMetadata[string];
233
277
  }
234
278
  const imagesValue = (
235
279
  providerMetadata[providerName] as { images?: unknown }
@@ -240,9 +284,7 @@ export async function generateImage({
240
284
  }
241
285
  } else {
242
286
  providerMetadata[providerName] ??= { images: [] };
243
- providerMetadata[providerName].images.push(
244
- ...result.providerMetadata[providerName].images,
245
- );
287
+ providerMetadata[providerName].images.push(...metadata.images);
246
288
  }
247
289
  }
248
290
  }
@@ -258,6 +300,7 @@ export async function generateImage({
258
300
 
259
301
  return new DefaultGenerateImageResult({
260
302
  images,
303
+ calls,
261
304
  warnings,
262
305
  responses,
263
306
  providerMetadata,
@@ -267,6 +310,7 @@ export async function generateImage({
267
310
 
268
311
  class DefaultGenerateImageResult implements GenerateImageResult {
269
312
  readonly images: Array<GeneratedFile>;
313
+ readonly calls: Array<GenerateImageCall>;
270
314
  readonly warnings: Array<Warning>;
271
315
  readonly responses: Array<ImageModelResponseMetadata>;
272
316
  readonly providerMetadata: ImageModelV4ProviderMetadata;
@@ -274,12 +318,14 @@ class DefaultGenerateImageResult implements GenerateImageResult {
274
318
 
275
319
  constructor(options: {
276
320
  images: Array<GeneratedFile>;
321
+ calls: Array<GenerateImageCall>;
277
322
  warnings: Array<Warning>;
278
323
  responses: Array<ImageModelResponseMetadata>;
279
324
  providerMetadata: ImageModelV4ProviderMetadata;
280
325
  usage: ImageModelUsage;
281
326
  }) {
282
327
  this.images = options.images;
328
+ this.calls = options.calls;
283
329
  this.warnings = options.warnings;
284
330
  this.responses = options.responses;
285
331
  this.providerMetadata = options.providerMetadata;
@@ -291,6 +337,30 @@ class DefaultGenerateImageResult implements GenerateImageResult {
291
337
  }
292
338
  }
293
339
 
340
+ /**
341
+ * Extracts per-image metadata from the legacy `providerMetadata.<provider>.images` result shape.
342
+ */
343
+ function getImageProviderMetadata(
344
+ providerMetadata: ImageModelV4ProviderMetadata | undefined,
345
+ imageIndex: number,
346
+ ): Record<string, JSONObject> | undefined {
347
+ if (providerMetadata == null) {
348
+ return undefined;
349
+ }
350
+
351
+ let imageMetadata: Record<string, JSONObject> | undefined;
352
+
353
+ for (const [providerName, metadata] of Object.entries(providerMetadata)) {
354
+ const value = metadata.images?.[imageIndex];
355
+
356
+ if (isJSONObject(value) && !Array.isArray(value)) {
357
+ (imageMetadata ??= {})[providerName] = value;
358
+ }
359
+ }
360
+
361
+ return imageMetadata;
362
+ }
363
+
294
364
  async function invokeModelMaxImagesPerCall(model: ImageModelV4) {
295
365
  const isFunction = model.maxImagesPerCall instanceof Function;
296
366
 
@@ -303,6 +373,34 @@ async function invokeModelMaxImagesPerCall(model: ImageModelV4) {
303
373
  });
304
374
  }
305
375
 
376
+ function addDecimalStrings(
377
+ value1: unknown,
378
+ value2: unknown,
379
+ ): string | undefined {
380
+ if (
381
+ typeof value1 !== 'string' ||
382
+ typeof value2 !== 'string' ||
383
+ !/^\d+(?:\.\d+)?$/.test(value1) ||
384
+ !/^\d+(?:\.\d+)?$/.test(value2)
385
+ ) {
386
+ return undefined;
387
+ }
388
+
389
+ const [integer1, fraction1 = ''] = value1.split('.');
390
+ const [integer2, fraction2 = ''] = value2.split('.');
391
+ const precision = Math.max(fraction1.length, fraction2.length);
392
+ const sum =
393
+ BigInt(integer1 + fraction1.padEnd(precision, '0')) +
394
+ BigInt(integer2 + fraction2.padEnd(precision, '0'));
395
+ const sumString = sum.toString().padStart(precision + 1, '0');
396
+
397
+ return precision === 0
398
+ ? sumString
399
+ : `${sumString.slice(0, -precision)}.${sumString.slice(
400
+ -precision,
401
+ )}`.replace(/\.?0+$/, '');
402
+ }
403
+
306
404
  function normalizePrompt(
307
405
  prompt: GenerateImagePrompt,
308
406
  ): Pick<ImageModelV4CallOptions, 'prompt' | 'files' | 'mask'> {
@@ -1,2 +1,5 @@
1
1
  export { generateImage } from './generate-image';
2
- export type { GenerateImageResult } from './generate-image-result';
2
+ export type {
3
+ GenerateImageCall,
4
+ GenerateImageResult,
5
+ } from './generate-image-result';
@@ -0,0 +1,206 @@
1
+ import type { LanguageModelV4Content } from '@ai-sdk/provider';
2
+ import type { ToolSet } from '@ai-sdk/provider-utils';
3
+ import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error';
4
+ import { getOwn } from '../util/get-own';
5
+ import type { ContentPart } from './content-part';
6
+ import { DefaultGeneratedFile } from './generated-file';
7
+ import type { ToolApprovalRequestOutput } from './tool-approval-request-output';
8
+ import type { ToolApprovalResponseOutput } from './tool-approval-response-output';
9
+ import type { TypedToolCall } from './tool-call';
10
+ import type { TypedToolError } from './tool-error';
11
+ import type { ToolOutput } from './tool-output';
12
+ import type { TypedToolResult } from './tool-result';
13
+
14
+ export function convertLanguageModelContent<TOOLS extends ToolSet>({
15
+ content,
16
+ toolCalls,
17
+ toolOutputs,
18
+ toolApprovalRequests,
19
+ toolApprovalResponses,
20
+ tools,
21
+ }: {
22
+ content: Array<LanguageModelV4Content>;
23
+ toolCalls: Array<TypedToolCall<TOOLS>>;
24
+ toolOutputs: Array<ToolOutput<TOOLS>>;
25
+ toolApprovalRequests: Array<ToolApprovalRequestOutput<TOOLS>>;
26
+ toolApprovalResponses: Array<ToolApprovalResponseOutput<TOOLS>>;
27
+ tools: TOOLS | undefined;
28
+ }): Array<ContentPart<TOOLS>> {
29
+ const contentParts: Array<ContentPart<TOOLS>> = [];
30
+ const toolOutputsWithApprovalResponses: Array<ToolOutput<TOOLS>> = [];
31
+ const toolOutputsWithoutApprovalResponses: Array<ToolOutput<TOOLS>> = [];
32
+ const toolCallIdsWithApprovalResponses = new Set(
33
+ toolApprovalResponses.map(
34
+ toolApprovalResponse => toolApprovalResponse.toolCall.toolCallId,
35
+ ),
36
+ );
37
+
38
+ for (const part of content) {
39
+ switch (part.type) {
40
+ case 'text':
41
+ case 'reasoning':
42
+ case 'custom':
43
+ case 'source':
44
+ contentParts.push(part);
45
+ break;
46
+
47
+ case 'file':
48
+ case 'reasoning-file': {
49
+ contentParts.push({
50
+ type: part.type as 'file' | 'reasoning-file',
51
+ file: new DefaultGeneratedFile({
52
+ data:
53
+ part.data.type === 'data'
54
+ ? part.data.data
55
+ : part.data.url.toString(),
56
+ mediaType: part.mediaType,
57
+ }),
58
+ ...(part.providerMetadata != null
59
+ ? { providerMetadata: part.providerMetadata }
60
+ : {}),
61
+ });
62
+ break;
63
+ }
64
+
65
+ case 'tool-call': {
66
+ const toolCall = toolCalls.find(
67
+ toolCall => toolCall.toolCallId === part.toolCallId,
68
+ );
69
+
70
+ if (toolCall == null) {
71
+ throw new Error(`Tool call ${part.toolCallId} not found.`);
72
+ }
73
+
74
+ contentParts.push(toolCall);
75
+ break;
76
+ }
77
+
78
+ case 'tool-result': {
79
+ const toolCall = toolCalls.find(
80
+ toolCall => toolCall.toolCallId === part.toolCallId,
81
+ );
82
+
83
+ // Handle deferred results for provider-executed tools (e.g., programmatic tool calling).
84
+ // When a server tool (like code_execution) triggers a client tool, the server tool's
85
+ // result may be deferred to a later turn. In this case, there's no matching tool-call
86
+ // in the current response.
87
+ if (toolCall == null) {
88
+ const tool = getOwn(tools, part.toolName);
89
+ const supportsDeferredResults =
90
+ tool?.type === 'provider' && tool.supportsDeferredResults;
91
+
92
+ if (!supportsDeferredResults) {
93
+ throw new Error(`Tool call ${part.toolCallId} not found.`);
94
+ }
95
+
96
+ // Create tool result without tool call input (deferred result)
97
+ if (part.isError) {
98
+ contentParts.push({
99
+ type: 'tool-error' as const,
100
+ toolCallId: part.toolCallId,
101
+ toolName: part.toolName as keyof TOOLS & string,
102
+ input: undefined,
103
+ error: part.result,
104
+ providerExecuted: true,
105
+ dynamic: part.dynamic,
106
+ ...(part.providerMetadata != null
107
+ ? { providerMetadata: part.providerMetadata }
108
+ : {}),
109
+ ...(tool?.metadata != null
110
+ ? { toolMetadata: tool.metadata }
111
+ : {}),
112
+ } as TypedToolError<TOOLS>);
113
+ } else {
114
+ contentParts.push({
115
+ type: 'tool-result' as const,
116
+ toolCallId: part.toolCallId,
117
+ toolName: part.toolName as keyof TOOLS & string,
118
+ input: undefined,
119
+ output: part.result,
120
+ providerExecuted: true,
121
+ dynamic: part.dynamic,
122
+ ...(part.providerMetadata != null
123
+ ? { providerMetadata: part.providerMetadata }
124
+ : {}),
125
+ ...(tool?.metadata != null
126
+ ? { toolMetadata: tool.metadata }
127
+ : {}),
128
+ } as TypedToolResult<TOOLS>);
129
+ }
130
+ break;
131
+ }
132
+
133
+ if (part.isError) {
134
+ contentParts.push({
135
+ type: 'tool-error' as const,
136
+ toolCallId: part.toolCallId,
137
+ toolName: part.toolName as keyof TOOLS & string,
138
+ input: toolCall.input,
139
+ error: part.result,
140
+ providerExecuted: true,
141
+ dynamic: toolCall.dynamic,
142
+ ...(part.providerMetadata != null
143
+ ? { providerMetadata: part.providerMetadata }
144
+ : {}),
145
+ ...(toolCall.toolMetadata != null
146
+ ? { toolMetadata: toolCall.toolMetadata }
147
+ : {}),
148
+ } as TypedToolError<TOOLS>);
149
+ } else {
150
+ contentParts.push({
151
+ type: 'tool-result' as const,
152
+ toolCallId: part.toolCallId,
153
+ toolName: part.toolName as keyof TOOLS & string,
154
+ input: toolCall.input,
155
+ output: part.result,
156
+ providerExecuted: true,
157
+ dynamic: toolCall.dynamic,
158
+ ...(part.providerMetadata != null
159
+ ? { providerMetadata: part.providerMetadata }
160
+ : {}),
161
+ ...(toolCall.toolMetadata != null
162
+ ? { toolMetadata: toolCall.toolMetadata }
163
+ : {}),
164
+ } as TypedToolResult<TOOLS>);
165
+ }
166
+ break;
167
+ }
168
+
169
+ case 'tool-approval-request': {
170
+ const toolCall = toolCalls.find(
171
+ toolCall => toolCall.toolCallId === part.toolCallId,
172
+ );
173
+
174
+ if (toolCall == null) {
175
+ throw new ToolCallNotFoundForApprovalError({
176
+ toolCallId: part.toolCallId,
177
+ approvalId: part.approvalId,
178
+ });
179
+ }
180
+
181
+ contentParts.push({
182
+ type: 'tool-approval-request' as const,
183
+ approvalId: part.approvalId,
184
+ toolCall,
185
+ });
186
+ break;
187
+ }
188
+ }
189
+ }
190
+
191
+ for (const toolOutput of toolOutputs) {
192
+ if (toolCallIdsWithApprovalResponses.has(toolOutput.toolCallId)) {
193
+ toolOutputsWithApprovalResponses.push(toolOutput);
194
+ } else {
195
+ toolOutputsWithoutApprovalResponses.push(toolOutput);
196
+ }
197
+ }
198
+
199
+ return [
200
+ ...contentParts,
201
+ ...toolOutputsWithoutApprovalResponses,
202
+ ...toolApprovalRequests,
203
+ ...toolApprovalResponses,
204
+ ...toolOutputsWithApprovalResponses,
205
+ ];
206
+ }