cloudassist-ai-provider 0.0.1 → 0.0.2

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 (112) hide show
  1. package/dist/index.d.ts +326 -9
  2. package/dist/index.js +2187 -2
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +86 -34
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/internal/index.d.ts +262 -4
  7. package/dist/internal/index.js +1850 -2
  8. package/dist/internal/index.js.map +1 -1
  9. package/dist/internal/index.mjs +79 -29
  10. package/dist/internal/index.mjs.map +1 -1
  11. package/package.json +1 -1
  12. package/dist/convert-json-schema-to-openapi-schema.d.ts +0 -6
  13. package/dist/convert-json-schema-to-openapi-schema.d.ts.map +0 -1
  14. package/dist/convert-json-schema-to-openapi-schema.js +0 -108
  15. package/dist/convert-json-schema-to-openapi-schema.test.d.ts +0 -2
  16. package/dist/convert-json-schema-to-openapi-schema.test.d.ts.map +0 -1
  17. package/dist/convert-json-schema-to-openapi-schema.test.js +0 -630
  18. package/dist/convert-to-google-generative-ai-messages.d.ts +0 -7
  19. package/dist/convert-to-google-generative-ai-messages.d.ts.map +0 -1
  20. package/dist/convert-to-google-generative-ai-messages.js +0 -195
  21. package/dist/convert-to-google-generative-ai-messages.test.d.ts +0 -2
  22. package/dist/convert-to-google-generative-ai-messages.test.d.ts.map +0 -1
  23. package/dist/convert-to-google-generative-ai-messages.test.js +0 -456
  24. package/dist/get-model-path.d.ts +0 -2
  25. package/dist/get-model-path.d.ts.map +0 -1
  26. package/dist/get-model-path.js +0 -3
  27. package/dist/get-model-path.test.d.ts +0 -2
  28. package/dist/get-model-path.test.d.ts.map +0 -1
  29. package/dist/get-model-path.test.js +0 -11
  30. package/dist/google-error.d.ts +0 -12
  31. package/dist/google-error.d.ts.map +0 -1
  32. package/dist/google-error.js +0 -13
  33. package/dist/google-generative-ai-embedding-model.d.ts +0 -21
  34. package/dist/google-generative-ai-embedding-model.d.ts.map +0 -1
  35. package/dist/google-generative-ai-embedding-model.js +0 -88
  36. package/dist/google-generative-ai-embedding-model.test.d.ts +0 -2
  37. package/dist/google-generative-ai-embedding-model.test.d.ts.map +0 -1
  38. package/dist/google-generative-ai-embedding-model.test.js +0 -148
  39. package/dist/google-generative-ai-embedding-options.d.ts +0 -8
  40. package/dist/google-generative-ai-embedding-options.d.ts.map +0 -1
  41. package/dist/google-generative-ai-embedding-options.js +0 -33
  42. package/dist/google-generative-ai-image-model.d.ts +0 -31
  43. package/dist/google-generative-ai-image-model.d.ts.map +0 -1
  44. package/dist/google-generative-ai-image-model.js +0 -96
  45. package/dist/google-generative-ai-image-model.test.d.ts +0 -2
  46. package/dist/google-generative-ai-image-model.test.d.ts.map +0 -1
  47. package/dist/google-generative-ai-image-model.test.js +0 -252
  48. package/dist/google-generative-ai-image-settings.d.ts +0 -8
  49. package/dist/google-generative-ai-image-settings.d.ts.map +0 -1
  50. package/dist/google-generative-ai-image-settings.js +0 -1
  51. package/dist/google-generative-ai-language-model.d.ts +0 -183
  52. package/dist/google-generative-ai-language-model.d.ts.map +0 -1
  53. package/dist/google-generative-ai-language-model.js +0 -1001
  54. package/dist/google-generative-ai-language-model.test.d.ts +0 -2
  55. package/dist/google-generative-ai-language-model.test.d.ts.map +0 -1
  56. package/dist/google-generative-ai-language-model.test.js +0 -3463
  57. package/dist/google-generative-ai-options.d.ts +0 -37
  58. package/dist/google-generative-ai-options.d.ts.map +0 -1
  59. package/dist/google-generative-ai-options.js +0 -149
  60. package/dist/google-generative-ai-prompt.d.ts +0 -52
  61. package/dist/google-generative-ai-prompt.d.ts.map +0 -1
  62. package/dist/google-generative-ai-prompt.js +0 -1
  63. package/dist/google-prepare-tools.d.ts +0 -27
  64. package/dist/google-prepare-tools.d.ts.map +0 -1
  65. package/dist/google-prepare-tools.js +0 -219
  66. package/dist/google-prepare-tools.test.d.ts +0 -2
  67. package/dist/google-prepare-tools.test.d.ts.map +0 -1
  68. package/dist/google-prepare-tools.test.js +0 -447
  69. package/dist/google-provider.d.ts +0 -65
  70. package/dist/google-provider.d.ts.map +0 -1
  71. package/dist/google-provider.js +0 -74
  72. package/dist/google-provider.test.d.ts +0 -2
  73. package/dist/google-provider.test.d.ts.map +0 -1
  74. package/dist/google-provider.test.js +0 -234
  75. package/dist/google-supported-file-url.d.ts +0 -2
  76. package/dist/google-supported-file-url.d.ts.map +0 -1
  77. package/dist/google-supported-file-url.js +0 -13
  78. package/dist/google-supported-file-url.test.d.ts +0 -2
  79. package/dist/google-supported-file-url.test.d.ts.map +0 -1
  80. package/dist/google-supported-file-url.test.js +0 -45
  81. package/dist/google-tools.d.ts +0 -76
  82. package/dist/google-tools.d.ts.map +0 -1
  83. package/dist/google-tools.js +0 -65
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/internal/index.d.ts.map +0 -1
  86. package/dist/map-google-generative-ai-finish-reason.d.ts +0 -6
  87. package/dist/map-google-generative-ai-finish-reason.d.ts.map +0 -1
  88. package/dist/map-google-generative-ai-finish-reason.js +0 -22
  89. package/dist/tool/code-execution.d.ts +0 -17
  90. package/dist/tool/code-execution.d.ts.map +0 -1
  91. package/dist/tool/code-execution.js +0 -25
  92. package/dist/tool/enterprise-web-search.d.ts +0 -2
  93. package/dist/tool/enterprise-web-search.d.ts.map +0 -1
  94. package/dist/tool/enterprise-web-search.js +0 -8
  95. package/dist/tool/file-search.d.ts +0 -16
  96. package/dist/tool/file-search.d.ts.map +0 -1
  97. package/dist/tool/file-search.js +0 -33
  98. package/dist/tool/google-maps.d.ts +0 -2
  99. package/dist/tool/google-maps.d.ts.map +0 -1
  100. package/dist/tool/google-maps.js +0 -9
  101. package/dist/tool/google-search.d.ts +0 -14
  102. package/dist/tool/google-search.d.ts.map +0 -1
  103. package/dist/tool/google-search.js +0 -15
  104. package/dist/tool/url-context.d.ts +0 -2
  105. package/dist/tool/url-context.d.ts.map +0 -1
  106. package/dist/tool/url-context.js +0 -7
  107. package/dist/tool/vertex-rag-store.d.ts +0 -16
  108. package/dist/tool/vertex-rag-store.d.ts.map +0 -1
  109. package/dist/tool/vertex-rag-store.js +0 -17
  110. package/dist/version.d.ts +0 -2
  111. package/dist/version.d.ts.map +0 -1
  112. package/dist/version.js +0 -3
@@ -1,3463 +0,0 @@
1
- import { createTestServer } from '@ai-sdk/test-server/with-vitest';
2
- import { convertReadableStreamToArray } from '@ai-sdk/provider-utils/test';
3
- import { GoogleGenerativeAILanguageModel, getGroundingMetadataSchema, getUrlContextMetadataSchema, } from './google-generative-ai-language-model';
4
- import { createGoogleGenerativeAI } from './google-provider';
5
- import { describe, it, expect, vi } from 'vitest';
6
- vi.mock('./version', () => ({
7
- VERSION: '0.0.0-test',
8
- }));
9
- const TEST_PROMPT = [
10
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
11
- ];
12
- const SAFETY_RATINGS = [
13
- {
14
- category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
15
- probability: 'NEGLIGIBLE',
16
- },
17
- {
18
- category: 'HARM_CATEGORY_HATE_SPEECH',
19
- probability: 'NEGLIGIBLE',
20
- },
21
- {
22
- category: 'HARM_CATEGORY_HARASSMENT',
23
- probability: 'NEGLIGIBLE',
24
- },
25
- {
26
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
27
- probability: 'NEGLIGIBLE',
28
- },
29
- ];
30
- const provider = createGoogleGenerativeAI({
31
- apiKey: 'test-api-key',
32
- generateId: () => 'test-id',
33
- });
34
- const model = provider.chat('gemini-pro');
35
- const groundingMetadataSchema = getGroundingMetadataSchema();
36
- const urlContextMetadataSchema = getUrlContextMetadataSchema();
37
- describe('groundingMetadataSchema', () => {
38
- it('validates complete grounding metadata with web search results', () => {
39
- const metadata = {
40
- webSearchQueries: ["What's the weather in Chicago this weekend?"],
41
- searchEntryPoint: {
42
- renderedContent: 'Sample rendered content for search results',
43
- },
44
- groundingChunks: [
45
- {
46
- web: {
47
- uri: 'https://example.com/weather',
48
- title: 'Chicago Weather Forecast',
49
- },
50
- },
51
- ],
52
- groundingSupports: [
53
- {
54
- segment: {
55
- startIndex: 0,
56
- endIndex: 65,
57
- text: 'Chicago weather changes rapidly, so layers let you adjust easily.',
58
- },
59
- groundingChunkIndices: [0],
60
- confidenceScores: [0.99],
61
- },
62
- ],
63
- retrievalMetadata: {
64
- webDynamicRetrievalScore: 0.96879,
65
- },
66
- };
67
- const result = groundingMetadataSchema.safeParse(metadata);
68
- expect(result.success).toBe(true);
69
- });
70
- it('validates groundingChunks[].web with missing title', () => {
71
- const metadata = {
72
- groundingChunks: [
73
- {
74
- web: {
75
- // Missing `title`
76
- uri: 'https://example.com/weather',
77
- },
78
- },
79
- ],
80
- };
81
- const result = groundingMetadataSchema.safeParse(metadata);
82
- expect(result.success).toBe(true);
83
- });
84
- it('validates complete grounding metadata with Vertex AI Search results', () => {
85
- const metadata = {
86
- retrievalQueries: ['How to make appointment to renew driving license?'],
87
- groundingChunks: [
88
- {
89
- retrievedContext: {
90
- uri: 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AXiHM.....QTN92V5ePQ==',
91
- title: 'dmv',
92
- },
93
- },
94
- ],
95
- groundingSupports: [
96
- {
97
- segment: {
98
- startIndex: 25,
99
- endIndex: 147,
100
- },
101
- segment_text: 'ipsum lorem ...',
102
- supportChunkIndices: [1, 2],
103
- confidenceScore: [0.9541752, 0.97726375],
104
- },
105
- ],
106
- };
107
- const result = groundingMetadataSchema.safeParse(metadata);
108
- expect(result.success).toBe(true);
109
- });
110
- it('validates groundingChunks[].retrievedContext with missing title', () => {
111
- const metadata = {
112
- groundingChunks: [
113
- {
114
- retrievedContext: {
115
- // Missing `title`
116
- uri: 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AXiHM.....QTN92V5ePQ==',
117
- },
118
- },
119
- ],
120
- };
121
- const result = groundingMetadataSchema.safeParse(metadata);
122
- expect(result.success).toBe(true);
123
- });
124
- it('validates groundingChunks[].retrievedContext with fileSearchStore (new format)', () => {
125
- const metadata = {
126
- groundingChunks: [
127
- {
128
- retrievedContext: {
129
- text: 'Sample content for testing...',
130
- fileSearchStore: 'fileSearchStores/test-store-xyz',
131
- title: 'Test Document',
132
- },
133
- },
134
- ],
135
- };
136
- const result = groundingMetadataSchema.safeParse(metadata);
137
- expect(result.success).toBe(true);
138
- });
139
- it('validates groundingChunks[].retrievedContext with fileSearchStore and missing uri', () => {
140
- const metadata = {
141
- groundingChunks: [
142
- {
143
- retrievedContext: {
144
- text: 'Content without uri field',
145
- fileSearchStore: 'fileSearchStores/store-abc',
146
- // Missing `uri` - should still be valid
147
- },
148
- },
149
- ],
150
- };
151
- const result = groundingMetadataSchema.safeParse(metadata);
152
- expect(result.success).toBe(true);
153
- });
154
- it('validates partial grounding metadata', () => {
155
- const metadata = {
156
- webSearchQueries: ['sample query'],
157
- // Missing other optional fields
158
- };
159
- const result = groundingMetadataSchema.safeParse(metadata);
160
- expect(result.success).toBe(true);
161
- });
162
- it('validates empty grounding metadata', () => {
163
- const metadata = {};
164
- const result = groundingMetadataSchema.safeParse(metadata);
165
- expect(result.success).toBe(true);
166
- });
167
- it('validates grounding metadata with maps chunks', () => {
168
- const metadata = {
169
- groundingChunks: [
170
- {
171
- maps: {
172
- uri: 'https://maps.google.com/maps?cid=12345',
173
- title: 'Best Italian Restaurant',
174
- text: 'A great Italian restaurant',
175
- placeId: 'ChIJ12345',
176
- },
177
- },
178
- ],
179
- };
180
- const result = groundingMetadataSchema.safeParse(metadata);
181
- expect(result.success).toBe(true);
182
- });
183
- it('validates groundingChunks[].maps with missing optional fields', () => {
184
- const metadata = {
185
- groundingChunks: [
186
- {
187
- maps: {
188
- uri: 'https://maps.google.com/maps?cid=12345',
189
- },
190
- },
191
- ],
192
- };
193
- const result = groundingMetadataSchema.safeParse(metadata);
194
- expect(result.success).toBe(true);
195
- });
196
- it('validates metadata with empty retrievalMetadata', () => {
197
- const metadata = {
198
- webSearchQueries: ['sample query'],
199
- retrievalMetadata: {},
200
- };
201
- const result = groundingMetadataSchema.safeParse(metadata);
202
- expect(result.success).toBe(true);
203
- });
204
- it('rejects invalid data types', () => {
205
- const metadata = {
206
- webSearchQueries: 'not an array', // Should be an array
207
- groundingSupports: [
208
- {
209
- confidenceScores: 'not an array', // Should be an array of numbers
210
- },
211
- ],
212
- };
213
- const result = groundingMetadataSchema.safeParse(metadata);
214
- expect(result.success).toBe(false);
215
- });
216
- });
217
- describe('urlContextMetadata', () => {
218
- it('validates complete url context output', () => {
219
- const output = {
220
- urlMetadata: [
221
- {
222
- retrievedUrl: 'https://example.com/weather',
223
- urlRetrievalStatus: 'URL_RETRIEVAL_STATUS_SUCCESS',
224
- },
225
- ],
226
- };
227
- const result = urlContextMetadataSchema.safeParse(output);
228
- expect(result.success).toBe(true);
229
- });
230
- it('validates empty url context output', () => {
231
- const output = {
232
- urlMetadata: [],
233
- };
234
- const result = urlContextMetadataSchema.safeParse(output);
235
- expect(result.success).toBe(true);
236
- });
237
- });
238
- describe('doGenerate', () => {
239
- const TEST_URL_GEMINI_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
240
- const TEST_URL_GEMINI_2_0_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
241
- const TEST_URL_GEMINI_2_0_FLASH_EXP = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
242
- const TEST_URL_GEMINI_1_0_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
243
- const TEST_URL_GEMINI_1_5_FLASH = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
244
- const server = createTestServer({
245
- [TEST_URL_GEMINI_PRO]: {},
246
- });
247
- const getRequestBody = async (index = 0) => {
248
- var _a;
249
- const body = await server.calls[index].requestBodyJson;
250
- return (_a = body === null || body === void 0 ? void 0 : body.request) !== null && _a !== void 0 ? _a : body;
251
- };
252
- const prepareJsonResponse = ({ content = '', usage = {
253
- promptTokenCount: 1,
254
- candidatesTokenCount: 2,
255
- totalTokenCount: 3,
256
- }, headers, groundingMetadata, url = TEST_URL_GEMINI_PRO, }) => {
257
- const responseBody = {
258
- candidates: [
259
- {
260
- content: {
261
- parts: [{ text: content }],
262
- role: 'model',
263
- },
264
- finishReason: 'STOP',
265
- index: 0,
266
- safetyRatings: SAFETY_RATINGS,
267
- ...(groundingMetadata && { groundingMetadata }),
268
- },
269
- ],
270
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
271
- usageMetadata: usage,
272
- };
273
- server.urls[url].response = {
274
- type: 'stream-chunks',
275
- headers: { ...headers, 'content-type': 'text/event-stream' },
276
- chunks: ['data: ' + JSON.stringify(responseBody) + '\n\n'],
277
- };
278
- };
279
- it('should extract text response', async () => {
280
- prepareJsonResponse({ content: 'Hello, World!' });
281
- const { content } = await model.doGenerate({
282
- prompt: TEST_PROMPT,
283
- });
284
- expect(content).toMatchInlineSnapshot(`
285
- [
286
- {
287
- "providerMetadata": undefined,
288
- "text": "Hello, World!",
289
- "type": "text",
290
- },
291
- ]
292
- `);
293
- });
294
- it('should extract usage', async () => {
295
- prepareJsonResponse({
296
- usage: {
297
- promptTokenCount: 20,
298
- candidatesTokenCount: 5,
299
- totalTokenCount: 25,
300
- },
301
- });
302
- const { usage } = await model.doGenerate({
303
- prompt: TEST_PROMPT,
304
- });
305
- expect(usage).toMatchInlineSnapshot(`
306
- {
307
- "cachedInputTokens": undefined,
308
- "inputTokens": 20,
309
- "outputTokens": 5,
310
- "reasoningTokens": undefined,
311
- "totalTokens": 25,
312
- }
313
- `);
314
- });
315
- it('should handle MALFORMED_FUNCTION_CALL finish reason and empty content object', async () => {
316
- server.urls[TEST_URL_GEMINI_PRO].response = {
317
- type: 'stream-chunks',
318
- headers: { 'content-type': 'text/event-stream' },
319
- chunks: ['data: ' + JSON.stringify({
320
- candidates: [
321
- {
322
- content: {},
323
- finishReason: 'MALFORMED_FUNCTION_CALL',
324
- },
325
- ],
326
- usageMetadata: {
327
- promptTokenCount: 9056,
328
- totalTokenCount: 9056,
329
- promptTokensDetails: [
330
- {
331
- modality: 'TEXT',
332
- tokenCount: 9056,
333
- },
334
- ],
335
- },
336
- modelVersion: 'gemini-2.0-flash-lite',
337
- }) + '\n\n'],
338
- };
339
- const { content, finishReason } = await model.doGenerate({
340
- prompt: TEST_PROMPT,
341
- });
342
- expect(content).toMatchInlineSnapshot(`[]`);
343
- expect(finishReason).toStrictEqual('error');
344
- });
345
- it('should extract tool calls', async () => {
346
- server.urls[TEST_URL_GEMINI_PRO].response = {
347
- type: 'stream-chunks',
348
- headers: { 'content-type': 'text/event-stream' },
349
- chunks: ['data: ' + JSON.stringify({
350
- candidates: [
351
- {
352
- content: {
353
- parts: [
354
- {
355
- functionCall: {
356
- name: 'test-tool',
357
- args: { value: 'example value' },
358
- },
359
- },
360
- ],
361
- role: 'model',
362
- },
363
- finishReason: 'STOP',
364
- index: 0,
365
- safetyRatings: SAFETY_RATINGS,
366
- },
367
- ],
368
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
369
- }) + '\n\n'],
370
- };
371
- const { content, finishReason } = await model.doGenerate({
372
- tools: [
373
- {
374
- type: 'function',
375
- name: 'test-tool',
376
- inputSchema: {
377
- type: 'object',
378
- properties: { value: { type: 'string' } },
379
- required: ['value'],
380
- additionalProperties: false,
381
- $schema: 'http://json-schema.org/draft-07/schema#',
382
- },
383
- },
384
- ],
385
- prompt: TEST_PROMPT,
386
- });
387
- expect(content).toMatchInlineSnapshot(`
388
- [
389
- {
390
- "input": "{"value":"example value"}",
391
- "providerMetadata": undefined,
392
- "toolCallId": "test-id",
393
- "toolName": "test-tool",
394
- "type": "tool-call",
395
- },
396
- ]
397
- `);
398
- expect(finishReason).toStrictEqual('tool-calls');
399
- });
400
- it('should expose the raw response headers', async () => {
401
- prepareJsonResponse({ headers: { 'test-header': 'test-value' } });
402
- const { response } = await model.doGenerate({
403
- prompt: TEST_PROMPT,
404
- });
405
- expect(response === null || response === void 0 ? void 0 : response.headers).toMatchObject({
406
- 'content-type': expect.stringContaining('text/event-stream'),
407
- 'test-header': 'test-value',
408
- });
409
- });
410
- it('should pass the model, messages, and options', async () => {
411
- prepareJsonResponse({});
412
- await model.doGenerate({
413
- prompt: [
414
- { role: 'system', content: 'test system instruction' },
415
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
416
- ],
417
- seed: 123,
418
- temperature: 0.5,
419
- });
420
- expect(await getRequestBody(0)).toStrictEqual({
421
- contents: [
422
- {
423
- role: 'user',
424
- parts: [{ text: 'Hello' }],
425
- },
426
- ],
427
- systemInstruction: { parts: [{ text: 'test system instruction' }] },
428
- generationConfig: {
429
- seed: 123,
430
- temperature: 0.5,
431
- },
432
- });
433
- });
434
- it('should only pass valid provider options', async () => {
435
- prepareJsonResponse({});
436
- await model.doGenerate({
437
- prompt: [
438
- { role: 'system', content: 'test system instruction' },
439
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
440
- ],
441
- seed: 123,
442
- temperature: 0.5,
443
- providerOptions: {
444
- google: { foo: 'bar', responseModalities: ['TEXT', 'IMAGE'] },
445
- },
446
- });
447
- expect(await getRequestBody(0)).toStrictEqual({
448
- contents: [
449
- {
450
- role: 'user',
451
- parts: [{ text: 'Hello' }],
452
- },
453
- ],
454
- systemInstruction: { parts: [{ text: 'test system instruction' }] },
455
- generationConfig: {
456
- seed: 123,
457
- temperature: 0.5,
458
- responseModalities: ['TEXT', 'IMAGE'],
459
- },
460
- });
461
- });
462
- it('should pass tools and toolChoice', async () => {
463
- prepareJsonResponse({});
464
- await model.doGenerate({
465
- tools: [
466
- {
467
- type: 'function',
468
- name: 'test-tool',
469
- inputSchema: {
470
- type: 'object',
471
- properties: { value: { type: 'string' } },
472
- required: ['value'],
473
- additionalProperties: false,
474
- $schema: 'http://json-schema.org/draft-07/schema#',
475
- },
476
- },
477
- ],
478
- toolChoice: {
479
- type: 'tool',
480
- toolName: 'test-tool',
481
- },
482
- prompt: TEST_PROMPT,
483
- });
484
- expect(await getRequestBody(0)).toStrictEqual({
485
- generationConfig: {},
486
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
487
- tools: [
488
- {
489
- functionDeclarations: [
490
- {
491
- name: 'test-tool',
492
- description: '',
493
- parameters: {
494
- type: 'object',
495
- properties: { value: { type: 'string' } },
496
- required: ['value'],
497
- },
498
- },
499
- ],
500
- },
501
- ],
502
- toolConfig: {
503
- functionCallingConfig: {
504
- mode: 'ANY',
505
- allowedFunctionNames: ['test-tool'],
506
- },
507
- },
508
- });
509
- });
510
- it('should set response mime type with responseFormat', async () => {
511
- prepareJsonResponse({});
512
- await model.doGenerate({
513
- responseFormat: {
514
- type: 'json',
515
- schema: {
516
- type: 'object',
517
- properties: { location: { type: 'string' } },
518
- },
519
- },
520
- prompt: TEST_PROMPT,
521
- });
522
- expect(await getRequestBody(0)).toStrictEqual({
523
- contents: [
524
- {
525
- role: 'user',
526
- parts: [{ text: 'Hello' }],
527
- },
528
- ],
529
- generationConfig: {
530
- responseMimeType: 'application/json',
531
- responseSchema: {
532
- type: 'object',
533
- properties: {
534
- location: {
535
- type: 'string',
536
- },
537
- },
538
- },
539
- },
540
- });
541
- });
542
- it('should pass specification with responseFormat and structuredOutputs = true (default)', async () => {
543
- prepareJsonResponse({});
544
- await provider.languageModel('gemini-pro').doGenerate({
545
- responseFormat: {
546
- type: 'json',
547
- schema: {
548
- type: 'object',
549
- properties: {
550
- property1: { type: 'string' },
551
- property2: { type: 'number' },
552
- },
553
- required: ['property1', 'property2'],
554
- additionalProperties: false,
555
- },
556
- },
557
- prompt: TEST_PROMPT,
558
- });
559
- expect(await getRequestBody(0)).toStrictEqual({
560
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
561
- generationConfig: {
562
- responseMimeType: 'application/json',
563
- responseSchema: {
564
- properties: {
565
- property1: { type: 'string' },
566
- property2: { type: 'number' },
567
- },
568
- required: ['property1', 'property2'],
569
- type: 'object',
570
- },
571
- },
572
- });
573
- });
574
- it('should not pass specification with responseFormat and structuredOutputs = false', async () => {
575
- prepareJsonResponse({});
576
- await provider.languageModel('gemini-pro').doGenerate({
577
- providerOptions: {
578
- google: {
579
- structuredOutputs: false,
580
- },
581
- },
582
- responseFormat: {
583
- type: 'json',
584
- schema: {
585
- type: 'object',
586
- properties: {
587
- property1: { type: 'string' },
588
- property2: { type: 'number' },
589
- },
590
- required: ['property1', 'property2'],
591
- additionalProperties: false,
592
- },
593
- },
594
- prompt: TEST_PROMPT,
595
- });
596
- expect(await getRequestBody(0)).toStrictEqual({
597
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
598
- generationConfig: {
599
- responseMimeType: 'application/json',
600
- },
601
- });
602
- });
603
- it('should pass tools and toolChoice', async () => {
604
- prepareJsonResponse({});
605
- await provider.languageModel('gemini-pro').doGenerate({
606
- tools: [
607
- {
608
- name: 'test-tool',
609
- type: 'function',
610
- inputSchema: {
611
- type: 'object',
612
- properties: {
613
- property1: { type: 'string' },
614
- property2: { type: 'number' },
615
- },
616
- required: ['property1', 'property2'],
617
- additionalProperties: false,
618
- },
619
- },
620
- ],
621
- toolChoice: { type: 'required' },
622
- prompt: TEST_PROMPT,
623
- });
624
- expect(await getRequestBody(0)).toStrictEqual({
625
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
626
- generationConfig: {},
627
- toolConfig: { functionCallingConfig: { mode: 'ANY' } },
628
- tools: [
629
- {
630
- functionDeclarations: [
631
- {
632
- name: 'test-tool',
633
- description: '',
634
- parameters: {
635
- properties: {
636
- property1: { type: 'string' },
637
- property2: { type: 'number' },
638
- },
639
- required: ['property1', 'property2'],
640
- type: 'object',
641
- },
642
- },
643
- ],
644
- },
645
- ],
646
- });
647
- });
648
- it('should pass headers', async () => {
649
- prepareJsonResponse({});
650
- const provider = createGoogleGenerativeAI({
651
- apiKey: 'test-api-key',
652
- headers: {
653
- 'Custom-Provider-Header': 'provider-header-value',
654
- },
655
- });
656
- await provider.chat('gemini-pro').doGenerate({
657
- prompt: TEST_PROMPT,
658
- headers: {
659
- 'Custom-Request-Header': 'request-header-value',
660
- },
661
- });
662
- const requestHeaders = server.calls[0].requestHeaders;
663
- expect(requestHeaders).toStrictEqual({
664
- 'authorization': 'Bearer test-api-key',
665
- 'content-type': 'application/json',
666
- 'custom-provider-header': 'provider-header-value',
667
- 'custom-request-header': 'request-header-value',
668
- });
669
- expect(server.calls[0].requestUserAgent).toContain(`ai-sdk/google-cloudassist/0.0.0-test`);
670
- });
671
- it('should pass response format', async () => {
672
- prepareJsonResponse({});
673
- await model.doGenerate({
674
- prompt: TEST_PROMPT,
675
- responseFormat: {
676
- type: 'json',
677
- schema: {
678
- type: 'object',
679
- properties: {
680
- text: { type: 'string' },
681
- },
682
- required: ['text'],
683
- },
684
- },
685
- });
686
- expect(await getRequestBody(0)).toStrictEqual({
687
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
688
- generationConfig: {
689
- responseMimeType: 'application/json',
690
- responseSchema: {
691
- type: 'object',
692
- properties: {
693
- text: { type: 'string' },
694
- },
695
- required: ['text'],
696
- },
697
- },
698
- });
699
- });
700
- it('should send request body', async () => {
701
- prepareJsonResponse({});
702
- await model.doGenerate({
703
- prompt: TEST_PROMPT,
704
- });
705
- expect(await getRequestBody(0)).toStrictEqual({
706
- contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
707
- generationConfig: {},
708
- });
709
- });
710
- it('should extract sources from grounding metadata', async () => {
711
- prepareJsonResponse({
712
- content: 'test response',
713
- groundingMetadata: {
714
- groundingChunks: [
715
- {
716
- web: { uri: 'https://source.example.com', title: 'Source Title' },
717
- },
718
- ],
719
- },
720
- });
721
- const { content } = await model.doGenerate({
722
- prompt: TEST_PROMPT,
723
- });
724
- expect(content).toMatchInlineSnapshot(`
725
- [
726
- {
727
- "providerMetadata": undefined,
728
- "text": "test response",
729
- "type": "text",
730
- },
731
- {
732
- "id": "test-id",
733
- "sourceType": "url",
734
- "title": "Source Title",
735
- "type": "source",
736
- "url": "https://source.example.com",
737
- },
738
- ]
739
- `);
740
- });
741
- it('should extract sources from RAG retrievedContext chunks', async () => {
742
- prepareJsonResponse({
743
- content: 'test response with RAG',
744
- groundingMetadata: {
745
- groundingChunks: [
746
- {
747
- web: { uri: 'https://web.example.com', title: 'Web Source' },
748
- },
749
- {
750
- retrievedContext: {
751
- uri: 'gs://rag-corpus/document.pdf',
752
- title: 'RAG Document',
753
- text: 'Retrieved context...',
754
- },
755
- },
756
- {
757
- retrievedContext: {
758
- uri: 'https://external-rag-source.com/page',
759
- title: 'External RAG Source',
760
- text: 'External retrieved context...',
761
- },
762
- },
763
- ],
764
- },
765
- });
766
- const { content } = await model.doGenerate({
767
- prompt: TEST_PROMPT,
768
- });
769
- expect(content).toMatchInlineSnapshot(`
770
- [
771
- {
772
- "providerMetadata": undefined,
773
- "text": "test response with RAG",
774
- "type": "text",
775
- },
776
- {
777
- "id": "test-id",
778
- "sourceType": "url",
779
- "title": "Web Source",
780
- "type": "source",
781
- "url": "https://web.example.com",
782
- },
783
- {
784
- "filename": "document.pdf",
785
- "id": "test-id",
786
- "mediaType": "application/pdf",
787
- "sourceType": "document",
788
- "title": "RAG Document",
789
- "type": "source",
790
- },
791
- {
792
- "id": "test-id",
793
- "sourceType": "url",
794
- "title": "External RAG Source",
795
- "type": "source",
796
- "url": "https://external-rag-source.com/page",
797
- },
798
- ]
799
- `);
800
- });
801
- it('should extract sources from File Search retrievedContext (new format)', async () => {
802
- prepareJsonResponse({
803
- content: 'test response with File Search',
804
- groundingMetadata: {
805
- groundingChunks: [
806
- {
807
- retrievedContext: {
808
- text: 'Sample content for testing...',
809
- fileSearchStore: 'fileSearchStores/test-store-xyz',
810
- title: 'Test Document',
811
- },
812
- },
813
- {
814
- retrievedContext: {
815
- text: 'Another document content...',
816
- fileSearchStore: 'fileSearchStores/another-store-abc',
817
- // Missing title - should default to 'Unknown Document'
818
- },
819
- },
820
- ],
821
- },
822
- });
823
- const { content } = await model.doGenerate({
824
- prompt: TEST_PROMPT,
825
- });
826
- expect(content).toMatchInlineSnapshot(`
827
- [
828
- {
829
- "providerMetadata": undefined,
830
- "text": "test response with File Search",
831
- "type": "text",
832
- },
833
- {
834
- "filename": "test-store-xyz",
835
- "id": "test-id",
836
- "mediaType": "application/octet-stream",
837
- "sourceType": "document",
838
- "title": "Test Document",
839
- "type": "source",
840
- },
841
- {
842
- "filename": "another-store-abc",
843
- "id": "test-id",
844
- "mediaType": "application/octet-stream",
845
- "sourceType": "document",
846
- "title": "Unknown Document",
847
- "type": "source",
848
- },
849
- ]
850
- `);
851
- });
852
- it('should handle URL sources with undefined title correctly', async () => {
853
- prepareJsonResponse({
854
- content: 'test response with URLs',
855
- groundingMetadata: {
856
- groundingChunks: [
857
- {
858
- web: {
859
- uri: 'https://example.com/page1',
860
- // No title provided
861
- },
862
- },
863
- {
864
- retrievedContext: {
865
- uri: 'https://example.com/page2',
866
- // No title provided
867
- },
868
- },
869
- ],
870
- },
871
- });
872
- const { content } = await model.doGenerate({
873
- prompt: TEST_PROMPT,
874
- });
875
- expect(content).toMatchInlineSnapshot(`
876
- [
877
- {
878
- "providerMetadata": undefined,
879
- "text": "test response with URLs",
880
- "type": "text",
881
- },
882
- {
883
- "id": "test-id",
884
- "sourceType": "url",
885
- "title": undefined,
886
- "type": "source",
887
- "url": "https://example.com/page1",
888
- },
889
- {
890
- "id": "test-id",
891
- "sourceType": "url",
892
- "title": undefined,
893
- "type": "source",
894
- "url": "https://example.com/page2",
895
- },
896
- ]
897
- `);
898
- });
899
- it('should extract sources from maps grounding metadata', async () => {
900
- prepareJsonResponse({
901
- content: 'test response with Maps',
902
- groundingMetadata: {
903
- groundingChunks: [
904
- {
905
- maps: {
906
- uri: 'https://maps.google.com/maps?cid=12345',
907
- title: 'Best Italian Restaurant',
908
- placeId: 'ChIJ12345',
909
- },
910
- },
911
- {
912
- maps: {
913
- uri: 'https://maps.google.com/maps?cid=67890',
914
- },
915
- },
916
- ],
917
- },
918
- });
919
- const { content } = await model.doGenerate({
920
- prompt: TEST_PROMPT,
921
- });
922
- expect(content).toMatchInlineSnapshot(`
923
- [
924
- {
925
- "providerMetadata": undefined,
926
- "text": "test response with Maps",
927
- "type": "text",
928
- },
929
- {
930
- "id": "test-id",
931
- "sourceType": "url",
932
- "title": "Best Italian Restaurant",
933
- "type": "source",
934
- "url": "https://maps.google.com/maps?cid=12345",
935
- },
936
- {
937
- "id": "test-id",
938
- "sourceType": "url",
939
- "title": undefined,
940
- "type": "source",
941
- "url": "https://maps.google.com/maps?cid=67890",
942
- },
943
- ]
944
- `);
945
- });
946
- it('should handle mixed source types with correct title defaults', async () => {
947
- prepareJsonResponse({
948
- content: 'test response with mixed sources',
949
- groundingMetadata: {
950
- groundingChunks: [
951
- {
952
- web: { uri: 'https://web.example.com' },
953
- },
954
- {
955
- retrievedContext: {
956
- uri: 'https://external.example.com',
957
- },
958
- },
959
- {
960
- retrievedContext: {
961
- uri: 'gs://bucket/document.pdf',
962
- },
963
- },
964
- {
965
- retrievedContext: {
966
- fileSearchStore: 'fileSearchStores/store-123',
967
- },
968
- },
969
- ],
970
- },
971
- });
972
- const { content } = await model.doGenerate({
973
- prompt: TEST_PROMPT,
974
- });
975
- expect(content).toMatchInlineSnapshot(`
976
- [
977
- {
978
- "providerMetadata": undefined,
979
- "text": "test response with mixed sources",
980
- "type": "text",
981
- },
982
- {
983
- "id": "test-id",
984
- "sourceType": "url",
985
- "title": undefined,
986
- "type": "source",
987
- "url": "https://web.example.com",
988
- },
989
- {
990
- "id": "test-id",
991
- "sourceType": "url",
992
- "title": undefined,
993
- "type": "source",
994
- "url": "https://external.example.com",
995
- },
996
- {
997
- "filename": "document.pdf",
998
- "id": "test-id",
999
- "mediaType": "application/pdf",
1000
- "sourceType": "document",
1001
- "title": "Unknown Document",
1002
- "type": "source",
1003
- },
1004
- {
1005
- "filename": "store-123",
1006
- "id": "test-id",
1007
- "mediaType": "application/octet-stream",
1008
- "sourceType": "document",
1009
- "title": "Unknown Document",
1010
- "type": "source",
1011
- },
1012
- ]
1013
- `);
1014
- });
1015
- describe('async headers handling', () => {
1016
- it('merges async config headers with sync request headers', async () => {
1017
- server.urls[TEST_URL_GEMINI_PRO].response = {
1018
- type: 'stream-chunks',
1019
- headers: { 'content-type': 'text/event-stream' },
1020
- chunks: ['data: ' + JSON.stringify({
1021
- candidates: [
1022
- {
1023
- content: {
1024
- parts: [{ text: '' }],
1025
- role: 'model',
1026
- },
1027
- finishReason: 'STOP',
1028
- index: 0,
1029
- safetyRatings: SAFETY_RATINGS,
1030
- },
1031
- ],
1032
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1033
- usageMetadata: {
1034
- promptTokenCount: 1,
1035
- candidatesTokenCount: 2,
1036
- totalTokenCount: 3,
1037
- },
1038
- }) + '\n\n'],
1039
- };
1040
- const model = new GoogleGenerativeAILanguageModel('gemini-pro', {
1041
- provider: 'google.generative-ai',
1042
- baseURL: 'https://cloudcode-pa.googleapis.com',
1043
- headers: async () => ({
1044
- 'X-Async-Config': 'async-config-value',
1045
- 'X-Common': 'config-value',
1046
- }),
1047
- generateId: () => 'test-id',
1048
- supportedUrls: () => ({
1049
- '*': [/^https?:\/\/.*$/],
1050
- }),
1051
- });
1052
- await model.doGenerate({
1053
- prompt: TEST_PROMPT,
1054
- headers: {
1055
- 'X-Sync-Request': 'sync-request-value',
1056
- 'X-Common': 'request-value', // Should override config value
1057
- },
1058
- });
1059
- expect(server.calls[0].requestHeaders).toStrictEqual({
1060
- 'content-type': 'application/json',
1061
- 'x-async-config': 'async-config-value',
1062
- 'x-sync-request': 'sync-request-value',
1063
- 'x-common': 'request-value', // Request headers take precedence
1064
- });
1065
- });
1066
- it('handles Promise-based headers', async () => {
1067
- server.urls[TEST_URL_GEMINI_PRO].response = {
1068
- type: 'stream-chunks',
1069
- headers: { 'content-type': 'text/event-stream' },
1070
- chunks: ['data: ' + JSON.stringify({
1071
- candidates: [
1072
- {
1073
- content: {
1074
- parts: [{ text: '' }],
1075
- role: 'model',
1076
- },
1077
- finishReason: 'STOP',
1078
- index: 0,
1079
- safetyRatings: SAFETY_RATINGS,
1080
- },
1081
- ],
1082
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1083
- usageMetadata: {
1084
- promptTokenCount: 1,
1085
- candidatesTokenCount: 2,
1086
- totalTokenCount: 3,
1087
- },
1088
- }) + '\n\n'],
1089
- };
1090
- const model = new GoogleGenerativeAILanguageModel('gemini-pro', {
1091
- provider: 'google.generative-ai',
1092
- baseURL: 'https://cloudcode-pa.googleapis.com',
1093
- headers: async () => ({
1094
- 'X-Promise-Header': 'promise-value',
1095
- }),
1096
- generateId: () => 'test-id',
1097
- });
1098
- await model.doGenerate({
1099
- prompt: TEST_PROMPT,
1100
- });
1101
- expect(server.calls[0].requestHeaders).toStrictEqual({
1102
- 'content-type': 'application/json',
1103
- 'x-promise-header': 'promise-value',
1104
- });
1105
- });
1106
- it('handles async function headers from config', async () => {
1107
- prepareJsonResponse({});
1108
- const model = new GoogleGenerativeAILanguageModel('gemini-pro', {
1109
- provider: 'google.generative-ai',
1110
- baseURL: 'https://cloudcode-pa.googleapis.com',
1111
- headers: async () => ({
1112
- 'X-Async-Header': 'async-value',
1113
- }),
1114
- generateId: () => 'test-id',
1115
- });
1116
- await model.doGenerate({
1117
- prompt: TEST_PROMPT,
1118
- });
1119
- expect(server.calls[0].requestHeaders).toStrictEqual({
1120
- 'content-type': 'application/json',
1121
- 'x-async-header': 'async-value',
1122
- });
1123
- });
1124
- });
1125
- it('should expose safety ratings in provider metadata', async () => {
1126
- server.urls[TEST_URL_GEMINI_PRO].response = {
1127
- type: 'stream-chunks',
1128
- headers: { 'content-type': 'text/event-stream' },
1129
- chunks: ['data: ' + JSON.stringify({
1130
- candidates: [
1131
- {
1132
- content: {
1133
- parts: [{ text: 'test response' }],
1134
- role: 'model',
1135
- },
1136
- finishReason: 'STOP',
1137
- index: 0,
1138
- safetyRatings: [
1139
- {
1140
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
1141
- probability: 'NEGLIGIBLE',
1142
- probabilityScore: 0.1,
1143
- severity: 'LOW',
1144
- severityScore: 0.2,
1145
- blocked: false,
1146
- },
1147
- ],
1148
- },
1149
- ],
1150
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1151
- }) + '\n\n'],
1152
- };
1153
- const { providerMetadata } = await model.doGenerate({
1154
- prompt: TEST_PROMPT,
1155
- });
1156
- expect(providerMetadata === null || providerMetadata === void 0 ? void 0 : providerMetadata.google.safetyRatings).toStrictEqual([
1157
- {
1158
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
1159
- probability: 'NEGLIGIBLE',
1160
- probabilityScore: 0.1,
1161
- severity: 'LOW',
1162
- severityScore: 0.2,
1163
- blocked: false,
1164
- },
1165
- ]);
1166
- });
1167
- it('should expose PromptFeedback in provider metadata', async () => {
1168
- server.urls[TEST_URL_GEMINI_PRO].response = {
1169
- type: 'stream-chunks',
1170
- headers: { 'content-type': 'text/event-stream' },
1171
- chunks: ['data: ' + JSON.stringify({
1172
- candidates: [
1173
- {
1174
- content: { parts: [{ text: 'No' }], role: 'model' },
1175
- finishReason: 'SAFETY',
1176
- index: 0,
1177
- safetyRatings: SAFETY_RATINGS,
1178
- },
1179
- ],
1180
- promptFeedback: {
1181
- blockReason: 'SAFETY',
1182
- safetyRatings: SAFETY_RATINGS,
1183
- },
1184
- }) + '\n\n'],
1185
- };
1186
- const { providerMetadata } = await model.doGenerate({
1187
- prompt: TEST_PROMPT,
1188
- });
1189
- expect(providerMetadata === null || providerMetadata === void 0 ? void 0 : providerMetadata.google.promptFeedback).toStrictEqual({
1190
- blockReason: 'SAFETY',
1191
- safetyRatings: SAFETY_RATINGS,
1192
- });
1193
- });
1194
- it('should expose grounding metadata in provider metadata', async () => {
1195
- prepareJsonResponse({
1196
- content: 'test response',
1197
- groundingMetadata: {
1198
- webSearchQueries: ["What's the weather in Chicago this weekend?"],
1199
- searchEntryPoint: {
1200
- renderedContent: 'Sample rendered content for search results',
1201
- },
1202
- groundingChunks: [
1203
- {
1204
- web: {
1205
- uri: 'https://example.com/weather',
1206
- title: 'Chicago Weather Forecast',
1207
- },
1208
- },
1209
- ],
1210
- groundingSupports: [
1211
- {
1212
- segment: {
1213
- startIndex: 0,
1214
- endIndex: 65,
1215
- text: 'Chicago weather changes rapidly, so layers let you adjust easily.',
1216
- },
1217
- groundingChunkIndices: [0],
1218
- confidenceScores: [0.99],
1219
- },
1220
- ],
1221
- retrievalMetadata: {
1222
- webDynamicRetrievalScore: 0.96879,
1223
- },
1224
- },
1225
- });
1226
- const { providerMetadata } = await model.doGenerate({
1227
- prompt: TEST_PROMPT,
1228
- });
1229
- expect(providerMetadata === null || providerMetadata === void 0 ? void 0 : providerMetadata.google.groundingMetadata).toStrictEqual({
1230
- webSearchQueries: ["What's the weather in Chicago this weekend?"],
1231
- searchEntryPoint: {
1232
- renderedContent: 'Sample rendered content for search results',
1233
- },
1234
- groundingChunks: [
1235
- {
1236
- web: {
1237
- uri: 'https://example.com/weather',
1238
- title: 'Chicago Weather Forecast',
1239
- },
1240
- },
1241
- ],
1242
- groundingSupports: [
1243
- {
1244
- segment: {
1245
- startIndex: 0,
1246
- endIndex: 65,
1247
- text: 'Chicago weather changes rapidly, so layers let you adjust easily.',
1248
- },
1249
- groundingChunkIndices: [0],
1250
- confidenceScores: [0.99],
1251
- },
1252
- ],
1253
- retrievalMetadata: {
1254
- webDynamicRetrievalScore: 0.96879,
1255
- },
1256
- });
1257
- });
1258
- it('should handle code execution tool calls', async () => {
1259
- server.urls[TEST_URL_GEMINI_2_0_PRO].response = {
1260
- type: 'stream-chunks',
1261
- headers: { 'content-type': 'text/event-stream' },
1262
- chunks: ['data: ' + JSON.stringify({
1263
- candidates: [
1264
- {
1265
- content: {
1266
- parts: [
1267
- {
1268
- executableCode: {
1269
- language: 'PYTHON',
1270
- code: 'print(1+1)',
1271
- },
1272
- },
1273
- {
1274
- codeExecutionResult: {
1275
- outcome: 'OUTCOME_OK',
1276
- output: '2',
1277
- },
1278
- },
1279
- ],
1280
- role: 'model',
1281
- },
1282
- finishReason: 'STOP',
1283
- },
1284
- ],
1285
- }) + '\n\n'],
1286
- };
1287
- const model = provider.languageModel('gemini-2.0-pro');
1288
- const { content } = await model.doGenerate({
1289
- tools: [
1290
- provider.tools.codeExecution({}),
1291
- ],
1292
- prompt: TEST_PROMPT,
1293
- });
1294
- const requestBody = await getRequestBody(0);
1295
- expect(requestBody.tools).toEqual([{ codeExecution: {} }]);
1296
- expect(content).toEqual([
1297
- {
1298
- type: 'tool-call',
1299
- toolCallId: 'test-id',
1300
- toolName: 'code_execution',
1301
- input: '{"language":"PYTHON","code":"print(1+1)"}',
1302
- providerExecuted: true,
1303
- },
1304
- {
1305
- type: 'tool-result',
1306
- toolCallId: 'test-id',
1307
- toolName: 'code_execution',
1308
- result: {
1309
- outcome: 'OUTCOME_OK',
1310
- output: '2',
1311
- },
1312
- providerExecuted: true,
1313
- },
1314
- ]);
1315
- });
1316
- describe('search tool selection', () => {
1317
- const provider = createGoogleGenerativeAI({
1318
- apiKey: 'test-api-key',
1319
- generateId: () => 'test-id',
1320
- });
1321
- it('should use googleSearch for gemini-2.0-pro', async () => {
1322
- prepareJsonResponse({
1323
- url: TEST_URL_GEMINI_2_0_PRO,
1324
- });
1325
- const gemini2Pro = provider.languageModel('gemini-2.0-pro');
1326
- await gemini2Pro.doGenerate({
1327
- prompt: TEST_PROMPT,
1328
- tools: [
1329
- {
1330
- type: 'provider-defined',
1331
- id: 'google.google_search',
1332
- name: 'google_search',
1333
- args: {},
1334
- },
1335
- ],
1336
- });
1337
- expect(await getRequestBody(0)).toMatchObject({
1338
- tools: [{ googleSearch: {} }],
1339
- });
1340
- });
1341
- it('should use googleSearch for gemini-2.0-flash-exp', async () => {
1342
- prepareJsonResponse({
1343
- url: TEST_URL_GEMINI_2_0_FLASH_EXP,
1344
- });
1345
- const gemini2Flash = provider.languageModel('gemini-2.0-flash-exp');
1346
- await gemini2Flash.doGenerate({
1347
- prompt: TEST_PROMPT,
1348
- tools: [
1349
- {
1350
- type: 'provider-defined',
1351
- id: 'google.google_search',
1352
- name: 'google_search',
1353
- args: {},
1354
- },
1355
- ],
1356
- });
1357
- expect(await getRequestBody(0)).toMatchObject({
1358
- tools: [{ googleSearch: {} }],
1359
- });
1360
- });
1361
- it('should use googleSearchRetrieval for non-gemini-2 models', async () => {
1362
- prepareJsonResponse({
1363
- url: TEST_URL_GEMINI_1_0_PRO,
1364
- });
1365
- const geminiPro = provider.languageModel('gemini-1.0-pro');
1366
- await geminiPro.doGenerate({
1367
- prompt: TEST_PROMPT,
1368
- tools: [
1369
- {
1370
- type: 'provider-defined',
1371
- id: 'google.google_search',
1372
- name: 'google_search',
1373
- args: {},
1374
- },
1375
- ],
1376
- });
1377
- expect(await getRequestBody(0)).toMatchObject({
1378
- tools: [{ googleSearchRetrieval: {} }],
1379
- });
1380
- });
1381
- it('should use dynamic retrieval for gemini-1-5', async () => {
1382
- prepareJsonResponse({
1383
- url: TEST_URL_GEMINI_1_5_FLASH,
1384
- });
1385
- const geminiPro = provider.languageModel('gemini-1.5-flash');
1386
- await geminiPro.doGenerate({
1387
- prompt: TEST_PROMPT,
1388
- tools: [
1389
- {
1390
- type: 'provider-defined',
1391
- id: 'google.google_search',
1392
- name: 'google_search',
1393
- args: {
1394
- mode: 'MODE_DYNAMIC',
1395
- dynamicThreshold: 1,
1396
- },
1397
- },
1398
- ],
1399
- });
1400
- expect(await getRequestBody(0)).toMatchObject({
1401
- tools: [
1402
- {
1403
- googleSearchRetrieval: {
1404
- dynamicRetrievalConfig: {
1405
- mode: 'MODE_DYNAMIC',
1406
- dynamicThreshold: 1,
1407
- },
1408
- },
1409
- },
1410
- ],
1411
- });
1412
- });
1413
- it('should use urlContextTool for gemini-2.0-pro', async () => {
1414
- prepareJsonResponse({
1415
- url: TEST_URL_GEMINI_2_0_PRO,
1416
- });
1417
- const gemini2Pro = provider.languageModel('gemini-2.0-pro');
1418
- await gemini2Pro.doGenerate({
1419
- prompt: TEST_PROMPT,
1420
- tools: [
1421
- {
1422
- type: 'provider-defined',
1423
- id: 'google.url_context',
1424
- name: 'url_context',
1425
- args: {},
1426
- },
1427
- ],
1428
- });
1429
- expect(await getRequestBody(0)).toMatchObject({
1430
- tools: [{ urlContext: {} }],
1431
- });
1432
- });
1433
- it('should use vertexRagStore for gemini-2.0-pro', async () => {
1434
- prepareJsonResponse({
1435
- url: TEST_URL_GEMINI_2_0_PRO,
1436
- });
1437
- const gemini2Pro = provider.languageModel('gemini-2.0-pro');
1438
- await gemini2Pro.doGenerate({
1439
- prompt: TEST_PROMPT,
1440
- tools: [
1441
- {
1442
- type: 'provider-defined',
1443
- id: 'google.vertex_rag_store',
1444
- name: 'vertex_rag_store',
1445
- args: {
1446
- ragCorpus: 'projects/my-project/locations/us-central1/ragCorpora/my-rag-corpus',
1447
- topK: 5,
1448
- },
1449
- },
1450
- ],
1451
- });
1452
- expect(await getRequestBody(0)).toMatchObject({
1453
- tools: [
1454
- {
1455
- retrieval: {
1456
- vertex_rag_store: {
1457
- rag_resources: {
1458
- rag_corpus: 'projects/my-project/locations/us-central1/ragCorpora/my-rag-corpus',
1459
- },
1460
- similarity_top_k: 5,
1461
- },
1462
- },
1463
- },
1464
- ],
1465
- });
1466
- });
1467
- });
1468
- it('should extract image file outputs', async () => {
1469
- server.urls[TEST_URL_GEMINI_PRO].response = {
1470
- type: 'stream-chunks',
1471
- headers: { 'content-type': 'text/event-stream' },
1472
- chunks: ['data: ' + JSON.stringify({
1473
- candidates: [
1474
- {
1475
- content: {
1476
- parts: [
1477
- { text: 'Here is an image:' },
1478
- {
1479
- inlineData: {
1480
- mimeType: 'image/jpeg',
1481
- data: 'base64encodedimagedata',
1482
- },
1483
- },
1484
- { text: 'And another image:' },
1485
- {
1486
- inlineData: {
1487
- mimeType: 'image/png',
1488
- data: 'anotherbase64encodedimagedata',
1489
- },
1490
- },
1491
- ],
1492
- role: 'model',
1493
- },
1494
- finishReason: 'STOP',
1495
- index: 0,
1496
- safetyRatings: SAFETY_RATINGS,
1497
- },
1498
- ],
1499
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1500
- usageMetadata: {
1501
- promptTokenCount: 10,
1502
- candidatesTokenCount: 20,
1503
- totalTokenCount: 30,
1504
- },
1505
- }) + '\n\n'],
1506
- };
1507
- const { content } = await model.doGenerate({
1508
- prompt: TEST_PROMPT,
1509
- });
1510
- expect(content).toMatchInlineSnapshot(`
1511
- [
1512
- {
1513
- "providerMetadata": undefined,
1514
- "text": "Here is an image:",
1515
- "type": "text",
1516
- },
1517
- {
1518
- "data": "base64encodedimagedata",
1519
- "mediaType": "image/jpeg",
1520
- "type": "file",
1521
- },
1522
- {
1523
- "providerMetadata": undefined,
1524
- "text": "And another image:",
1525
- "type": "text",
1526
- },
1527
- {
1528
- "data": "anotherbase64encodedimagedata",
1529
- "mediaType": "image/png",
1530
- "type": "file",
1531
- },
1532
- ]
1533
- `);
1534
- });
1535
- it('should handle responses with only images and no text', async () => {
1536
- server.urls[TEST_URL_GEMINI_PRO].response = {
1537
- type: 'stream-chunks',
1538
- headers: { 'content-type': 'text/event-stream' },
1539
- chunks: ['data: ' + JSON.stringify({
1540
- candidates: [
1541
- {
1542
- content: {
1543
- parts: [
1544
- {
1545
- inlineData: {
1546
- mimeType: 'image/jpeg',
1547
- data: 'imagedata1',
1548
- },
1549
- },
1550
- {
1551
- inlineData: {
1552
- mimeType: 'image/png',
1553
- data: 'imagedata2',
1554
- },
1555
- },
1556
- ],
1557
- role: 'model',
1558
- },
1559
- finishReason: 'STOP',
1560
- index: 0,
1561
- safetyRatings: SAFETY_RATINGS,
1562
- },
1563
- ],
1564
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1565
- usageMetadata: {
1566
- promptTokenCount: 10,
1567
- candidatesTokenCount: 20,
1568
- totalTokenCount: 30,
1569
- },
1570
- }) + '\n\n'],
1571
- };
1572
- const { content } = await model.doGenerate({
1573
- prompt: TEST_PROMPT,
1574
- });
1575
- expect(content).toMatchInlineSnapshot(`
1576
- [
1577
- {
1578
- "data": "imagedata1",
1579
- "mediaType": "image/jpeg",
1580
- "type": "file",
1581
- },
1582
- {
1583
- "data": "imagedata2",
1584
- "mediaType": "image/png",
1585
- "type": "file",
1586
- },
1587
- ]
1588
- `);
1589
- });
1590
- it('should pass responseModalities in provider options', async () => {
1591
- prepareJsonResponse({});
1592
- await model.doGenerate({
1593
- prompt: TEST_PROMPT,
1594
- providerOptions: {
1595
- google: {
1596
- responseModalities: ['TEXT', 'IMAGE'],
1597
- },
1598
- },
1599
- });
1600
- expect(await getRequestBody(0)).toMatchObject({
1601
- generationConfig: {
1602
- responseModalities: ['TEXT', 'IMAGE'],
1603
- },
1604
- });
1605
- });
1606
- it('should pass mediaResolution in provider options', async () => {
1607
- prepareJsonResponse({});
1608
- await model.doGenerate({
1609
- prompt: TEST_PROMPT,
1610
- providerOptions: {
1611
- google: {
1612
- mediaResolution: 'MEDIA_RESOLUTION_LOW',
1613
- },
1614
- },
1615
- });
1616
- expect(await getRequestBody(0)).toMatchObject({
1617
- generationConfig: {
1618
- mediaResolution: 'MEDIA_RESOLUTION_LOW',
1619
- },
1620
- });
1621
- });
1622
- it('should pass imageConfig.aspectRatio in provider options', async () => {
1623
- prepareJsonResponse({});
1624
- await model.doGenerate({
1625
- prompt: TEST_PROMPT,
1626
- providerOptions: {
1627
- google: {
1628
- imageConfig: {
1629
- aspectRatio: '16:9',
1630
- },
1631
- },
1632
- },
1633
- });
1634
- expect(await getRequestBody(0)).toMatchObject({
1635
- generationConfig: {
1636
- imageConfig: {
1637
- aspectRatio: '16:9',
1638
- },
1639
- },
1640
- });
1641
- });
1642
- it('should pass retrievalConfig in provider options', async () => {
1643
- prepareJsonResponse({ url: TEST_URL_GEMINI_2_0_FLASH_EXP });
1644
- const gemini2Model = provider.chat('gemini-2.0-flash-exp');
1645
- await gemini2Model.doGenerate({
1646
- prompt: TEST_PROMPT,
1647
- tools: [
1648
- {
1649
- type: 'provider-defined',
1650
- id: 'google.google_maps',
1651
- name: 'google_maps',
1652
- args: {},
1653
- },
1654
- ],
1655
- providerOptions: {
1656
- google: {
1657
- retrievalConfig: {
1658
- latLng: {
1659
- latitude: 34.090199,
1660
- longitude: -117.881081,
1661
- },
1662
- },
1663
- },
1664
- },
1665
- });
1666
- expect(await getRequestBody(0)).toMatchObject({
1667
- tools: [{ googleMaps: {} }],
1668
- toolConfig: {
1669
- retrievalConfig: {
1670
- latLng: {
1671
- latitude: 34.090199,
1672
- longitude: -117.881081,
1673
- },
1674
- },
1675
- },
1676
- });
1677
- });
1678
- it('should include non-image inlineData parts', async () => {
1679
- server.urls[TEST_URL_GEMINI_PRO].response = {
1680
- type: 'stream-chunks',
1681
- headers: { 'content-type': 'text/event-stream' },
1682
- chunks: ['data: ' + JSON.stringify({
1683
- candidates: [
1684
- {
1685
- content: {
1686
- parts: [
1687
- { text: 'Here is content:' },
1688
- {
1689
- inlineData: {
1690
- mimeType: 'image/jpeg',
1691
- data: 'validimagedata',
1692
- },
1693
- },
1694
- {
1695
- inlineData: {
1696
- mimeType: 'application/pdf',
1697
- data: 'pdfdata',
1698
- },
1699
- },
1700
- ],
1701
- role: 'model',
1702
- },
1703
- finishReason: 'STOP',
1704
- index: 0,
1705
- safetyRatings: SAFETY_RATINGS,
1706
- },
1707
- ],
1708
- promptFeedback: { safetyRatings: SAFETY_RATINGS },
1709
- }) + '\n\n'],
1710
- };
1711
- const { content } = await model.doGenerate({
1712
- prompt: TEST_PROMPT,
1713
- });
1714
- expect(content).toMatchInlineSnapshot(`
1715
- [
1716
- {
1717
- "providerMetadata": undefined,
1718
- "text": "Here is content:",
1719
- "type": "text",
1720
- },
1721
- {
1722
- "data": "validimagedata",
1723
- "mediaType": "image/jpeg",
1724
- "type": "file",
1725
- },
1726
- {
1727
- "data": "pdfdata",
1728
- "mediaType": "application/pdf",
1729
- "type": "file",
1730
- },
1731
- ]
1732
- `);
1733
- });
1734
- it('should correctly parse and separate reasoning parts from text output', async () => {
1735
- server.urls[TEST_URL_GEMINI_PRO].response = {
1736
- type: 'stream-chunks',
1737
- headers: { 'content-type': 'text/event-stream' },
1738
- chunks: ['data: ' + JSON.stringify({
1739
- candidates: [
1740
- {
1741
- content: {
1742
- parts: [
1743
- { text: 'Visible text part 1. ' },
1744
- { text: 'This is a thought process.', thought: true },
1745
- { text: 'Visible text part 2.' },
1746
- { text: 'Another internal thought.', thought: true },
1747
- ],
1748
- role: 'model',
1749
- },
1750
- finishReason: 'STOP',
1751
- index: 0,
1752
- safetyRatings: SAFETY_RATINGS,
1753
- },
1754
- ],
1755
- usageMetadata: {
1756
- promptTokenCount: 10,
1757
- candidatesTokenCount: 20,
1758
- totalTokenCount: 30,
1759
- },
1760
- }) + '\n\n'],
1761
- };
1762
- const { content } = await model.doGenerate({
1763
- prompt: TEST_PROMPT,
1764
- });
1765
- expect(content).toMatchInlineSnapshot(`
1766
- [
1767
- {
1768
- "providerMetadata": undefined,
1769
- "text": "Visible text part 1. ",
1770
- "type": "text",
1771
- },
1772
- {
1773
- "providerMetadata": undefined,
1774
- "text": "This is a thought process.",
1775
- "type": "reasoning",
1776
- },
1777
- {
1778
- "providerMetadata": undefined,
1779
- "text": "Visible text part 2.",
1780
- "type": "text",
1781
- },
1782
- {
1783
- "providerMetadata": undefined,
1784
- "text": "Another internal thought.",
1785
- "type": "reasoning",
1786
- },
1787
- ]
1788
- `);
1789
- });
1790
- it('should correctly parse thought signatures with reasoning parts', async () => {
1791
- server.urls[TEST_URL_GEMINI_PRO].response = {
1792
- type: 'stream-chunks',
1793
- headers: { 'content-type': 'text/event-stream' },
1794
- chunks: ['data: ' + JSON.stringify({
1795
- candidates: [
1796
- {
1797
- content: {
1798
- parts: [
1799
- { text: 'Visible text part 1. ', thoughtSignature: 'sig1' },
1800
- {
1801
- text: 'This is a thought process.',
1802
- thought: true,
1803
- thoughtSignature: 'sig2',
1804
- },
1805
- { text: 'Visible text part 2.', thoughtSignature: 'sig3' },
1806
- ],
1807
- role: 'model',
1808
- },
1809
- finishReason: 'STOP',
1810
- index: 0,
1811
- safetyRatings: SAFETY_RATINGS,
1812
- },
1813
- ],
1814
- usageMetadata: {
1815
- promptTokenCount: 10,
1816
- candidatesTokenCount: 20,
1817
- totalTokenCount: 30,
1818
- },
1819
- }) + '\n\n'],
1820
- };
1821
- const { content } = await model.doGenerate({
1822
- prompt: TEST_PROMPT,
1823
- });
1824
- expect(content).toMatchInlineSnapshot(`
1825
- [
1826
- {
1827
- "providerMetadata": {
1828
- "google": {
1829
- "thoughtSignature": "sig1",
1830
- },
1831
- },
1832
- "text": "Visible text part 1. ",
1833
- "type": "text",
1834
- },
1835
- {
1836
- "providerMetadata": {
1837
- "google": {
1838
- "thoughtSignature": "sig2",
1839
- },
1840
- },
1841
- "text": "This is a thought process.",
1842
- "type": "reasoning",
1843
- },
1844
- {
1845
- "providerMetadata": {
1846
- "google": {
1847
- "thoughtSignature": "sig3",
1848
- },
1849
- },
1850
- "text": "Visible text part 2.",
1851
- "type": "text",
1852
- },
1853
- ]
1854
- `);
1855
- });
1856
- it('should correctly parse thought signatures with function calls', async () => {
1857
- server.urls[TEST_URL_GEMINI_PRO].response = {
1858
- type: 'stream-chunks',
1859
- headers: { 'content-type': 'text/event-stream' },
1860
- chunks: ['data: ' + JSON.stringify({
1861
- candidates: [
1862
- {
1863
- content: {
1864
- parts: [
1865
- {
1866
- functionCall: {
1867
- name: 'test-tool',
1868
- args: { value: 'test' },
1869
- },
1870
- thoughtSignature: 'func_sig1',
1871
- },
1872
- ],
1873
- role: 'model',
1874
- },
1875
- finishReason: 'STOP',
1876
- index: 0,
1877
- safetyRatings: SAFETY_RATINGS,
1878
- },
1879
- ],
1880
- usageMetadata: {
1881
- promptTokenCount: 10,
1882
- candidatesTokenCount: 20,
1883
- totalTokenCount: 30,
1884
- },
1885
- }) + '\n\n'],
1886
- };
1887
- const { content } = await model.doGenerate({
1888
- prompt: TEST_PROMPT,
1889
- });
1890
- expect(content).toMatchInlineSnapshot(`
1891
- [
1892
- {
1893
- "input": "{"value":"test"}",
1894
- "providerMetadata": {
1895
- "google": {
1896
- "thoughtSignature": "func_sig1",
1897
- },
1898
- },
1899
- "toolCallId": "test-id",
1900
- "toolName": "test-tool",
1901
- "type": "tool-call",
1902
- },
1903
- ]
1904
- `);
1905
- });
1906
- it('should support includeThoughts with google generative ai provider', async () => {
1907
- server.urls[TEST_URL_GEMINI_PRO].response = {
1908
- type: 'stream-chunks',
1909
- headers: { 'content-type': 'text/event-stream' },
1910
- chunks: ['data: ' + JSON.stringify({
1911
- candidates: [
1912
- {
1913
- content: {
1914
- parts: [
1915
- {
1916
- text: 'let me think about this problem',
1917
- thought: true,
1918
- thoughtSignature: 'reasoning_sig',
1919
- },
1920
- { text: 'the answer is 42' },
1921
- ],
1922
- role: 'model',
1923
- },
1924
- finishReason: 'STOP',
1925
- safetyRatings: SAFETY_RATINGS,
1926
- },
1927
- ],
1928
- usageMetadata: {
1929
- promptTokenCount: 10,
1930
- candidatesTokenCount: 15,
1931
- totalTokenCount: 25,
1932
- thoughtsTokenCount: 8,
1933
- },
1934
- }) + '\n\n'],
1935
- };
1936
- const { content, usage } = await model.doGenerate({
1937
- prompt: TEST_PROMPT,
1938
- providerOptions: {
1939
- google: {
1940
- thinkingConfig: {
1941
- includeThoughts: true,
1942
- thinkingBudget: 1024,
1943
- },
1944
- },
1945
- },
1946
- });
1947
- expect(content).toMatchInlineSnapshot(`
1948
- [
1949
- {
1950
- "providerMetadata": {
1951
- "google": {
1952
- "thoughtSignature": "reasoning_sig",
1953
- },
1954
- },
1955
- "text": "let me think about this problem",
1956
- "type": "reasoning",
1957
- },
1958
- {
1959
- "providerMetadata": undefined,
1960
- "text": "the answer is 42",
1961
- "type": "text",
1962
- },
1963
- ]
1964
- `);
1965
- expect(usage).toMatchInlineSnapshot(`
1966
- {
1967
- "cachedInputTokens": undefined,
1968
- "inputTokens": 10,
1969
- "outputTokens": 15,
1970
- "reasoningTokens": 8,
1971
- "totalTokens": 25,
1972
- }
1973
- `);
1974
- });
1975
- it('should pass thinkingLevel in provider options', async () => {
1976
- prepareJsonResponse({});
1977
- await model.doGenerate({
1978
- prompt: TEST_PROMPT,
1979
- providerOptions: {
1980
- google: {
1981
- thinkingConfig: {
1982
- thinkingLevel: 'high',
1983
- },
1984
- },
1985
- },
1986
- });
1987
- expect(await getRequestBody(0)).toMatchObject({
1988
- generationConfig: {
1989
- thinkingConfig: {
1990
- thinkingLevel: 'high',
1991
- },
1992
- },
1993
- });
1994
- });
1995
- });
1996
- describe('doStream', () => {
1997
- const TEST_URL_GEMINI_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
1998
- const TEST_URL_GEMINI_2_0_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
1999
- const TEST_URL_GEMINI_2_0_FLASH_EXP = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
2000
- const TEST_URL_GEMINI_1_0_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
2001
- const TEST_URL_GEMINI_1_5_FLASH = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
2002
- const server = createTestServer({
2003
- [TEST_URL_GEMINI_PRO]: {},
2004
- });
2005
- const getRequestBody = async (index = 0) => {
2006
- var _a;
2007
- const body = await server.calls[index].requestBodyJson;
2008
- return (_a = body === null || body === void 0 ? void 0 : body.request) !== null && _a !== void 0 ? _a : body;
2009
- };
2010
- const prepareStreamResponse = ({ content, headers, groundingMetadata, urlContextMetadata, url = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent', }) => {
2011
- server.urls[url].response = {
2012
- headers,
2013
- type: 'stream-chunks',
2014
- chunks: content.map((text, index) => `data: ${JSON.stringify({
2015
- candidates: [
2016
- {
2017
- content: { parts: [{ text }], role: 'model' },
2018
- finishReason: 'STOP',
2019
- index: 0,
2020
- safetyRatings: SAFETY_RATINGS,
2021
- ...(groundingMetadata && { groundingMetadata }),
2022
- ...(urlContextMetadata && { urlContextMetadata }),
2023
- },
2024
- ],
2025
- // Include usage metadata only in the last chunk
2026
- ...(index === content.length - 1 && {
2027
- usageMetadata: {
2028
- promptTokenCount: 294,
2029
- candidatesTokenCount: 233,
2030
- totalTokenCount: 527,
2031
- },
2032
- }),
2033
- })}\n\n`),
2034
- };
2035
- };
2036
- it('should expose grounding metadata in provider metadata on finish', async () => {
2037
- var _a;
2038
- prepareStreamResponse({
2039
- content: ['test'],
2040
- groundingMetadata: {
2041
- webSearchQueries: ["What's the weather in Chicago this weekend?"],
2042
- searchEntryPoint: {
2043
- renderedContent: 'Sample rendered content for search results',
2044
- },
2045
- groundingChunks: [
2046
- {
2047
- web: {
2048
- uri: 'https://example.com/weather',
2049
- title: 'Chicago Weather Forecast',
2050
- },
2051
- },
2052
- ],
2053
- groundingSupports: [
2054
- {
2055
- segment: {
2056
- startIndex: 0,
2057
- endIndex: 65,
2058
- text: 'Chicago weather changes rapidly, so layers let you adjust easily.',
2059
- },
2060
- groundingChunkIndices: [0],
2061
- confidenceScores: [0.99],
2062
- },
2063
- ],
2064
- retrievalMetadata: {
2065
- webDynamicRetrievalScore: 0.96879,
2066
- },
2067
- },
2068
- });
2069
- const { stream } = await model.doStream({
2070
- prompt: TEST_PROMPT,
2071
- includeRawChunks: false,
2072
- });
2073
- const events = await convertReadableStreamToArray(stream);
2074
- const finishEvent = events.find(event => event.type === 'finish');
2075
- expect((finishEvent === null || finishEvent === void 0 ? void 0 : finishEvent.type) === 'finish' &&
2076
- ((_a = finishEvent.providerMetadata) === null || _a === void 0 ? void 0 : _a.google.groundingMetadata)).toStrictEqual({
2077
- webSearchQueries: ["What's the weather in Chicago this weekend?"],
2078
- searchEntryPoint: {
2079
- renderedContent: 'Sample rendered content for search results',
2080
- },
2081
- groundingChunks: [
2082
- {
2083
- web: {
2084
- uri: 'https://example.com/weather',
2085
- title: 'Chicago Weather Forecast',
2086
- },
2087
- },
2088
- ],
2089
- groundingSupports: [
2090
- {
2091
- segment: {
2092
- startIndex: 0,
2093
- endIndex: 65,
2094
- text: 'Chicago weather changes rapidly, so layers let you adjust easily.',
2095
- },
2096
- groundingChunkIndices: [0],
2097
- confidenceScores: [0.99],
2098
- },
2099
- ],
2100
- retrievalMetadata: {
2101
- webDynamicRetrievalScore: 0.96879,
2102
- },
2103
- });
2104
- });
2105
- it('should expose url context metadata in provider metadata on finish', async () => {
2106
- var _a;
2107
- prepareStreamResponse({
2108
- content: ['test'],
2109
- urlContextMetadata: {
2110
- urlMetadata: [
2111
- {
2112
- retrievedUrl: 'https://example.com/weather',
2113
- urlRetrievalStatus: 'URL_RETRIEVAL_STATUS_SUCCESS',
2114
- },
2115
- ],
2116
- },
2117
- });
2118
- const { stream } = await model.doStream({
2119
- prompt: TEST_PROMPT,
2120
- includeRawChunks: false,
2121
- });
2122
- const events = await convertReadableStreamToArray(stream);
2123
- const finishEvent = events.find(event => event.type === 'finish');
2124
- expect((finishEvent === null || finishEvent === void 0 ? void 0 : finishEvent.type) === 'finish' &&
2125
- ((_a = finishEvent.providerMetadata) === null || _a === void 0 ? void 0 : _a.google.urlContextMetadata)).toStrictEqual({
2126
- urlMetadata: [
2127
- {
2128
- retrievedUrl: 'https://example.com/weather',
2129
- urlRetrievalStatus: 'URL_RETRIEVAL_STATUS_SUCCESS',
2130
- },
2131
- ],
2132
- });
2133
- });
2134
- it('should stream text deltas', async () => {
2135
- prepareStreamResponse({ content: ['Hello', ', ', 'world!'] });
2136
- const { stream } = await model.doStream({
2137
- prompt: TEST_PROMPT,
2138
- includeRawChunks: false,
2139
- });
2140
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
2141
- [
2142
- {
2143
- "type": "stream-start",
2144
- "warnings": [],
2145
- },
2146
- {
2147
- "id": "0",
2148
- "providerMetadata": undefined,
2149
- "type": "text-start",
2150
- },
2151
- {
2152
- "delta": "Hello",
2153
- "id": "0",
2154
- "providerMetadata": undefined,
2155
- "type": "text-delta",
2156
- },
2157
- {
2158
- "delta": ", ",
2159
- "id": "0",
2160
- "providerMetadata": undefined,
2161
- "type": "text-delta",
2162
- },
2163
- {
2164
- "delta": "world!",
2165
- "id": "0",
2166
- "providerMetadata": undefined,
2167
- "type": "text-delta",
2168
- },
2169
- {
2170
- "id": "0",
2171
- "type": "text-end",
2172
- },
2173
- {
2174
- "finishReason": "stop",
2175
- "providerMetadata": {
2176
- "google": {
2177
- "groundingMetadata": null,
2178
- "promptFeedback": null,
2179
- "safetyRatings": [
2180
- {
2181
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
2182
- "probability": "NEGLIGIBLE",
2183
- },
2184
- {
2185
- "category": "HARM_CATEGORY_HATE_SPEECH",
2186
- "probability": "NEGLIGIBLE",
2187
- },
2188
- {
2189
- "category": "HARM_CATEGORY_HARASSMENT",
2190
- "probability": "NEGLIGIBLE",
2191
- },
2192
- {
2193
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
2194
- "probability": "NEGLIGIBLE",
2195
- },
2196
- ],
2197
- "urlContextMetadata": null,
2198
- "usageMetadata": {
2199
- "candidatesTokenCount": 233,
2200
- "promptTokenCount": 294,
2201
- "totalTokenCount": 527,
2202
- },
2203
- },
2204
- },
2205
- "type": "finish",
2206
- "usage": {
2207
- "cachedInputTokens": undefined,
2208
- "inputTokens": 294,
2209
- "outputTokens": 233,
2210
- "reasoningTokens": undefined,
2211
- "totalTokens": 527,
2212
- },
2213
- },
2214
- ]
2215
- `);
2216
- });
2217
- it('should expose safety ratings in provider metadata on finish', async () => {
2218
- var _a;
2219
- server.urls[TEST_URL_GEMINI_PRO].response = {
2220
- type: 'stream-chunks',
2221
- chunks: [
2222
- `data: {"candidates": [{"content": {"parts": [{"text": "test"}],"role": "model"},` +
2223
- `"finishReason": "STOP","index": 0,"safetyRatings": [` +
2224
- `{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE",` +
2225
- `"probabilityScore": 0.1,"severity": "LOW","severityScore": 0.2,"blocked": false}]}]}\n\n`,
2226
- ],
2227
- };
2228
- const { stream } = await model.doStream({
2229
- prompt: TEST_PROMPT,
2230
- includeRawChunks: false,
2231
- });
2232
- const events = await convertReadableStreamToArray(stream);
2233
- const finishEvent = events.find(event => event.type === 'finish');
2234
- expect((finishEvent === null || finishEvent === void 0 ? void 0 : finishEvent.type) === 'finish' &&
2235
- ((_a = finishEvent.providerMetadata) === null || _a === void 0 ? void 0 : _a.google.safetyRatings)).toStrictEqual([
2236
- {
2237
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
2238
- probability: 'NEGLIGIBLE',
2239
- probabilityScore: 0.1,
2240
- severity: 'LOW',
2241
- severityScore: 0.2,
2242
- blocked: false,
2243
- },
2244
- ]);
2245
- });
2246
- it('should expose PromptFeedback in provider metadata on finish', async () => {
2247
- var _a;
2248
- server.urls[TEST_URL_GEMINI_PRO].response = {
2249
- type: 'stream-chunks',
2250
- chunks: [
2251
- `data: {"candidates": [{"content": {"parts": [{"text": "No"}],"role": "model"},` +
2252
- `"finishReason": "PROHIBITED_CONTENT","index": 0}],` +
2253
- `"promptFeedback": {"blockReason": "PROHIBITED_CONTENT","safetyRatings": [` +
2254
- `{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},` +
2255
- `{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},` +
2256
- `{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},` +
2257
- `{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}\n\n`,
2258
- ],
2259
- };
2260
- const { stream } = await model.doStream({
2261
- prompt: TEST_PROMPT,
2262
- });
2263
- const events = await convertReadableStreamToArray(stream);
2264
- const finishEvent = events.find(event => event.type === 'finish');
2265
- expect((finishEvent === null || finishEvent === void 0 ? void 0 : finishEvent.type) === 'finish' &&
2266
- ((_a = finishEvent.providerMetadata) === null || _a === void 0 ? void 0 : _a.google.promptFeedback)).toStrictEqual({
2267
- blockReason: 'PROHIBITED_CONTENT',
2268
- safetyRatings: SAFETY_RATINGS,
2269
- });
2270
- });
2271
- it('should stream code execution tool calls and results', async () => {
2272
- server.urls[TEST_URL_GEMINI_2_0_PRO].response = {
2273
- type: 'stream-chunks',
2274
- chunks: [
2275
- `data: ${JSON.stringify({
2276
- candidates: [
2277
- {
2278
- content: {
2279
- parts: [
2280
- {
2281
- executableCode: {
2282
- language: 'PYTHON',
2283
- code: 'print("hello")',
2284
- },
2285
- },
2286
- ],
2287
- },
2288
- },
2289
- ],
2290
- })}\n\n`,
2291
- `data: ${JSON.stringify({
2292
- candidates: [
2293
- {
2294
- content: {
2295
- parts: [
2296
- {
2297
- codeExecutionResult: {
2298
- outcome: 'OUTCOME_OK',
2299
- output: 'hello\n',
2300
- },
2301
- },
2302
- ],
2303
- },
2304
- finishReason: 'STOP',
2305
- },
2306
- ],
2307
- })}\n\n`,
2308
- ],
2309
- };
2310
- const model = provider.languageModel('gemini-2.0-pro');
2311
- const { stream } = await model.doStream({
2312
- tools: [
2313
- provider.tools.codeExecution({}),
2314
- ],
2315
- prompt: TEST_PROMPT,
2316
- });
2317
- const events = await convertReadableStreamToArray(stream);
2318
- const toolEvents = events.filter(e => e.type === 'tool-call' || e.type === 'tool-result');
2319
- expect(toolEvents).toEqual([
2320
- {
2321
- type: 'tool-call',
2322
- toolCallId: 'test-id',
2323
- toolName: 'code_execution',
2324
- input: '{"language":"PYTHON","code":"print(\\"hello\\")"}',
2325
- providerExecuted: true,
2326
- },
2327
- {
2328
- type: 'tool-result',
2329
- toolCallId: 'test-id',
2330
- toolName: 'code_execution',
2331
- result: {
2332
- outcome: 'OUTCOME_OK',
2333
- output: 'hello\n',
2334
- },
2335
- providerExecuted: true,
2336
- },
2337
- ]);
2338
- });
2339
- describe('search tool selection', () => {
2340
- const provider = createGoogleGenerativeAI({
2341
- apiKey: 'test-api-key',
2342
- generateId: () => 'test-id',
2343
- });
2344
- it('should use googleSearch for gemini-2.0-pro', async () => {
2345
- prepareStreamResponse({
2346
- content: [''],
2347
- url: TEST_URL_GEMINI_2_0_PRO,
2348
- });
2349
- const gemini2Pro = provider.languageModel('gemini-2.0-pro');
2350
- await gemini2Pro.doStream({
2351
- prompt: TEST_PROMPT,
2352
- includeRawChunks: false,
2353
- tools: [
2354
- {
2355
- type: 'provider-defined',
2356
- id: 'google.google_search',
2357
- name: 'google_search',
2358
- args: {},
2359
- },
2360
- ],
2361
- });
2362
- expect(await getRequestBody(0)).toMatchObject({
2363
- tools: [{ googleSearch: {} }],
2364
- });
2365
- });
2366
- it('should use googleSearch for gemini-2.0-flash-exp', async () => {
2367
- prepareStreamResponse({
2368
- content: [''],
2369
- url: TEST_URL_GEMINI_2_0_FLASH_EXP,
2370
- });
2371
- const gemini2Flash = provider.languageModel('gemini-2.0-flash-exp');
2372
- await gemini2Flash.doStream({
2373
- prompt: TEST_PROMPT,
2374
- includeRawChunks: false,
2375
- tools: [
2376
- {
2377
- type: 'provider-defined',
2378
- id: 'google.google_search',
2379
- name: 'google_search',
2380
- args: {},
2381
- },
2382
- ],
2383
- });
2384
- expect(await getRequestBody(0)).toMatchObject({
2385
- tools: [{ googleSearch: {} }],
2386
- });
2387
- });
2388
- it('should use googleSearchRetrieval for non-gemini-2 models', async () => {
2389
- prepareStreamResponse({
2390
- content: [''],
2391
- url: TEST_URL_GEMINI_1_0_PRO,
2392
- });
2393
- const geminiPro = provider.languageModel('gemini-1.0-pro');
2394
- await geminiPro.doStream({
2395
- prompt: TEST_PROMPT,
2396
- includeRawChunks: false,
2397
- tools: [
2398
- {
2399
- type: 'provider-defined',
2400
- id: 'google.google_search',
2401
- name: 'google_search',
2402
- args: {},
2403
- },
2404
- ],
2405
- });
2406
- expect(await getRequestBody(0)).toMatchObject({
2407
- tools: [{ googleSearchRetrieval: {} }],
2408
- });
2409
- });
2410
- it('should use dynamic retrieval for gemini-1-5', async () => {
2411
- prepareStreamResponse({
2412
- content: [''],
2413
- url: TEST_URL_GEMINI_1_5_FLASH,
2414
- });
2415
- const geminiPro = provider.languageModel('gemini-1.5-flash');
2416
- await geminiPro.doStream({
2417
- prompt: TEST_PROMPT,
2418
- includeRawChunks: false,
2419
- tools: [
2420
- {
2421
- type: 'provider-defined',
2422
- id: 'google.google_search',
2423
- name: 'google_search',
2424
- args: {
2425
- mode: 'MODE_DYNAMIC',
2426
- dynamicThreshold: 1,
2427
- },
2428
- },
2429
- ],
2430
- });
2431
- expect(await getRequestBody(0)).toMatchObject({
2432
- tools: [
2433
- {
2434
- googleSearchRetrieval: {
2435
- dynamicRetrievalConfig: {
2436
- mode: 'MODE_DYNAMIC',
2437
- dynamicThreshold: 1,
2438
- },
2439
- },
2440
- },
2441
- ],
2442
- });
2443
- });
2444
- });
2445
- it('should stream source events', async () => {
2446
- prepareStreamResponse({
2447
- content: ['Some initial text'],
2448
- groundingMetadata: {
2449
- groundingChunks: [
2450
- {
2451
- web: {
2452
- uri: 'https://source.example.com',
2453
- title: 'Source Title',
2454
- },
2455
- },
2456
- ],
2457
- },
2458
- });
2459
- const { stream } = await model.doStream({
2460
- prompt: TEST_PROMPT,
2461
- includeRawChunks: false,
2462
- });
2463
- const events = await convertReadableStreamToArray(stream);
2464
- const sourceEvents = events.filter(event => event.type === 'source');
2465
- expect(sourceEvents).toMatchInlineSnapshot(`
2466
- [
2467
- {
2468
- "id": "test-id",
2469
- "sourceType": "url",
2470
- "title": "Source Title",
2471
- "type": "source",
2472
- "url": "https://source.example.com",
2473
- },
2474
- ]
2475
- `);
2476
- });
2477
- it('should stream sources during intermediate chunks', async () => {
2478
- server.urls[TEST_URL_GEMINI_PRO].response = {
2479
- type: 'stream-chunks',
2480
- chunks: [
2481
- `data: ${JSON.stringify({
2482
- candidates: [
2483
- {
2484
- content: { parts: [{ text: 'text' }], role: 'model' },
2485
- index: 0,
2486
- safetyRatings: SAFETY_RATINGS,
2487
- groundingMetadata: {
2488
- groundingChunks: [
2489
- { web: { uri: 'https://a.com', title: 'A' } },
2490
- { web: { uri: 'https://b.com', title: 'B' } },
2491
- ],
2492
- },
2493
- },
2494
- ],
2495
- })}\n\n`,
2496
- `data: ${JSON.stringify({
2497
- candidates: [
2498
- {
2499
- content: { parts: [{ text: 'more' }], role: 'model' },
2500
- finishReason: 'STOP',
2501
- index: 0,
2502
- safetyRatings: SAFETY_RATINGS,
2503
- },
2504
- ],
2505
- })}\n\n`,
2506
- ],
2507
- };
2508
- const { stream } = await model.doStream({
2509
- prompt: TEST_PROMPT,
2510
- includeRawChunks: false,
2511
- });
2512
- const events = await convertReadableStreamToArray(stream);
2513
- const sourceEvents = events.filter(event => event.type === 'source');
2514
- expect(sourceEvents).toMatchInlineSnapshot(`
2515
- [
2516
- {
2517
- "id": "test-id",
2518
- "sourceType": "url",
2519
- "title": "A",
2520
- "type": "source",
2521
- "url": "https://a.com",
2522
- },
2523
- {
2524
- "id": "test-id",
2525
- "sourceType": "url",
2526
- "title": "B",
2527
- "type": "source",
2528
- "url": "https://b.com",
2529
- },
2530
- ]
2531
- `);
2532
- });
2533
- it('should deduplicate sources across chunks', async () => {
2534
- server.urls[TEST_URL_GEMINI_PRO].response = {
2535
- type: 'stream-chunks',
2536
- chunks: [
2537
- `data: ${JSON.stringify({
2538
- candidates: [
2539
- {
2540
- content: { parts: [{ text: 'first chunk' }], role: 'model' },
2541
- index: 0,
2542
- safetyRatings: SAFETY_RATINGS,
2543
- groundingMetadata: {
2544
- groundingChunks: [
2545
- { web: { uri: 'https://example.com', title: 'Example' } },
2546
- { web: { uri: 'https://unique.com', title: 'Unique' } },
2547
- ],
2548
- },
2549
- },
2550
- ],
2551
- })}\n\n`,
2552
- `data: ${JSON.stringify({
2553
- candidates: [
2554
- {
2555
- content: { parts: [{ text: 'second chunk' }], role: 'model' },
2556
- index: 0,
2557
- safetyRatings: SAFETY_RATINGS,
2558
- groundingMetadata: {
2559
- groundingChunks: [
2560
- {
2561
- web: {
2562
- uri: 'https://example.com',
2563
- title: 'Example Duplicate',
2564
- },
2565
- },
2566
- { web: { uri: 'https://another.com', title: 'Another' } },
2567
- ],
2568
- },
2569
- },
2570
- ],
2571
- })}\n\n`,
2572
- `data: ${JSON.stringify({
2573
- candidates: [
2574
- {
2575
- content: { parts: [{ text: 'final chunk' }], role: 'model' },
2576
- finishReason: 'STOP',
2577
- index: 0,
2578
- safetyRatings: SAFETY_RATINGS,
2579
- },
2580
- ],
2581
- })}\n\n`,
2582
- ],
2583
- };
2584
- const { stream } = await model.doStream({
2585
- prompt: TEST_PROMPT,
2586
- includeRawChunks: false,
2587
- });
2588
- const events = await convertReadableStreamToArray(stream);
2589
- const sourceEvents = events.filter(event => event.type === 'source');
2590
- expect(sourceEvents).toMatchInlineSnapshot(`
2591
- [
2592
- {
2593
- "id": "test-id",
2594
- "sourceType": "url",
2595
- "title": "Example",
2596
- "type": "source",
2597
- "url": "https://example.com",
2598
- },
2599
- {
2600
- "id": "test-id",
2601
- "sourceType": "url",
2602
- "title": "Unique",
2603
- "type": "source",
2604
- "url": "https://unique.com",
2605
- },
2606
- {
2607
- "id": "test-id",
2608
- "sourceType": "url",
2609
- "title": "Another",
2610
- "type": "source",
2611
- "url": "https://another.com",
2612
- },
2613
- ]
2614
- `);
2615
- });
2616
- it('should stream files', async () => {
2617
- server.urls[TEST_URL_GEMINI_PRO].response = {
2618
- type: 'stream-chunks',
2619
- chunks: [
2620
- `data: {"candidates": [{"content": {"parts": [{"inlineData": {"data": "test","mimeType": "text/plain"}}]` +
2621
- `,"role": "model"},` +
2622
- `"finishReason": "STOP","index": 0,"safetyRatings": [` +
2623
- `{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},` +
2624
- `{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},` +
2625
- `{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},` +
2626
- `{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}\n\n`,
2627
- `data: {"usageMetadata": {"promptTokenCount": 294,"candidatesTokenCount": 233,"totalTokenCount": 527}}\n\n`,
2628
- ],
2629
- };
2630
- const { stream } = await model.doStream({
2631
- prompt: TEST_PROMPT,
2632
- includeRawChunks: false,
2633
- });
2634
- const events = await convertReadableStreamToArray(stream);
2635
- expect(events).toMatchInlineSnapshot(`
2636
- [
2637
- {
2638
- "type": "stream-start",
2639
- "warnings": [],
2640
- },
2641
- {
2642
- "data": "test",
2643
- "mediaType": "text/plain",
2644
- "type": "file",
2645
- },
2646
- {
2647
- "finishReason": "stop",
2648
- "providerMetadata": {
2649
- "google": {
2650
- "groundingMetadata": null,
2651
- "promptFeedback": null,
2652
- "safetyRatings": [
2653
- {
2654
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
2655
- "probability": "NEGLIGIBLE",
2656
- },
2657
- {
2658
- "category": "HARM_CATEGORY_HATE_SPEECH",
2659
- "probability": "NEGLIGIBLE",
2660
- },
2661
- {
2662
- "category": "HARM_CATEGORY_HARASSMENT",
2663
- "probability": "NEGLIGIBLE",
2664
- },
2665
- {
2666
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
2667
- "probability": "NEGLIGIBLE",
2668
- },
2669
- ],
2670
- "urlContextMetadata": null,
2671
- },
2672
- },
2673
- "type": "finish",
2674
- "usage": {
2675
- "cachedInputTokens": undefined,
2676
- "inputTokens": 294,
2677
- "outputTokens": 233,
2678
- "reasoningTokens": undefined,
2679
- "totalTokens": 527,
2680
- },
2681
- },
2682
- ]
2683
- `);
2684
- });
2685
- it('should stream text and files in correct order', async () => {
2686
- server.urls[TEST_URL_GEMINI_PRO].response = {
2687
- type: 'stream-chunks',
2688
- chunks: [
2689
- `data: ${JSON.stringify({
2690
- candidates: [
2691
- {
2692
- content: {
2693
- parts: [
2694
- { text: 'Step 1: ' },
2695
- { inlineData: { data: 'image1', mimeType: 'image/png' } },
2696
- { text: ' Step 2: ' },
2697
- { inlineData: { data: 'image2', mimeType: 'image/jpeg' } },
2698
- { text: ' Done' },
2699
- ],
2700
- role: 'model',
2701
- },
2702
- finishReason: 'STOP',
2703
- index: 0,
2704
- safetyRatings: SAFETY_RATINGS,
2705
- },
2706
- ],
2707
- usageMetadata: {
2708
- promptTokenCount: 10,
2709
- candidatesTokenCount: 20,
2710
- totalTokenCount: 30,
2711
- },
2712
- })}\n\n`,
2713
- ],
2714
- };
2715
- const { stream } = await model.doStream({
2716
- prompt: TEST_PROMPT,
2717
- includeRawChunks: false,
2718
- });
2719
- const events = await convertReadableStreamToArray(stream);
2720
- // Filter to content events only (excluding metadata)
2721
- const contentEvents = events.filter(event => event.type === 'text-start' ||
2722
- event.type === 'text-delta' ||
2723
- event.type === 'text-end' ||
2724
- event.type === 'file');
2725
- // Verify that text and file parts are interleaved in the correct order
2726
- expect(contentEvents).toMatchInlineSnapshot(`
2727
- [
2728
- {
2729
- "id": "0",
2730
- "providerMetadata": undefined,
2731
- "type": "text-start",
2732
- },
2733
- {
2734
- "delta": "Step 1: ",
2735
- "id": "0",
2736
- "providerMetadata": undefined,
2737
- "type": "text-delta",
2738
- },
2739
- {
2740
- "data": "image1",
2741
- "mediaType": "image/png",
2742
- "type": "file",
2743
- },
2744
- {
2745
- "delta": " Step 2: ",
2746
- "id": "0",
2747
- "providerMetadata": undefined,
2748
- "type": "text-delta",
2749
- },
2750
- {
2751
- "data": "image2",
2752
- "mediaType": "image/jpeg",
2753
- "type": "file",
2754
- },
2755
- {
2756
- "delta": " Done",
2757
- "id": "0",
2758
- "providerMetadata": undefined,
2759
- "type": "text-delta",
2760
- },
2761
- {
2762
- "id": "0",
2763
- "type": "text-end",
2764
- },
2765
- ]
2766
- `);
2767
- });
2768
- it('should set finishReason to tool-calls when chunk contains functionCall', async () => {
2769
- server.urls[TEST_URL_GEMINI_PRO].response = {
2770
- type: 'stream-chunks',
2771
- chunks: [
2772
- `data: ${JSON.stringify({
2773
- candidates: [
2774
- {
2775
- content: {
2776
- parts: [{ text: 'Initial text response' }],
2777
- role: 'model',
2778
- },
2779
- index: 0,
2780
- safetyRatings: SAFETY_RATINGS,
2781
- },
2782
- ],
2783
- })}\n\n`,
2784
- `data: ${JSON.stringify({
2785
- candidates: [
2786
- {
2787
- content: {
2788
- parts: [
2789
- {
2790
- functionCall: {
2791
- name: 'test-tool',
2792
- args: { value: 'example value' },
2793
- },
2794
- },
2795
- ],
2796
- role: 'model',
2797
- },
2798
- finishReason: 'STOP',
2799
- index: 0,
2800
- safetyRatings: SAFETY_RATINGS,
2801
- },
2802
- ],
2803
- usageMetadata: {
2804
- promptTokenCount: 10,
2805
- candidatesTokenCount: 20,
2806
- totalTokenCount: 30,
2807
- },
2808
- })}\n\n`,
2809
- ],
2810
- };
2811
- const { stream } = await model.doStream({
2812
- tools: [
2813
- {
2814
- type: 'function',
2815
- name: 'test-tool',
2816
- inputSchema: {
2817
- type: 'object',
2818
- properties: { value: { type: 'string' } },
2819
- required: ['value'],
2820
- additionalProperties: false,
2821
- $schema: 'http://json-schema.org/draft-07/schema#',
2822
- },
2823
- },
2824
- ],
2825
- prompt: TEST_PROMPT,
2826
- includeRawChunks: false,
2827
- });
2828
- const events = await convertReadableStreamToArray(stream);
2829
- const finishEvent = events.find(event => event.type === 'finish');
2830
- expect((finishEvent === null || finishEvent === void 0 ? void 0 : finishEvent.type) === 'finish' && finishEvent.finishReason).toEqual('tool-calls');
2831
- });
2832
- it('should only pass valid provider options', async () => {
2833
- prepareStreamResponse({ content: [''] });
2834
- await model.doStream({
2835
- prompt: TEST_PROMPT,
2836
- includeRawChunks: false,
2837
- providerOptions: {
2838
- google: { foo: 'bar', responseModalities: ['TEXT', 'IMAGE'] },
2839
- },
2840
- });
2841
- expect(await getRequestBody(0)).toMatchObject({
2842
- contents: [
2843
- {
2844
- role: 'user',
2845
- parts: [{ text: 'Hello' }],
2846
- },
2847
- ],
2848
- generationConfig: {
2849
- responseModalities: ['TEXT', 'IMAGE'],
2850
- },
2851
- });
2852
- });
2853
- it('should stream reasoning parts separately from text parts', async () => {
2854
- server.urls[TEST_URL_GEMINI_PRO].response = {
2855
- type: 'stream-chunks',
2856
- chunks: [
2857
- `data: ${JSON.stringify({
2858
- candidates: [
2859
- {
2860
- content: {
2861
- parts: [
2862
- {
2863
- text: 'I need to think about this carefully. The user wants a simple explanation.',
2864
- thought: true,
2865
- },
2866
- ],
2867
- role: 'model',
2868
- },
2869
- index: 0,
2870
- },
2871
- ],
2872
- usageMetadata: {
2873
- promptTokenCount: 14,
2874
- totalTokenCount: 84,
2875
- thoughtsTokenCount: 70,
2876
- },
2877
- })}\n\n`,
2878
- `data: ${JSON.stringify({
2879
- candidates: [
2880
- {
2881
- content: {
2882
- parts: [
2883
- {
2884
- text: 'Let me organize my thoughts and provide a clear answer.',
2885
- thought: true,
2886
- },
2887
- ],
2888
- role: 'model',
2889
- },
2890
- index: 0,
2891
- },
2892
- ],
2893
- usageMetadata: {
2894
- promptTokenCount: 14,
2895
- totalTokenCount: 156,
2896
- thoughtsTokenCount: 142,
2897
- },
2898
- })}\n\n`,
2899
- `data: ${JSON.stringify({
2900
- candidates: [
2901
- {
2902
- content: {
2903
- parts: [
2904
- {
2905
- text: 'Here is a simple explanation: ',
2906
- },
2907
- ],
2908
- role: 'model',
2909
- },
2910
- index: 0,
2911
- },
2912
- ],
2913
- usageMetadata: {
2914
- promptTokenCount: 14,
2915
- candidatesTokenCount: 8,
2916
- totalTokenCount: 164,
2917
- thoughtsTokenCount: 142,
2918
- },
2919
- })}\n\n`,
2920
- `data: ${JSON.stringify({
2921
- candidates: [
2922
- {
2923
- content: {
2924
- parts: [
2925
- {
2926
- text: 'The concept works because of basic principles.',
2927
- },
2928
- ],
2929
- role: 'model',
2930
- },
2931
- finishReason: 'STOP',
2932
- index: 0,
2933
- },
2934
- ],
2935
- usageMetadata: {
2936
- promptTokenCount: 14,
2937
- candidatesTokenCount: 18,
2938
- totalTokenCount: 174,
2939
- thoughtsTokenCount: 142,
2940
- },
2941
- })}\n\n`,
2942
- ],
2943
- };
2944
- const { stream } = await model.doStream({
2945
- prompt: TEST_PROMPT,
2946
- includeRawChunks: false,
2947
- });
2948
- const allEvents = await convertReadableStreamToArray(stream);
2949
- expect(allEvents).toMatchInlineSnapshot(`
2950
- [
2951
- {
2952
- "type": "stream-start",
2953
- "warnings": [],
2954
- },
2955
- {
2956
- "id": "0",
2957
- "providerMetadata": undefined,
2958
- "type": "reasoning-start",
2959
- },
2960
- {
2961
- "delta": "I need to think about this carefully. The user wants a simple explanation.",
2962
- "id": "0",
2963
- "providerMetadata": undefined,
2964
- "type": "reasoning-delta",
2965
- },
2966
- {
2967
- "delta": "Let me organize my thoughts and provide a clear answer.",
2968
- "id": "0",
2969
- "providerMetadata": undefined,
2970
- "type": "reasoning-delta",
2971
- },
2972
- {
2973
- "id": "0",
2974
- "type": "reasoning-end",
2975
- },
2976
- {
2977
- "id": "1",
2978
- "providerMetadata": undefined,
2979
- "type": "text-start",
2980
- },
2981
- {
2982
- "delta": "Here is a simple explanation: ",
2983
- "id": "1",
2984
- "providerMetadata": undefined,
2985
- "type": "text-delta",
2986
- },
2987
- {
2988
- "delta": "The concept works because of basic principles.",
2989
- "id": "1",
2990
- "providerMetadata": undefined,
2991
- "type": "text-delta",
2992
- },
2993
- {
2994
- "id": "1",
2995
- "type": "text-end",
2996
- },
2997
- {
2998
- "finishReason": "stop",
2999
- "providerMetadata": {
3000
- "google": {
3001
- "groundingMetadata": null,
3002
- "promptFeedback": null,
3003
- "safetyRatings": null,
3004
- "urlContextMetadata": null,
3005
- "usageMetadata": {
3006
- "candidatesTokenCount": 18,
3007
- "promptTokenCount": 14,
3008
- "thoughtsTokenCount": 142,
3009
- "totalTokenCount": 174,
3010
- },
3011
- },
3012
- },
3013
- "type": "finish",
3014
- "usage": {
3015
- "cachedInputTokens": undefined,
3016
- "inputTokens": 14,
3017
- "outputTokens": 18,
3018
- "reasoningTokens": 142,
3019
- "totalTokens": 174,
3020
- },
3021
- },
3022
- ]
3023
- `);
3024
- });
3025
- it('should stream thought signatures with reasoning and text parts', async () => {
3026
- server.urls[TEST_URL_GEMINI_PRO].response = {
3027
- type: 'stream-chunks',
3028
- chunks: [
3029
- `data: ${JSON.stringify({
3030
- candidates: [
3031
- {
3032
- content: {
3033
- parts: [
3034
- {
3035
- text: 'I need to think about this.',
3036
- thought: true,
3037
- thoughtSignature: 'reasoning_sig1',
3038
- },
3039
- ],
3040
- role: 'model',
3041
- },
3042
- index: 0,
3043
- },
3044
- ],
3045
- })}\n\n`,
3046
- `data: ${JSON.stringify({
3047
- candidates: [
3048
- {
3049
- content: {
3050
- parts: [
3051
- {
3052
- text: 'Here is the answer.',
3053
- thoughtSignature: 'text_sig1',
3054
- },
3055
- ],
3056
- role: 'model',
3057
- },
3058
- index: 0,
3059
- finishReason: 'STOP',
3060
- safetyRatings: SAFETY_RATINGS,
3061
- },
3062
- ],
3063
- })}\n\n`,
3064
- ],
3065
- };
3066
- const { stream } = await model.doStream({
3067
- prompt: TEST_PROMPT,
3068
- });
3069
- const chunks = await convertReadableStreamToArray(stream);
3070
- expect(chunks).toMatchInlineSnapshot(`
3071
- [
3072
- {
3073
- "type": "stream-start",
3074
- "warnings": [],
3075
- },
3076
- {
3077
- "id": "0",
3078
- "providerMetadata": {
3079
- "google": {
3080
- "thoughtSignature": "reasoning_sig1",
3081
- },
3082
- },
3083
- "type": "reasoning-start",
3084
- },
3085
- {
3086
- "delta": "I need to think about this.",
3087
- "id": "0",
3088
- "providerMetadata": {
3089
- "google": {
3090
- "thoughtSignature": "reasoning_sig1",
3091
- },
3092
- },
3093
- "type": "reasoning-delta",
3094
- },
3095
- {
3096
- "id": "0",
3097
- "type": "reasoning-end",
3098
- },
3099
- {
3100
- "id": "1",
3101
- "providerMetadata": {
3102
- "google": {
3103
- "thoughtSignature": "text_sig1",
3104
- },
3105
- },
3106
- "type": "text-start",
3107
- },
3108
- {
3109
- "delta": "Here is the answer.",
3110
- "id": "1",
3111
- "providerMetadata": {
3112
- "google": {
3113
- "thoughtSignature": "text_sig1",
3114
- },
3115
- },
3116
- "type": "text-delta",
3117
- },
3118
- {
3119
- "id": "1",
3120
- "type": "text-end",
3121
- },
3122
- {
3123
- "finishReason": "stop",
3124
- "providerMetadata": {
3125
- "google": {
3126
- "groundingMetadata": null,
3127
- "promptFeedback": null,
3128
- "safetyRatings": [
3129
- {
3130
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
3131
- "probability": "NEGLIGIBLE",
3132
- },
3133
- {
3134
- "category": "HARM_CATEGORY_HATE_SPEECH",
3135
- "probability": "NEGLIGIBLE",
3136
- },
3137
- {
3138
- "category": "HARM_CATEGORY_HARASSMENT",
3139
- "probability": "NEGLIGIBLE",
3140
- },
3141
- {
3142
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
3143
- "probability": "NEGLIGIBLE",
3144
- },
3145
- ],
3146
- "urlContextMetadata": null,
3147
- },
3148
- },
3149
- "type": "finish",
3150
- "usage": {
3151
- "inputTokens": undefined,
3152
- "outputTokens": undefined,
3153
- "totalTokens": undefined,
3154
- },
3155
- },
3156
- ]
3157
- `);
3158
- });
3159
- describe('raw chunks', () => {
3160
- it('should include raw chunks when includeRawChunks is enabled', async () => {
3161
- prepareStreamResponse({
3162
- content: ['Hello', ' World!'],
3163
- });
3164
- const { stream } = await model.doStream({
3165
- prompt: TEST_PROMPT,
3166
- includeRawChunks: true,
3167
- });
3168
- const chunks = await convertReadableStreamToArray(stream);
3169
- expect(chunks.filter(chunk => chunk.type === 'raw'))
3170
- .toMatchInlineSnapshot(`
3171
- [
3172
- {
3173
- "rawValue": {
3174
- "candidates": [
3175
- {
3176
- "content": {
3177
- "parts": [
3178
- {
3179
- "text": "Hello",
3180
- },
3181
- ],
3182
- "role": "model",
3183
- },
3184
- "finishReason": "STOP",
3185
- "index": 0,
3186
- "safetyRatings": [
3187
- {
3188
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
3189
- "probability": "NEGLIGIBLE",
3190
- },
3191
- {
3192
- "category": "HARM_CATEGORY_HATE_SPEECH",
3193
- "probability": "NEGLIGIBLE",
3194
- },
3195
- {
3196
- "category": "HARM_CATEGORY_HARASSMENT",
3197
- "probability": "NEGLIGIBLE",
3198
- },
3199
- {
3200
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
3201
- "probability": "NEGLIGIBLE",
3202
- },
3203
- ],
3204
- },
3205
- ],
3206
- },
3207
- "type": "raw",
3208
- },
3209
- {
3210
- "rawValue": {
3211
- "candidates": [
3212
- {
3213
- "content": {
3214
- "parts": [
3215
- {
3216
- "text": " World!",
3217
- },
3218
- ],
3219
- "role": "model",
3220
- },
3221
- "finishReason": "STOP",
3222
- "index": 0,
3223
- "safetyRatings": [
3224
- {
3225
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
3226
- "probability": "NEGLIGIBLE",
3227
- },
3228
- {
3229
- "category": "HARM_CATEGORY_HATE_SPEECH",
3230
- "probability": "NEGLIGIBLE",
3231
- },
3232
- {
3233
- "category": "HARM_CATEGORY_HARASSMENT",
3234
- "probability": "NEGLIGIBLE",
3235
- },
3236
- {
3237
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
3238
- "probability": "NEGLIGIBLE",
3239
- },
3240
- ],
3241
- },
3242
- ],
3243
- "usageMetadata": {
3244
- "candidatesTokenCount": 233,
3245
- "promptTokenCount": 294,
3246
- "totalTokenCount": 527,
3247
- },
3248
- },
3249
- "type": "raw",
3250
- },
3251
- ]
3252
- `);
3253
- });
3254
- it('should not include raw chunks when includeRawChunks is false', async () => {
3255
- prepareStreamResponse({
3256
- content: ['Hello', ' World!'],
3257
- });
3258
- const { stream } = await model.doStream({
3259
- prompt: TEST_PROMPT,
3260
- includeRawChunks: false,
3261
- });
3262
- const chunks = await convertReadableStreamToArray(stream);
3263
- expect(chunks.filter(chunk => chunk.type === 'raw')).toHaveLength(0);
3264
- });
3265
- });
3266
- });
3267
- describe('GEMMA Model System Instruction Fix', () => {
3268
- const TEST_PROMPT_WITH_SYSTEM = [
3269
- { role: 'system', content: 'You are a helpful assistant.' },
3270
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
3271
- ];
3272
- const TEST_URL_GEMMA_3_12B_IT = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
3273
- const TEST_URL_GEMMA_3_27B_IT = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
3274
- const TEST_URL_GEMINI_PRO = 'https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent';
3275
- const server = createTestServer({
3276
- [TEST_URL_GEMMA_3_12B_IT]: {},
3277
- });
3278
- const getRequestBody = async (index = 0) => {
3279
- var _a;
3280
- const body = await server.calls[index].requestBodyJson;
3281
- return (_a = body === null || body === void 0 ? void 0 : body.request) !== null && _a !== void 0 ? _a : body;
3282
- };
3283
- it('should NOT send systemInstruction for GEMMA-3-12b-it model', async () => {
3284
- server.urls[TEST_URL_GEMMA_3_12B_IT].response = {
3285
- type: 'stream-chunks',
3286
- headers: { 'content-type': 'text/event-stream' },
3287
- chunks: ['data: ' + JSON.stringify({
3288
- candidates: [
3289
- {
3290
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3291
- finishReason: 'STOP',
3292
- index: 0,
3293
- },
3294
- ],
3295
- }) + '\n\n'],
3296
- };
3297
- const model = new GoogleGenerativeAILanguageModel('gemma-3-12b-it', {
3298
- provider: 'google.generative-ai',
3299
- baseURL: 'https://cloudcode-pa.googleapis.com',
3300
- headers: { 'x-goog-api-key': 'test-api-key' },
3301
- generateId: () => 'test-id',
3302
- });
3303
- await model.doGenerate({
3304
- prompt: TEST_PROMPT_WITH_SYSTEM,
3305
- });
3306
- // Verify that systemInstruction was NOT sent for GEMMA model
3307
- const requestBody = await getRequestBody(server.calls.length - 1);
3308
- expect(requestBody).not.toHaveProperty('systemInstruction');
3309
- });
3310
- it('should NOT send systemInstruction for GEMMA-3-27b-it model', async () => {
3311
- server.urls[TEST_URL_GEMMA_3_27B_IT].response = {
3312
- type: 'stream-chunks',
3313
- headers: { 'content-type': 'text/event-stream' },
3314
- chunks: ['data: ' + JSON.stringify({
3315
- candidates: [
3316
- {
3317
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3318
- finishReason: 'STOP',
3319
- index: 0,
3320
- },
3321
- ],
3322
- }) + '\n\n'],
3323
- };
3324
- const model = new GoogleGenerativeAILanguageModel('gemma-3-27b-it', {
3325
- provider: 'google.generative-ai',
3326
- baseURL: 'https://cloudcode-pa.googleapis.com',
3327
- headers: { 'x-goog-api-key': 'test-api-key' },
3328
- generateId: () => 'test-id',
3329
- });
3330
- await model.doGenerate({
3331
- prompt: TEST_PROMPT_WITH_SYSTEM,
3332
- });
3333
- const requestBody = await getRequestBody(server.calls.length - 1);
3334
- expect(requestBody).not.toHaveProperty('systemInstruction');
3335
- });
3336
- it('should still send systemInstruction for Gemini models (regression test)', async () => {
3337
- server.urls[TEST_URL_GEMINI_PRO].response = {
3338
- type: 'stream-chunks',
3339
- headers: { 'content-type': 'text/event-stream' },
3340
- chunks: ['data: ' + JSON.stringify({
3341
- candidates: [
3342
- {
3343
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3344
- finishReason: 'STOP',
3345
- index: 0,
3346
- },
3347
- ],
3348
- }) + '\n\n'],
3349
- };
3350
- const model = new GoogleGenerativeAILanguageModel('gemini-pro', {
3351
- provider: 'google.generative-ai',
3352
- baseURL: 'https://cloudcode-pa.googleapis.com',
3353
- headers: { 'x-goog-api-key': 'test-api-key' },
3354
- generateId: () => 'test-id',
3355
- });
3356
- await model.doGenerate({
3357
- prompt: TEST_PROMPT_WITH_SYSTEM,
3358
- });
3359
- const requestBody = await getRequestBody(server.calls.length - 1);
3360
- expect(requestBody).toHaveProperty('systemInstruction');
3361
- expect(requestBody.systemInstruction).toEqual({
3362
- parts: [{ text: 'You are a helpful assistant.' }],
3363
- });
3364
- });
3365
- it('should NOT generate warning when GEMMA model is used without system instructions', async () => {
3366
- server.urls[TEST_URL_GEMMA_3_12B_IT].response = {
3367
- type: 'stream-chunks',
3368
- headers: { 'content-type': 'text/event-stream' },
3369
- chunks: ['data: ' + JSON.stringify({
3370
- candidates: [
3371
- {
3372
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3373
- finishReason: 'STOP',
3374
- index: 0,
3375
- },
3376
- ],
3377
- }) + '\n\n'],
3378
- };
3379
- const model = new GoogleGenerativeAILanguageModel('gemma-3-12b-it', {
3380
- provider: 'google.generative-ai',
3381
- baseURL: 'https://cloudcode-pa.googleapis.com',
3382
- headers: { 'x-goog-api-key': 'test-api-key' },
3383
- generateId: () => 'test-id',
3384
- });
3385
- const TEST_PROMPT_WITHOUT_SYSTEM = [
3386
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
3387
- ];
3388
- const { warnings } = await model.doGenerate({
3389
- prompt: TEST_PROMPT_WITHOUT_SYSTEM,
3390
- });
3391
- expect(warnings).toHaveLength(0);
3392
- });
3393
- it('should NOT generate warning when Gemini model is used with system instructions', async () => {
3394
- server.urls[TEST_URL_GEMINI_PRO].response = {
3395
- type: 'stream-chunks',
3396
- headers: { 'content-type': 'text/event-stream' },
3397
- chunks: ['data: ' + JSON.stringify({
3398
- candidates: [
3399
- {
3400
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3401
- finishReason: 'STOP',
3402
- index: 0,
3403
- },
3404
- ],
3405
- }) + '\n\n'],
3406
- };
3407
- const model = new GoogleGenerativeAILanguageModel('gemini-pro', {
3408
- provider: 'google.generative-ai',
3409
- baseURL: 'https://cloudcode-pa.googleapis.com',
3410
- headers: { 'x-goog-api-key': 'test-api-key' },
3411
- generateId: () => 'test-id',
3412
- });
3413
- const { warnings } = await model.doGenerate({
3414
- prompt: TEST_PROMPT_WITH_SYSTEM,
3415
- });
3416
- expect(warnings).toHaveLength(0);
3417
- });
3418
- it('should prepend system instruction to first user message for GEMMA models', async () => {
3419
- server.urls[TEST_URL_GEMMA_3_12B_IT].response = {
3420
- type: 'stream-chunks',
3421
- headers: { 'content-type': 'text/event-stream' },
3422
- chunks: ['data: ' + JSON.stringify({
3423
- candidates: [
3424
- {
3425
- content: { parts: [{ text: 'Hello!' }], role: 'model' },
3426
- finishReason: 'STOP',
3427
- index: 0,
3428
- },
3429
- ],
3430
- }) + '\n\n'],
3431
- };
3432
- const model = new GoogleGenerativeAILanguageModel('gemma-3-12b-it', {
3433
- provider: 'google.generative-ai',
3434
- baseURL: 'https://cloudcode-pa.googleapis.com',
3435
- headers: { 'x-goog-api-key': 'test-api-key' },
3436
- generateId: () => 'test-id',
3437
- });
3438
- await model.doGenerate({
3439
- prompt: TEST_PROMPT_WITH_SYSTEM,
3440
- });
3441
- const requestBody = await getRequestBody(server.calls.length - 1);
3442
- expect(requestBody).toMatchInlineSnapshot(`
3443
- {
3444
- "contents": [
3445
- {
3446
- "parts": [
3447
- {
3448
- "text": "You are a helpful assistant.
3449
-
3450
- ",
3451
- },
3452
- {
3453
- "text": "Hello",
3454
- },
3455
- ],
3456
- "role": "user",
3457
- },
3458
- ],
3459
- "generationConfig": {},
3460
- }
3461
- `);
3462
- });
3463
- });