@ai-sdk/google 3.0.139 → 3.0.141

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.
@@ -447,12 +447,12 @@ Things to keep in mind:
447
447
 
448
448
  #### Long decode with the Interactions API
449
449
 
450
- With the [Interactions API](#interactions-api), long decode requires
451
- `background: true`. The server runs every request itself, so no client-side
452
- continuation is needed, and `usage` covers all requests. Set an explicit
453
- `maxOutputTokens` above 65,536 (without it, you get a single 64K request) and
454
- keep `store` enabled (the default). The provider polls the interaction (or
455
- streams it, reconnecting when the connection drops) until it finishes.
450
+ With the [Interactions API](#interactions-api), the AI SDK currently requires
451
+ `background: true` for long decode. The server runs every request itself, so no
452
+ client-side continuation is needed, and `usage` covers all requests. Set an
453
+ explicit `maxOutputTokens` above 65,536 and keep `store` enabled (the default).
454
+ The provider polls the interaction (or streams it, reconnecting when the
455
+ connection drops) until it finishes.
456
456
 
457
457
  ```ts
458
458
  import { google } from '@ai-sdk/google';
@@ -471,10 +471,14 @@ const result = await generateText({
471
471
  });
472
472
  ```
473
473
 
474
- Without `background: true`, an interaction generates at most 65,536 tokens.
475
- Requests with a higher `maxOutputTokens` are rejected by the API, and an
476
- interaction that reaches the cap finishes with `length`. Long decode is not
477
- available with the Vertex AI Interactions API.
474
+ Foreground client-side continuation is not yet supported by the AI SDK.
475
+ If you do not set `maxOutputTokens`, non-streaming calls (`generateText`)
476
+ default to 32,768 tokens and streaming calls (`streamText`) to 65,536 tokens.
477
+ An interaction that reaches its limit finishes with `length`. Long decode is
478
+ not available with the Vertex AI Interactions API.
479
+
480
+ The token budget includes both thinking and output tokens. To retain the previous
481
+ 64K budget for a non-streaming call, set `maxOutputTokens: 65_536` explicitly.
478
482
 
479
483
  ### File Inputs
480
484
 
@@ -505,7 +509,14 @@ const result = await generateText({
505
509
  });
506
510
  ```
507
511
 
508
- You can also use YouTube URLs directly:
512
+ You can also use YouTube URLs directly. Pass `videoMetadata` through
513
+ `providerOptions.google` on a video file part to clip a segment or control the
514
+ frame rate. This also works with inline video data and uploaded file URLs.
515
+ The metadata supports these optional fields:
516
+
517
+ - `startOffset` (string): Start time as a duration in seconds, e.g. `'60s'` or `'3.5s'`.
518
+ - `endOffset` (string): End time in the same duration format.
519
+ - `fps` (number): Frames per second in the range `(0, 24]`. The default is `1`.
509
520
 
510
521
  ```ts
511
522
  import { google } from '@ai-sdk/google';
@@ -519,12 +530,21 @@ const result = await generateText({
519
530
  content: [
520
531
  {
521
532
  type: 'text',
522
- text: 'Summarize this video',
533
+ text: 'Summarize this part of the video',
523
534
  },
524
535
  {
525
536
  type: 'file',
526
537
  data: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
527
538
  mediaType: 'video/mp4',
539
+ providerOptions: {
540
+ google: {
541
+ videoMetadata: {
542
+ startOffset: '60s',
543
+ endOffset: '600s',
544
+ fps: 1,
545
+ },
546
+ },
547
+ },
528
548
  },
529
549
  ],
530
550
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.139",
3
+ "version": "3.0.141",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@ai-sdk/provider": "3.0.19",
40
- "@ai-sdk/provider-utils": "4.0.62"
40
+ "@ai-sdk/provider-utils": "4.0.64"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -1,18 +1,21 @@
1
1
  import {
2
+ type JSONValue,
2
3
  UnsupportedFunctionalityError,
3
4
  type LanguageModelV3Prompt,
4
5
  type SharedV3Warning,
5
6
  } from '@ai-sdk/provider';
6
7
  import {
8
+ stringifyWithBigIntAndCycles,
7
9
  convertToBase64,
8
10
  isUrlSupported,
9
11
  secureJsonParse,
10
12
  } from '@ai-sdk/provider-utils';
11
- import type {
12
- GoogleGenerativeAIContent,
13
- GoogleGenerativeAIContentPart,
14
- GoogleGenerativeAIFunctionResponsePart,
15
- GoogleGenerativeAIPrompt,
13
+ import {
14
+ googleVideoMetadataSchema,
15
+ type GoogleGenerativeAIContent,
16
+ type GoogleGenerativeAIContentPart,
17
+ type GoogleGenerativeAIFunctionResponsePart,
18
+ type GoogleGenerativeAIPrompt,
16
19
  } from './google-generative-ai-prompt';
17
20
 
18
21
  /**
@@ -34,6 +37,7 @@ type GoogleProviderOptions = {
34
37
  thoughtSignature?: unknown;
35
38
  serverToolCallId?: unknown;
36
39
  serverToolType?: unknown;
40
+ videoMetadata?: unknown;
37
41
  };
38
42
 
39
43
  function getGoogleProviderOptions(
@@ -103,6 +107,42 @@ function convertUrlToolResultPart(
103
107
  : undefined;
104
108
  }
105
109
 
110
+ function serializeFunctionResponseContent(
111
+ value: JSONValue,
112
+ ): JSONValue | string {
113
+ // Keep Google's structured response for ordinary JSON, but normalize values
114
+ // that would otherwise fail when the final request body is serialized.
115
+ return containsNonJsonValue(value)
116
+ ? secureJsonParse(stringifyWithBigIntAndCycles(value))
117
+ : value;
118
+ }
119
+
120
+ function containsNonJsonValue(
121
+ value: unknown,
122
+ ancestors = new Set<object>(),
123
+ ): boolean {
124
+ if (typeof value === 'bigint') {
125
+ return true;
126
+ }
127
+ if (typeof value !== 'object' || value === null) {
128
+ return false;
129
+ }
130
+ if (typeof (value as { toJSON?: unknown }).toJSON === 'function') {
131
+ return true;
132
+ }
133
+ if (ancestors.has(value)) {
134
+ return true;
135
+ }
136
+ ancestors.add(value);
137
+ try {
138
+ return Object.values(value).some(item =>
139
+ containsNonJsonValue(item, ancestors),
140
+ );
141
+ } finally {
142
+ ancestors.delete(value);
143
+ }
144
+ }
145
+
106
146
  /*
107
147
  * Appends tool result content parts to the message using the functionResponse
108
148
  * format with support for multimodal parts (e.g. inline images/files alongside
@@ -149,12 +189,12 @@ function appendToolResultParts(
149
189
  if (functionResponsePart != null) {
150
190
  functionResponseParts.push(functionResponsePart);
151
191
  } else {
152
- responseTextParts.push(JSON.stringify(contentPart));
192
+ responseTextParts.push(stringifyWithBigIntAndCycles(contentPart));
153
193
  }
154
194
  break;
155
195
  }
156
196
  default: {
157
- responseTextParts.push(JSON.stringify(contentPart));
197
+ responseTextParts.push(stringifyWithBigIntAndCycles(contentPart));
158
198
  break;
159
199
  }
160
200
  }
@@ -232,7 +272,7 @@ function appendLegacyToolResultParts(
232
272
  break;
233
273
  }
234
274
  default:
235
- parts.push({ text: JSON.stringify(contentPart) });
275
+ parts.push({ text: stringifyWithBigIntAndCycles(contentPart) });
236
276
  break;
237
277
  }
238
278
  }
@@ -313,6 +353,14 @@ export function convertToGoogleGenerativeAIMessages(
313
353
  // default to image/jpeg for unknown image/* types
314
354
  const mediaType =
315
355
  part.mediaType === 'image/*' ? 'image/jpeg' : part.mediaType;
356
+ const rawVideoMetadata = getGoogleProviderOptions(
357
+ part.providerOptions,
358
+ providerOptionsName,
359
+ )?.videoMetadata;
360
+ const videoMetadata =
361
+ rawVideoMetadata == null
362
+ ? undefined
363
+ : googleVideoMetadataSchema.parse(rawVideoMetadata);
316
364
 
317
365
  parts.push(
318
366
  part.data instanceof URL
@@ -325,12 +373,14 @@ export function convertToGoogleGenerativeAIMessages(
325
373
  ? part.originalUrl
326
374
  : part.data.toString(),
327
375
  },
376
+ ...(videoMetadata != null ? { videoMetadata } : {}),
328
377
  }
329
378
  : {
330
379
  inlineData: {
331
380
  mimeType: mediaType,
332
381
  data: convertToBase64(part.data),
333
382
  },
383
+ ...(videoMetadata != null ? { videoMetadata } : {}),
334
384
  },
335
385
  );
336
386
 
@@ -450,7 +500,7 @@ export function convertToGoogleGenerativeAIMessages(
450
500
  ? { id: part.toolCallId }
451
501
  : {}),
452
502
  name: part.toolName,
453
- args: part.input,
503
+ args: parseToolCallInput(part.input),
454
504
  },
455
505
  thoughtSignature: effectiveThoughtSignature,
456
506
  };
@@ -567,7 +617,7 @@ export function convertToGoogleGenerativeAIMessages(
567
617
  content:
568
618
  output.type === 'execution-denied'
569
619
  ? (output.reason ?? 'Tool call execution denied.')
570
- : output.value,
620
+ : serializeFunctionResponseContent(output.value),
571
621
  },
572
622
  },
573
623
  });
@@ -621,3 +671,14 @@ export function convertToGoogleGenerativeAIMessages(
621
671
  contents,
622
672
  };
623
673
  }
674
+
675
+ function parseToolCallInput(input: unknown): unknown {
676
+ if (typeof input !== 'string') {
677
+ return input;
678
+ }
679
+ try {
680
+ return secureJsonParse(input);
681
+ } catch {
682
+ return input;
683
+ }
684
+ }
@@ -6,15 +6,18 @@ import {
6
6
  } from '@ai-sdk/provider-utils';
7
7
  import { z } from 'zod/v4';
8
8
 
9
+ export const getGoogleErrorSchema = () =>
10
+ z.object({
11
+ code: z.number().nullable(),
12
+ message: z.string(),
13
+ status: z.string(),
14
+ details: z.array(z.unknown()).nullish(),
15
+ });
16
+
9
17
  const googleErrorDataSchema = lazySchema(() =>
10
18
  zodSchema(
11
19
  z.object({
12
- error: z.object({
13
- code: z.number().nullable(),
14
- message: z.string(),
15
- status: z.string(),
16
- details: z.array(z.unknown()).nullish(),
17
- }),
20
+ error: getGoogleErrorSchema(),
18
21
  }),
19
22
  ),
20
23
  );
@@ -25,3 +28,40 @@ export const googleFailedResponseHandler = createJsonErrorResponseHandler({
25
28
  errorSchema: googleErrorDataSchema,
26
29
  errorToMessage: data => data.error.message,
27
30
  });
31
+
32
+ // Google RPC status names can be sent without a numeric HTTP error code.
33
+ export function mapGoogleErrorStatusToHttpStatusCode(
34
+ status: string,
35
+ ): number | undefined {
36
+ switch (status) {
37
+ case 'CANCELLED':
38
+ return 499;
39
+ case 'INVALID_ARGUMENT':
40
+ case 'FAILED_PRECONDITION':
41
+ case 'OUT_OF_RANGE':
42
+ return 400;
43
+ case 'DEADLINE_EXCEEDED':
44
+ return 504;
45
+ case 'NOT_FOUND':
46
+ return 404;
47
+ case 'ALREADY_EXISTS':
48
+ case 'ABORTED':
49
+ return 409;
50
+ case 'PERMISSION_DENIED':
51
+ return 403;
52
+ case 'UNAUTHENTICATED':
53
+ return 401;
54
+ case 'RESOURCE_EXHAUSTED':
55
+ return 429;
56
+ case 'UNIMPLEMENTED':
57
+ return 501;
58
+ case 'UNKNOWN':
59
+ case 'INTERNAL':
60
+ case 'DATA_LOSS':
61
+ return 500;
62
+ case 'UNAVAILABLE':
63
+ return 503;
64
+ default:
65
+ return undefined;
66
+ }
67
+ }
@@ -1,15 +1,16 @@
1
- import type {
2
- LanguageModelV3,
3
- LanguageModelV3CallOptions,
4
- LanguageModelV3Content,
5
- LanguageModelV3FinishReason,
6
- LanguageModelV3GenerateResult,
7
- LanguageModelV3Source,
8
- LanguageModelV3StreamPart,
9
- LanguageModelV3StreamResult,
10
- JSONObject,
11
- SharedV3ProviderMetadata,
12
- SharedV3Warning,
1
+ import {
2
+ APICallError,
3
+ type LanguageModelV3,
4
+ type LanguageModelV3CallOptions,
5
+ type LanguageModelV3Content,
6
+ type LanguageModelV3FinishReason,
7
+ type LanguageModelV3GenerateResult,
8
+ type LanguageModelV3Source,
9
+ type LanguageModelV3StreamPart,
10
+ type LanguageModelV3StreamResult,
11
+ type JSONObject,
12
+ type SharedV3ProviderMetadata,
13
+ type SharedV3Warning,
13
14
  } from '@ai-sdk/provider';
14
15
  import {
15
16
  combineHeaders,
@@ -37,7 +38,11 @@ import { convertJSONSchemaToOpenAPISchema } from './convert-json-schema-to-opena
37
38
  import { convertToGoogleGenerativeAIMessages } from './convert-to-google-generative-ai-messages';
38
39
  import { downloadToolResultFiles } from './download-tool-result-files';
39
40
  import { getModelPath } from './get-model-path';
40
- import { googleFailedResponseHandler } from './google-error';
41
+ import {
42
+ getGoogleErrorSchema,
43
+ googleFailedResponseHandler,
44
+ mapGoogleErrorStatusToHttpStatusCode,
45
+ } from './google-error';
41
46
  import {
42
47
  googleLanguageModelOptions,
43
48
  type GoogleGenerativeAIModelId,
@@ -684,11 +689,12 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
684
689
  extraHeaders,
685
690
  );
686
691
 
692
+ const url = `${this.config.baseURL}/${getModelPath(
693
+ this.modelId,
694
+ )}:streamGenerateContent?alt=sse`;
687
695
  const streamGenerateContent = (body: typeof args) =>
688
696
  postJsonToApi({
689
- url: `${this.config.baseURL}/${getModelPath(
690
- this.modelId,
691
- )}:streamGenerateContent?alt=sse`,
697
+ url,
692
698
  headers,
693
699
  body,
694
700
  failedResponseHandler: googleFailedResponseHandler,
@@ -829,6 +835,26 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
829
835
 
830
836
  const value = chunk.value;
831
837
 
838
+ if (value.error != null) {
839
+ finishReason = { unified: 'error', raw: undefined };
840
+ const error = value.error;
841
+ controller.enqueue({
842
+ type: 'error',
843
+ error: new APICallError({
844
+ message: error.message,
845
+ url,
846
+ requestBodyValues: args,
847
+ statusCode:
848
+ error.code ??
849
+ mapGoogleErrorStatusToHttpStatusCode(error.status),
850
+ responseHeaders,
851
+ responseBody: JSON.stringify({ error }),
852
+ data: error,
853
+ }),
854
+ });
855
+ return;
856
+ }
857
+
832
858
  if (!hasEmittedResponseMetadata && value.responseId != null) {
833
859
  hasEmittedResponseMetadata = true;
834
860
  controller.enqueue({
@@ -1768,6 +1794,7 @@ export type UsageMetadataSchema = NonNullable<
1768
1794
  const chunkSchema = lazySchema(() =>
1769
1795
  zodSchema(
1770
1796
  z.object({
1797
+ error: getGoogleErrorSchema().nullish(),
1771
1798
  responseId: z.string().nullish(),
1772
1799
  candidates: z
1773
1800
  .array(
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod/v4';
1
2
  import {
2
3
  type GroundingMetadataSchema,
3
4
  type PromptFeedbackSchema,
@@ -20,9 +21,24 @@ export type GoogleGenerativeAIContent = {
20
21
  parts: Array<GoogleGenerativeAIContentPart>;
21
22
  };
22
23
 
24
+ export const googleVideoMetadataSchema = z.object({
25
+ /** Start offset as a duration in seconds, e.g. `'3.5s'`. */
26
+ startOffset: z.string().optional(),
27
+ /** End offset as a duration in seconds, e.g. `'10s'`. */
28
+ endOffset: z.string().optional(),
29
+ /** Frames per second in the range (0, 24]. Defaults to 1. */
30
+ fps: z.number().gt(0).max(24).optional(),
31
+ });
32
+
33
+ /** Metadata for preprocessing video file parts. */
34
+ export type GoogleVideoMetadata = z.infer<typeof googleVideoMetadataSchema>;
35
+
23
36
  export type GoogleGenerativeAIContentPart =
24
37
  | { text: string; thought?: boolean; thoughtSignature?: string }
25
- | { inlineData: { mimeType: string; data: string } }
38
+ | {
39
+ inlineData: { mimeType: string; data: string };
40
+ videoMetadata?: GoogleVideoMetadata;
41
+ }
26
42
  | {
27
43
  functionCall: { id?: string; name: string; args: unknown };
28
44
  thoughtSignature?: string;
@@ -35,7 +51,10 @@ export type GoogleGenerativeAIContentPart =
35
51
  parts?: Array<GoogleGenerativeAIFunctionResponsePart>;
36
52
  };
37
53
  }
38
- | { fileData: { mimeType: string; fileUri: string } }
54
+ | {
55
+ fileData: { mimeType: string; fileUri: string };
56
+ videoMetadata?: GoogleVideoMetadata;
57
+ }
39
58
  | {
40
59
  toolCall: {
41
60
  toolType: string;
package/src/index.ts CHANGED
@@ -4,7 +4,10 @@ export type {
4
4
  /** @deprecated Use `GoogleLanguageModelOptions` instead. */
5
5
  GoogleLanguageModelOptions as GoogleGenerativeAIProviderOptions,
6
6
  } from './google-generative-ai-options';
7
- export type { GoogleGenerativeAIProviderMetadata } from './google-generative-ai-prompt';
7
+ export type {
8
+ GoogleGenerativeAIProviderMetadata,
9
+ GoogleVideoMetadata,
10
+ } from './google-generative-ai-prompt';
8
11
  export type {
9
12
  GoogleImageModelOptions,
10
13
  /** @deprecated Use `GoogleImageModelOptions` instead. */
@@ -39,7 +39,7 @@ export type PrepareGoogleInteractionsToolsResult = {
39
39
  * `toolChoice` shapes:
40
40
  * - `'auto'` -> `'auto'`
41
41
  * - `'required'` -> `'any'`
42
- * - `'none'` -> `'none'`
42
+ * - `'none'` -> omit all tools and `tool_choice`
43
43
  * - `{ type: 'tool', toolName }` -> `{ allowed_tools: { mode: 'validated', tools: [name] } }`
44
44
  * (Interactions `AllowedTools.tools` is an `Array<string>` of function
45
45
  * names, not tool descriptors -- see `src/interactions/resources/interactions.ts`
@@ -54,6 +54,12 @@ export function prepareGoogleInteractionsTools({
54
54
  }): PrepareGoogleInteractionsToolsResult {
55
55
  const toolWarnings: Array<SharedV3Warning> = [];
56
56
 
57
+ // The API's tool_choice only controls function tools. Omit all tools to
58
+ // disable provider-executed tools as well.
59
+ if (toolChoice?.type === 'none') {
60
+ return { tools: undefined, toolChoice: undefined, toolWarnings };
61
+ }
62
+
57
63
  const normalized = tools?.length ? tools : undefined;
58
64
 
59
65
  if (normalized == null) {
@@ -223,9 +229,6 @@ export function prepareGoogleInteractionsTools({
223
229
  case 'required':
224
230
  mappedToolChoice = 'any';
225
231
  break;
226
- case 'none':
227
- mappedToolChoice = 'none';
228
- break;
229
232
  case 'tool':
230
233
  mappedToolChoice = {
231
234
  allowed_tools: {