@ai-sdk/google 4.0.109 → 4.0.111

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.
@@ -486,12 +486,12 @@ Things to keep in mind:
486
486
 
487
487
  #### Long decode with the Interactions API
488
488
 
489
- With the [Interactions API](#interactions-api), long decode requires
490
- `background: true`. The server runs every request itself, so no client-side
491
- continuation is needed, and `usage` covers all requests. Set an explicit
492
- `maxOutputTokens` above 65,536 (without it, you get a single 64K request) and
493
- keep `store` enabled (the default). The provider polls the interaction (or
494
- streams it, reconnecting when the connection drops) until it finishes.
489
+ With the [Interactions API](#interactions-api), the AI SDK currently requires
490
+ `background: true` for long decode. The server runs every request itself, so no
491
+ client-side continuation is needed, and `usage` covers all requests. Set an
492
+ explicit `maxOutputTokens` above 65,536 and keep `store` enabled (the default).
493
+ The provider polls the interaction (or streams it, reconnecting when the
494
+ connection drops) until it finishes.
495
495
 
496
496
  ```ts
497
497
  import { google } from '@ai-sdk/google';
@@ -510,10 +510,14 @@ const result = await generateText({
510
510
  });
511
511
  ```
512
512
 
513
- Without `background: true`, an interaction generates at most 65,536 tokens.
514
- Requests with a higher `maxOutputTokens` are rejected by the API, and an
515
- interaction that reaches the cap finishes with `length`. Long decode is not
516
- available with the Vertex AI Interactions API.
513
+ Foreground client-side continuation is not yet supported by the AI SDK.
514
+ If you do not set `maxOutputTokens`, non-streaming calls (`generateText`)
515
+ default to 32,768 tokens and streaming calls (`streamText`) to 65,536 tokens.
516
+ An interaction that reaches its limit finishes with `length`. Long decode is
517
+ not available with the Vertex AI Interactions API.
518
+
519
+ The token budget includes both thinking and output tokens. To retain the previous
520
+ 64K budget for a non-streaming call, set `maxOutputTokens: 65_536` explicitly.
517
521
 
518
522
  ### File Inputs
519
523
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.109",
3
+ "version": "4.0.111",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@ai-sdk/provider": "4.0.35",
39
- "@ai-sdk/provider-utils": "5.0.72"
39
+ "@ai-sdk/provider-utils": "5.0.74"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -6,6 +6,7 @@ import {
6
6
  type SharedV4Warning,
7
7
  } from '@ai-sdk/provider';
8
8
  import {
9
+ stringifyWithBigIntAndCycles,
9
10
  convertToBase64,
10
11
  getTopLevelMediaType,
11
12
  isFullMediaType,
@@ -67,18 +68,26 @@ function convertUrlToolResultPart(
67
68
  };
68
69
  }
69
70
 
70
- function containsJSONSchemaReference(value: JSONValue | undefined): boolean {
71
- if (Array.isArray(value)) {
72
- return value.some(containsJSONSchemaReference);
71
+ function containsJSONSchemaReference(
72
+ value: JSONValue | undefined,
73
+ seen: WeakSet<object> = new WeakSet(),
74
+ ): boolean {
75
+ if (value === null || typeof value !== 'object') {
76
+ return false;
73
77
  }
74
78
 
75
- if (typeof value !== 'object' || value === null) {
79
+ if (seen.has(value)) {
76
80
  return false;
77
81
  }
82
+ seen.add(value);
83
+
84
+ if (Array.isArray(value)) {
85
+ return value.some(item => containsJSONSchemaReference(item, seen));
86
+ }
78
87
 
79
88
  return Object.entries(value).some(
80
89
  ([key, nestedValue]) =>
81
- key === '$ref' || containsJSONSchemaReference(nestedValue),
90
+ key === '$ref' || containsJSONSchemaReference(nestedValue, seen),
82
91
  );
83
92
  }
84
93
 
@@ -88,7 +97,41 @@ function serializeFunctionResponseContent(
88
97
  // Google reserves { $ref: displayName } in structured function responses for
89
98
  // multimodal parts. This conflicts with JSON Schema $ref, so serialize the
90
99
  // result to preserve it without triggering Google's reference handling.
91
- return containsJSONSchemaReference(value) ? JSON.stringify(value) : value;
100
+ if (containsJSONSchemaReference(value)) {
101
+ return stringifyWithBigIntAndCycles(value);
102
+ }
103
+
104
+ // Keep Google's structured response for ordinary JSON, but normalize values
105
+ // that would otherwise fail when the final request body is serialized.
106
+ return containsNonJsonValue(value)
107
+ ? secureJsonParse(stringifyWithBigIntAndCycles(value))
108
+ : value;
109
+ }
110
+
111
+ function containsNonJsonValue(
112
+ value: unknown,
113
+ ancestors = new Set<object>(),
114
+ ): boolean {
115
+ if (typeof value === 'bigint') {
116
+ return true;
117
+ }
118
+ if (typeof value !== 'object' || value === null) {
119
+ return false;
120
+ }
121
+ if (typeof (value as { toJSON?: unknown }).toJSON === 'function') {
122
+ return true;
123
+ }
124
+ if (ancestors.has(value)) {
125
+ return true;
126
+ }
127
+ ancestors.add(value);
128
+ try {
129
+ return Object.values(value).some(item =>
130
+ containsNonJsonValue(item, ancestors),
131
+ );
132
+ } finally {
133
+ ancestors.delete(value);
134
+ }
92
135
  }
93
136
 
94
137
  /*
@@ -586,7 +629,7 @@ export function convertToGoogleMessages(
586
629
  ? { id: part.toolCallId }
587
630
  : {}),
588
631
  name: part.toolName,
589
- args: part.input,
632
+ args: parseToolCallInput(part.input),
590
633
  },
591
634
  thoughtSignature: effectiveThoughtSignature,
592
635
  };
@@ -775,3 +818,14 @@ export function convertToGoogleMessages(
775
818
  contents,
776
819
  };
777
820
  }
821
+
822
+ function parseToolCallInput(input: unknown): unknown {
823
+ if (typeof input !== 'string') {
824
+ return input;
825
+ }
826
+ try {
827
+ return secureJsonParse(input);
828
+ } catch {
829
+ return input;
830
+ }
831
+ }
@@ -28,3 +28,40 @@ export const googleFailedResponseHandler = createJsonErrorResponseHandler({
28
28
  errorSchema: googleErrorDataSchema,
29
29
  errorToMessage: data => data.error.message,
30
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
+ }
@@ -51,6 +51,7 @@ import { getModelPath } from './get-model-path';
51
51
  import {
52
52
  getGoogleErrorSchema,
53
53
  googleFailedResponseHandler,
54
+ mapGoogleErrorStatusToHttpStatusCode,
54
55
  } from './google-error';
55
56
  import { sanitizeResponseJsonSchema } from './sanitize-response-json-schema';
56
57
  import {
@@ -1040,19 +1041,22 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1040
1041
  if (value.error != null) {
1041
1042
  finishReason = { unified: 'error', raw: undefined };
1042
1043
  const error = value.error;
1044
+ const statusCode =
1045
+ error.code ??
1046
+ mapGoogleErrorStatusToHttpStatusCode(error.status);
1043
1047
  controller.enqueue({
1044
1048
  type: 'error',
1045
1049
  error: createProviderStreamError({
1046
1050
  message: error.message,
1047
1051
  type: error.status,
1048
1052
  code: error.code ?? undefined,
1049
- statusCode: error.code ?? undefined,
1053
+ statusCode,
1050
1054
  isRetryable:
1051
- error.code != null &&
1052
- (error.code === 408 ||
1053
- error.code === 409 ||
1054
- error.code === 429 ||
1055
- error.code >= 500),
1055
+ statusCode != null &&
1056
+ (statusCode === 408 ||
1057
+ statusCode === 409 ||
1058
+ statusCode === 429 ||
1059
+ statusCode >= 500),
1056
1060
  data: error,
1057
1061
  }),
1058
1062
  });
@@ -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<SharedV4Warning> = [];
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: {