@ai-sdk/google 3.0.88 → 3.0.90

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.88",
3
+ "version": "3.0.90",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -37,15 +37,15 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@ai-sdk/provider": "3.0.13",
40
- "@ai-sdk/provider-utils": "4.0.35"
40
+ "@ai-sdk/provider-utils": "4.0.37"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
44
44
  "tsup": "^8",
45
45
  "typescript": "5.8.3",
46
46
  "zod": "3.25.76",
47
- "@vercel/ai-tsconfig": "0.0.0",
48
- "@ai-sdk/test-server": "1.0.6"
47
+ "@ai-sdk/test-server": "1.0.6",
48
+ "@vercel/ai-tsconfig": "0.0.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "zod": "^3.25.76 || ^4.1.8"
@@ -3,7 +3,7 @@ import {
3
3
  type LanguageModelV3Prompt,
4
4
  type SharedV3Warning,
5
5
  } from '@ai-sdk/provider';
6
- import { convertToBase64 } from '@ai-sdk/provider-utils';
6
+ import { convertToBase64, secureJsonParse } from '@ai-sdk/provider-utils';
7
7
  import type {
8
8
  GoogleGenerativeAIContent,
9
9
  GoogleGenerativeAIContentPart,
@@ -385,7 +385,7 @@ export function convertToGoogleGenerativeAIMessages(
385
385
  toolType: serverToolType,
386
386
  args:
387
387
  typeof part.input === 'string'
388
- ? JSON.parse(part.input)
388
+ ? secureJsonParse(part.input)
389
389
  : part.input,
390
390
  id: serverToolCallId,
391
391
  },
@@ -510,7 +510,7 @@ export function convertToGoogleGenerativeAIMessages(
510
510
  name: part.toolName,
511
511
  content:
512
512
  output.type === 'execution-denied'
513
- ? (output.reason ?? 'Tool execution denied.')
513
+ ? (output.reason ?? 'Tool call execution denied.')
514
514
  : output.value,
515
515
  },
516
516
  },
@@ -29,7 +29,7 @@ type GoogleGenerativeAIEmbeddingConfig = {
29
29
  export class GoogleGenerativeAIEmbeddingModel implements EmbeddingModelV3 {
30
30
  readonly specificationVersion = 'v3';
31
31
  readonly modelId: GoogleGenerativeAIEmbeddingModelId;
32
- readonly maxEmbeddingsPerCall = 2048;
32
+ readonly maxEmbeddingsPerCall = 100;
33
33
  readonly supportsParallelCalls = true;
34
34
 
35
35
  private readonly config: GoogleGenerativeAIEmbeddingConfig;
@@ -141,6 +141,37 @@ export interface GoogleGenerativeAIProviderSettings {
141
141
  name?: string;
142
142
  }
143
143
 
144
+ const supportedExternalUrlMediaTypes = [
145
+ 'text/html',
146
+ 'text/css',
147
+ 'text/plain',
148
+ 'text/xml',
149
+ 'text/csv',
150
+ 'text/rtf',
151
+ 'text/javascript',
152
+ 'application/json',
153
+ 'application/pdf',
154
+ 'image/bmp',
155
+ 'image/jpeg',
156
+ 'image/png',
157
+ 'image/webp',
158
+ 'video/mp4',
159
+ 'video/mpeg',
160
+ 'video/quicktime',
161
+ 'video/avi',
162
+ 'video/x-flv',
163
+ 'video/mpg',
164
+ 'video/webm',
165
+ 'video/wmv',
166
+ 'video/3gpp',
167
+ ];
168
+
169
+ const externalHttpsUrlPattern = /^https:\/\/.*$/;
170
+
171
+ function supportsExternalFileUrls(modelId: string) {
172
+ return /(^|\/)gemini-/.test(modelId) && !/(^|\/)gemini-2\.0/.test(modelId);
173
+ }
174
+
144
175
  /**
145
176
  * Create a Google Generative AI provider instance.
146
177
  */
@@ -183,6 +214,14 @@ export function createGoogleGenerativeAI(
183
214
  ),
184
215
  new RegExp(`^https://youtu\\.be/[\\w-]+(?:\\?[\\w=&.-]*)?$`),
185
216
  ],
217
+ ...(supportsExternalFileUrls(modelId)
218
+ ? Object.fromEntries(
219
+ supportedExternalUrlMediaTypes.map(mediaType => [
220
+ mediaType,
221
+ [externalHttpsUrlPattern],
222
+ ]),
223
+ )
224
+ : {}),
186
225
  }),
187
226
  fetch: options.fetch,
188
227
  });
@@ -11,7 +11,10 @@ import type {
11
11
  GoogleInteractionsEvent,
12
12
  GoogleInteractionsUsage,
13
13
  } from './google-interactions-api';
14
- import { convertGoogleInteractionsUsage } from './convert-google-interactions-usage';
14
+ import {
15
+ convertGoogleInteractionsUsage,
16
+ getGoogleInteractionsOutputTokensByModality,
17
+ } from './convert-google-interactions-usage';
15
18
  import {
16
19
  annotationsToSources,
17
20
  builtinToolResultToSources,
@@ -490,6 +493,47 @@ export function buildGoogleInteractionsStreamTransform({
490
493
  break;
491
494
  }
492
495
 
496
+ /*
497
+ * `video` deltas inside `model_output` carry the full payload in a
498
+ * single chunk (no per-byte streaming), mirroring `image`. Emit the
499
+ * `file` part as soon as the delta arrives so it surfaces regardless
500
+ * of whether a text block is currently open at the same index.
501
+ */
502
+ if (
503
+ dtype === 'video' &&
504
+ (open.kind === 'pending_model_output' || open.kind === 'text')
505
+ ) {
506
+ const videoDelta = event.delta as
507
+ | { data?: string; mime_type?: string; uri?: string }
508
+ | undefined;
509
+ const google: Record<string, string> = {};
510
+ if (interactionId != null) google.interactionId = interactionId;
511
+ const providerMetadata =
512
+ Object.keys(google).length > 0 ? { google } : undefined;
513
+ if (videoDelta?.data != null && videoDelta.data.length > 0) {
514
+ controller.enqueue({
515
+ type: 'file',
516
+ mediaType: videoDelta.mime_type ?? 'video/mp4',
517
+ data: videoDelta.data,
518
+ ...(providerMetadata ? { providerMetadata } : {}),
519
+ });
520
+ } else if (videoDelta?.uri != null && videoDelta.uri.length > 0) {
521
+ const uriProviderMetadata = {
522
+ google: {
523
+ ...(interactionId != null ? { interactionId } : {}),
524
+ videoUri: videoDelta.uri,
525
+ },
526
+ };
527
+ controller.enqueue({
528
+ type: 'file',
529
+ mediaType: videoDelta.mime_type ?? 'video/mp4',
530
+ data: '',
531
+ providerMetadata: uriProviderMetadata,
532
+ });
533
+ }
534
+ break;
535
+ }
536
+
493
537
  const delta = event.delta as
494
538
  | {
495
539
  type?: string;
@@ -824,10 +868,14 @@ export function buildGoogleInteractionsStreamTransform({
824
868
  raw: finishStatus,
825
869
  };
826
870
 
871
+ const outputTokensByModality =
872
+ getGoogleInteractionsOutputTokensByModality(usage);
873
+
827
874
  const providerMetadata: SharedV3ProviderMetadata = {
828
875
  google: {
829
876
  ...(interactionId != null ? { interactionId } : {}),
830
877
  ...(serviceTier != null ? { serviceTier } : {}),
878
+ ...(outputTokensByModality != null ? { outputTokensByModality } : {}),
831
879
  },
832
880
  };
833
881
 
@@ -45,3 +45,25 @@ export function convertGoogleInteractionsUsage(
45
45
  raw: usage as unknown as JSONObject,
46
46
  };
47
47
  }
48
+
49
+ /**
50
+ * Extracts the per-modality output token breakdown from an Interactions usage
51
+ * record (e.g. `{ video: 57920, text: 12 }`).
52
+ */
53
+ export function getGoogleInteractionsOutputTokensByModality(
54
+ usage: GoogleInteractionsUsage | undefined | null,
55
+ ): Record<string, number> | undefined {
56
+ const byModality = usage?.output_tokens_by_modality;
57
+ if (byModality == null) {
58
+ return undefined;
59
+ }
60
+
61
+ const result: Record<string, number> = {};
62
+ for (const entry of byModality) {
63
+ if (entry?.modality != null && entry.tokens != null) {
64
+ result[entry.modality] = entry.tokens;
65
+ }
66
+ }
67
+
68
+ return Object.keys(result).length > 0 ? result : undefined;
69
+ }
@@ -4,7 +4,7 @@ import type {
4
4
  LanguageModelV3ToolResultOutput,
5
5
  SharedV3Warning,
6
6
  } from '@ai-sdk/provider';
7
- import { convertToBase64 } from '@ai-sdk/provider-utils';
7
+ import { convertToBase64, secureJsonParse } from '@ai-sdk/provider-utils';
8
8
  import type {
9
9
  GoogleInteractionsContent,
10
10
  GoogleInteractionsContentBlock,
@@ -372,7 +372,7 @@ function compactPromptForPreviousInteraction({
372
372
 
373
373
  function safeParseToolArgs(input: string): Record<string, unknown> {
374
374
  try {
375
- const parsed = JSON.parse(input);
375
+ const parsed = secureJsonParse(input);
376
376
  if (
377
377
  parsed != null &&
378
378
  typeof parsed === 'object' &&
@@ -140,9 +140,19 @@ const contentBlockSchema = () => {
140
140
  })
141
141
  .loose();
142
142
 
143
+ const videoContent = z
144
+ .object({
145
+ type: z.literal('video'),
146
+ data: z.string().nullish(),
147
+ mime_type: z.string().nullish(),
148
+ uri: z.string().nullish(),
149
+ })
150
+ .loose();
151
+
143
152
  return z.union([
144
153
  textContent,
145
154
  imageContent,
155
+ videoContent,
146
156
  z.object({ type: z.string() }).loose(),
147
157
  ]);
148
158
  };
@@ -384,6 +394,19 @@ export const googleInteractionsEventSchema = lazySchema(() =>
384
394
  })
385
395
  .loose();
386
396
 
397
+ /*
398
+ * `video` deltas carry the entire payload per delta (`data` base64 +
399
+ * `mime_type`, or `uri`) — there is no per-byte streaming.
400
+ */
401
+ const stepDeltaVideo = z
402
+ .object({
403
+ type: z.literal('video'),
404
+ data: z.string().nullish(),
405
+ mime_type: z.string().nullish(),
406
+ uri: z.string().nullish(),
407
+ })
408
+ .loose();
409
+
387
410
  /*
388
411
  * Built-in tool call/result step deltas mirror the shape of their step
389
412
  * counterparts (full payload per delta — there is no per-token
@@ -419,6 +442,7 @@ export const googleInteractionsEventSchema = lazySchema(() =>
419
442
  const stepDeltaUnion = z.union([
420
443
  stepDeltaText,
421
444
  stepDeltaImage,
445
+ stepDeltaVideo,
422
446
  stepDeltaThoughtSummary,
423
447
  stepDeltaThoughtSignature,
424
448
  stepDeltaArgumentsDelta,
@@ -20,7 +20,10 @@ import {
20
20
  } from '@ai-sdk/provider-utils';
21
21
  import { googleFailedResponseHandler } from '../google-error';
22
22
  import { buildGoogleInteractionsStreamTransform } from './build-google-interactions-stream-transform';
23
- import { convertGoogleInteractionsUsage } from './convert-google-interactions-usage';
23
+ import {
24
+ convertGoogleInteractionsUsage,
25
+ getGoogleInteractionsOutputTokensByModality,
26
+ } from './convert-google-interactions-usage';
24
27
  import { convertToGoogleInteractionsInput } from './convert-to-google-interactions-input';
25
28
  import {
26
29
  googleInteractionsEventSchema,
@@ -519,10 +522,15 @@ export class GoogleInteractionsLanguageModel implements LanguageModelV3 {
519
522
  * `response.id` is omitted when `store: false` (fully stateless mode), so
520
523
  * `interactionId` is only surfaced when the API actually returned one.
521
524
  */
525
+ const outputTokensByModality = getGoogleInteractionsOutputTokensByModality(
526
+ response.usage,
527
+ );
528
+
522
529
  const providerMetadata: SharedV3ProviderMetadata = {
523
530
  google: {
524
531
  ...(interactionId != null ? { interactionId } : {}),
525
532
  ...(serviceTier != null ? { serviceTier } : {}),
533
+ ...(outputTokensByModality != null ? { outputTokensByModality } : {}),
526
534
  },
527
535
  };
528
536
 
@@ -15,6 +15,14 @@ export type GoogleInteractionsProviderMetadata = {
15
15
  */
16
16
  serviceTier?: string;
17
17
 
18
+ /**
19
+ * Output token counts keyed by modality (e.g. `{ video: 57920 }`), sourced
20
+ * from the Interactions API `output_tokens_by_modality`. Present only when
21
+ * the response reports a breakdown. Preview surface for per-modality billing;
22
+ * may be promoted to a first-class usage field later.
23
+ */
24
+ outputTokensByModality?: Record<string, number>;
25
+
18
26
  /**
19
27
  * Per-block signature hash for backend validation. Set by the SDK on output
20
28
  * reasoning / tool-call parts and round-tripped on input parts.
@@ -68,7 +68,7 @@ function builtinToolNameFromResultType(type: string): string {
68
68
  * `LanguageModelV3Content[]`. Surfaces:
69
69
  *
70
70
  * - `model_output` steps: iterates `step.content[]` for `text` (with
71
- * annotations → source parts) and `image` content blocks.
71
+ * annotations → source parts), `image`, and `video` content blocks.
72
72
  * - `thought` steps: emits a single `reasoning` part from `summary[*]`.
73
73
  * - `function_call` steps: emits a `tool-call` part directly.
74
74
  * - Built-in tool `*_call` / `*_result` steps (Google Search, Code Execution,
@@ -162,6 +162,38 @@ export function parseGoogleInteractionsOutputs({
162
162
  },
163
163
  });
164
164
  }
165
+ } else if (blockType === 'video') {
166
+ const video = block as {
167
+ data?: string;
168
+ mime_type?: string;
169
+ uri?: string;
170
+ };
171
+ if (video.data != null && video.data.length > 0) {
172
+ content.push({
173
+ type: 'file',
174
+ mediaType: video.mime_type ?? 'video/mp4',
175
+ data: video.data,
176
+ ...googleProviderMetadata({ interactionId }),
177
+ });
178
+ } else if (video.uri != null && video.uri.length > 0) {
179
+ /*
180
+ * V3 `LanguageModelV3File` only supports inline data (`string` /
181
+ * `Uint8Array`). URL-only video outputs cannot be represented as
182
+ * a file content part on the v3 spec; surface the URI through
183
+ * provider metadata so callers can still recover it.
184
+ */
185
+ content.push({
186
+ type: 'file',
187
+ mediaType: video.mime_type ?? 'video/mp4',
188
+ data: '',
189
+ providerMetadata: {
190
+ google: {
191
+ ...(interactionId != null ? { interactionId } : {}),
192
+ videoUri: video.uri,
193
+ },
194
+ },
195
+ });
196
+ }
165
197
  }
166
198
  }
167
199
  break;
@@ -1,3 +1,9 @@
1
1
  export * from '../google-generative-ai-language-model';
2
2
  export { googleTools } from '../google-tools';
3
3
  export type { GoogleGenerativeAIModelId } from '../google-generative-ai-options';
4
+ export {
5
+ GoogleInteractionsLanguageModel,
6
+ type GoogleInteractionsModelInput,
7
+ } from '../interactions/google-interactions-language-model';
8
+ export type { GoogleInteractionsModelId } from '../interactions/google-interactions-language-model-options';
9
+ export type { GoogleInteractionsAgentName } from '../interactions/google-interactions-agent';