@ai-sdk/google 3.0.10 → 3.0.12

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/docs/15-google-generative-ai.mdx +1088 -0
  5. package/package.json +9 -4
  6. package/src/__snapshots__/google-generative-ai-embedding-model.test.ts.snap +33 -0
  7. package/src/convert-google-generative-ai-usage.ts +51 -0
  8. package/src/convert-json-schema-to-openapi-schema.test.ts +684 -0
  9. package/src/convert-json-schema-to-openapi-schema.ts +158 -0
  10. package/src/convert-to-google-generative-ai-messages.test.ts +495 -0
  11. package/src/convert-to-google-generative-ai-messages.ts +232 -0
  12. package/src/get-model-path.test.ts +16 -0
  13. package/src/get-model-path.ts +3 -0
  14. package/src/google-error.ts +26 -0
  15. package/src/google-generative-ai-embedding-model.test.ts +204 -0
  16. package/src/google-generative-ai-embedding-model.ts +159 -0
  17. package/src/google-generative-ai-embedding-options.ts +52 -0
  18. package/src/google-generative-ai-image-model.test.ts +411 -0
  19. package/src/google-generative-ai-image-model.ts +184 -0
  20. package/src/google-generative-ai-image-settings.ts +12 -0
  21. package/src/google-generative-ai-language-model.test.ts +4616 -0
  22. package/src/google-generative-ai-language-model.ts +1009 -0
  23. package/src/google-generative-ai-options.ts +193 -0
  24. package/src/google-generative-ai-prompt.ts +38 -0
  25. package/src/google-prepare-tools.test.ts +474 -0
  26. package/src/google-prepare-tools.ts +264 -0
  27. package/src/google-provider.test.ts +307 -0
  28. package/src/google-provider.ts +201 -0
  29. package/src/google-supported-file-url.test.ts +57 -0
  30. package/src/google-supported-file-url.ts +20 -0
  31. package/src/google-tools.ts +71 -0
  32. package/src/index.ts +11 -0
  33. package/src/internal/index.ts +3 -0
  34. package/src/map-google-generative-ai-finish-reason.ts +29 -0
  35. package/src/tool/code-execution.ts +35 -0
  36. package/src/tool/enterprise-web-search.ts +18 -0
  37. package/src/tool/file-search.ts +51 -0
  38. package/src/tool/google-maps.ts +14 -0
  39. package/src/tool/google-search.ts +40 -0
  40. package/src/tool/url-context.ts +16 -0
  41. package/src/tool/vertex-rag-store.ts +31 -0
  42. package/src/version.ts +6 -0
@@ -0,0 +1,411 @@
1
+ import { createTestServer } from '@ai-sdk/test-server/with-vitest';
2
+ import { GoogleGenerativeAIImageModel } from './google-generative-ai-image-model';
3
+ import { describe, it, expect } from 'vitest';
4
+
5
+ const prompt = 'A cute baby sea otter';
6
+
7
+ const model = new GoogleGenerativeAIImageModel(
8
+ 'imagen-3.0-generate-002',
9
+ {},
10
+ {
11
+ provider: 'google.generative-ai',
12
+ baseURL: 'https://api.example.com/v1beta',
13
+ headers: () => ({ 'api-key': 'test-api-key' }),
14
+ },
15
+ );
16
+
17
+ const server = createTestServer({
18
+ 'https://api.example.com/v1beta/models/imagen-3.0-generate-002:predict': {
19
+ response: {
20
+ type: 'json-value',
21
+ body: {
22
+ predictions: [
23
+ { bytesBase64Encoded: 'base64-image-1' },
24
+ { bytesBase64Encoded: 'base64-image-2' },
25
+ ],
26
+ },
27
+ },
28
+ },
29
+ });
30
+ describe('GoogleGenerativeAIImageModel', () => {
31
+ describe('doGenerate', () => {
32
+ function prepareJsonResponse({
33
+ headers,
34
+ }: {
35
+ headers?: Record<string, string>;
36
+ } = {}) {
37
+ const url =
38
+ 'https://api.example.com/v1beta/models/imagen-3.0-generate-002:predict';
39
+ server.urls[url].response = {
40
+ type: 'json-value',
41
+ headers,
42
+ body: {
43
+ predictions: [
44
+ { bytesBase64Encoded: 'base64-image-1' },
45
+ { bytesBase64Encoded: 'base64-image-2' },
46
+ ],
47
+ },
48
+ };
49
+ }
50
+
51
+ it('should pass headers', async () => {
52
+ prepareJsonResponse();
53
+
54
+ const modelWithHeaders = new GoogleGenerativeAIImageModel(
55
+ 'imagen-3.0-generate-002',
56
+ {},
57
+ {
58
+ provider: 'google.generative-ai',
59
+ baseURL: 'https://api.example.com/v1beta',
60
+ headers: () => ({
61
+ 'Custom-Provider-Header': 'provider-header-value',
62
+ }),
63
+ },
64
+ );
65
+
66
+ await modelWithHeaders.doGenerate({
67
+ prompt,
68
+ files: undefined,
69
+ mask: undefined,
70
+ n: 2,
71
+ size: undefined,
72
+ aspectRatio: undefined,
73
+ seed: undefined,
74
+ providerOptions: {},
75
+ headers: {
76
+ 'Custom-Request-Header': 'request-header-value',
77
+ },
78
+ });
79
+
80
+ expect(server.calls[0].requestHeaders).toStrictEqual({
81
+ 'content-type': 'application/json',
82
+ 'custom-provider-header': 'provider-header-value',
83
+ 'custom-request-header': 'request-header-value',
84
+ });
85
+ });
86
+
87
+ it('should respect maxImagesPerCall setting', () => {
88
+ const customModel = new GoogleGenerativeAIImageModel(
89
+ 'imagen-3.0-generate-002',
90
+ { maxImagesPerCall: 2 },
91
+ {
92
+ provider: 'google.generative-ai',
93
+ baseURL: 'https://api.example.com/v1beta',
94
+ headers: () => ({ 'api-key': 'test-api-key' }),
95
+ },
96
+ );
97
+
98
+ expect(customModel.maxImagesPerCall).toBe(2);
99
+ });
100
+
101
+ it('should use default maxImagesPerCall when not specified', () => {
102
+ const defaultModel = new GoogleGenerativeAIImageModel(
103
+ 'imagen-3.0-generate-002',
104
+ {},
105
+ {
106
+ provider: 'google.generative-ai',
107
+ baseURL: 'https://api.example.com/v1beta',
108
+ headers: () => ({ 'api-key': 'test-api-key' }),
109
+ },
110
+ );
111
+
112
+ expect(defaultModel.maxImagesPerCall).toBe(4);
113
+ });
114
+
115
+ it('should extract the generated images', async () => {
116
+ prepareJsonResponse();
117
+
118
+ const result = await model.doGenerate({
119
+ prompt,
120
+ files: undefined,
121
+ mask: undefined,
122
+ n: 2,
123
+ size: undefined,
124
+ aspectRatio: undefined,
125
+ seed: undefined,
126
+ providerOptions: {},
127
+ });
128
+
129
+ expect(result.images).toStrictEqual(['base64-image-1', 'base64-image-2']);
130
+ });
131
+
132
+ it('sends aspect ratio in the request', async () => {
133
+ prepareJsonResponse();
134
+
135
+ await model.doGenerate({
136
+ prompt: 'test prompt',
137
+ files: undefined,
138
+ mask: undefined,
139
+ n: 1,
140
+ size: undefined,
141
+ aspectRatio: '16:9',
142
+ seed: undefined,
143
+ providerOptions: {},
144
+ });
145
+
146
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
147
+ {
148
+ "instances": [
149
+ {
150
+ "prompt": "test prompt",
151
+ },
152
+ ],
153
+ "parameters": {
154
+ "aspectRatio": "16:9",
155
+ "sampleCount": 1,
156
+ },
157
+ }
158
+ `);
159
+ });
160
+
161
+ it('should pass aspect ratio directly when specified', async () => {
162
+ prepareJsonResponse();
163
+
164
+ await model.doGenerate({
165
+ prompt: 'test prompt',
166
+ files: undefined,
167
+ mask: undefined,
168
+ n: 1,
169
+ size: undefined,
170
+ aspectRatio: '16:9',
171
+ seed: undefined,
172
+ providerOptions: {},
173
+ });
174
+
175
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
176
+ {
177
+ "instances": [
178
+ {
179
+ "prompt": "test prompt",
180
+ },
181
+ ],
182
+ "parameters": {
183
+ "aspectRatio": "16:9",
184
+ "sampleCount": 1,
185
+ },
186
+ }
187
+ `);
188
+ });
189
+
190
+ it('should combine aspectRatio and provider options', async () => {
191
+ prepareJsonResponse();
192
+
193
+ await model.doGenerate({
194
+ prompt: 'test prompt',
195
+ files: undefined,
196
+ mask: undefined,
197
+ n: 1,
198
+ size: undefined,
199
+ aspectRatio: '1:1',
200
+ seed: undefined,
201
+ providerOptions: {
202
+ google: {
203
+ personGeneration: 'dont_allow',
204
+ },
205
+ },
206
+ });
207
+
208
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
209
+ {
210
+ "instances": [
211
+ {
212
+ "prompt": "test prompt",
213
+ },
214
+ ],
215
+ "parameters": {
216
+ "aspectRatio": "1:1",
217
+ "personGeneration": "dont_allow",
218
+ "sampleCount": 1,
219
+ },
220
+ }
221
+ `);
222
+ });
223
+
224
+ it('should return warnings for unsupported settings', async () => {
225
+ prepareJsonResponse();
226
+
227
+ const result = await model.doGenerate({
228
+ prompt,
229
+ files: undefined,
230
+ mask: undefined,
231
+ n: 1,
232
+ size: '1024x1024',
233
+ aspectRatio: '1:1',
234
+ seed: 123,
235
+ providerOptions: {},
236
+ });
237
+
238
+ expect(result.warnings).toMatchInlineSnapshot(`
239
+ [
240
+ {
241
+ "details": "This model does not support the \`size\` option. Use \`aspectRatio\` instead.",
242
+ "feature": "size",
243
+ "type": "unsupported",
244
+ },
245
+ {
246
+ "details": "This model does not support the \`seed\` option through this provider.",
247
+ "feature": "seed",
248
+ "type": "unsupported",
249
+ },
250
+ ]
251
+ `);
252
+ });
253
+
254
+ it('should include response data with timestamp, modelId and headers', async () => {
255
+ prepareJsonResponse({
256
+ headers: {
257
+ 'request-id': 'test-request-id',
258
+ 'x-goog-quota-remaining': '123',
259
+ },
260
+ });
261
+
262
+ const testDate = new Date('2024-03-15T12:00:00Z');
263
+
264
+ const customModel = new GoogleGenerativeAIImageModel(
265
+ 'imagen-3.0-generate-002',
266
+ {},
267
+ {
268
+ provider: 'google.generative-ai',
269
+ baseURL: 'https://api.example.com/v1beta',
270
+ headers: () => ({ 'api-key': 'test-api-key' }),
271
+ _internal: {
272
+ currentDate: () => testDate,
273
+ },
274
+ },
275
+ );
276
+
277
+ const result = await customModel.doGenerate({
278
+ prompt,
279
+ files: undefined,
280
+ mask: undefined,
281
+ n: 1,
282
+ size: undefined,
283
+ aspectRatio: undefined,
284
+ seed: undefined,
285
+ providerOptions: {},
286
+ });
287
+
288
+ expect(result.response).toStrictEqual({
289
+ timestamp: testDate,
290
+ modelId: 'imagen-3.0-generate-002',
291
+ headers: {
292
+ 'content-length': '97',
293
+ 'content-type': 'application/json',
294
+ 'request-id': 'test-request-id',
295
+ 'x-goog-quota-remaining': '123',
296
+ },
297
+ });
298
+ });
299
+
300
+ it('should use real date when no custom date provider is specified', async () => {
301
+ prepareJsonResponse();
302
+ const beforeDate = new Date();
303
+
304
+ const result = await model.doGenerate({
305
+ prompt,
306
+ files: undefined,
307
+ mask: undefined,
308
+ n: 2,
309
+ size: undefined,
310
+ aspectRatio: undefined,
311
+ seed: undefined,
312
+ providerOptions: {},
313
+ });
314
+
315
+ const afterDate = new Date();
316
+
317
+ expect(result.response.timestamp.getTime()).toBeGreaterThanOrEqual(
318
+ beforeDate.getTime(),
319
+ );
320
+ expect(result.response.timestamp.getTime()).toBeLessThanOrEqual(
321
+ afterDate.getTime(),
322
+ );
323
+ expect(result.response.modelId).toBe('imagen-3.0-generate-002');
324
+ });
325
+
326
+ it('should only pass valid provider options', async () => {
327
+ prepareJsonResponse();
328
+
329
+ await model.doGenerate({
330
+ prompt,
331
+ files: undefined,
332
+ mask: undefined,
333
+ n: 2,
334
+ size: undefined,
335
+ aspectRatio: '16:9',
336
+ seed: undefined,
337
+ providerOptions: {
338
+ google: {
339
+ addWatermark: false,
340
+ personGeneration: 'allow_all',
341
+ foo: 'bar',
342
+ negativePrompt: 'negative prompt',
343
+ },
344
+ },
345
+ });
346
+
347
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
348
+ {
349
+ "instances": [
350
+ {
351
+ "prompt": "A cute baby sea otter",
352
+ },
353
+ ],
354
+ "parameters": {
355
+ "aspectRatio": "16:9",
356
+ "personGeneration": "allow_all",
357
+ "sampleCount": 2,
358
+ },
359
+ }
360
+ `);
361
+ });
362
+ });
363
+
364
+ describe('Image Editing (Not Supported)', () => {
365
+ it('should throw error when files are provided', async () => {
366
+ await expect(
367
+ model.doGenerate({
368
+ prompt: 'Edit this image',
369
+ files: [
370
+ {
371
+ type: 'file',
372
+ data: 'base64-source-image',
373
+ mediaType: 'image/png',
374
+ },
375
+ ],
376
+ mask: undefined,
377
+ n: 1,
378
+ size: undefined,
379
+ aspectRatio: undefined,
380
+ seed: undefined,
381
+ providerOptions: {},
382
+ }),
383
+ ).rejects.toThrow(
384
+ 'Google Generative AI does not support image editing. ' +
385
+ 'Use Google Vertex AI (@ai-sdk/google-vertex) for image editing capabilities.',
386
+ );
387
+ });
388
+
389
+ it('should throw error when mask is provided', async () => {
390
+ await expect(
391
+ model.doGenerate({
392
+ prompt: 'Edit this image',
393
+ files: undefined,
394
+ mask: {
395
+ type: 'file',
396
+ data: 'base64-mask-image',
397
+ mediaType: 'image/png',
398
+ },
399
+ n: 1,
400
+ size: undefined,
401
+ aspectRatio: undefined,
402
+ seed: undefined,
403
+ providerOptions: {},
404
+ }),
405
+ ).rejects.toThrow(
406
+ 'Google Generative AI does not support image editing with masks. ' +
407
+ 'Use Google Vertex AI (@ai-sdk/google-vertex) for image editing capabilities.',
408
+ );
409
+ });
410
+ });
411
+ });
@@ -0,0 +1,184 @@
1
+ import { ImageModelV3, SharedV3Warning } from '@ai-sdk/provider';
2
+ import {
3
+ combineHeaders,
4
+ createJsonResponseHandler,
5
+ type InferSchema,
6
+ lazySchema,
7
+ parseProviderOptions,
8
+ postJsonToApi,
9
+ resolve,
10
+ zodSchema,
11
+ } from '@ai-sdk/provider-utils';
12
+ import { z } from 'zod/v4';
13
+ import { googleFailedResponseHandler } from './google-error';
14
+ import {
15
+ GoogleGenerativeAIImageModelId,
16
+ GoogleGenerativeAIImageSettings,
17
+ } from './google-generative-ai-image-settings';
18
+ import { FetchFunction, Resolvable } from '@ai-sdk/provider-utils';
19
+
20
+ interface GoogleGenerativeAIImageModelConfig {
21
+ provider: string;
22
+ baseURL: string;
23
+ headers?: Resolvable<Record<string, string | undefined>>;
24
+ fetch?: FetchFunction;
25
+ generateId?: () => string;
26
+ _internal?: {
27
+ currentDate?: () => Date;
28
+ };
29
+ }
30
+
31
+ export class GoogleGenerativeAIImageModel implements ImageModelV3 {
32
+ readonly specificationVersion = 'v3';
33
+
34
+ get maxImagesPerCall(): number {
35
+ // https://ai.google.dev/gemini-api/docs/imagen#imagen-model
36
+ return this.settings.maxImagesPerCall ?? 4;
37
+ }
38
+
39
+ get provider(): string {
40
+ return this.config.provider;
41
+ }
42
+
43
+ constructor(
44
+ readonly modelId: GoogleGenerativeAIImageModelId,
45
+ private readonly settings: GoogleGenerativeAIImageSettings,
46
+ private readonly config: GoogleGenerativeAIImageModelConfig,
47
+ ) {}
48
+
49
+ async doGenerate(
50
+ options: Parameters<ImageModelV3['doGenerate']>[0],
51
+ ): Promise<Awaited<ReturnType<ImageModelV3['doGenerate']>>> {
52
+ const {
53
+ prompt,
54
+ n = 1,
55
+ size,
56
+ aspectRatio = '1:1',
57
+ seed,
58
+ providerOptions,
59
+ headers,
60
+ abortSignal,
61
+ files,
62
+ mask,
63
+ } = options;
64
+ const warnings: Array<SharedV3Warning> = [];
65
+
66
+ // Google Generative AI does not support image editing
67
+ if (files != null && files.length > 0) {
68
+ throw new Error(
69
+ 'Google Generative AI does not support image editing. ' +
70
+ 'Use Google Vertex AI (@ai-sdk/google-vertex) for image editing capabilities.',
71
+ );
72
+ }
73
+
74
+ if (mask != null) {
75
+ throw new Error(
76
+ 'Google Generative AI does not support image editing with masks. ' +
77
+ 'Use Google Vertex AI (@ai-sdk/google-vertex) for image editing capabilities.',
78
+ );
79
+ }
80
+
81
+ if (size != null) {
82
+ warnings.push({
83
+ type: 'unsupported',
84
+ feature: 'size',
85
+ details:
86
+ 'This model does not support the `size` option. Use `aspectRatio` instead.',
87
+ });
88
+ }
89
+
90
+ if (seed != null) {
91
+ warnings.push({
92
+ type: 'unsupported',
93
+ feature: 'seed',
94
+ details:
95
+ 'This model does not support the `seed` option through this provider.',
96
+ });
97
+ }
98
+
99
+ const googleOptions = await parseProviderOptions({
100
+ provider: 'google',
101
+ providerOptions,
102
+ schema: googleImageProviderOptionsSchema,
103
+ });
104
+
105
+ const currentDate = this.config._internal?.currentDate?.() ?? new Date();
106
+
107
+ const parameters: Record<string, unknown> = {
108
+ sampleCount: n,
109
+ };
110
+
111
+ if (aspectRatio != null) {
112
+ parameters.aspectRatio = aspectRatio;
113
+ }
114
+
115
+ if (googleOptions) {
116
+ Object.assign(parameters, googleOptions);
117
+ }
118
+
119
+ const body = {
120
+ instances: [{ prompt }],
121
+ parameters,
122
+ };
123
+
124
+ const { responseHeaders, value: response } = await postJsonToApi<{
125
+ predictions: Array<{ bytesBase64Encoded: string }>;
126
+ }>({
127
+ url: `${this.config.baseURL}/models/${this.modelId}:predict`,
128
+ headers: combineHeaders(await resolve(this.config.headers), headers),
129
+ body,
130
+ failedResponseHandler: googleFailedResponseHandler,
131
+ successfulResponseHandler: createJsonResponseHandler(
132
+ googleImageResponseSchema,
133
+ ),
134
+ abortSignal,
135
+ fetch: this.config.fetch,
136
+ });
137
+ return {
138
+ images: response.predictions.map(
139
+ (p: { bytesBase64Encoded: string }) => p.bytesBase64Encoded,
140
+ ),
141
+ warnings: warnings ?? [],
142
+ providerMetadata: {
143
+ google: {
144
+ images: response.predictions.map(prediction => ({
145
+ // Add any prediction-specific metadata here
146
+ })),
147
+ },
148
+ },
149
+ response: {
150
+ timestamp: currentDate,
151
+ modelId: this.modelId,
152
+ headers: responseHeaders,
153
+ },
154
+ };
155
+ }
156
+ }
157
+
158
+ // minimal version of the schema
159
+ const googleImageResponseSchema = lazySchema(() =>
160
+ zodSchema(
161
+ z.object({
162
+ predictions: z
163
+ .array(z.object({ bytesBase64Encoded: z.string() }))
164
+ .default([]),
165
+ }),
166
+ ),
167
+ );
168
+
169
+ // Note: For the initial GA launch of Imagen 3, safety filters are not configurable.
170
+ // https://ai.google.dev/gemini-api/docs/imagen#imagen-model
171
+ const googleImageProviderOptionsSchema = lazySchema(() =>
172
+ zodSchema(
173
+ z.object({
174
+ personGeneration: z
175
+ .enum(['dont_allow', 'allow_adult', 'allow_all'])
176
+ .nullish(),
177
+ aspectRatio: z.enum(['1:1', '3:4', '4:3', '9:16', '16:9']).nullish(),
178
+ }),
179
+ ),
180
+ );
181
+
182
+ export type GoogleGenerativeAIImageProviderOptions = InferSchema<
183
+ typeof googleImageProviderOptionsSchema
184
+ >;
@@ -0,0 +1,12 @@
1
+ export type GoogleGenerativeAIImageModelId =
2
+ | 'imagen-4.0-generate-001'
3
+ | 'imagen-4.0-ultra-generate-001'
4
+ | 'imagen-4.0-fast-generate-001'
5
+ | (string & {});
6
+
7
+ export interface GoogleGenerativeAIImageSettings {
8
+ /**
9
+ Override the maximum number of images per call (default 4)
10
+ */
11
+ maxImagesPerCall?: number;
12
+ }