@ai-sdk/google 4.0.46 → 4.0.48

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.
@@ -1467,7 +1467,7 @@ Function tools work the same way as on the standard provider:
1467
1467
 
1468
1468
  ```ts
1469
1469
  import { google } from '@ai-sdk/google';
1470
- import { generateText, stepCountIs, tool } from 'ai';
1470
+ import { generateText, isStepCount, tool } from 'ai';
1471
1471
  import { z } from 'zod';
1472
1472
 
1473
1473
  const weatherTool = tool({
@@ -1479,7 +1479,7 @@ const weatherTool = tool({
1479
1479
  const { text, toolCalls } = await generateText({
1480
1480
  model: google.interactions('gemini-3.7-flash'),
1481
1481
  tools: { getWeather: weatherTool },
1482
- stopWhen: stepCountIs(5),
1482
+ stopWhen: isStepCount(5),
1483
1483
  prompt: 'What is the weather in San Francisco right now?',
1484
1484
  });
1485
1485
  ```
@@ -1804,7 +1804,7 @@ import { generateText } from 'ai';
1804
1804
 
1805
1805
  const { text } = await generateText({
1806
1806
  model: google('gemma-3-27b-it'),
1807
- system: 'You are a helpful assistant that responds concisely.',
1807
+ instructions: 'You are a helpful assistant that responds concisely.',
1808
1808
  prompt: 'What is machine learning?',
1809
1809
  });
1810
1810
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.46",
3
+ "version": "4.0.48",
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.7",
39
- "@ai-sdk/provider-utils": "5.0.27"
39
+ "@ai-sdk/provider-utils": "5.0.28"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "22.19.19",
@@ -4,6 +4,16 @@ import {
4
4
  type JSONSchema7Definition,
5
5
  } from '@ai-sdk/provider';
6
6
 
7
+ type JSONSchema7WithDefinitions = JSONSchema7 & {
8
+ $defs?: Record<string, JSONSchema7Definition>;
9
+ };
10
+
11
+ type ReferenceContext = {
12
+ definitions: Record<string, JSONSchema7Definition> | undefined;
13
+ dollarDefinitions: Record<string, JSONSchema7Definition> | undefined;
14
+ resolvingReferences: ReadonlySet<string>;
15
+ };
16
+
7
17
  /**
8
18
  * Converts JSON Schema 7 to OpenAPI Schema 3.0
9
19
  */
@@ -11,26 +21,52 @@ export function convertJSONSchemaToOpenAPISchema(
11
21
  jsonSchema: JSONSchema7Definition | undefined,
12
22
  isRoot = true,
13
23
  ): unknown {
14
- // Handle empty object schemas: undefined at root, preserved when nested
24
+ const rootSchema =
25
+ typeof jsonSchema === 'object'
26
+ ? (jsonSchema as JSONSchema7WithDefinitions)
27
+ : undefined;
28
+
29
+ return convertJSONSchemaDefinition(jsonSchema, isRoot, {
30
+ definitions: rootSchema?.definitions,
31
+ dollarDefinitions: rootSchema?.$defs,
32
+ resolvingReferences: new Set(),
33
+ });
34
+ }
35
+
36
+ function convertJSONSchemaDefinition(
37
+ jsonSchema: JSONSchema7Definition | undefined,
38
+ isRoot: boolean,
39
+ referenceContext: ReferenceContext,
40
+ ): unknown {
15
41
  if (jsonSchema == null) {
16
42
  return undefined;
17
43
  }
18
44
 
45
+ if (typeof jsonSchema === 'boolean') {
46
+ return { type: 'boolean', properties: {} };
47
+ }
48
+
49
+ if (jsonSchema.$ref != null) {
50
+ return convertJSONSchemaReference({
51
+ jsonSchema,
52
+ reference: jsonSchema.$ref,
53
+ isRoot,
54
+ referenceContext,
55
+ });
56
+ }
57
+
58
+ // Handle empty object schemas: undefined at root, preserved when nested
19
59
  if (isEmptyObjectSchema(jsonSchema)) {
20
60
  if (isRoot) {
21
61
  return undefined;
22
62
  }
23
63
 
24
- if (typeof jsonSchema === 'object' && jsonSchema.description) {
64
+ if (jsonSchema.description) {
25
65
  return { type: 'object', description: jsonSchema.description };
26
66
  }
27
67
  return { type: 'object' };
28
68
  }
29
69
 
30
- if (typeof jsonSchema === 'boolean') {
31
- return { type: 'boolean', properties: {} };
32
- }
33
-
34
70
  const {
35
71
  type,
36
72
  description,
@@ -83,7 +119,7 @@ export function convertJSONSchemaToOpenAPISchema(
83
119
  if (properties != null) {
84
120
  result.properties = Object.entries(properties).reduce(
85
121
  (acc, [key, value]) => {
86
- acc[key] = convertJSONSchemaToOpenAPISchema(value, false);
122
+ acc[key] = convertJSONSchemaDefinition(value, false, referenceContext);
87
123
  return acc;
88
124
  },
89
125
  {} as Record<string, unknown>,
@@ -92,13 +128,15 @@ export function convertJSONSchemaToOpenAPISchema(
92
128
 
93
129
  if (items) {
94
130
  result.items = Array.isArray(items)
95
- ? items.map(item => convertJSONSchemaToOpenAPISchema(item, false))
96
- : convertJSONSchemaToOpenAPISchema(items, false);
131
+ ? items.map(item =>
132
+ convertJSONSchemaDefinition(item, false, referenceContext),
133
+ )
134
+ : convertJSONSchemaDefinition(items, false, referenceContext);
97
135
  }
98
136
 
99
137
  if (allOf) {
100
138
  result.allOf = allOf.map(item =>
101
- convertJSONSchemaToOpenAPISchema(item, false),
139
+ convertJSONSchemaDefinition(item, false, referenceContext),
102
140
  );
103
141
  }
104
142
  if (anyOf) {
@@ -114,9 +152,10 @@ export function convertJSONSchemaToOpenAPISchema(
114
152
 
115
153
  if (nonNullSchemas.length === 1) {
116
154
  // If there's only one non-null schema, convert it and make it nullable
117
- const converted = convertJSONSchemaToOpenAPISchema(
155
+ const converted = convertJSONSchemaDefinition(
118
156
  nonNullSchemas[0],
119
157
  false,
158
+ referenceContext,
120
159
  );
121
160
  if (typeof converted === 'object') {
122
161
  result.nullable = true;
@@ -125,19 +164,19 @@ export function convertJSONSchemaToOpenAPISchema(
125
164
  } else {
126
165
  // If there are multiple non-null schemas, keep them in anyOf
127
166
  result.anyOf = nonNullSchemas.map(item =>
128
- convertJSONSchemaToOpenAPISchema(item, false),
167
+ convertJSONSchemaDefinition(item, false, referenceContext),
129
168
  );
130
169
  result.nullable = true;
131
170
  }
132
171
  } else {
133
172
  result.anyOf = anyOf.map(item =>
134
- convertJSONSchemaToOpenAPISchema(item, false),
173
+ convertJSONSchemaDefinition(item, false, referenceContext),
135
174
  );
136
175
  }
137
176
  }
138
177
  if (oneOf) {
139
178
  result.oneOf = oneOf.map(item =>
140
- convertJSONSchemaToOpenAPISchema(item, false),
179
+ convertJSONSchemaDefinition(item, false, referenceContext),
141
180
  );
142
181
  }
143
182
 
@@ -148,6 +187,124 @@ export function convertJSONSchemaToOpenAPISchema(
148
187
  return result;
149
188
  }
150
189
 
190
+ function convertJSONSchemaReference({
191
+ jsonSchema,
192
+ reference,
193
+ isRoot,
194
+ referenceContext,
195
+ }: {
196
+ jsonSchema: JSONSchema7;
197
+ reference: string;
198
+ isRoot: boolean;
199
+ referenceContext: ReferenceContext;
200
+ }): unknown {
201
+ const { definition, referenceKey } = getReferencedDefinition(
202
+ reference,
203
+ referenceContext,
204
+ );
205
+
206
+ if (referenceContext.resolvingReferences.has(referenceKey)) {
207
+ throw new UnsupportedFunctionalityError({
208
+ functionality: `recursive JSON Schema reference: ${reference}`,
209
+ message:
210
+ 'Google schema conversion does not support recursive JSON Schema references.',
211
+ });
212
+ }
213
+
214
+ const resolvingReferences = new Set(referenceContext.resolvingReferences);
215
+ resolvingReferences.add(referenceKey);
216
+
217
+ // Inline references instead of emitting Google's `ref` / `defs` fields.
218
+ // Those fields are supported by Vertex AI's Schema representation but are
219
+ // rejected by the Gemini Developer API representation used by this shared
220
+ // converter.
221
+ const { $ref: _reference, ...siblingSchema } = jsonSchema;
222
+ const resolvedSchema =
223
+ typeof definition === 'boolean'
224
+ ? definition
225
+ ? siblingSchema
226
+ : false
227
+ : { ...definition, ...siblingSchema };
228
+
229
+ return convertJSONSchemaDefinition(resolvedSchema, isRoot, {
230
+ ...referenceContext,
231
+ resolvingReferences,
232
+ });
233
+ }
234
+
235
+ function getReferencedDefinition(
236
+ reference: string,
237
+ referenceContext: ReferenceContext,
238
+ ): {
239
+ definition: JSONSchema7Definition;
240
+ referenceKey: string;
241
+ } {
242
+ const definitionSources = [
243
+ {
244
+ prefix: '#/$defs/',
245
+ definitions: referenceContext.dollarDefinitions,
246
+ },
247
+ {
248
+ prefix: '#/definitions/',
249
+ definitions: referenceContext.definitions,
250
+ },
251
+ ];
252
+
253
+ const source = definitionSources.find(({ prefix }) =>
254
+ reference.startsWith(prefix),
255
+ );
256
+ const encodedDefinitionName = source
257
+ ? reference.slice(source.prefix.length)
258
+ : undefined;
259
+
260
+ if (
261
+ source == null ||
262
+ encodedDefinitionName == null ||
263
+ encodedDefinitionName.length === 0 ||
264
+ encodedDefinitionName.includes('/')
265
+ ) {
266
+ throwUnsupportedReference(reference);
267
+ }
268
+
269
+ let decodedDefinitionName: string;
270
+ try {
271
+ decodedDefinitionName = decodeURIComponent(encodedDefinitionName);
272
+ } catch {
273
+ throwUnsupportedReference(reference);
274
+ }
275
+
276
+ if (
277
+ decodedDefinitionName.includes('/') ||
278
+ /~(?![01])/u.test(decodedDefinitionName) ||
279
+ source.definitions == null
280
+ ) {
281
+ throwUnsupportedReference(reference);
282
+ }
283
+
284
+ const definitionName = decodedDefinitionName.replace(/~[01]/g, match =>
285
+ match === '~1' ? '/' : '~',
286
+ );
287
+
288
+ if (
289
+ !Object.prototype.hasOwnProperty.call(source.definitions, definitionName)
290
+ ) {
291
+ throwUnsupportedReference(reference);
292
+ }
293
+
294
+ return {
295
+ definition: source.definitions[definitionName],
296
+ referenceKey: `${source.prefix}${definitionName}`,
297
+ };
298
+ }
299
+
300
+ function throwUnsupportedReference(reference: string): never {
301
+ throw new UnsupportedFunctionalityError({
302
+ functionality: `JSON Schema reference: ${reference}`,
303
+ message:
304
+ 'Google schema conversion only supports references to direct children of root-level $defs or definitions.',
305
+ });
306
+ }
307
+
151
308
  type EnumValues = NonNullable<JSONSchema7['enum']>;
152
309
  type EnumType = 'string' | 'number' | 'integer' | 'boolean';
153
310
  type GoogleEnumSchema = {
@@ -466,7 +466,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
466
466
  } else if ('functionCall' in part && part.functionCall.name != null) {
467
467
  content.push({
468
468
  type: 'tool-call' as const,
469
- toolCallId: part.functionCall.id ?? this.config.generateId(),
469
+ toolCallId: part.functionCall.id || this.config.generateId(),
470
470
  toolName: part.functionCall.name,
471
471
  input: JSON.stringify(part.functionCall.args ?? {}),
472
472
  providerMetadata: part.thoughtSignature
@@ -489,7 +489,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
489
489
  : undefined,
490
490
  });
491
491
  } else if ('toolCall' in part && part.toolCall) {
492
- const toolCallId = part.toolCall.id ?? this.config.generateId();
492
+ const toolCallId = part.toolCall.id || this.config.generateId();
493
493
  lastServerToolCallId = toolCallId;
494
494
  content.push({
495
495
  type: 'tool-call',
@@ -511,8 +511,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
511
511
  });
512
512
  } else if ('toolResponse' in part && part.toolResponse) {
513
513
  const responseToolCallId =
514
- lastServerToolCallId ??
515
- part.toolResponse.id ??
514
+ lastServerToolCallId ||
515
+ part.toolResponse.id ||
516
516
  this.config.generateId();
517
517
  content.push({
518
518
  type: 'tool-result',
@@ -876,7 +876,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
876
876
  providerMetadata: fileMeta,
877
877
  });
878
878
  } else if ('toolCall' in part && part.toolCall) {
879
- const toolCallId = part.toolCall.id ?? generateId();
879
+ const toolCallId = part.toolCall.id || generateId();
880
880
  lastServerToolCallId = toolCallId;
881
881
  const serverMeta = wrapProviderMetadata({
882
882
  ...(part.thoughtSignature
@@ -897,8 +897,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
897
897
  });
898
898
  } else if ('toolResponse' in part && part.toolResponse) {
899
899
  const responseToolCallId =
900
- lastServerToolCallId ??
901
- part.toolResponse.id ??
900
+ lastServerToolCallId ||
901
+ part.toolResponse.id ||
902
902
  generateId();
903
903
  const serverMeta = wrapProviderMetadata({
904
904
  ...(part.thoughtSignature
@@ -952,7 +952,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
952
952
 
953
953
  if (isStreamingChunk) {
954
954
  if (part.functionCall.name != null) {
955
- const toolCallId = part.functionCall.id ?? generateId();
955
+ const toolCallId = part.functionCall.id || generateId();
956
956
  const accumulator = new GoogleJSONAccumulator();
957
957
  activeStreamingToolCalls.push({
958
958
  toolCallId,
@@ -1021,7 +1021,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1021
1021
  ) {
1022
1022
  finishActiveStreamingToolCall(controller);
1023
1023
  } else if (isCompleteCall) {
1024
- const toolCallId = part.functionCall.id ?? generateId();
1024
+ const toolCallId = part.functionCall.id || generateId();
1025
1025
  const toolName = part.functionCall.name!;
1026
1026
  const args =
1027
1027
  typeof part.functionCall.args === 'string'
@@ -1058,7 +1058,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1058
1058
 
1059
1059
  hasToolCalls = true;
1060
1060
  } else if (isNoArgsCompleteCall) {
1061
- const toolCallId = part.functionCall.id ?? generateId();
1061
+ const toolCallId = part.functionCall.id || generateId();
1062
1062
  const toolName = part.functionCall.name!;
1063
1063
 
1064
1064
  controller.enqueue({
@@ -335,7 +335,7 @@ export function buildGoogleInteractionsStreamTransform({
335
335
  }
336
336
  }
337
337
  } else if (stepType === 'function_call') {
338
- const toolCallId = step?.id ?? blockId;
338
+ const toolCallId = step?.id || blockId;
339
339
  const toolName = step?.name ?? 'unknown';
340
340
  hasFunctionCall = true;
341
341
  const state: Extract<OpenBlockState, { kind: 'function_call' }> = {
@@ -360,7 +360,7 @@ export function buildGoogleInteractionsStreamTransform({
360
360
  stepType === 'mcp_server_tool_call'
361
361
  ? (step?.name ?? 'mcp_server_tool')
362
362
  : builtinToolNameFromCallType(stepType);
363
- const toolCallId = step?.id ?? blockId;
363
+ const toolCallId = step?.id || blockId;
364
364
  const state: Extract<
365
365
  OpenBlockState,
366
366
  { kind: 'builtin_tool_call' }
@@ -382,7 +382,7 @@ export function buildGoogleInteractionsStreamTransform({
382
382
  stepType === 'mcp_server_tool_result'
383
383
  ? (step?.name ?? 'mcp_server_tool')
384
384
  : builtinToolNameFromResultType(stepType);
385
- const callId = step?.call_id ?? blockId;
385
+ const callId = step?.call_id || blockId;
386
386
  const state: Extract<
387
387
  OpenBlockState,
388
388
  { kind: 'builtin_tool_result' }
@@ -608,7 +608,7 @@ export function buildGoogleInteractionsStreamTransform({
608
608
  delta: slice,
609
609
  });
610
610
  }
611
- if (delta.id != null) {
611
+ if (delta.id != null && delta.id.length > 0) {
612
612
  open.toolCallId = delta.id;
613
613
  }
614
614
  if (delta.signature != null) {
@@ -619,7 +619,9 @@ export function buildGoogleInteractionsStreamTransform({
619
619
  open.kind === 'builtin_tool_call' &&
620
620
  delta?.type === open.blockType
621
621
  ) {
622
- if (delta.id != null) open.toolCallId = delta.id;
622
+ if (delta.id != null && delta.id.length > 0) {
623
+ open.toolCallId = delta.id;
624
+ }
623
625
  if (
624
626
  delta.arguments != null &&
625
627
  typeof delta.arguments === 'object'
@@ -636,7 +638,9 @@ export function buildGoogleInteractionsStreamTransform({
636
638
  open.kind === 'builtin_tool_result' &&
637
639
  delta?.type === open.blockType
638
640
  ) {
639
- if (delta.call_id != null) open.callId = delta.call_id;
641
+ if (delta.call_id != null && delta.call_id.length > 0) {
642
+ open.callId = delta.call_id;
643
+ }
640
644
  if (delta.result !== undefined) open.result = delta.result;
641
645
  if (delta.is_error != null) open.isError = delta.is_error;
642
646
  if (
@@ -233,7 +233,7 @@ export function parseGoogleInteractionsOutputs({
233
233
  const input = JSON.stringify(call.arguments ?? {});
234
234
  content.push({
235
235
  type: 'tool-call',
236
- toolCallId: call.id ?? generateId(),
236
+ toolCallId: call.id || generateId(),
237
237
  toolName,
238
238
  input,
239
239
  providerExecuted: true,
@@ -251,7 +251,7 @@ export function parseGoogleInteractionsOutputs({
251
251
  : builtinToolNameFromResultType(type);
252
252
  content.push({
253
253
  type: 'tool-result',
254
- toolCallId: result.call_id ?? generateId(),
254
+ toolCallId: result.call_id || generateId(),
255
255
  toolName,
256
256
  result: (result.result ?? null) as NonNullable<JSONValue>,
257
257
  });