@ai-sdk/google 3.0.142 → 3.0.143

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.
@@ -1210,6 +1210,16 @@ The errors that you get in this case look like this:
1210
1210
  `GenerateContentRequest.generation_config.response_schema.properties[occupation].type: must be specified`
1211
1211
 
1212
1212
  By default, structured outputs are enabled (and for tool calling they are required).
1213
+
1214
+ The Google provider bounds schema conversion to prevent excessive resource use
1215
+ when expanding local references. Input inspection and conversion each have a
1216
+ separate budget of 10,000 visited JSON values and a conservative
1217
+ 1,000,000-character serialized-size allowance, with a depth limit of 100.
1218
+ String-size accounting reserves six characters per UTF-16 code unit for JSON
1219
+ escaping. Schemas that exceed these limits throw `UnsupportedFunctionalityError`
1220
+ before a model request is sent. Reduce the schema's size, nesting, or repeated
1221
+ references to stay within these limits.
1222
+
1213
1223
  You can disable structured outputs for object generation as a workaround:
1214
1224
 
1215
1225
  ```ts highlight="3,8"
@@ -1334,6 +1344,14 @@ await generateText({
1334
1344
 
1335
1345
  The following optional provider options are available:
1336
1346
 
1347
+ - **labels** _Record<string, string>_
1348
+
1349
+ User-defined metadata sent in the top-level `labels` field of the Interactions
1350
+ request. For example, set `{ safety_identifier: 'opaque_user_123' }` to attach
1351
+ an opaque end-user identifier. Other labels are preserved unchanged.
1352
+ Vertex AI Interactions models also read labels from `providerOptions.google`,
1353
+ not `providerOptions.googleVertex` or `providerOptions.vertex`.
1354
+
1337
1355
  - **previousInteractionId** _string_
1338
1356
 
1339
1357
  Server-side interaction id from a prior turn. When set, the server pulls
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.142",
3
+ "version": "3.0.143",
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.64"
40
+ "@ai-sdk/provider-utils": "4.0.65"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -12,8 +12,14 @@ type ReferenceContext = {
12
12
  definitions: Record<string, JSONSchema7Definition> | undefined;
13
13
  dollarDefinitions: Record<string, JSONSchema7Definition> | undefined;
14
14
  resolvingReferences: ReadonlySet<string>;
15
+ budget: { nodes: number; size: number };
16
+ depth: number;
15
17
  };
16
18
 
19
+ const maxSchemaDepth = 100;
20
+ const maxSchemaNodes = 10_000;
21
+ const maxSchemaSize = 1_000_000;
22
+
17
23
  const recursiveReferenceFunctionalityPrefix =
18
24
  'recursive JSON Schema reference:';
19
25
 
@@ -33,6 +39,12 @@ export function convertJSONSchemaToOpenAPISchema(
33
39
  jsonSchema: JSONSchema7Definition | undefined,
34
40
  isRoot = true,
35
41
  ): unknown {
42
+ // Bound the input too, including metadata copied while resolving references.
43
+ consumeSchemaBudget(jsonSchema, {
44
+ nodes: maxSchemaNodes,
45
+ size: maxSchemaSize,
46
+ });
47
+
36
48
  const rootSchema =
37
49
  typeof jsonSchema === 'object'
38
50
  ? (jsonSchema as JSONSchema7WithDefinitions)
@@ -42,6 +54,8 @@ export function convertJSONSchemaToOpenAPISchema(
42
54
  definitions: rootSchema?.definitions,
43
55
  dollarDefinitions: rootSchema?.$defs,
44
56
  resolvingReferences: new Set(),
57
+ budget: { nodes: maxSchemaNodes, size: maxSchemaSize },
58
+ depth: 0,
45
59
  });
46
60
  }
47
61
 
@@ -54,10 +68,34 @@ function convertJSONSchemaDefinition(
54
68
  return undefined;
55
69
  }
56
70
 
71
+ // Every visit is charged, even when the same definition was already expanded.
72
+ // Reserve space for emitted schema keywords and container punctuation.
73
+ referenceContext.budget.nodes--;
74
+ referenceContext.budget.size -= 128;
75
+ checkSchemaBudget(referenceContext.budget, referenceContext.depth);
76
+ referenceContext = {
77
+ ...referenceContext,
78
+ depth: referenceContext.depth + 1,
79
+ };
80
+
57
81
  if (typeof jsonSchema === 'boolean') {
58
82
  return { type: 'boolean', properties: {} };
59
83
  }
60
84
 
85
+ consumeSchemaBudget(
86
+ [
87
+ jsonSchema.$ref,
88
+ jsonSchema.type,
89
+ jsonSchema.description,
90
+ jsonSchema.required,
91
+ jsonSchema.format,
92
+ jsonSchema.const,
93
+ jsonSchema.enum,
94
+ jsonSchema.minLength,
95
+ ],
96
+ referenceContext.budget,
97
+ );
98
+
61
99
  if (jsonSchema.$ref != null) {
62
100
  return convertJSONSchemaReference({
63
101
  jsonSchema,
@@ -141,6 +179,7 @@ function convertJSONSchemaDefinition(
141
179
  if (properties != null) {
142
180
  result.properties = Object.entries(properties).reduce(
143
181
  (acc, [key, value]) => {
182
+ consumeSchemaBudget(key, referenceContext.budget);
144
183
  acc[key] = convertJSONSchemaDefinition(value, false, referenceContext);
145
184
  return acc;
146
185
  },
@@ -209,6 +248,52 @@ function convertJSONSchemaDefinition(
209
248
  return result;
210
249
  }
211
250
 
251
+ function checkSchemaBudget(
252
+ budget: ReferenceContext['budget'],
253
+ depth: number,
254
+ ): void {
255
+ if (budget.nodes < 0 || budget.size < 0 || depth > maxSchemaDepth) {
256
+ throw new UnsupportedFunctionalityError({
257
+ functionality: 'JSON Schema conversion exceeding resource limits',
258
+ message:
259
+ 'Google schema conversion exceeded its node, size, or depth limit.',
260
+ });
261
+ }
262
+ }
263
+
264
+ function consumeSchemaBudget(
265
+ value: unknown,
266
+ budget: ReferenceContext['budget'],
267
+ depth = 0,
268
+ ): void {
269
+ if (value === undefined) {
270
+ return;
271
+ }
272
+
273
+ budget.nodes--;
274
+ // A JSON string can require six characters per UTF-16 code unit when escaped.
275
+ // Other values reserve room for numbers, delimiters, and separators.
276
+ budget.size -= typeof value === 'string' ? value.length * 6 + 2 : 32;
277
+ checkSchemaBudget(budget, depth);
278
+
279
+ if (Array.isArray(value)) {
280
+ for (const item of value) {
281
+ consumeSchemaBudget(item, budget, depth + 1);
282
+ }
283
+ } else if (value !== null && typeof value === 'object') {
284
+ for (const key in value) {
285
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
286
+ consumeSchemaBudget(key, budget, depth + 1);
287
+ consumeSchemaBudget(
288
+ (value as Record<string, unknown>)[key],
289
+ budget,
290
+ depth + 1,
291
+ );
292
+ }
293
+ }
294
+ }
295
+ }
296
+
212
297
  type EnumValues = NonNullable<JSONSchema7['enum']>;
213
298
  type EnumType = 'string' | 'number' | 'integer' | 'boolean';
214
299
  type GoogleEnumSchema = {
@@ -61,6 +61,7 @@ export class GoogleJSONAccumulator {
61
61
  if (!rawPath || rawPath === '$') continue;
62
62
 
63
63
  const segments = parsePath(rawPath);
64
+ if (!hasContiguousIndexes(this.accumulatedArgs, segments)) continue;
64
65
 
65
66
  const existingValue = getNestedValue(this.accumulatedArgs, segments);
66
67
  const isStringContinuation =
@@ -374,6 +375,39 @@ function getNestedValue(
374
375
  return current;
375
376
  }
376
377
 
378
+ /**
379
+ * Checks that every array index on the path is an existing element or the next
380
+ * one. Arrays are streamed in order, and a sparse index would make `finalize()`
381
+ * serialize one `null` per hole, so a tiny response could allocate without bound.
382
+ * A missing or `null` parent is replaced with a new array, so it starts at 0.
383
+ *
384
+ * Input: ({items:["a"]}, ["items",1]) or ({items:["a"]}, ["items",5])
385
+ * Output: true or false
386
+ */
387
+ function hasContiguousIndexes(
388
+ obj: Record<string, unknown>,
389
+ segments: Array<string | number>,
390
+ ): boolean {
391
+ let current: unknown = obj;
392
+ for (const pathSegment of segments) {
393
+ if (typeof pathSegment === 'number') {
394
+ const length = Array.isArray(current)
395
+ ? current.length
396
+ : current == null
397
+ ? 0
398
+ : undefined;
399
+ if (length !== undefined && pathSegment > length) return false;
400
+ }
401
+ current =
402
+ current != null &&
403
+ typeof current === 'object' &&
404
+ hasOwnProperty(current as Record<string | number, unknown>, pathSegment)
405
+ ? (current as Record<string | number, unknown>)[pathSegment]
406
+ : undefined;
407
+ }
408
+ return true;
409
+ }
410
+
377
411
  /**
378
412
  * Sets a value at a nested path, creating intermediate objects or arrays as needed.
379
413
  *
@@ -53,6 +53,8 @@ export type GoogleInteractionsModelId =
53
53
  export const googleInteractionsLanguageModelOptions = lazySchema(() =>
54
54
  zodSchema(
55
55
  z.object({
56
+ /** User-defined request metadata, including `safety_identifier`. */
57
+ labels: z.record(z.string(), z.string()).nullish(),
56
58
  previousInteractionId: z.string().nullish(),
57
59
  store: z.boolean().nullish(),
58
60
 
@@ -420,6 +420,7 @@ export class GoogleInteractionsLanguageModel implements LanguageModelV3 {
420
420
  const args: GoogleInteractionsRequestBody = pruneUndefined({
421
421
  ...(isAgent ? { agent: this.agent } : { model: this.modelId }),
422
422
  input,
423
+ labels: opts?.labels ?? undefined,
423
424
  system_instruction: systemInstruction,
424
425
  tools: toolsForBody,
425
426
  response_format:
@@ -546,6 +546,7 @@ export type GoogleInteractionsRequestBody = {
546
546
  model?: string;
547
547
  agent?: string;
548
548
  input: GoogleInteractionsInput;
549
+ labels?: Record<string, string>;
549
550
  system_instruction?: string;
550
551
  tools?: Array<GoogleInteractionsTool>;
551
552
  response_format?: Array<GoogleInteractionsResponseFormatEntry>;