@ai-sdk/google 4.0.66 → 4.0.68

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": "4.0.66",
3
+ "version": "4.0.68",
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.12",
39
- "@ai-sdk/provider-utils": "5.0.38"
38
+ "@ai-sdk/provider": "4.0.14",
39
+ "@ai-sdk/provider-utils": "5.0.40"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.1",
@@ -1,15 +1,22 @@
1
1
  import {
2
2
  InvalidArgumentError,
3
3
  InvalidResponseDataError,
4
+ UnsupportedFunctionalityError,
4
5
  type Experimental_BatchV4 as BatchV4,
6
+ type Experimental_BatchV4CancelResult as BatchV4CancelResult,
5
7
  type Experimental_BatchV4Error as BatchV4Error,
6
8
  type Experimental_BatchV4ItemResult as BatchV4ItemResult,
9
+ type Experimental_BatchV4ListOptions as BatchV4ListOptions,
10
+ type Experimental_BatchV4ListResult as BatchV4ListResult,
7
11
  type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
8
12
  type Experimental_BatchV4StartResult as BatchV4StartResult,
9
13
  type Experimental_BatchV4Status as BatchV4Status,
10
- type Experimental_TextBatchV4Request as TextBatchV4Request,
14
+ type Experimental_ImageBatchV4Request as ImageBatchV4Request,
11
15
  type Experimental_BatchV4StartOptions as BatchV4StartOptions,
12
16
  type LanguageModelV4GenerateResult,
17
+ type ImageModelV4Result,
18
+ type LanguageModelV4Prompt,
19
+ type SharedV4Warning,
13
20
  } from '@ai-sdk/provider';
14
21
  import {
15
22
  combineHeaders,
@@ -20,11 +27,13 @@ import {
20
27
  getFromApi,
21
28
  lazySchema,
22
29
  normalizeBatchRequestCounts,
30
+ parseProviderOptions,
23
31
  postJsonToApi,
24
32
  postToApi,
25
33
  resolve,
26
34
  safeValidateTypes,
27
35
  zodSchema,
36
+ convertToBase64,
28
37
  type InferSchema,
29
38
  type ResponseHandler,
30
39
  } from '@ai-sdk/provider-utils';
@@ -36,7 +45,12 @@ import {
36
45
  responseSchema,
37
46
  type GoogleLanguageModelConfig,
38
47
  } from './google-language-model';
39
- import type { GoogleModelId } from './google-language-model-options';
48
+ import {
49
+ googleLanguageModelOptions,
50
+ type GoogleModelId,
51
+ } from './google-language-model-options';
52
+ import type { GoogleImageModelId } from './google-image-settings';
53
+ import { googleImageModelOptionsSchema } from './google-image-model-options';
40
54
 
41
55
  const googleBatchInputFileMaxBytes = 2 * 1024 * 1024 * 1024;
42
56
  const googleBatchInlineCreationMaxBytes = 20_000_000;
@@ -44,7 +58,25 @@ const supportedGoogleBatchContentTypes = new Set<
44
58
  LanguageModelV4GenerateResult['content'][number]['type']
45
59
  >(['text', 'reasoning', 'source', 'tool-call', 'tool-result']);
46
60
 
47
- type GoogleBatchRequest = TextBatchV4Request<GoogleModelId>;
61
+ type GoogleImageBatchRequest = ImageBatchV4Request<GoogleImageModelId>;
62
+ type GoogleBatchModelIds = {
63
+ readonly text: GoogleModelId;
64
+ readonly image: GoogleImageModelId;
65
+ };
66
+
67
+ function assertSupportedBatchRequests(
68
+ requests: BatchV4StartOptions<GoogleBatchModelIds>['requests'],
69
+ ) {
70
+ for (const request of requests) {
71
+ const requestType = request.type;
72
+ if (requestType !== 'text' && requestType !== 'image') {
73
+ throw new UnsupportedFunctionalityError({
74
+ functionality: `batch request type: ${requestType}`,
75
+ message: `The Google Batch API does not support batch requests with type "${requestType}".`,
76
+ });
77
+ }
78
+ }
79
+ }
48
80
 
49
81
  const googleRpcStatusSchema = z.object({
50
82
  code: z.union([z.number(), z.string()]).nullish(),
@@ -76,26 +108,40 @@ const googleBatchOutputSchema = z.object({
76
108
  .nullish(),
77
109
  });
78
110
 
111
+ const googleBatchOperationZodSchema = () =>
112
+ z.object({
113
+ name: z.string(),
114
+ metadata: z
115
+ .object({
116
+ state: z.string().nullish(),
117
+ createTime: z.string().nullish(),
118
+ batchStats: googleBatchStatsSchema.nullish(),
119
+ output: googleBatchOutputSchema.nullish(),
120
+ })
121
+ .nullish(),
122
+ done: z.boolean().nullish(),
123
+ error: googleRpcStatusSchema.nullish(),
124
+ response: googleBatchOutputSchema.nullish(),
125
+ });
126
+
79
127
  const googleBatchOperationSchema = lazySchema(() =>
128
+ zodSchema(googleBatchOperationZodSchema()),
129
+ );
130
+
131
+ type GoogleBatchOperation = InferSchema<typeof googleBatchOperationSchema>;
132
+
133
+ const googleBatchListResponseSchema = lazySchema(() =>
80
134
  zodSchema(
81
135
  z.object({
82
- name: z.string(),
83
- metadata: z
84
- .object({
85
- state: z.string().nullish(),
86
- createTime: z.string().nullish(),
87
- batchStats: googleBatchStatsSchema.nullish(),
88
- output: googleBatchOutputSchema.nullish(),
89
- })
90
- .nullish(),
91
- done: z.boolean().nullish(),
92
- error: googleRpcStatusSchema.nullish(),
93
- response: googleBatchOutputSchema.nullish(),
136
+ operations: z.array(googleBatchOperationZodSchema()).nullish(),
137
+ nextPageToken: z.string().nullish(),
94
138
  }),
95
139
  ),
96
140
  );
97
141
 
98
- type GoogleBatchOperation = InferSchema<typeof googleBatchOperationSchema>;
142
+ const googleBatchCancelResponseSchema = lazySchema(() =>
143
+ zodSchema(z.object({})),
144
+ );
99
145
 
100
146
  const googleFileUploadResponseSchema = lazySchema(() =>
101
147
  zodSchema(
@@ -133,7 +179,7 @@ const googleBatchResponsePreviewSchema = lazySchema(() =>
133
179
  ),
134
180
  );
135
181
 
136
- export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
182
+ export class GoogleBatch implements BatchV4<GoogleBatchModelIds> {
137
183
  readonly specificationVersion = 'v4' as const;
138
184
  readonly provider: string;
139
185
  readonly supportedUrls: Record<string, RegExp[]>;
@@ -152,8 +198,9 @@ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
152
198
  }
153
199
 
154
200
  async doStartBatch(
155
- options: BatchV4StartOptions<{ text: GoogleModelId }>,
201
+ options: BatchV4StartOptions<GoogleBatchModelIds>,
156
202
  ): Promise<BatchV4StartResult> {
203
+ assertSupportedBatchRequests(options.requests);
157
204
  const modelId = getGoogleBatchModelId(options.requests);
158
205
  const warnings: BatchV4StartResult['warnings'] = [];
159
206
  const displayName = `ai-sdk-batch-${this.batchGenerateId()}`;
@@ -179,11 +226,14 @@ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
179
226
  let fileParts: string[] | undefined;
180
227
 
181
228
  for (const request of options.requests) {
182
- const preparedRequest = await GoogleLanguageModel.prepareRequest({
183
- modelId: request.modelId,
184
- config: this.batchConfig,
185
- options: request.options,
186
- });
229
+ const preparedRequest =
230
+ request.type === 'text'
231
+ ? await GoogleLanguageModel.prepareRequest({
232
+ modelId: request.modelId,
233
+ config: this.batchConfig,
234
+ options: request.options,
235
+ })
236
+ : await this.prepareImageRequest(request);
187
237
  const inlinedRequest = {
188
238
  request: preparedRequest.args,
189
239
  metadata: { key: request.id },
@@ -355,6 +405,54 @@ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
355
405
  return convertGoogleBatchStatus(await this.retrieveBatch(options));
356
406
  }
357
407
 
408
+ async doCancelBatch(
409
+ options: BatchV4OperationOptions,
410
+ ): Promise<BatchV4CancelResult> {
411
+ await postJsonToApi({
412
+ url: `${this.batchConfig.baseURL}/${options.batchId}:cancel`,
413
+ headers: await this.getHeaders(options.headers),
414
+ body: {},
415
+ failedResponseHandler: googleFailedResponseHandler,
416
+ successfulResponseHandler: createJsonResponseHandler(
417
+ googleBatchCancelResponseSchema,
418
+ ),
419
+ abortSignal: options.abortSignal,
420
+ fetch: this.batchConfig.fetch,
421
+ });
422
+
423
+ return {};
424
+ }
425
+
426
+ async doListBatches(options: BatchV4ListOptions): Promise<BatchV4ListResult> {
427
+ const url = new URL(`${this.batchConfig.baseURL}/batches`);
428
+ if (options.limit != null) {
429
+ url.searchParams.set('pageSize', String(options.limit));
430
+ }
431
+ if (options.cursor != null) {
432
+ url.searchParams.set('pageToken', options.cursor);
433
+ }
434
+
435
+ const { value: page } = await getFromApi({
436
+ url: url.toString(),
437
+ headers: await this.getHeaders(options.headers),
438
+ failedResponseHandler: googleFailedResponseHandler,
439
+ successfulResponseHandler: createJsonResponseHandler(
440
+ googleBatchListResponseSchema,
441
+ ),
442
+ abortSignal: options.abortSignal,
443
+ fetch: this.batchConfig.fetch,
444
+ validateUrl: false,
445
+ });
446
+
447
+ return {
448
+ batches: (page.operations ?? []).map(operation => ({
449
+ batchId: operation.name,
450
+ ...convertGoogleBatchStatus(operation),
451
+ })),
452
+ ...(page.nextPageToken != null ? { nextCursor: page.nextPageToken } : {}),
453
+ };
454
+ }
455
+
358
456
  async doGetBatchResults(
359
457
  options: BatchV4OperationOptions,
360
458
  ): Promise<ReadableStream<BatchV4ItemResult>> {
@@ -537,6 +635,16 @@ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
537
635
  warnings: [],
538
636
  providerOptionsNames: ['google'],
539
637
  });
638
+ const imageResult = convertGoogleImageBatchResult(result);
639
+ if (imageResult != null) {
640
+ yield {
641
+ type: 'image',
642
+ id: line.key,
643
+ status: 'succeeded',
644
+ result: imageResult,
645
+ };
646
+ continue;
647
+ }
540
648
  const unsupportedPart = result.content.find(
541
649
  part => !supportedGoogleBatchContentTypes.has(part.type),
542
650
  );
@@ -560,6 +668,108 @@ export class GoogleBatch implements BatchV4<{ readonly text: GoogleModelId }> {
560
668
  }
561
669
  }
562
670
 
671
+ private async prepareImageRequest(request: GoogleImageBatchRequest) {
672
+ const { prompt, n, size, aspectRatio, seed, files, mask, providerOptions } =
673
+ request.options;
674
+ const warnings: SharedV4Warning[] = [];
675
+
676
+ if (mask != null) {
677
+ throw new UnsupportedFunctionalityError({
678
+ functionality: 'mask-based image editing in Google batches',
679
+ });
680
+ }
681
+ if (n > 1) {
682
+ throw new UnsupportedFunctionalityError({
683
+ functionality: 'multiple images per Google batch request',
684
+ });
685
+ }
686
+ if (size != null) {
687
+ warnings.push({
688
+ type: 'unsupported',
689
+ feature: 'size',
690
+ details:
691
+ 'This model does not support the `size` option. Use `aspectRatio` instead.',
692
+ });
693
+ }
694
+
695
+ const userContent: Extract<
696
+ LanguageModelV4Prompt[number],
697
+ { role: 'user' }
698
+ >['content'] = [];
699
+ if (prompt != null) userContent.push({ type: 'text', text: prompt });
700
+ for (const file of files ?? []) {
701
+ userContent.push(
702
+ file.type === 'url'
703
+ ? {
704
+ type: 'file',
705
+ data: { type: 'url', url: new URL(file.url) },
706
+ mediaType: 'image/*',
707
+ }
708
+ : {
709
+ type: 'file',
710
+ data: { type: 'data', data: file.data },
711
+ mediaType: file.mediaType,
712
+ },
713
+ );
714
+ }
715
+
716
+ const googleImageOptions = await parseProviderOptions({
717
+ provider: 'google',
718
+ providerOptions,
719
+ schema: googleImageModelOptionsSchema,
720
+ });
721
+ const {
722
+ responseModalities: _responseModalities,
723
+ imageConfig: userImageConfig,
724
+ ...passthroughGoogleOptions
725
+ } = (await parseProviderOptions({
726
+ provider: 'google',
727
+ providerOptions,
728
+ schema: googleLanguageModelOptions,
729
+ })) ?? {};
730
+ const preparedGoogleOptions = await parseProviderOptions({
731
+ provider: 'google',
732
+ providerOptions: {
733
+ google: {
734
+ ...passthroughGoogleOptions,
735
+ responseModalities: ['IMAGE'],
736
+ imageConfig:
737
+ aspectRatio != null || userImageConfig != null
738
+ ? {
739
+ ...userImageConfig,
740
+ ...(aspectRatio != null ? { aspectRatio } : {}),
741
+ }
742
+ : undefined,
743
+ },
744
+ },
745
+ schema: googleLanguageModelOptions,
746
+ });
747
+
748
+ const prepared = await GoogleLanguageModel.prepareRequest({
749
+ modelId: request.modelId,
750
+ config: this.batchConfig,
751
+ options: {
752
+ prompt: [{ role: 'user', content: userContent }],
753
+ seed,
754
+ providerOptions: {
755
+ google: preparedGoogleOptions ?? { responseModalities: ['IMAGE'] },
756
+ },
757
+ tools:
758
+ googleImageOptions?.googleSearch != null
759
+ ? [
760
+ {
761
+ type: 'provider',
762
+ id: 'google.google_search',
763
+ name: 'google_search',
764
+ args: googleImageOptions.googleSearch,
765
+ },
766
+ ]
767
+ : undefined,
768
+ },
769
+ });
770
+ return { ...prepared, warnings: [...warnings, ...prepared.warnings] };
771
+ }
772
+
563
773
  private async getHeaders(headers?: Record<string, string | undefined>) {
564
774
  return combineHeaders(
565
775
  this.batchConfig.headers
@@ -683,8 +893,8 @@ const googleUploadUrlResponseHandler: ResponseHandler<string> = async ({
683
893
  };
684
894
 
685
895
  function getGoogleBatchModelId(
686
- requests: readonly GoogleBatchRequest[],
687
- ): GoogleModelId {
896
+ requests: BatchV4StartOptions<GoogleBatchModelIds>['requests'],
897
+ ): GoogleModelId | GoogleImageModelId {
688
898
  const modelId = requests[0]?.modelId;
689
899
 
690
900
  if (modelId == null) {
@@ -706,3 +916,39 @@ function getGoogleBatchModelId(
706
916
 
707
917
  return modelId;
708
918
  }
919
+
920
+ function convertGoogleImageBatchResult(
921
+ result: LanguageModelV4GenerateResult,
922
+ ): ImageModelV4Result | undefined {
923
+ const images = result.content.flatMap(part =>
924
+ part.type === 'file' &&
925
+ part.mediaType.startsWith('image/') &&
926
+ part.data.type === 'data'
927
+ ? [convertToBase64(part.data.data)]
928
+ : [],
929
+ );
930
+ if (images.length === 0) return undefined;
931
+
932
+ const googleMetadata =
933
+ (result.providerMetadata?.google as Record<string, unknown> | undefined) ??
934
+ {};
935
+ return {
936
+ images,
937
+ warnings: result.warnings,
938
+ providerMetadata: {
939
+ google: { ...googleMetadata, images: images.map(() => ({})) },
940
+ },
941
+ response: {
942
+ timestamp: new Date(),
943
+ modelId: result.response?.modelId ?? '',
944
+ headers: result.response?.headers,
945
+ },
946
+ usage: {
947
+ inputTokens: result.usage.inputTokens.total,
948
+ outputTokens: result.usage.outputTokens.total,
949
+ totalTokens:
950
+ (result.usage.inputTokens.total ?? 0) +
951
+ (result.usage.outputTokens.total ?? 0),
952
+ },
953
+ };
954
+ }
@@ -61,7 +61,10 @@ export interface GoogleProvider extends ProviderV4 {
61
61
 
62
62
  chat(modelId: GoogleModelId): LanguageModelV4;
63
63
 
64
- experimental_batch(): BatchV4<{ text: GoogleModelId }>;
64
+ experimental_batch(): BatchV4<{
65
+ text: GoogleModelId;
66
+ image: GoogleImageModelId;
67
+ }>;
65
68
 
66
69
  /**
67
70
  * Creates a model for image generation.