@ai-sdk/google 4.0.79 → 4.0.82

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.
@@ -923,14 +923,14 @@ The `vertexRagStore` tool accepts the following configuration options:
923
923
 
924
924
  ### Image Outputs
925
925
 
926
- Gemini models with image generation capabilities (e.g. `gemini-2.5-flash-image`) support generating images as part of a multimodal response. Images are exposed as files in the response.
926
+ Gemini models with image generation capabilities (e.g. `gemini-3.1-flash-image-preview`) support generating images as part of a multimodal response. Images are exposed as files in the response.
927
927
 
928
928
  ```ts
929
929
  import { google } from '@ai-sdk/google';
930
930
  import { generateText } from 'ai';
931
931
 
932
932
  const result = await generateText({
933
- model: google('gemini-2.5-flash-image'),
933
+ model: google('gemini-3.1-flash-image-preview'),
934
934
  prompt:
935
935
  'Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme',
936
936
  });
@@ -1088,7 +1088,7 @@ You can pass a `webhookUrl` to receive a notification when the batch reaches a t
1088
1088
  import { google } from '@ai-sdk/google';
1089
1089
  import { experimental_startBatch as startBatch } from 'ai';
1090
1090
 
1091
- const model = 'gemini-3.6-flash';
1091
+ const model = 'gemini-3.8-flash';
1092
1092
 
1093
1093
  const batch = await startBatch({
1094
1094
  provider: google,
@@ -1578,7 +1578,7 @@ import { google, type GoogleInteractionsVideoOptions } from '@ai-sdk/google';
1578
1578
  import { generateText } from 'ai';
1579
1579
 
1580
1580
  const result = await generateText({
1581
- model: google.interactions('gemini-3.7-flash'),
1581
+ model: google.interactions('gemini-3.8-flash'),
1582
1582
  messages: [
1583
1583
  {
1584
1584
  role: 'user',
@@ -1626,7 +1626,7 @@ These parts are informational and do not require a tool response. They are
1626
1626
  available from both `generateText` and `streamText`.
1627
1627
 
1628
1628
  For static processing, use `'static'` or provide clipping and frame-rate
1629
- options:
1629
+ options. `startOffset` and `endOffset` are measured in seconds:
1630
1630
 
1631
1631
  ```ts
1632
1632
  const video = {
@@ -1637,8 +1637,8 @@ const video = {
1637
1637
  google: {
1638
1638
  processing: {
1639
1639
  type: 'static',
1640
- startOffset: 1200,
1641
- endOffset: 1500,
1640
+ startOffset: 0,
1641
+ endOffset: 10.5,
1642
1642
  fps: 0.5,
1643
1643
  },
1644
1644
  } satisfies GoogleInteractionsVideoOptions,
@@ -1778,7 +1778,7 @@ import {
1778
1778
  import { generateText } from 'ai';
1779
1779
 
1780
1780
  const result = await generateText({
1781
- model: google.interactions('gemini-2.5-flash-image'),
1781
+ model: google.interactions('gemini-3.1-flash-image-preview'),
1782
1782
  prompt:
1783
1783
  'Tell me a three sentence bedtime story about a unicorn, accompanied by a suitable illustration.',
1784
1784
  providerOptions: {
@@ -2042,7 +2042,7 @@ You can create models that call the [Google Generative AI embeddings API](https:
2042
2042
  using the `.embedding()` factory method.
2043
2043
 
2044
2044
  ```ts
2045
- const model = google.embedding('gemini-embedding-001');
2045
+ const model = google.embedding('gemini-embedding-2');
2046
2046
  ```
2047
2047
 
2048
2048
  The Google provider sends API calls to the right endpoint based on the type of embedding:
@@ -2078,7 +2078,7 @@ import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google';
2078
2078
  import { embedMany } from 'ai';
2079
2079
 
2080
2080
  const { embeddings } = await embedMany({
2081
- model: google.embedding('gemini-embedding-2-preview'),
2081
+ model: google.embedding('gemini-embedding-2'),
2082
2082
  values: ['sunny day at the beach', 'rainy afternoon in the city'],
2083
2083
  providerOptions: {
2084
2084
  google: {
@@ -2132,14 +2132,14 @@ output capabilities through the `:generateContent` API.
2132
2132
 
2133
2133
  ### Gemini Image Models
2134
2134
 
2135
- [Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-2.5-flash-image`) are technically multimodal output language models, but they can be used with the `generateImage()` function for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`.
2135
+ [Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-3.1-flash-image-preview`) are technically multimodal output language models, but they can be used with the `generateImage()` function for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`.
2136
2136
 
2137
2137
  ```ts
2138
2138
  import { google } from '@ai-sdk/google';
2139
2139
  import { generateImage } from 'ai';
2140
2140
 
2141
2141
  const { image } = await generateImage({
2142
- model: google.image('gemini-2.5-flash-image'),
2142
+ model: google.image('gemini-3.1-flash-image-preview'),
2143
2143
  prompt: 'A photorealistic image of a cat wearing a wizard hat',
2144
2144
  aspectRatio: '1:1',
2145
2145
  });
@@ -2155,7 +2155,7 @@ import fs from 'node:fs';
2155
2155
  const sourceImage = fs.readFileSync('./cat.png');
2156
2156
 
2157
2157
  const { image } = await generateImage({
2158
- model: google.image('gemini-2.5-flash-image'),
2158
+ model: google.image('gemini-3.1-flash-image-preview'),
2159
2159
  prompt: {
2160
2160
  text: 'Add a small wizard hat to this cat',
2161
2161
  images: [sourceImage],
@@ -2170,7 +2170,7 @@ import { google } from '@ai-sdk/google';
2170
2170
  import { generateImage } from 'ai';
2171
2171
 
2172
2172
  const { image } = await generateImage({
2173
- model: google.image('gemini-2.5-flash-image'),
2173
+ model: google.image('gemini-3.1-flash-image-preview'),
2174
2174
  prompt: {
2175
2175
  text: 'Add a small wizard hat to this cat',
2176
2176
  images: ['https://example.com/cat.png'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.79",
3
+ "version": "4.0.82",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@ai-sdk/provider": "4.0.18",
39
- "@ai-sdk/provider-utils": "5.0.47"
39
+ "@ai-sdk/provider-utils": "5.0.49"
40
40
  },
41
41
  "devDependencies": {
42
- "@ai-sdk/test-server": "2.0.1",
42
+ "@ai-sdk/test-server": "2.0.2",
43
43
  "@types/node": "22.19.19",
44
44
  "@vercel/ai-tsconfig": "0.0.0",
45
45
  "tsup": "^8.5.1",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  UnsupportedFunctionalityError,
3
+ type JSONValue,
3
4
  type LanguageModelV4Prompt,
4
5
  type LanguageModelV4ToolResultOutput,
5
6
  type SharedV4Warning,
@@ -8,6 +9,7 @@ import {
8
9
  convertToBase64,
9
10
  getTopLevelMediaType,
10
11
  isFullMediaType,
12
+ isUrlSupported,
11
13
  resolveFullMediaType,
12
14
  resolveProviderReference,
13
15
  secureJsonParse,
@@ -51,9 +53,6 @@ function parseBase64DataUrl(
51
53
  function convertUrlToolResultPart(
52
54
  url: string,
53
55
  ): GoogleFunctionResponsePart | undefined {
54
- // Per https://ai.google.dev/api/caching#FunctionResponsePart, only inline data is supported.
55
- // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/function-calling#functionresponsepart suggests that this
56
- // may be different for Vertex, but this needs to be confirmed and further tested for both APIs.
57
56
  const parsedDataUrl = parseBase64DataUrl(url);
58
57
  if (parsedDataUrl == null) {
59
58
  return undefined;
@@ -67,6 +66,30 @@ function convertUrlToolResultPart(
67
66
  };
68
67
  }
69
68
 
69
+ function containsJSONSchemaReference(value: JSONValue | undefined): boolean {
70
+ if (Array.isArray(value)) {
71
+ return value.some(containsJSONSchemaReference);
72
+ }
73
+
74
+ if (typeof value !== 'object' || value === null) {
75
+ return false;
76
+ }
77
+
78
+ return Object.entries(value).some(
79
+ ([key, nestedValue]) =>
80
+ key === '$ref' || containsJSONSchemaReference(nestedValue),
81
+ );
82
+ }
83
+
84
+ function serializeFunctionResponseContent(
85
+ value: JSONValue,
86
+ ): JSONValue | string {
87
+ // Google reserves { $ref: displayName } in structured function responses for
88
+ // multimodal parts. This conflicts with JSON Schema $ref, so serialize the
89
+ // result to preserve it without triggering Google's reference handling.
90
+ return containsJSONSchemaReference(value) ? JSON.stringify(value) : value;
91
+ }
92
+
70
93
  /*
71
94
  * Appends tool result content parts to the message using the functionResponse
72
95
  * format with support for multimodal parts (e.g. inline images/files alongside
@@ -81,6 +104,7 @@ function appendToolResultParts(
81
104
  >['value'],
82
105
  toolCallId?: string,
83
106
  includeFunctionCallIds = true,
107
+ supportedUrls: Record<string, RegExp[]> = {},
84
108
  ): void {
85
109
  const functionResponseParts: GoogleFunctionResponsePart[] = [];
86
110
  const responseTextParts: string[] = [];
@@ -100,12 +124,30 @@ function appendToolResultParts(
100
124
  },
101
125
  });
102
126
  } else if (contentPart.data.type === 'url') {
103
- const functionResponsePart = convertUrlToolResultPart(
104
- contentPart.data.url.toString(),
105
- );
106
-
107
- if (functionResponsePart != null) {
108
- functionResponseParts.push(functionResponsePart);
127
+ const url = contentPart.data.url.toString();
128
+ const convertedUrlPart = convertUrlToolResultPart(url);
129
+ const supportedUrl =
130
+ contentPart.data.url.protocol === 'gs:' &&
131
+ contentPart.data.originalUrl != null
132
+ ? contentPart.data.originalUrl
133
+ : url;
134
+
135
+ if (convertedUrlPart != null) {
136
+ functionResponseParts.push(convertedUrlPart);
137
+ } else if (
138
+ isFullMediaType(contentPart.mediaType) &&
139
+ isUrlSupported({
140
+ url: supportedUrl,
141
+ mediaType: contentPart.mediaType,
142
+ supportedUrls,
143
+ })
144
+ ) {
145
+ functionResponseParts.push({
146
+ fileData: {
147
+ mimeType: contentPart.mediaType,
148
+ fileUri: supportedUrl,
149
+ },
150
+ });
109
151
  } else {
110
152
  responseTextParts.push(JSON.stringify(contentPart));
111
153
  }
@@ -217,6 +259,7 @@ export function convertToGoogleMessages(
217
259
  providerOptionsNames?: readonly string[];
218
260
  supportsFunctionResponseParts?: boolean;
219
261
  includeFunctionCallIds?: boolean;
262
+ supportedFunctionResponseUrls?: Record<string, RegExp[]>;
220
263
  },
221
264
  ): GooglePrompt {
222
265
  const systemInstructionParts: Array<{ text: string }> = [];
@@ -230,6 +273,8 @@ export function convertToGoogleMessages(
230
273
  const supportsFunctionResponseParts =
231
274
  options?.supportsFunctionResponseParts ?? true;
232
275
  const includeFunctionCallIds = options?.includeFunctionCallIds ?? true;
276
+ const supportedFunctionResponseUrls =
277
+ options?.supportedFunctionResponseUrls ?? {};
233
278
 
234
279
  let sentinelInjected = false;
235
280
  const missingSignatureToolNames: string[] = [];
@@ -632,6 +677,7 @@ export function convertToGoogleMessages(
632
677
  output.value,
633
678
  part.toolCallId,
634
679
  includeFunctionCallIds,
680
+ supportedFunctionResponseUrls,
635
681
  );
636
682
  } else {
637
683
  appendLegacyToolResultParts(
@@ -654,7 +700,7 @@ export function convertToGoogleMessages(
654
700
  content:
655
701
  output.type === 'execution-denied'
656
702
  ? (output.reason ?? 'Tool call execution denied.')
657
- : output.value,
703
+ : serializeFunctionResponseContent(output.value),
658
704
  },
659
705
  },
660
706
  });
@@ -6,20 +6,23 @@ import {
6
6
  detectMediaType,
7
7
  downloadBlob,
8
8
  isFullMediaType,
9
+ isUrlSupported,
9
10
  } from '@ai-sdk/provider-utils';
10
11
 
11
12
  /**
12
- * Vertex function responses only accept inline file data. Download remote tool
13
- * result files before converting the prompt to the Google request format.
13
+ * Download tool result file URLs that Vertex cannot reference directly before
14
+ * converting the prompt to the Google request format.
14
15
  */
15
16
  export async function downloadToolResultFiles(
16
17
  prompt: LanguageModelV4Prompt,
17
18
  {
18
19
  abortSignal,
19
20
  maxBytes,
21
+ supportedUrls = {},
20
22
  }: {
21
23
  abortSignal: AbortSignal | undefined;
22
24
  maxBytes: number;
25
+ supportedUrls?: Record<string, RegExp[]>;
23
26
  },
24
27
  ): Promise<LanguageModelV4Prompt> {
25
28
  const result: LanguageModelV4Prompt = [];
@@ -36,6 +39,7 @@ export async function downloadToolResultFiles(
36
39
  output: await downloadToolResultOutput(part.output, {
37
40
  abortSignal,
38
41
  maxBytes,
42
+ supportedUrls,
39
43
  }),
40
44
  }
41
45
  : part,
@@ -60,6 +64,7 @@ export async function downloadToolResultFiles(
60
64
  output: await downloadToolResultOutput(part.output, {
61
65
  abortSignal,
62
66
  maxBytes,
67
+ supportedUrls,
63
68
  }),
64
69
  });
65
70
  }
@@ -79,9 +84,11 @@ async function downloadToolResultOutput(
79
84
  {
80
85
  abortSignal,
81
86
  maxBytes,
87
+ supportedUrls,
82
88
  }: {
83
89
  abortSignal: AbortSignal | undefined;
84
90
  maxBytes: number;
91
+ supportedUrls: Record<string, RegExp[]>;
85
92
  },
86
93
  ): Promise<LanguageModelV4ToolResultOutput> {
87
94
  if (output.type !== 'content') {
@@ -96,6 +103,17 @@ async function downloadToolResultOutput(
96
103
  continue;
97
104
  }
98
105
 
106
+ if (
107
+ isUrlSupported({
108
+ url: part.data.url.toString(),
109
+ mediaType: part.mediaType,
110
+ supportedUrls,
111
+ })
112
+ ) {
113
+ value.push(part);
114
+ continue;
115
+ }
116
+
99
117
  const blob = await downloadBlob(part.data.url.toString(), {
100
118
  abortSignal,
101
119
  maxBytes,
@@ -66,6 +66,14 @@ const configurableSafetySettingCategories = [
66
66
 
67
67
  const gemini25ModelPattern = /(^|\/)gemini-2\.5(?:[.-]|$)/i;
68
68
 
69
+ const googleCloudStorageFunctionResponseUrls = {
70
+ 'image/png': [/^gs:\/\/.*$/],
71
+ 'image/jpeg': [/^gs:\/\/.*$/],
72
+ 'image/webp': [/^gs:\/\/.*$/],
73
+ 'application/pdf': [/^gs:\/\/.*$/],
74
+ 'text/plain': [/^gs:\/\/.*$/],
75
+ } satisfies Record<string, RegExp[]>;
76
+
69
77
  export type GoogleLanguageModelConfig = {
70
78
  provider: string;
71
79
  baseURL: string;
@@ -83,6 +91,7 @@ export type GoogleLanguageModelConfig = {
83
91
  */
84
92
  downloadToolResultFiles?: {
85
93
  maxBytes: number;
94
+ supportsGoogleCloudStorageUrls?: boolean;
86
95
  };
87
96
  };
88
97
 
@@ -295,11 +304,17 @@ export class GoogleLanguageModel implements LanguageModelV4 {
295
304
  }
296
305
 
297
306
  const { usesGemini3Features } = getGoogleModelCapabilities(modelId);
307
+ const supportedFunctionResponseUrls =
308
+ usesGemini3Features &&
309
+ config.downloadToolResultFiles?.supportsGoogleCloudStorageUrls
310
+ ? googleCloudStorageFunctionResponseUrls
311
+ : undefined;
298
312
 
299
313
  const promptWithDownloadedToolResultFiles = config.downloadToolResultFiles
300
314
  ? await downloadToolResultFiles(prompt, {
301
315
  abortSignal,
302
316
  maxBytes: config.downloadToolResultFiles.maxBytes,
317
+ supportedUrls: supportedFunctionResponseUrls,
303
318
  })
304
319
  : prompt;
305
320
 
@@ -312,6 +327,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
312
327
  providerOptionsNames,
313
328
  supportsFunctionResponseParts: usesGemini3Features,
314
329
  includeFunctionCallIds: !isVertexProvider,
330
+ supportedFunctionResponseUrls,
315
331
  },
316
332
  );
317
333
 
@@ -73,9 +73,13 @@ export type GoogleContentPart =
73
73
  };
74
74
  };
75
75
 
76
- export type GoogleFunctionResponsePart = {
77
- inlineData: { mimeType: string; data: string };
78
- };
76
+ export type GoogleFunctionResponsePart =
77
+ | {
78
+ inlineData: { mimeType: string; data: string };
79
+ }
80
+ | {
81
+ fileData: { mimeType: string; fileUri: string };
82
+ };
79
83
 
80
84
  export type GoogleGroundingMetadata = GroundingMetadataSchema;
81
85
 
@@ -401,10 +401,10 @@ function getVideoProcessingField({
401
401
  processing: {
402
402
  type: 'static',
403
403
  ...(typeof config.startOffset === 'number'
404
- ? { start_offset: config.startOffset }
404
+ ? { start_offset: `${config.startOffset}s` }
405
405
  : {}),
406
406
  ...(typeof config.endOffset === 'number'
407
- ? { end_offset: config.endOffset }
407
+ ? { end_offset: `${config.endOffset}s` }
408
408
  : {}),
409
409
  ...(typeof config.fps === 'number' ? { fps: config.fps } : {}),
410
410
  },
@@ -40,8 +40,8 @@ export type GoogleInteractionsVideoProcessing =
40
40
  | 'static'
41
41
  | {
42
42
  type: 'static';
43
- start_offset?: number;
44
- end_offset?: number;
43
+ start_offset?: string;
44
+ end_offset?: string;
45
45
  fps?: number;
46
46
  };
47
47