@ai-sdk/google 4.0.113 → 4.0.116

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.
@@ -1594,6 +1594,14 @@ await generateText({
1594
1594
 
1595
1595
  The following optional provider options are available:
1596
1596
 
1597
+ - **labels** _Record<string, string>_
1598
+
1599
+ User-defined metadata sent in the top-level `labels` field of the Interactions
1600
+ request. For example, set `{ safety_identifier: 'opaque_user_123' }` to attach
1601
+ an opaque end-user identifier. Other labels are preserved unchanged.
1602
+ Vertex AI Interactions models also read labels from `providerOptions.google`,
1603
+ not `providerOptions.googleVertex` or `providerOptions.vertex`.
1604
+
1597
1605
  - **previousInteractionId** _string_
1598
1606
 
1599
1607
  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": "4.0.113",
3
+ "version": "4.0.116",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -35,8 +35,8 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@ai-sdk/provider": "4.0.36",
39
- "@ai-sdk/provider-utils": "5.0.75"
38
+ "@ai-sdk/provider": "4.0.37",
39
+ "@ai-sdk/provider-utils": "5.0.78"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -61,6 +61,7 @@ export class GoogleJSONAccumulator {
61
61
  if (!rawPath || rawPath === '$') continue;
62
62
 
63
63
  const segments = parsePath(rawPath);
64
+ if (!normalizeAndCheckIndexes(this.accumulatedArgs, segments)) continue;
64
65
 
65
66
  const existingValue = getNestedValue(this.accumulatedArgs, segments);
66
67
  const isStringContinuation =
@@ -386,6 +387,54 @@ function getNestedValue(
386
387
  return current;
387
388
  }
388
389
 
390
+ /**
391
+ * Normalizes dotted array indexes in place and checks that every array index
392
+ * is an existing element or the next one. Arrays are streamed in order, and a
393
+ * sparse index would make `finalize()` serialize one `null` per hole, so a tiny
394
+ * response could allocate without bound.
395
+ * A missing or `null` parent is replaced with a new array, so it starts at 0.
396
+ *
397
+ * Input: ({items:["a"]}, ["items",1]) or ({items:["a"]}, ["items",5])
398
+ * Output: true or false
399
+ */
400
+ function normalizeAndCheckIndexes(
401
+ obj: Record<string, unknown>,
402
+ segments: Array<string | number>,
403
+ ): boolean {
404
+ let current: unknown = obj;
405
+ for (let i = 0; i < segments.length; i++) {
406
+ let pathSegment = segments[i];
407
+ if (Array.isArray(current) && typeof pathSegment === 'string') {
408
+ const index = Number(pathSegment);
409
+ // Only canonical array-index keys affect length. Keep object keys as strings.
410
+ if (
411
+ Number.isInteger(index) &&
412
+ index >= 0 &&
413
+ index < 2 ** 32 - 1 &&
414
+ String(index) === pathSegment
415
+ ) {
416
+ pathSegment = index;
417
+ segments[i] = index;
418
+ }
419
+ }
420
+ if (typeof pathSegment === 'number') {
421
+ const length = Array.isArray(current)
422
+ ? current.length
423
+ : current == null
424
+ ? 0
425
+ : undefined;
426
+ if (length !== undefined && pathSegment > length) return false;
427
+ }
428
+ current =
429
+ current != null &&
430
+ typeof current === 'object' &&
431
+ hasOwnProperty(current as Record<string | number, unknown>, pathSegment)
432
+ ? (current as Record<string | number, unknown>)[pathSegment]
433
+ : undefined;
434
+ }
435
+ return true;
436
+ }
437
+
389
438
  /**
390
439
  * Sets a value at a nested path, creating intermediate objects or arrays as needed.
391
440
  *
@@ -24,9 +24,9 @@ import {
24
24
  lazySchema,
25
25
  mapReasoningToProviderBudget,
26
26
  mapReasoningToProviderEffort,
27
- parseProviderOptions,
28
27
  postJsonToApi,
29
28
  resolve,
29
+ resolveProviderOptions,
30
30
  serializeModelOptions,
31
31
  WORKFLOW_SERIALIZE,
32
32
  WORKFLOW_DESERIALIZE,
@@ -179,25 +179,15 @@ export class GoogleLanguageModel implements LanguageModelV4 {
179
179
  ? (['googleVertex', 'vertex'] as const)
180
180
  : (['google'] as const);
181
181
 
182
- let googleOptions: GoogleLanguageModelOptions | undefined;
183
- for (const name of providerOptionsNames) {
184
- googleOptions = await parseProviderOptions({
185
- provider: name,
182
+ // The first present namespace wins as a whole. A Vertex provider also
183
+ // falls back to the `google` key (e.g. via the AI Gateway).
184
+ const { options: googleOptions } =
185
+ await resolveProviderOptions<GoogleLanguageModelOptions>({
186
186
  providerOptions,
187
+ namespaces: [...providerOptionsNames, 'google'],
188
+ resolution: 'first',
187
189
  schema: googleLanguageModelOptions,
188
190
  });
189
- if (googleOptions != null) break;
190
- }
191
-
192
- // Cross-namespace fallback: a Vertex provider may receive options under
193
- // the `google` key (e.g. via the AI Gateway).
194
- if (googleOptions == null && !providerOptionsNames.includes('google')) {
195
- googleOptions = await parseProviderOptions({
196
- provider: 'google',
197
- providerOptions,
198
- schema: googleLanguageModelOptions,
199
- });
200
- }
201
191
 
202
192
  // Add warning if Vertex rag tools are used with a non-Vertex Google provider
203
193
  const isVertexProvider = config.provider.startsWith('google.vertex.');
@@ -468,7 +458,13 @@ export class GoogleLanguageModel implements LanguageModelV4 {
468
458
  options: LanguageModelV4CallOptions,
469
459
  { isStreaming = false }: { isStreaming?: boolean } = {},
470
460
  ) {
471
- const { responseFormat, tools, toolChoice } = options;
461
+ const { responseFormat, toolChoice } = options;
462
+ // Enforce the caller's tool choice before the JSON response fallback can
463
+ // replace it with a forced call to the synthetic response tool.
464
+ const tools =
465
+ toolChoice?.type === 'none'
466
+ ? options.tools?.filter(tool => tool.type === 'function')
467
+ : options.tools;
472
468
  const entry = getGoogleLanguageModelEntry(this.modelId);
473
469
  const { structuredOutput } = entry.capabilities.language;
474
470
  let jsonResponseTool: LanguageModelV4FunctionTool | undefined;
@@ -522,6 +518,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
522
518
  static convertGenerateContentResponse({
523
519
  config,
524
520
  response,
521
+ responseHeaders,
525
522
  warnings,
526
523
  providerOptionsNames,
527
524
  toolNameMapping,
@@ -531,6 +528,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
531
528
  }: {
532
529
  config: GoogleLanguageModelConfig;
533
530
  response: InferSchema<typeof responseSchema>;
531
+ responseHeaders?: Record<string, string>;
534
532
  warnings: SharedV4Warning[];
535
533
  providerOptionsNames: readonly string[];
536
534
  toolNameMapping?: ReturnType<typeof createToolNameMapping>;
@@ -734,7 +732,10 @@ export class GoogleLanguageModel implements LanguageModelV4 {
734
732
  safetyRatings: candidate?.safetyRatings ?? null,
735
733
  usageMetadata: usageMetadata ?? null,
736
734
  finishMessage: candidate?.finishMessage ?? null,
737
- serviceTier: usageMetadata?.serviceTier ?? null,
735
+ serviceTier:
736
+ usageMetadata?.serviceTier ??
737
+ responseHeaders?.['x-gemini-service-tier'] ??
738
+ null,
738
739
  ...(candidate?.continuationToken != null
739
740
  ? { continuationToken: candidate.continuationToken }
740
741
  : {}),
@@ -836,6 +837,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
836
837
  const lastResult = GoogleLanguageModel.convertGenerateContentResponse({
837
838
  config: this.config,
838
839
  response: { ...response, usageMetadata },
840
+ responseHeaders,
839
841
  warnings,
840
842
  providerOptionsNames,
841
843
  toolNameMapping,
@@ -1595,7 +1597,10 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1595
1597
  safetyRatings: lastSafetyRatings,
1596
1598
  usageMetadata: usage ?? null,
1597
1599
  finishMessage: lastFinishMessage,
1598
- serviceTier: usage?.serviceTier ?? null,
1600
+ serviceTier:
1601
+ usage?.serviceTier ??
1602
+ responseHeaders?.['x-gemini-service-tier'] ??
1603
+ null,
1599
1604
  // Only surface the token when the response stopped early
1600
1605
  // (budget used up, or continuing failed), not for the
1601
1606
  // checkpoint tokens of a stream that finished normally.
@@ -48,6 +48,13 @@ export function prepareTools({
48
48
  };
49
49
  toolWarnings: SharedV4Warning[];
50
50
  } {
51
+ // NONE only disables function calling in the Google API. Provider tools
52
+ // must be omitted so they cannot execute server-side. Keep function
53
+ // declarations for conversation history containing function calls.
54
+ if (toolChoice?.type === 'none') {
55
+ tools = tools?.filter(tool => tool.type === 'function');
56
+ }
57
+
51
58
  // when the tools array is empty, change it to undefined to prevent errors:
52
59
  tools = tools?.length ? tools : undefined;
53
60
 
@@ -76,6 +76,8 @@ export type GoogleInteractionsVideoOptions = {
76
76
  export const googleInteractionsLanguageModelOptions = lazySchema(() =>
77
77
  zodSchema(
78
78
  z.object({
79
+ /** User-defined request metadata, including `safety_identifier`. */
80
+ labels: z.record(z.string(), z.string()).nullish(),
79
81
  previousInteractionId: z.string().nullish(),
80
82
  store: z.boolean().nullish(),
81
83
 
@@ -447,6 +447,7 @@ export class GoogleInteractionsLanguageModel implements LanguageModelV4 {
447
447
  const args: GoogleInteractionsRequestBody = pruneUndefined({
448
448
  ...(isAgent ? { agent: this.agent } : { model: this.modelId }),
449
449
  input,
450
+ labels: googleOptions?.labels ?? undefined,
450
451
  system_instruction: systemInstruction,
451
452
  tools: toolsForBody,
452
453
  response_format:
@@ -577,6 +577,7 @@ export type GoogleInteractionsRequestBody = {
577
577
  model?: string;
578
578
  agent?: string;
579
579
  input: GoogleInteractionsInput;
580
+ labels?: Record<string, string>;
580
581
  system_instruction?: string;
581
582
  tools?: Array<GoogleInteractionsTool>;
582
583
  response_format?: Array<GoogleInteractionsResponseFormatEntry>;