@ai-sdk/google 3.0.140 → 3.0.142

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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.140",
3
+ "version": "3.0.142",
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.63"
40
+ "@ai-sdk/provider-utils": "4.0.64"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -1,9 +1,11 @@
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,
@@ -105,6 +107,42 @@ function convertUrlToolResultPart(
105
107
  : undefined;
106
108
  }
107
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
+
108
146
  /*
109
147
  * Appends tool result content parts to the message using the functionResponse
110
148
  * format with support for multimodal parts (e.g. inline images/files alongside
@@ -151,12 +189,12 @@ function appendToolResultParts(
151
189
  if (functionResponsePart != null) {
152
190
  functionResponseParts.push(functionResponsePart);
153
191
  } else {
154
- responseTextParts.push(JSON.stringify(contentPart));
192
+ responseTextParts.push(stringifyWithBigIntAndCycles(contentPart));
155
193
  }
156
194
  break;
157
195
  }
158
196
  default: {
159
- responseTextParts.push(JSON.stringify(contentPart));
197
+ responseTextParts.push(stringifyWithBigIntAndCycles(contentPart));
160
198
  break;
161
199
  }
162
200
  }
@@ -234,7 +272,7 @@ function appendLegacyToolResultParts(
234
272
  break;
235
273
  }
236
274
  default:
237
- parts.push({ text: JSON.stringify(contentPart) });
275
+ parts.push({ text: stringifyWithBigIntAndCycles(contentPart) });
238
276
  break;
239
277
  }
240
278
  }
@@ -462,7 +500,7 @@ export function convertToGoogleGenerativeAIMessages(
462
500
  ? { id: part.toolCallId }
463
501
  : {}),
464
502
  name: part.toolName,
465
- args: part.input,
503
+ args: parseToolCallInput(part.input),
466
504
  },
467
505
  thoughtSignature: effectiveThoughtSignature,
468
506
  };
@@ -579,7 +617,7 @@ export function convertToGoogleGenerativeAIMessages(
579
617
  content:
580
618
  output.type === 'execution-denied'
581
619
  ? (output.reason ?? 'Tool call execution denied.')
582
- : output.value,
620
+ : serializeFunctionResponseContent(output.value),
583
621
  },
584
622
  },
585
623
  });
@@ -633,3 +671,14 @@ export function convertToGoogleGenerativeAIMessages(
633
671
  contents,
634
672
  };
635
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(
@@ -53,6 +53,13 @@ export function prepareTools({
53
53
  };
54
54
  toolWarnings: SharedV3Warning[];
55
55
  } {
56
+ // NONE only disables function calling in the Google API. Provider tools
57
+ // must be omitted so they cannot execute server-side. Keep function
58
+ // declarations for conversation history containing function calls.
59
+ if (toolChoice?.type === 'none') {
60
+ tools = tools?.filter(tool => tool.type === 'function');
61
+ }
62
+
56
63
  // when the tools array is empty, change it to undefined to prevent errors:
57
64
  tools = tools?.length ? tools : undefined;
58
65
 
@@ -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: {