@ai-sdk/google 4.0.64 → 4.0.66

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.
@@ -1065,66 +1065,46 @@ The following Zod features are known to not work with Google:
1065
1065
  available provider model ID as a string if needed.
1066
1066
  </Note>
1067
1067
 
1068
- ### Text Batches
1068
+ ### Batch
1069
1069
 
1070
1070
  <Note type="warning">
1071
- Text batch support is experimental and the API may change in patch releases.
1071
+ Batch support is experimental and the API may change in patch releases.
1072
1072
  </Note>
1073
1073
 
1074
- The Google provider supports asynchronous text generation through the
1075
- [Gemini Batch API](https://ai.google.dev/gemini-api/docs/batch-api). Use the
1076
- experimental text batch APIs to start a batch, poll its status, and stream its
1077
- results:
1074
+ The Google provider supports asynchronous text generation through the [Gemini
1075
+ Batch API](https://ai.google.dev/gemini-api/docs/batch-api). Pass the Google
1076
+ provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch)
1077
+ API for the complete workflow, including polling, persistence, and result handling.
1078
1078
 
1079
- ```ts
1080
- import { google } from '@ai-sdk/google';
1081
- import {
1082
- experimental_getBatchResults as getBatchResults,
1083
- experimental_getBatchStatus as getBatchStatus,
1084
- experimental_startTextBatch as startTextBatch,
1085
- } from 'ai';
1086
- import { setTimeout } from 'node:timers/promises';
1087
-
1088
- const model = google('gemini-3.6-flash');
1089
-
1090
- const batch = await startTextBatch({
1091
- model,
1092
- requests: [
1093
- { id: 'capital-france', prompt: 'What is the capital of France?' },
1094
- { id: 'capital-germany', prompt: 'What is the capital of Germany?' },
1095
- ],
1096
- });
1097
-
1098
- let status = batch.status;
1099
- while (status === 'pending') {
1100
- await setTimeout(60_000);
1101
- ({ status } = await getBatchStatus({ model, batch }));
1102
- }
1103
-
1104
- for await (const item of getBatchResults({ model, batch })) {
1105
- if (item.status === 'succeeded') {
1106
- console.log(item.id, item.text);
1107
- } else {
1108
- console.error(item.id, item.error);
1109
- }
1110
- }
1111
- ```
1112
-
1113
- `startTextBatch` returns a serializable batch reference. Persist this reference
1114
- to check the batch status or retrieve its results from another process. Results
1115
- can arrive in a different order from the input requests, so match each result by
1116
- its `id`.
1079
+ Each request specifies its `type` and `model`. Google requires every text
1080
+ request in a batch to use the same model and throws before submission when the
1081
+ models differ.
1117
1082
 
1118
1083
  #### Webhooks
1119
1084
 
1120
1085
  You can pass a `webhookUrl` to receive a notification when the batch reaches a terminal state:
1121
1086
 
1122
1087
  ```ts
1123
- const batch = await startTextBatch({
1124
- model,
1088
+ import { google } from '@ai-sdk/google';
1089
+ import { experimental_startBatch as startBatch } from 'ai';
1090
+
1091
+ const model = 'gemini-3.6-flash';
1092
+
1093
+ const batch = await startBatch({
1094
+ provider: google,
1125
1095
  requests: [
1126
- { id: 'capital-france', prompt: 'What is the capital of France?' },
1127
- { id: 'capital-germany', prompt: 'What is the capital of Germany?' },
1096
+ {
1097
+ id: 'capital-france',
1098
+ type: 'text',
1099
+ model,
1100
+ prompt: 'What is the capital of France?',
1101
+ },
1102
+ {
1103
+ id: 'capital-germany',
1104
+ type: 'text',
1105
+ model,
1106
+ prompt: 'What is the capital of Germany?',
1107
+ },
1128
1108
  ],
1129
1109
  webhookUrl: 'https://example.com/api/google-batch-webhook',
1130
1110
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.64",
3
+ "version": "4.0.66",
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.10",
39
- "@ai-sdk/provider-utils": "5.0.36"
38
+ "@ai-sdk/provider": "4.0.12",
39
+ "@ai-sdk/provider-utils": "5.0.38"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.1",
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  InvalidArgumentError,
3
3
  InvalidResponseDataError,
4
- type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
4
+ type Experimental_BatchV4 as BatchV4,
5
5
  type Experimental_BatchV4Error as BatchV4Error,
6
6
  type Experimental_BatchV4ItemResult as BatchV4ItemResult,
7
7
  type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
8
- type Experimental_BatchV4StartOptions as BatchV4StartOptions,
9
8
  type Experimental_BatchV4StartResult as BatchV4StartResult,
10
9
  type Experimental_BatchV4Status as BatchV4Status,
10
+ type Experimental_TextBatchV4Request as TextBatchV4Request,
11
+ type Experimental_BatchV4StartOptions as BatchV4StartOptions,
11
12
  type LanguageModelV4GenerateResult,
12
13
  } from '@ai-sdk/provider';
13
14
  import {
@@ -23,8 +24,6 @@ import {
23
24
  postToApi,
24
25
  resolve,
25
26
  safeValidateTypes,
26
- WORKFLOW_DESERIALIZE,
27
- WORKFLOW_SERIALIZE,
28
27
  zodSchema,
29
28
  type InferSchema,
30
29
  type ResponseHandler,
@@ -45,9 +44,7 @@ const supportedGoogleBatchContentTypes = new Set<
45
44
  LanguageModelV4GenerateResult['content'][number]['type']
46
45
  >(['text', 'reasoning', 'source', 'tool-call', 'tool-result']);
47
46
 
48
- type GoogleBatchRequest = Parameters<
49
- BatchLanguageModelV4['experimental_doStartBatch']
50
- >[0]['requests'][number];
47
+ type GoogleBatchRequest = TextBatchV4Request<GoogleModelId>;
51
48
 
52
49
  const googleRpcStatusSchema = z.object({
53
50
  code: z.union([z.number(), z.string()]).nullish(),
@@ -136,33 +133,28 @@ const googleBatchResponsePreviewSchema = lazySchema(() =>
136
133
  ),
137
134
  );
138
135
 
139
- export class GoogleBatchLanguageModel
140
- extends GoogleLanguageModel
141
- implements BatchLanguageModelV4
142
- {
136
+ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
137
+ readonly specificationVersion = 'v4' as const;
138
+ readonly provider: string;
139
+ readonly supportedUrls: Record<string, RegExp[]>;
143
140
  private readonly batchConfig: GoogleLanguageModelConfig;
144
141
  private readonly batchGenerateId: () => string;
145
142
 
146
- static [WORKFLOW_SERIALIZE](model: GoogleLanguageModel) {
147
- return GoogleLanguageModel[WORKFLOW_SERIALIZE](model);
148
- }
149
-
150
- static [WORKFLOW_DESERIALIZE](options: {
151
- modelId: string;
143
+ constructor(options: {
144
+ provider: string;
152
145
  config: GoogleLanguageModelConfig;
146
+ supportedUrls: Record<string, RegExp[]>;
153
147
  }) {
154
- return new GoogleBatchLanguageModel(options.modelId, options.config);
155
- }
156
-
157
- constructor(modelId: GoogleModelId, config: GoogleLanguageModelConfig) {
158
- super(modelId, config);
159
- this.batchConfig = config;
160
- this.batchGenerateId = config.generateId ?? generateId;
148
+ this.provider = options.provider;
149
+ this.batchConfig = options.config;
150
+ this.supportedUrls = options.supportedUrls;
151
+ this.batchGenerateId = options.config.generateId ?? generateId;
161
152
  }
162
153
 
163
- async experimental_doStartBatch(
164
- options: BatchV4StartOptions<GoogleBatchRequest>,
154
+ async doStartBatch(
155
+ options: BatchV4StartOptions<{ text: GoogleModelId }>,
165
156
  ): Promise<BatchV4StartResult> {
157
+ const modelId = getGoogleBatchModelId(options.requests);
166
158
  const warnings: BatchV4StartResult['warnings'] = [];
167
159
  const displayName = `ai-sdk-batch-${this.batchGenerateId()}`;
168
160
  const inlinedRequests: Array<{
@@ -187,7 +179,11 @@ export class GoogleBatchLanguageModel
187
179
  let fileParts: string[] | undefined;
188
180
 
189
181
  for (const request of options.requests) {
190
- const preparedRequest = await this.getArgs(request.options);
182
+ const preparedRequest = await GoogleLanguageModel.prepareRequest({
183
+ modelId: request.modelId,
184
+ config: this.batchConfig,
185
+ options: request.options,
186
+ });
191
187
  const inlinedRequest = {
192
188
  request: preparedRequest.args,
193
189
  metadata: { key: request.id },
@@ -242,7 +238,7 @@ export class GoogleBatchLanguageModel
242
238
 
243
239
  const headers = await this.getHeaders(options.headers);
244
240
  const createUrl = `${this.batchConfig.baseURL}/${getModelPath(
245
- this.modelId,
241
+ modelId,
246
242
  )}:batchGenerateContent`;
247
243
 
248
244
  if (fileParts == null) {
@@ -353,15 +349,15 @@ export class GoogleBatchLanguageModel
353
349
  };
354
350
  }
355
351
 
356
- async experimental_doGetBatchStatus(
352
+ async doGetBatchStatus(
357
353
  options: BatchV4OperationOptions,
358
354
  ): Promise<BatchV4Status> {
359
355
  return convertGoogleBatchStatus(await this.retrieveBatch(options));
360
356
  }
361
357
 
362
- async experimental_doGetBatchResults(
358
+ async doGetBatchResults(
363
359
  options: BatchV4OperationOptions,
364
- ): Promise<ReadableStream<BatchV4ItemResult<LanguageModelV4GenerateResult>>> {
360
+ ): Promise<ReadableStream<BatchV4ItemResult>> {
365
361
  const operation = await this.retrieveBatch(options);
366
362
  const batchStatus = convertGoogleBatchStatus(operation);
367
363
 
@@ -450,7 +446,7 @@ export class GoogleBatchLanguageModel
450
446
  results:
451
447
  | Iterable<GoogleBatchResultLine>
452
448
  | AsyncIterable<GoogleBatchResultLine>,
453
- ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
449
+ ): AsyncGenerator<BatchV4ItemResult> {
454
450
  for await (const line of results) {
455
451
  if (line.error != null) {
456
452
  const error = convertGoogleRpcError(
@@ -462,12 +458,13 @@ export class GoogleBatchLanguageModel
462
458
  ? 'cancelled'
463
459
  : 'failed';
464
460
 
465
- yield { id: line.key, status, error };
461
+ yield { type: 'text', id: line.key, status, error };
466
462
  continue;
467
463
  }
468
464
 
469
465
  if (line.response == null) {
470
466
  yield {
467
+ type: 'text',
471
468
  id: line.key,
472
469
  status: 'failed',
473
470
  error: {
@@ -491,6 +488,7 @@ export class GoogleBatchLanguageModel
491
488
  const promptFeedback = preview.value.promptFeedback ?? undefined;
492
489
  const blockReason = promptFeedback?.blockReason ?? undefined;
493
490
  yield {
491
+ type: 'text',
494
492
  id: line.key,
495
493
  status: 'failed',
496
494
  error: {
@@ -522,6 +520,7 @@ export class GoogleBatchLanguageModel
522
520
  });
523
521
  if (!response.success) {
524
522
  yield {
523
+ type: 'text',
525
524
  id: line.key,
526
525
  status: 'failed',
527
526
  error: {
@@ -532,7 +531,8 @@ export class GoogleBatchLanguageModel
532
531
  continue;
533
532
  }
534
533
 
535
- const result = this.convertGenerateContentResponse({
534
+ const result = GoogleLanguageModel.convertGenerateContentResponse({
535
+ config: this.batchConfig,
536
536
  response: response.value,
537
537
  warnings: [],
538
538
  providerOptionsNames: ['google'],
@@ -543,6 +543,7 @@ export class GoogleBatchLanguageModel
543
543
 
544
544
  if (unsupportedPart != null) {
545
545
  yield {
546
+ type: 'text',
546
547
  id: line.key,
547
548
  status: 'failed',
548
549
  error: {
@@ -555,7 +556,7 @@ export class GoogleBatchLanguageModel
555
556
  continue;
556
557
  }
557
558
 
558
- yield { id: line.key, status: 'succeeded', result };
559
+ yield { type: 'text', id: line.key, status: 'succeeded', result };
559
560
  }
560
561
  }
561
562
 
@@ -680,3 +681,28 @@ const googleUploadUrlResponseHandler: ResponseHandler<string> = async ({
680
681
 
681
682
  return { value: uploadUrl };
682
683
  };
684
+
685
+ function getGoogleBatchModelId(
686
+ requests: readonly GoogleBatchRequest[],
687
+ ): GoogleModelId {
688
+ const modelId = requests[0]?.modelId;
689
+
690
+ if (modelId == null) {
691
+ throw new InvalidArgumentError({
692
+ argument: 'requests',
693
+ message: 'Google batches require at least one request.',
694
+ });
695
+ }
696
+
697
+ for (const request of requests) {
698
+ if (request.modelId !== modelId) {
699
+ throw new InvalidArgumentError({
700
+ argument: 'requests',
701
+ message:
702
+ 'Google batches require every request to use the same model because the model is part of the batch endpoint.',
703
+ });
704
+ }
705
+ }
706
+
707
+ return modelId;
708
+ }
@@ -243,6 +243,9 @@ export class GoogleImageModel implements ImageModelV4 {
243
243
 
244
244
  return {
245
245
  images,
246
+ ...(result.finishReason.unified === 'content-filter'
247
+ ? { isRetryable: false }
248
+ : {}),
246
249
  warnings,
247
250
  providerMetadata: {
248
251
  google: {
@@ -114,8 +114,10 @@ export class GoogleLanguageModel implements LanguageModelV4 {
114
114
  return this.config.supportedUrls?.() ?? {};
115
115
  }
116
116
 
117
- protected async getArgs(
118
- {
117
+ static async prepareRequest({
118
+ modelId,
119
+ config,
120
+ options: {
119
121
  prompt,
120
122
  maxOutputTokens,
121
123
  temperature,
@@ -130,19 +132,25 @@ export class GoogleLanguageModel implements LanguageModelV4 {
130
132
  toolChoice,
131
133
  reasoning,
132
134
  providerOptions,
133
- }: LanguageModelV4CallOptions,
134
- { isStreaming = false }: { isStreaming?: boolean } = {},
135
- ) {
135
+ },
136
+ isStreaming = false,
137
+ }: {
138
+ modelId: GoogleModelId;
139
+ config: GoogleLanguageModelConfig;
140
+ options: LanguageModelV4CallOptions;
141
+ isStreaming?: boolean;
142
+ }) {
136
143
  const warnings: SharedV4Warning[] = [];
137
144
 
138
145
  // Names to look up in providerOptions and to write into providerMetadata.
139
146
  // For the Vertex provider we read both the new `googleVertex` key and the
140
147
  // legacy `vertex` key (new takes precedence) and write under both for
141
148
  // backward compatibility. For other Google providers we use just `google`.
142
- const providerOptionsNames: readonly string[] =
143
- this.config.provider.includes('vertex')
144
- ? (['googleVertex', 'vertex'] as const)
145
- : (['google'] as const);
149
+ const providerOptionsNames: readonly string[] = config.provider.includes(
150
+ 'vertex',
151
+ )
152
+ ? (['googleVertex', 'vertex'] as const)
153
+ : (['google'] as const);
146
154
 
147
155
  let googleOptions: GoogleLanguageModelOptions | undefined;
148
156
  for (const name of providerOptionsNames) {
@@ -165,7 +173,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
165
173
  }
166
174
 
167
175
  // Add warning if Vertex rag tools are used with a non-Vertex Google provider
168
- const isVertexProvider = this.config.provider.startsWith('google.vertex.');
176
+ const isVertexProvider = config.provider.startsWith('google.vertex.');
169
177
 
170
178
  if (
171
179
  tools?.some(
@@ -179,7 +187,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
179
187
  message:
180
188
  "The 'vertex_rag_store' tool is only supported with the Google Vertex provider " +
181
189
  'and might not be supported or could behave unexpectedly with the current Google provider ' +
182
- `(${this.config.provider}).`,
190
+ `(${config.provider}).`,
183
191
  });
184
192
  }
185
193
 
@@ -189,7 +197,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
189
197
  message:
190
198
  "'streamFunctionCallArguments' is only supported on the Vertex AI API " +
191
199
  'and will be ignored with the current Google provider ' +
192
- `(${this.config.provider}). See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc`,
200
+ `(${config.provider}). See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc`,
193
201
  });
194
202
  }
195
203
 
@@ -210,7 +218,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
210
218
  type: 'other',
211
219
  message:
212
220
  "'sharedRequestType' and 'requestType' are Vertex AI options and " +
213
- `are ignored with the current Google provider (${this.config.provider}).`,
221
+ `are ignored with the current Google provider (${config.provider}).`,
214
222
  });
215
223
  }
216
224
 
@@ -254,15 +262,15 @@ export class GoogleLanguageModel implements LanguageModelV4 {
254
262
  message:
255
263
  `${droppedImageConfigFields.join(', ')} ` +
256
264
  `${droppedImageConfigFields.length === 1 ? 'is a Vertex AI option and is' : 'are Vertex AI options and are'} ` +
257
- `ignored with the current Google provider (${this.config.provider}).`,
265
+ `ignored with the current Google provider (${config.provider}).`,
258
266
  });
259
267
  imageConfig = geminiApiImageConfig;
260
268
  }
261
269
  }
262
270
 
263
- const isGemmaModel = this.modelId.toLowerCase().startsWith('gemma-');
271
+ const isGemmaModel = modelId.toLowerCase().startsWith('gemma-');
264
272
  const isGemini25DeveloperApiModel =
265
- !isVertexProvider && gemini25ModelPattern.test(this.modelId);
273
+ !isVertexProvider && gemini25ModelPattern.test(modelId);
266
274
 
267
275
  if (isGemini25DeveloperApiModel && frequencyPenalty != null) {
268
276
  warnings.push({
@@ -277,7 +285,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
277
285
  });
278
286
  }
279
287
 
280
- const { usesGemini3Features } = getGoogleModelCapabilities(this.modelId);
288
+ const { usesGemini3Features } = getGoogleModelCapabilities(modelId);
281
289
 
282
290
  const { contents, systemInstruction } = convertToGoogleMessages(prompt, {
283
291
  isGemmaModel,
@@ -295,7 +303,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
295
303
  } = prepareTools({
296
304
  tools,
297
305
  toolChoice,
298
- modelId: this.modelId,
306
+ modelId,
299
307
  isVertexProvider,
300
308
  });
301
309
  const toolNameMapping = createToolNameMapping({
@@ -307,7 +315,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
307
315
 
308
316
  const resolvedThinking = resolveThinkingConfig({
309
317
  reasoning,
310
- modelId: this.modelId,
318
+ modelId,
311
319
  warnings,
312
320
  });
313
321
  const thinkingConfig =
@@ -405,12 +413,26 @@ export class GoogleLanguageModel implements LanguageModelV4 {
405
413
  };
406
414
  }
407
415
 
408
- protected convertGenerateContentResponse({
416
+ private getArgs(
417
+ options: LanguageModelV4CallOptions,
418
+ { isStreaming = false }: { isStreaming?: boolean } = {},
419
+ ) {
420
+ return GoogleLanguageModel.prepareRequest({
421
+ modelId: this.modelId,
422
+ config: this.config,
423
+ options,
424
+ isStreaming,
425
+ });
426
+ }
427
+
428
+ static convertGenerateContentResponse({
429
+ config,
409
430
  response,
410
431
  warnings,
411
432
  providerOptionsNames,
412
433
  toolNameMapping,
413
434
  }: {
435
+ config: GoogleLanguageModelConfig;
414
436
  response: InferSchema<typeof responseSchema>;
415
437
  warnings: SharedV4Warning[];
416
438
  providerOptionsNames: readonly string[];
@@ -441,7 +463,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
441
463
  // Build content array from all parts
442
464
  for (const part of parts) {
443
465
  if ('executableCode' in part && part.executableCode?.code) {
444
- const toolCallId = this.config.generateId();
466
+ const toolCallId = config.generateId();
445
467
  lastCodeExecutionToolCallId = toolCallId;
446
468
 
447
469
  content.push({
@@ -488,7 +510,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
488
510
  } else if ('functionCall' in part && part.functionCall.name != null) {
489
511
  content.push({
490
512
  type: 'tool-call' as const,
491
- toolCallId: part.functionCall.id || this.config.generateId(),
513
+ toolCallId: part.functionCall.id || config.generateId(),
492
514
  toolName: part.functionCall.name,
493
515
  input: JSON.stringify(part.functionCall.args ?? {}),
494
516
  providerMetadata: part.thoughtSignature
@@ -511,7 +533,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
511
533
  : undefined,
512
534
  });
513
535
  } else if ('toolCall' in part && part.toolCall) {
514
- const toolCallId = part.toolCall.id || this.config.generateId();
536
+ const toolCallId = part.toolCall.id || config.generateId();
515
537
  lastServerToolCallId = toolCallId;
516
538
  content.push({
517
539
  type: 'tool-call',
@@ -533,9 +555,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
533
555
  });
534
556
  } else if ('toolResponse' in part && part.toolResponse) {
535
557
  const responseToolCallId =
536
- lastServerToolCallId ||
537
- part.toolResponse.id ||
538
- this.config.generateId();
558
+ lastServerToolCallId || part.toolResponse.id || config.generateId();
539
559
  content.push({
540
560
  type: 'tool-result',
541
561
  toolCallId: responseToolCallId,
@@ -559,7 +579,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
559
579
  const sources =
560
580
  extractSources({
561
581
  groundingMetadata: candidate?.groundingMetadata,
562
- generateId: this.config.generateId,
582
+ generateId: config.generateId,
563
583
  }) ?? [];
564
584
  for (const source of sources) {
565
585
  content.push(source);
@@ -630,7 +650,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
630
650
  fetch: this.config.fetch,
631
651
  });
632
652
 
633
- const result = this.convertGenerateContentResponse({
653
+ const result = GoogleLanguageModel.convertGenerateContentResponse({
654
+ config: this.config,
634
655
  response,
635
656
  warnings,
636
657
  providerOptionsNames,
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  EmbeddingModelV4,
3
- Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
3
+ Experimental_BatchV4 as BatchV4,
4
4
  Experimental_VideoModelV4,
5
5
  FilesV4,
6
6
  ImageModelV4,
@@ -23,7 +23,8 @@ import {
23
23
  import { VERSION } from './version';
24
24
  import { GoogleEmbeddingModel } from './google-embedding-model';
25
25
  import type { GoogleEmbeddingModelId } from './google-embedding-model-options';
26
- import { GoogleBatchLanguageModel } from './google-batch';
26
+ import { GoogleBatch } from './google-batch';
27
+ import { GoogleLanguageModel } from './google-language-model';
27
28
  import type { GoogleModelId } from './google-language-model-options';
28
29
  import { googleTools } from './google-tools';
29
30
 
@@ -54,11 +55,13 @@ const googleFilesUrlPattern =
54
55
  /^https:\/\/generativelanguage\.googleapis\.com\/v1beta\/files\/.*$/;
55
56
 
56
57
  export interface GoogleProvider extends ProviderV4 {
57
- (modelId: GoogleModelId): BatchLanguageModelV4;
58
+ (modelId: GoogleModelId): LanguageModelV4;
58
59
 
59
- languageModel(modelId: GoogleModelId): BatchLanguageModelV4;
60
+ languageModel(modelId: GoogleModelId): LanguageModelV4;
60
61
 
61
- chat(modelId: GoogleModelId): BatchLanguageModelV4;
62
+ chat(modelId: GoogleModelId): LanguageModelV4;
63
+
64
+ experimental_batch(): BatchV4<{ text: GoogleModelId }>;
62
65
 
63
66
  /**
64
67
  * Creates a model for image generation.
@@ -71,7 +74,7 @@ export interface GoogleProvider extends ProviderV4 {
71
74
  /**
72
75
  * @deprecated Use `chat()` instead.
73
76
  */
74
- generativeAI(modelId: GoogleModelId): BatchLanguageModelV4;
77
+ generativeAI(modelId: GoogleModelId): LanguageModelV4;
75
78
 
76
79
  /**
77
80
  * Creates a model for text embeddings.
@@ -258,35 +261,49 @@ export function createGoogle(
258
261
  `ai-sdk/google/${VERSION}`,
259
262
  );
260
263
 
264
+ const getSupportedUrls = (
265
+ modelId?: GoogleModelId,
266
+ includeExternalUrls = modelId == null || supportsExternalFileUrls(modelId),
267
+ ) => ({
268
+ '*': [
269
+ googleFilesUrlPattern,
270
+ new RegExp(`^${baseURL}/files/.*$`),
271
+ new RegExp(
272
+ `^https://(?:www\\.)?youtube\\.com/watch\\?v=[\\w-]+(?:&[\\w=&.-]*)?$`,
273
+ ),
274
+ new RegExp(`^https://youtu\\.be/[\\w-]+(?:\\?[\\w=&.-]*)?$`),
275
+ ],
276
+ ...(includeExternalUrls
277
+ ? Object.fromEntries(
278
+ supportedExternalUrlMediaTypes.map(mediaType => [
279
+ mediaType,
280
+ [externalHttpsUrlPattern],
281
+ ]),
282
+ )
283
+ : {}),
284
+ });
285
+
286
+ const languageModelConfig = {
287
+ provider: providerName,
288
+ baseURL,
289
+ headers: getHeaders,
290
+ generateId: options.generateId ?? generateId,
291
+ fetch: options.fetch,
292
+ };
293
+
261
294
  const createChatModel = (modelId: GoogleModelId) =>
262
- new GoogleBatchLanguageModel(modelId, {
263
- provider: providerName,
264
- baseURL,
265
- headers: getHeaders,
266
- generateId: options.generateId ?? generateId,
267
- supportedUrls: () => ({
268
- '*': [
269
- // Default Google Generative Language "files" endpoint
270
- // e.g. https://generativelanguage.googleapis.com/v1beta/files/...
271
- googleFilesUrlPattern,
272
- // Configured Google Generative Language "files" endpoint
273
- new RegExp(`^${baseURL}/files/.*$`),
274
- // YouTube URLs (public or unlisted videos)
275
- new RegExp(
276
- `^https://(?:www\\.)?youtube\\.com/watch\\?v=[\\w-]+(?:&[\\w=&.-]*)?$`,
277
- ),
278
- new RegExp(`^https://youtu\\.be/[\\w-]+(?:\\?[\\w=&.-]*)?$`),
279
- ],
280
- ...(supportsExternalFileUrls(modelId)
281
- ? Object.fromEntries(
282
- supportedExternalUrlMediaTypes.map(mediaType => [
283
- mediaType,
284
- [externalHttpsUrlPattern],
285
- ]),
286
- )
287
- : {}),
288
- }),
289
- fetch: options.fetch,
295
+ new GoogleLanguageModel(modelId, {
296
+ ...languageModelConfig,
297
+ supportedUrls: () => getSupportedUrls(modelId),
298
+ });
299
+
300
+ const createBatch = () =>
301
+ new GoogleBatch({
302
+ provider: `${providerName.replace(/\.generative-ai$/, '')}.batch`,
303
+ config: languageModelConfig,
304
+ // Batch prompt conversion happens before the model is available to the
305
+ // provider. Only advertise URL support shared by every batch model.
306
+ supportedUrls: getSupportedUrls(undefined, false),
290
307
  });
291
308
 
292
309
  const createEmbeddingModel = (modelId: GoogleEmbeddingModelId) =>
@@ -410,6 +427,7 @@ export function createGoogle(
410
427
  provider.languageModel = createChatModel;
411
428
  provider.chat = createChatModel;
412
429
  provider.generativeAI = createChatModel;
430
+ provider.experimental_batch = createBatch;
413
431
  provider.embedding = createEmbeddingModel;
414
432
  provider.embeddingModel = createEmbeddingModel;
415
433
  provider.textEmbedding = createEmbeddingModel;