@ai-sdk/google 3.0.89 → 3.0.91

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.89",
3
+ "version": "3.0.91",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@ai-sdk/provider": "3.0.13",
40
- "@ai-sdk/provider-utils": "4.0.36"
39
+ "@ai-sdk/provider": "3.0.14",
40
+ "@ai-sdk/provider-utils": "4.0.38"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -188,6 +188,9 @@ function appendLegacyToolResultParts(
188
188
  });
189
189
  break;
190
190
  case 'image-data':
191
+ case 'file-data': {
192
+ const topLevelMediaType = String(contentPart.mediaType).split('/')[0];
193
+
191
194
  parts.push(
192
195
  {
193
196
  inlineData: {
@@ -196,10 +199,14 @@ function appendLegacyToolResultParts(
196
199
  },
197
200
  },
198
201
  {
199
- text: 'Tool executed successfully and returned this image as a response',
202
+ text:
203
+ `Tool executed successfully and returned this ` +
204
+ `${topLevelMediaType === 'image' ? 'image' : 'file'} ` +
205
+ `as a response`,
200
206
  },
201
207
  );
202
208
  break;
209
+ }
203
210
  default:
204
211
  parts.push({ text: JSON.stringify(contentPart) });
205
212
  break;
@@ -510,7 +517,7 @@ export function convertToGoogleGenerativeAIMessages(
510
517
  name: part.toolName,
511
518
  content:
512
519
  output.type === 'execution-denied'
513
- ? (output.reason ?? 'Tool execution denied.')
520
+ ? (output.reason ?? 'Tool call execution denied.')
514
521
  : output.value,
515
522
  },
516
523
  },
@@ -50,6 +50,13 @@ import {
50
50
  } from './google-json-accumulator';
51
51
  import { mapGoogleGenerativeAIFinishReason } from './map-google-generative-ai-finish-reason';
52
52
 
53
+ const configurableSafetySettingCategories = [
54
+ 'HARM_CATEGORY_HATE_SPEECH',
55
+ 'HARM_CATEGORY_DANGEROUS_CONTENT',
56
+ 'HARM_CATEGORY_HARASSMENT',
57
+ 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
58
+ ] as const;
59
+
53
60
  type GoogleGenerativeAIConfig = {
54
61
  provider: string;
55
62
  baseURL: string;
@@ -223,6 +230,16 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
223
230
  ? (googleOptions?.streamFunctionCallArguments ?? false)
224
231
  : undefined;
225
232
 
233
+ const safetyThreshold = googleOptions?.threshold;
234
+ const safetySettings =
235
+ googleOptions?.safetySettings ??
236
+ (safetyThreshold != null
237
+ ? configurableSafetySettingCategories.map(category => ({
238
+ category,
239
+ threshold: safetyThreshold,
240
+ }))
241
+ : undefined);
242
+
226
243
  const toolConfig =
227
244
  googleToolConfig ||
228
245
  streamFunctionCallArguments ||
@@ -282,7 +299,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
282
299
  },
283
300
  contents,
284
301
  systemInstruction: isGemmaModel ? undefined : systemInstruction,
285
- safetySettings: googleOptions?.safetySettings,
302
+ safetySettings,
286
303
  tools: googleTools,
287
304
  toolConfig,
288
305
  cachedContent: googleOptions?.cachedContent,
@@ -110,13 +110,11 @@ function convertFileToGoogleImage(
110
110
  ? file.data
111
111
  : convertUint8ArrayToBase64(file.data);
112
112
 
113
- // The Gemini Developer API (generativelanguage.googleapis.com) requires
114
- // inline image bytes wrapped as `inlineData`.
113
+ // Veo's predictLongRunning endpoint uses Vertex-style image payloads, not
114
+ // Gemini generateContent inlineData.
115
115
  return {
116
- inlineData: {
117
- mimeType: file.mediaType || 'image/png',
118
- data: base64Data,
119
- },
116
+ bytesBase64Encoded: base64Data,
117
+ mimeType: file.mediaType || 'image/png',
120
118
  };
121
119
  }
122
120
 
@@ -125,16 +123,19 @@ function convertProviderReferenceImage(
125
123
  ): Record<string, unknown> {
126
124
  if (refImg.bytesBase64Encoded) {
127
125
  return {
128
- inlineData: {
126
+ image: {
127
+ bytesBase64Encoded: refImg.bytesBase64Encoded,
129
128
  mimeType: 'image/png',
130
- data: refImg.bytesBase64Encoded,
131
129
  },
132
130
  };
133
131
  }
134
132
 
135
133
  if (refImg.gcsUri) {
136
134
  return {
137
- gcsUri: refImg.gcsUri,
135
+ image: {
136
+ gcsUri: refImg.gcsUri,
137
+ mimeType: 'image/png',
138
+ },
138
139
  };
139
140
  }
140
141
 
@@ -146,7 +147,7 @@ function convertInputReferenceImage(
146
147
  warnings: SharedV3Warning[],
147
148
  ): Record<string, unknown> | undefined {
148
149
  const image = convertFileToGoogleImage(file, warnings);
149
- return image != null ? { image, referenceType: 'asset' } : undefined;
150
+ return image != null ? { image } : undefined;
150
151
  }
151
152
 
152
153
  export class GoogleGenerativeAIVideoModel implements Experimental_VideoModelV3 {
@@ -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
+ }
@@ -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';