@ai-sdk/google 4.0.0-canary.63 → 4.0.0-canary.65

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.
@@ -1154,21 +1154,28 @@ The following optional provider options are available:
1154
1154
  Whether the model returns synthesized thought summaries on reasoning
1155
1155
  parts. Defaults to the API default.
1156
1156
 
1157
- - **imageConfig** _\{ aspectRatio?: string; imageSize?: '1K' | '2K' | '4K' | '512' \}_
1157
+ - **responseFormat** _Array\<\{ type: 'text' | 'image' | 'audio'; mimeType?: string; schema?: unknown; aspectRatio?: string; imageSize?: '1K' \| '2K' \| '4K' \| '512' \}\>_
1158
1158
 
1159
- Image generation configuration when `responseModalities` includes
1160
- `'image'`. `aspectRatio` accepts `1:1`, `2:3`, `3:2`, `3:4`, `4:3`,
1161
- `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:8`, `8:1`, `1:4`, `4:1`.
1159
+ Output-format entries that map directly to the API's `response_format`
1160
+ array. Use this for fine-grained control over image, audio, or non-JSON
1161
+ text outputs (e.g. `aspectRatio` and `imageSize` for image generation).
1162
+ The AI SDK call-level `responseFormat: { type: 'json', schema }` still
1163
+ drives JSON-mode automatically and prepends a matching text entry;
1164
+ entries listed here are appended.
1162
1165
 
1163
- - **mediaResolution** _'low' | 'medium' | 'high' | 'ultra_high'_
1166
+ `aspectRatio` accepts `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`,
1167
+ `9:16`, `16:9`, `21:9`, `1:8`, `8:1`, `1:4`, `4:1`.
1164
1168
 
1165
- Media resolution applied to image inputs / outputs.
1169
+ - **imageConfig** _\{ aspectRatio?: string; imageSize?: '1K' | '2K' | '4K' | '512' \}_ (deprecated)
1166
1170
 
1167
- - **responseModalities** _Array\<'text' | 'image' | 'audio' | 'video' | 'document'\>_
1171
+ Use **responseFormat** with a `{ type: 'image', ... }` entry instead.
1172
+ Retained for backwards compatibility; the SDK translates `imageConfig`
1173
+ into a matching `response_format` image entry and emits a warning when
1174
+ set. Ignored when `responseFormat` already supplies an image entry.
1168
1175
 
1169
- The modalities the model may emit. Defaults to text-only. Pass
1170
- `['image']` (or `['text', 'image']`) to enable native image output. See
1171
- [Image output](#image-output-via-interactions).
1176
+ - **mediaResolution** _'low' | 'medium' | 'high' | 'ultra_high'_
1177
+
1178
+ Media resolution applied to image inputs / outputs.
1172
1179
 
1173
1180
  - **serviceTier** _'flex' | 'standard' | 'priority'_
1174
1181
 
@@ -1321,9 +1328,10 @@ const { text, toolCalls } = await generateText({
1321
1328
 
1322
1329
  ### Image output via Interactions
1323
1330
 
1324
- Set `responseModalities: ['image']` on a Gemini image-capable model to get
1325
- images as `LanguageModelV4FilePart` files in the response. No tool wrapping
1326
- is required.
1331
+ Add a `{ type: 'image' }` entry to `responseFormat` on a Gemini
1332
+ image-capable model to get images as `LanguageModelV4FilePart` files in
1333
+ the response. No tool wrapping is required, and the entry doubles as the
1334
+ place to set `aspectRatio`, `imageSize`, and `mimeType`.
1327
1335
 
1328
1336
  ```ts
1329
1337
  import { google } from '@ai-sdk/google';
@@ -1334,7 +1342,7 @@ const result = await generateText({
1334
1342
  prompt: 'Generate an image of a comic cat in a spaceship.',
1335
1343
  providerOptions: {
1336
1344
  google: {
1337
- responseModalities: ['image'],
1345
+ responseFormat: [{ type: 'image' }],
1338
1346
  },
1339
1347
  },
1340
1348
  });
@@ -1346,6 +1354,54 @@ for (const file of result.files) {
1346
1354
  }
1347
1355
  ```
1348
1356
 
1357
+ To control aspect ratio, image size, or output mime type, add those
1358
+ fields to the same image entry:
1359
+
1360
+ ```ts
1361
+ const result = await generateText({
1362
+ model: google.interactions('gemini-3-pro-image-preview'),
1363
+ prompt: 'Generate a high-quality landscape photo of mountains at sunset.',
1364
+ providerOptions: {
1365
+ google: {
1366
+ responseFormat: [
1367
+ {
1368
+ type: 'image',
1369
+ aspectRatio: '16:9',
1370
+ imageSize: '4K',
1371
+ },
1372
+ ],
1373
+ },
1374
+ },
1375
+ });
1376
+ ```
1377
+
1378
+ For multimodal output, list one entry per modality. The model returns
1379
+ text in `result.text` and the accompanying image(s) in `result.files`:
1380
+
1381
+ ```ts
1382
+ import { google } from '@ai-sdk/google';
1383
+ import { generateText } from 'ai';
1384
+
1385
+ const result = await generateText({
1386
+ model: google.interactions('gemini-2.5-flash-image'),
1387
+ prompt:
1388
+ 'Tell me a three sentence bedtime story about a unicorn, accompanied by a suitable illustration.',
1389
+ providerOptions: {
1390
+ google: {
1391
+ responseFormat: [
1392
+ { type: 'text' },
1393
+ { type: 'image', aspectRatio: '16:9' },
1394
+ ],
1395
+ },
1396
+ },
1397
+ });
1398
+
1399
+ console.log(result.text);
1400
+
1401
+ const images = result.files.filter(file => file.mediaType.startsWith('image/'));
1402
+ // images[0].uint8Array | images[0].base64 | images[0].mediaType
1403
+ ```
1404
+
1349
1405
  Iterative image editing pairs naturally with stateful chaining — keep
1350
1406
  `previousInteractionId` set across turns and the model edits its prior
1351
1407
  output:
@@ -1359,7 +1415,7 @@ const model = google.interactions('gemini-3-pro-image-preview');
1359
1415
  const turn1 = await generateText({
1360
1416
  model,
1361
1417
  prompt: 'Generate an image of a comic cat in a spaceship.',
1362
- providerOptions: { google: { responseModalities: ['image'] } },
1418
+ providerOptions: { google: { responseFormat: [{ type: 'image' }] } },
1363
1419
  });
1364
1420
 
1365
1421
  const interactionId = turn1.providerMetadata?.google?.interactionId as
@@ -1371,7 +1427,7 @@ const turn2 = await generateText({
1371
1427
  prompt: 'now make the cat red',
1372
1428
  providerOptions: {
1373
1429
  google: {
1374
- responseModalities: ['image'],
1430
+ responseFormat: [{ type: 'image' }],
1375
1431
  previousInteractionId: interactionId,
1376
1432
  },
1377
1433
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.0-canary.63",
3
+ "version": "4.0.0-canary.65",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -64,6 +64,7 @@ function appendToolResultParts(
64
64
  LanguageModelV4ToolResultOutput,
65
65
  { type: 'content' }
66
66
  >['value'],
67
+ toolCallId?: string,
67
68
  ): void {
68
69
  const functionResponseParts: GoogleFunctionResponsePart[] = [];
69
70
  const responseTextParts: string[] = [];
@@ -106,6 +107,7 @@ function appendToolResultParts(
106
107
 
107
108
  parts.push({
108
109
  functionResponse: {
110
+ ...(toolCallId != null ? { id: toolCallId } : {}),
109
111
  name: toolName,
110
112
  response: {
111
113
  name: toolName,
@@ -133,12 +135,14 @@ function appendLegacyToolResultParts(
133
135
  LanguageModelV4ToolResultOutput,
134
136
  { type: 'content' }
135
137
  >['value'],
138
+ toolCallId?: string,
136
139
  ): void {
137
140
  for (const contentPart of outputValue) {
138
141
  switch (contentPart.type) {
139
142
  case 'text':
140
143
  parts.push({
141
144
  functionResponse: {
145
+ ...(toolCallId != null ? { id: toolCallId } : {}),
142
146
  name: toolName,
143
147
  response: {
144
148
  name: toolName,
@@ -447,6 +451,9 @@ export function convertToGoogleMessages(
447
451
 
448
452
  return {
449
453
  functionCall: {
454
+ ...(part.toolCallId != null
455
+ ? { id: part.toolCallId }
456
+ : {}),
450
457
  name: part.toolName,
451
458
  args: part.input,
452
459
  },
@@ -533,13 +540,24 @@ export function convertToGoogleMessages(
533
540
 
534
541
  if (output.type === 'content') {
535
542
  if (supportsFunctionResponseParts) {
536
- appendToolResultParts(parts, part.toolName, output.value);
543
+ appendToolResultParts(
544
+ parts,
545
+ part.toolName,
546
+ output.value,
547
+ part.toolCallId,
548
+ );
537
549
  } else {
538
- appendLegacyToolResultParts(parts, part.toolName, output.value);
550
+ appendLegacyToolResultParts(
551
+ parts,
552
+ part.toolName,
553
+ output.value,
554
+ part.toolCallId,
555
+ );
539
556
  }
540
557
  } else {
541
558
  parts.push({
542
559
  functionResponse: {
560
+ ...(part.toolCallId != null ? { id: part.toolCallId } : {}),
543
561
  name: part.toolName,
544
562
  response: {
545
563
  name: part.toolName,
@@ -387,7 +387,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
387
387
  } else if ('functionCall' in part && part.functionCall.name != null) {
388
388
  content.push({
389
389
  type: 'tool-call' as const,
390
- toolCallId: this.config.generateId(),
390
+ toolCallId: part.functionCall.id ?? this.config.generateId(),
391
391
  toolName: part.functionCall.name,
392
392
  input: JSON.stringify(part.functionCall.args ?? {}),
393
393
  providerMetadata: part.thoughtSignature
@@ -838,7 +838,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
838
838
  part.functionCall.name != null &&
839
839
  part.functionCall.willContinue === true
840
840
  ) {
841
- const toolCallId = generateId();
841
+ const toolCallId = part.functionCall.id ?? generateId();
842
842
  const accumulator = new GoogleJSONAccumulator();
843
843
  activeStreamingToolCalls.push({
844
844
  toolCallId,
@@ -920,7 +920,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
920
920
 
921
921
  hasToolCalls = true;
922
922
  } else if (isCompleteCall) {
923
- const toolCallId = generateId();
923
+ const toolCallId = part.functionCall.id ?? generateId();
924
924
  const toolName = part.functionCall.name!;
925
925
  const args =
926
926
  typeof part.functionCall.args === 'string'
@@ -957,7 +957,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
957
957
 
958
958
  hasToolCalls = true;
959
959
  } else if (isNoArgsCompleteCall) {
960
- const toolCallId = generateId();
960
+ const toolCallId = part.functionCall.id ?? generateId();
961
961
  const toolName = part.functionCall.name!;
962
962
 
963
963
  controller.enqueue({
@@ -1333,6 +1333,7 @@ const getContentSchema = () =>
1333
1333
  // note: order matters since text can be fully empty
1334
1334
  z.object({
1335
1335
  functionCall: z.object({
1336
+ id: z.string().nullish(),
1336
1337
  name: z.string().nullish(),
1337
1338
  args: z.unknown().nullish(),
1338
1339
  partialArgs: z.array(partialArgSchema).nullish(),
@@ -27,9 +27,13 @@ export type GoogleContentPart =
27
27
  thought?: boolean;
28
28
  thoughtSignature?: string;
29
29
  }
30
- | { functionCall: { name: string; args: unknown }; thoughtSignature?: string }
30
+ | {
31
+ functionCall: { id?: string; name: string; args: unknown };
32
+ thoughtSignature?: string;
33
+ }
31
34
  | {
32
35
  functionResponse: {
36
+ id?: string;
33
37
  name: string;
34
38
  response: unknown;
35
39
  parts?: Array<GoogleFunctionResponsePart>;