@ai-sdk/google 3.0.126 → 3.0.128

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.126",
3
+ "version": "3.0.128",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@ai-sdk/provider": "3.0.17",
40
- "@ai-sdk/provider-utils": "4.0.53"
39
+ "@ai-sdk/provider": "3.0.18",
40
+ "@ai-sdk/provider-utils": "4.0.55"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -3,7 +3,11 @@ import {
3
3
  type LanguageModelV3Prompt,
4
4
  type SharedV3Warning,
5
5
  } from '@ai-sdk/provider';
6
- import { convertToBase64, secureJsonParse } from '@ai-sdk/provider-utils';
6
+ import {
7
+ convertToBase64,
8
+ isUrlSupported,
9
+ secureJsonParse,
10
+ } from '@ai-sdk/provider-utils';
7
11
  import type {
8
12
  GoogleGenerativeAIContent,
9
13
  GoogleGenerativeAIContentPart,
@@ -71,21 +75,32 @@ function parseBase64DataUrl(
71
75
 
72
76
  function convertUrlToolResultPart(
73
77
  url: string,
78
+ mediaType: string | undefined,
79
+ supportedFunctionResponseUrls: Record<string, RegExp[]>,
74
80
  ): GoogleGenerativeAIFunctionResponsePart | undefined {
75
- // Per https://ai.google.dev/api/caching#FunctionResponsePart, only inline data is supported.
76
- // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/function-calling#functionresponsepart suggests that this
77
- // may be different for Vertex, but this needs to be confirmed and further tested for both APIs.
78
81
  const parsedDataUrl = parseBase64DataUrl(url);
79
- if (parsedDataUrl == null) {
80
- return undefined;
82
+ if (parsedDataUrl != null) {
83
+ return {
84
+ inlineData: {
85
+ mimeType: parsedDataUrl.mediaType,
86
+ data: parsedDataUrl.data,
87
+ },
88
+ };
81
89
  }
82
90
 
83
- return {
84
- inlineData: {
85
- mimeType: parsedDataUrl.mediaType,
86
- data: parsedDataUrl.data,
87
- },
88
- };
91
+ return mediaType != null &&
92
+ isUrlSupported({
93
+ url,
94
+ mediaType,
95
+ supportedUrls: supportedFunctionResponseUrls,
96
+ })
97
+ ? {
98
+ fileData: {
99
+ mimeType: mediaType,
100
+ fileUri: url,
101
+ },
102
+ }
103
+ : undefined;
89
104
  }
90
105
 
91
106
  /*
@@ -102,6 +117,7 @@ function appendToolResultParts(
102
117
  }>,
103
118
  toolCallId?: string,
104
119
  includeFunctionCallIds = true,
120
+ supportedFunctionResponseUrls: Record<string, RegExp[]> = {},
105
121
  ): void {
106
122
  const functionResponseParts: GoogleGenerativeAIFunctionResponsePart[] = [];
107
123
  const responseTextParts: string[] = [];
@@ -126,6 +142,8 @@ function appendToolResultParts(
126
142
  case 'file-url': {
127
143
  const functionResponsePart = convertUrlToolResultPart(
128
144
  contentPart.url as string,
145
+ contentPart.mediaType as string | undefined,
146
+ supportedFunctionResponseUrls,
129
147
  );
130
148
 
131
149
  if (functionResponsePart != null) {
@@ -241,6 +259,7 @@ export function convertToGoogleGenerativeAIMessages(
241
259
  */
242
260
  onWarning?: (warning: SharedV3Warning) => void;
243
261
  includeFunctionCallIds?: boolean;
262
+ supportedFunctionResponseUrls?: Record<string, RegExp[]>;
244
263
  },
245
264
  ): GoogleGenerativeAIPrompt {
246
265
  const systemInstructionParts: Array<{ text: string }> = [];
@@ -253,6 +272,8 @@ export function convertToGoogleGenerativeAIMessages(
253
272
  options?.supportsFunctionResponseParts ?? true;
254
273
  const onWarning = options?.onWarning;
255
274
  const includeFunctionCallIds = options?.includeFunctionCallIds ?? true;
275
+ const supportedFunctionResponseUrls =
276
+ options?.supportedFunctionResponseUrls ?? {};
256
277
 
257
278
  let sentinelInjected = false;
258
279
  const missingSignatureToolNames: string[] = [];
@@ -523,6 +544,7 @@ export function convertToGoogleGenerativeAIMessages(
523
544
  output.value,
524
545
  part.toolCallId,
525
546
  includeFunctionCallIds,
547
+ supportedFunctionResponseUrls,
526
548
  );
527
549
  } else {
528
550
  appendLegacyToolResultParts(
@@ -0,0 +1,103 @@
1
+ import type {
2
+ LanguageModelV3Prompt,
3
+ LanguageModelV3ToolResultOutput,
4
+ } from '@ai-sdk/provider';
5
+ import {
6
+ convertUint8ArrayToBase64,
7
+ downloadBlob,
8
+ isUrlSupported,
9
+ } from '@ai-sdk/provider-utils';
10
+
11
+ /**
12
+ * Vertex accepts more URLs in user messages than in function responses.
13
+ * Download tool result files that cannot be referenced in function responses.
14
+ */
15
+ export async function downloadToolResultFiles(
16
+ prompt: LanguageModelV3Prompt,
17
+ {
18
+ abortSignal,
19
+ supportedUrls = {},
20
+ }: {
21
+ abortSignal: AbortSignal | undefined;
22
+ supportedUrls?: Record<string, RegExp[]>;
23
+ },
24
+ ): Promise<LanguageModelV3Prompt> {
25
+ async function downloadOutput(
26
+ output: LanguageModelV3ToolResultOutput,
27
+ ): Promise<LanguageModelV3ToolResultOutput> {
28
+ if (output.type !== 'content') {
29
+ return output;
30
+ }
31
+
32
+ return {
33
+ ...output,
34
+ value: await Promise.all(
35
+ output.value.map(async part => {
36
+ if (part.type !== 'file-url' && part.type !== 'image-url') {
37
+ return part;
38
+ }
39
+
40
+ if (
41
+ part.type === 'file-url' &&
42
+ part.mediaType != null &&
43
+ isUrlSupported({
44
+ url: part.url,
45
+ mediaType: part.mediaType,
46
+ supportedUrls,
47
+ })
48
+ ) {
49
+ return part;
50
+ }
51
+
52
+ const blob = await downloadBlob(part.url, { abortSignal });
53
+ return {
54
+ type:
55
+ part.type === 'file-url'
56
+ ? ('file-data' as const)
57
+ : ('image-data' as const),
58
+ data: convertUint8ArrayToBase64(
59
+ new Uint8Array(await blob.arrayBuffer()),
60
+ ),
61
+ mediaType:
62
+ blob.type ||
63
+ (part.type === 'file-url'
64
+ ? (part.mediaType ?? 'application/octet-stream')
65
+ : 'image/*'),
66
+ providerOptions: part.providerOptions,
67
+ };
68
+ }),
69
+ ),
70
+ };
71
+ }
72
+
73
+ return Promise.all(
74
+ prompt.map(async message => {
75
+ switch (message.role) {
76
+ case 'assistant':
77
+ return {
78
+ ...message,
79
+ content: await Promise.all(
80
+ message.content.map(async part =>
81
+ part.type === 'tool-result'
82
+ ? { ...part, output: await downloadOutput(part.output) }
83
+ : part,
84
+ ),
85
+ ),
86
+ };
87
+ case 'tool':
88
+ return {
89
+ ...message,
90
+ content: await Promise.all(
91
+ message.content.map(async part =>
92
+ part.type === 'tool-result'
93
+ ? { ...part, output: await downloadOutput(part.output) }
94
+ : part,
95
+ ),
96
+ ),
97
+ };
98
+ default:
99
+ return message;
100
+ }
101
+ }),
102
+ );
103
+ }
@@ -5,6 +5,8 @@ import {
5
5
  import {
6
6
  combineHeaders,
7
7
  createJsonResponseHandler,
8
+ type EmbeddingModelProviderOptionsTransformer,
9
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
8
10
  lazySchema,
9
11
  parseProviderOptions,
10
12
  postJsonToApi,
@@ -31,6 +33,28 @@ export class GoogleGenerativeAIEmbeddingModel implements EmbeddingModelV3 {
31
33
  readonly modelId: GoogleGenerativeAIEmbeddingModelId;
32
34
  readonly maxEmbeddingsPerCall = 100;
33
35
  readonly supportsParallelCalls = true;
36
+ readonly [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]: EmbeddingModelProviderOptionsTransformer =
37
+ ({ providerOptions, values, startIndex, endIndex }) => {
38
+ const multimodalContent = providerOptions?.google?.content;
39
+
40
+ // Leave schema validation to doEmbed, after middleware transforms options.
41
+ if (!Array.isArray(multimodalContent)) {
42
+ return providerOptions;
43
+ }
44
+
45
+ validateMultimodalContentLength({
46
+ multimodalContent,
47
+ values,
48
+ });
49
+
50
+ return {
51
+ ...providerOptions,
52
+ google: {
53
+ ...providerOptions?.google,
54
+ content: multimodalContent.slice(startIndex, endIndex),
55
+ },
56
+ };
57
+ };
34
58
 
35
59
  private readonly config: GoogleGenerativeAIEmbeddingConfig;
36
60
 
@@ -76,14 +100,7 @@ export class GoogleGenerativeAIEmbeddingModel implements EmbeddingModelV3 {
76
100
 
77
101
  const multimodalContent = googleOptions?.content;
78
102
 
79
- if (
80
- multimodalContent != null &&
81
- multimodalContent.length !== values.length
82
- ) {
83
- throw new Error(
84
- `The number of multimodal content entries (${multimodalContent.length}) must match the number of values (${values.length}).`,
85
- );
86
- }
103
+ validateMultimodalContentLength({ multimodalContent, values });
87
104
 
88
105
  // For single embeddings, use the single endpoint
89
106
  if (values.length === 1) {
@@ -186,3 +203,17 @@ const googleGenerativeAISingleEmbeddingResponseSchema = lazySchema(() =>
186
203
  }),
187
204
  ),
188
205
  );
206
+
207
+ function validateMultimodalContentLength({
208
+ multimodalContent,
209
+ values,
210
+ }: {
211
+ multimodalContent: Array<unknown> | undefined;
212
+ values: Array<string>;
213
+ }) {
214
+ if (multimodalContent != null && multimodalContent.length !== values.length) {
215
+ throw new Error(
216
+ `The number of multimodal content entries (${multimodalContent.length}) must match the number of values (${values.length}).`,
217
+ );
218
+ }
219
+ }
@@ -33,6 +33,7 @@ import {
33
33
  } from './convert-google-generative-ai-usage';
34
34
  import { convertJSONSchemaToOpenAPISchema } from './convert-json-schema-to-openapi-schema';
35
35
  import { convertToGoogleGenerativeAIMessages } from './convert-to-google-generative-ai-messages';
36
+ import { downloadToolResultFiles } from './download-tool-result-files';
36
37
  import { getModelPath } from './get-model-path';
37
38
  import { googleFailedResponseHandler } from './google-error';
38
39
  import {
@@ -60,6 +61,14 @@ const configurableSafetySettingCategories = [
60
61
 
61
62
  const gemini25ModelPattern = /(^|\/)gemini-2\.5(?:[.-]|$)/i;
62
63
 
64
+ const googleCloudStorageFunctionResponseUrls = {
65
+ 'image/png': [/^gs:\/\/.*$/],
66
+ 'image/jpeg': [/^gs:\/\/.*$/],
67
+ 'image/webp': [/^gs:\/\/.*$/],
68
+ 'application/pdf': [/^gs:\/\/.*$/],
69
+ 'text/plain': [/^gs:\/\/.*$/],
70
+ } satisfies Record<string, RegExp[]>;
71
+
63
72
  type GoogleGenerativeAIConfig = {
64
73
  provider: string;
65
74
  baseURL: string;
@@ -113,6 +122,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
113
122
  tools,
114
123
  toolChoice,
115
124
  providerOptions,
125
+ abortSignal,
116
126
  }: LanguageModelV3CallOptions,
117
127
  { isStreaming = false }: { isStreaming?: boolean } = {},
118
128
  ) {
@@ -249,9 +259,19 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
249
259
  }
250
260
 
251
261
  const { usesGemini3Features } = getGoogleModelCapabilities(this.modelId);
262
+ const supportedFunctionResponseUrls =
263
+ usesGemini3Features && isVertexProvider
264
+ ? googleCloudStorageFunctionResponseUrls
265
+ : undefined;
266
+ const promptWithDownloadedToolResultFiles = isVertexProvider
267
+ ? await downloadToolResultFiles(prompt, {
268
+ abortSignal,
269
+ supportedUrls: supportedFunctionResponseUrls,
270
+ })
271
+ : prompt;
252
272
 
253
273
  const { contents, systemInstruction } = convertToGoogleGenerativeAIMessages(
254
- prompt,
274
+ promptWithDownloadedToolResultFiles,
255
275
  {
256
276
  isGemmaModel,
257
277
  isGemini3Model: usesGemini3Features,
@@ -259,6 +279,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
259
279
  supportsFunctionResponseParts: usesGemini3Features,
260
280
  onWarning: warning => warnings.push(warning),
261
281
  includeFunctionCallIds: !isVertexProvider,
282
+ supportedFunctionResponseUrls,
262
283
  },
263
284
  );
264
285
 
@@ -53,9 +53,13 @@ export type GoogleGenerativeAIContentPart =
53
53
  thoughtSignature?: string;
54
54
  };
55
55
 
56
- export type GoogleGenerativeAIFunctionResponsePart = {
57
- inlineData: { mimeType: string; data: string };
58
- };
56
+ export type GoogleGenerativeAIFunctionResponsePart =
57
+ | {
58
+ inlineData: { mimeType: string; data: string };
59
+ }
60
+ | {
61
+ fileData: { mimeType: string; fileUri: string };
62
+ };
59
63
 
60
64
  export type GoogleGenerativeAIGroundingMetadata = GroundingMetadataSchema;
61
65