@depup/ai-sdk__google 3.0.43-depup.0

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 (48) hide show
  1. package/CHANGELOG.md +2531 -0
  2. package/LICENSE +13 -0
  3. package/README.md +25 -0
  4. package/changes.json +5 -0
  5. package/dist/index.d.mts +367 -0
  6. package/dist/index.d.ts +367 -0
  7. package/dist/index.js +2404 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +2454 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/internal/index.d.mts +283 -0
  12. package/dist/internal/index.d.ts +283 -0
  13. package/dist/internal/index.js +1670 -0
  14. package/dist/internal/index.js.map +1 -0
  15. package/dist/internal/index.mjs +1678 -0
  16. package/dist/internal/index.mjs.map +1 -0
  17. package/docs/15-google-generative-ai.mdx +1298 -0
  18. package/internal.d.ts +1 -0
  19. package/package.json +96 -0
  20. package/src/convert-google-generative-ai-usage.ts +51 -0
  21. package/src/convert-json-schema-to-openapi-schema.ts +158 -0
  22. package/src/convert-to-google-generative-ai-messages.ts +236 -0
  23. package/src/get-model-path.ts +3 -0
  24. package/src/google-error.ts +26 -0
  25. package/src/google-generative-ai-embedding-model.ts +159 -0
  26. package/src/google-generative-ai-embedding-options.ts +51 -0
  27. package/src/google-generative-ai-image-model.ts +359 -0
  28. package/src/google-generative-ai-image-settings.ts +17 -0
  29. package/src/google-generative-ai-language-model.ts +1056 -0
  30. package/src/google-generative-ai-options.ts +198 -0
  31. package/src/google-generative-ai-prompt.ts +38 -0
  32. package/src/google-generative-ai-video-model.ts +374 -0
  33. package/src/google-generative-ai-video-settings.ts +8 -0
  34. package/src/google-prepare-tools.ts +254 -0
  35. package/src/google-provider.ts +227 -0
  36. package/src/google-supported-file-url.ts +20 -0
  37. package/src/google-tools.ts +71 -0
  38. package/src/index.ts +29 -0
  39. package/src/internal/index.ts +3 -0
  40. package/src/map-google-generative-ai-finish-reason.ts +29 -0
  41. package/src/tool/code-execution.ts +35 -0
  42. package/src/tool/enterprise-web-search.ts +18 -0
  43. package/src/tool/file-search.ts +51 -0
  44. package/src/tool/google-maps.ts +14 -0
  45. package/src/tool/google-search.ts +43 -0
  46. package/src/tool/url-context.ts +16 -0
  47. package/src/tool/vertex-rag-store.ts +31 -0
  48. package/src/version.ts +6 -0
@@ -0,0 +1,1056 @@
1
+ import {
2
+ LanguageModelV3,
3
+ LanguageModelV3CallOptions,
4
+ LanguageModelV3Content,
5
+ LanguageModelV3FinishReason,
6
+ LanguageModelV3GenerateResult,
7
+ LanguageModelV3Source,
8
+ LanguageModelV3StreamPart,
9
+ LanguageModelV3StreamResult,
10
+ SharedV3ProviderMetadata,
11
+ SharedV3Warning,
12
+ } from '@ai-sdk/provider';
13
+ import {
14
+ combineHeaders,
15
+ createEventSourceResponseHandler,
16
+ createJsonResponseHandler,
17
+ FetchFunction,
18
+ generateId,
19
+ InferSchema,
20
+ lazySchema,
21
+ parseProviderOptions,
22
+ ParseResult,
23
+ postJsonToApi,
24
+ Resolvable,
25
+ resolve,
26
+ zodSchema,
27
+ } from '@ai-sdk/provider-utils';
28
+ import { z } from 'zod/v4';
29
+ import {
30
+ convertGoogleGenerativeAIUsage,
31
+ GoogleGenerativeAIUsageMetadata,
32
+ } from './convert-google-generative-ai-usage';
33
+ import { convertJSONSchemaToOpenAPISchema } from './convert-json-schema-to-openapi-schema';
34
+ import { convertToGoogleGenerativeAIMessages } from './convert-to-google-generative-ai-messages';
35
+ import { getModelPath } from './get-model-path';
36
+ import { googleFailedResponseHandler } from './google-error';
37
+ import {
38
+ GoogleGenerativeAIModelId,
39
+ googleLanguageModelOptions,
40
+ } from './google-generative-ai-options';
41
+ import { GoogleGenerativeAIContentPart } from './google-generative-ai-prompt';
42
+ import { prepareTools } from './google-prepare-tools';
43
+ import { mapGoogleGenerativeAIFinishReason } from './map-google-generative-ai-finish-reason';
44
+
45
+ type GoogleGenerativeAIConfig = {
46
+ provider: string;
47
+ baseURL: string;
48
+ headers: Resolvable<Record<string, string | undefined>>;
49
+ fetch?: FetchFunction;
50
+ generateId: () => string;
51
+
52
+ /**
53
+ * The supported URLs for the model.
54
+ */
55
+ supportedUrls?: () => LanguageModelV3['supportedUrls'];
56
+ };
57
+
58
+ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
59
+ readonly specificationVersion = 'v3';
60
+
61
+ readonly modelId: GoogleGenerativeAIModelId;
62
+
63
+ private readonly config: GoogleGenerativeAIConfig;
64
+ private readonly generateId: () => string;
65
+
66
+ constructor(
67
+ modelId: GoogleGenerativeAIModelId,
68
+ config: GoogleGenerativeAIConfig,
69
+ ) {
70
+ this.modelId = modelId;
71
+ this.config = config;
72
+ this.generateId = config.generateId ?? generateId;
73
+ }
74
+
75
+ get provider(): string {
76
+ return this.config.provider;
77
+ }
78
+
79
+ get supportedUrls() {
80
+ return this.config.supportedUrls?.() ?? {};
81
+ }
82
+
83
+ private async getArgs({
84
+ prompt,
85
+ maxOutputTokens,
86
+ temperature,
87
+ topP,
88
+ topK,
89
+ frequencyPenalty,
90
+ presencePenalty,
91
+ stopSequences,
92
+ responseFormat,
93
+ seed,
94
+ tools,
95
+ toolChoice,
96
+ providerOptions,
97
+ }: LanguageModelV3CallOptions) {
98
+ const warnings: SharedV3Warning[] = [];
99
+
100
+ const providerOptionsName = this.config.provider.includes('vertex')
101
+ ? 'vertex'
102
+ : 'google';
103
+ let googleOptions = await parseProviderOptions({
104
+ provider: providerOptionsName,
105
+ providerOptions,
106
+ schema: googleLanguageModelOptions,
107
+ });
108
+
109
+ if (googleOptions == null && providerOptionsName !== 'google') {
110
+ googleOptions = await parseProviderOptions({
111
+ provider: 'google',
112
+ providerOptions,
113
+ schema: googleLanguageModelOptions,
114
+ });
115
+ }
116
+
117
+ // Add warning if Vertex rag tools are used with a non-Vertex Google provider
118
+ if (
119
+ tools?.some(
120
+ tool =>
121
+ tool.type === 'provider' && tool.id === 'google.vertex_rag_store',
122
+ ) &&
123
+ !this.config.provider.startsWith('google.vertex.')
124
+ ) {
125
+ warnings.push({
126
+ type: 'other',
127
+ message:
128
+ "The 'vertex_rag_store' tool is only supported with the Google Vertex provider " +
129
+ 'and might not be supported or could behave unexpectedly with the current Google provider ' +
130
+ `(${this.config.provider}).`,
131
+ });
132
+ }
133
+
134
+ const isGemmaModel = this.modelId.toLowerCase().startsWith('gemma-');
135
+
136
+ const { contents, systemInstruction } = convertToGoogleGenerativeAIMessages(
137
+ prompt,
138
+ { isGemmaModel, providerOptionsName },
139
+ );
140
+
141
+ const {
142
+ tools: googleTools,
143
+ toolConfig: googleToolConfig,
144
+ toolWarnings,
145
+ } = prepareTools({
146
+ tools,
147
+ toolChoice,
148
+ modelId: this.modelId,
149
+ });
150
+
151
+ return {
152
+ args: {
153
+ generationConfig: {
154
+ // standardized settings:
155
+ maxOutputTokens,
156
+ temperature,
157
+ topK,
158
+ topP,
159
+ frequencyPenalty,
160
+ presencePenalty,
161
+ stopSequences,
162
+ seed,
163
+
164
+ // response format:
165
+ responseMimeType:
166
+ responseFormat?.type === 'json' ? 'application/json' : undefined,
167
+ responseSchema:
168
+ responseFormat?.type === 'json' &&
169
+ responseFormat.schema != null &&
170
+ // Google GenAI does not support all OpenAPI Schema features,
171
+ // so this is needed as an escape hatch:
172
+ // TODO convert into provider option
173
+ (googleOptions?.structuredOutputs ?? true)
174
+ ? convertJSONSchemaToOpenAPISchema(responseFormat.schema)
175
+ : undefined,
176
+ ...(googleOptions?.audioTimestamp && {
177
+ audioTimestamp: googleOptions.audioTimestamp,
178
+ }),
179
+
180
+ // provider options:
181
+ responseModalities: googleOptions?.responseModalities,
182
+ thinkingConfig: googleOptions?.thinkingConfig,
183
+ ...(googleOptions?.mediaResolution && {
184
+ mediaResolution: googleOptions.mediaResolution,
185
+ }),
186
+ ...(googleOptions?.imageConfig && {
187
+ imageConfig: googleOptions.imageConfig,
188
+ }),
189
+ },
190
+ contents,
191
+ systemInstruction: isGemmaModel ? undefined : systemInstruction,
192
+ safetySettings: googleOptions?.safetySettings,
193
+ tools: googleTools,
194
+ toolConfig: googleOptions?.retrievalConfig
195
+ ? {
196
+ ...googleToolConfig,
197
+ retrievalConfig: googleOptions.retrievalConfig,
198
+ }
199
+ : googleToolConfig,
200
+ cachedContent: googleOptions?.cachedContent,
201
+ labels: googleOptions?.labels,
202
+ },
203
+ warnings: [...warnings, ...toolWarnings],
204
+ providerOptionsName,
205
+ };
206
+ }
207
+
208
+ async doGenerate(
209
+ options: LanguageModelV3CallOptions,
210
+ ): Promise<LanguageModelV3GenerateResult> {
211
+ const { args, warnings, providerOptionsName } = await this.getArgs(options);
212
+
213
+ const mergedHeaders = combineHeaders(
214
+ await resolve(this.config.headers),
215
+ options.headers,
216
+ );
217
+
218
+ const {
219
+ responseHeaders,
220
+ value: response,
221
+ rawValue: rawResponse,
222
+ } = await postJsonToApi({
223
+ url: `${this.config.baseURL}/${getModelPath(
224
+ this.modelId,
225
+ )}:generateContent`,
226
+ headers: mergedHeaders,
227
+ body: args,
228
+ failedResponseHandler: googleFailedResponseHandler,
229
+ successfulResponseHandler: createJsonResponseHandler(responseSchema),
230
+ abortSignal: options.abortSignal,
231
+ fetch: this.config.fetch,
232
+ });
233
+
234
+ const candidate = response.candidates[0];
235
+ const content: Array<LanguageModelV3Content> = [];
236
+
237
+ // map ordered parts to content:
238
+ const parts = candidate.content?.parts ?? [];
239
+
240
+ const usageMetadata = response.usageMetadata;
241
+
242
+ // Associates a code execution result with its preceding call.
243
+ let lastCodeExecutionToolCallId: string | undefined;
244
+
245
+ // Build content array from all parts
246
+ for (const part of parts) {
247
+ if ('executableCode' in part && part.executableCode?.code) {
248
+ const toolCallId = this.config.generateId();
249
+ lastCodeExecutionToolCallId = toolCallId;
250
+
251
+ content.push({
252
+ type: 'tool-call',
253
+ toolCallId,
254
+ toolName: 'code_execution',
255
+ input: JSON.stringify(part.executableCode),
256
+ providerExecuted: true,
257
+ });
258
+ } else if ('codeExecutionResult' in part && part.codeExecutionResult) {
259
+ content.push({
260
+ type: 'tool-result',
261
+ // Assumes a result directly follows its corresponding call part.
262
+ toolCallId: lastCodeExecutionToolCallId!,
263
+ toolName: 'code_execution',
264
+ result: {
265
+ outcome: part.codeExecutionResult.outcome,
266
+ output: part.codeExecutionResult.output ?? '',
267
+ },
268
+ });
269
+ // Clear the ID after use to avoid accidental reuse.
270
+ lastCodeExecutionToolCallId = undefined;
271
+ } else if ('text' in part && part.text != null) {
272
+ const thoughtSignatureMetadata = part.thoughtSignature
273
+ ? {
274
+ [providerOptionsName]: {
275
+ thoughtSignature: part.thoughtSignature,
276
+ },
277
+ }
278
+ : undefined;
279
+
280
+ if (part.text.length === 0) {
281
+ if (thoughtSignatureMetadata != null && content.length > 0) {
282
+ const lastContent = content[content.length - 1];
283
+ lastContent.providerMetadata = thoughtSignatureMetadata;
284
+ }
285
+ } else {
286
+ content.push({
287
+ type: part.thought === true ? 'reasoning' : 'text',
288
+ text: part.text,
289
+ providerMetadata: thoughtSignatureMetadata,
290
+ });
291
+ }
292
+ } else if ('functionCall' in part) {
293
+ content.push({
294
+ type: 'tool-call' as const,
295
+ toolCallId: this.config.generateId(),
296
+ toolName: part.functionCall.name,
297
+ input: JSON.stringify(part.functionCall.args),
298
+ providerMetadata: part.thoughtSignature
299
+ ? {
300
+ [providerOptionsName]: {
301
+ thoughtSignature: part.thoughtSignature,
302
+ },
303
+ }
304
+ : undefined,
305
+ });
306
+ } else if ('inlineData' in part) {
307
+ content.push({
308
+ type: 'file' as const,
309
+ data: part.inlineData.data,
310
+ mediaType: part.inlineData.mimeType,
311
+ providerMetadata: part.thoughtSignature
312
+ ? {
313
+ [providerOptionsName]: {
314
+ thoughtSignature: part.thoughtSignature,
315
+ },
316
+ }
317
+ : undefined,
318
+ });
319
+ }
320
+ }
321
+
322
+ const sources =
323
+ extractSources({
324
+ groundingMetadata: candidate.groundingMetadata,
325
+ generateId: this.config.generateId,
326
+ }) ?? [];
327
+ for (const source of sources) {
328
+ content.push(source);
329
+ }
330
+
331
+ return {
332
+ content,
333
+ finishReason: {
334
+ unified: mapGoogleGenerativeAIFinishReason({
335
+ finishReason: candidate.finishReason,
336
+ // Only count client-executed tool calls for finish reason determination.
337
+ hasToolCalls: content.some(
338
+ part => part.type === 'tool-call' && !part.providerExecuted,
339
+ ),
340
+ }),
341
+ raw: candidate.finishReason ?? undefined,
342
+ },
343
+ usage: convertGoogleGenerativeAIUsage(usageMetadata),
344
+ warnings,
345
+ providerMetadata: {
346
+ [providerOptionsName]: {
347
+ promptFeedback: response.promptFeedback ?? null,
348
+ groundingMetadata: candidate.groundingMetadata ?? null,
349
+ urlContextMetadata: candidate.urlContextMetadata ?? null,
350
+ safetyRatings: candidate.safetyRatings ?? null,
351
+ usageMetadata: usageMetadata ?? null,
352
+ },
353
+ },
354
+ request: { body: args },
355
+ response: {
356
+ // TODO timestamp, model id, id
357
+ headers: responseHeaders,
358
+ body: rawResponse,
359
+ },
360
+ };
361
+ }
362
+
363
+ async doStream(
364
+ options: LanguageModelV3CallOptions,
365
+ ): Promise<LanguageModelV3StreamResult> {
366
+ const { args, warnings, providerOptionsName } = await this.getArgs(options);
367
+
368
+ const headers = combineHeaders(
369
+ await resolve(this.config.headers),
370
+ options.headers,
371
+ );
372
+
373
+ const { responseHeaders, value: response } = await postJsonToApi({
374
+ url: `${this.config.baseURL}/${getModelPath(
375
+ this.modelId,
376
+ )}:streamGenerateContent?alt=sse`,
377
+ headers,
378
+ body: args,
379
+ failedResponseHandler: googleFailedResponseHandler,
380
+ successfulResponseHandler: createEventSourceResponseHandler(chunkSchema),
381
+ abortSignal: options.abortSignal,
382
+ fetch: this.config.fetch,
383
+ });
384
+
385
+ let finishReason: LanguageModelV3FinishReason = {
386
+ unified: 'other',
387
+ raw: undefined,
388
+ };
389
+ let usage: GoogleGenerativeAIUsageMetadata | undefined = undefined;
390
+ let providerMetadata: SharedV3ProviderMetadata | undefined = undefined;
391
+
392
+ const generateId = this.config.generateId;
393
+ let hasToolCalls = false;
394
+
395
+ // Track active blocks to group consecutive parts of same type
396
+ let currentTextBlockId: string | null = null;
397
+ let currentReasoningBlockId: string | null = null;
398
+ let blockCounter = 0;
399
+
400
+ // Track emitted sources to prevent duplicates
401
+ const emittedSourceUrls = new Set<string>();
402
+ // Associates a code execution result with its preceding call.
403
+ let lastCodeExecutionToolCallId: string | undefined;
404
+
405
+ return {
406
+ stream: response.pipeThrough(
407
+ new TransformStream<
408
+ ParseResult<ChunkSchema>,
409
+ LanguageModelV3StreamPart
410
+ >({
411
+ start(controller) {
412
+ controller.enqueue({ type: 'stream-start', warnings });
413
+ },
414
+
415
+ transform(chunk, controller) {
416
+ if (options.includeRawChunks) {
417
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
418
+ }
419
+
420
+ if (!chunk.success) {
421
+ controller.enqueue({ type: 'error', error: chunk.error });
422
+ return;
423
+ }
424
+
425
+ const value = chunk.value;
426
+
427
+ const usageMetadata = value.usageMetadata;
428
+
429
+ if (usageMetadata != null) {
430
+ usage = usageMetadata;
431
+ }
432
+
433
+ const candidate = value.candidates?.[0];
434
+
435
+ // sometimes the API returns an empty candidates array
436
+ if (candidate == null) {
437
+ return;
438
+ }
439
+
440
+ const content = candidate.content;
441
+
442
+ const sources = extractSources({
443
+ groundingMetadata: candidate.groundingMetadata,
444
+ generateId,
445
+ });
446
+ if (sources != null) {
447
+ for (const source of sources) {
448
+ if (
449
+ source.sourceType === 'url' &&
450
+ !emittedSourceUrls.has(source.url)
451
+ ) {
452
+ emittedSourceUrls.add(source.url);
453
+ controller.enqueue(source);
454
+ }
455
+ }
456
+ }
457
+
458
+ // Process tool call's parts before determining finishReason to ensure hasToolCalls is properly set
459
+ if (content != null) {
460
+ // Process all parts in a single loop to preserve original order
461
+ const parts = content.parts ?? [];
462
+ for (const part of parts) {
463
+ if ('executableCode' in part && part.executableCode?.code) {
464
+ const toolCallId = generateId();
465
+ lastCodeExecutionToolCallId = toolCallId;
466
+
467
+ controller.enqueue({
468
+ type: 'tool-call',
469
+ toolCallId,
470
+ toolName: 'code_execution',
471
+ input: JSON.stringify(part.executableCode),
472
+ providerExecuted: true,
473
+ });
474
+ } else if (
475
+ 'codeExecutionResult' in part &&
476
+ part.codeExecutionResult
477
+ ) {
478
+ // Assumes a result directly follows its corresponding call part.
479
+ const toolCallId = lastCodeExecutionToolCallId;
480
+
481
+ if (toolCallId) {
482
+ controller.enqueue({
483
+ type: 'tool-result',
484
+ toolCallId,
485
+ toolName: 'code_execution',
486
+ result: {
487
+ outcome: part.codeExecutionResult.outcome,
488
+ output: part.codeExecutionResult.output ?? '',
489
+ },
490
+ });
491
+ // Clear the ID after use.
492
+ lastCodeExecutionToolCallId = undefined;
493
+ }
494
+ } else if ('text' in part && part.text != null) {
495
+ const thoughtSignatureMetadata = part.thoughtSignature
496
+ ? {
497
+ [providerOptionsName]: {
498
+ thoughtSignature: part.thoughtSignature,
499
+ },
500
+ }
501
+ : undefined;
502
+
503
+ if (part.text.length === 0) {
504
+ if (
505
+ thoughtSignatureMetadata != null &&
506
+ currentTextBlockId !== null
507
+ ) {
508
+ controller.enqueue({
509
+ type: 'text-delta',
510
+ id: currentTextBlockId,
511
+ delta: '',
512
+ providerMetadata: thoughtSignatureMetadata,
513
+ });
514
+ }
515
+ } else if (part.thought === true) {
516
+ // End any active text block before starting reasoning
517
+ if (currentTextBlockId !== null) {
518
+ controller.enqueue({
519
+ type: 'text-end',
520
+ id: currentTextBlockId,
521
+ });
522
+ currentTextBlockId = null;
523
+ }
524
+
525
+ // Start new reasoning block if not already active
526
+ if (currentReasoningBlockId === null) {
527
+ currentReasoningBlockId = String(blockCounter++);
528
+ controller.enqueue({
529
+ type: 'reasoning-start',
530
+ id: currentReasoningBlockId,
531
+ providerMetadata: thoughtSignatureMetadata,
532
+ });
533
+ }
534
+
535
+ controller.enqueue({
536
+ type: 'reasoning-delta',
537
+ id: currentReasoningBlockId,
538
+ delta: part.text,
539
+ providerMetadata: thoughtSignatureMetadata,
540
+ });
541
+ } else {
542
+ if (currentReasoningBlockId !== null) {
543
+ controller.enqueue({
544
+ type: 'reasoning-end',
545
+ id: currentReasoningBlockId,
546
+ });
547
+ currentReasoningBlockId = null;
548
+ }
549
+
550
+ if (currentTextBlockId === null) {
551
+ currentTextBlockId = String(blockCounter++);
552
+ controller.enqueue({
553
+ type: 'text-start',
554
+ id: currentTextBlockId,
555
+ providerMetadata: thoughtSignatureMetadata,
556
+ });
557
+ }
558
+
559
+ controller.enqueue({
560
+ type: 'text-delta',
561
+ id: currentTextBlockId,
562
+ delta: part.text,
563
+ providerMetadata: thoughtSignatureMetadata,
564
+ });
565
+ }
566
+ } else if ('inlineData' in part) {
567
+ // End any active text or reasoning block before starting file output.
568
+ // Relevant for multimodal output models.
569
+ if (currentTextBlockId !== null) {
570
+ controller.enqueue({
571
+ type: 'text-end',
572
+ id: currentTextBlockId,
573
+ });
574
+ currentTextBlockId = null;
575
+ }
576
+ if (currentReasoningBlockId !== null) {
577
+ controller.enqueue({
578
+ type: 'reasoning-end',
579
+ id: currentReasoningBlockId,
580
+ });
581
+ currentReasoningBlockId = null;
582
+ }
583
+
584
+ // Process file parts inline to preserve order with text
585
+ const thoughtSignatureMetadata = part.thoughtSignature
586
+ ? {
587
+ [providerOptionsName]: {
588
+ thoughtSignature: part.thoughtSignature,
589
+ },
590
+ }
591
+ : undefined;
592
+ controller.enqueue({
593
+ type: 'file',
594
+ mediaType: part.inlineData.mimeType,
595
+ data: part.inlineData.data,
596
+ providerMetadata: thoughtSignatureMetadata,
597
+ });
598
+ }
599
+ }
600
+
601
+ const toolCallDeltas = getToolCallsFromParts({
602
+ parts: content.parts,
603
+ generateId,
604
+ providerOptionsName,
605
+ });
606
+
607
+ if (toolCallDeltas != null) {
608
+ for (const toolCall of toolCallDeltas) {
609
+ controller.enqueue({
610
+ type: 'tool-input-start',
611
+ id: toolCall.toolCallId,
612
+ toolName: toolCall.toolName,
613
+ providerMetadata: toolCall.providerMetadata,
614
+ });
615
+
616
+ controller.enqueue({
617
+ type: 'tool-input-delta',
618
+ id: toolCall.toolCallId,
619
+ delta: toolCall.args,
620
+ providerMetadata: toolCall.providerMetadata,
621
+ });
622
+
623
+ controller.enqueue({
624
+ type: 'tool-input-end',
625
+ id: toolCall.toolCallId,
626
+ providerMetadata: toolCall.providerMetadata,
627
+ });
628
+
629
+ controller.enqueue({
630
+ type: 'tool-call',
631
+ toolCallId: toolCall.toolCallId,
632
+ toolName: toolCall.toolName,
633
+ input: toolCall.args,
634
+ providerMetadata: toolCall.providerMetadata,
635
+ });
636
+
637
+ hasToolCalls = true;
638
+ }
639
+ }
640
+ }
641
+
642
+ if (candidate.finishReason != null) {
643
+ finishReason = {
644
+ unified: mapGoogleGenerativeAIFinishReason({
645
+ finishReason: candidate.finishReason,
646
+ hasToolCalls,
647
+ }),
648
+ raw: candidate.finishReason,
649
+ };
650
+
651
+ providerMetadata = {
652
+ [providerOptionsName]: {
653
+ promptFeedback: value.promptFeedback ?? null,
654
+ groundingMetadata: candidate.groundingMetadata ?? null,
655
+ urlContextMetadata: candidate.urlContextMetadata ?? null,
656
+ safetyRatings: candidate.safetyRatings ?? null,
657
+ },
658
+ };
659
+ if (usageMetadata != null) {
660
+ (
661
+ providerMetadata[providerOptionsName] as Record<
662
+ string,
663
+ unknown
664
+ >
665
+ ).usageMetadata = usageMetadata;
666
+ }
667
+ }
668
+ },
669
+
670
+ flush(controller) {
671
+ if (currentTextBlockId !== null) {
672
+ controller.enqueue({
673
+ type: 'text-end',
674
+ id: currentTextBlockId,
675
+ });
676
+ }
677
+ if (currentReasoningBlockId !== null) {
678
+ controller.enqueue({
679
+ type: 'reasoning-end',
680
+ id: currentReasoningBlockId,
681
+ });
682
+ }
683
+
684
+ controller.enqueue({
685
+ type: 'finish',
686
+ finishReason,
687
+ usage: convertGoogleGenerativeAIUsage(usage),
688
+ providerMetadata,
689
+ });
690
+ },
691
+ }),
692
+ ),
693
+ response: { headers: responseHeaders },
694
+ request: { body: args },
695
+ };
696
+ }
697
+ }
698
+
699
+ function getToolCallsFromParts({
700
+ parts,
701
+ generateId,
702
+ providerOptionsName,
703
+ }: {
704
+ parts: ContentSchema['parts'];
705
+ generateId: () => string;
706
+ providerOptionsName: string;
707
+ }) {
708
+ const functionCallParts = parts?.filter(
709
+ part => 'functionCall' in part,
710
+ ) as Array<
711
+ GoogleGenerativeAIContentPart & {
712
+ functionCall: { name: string; args: unknown };
713
+ thoughtSignature?: string | null;
714
+ }
715
+ >;
716
+
717
+ return functionCallParts == null || functionCallParts.length === 0
718
+ ? undefined
719
+ : functionCallParts.map(part => ({
720
+ type: 'tool-call' as const,
721
+ toolCallId: generateId(),
722
+ toolName: part.functionCall.name,
723
+ args: JSON.stringify(part.functionCall.args),
724
+ providerMetadata: part.thoughtSignature
725
+ ? {
726
+ [providerOptionsName]: {
727
+ thoughtSignature: part.thoughtSignature,
728
+ },
729
+ }
730
+ : undefined,
731
+ }));
732
+ }
733
+
734
+ function extractSources({
735
+ groundingMetadata,
736
+ generateId,
737
+ }: {
738
+ groundingMetadata: GroundingMetadataSchema | undefined | null;
739
+ generateId: () => string;
740
+ }): undefined | LanguageModelV3Source[] {
741
+ if (!groundingMetadata?.groundingChunks) {
742
+ return undefined;
743
+ }
744
+
745
+ const sources: LanguageModelV3Source[] = [];
746
+
747
+ for (const chunk of groundingMetadata.groundingChunks) {
748
+ if (chunk.web != null) {
749
+ // Handle web chunks as URL sources
750
+ sources.push({
751
+ type: 'source',
752
+ sourceType: 'url',
753
+ id: generateId(),
754
+ url: chunk.web.uri,
755
+ title: chunk.web.title ?? undefined,
756
+ });
757
+ } else if (chunk.image != null) {
758
+ // Handle image chunks as image sources
759
+ sources.push({
760
+ type: 'source',
761
+ sourceType: 'url',
762
+ id: generateId(),
763
+ // Google requires attribution to the source URI, not the actual image URI.
764
+ // TODO: add another type in v7 to allow both the image and source URL to be included separately
765
+ url: chunk.image.sourceUri,
766
+ title: chunk.image.title ?? undefined,
767
+ });
768
+ } else if (chunk.retrievedContext != null) {
769
+ // Handle retrievedContext chunks from RAG operations
770
+ const uri = chunk.retrievedContext.uri;
771
+ const fileSearchStore = chunk.retrievedContext.fileSearchStore;
772
+
773
+ if (uri && (uri.startsWith('http://') || uri.startsWith('https://'))) {
774
+ // Old format: Google Search with HTTP/HTTPS URL
775
+ sources.push({
776
+ type: 'source',
777
+ sourceType: 'url',
778
+ id: generateId(),
779
+ url: uri,
780
+ title: chunk.retrievedContext.title ?? undefined,
781
+ });
782
+ } else if (uri) {
783
+ // Old format: Document with file path (gs://, etc.)
784
+ const title = chunk.retrievedContext.title ?? 'Unknown Document';
785
+ let mediaType = 'application/octet-stream';
786
+ let filename: string | undefined = undefined;
787
+
788
+ if (uri.endsWith('.pdf')) {
789
+ mediaType = 'application/pdf';
790
+ filename = uri.split('/').pop();
791
+ } else if (uri.endsWith('.txt')) {
792
+ mediaType = 'text/plain';
793
+ filename = uri.split('/').pop();
794
+ } else if (uri.endsWith('.docx')) {
795
+ mediaType =
796
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
797
+ filename = uri.split('/').pop();
798
+ } else if (uri.endsWith('.doc')) {
799
+ mediaType = 'application/msword';
800
+ filename = uri.split('/').pop();
801
+ } else if (uri.match(/\.(md|markdown)$/)) {
802
+ mediaType = 'text/markdown';
803
+ filename = uri.split('/').pop();
804
+ } else {
805
+ filename = uri.split('/').pop();
806
+ }
807
+
808
+ sources.push({
809
+ type: 'source',
810
+ sourceType: 'document',
811
+ id: generateId(),
812
+ mediaType,
813
+ title,
814
+ filename,
815
+ });
816
+ } else if (fileSearchStore) {
817
+ // New format: File Search with fileSearchStore (no uri)
818
+ const title = chunk.retrievedContext.title ?? 'Unknown Document';
819
+ sources.push({
820
+ type: 'source',
821
+ sourceType: 'document',
822
+ id: generateId(),
823
+ mediaType: 'application/octet-stream',
824
+ title,
825
+ filename: fileSearchStore.split('/').pop(),
826
+ });
827
+ }
828
+ } else if (chunk.maps != null) {
829
+ if (chunk.maps.uri) {
830
+ sources.push({
831
+ type: 'source',
832
+ sourceType: 'url',
833
+ id: generateId(),
834
+ url: chunk.maps.uri,
835
+ title: chunk.maps.title ?? undefined,
836
+ });
837
+ }
838
+ }
839
+ }
840
+
841
+ return sources.length > 0 ? sources : undefined;
842
+ }
843
+
844
+ export const getGroundingMetadataSchema = () =>
845
+ z.object({
846
+ webSearchQueries: z.array(z.string()).nullish(),
847
+ imageSearchQueries: z.array(z.string()).nullish(),
848
+ retrievalQueries: z.array(z.string()).nullish(),
849
+ searchEntryPoint: z.object({ renderedContent: z.string() }).nullish(),
850
+ groundingChunks: z
851
+ .array(
852
+ z.object({
853
+ web: z
854
+ .object({ uri: z.string(), title: z.string().nullish() })
855
+ .nullish(),
856
+ image: z
857
+ .object({
858
+ sourceUri: z.string(),
859
+ imageUri: z.string(),
860
+ title: z.string().nullish(),
861
+ domain: z.string().nullish(),
862
+ })
863
+ .nullish(),
864
+ retrievedContext: z
865
+ .object({
866
+ uri: z.string().nullish(),
867
+ title: z.string().nullish(),
868
+ text: z.string().nullish(),
869
+ fileSearchStore: z.string().nullish(),
870
+ })
871
+ .nullish(),
872
+ maps: z
873
+ .object({
874
+ uri: z.string().nullish(),
875
+ title: z.string().nullish(),
876
+ text: z.string().nullish(),
877
+ placeId: z.string().nullish(),
878
+ })
879
+ .nullish(),
880
+ }),
881
+ )
882
+ .nullish(),
883
+ groundingSupports: z
884
+ .array(
885
+ z.object({
886
+ segment: z
887
+ .object({
888
+ startIndex: z.number().nullish(),
889
+ endIndex: z.number().nullish(),
890
+ text: z.string().nullish(),
891
+ })
892
+ .nullish(),
893
+ segment_text: z.string().nullish(),
894
+ groundingChunkIndices: z.array(z.number()).nullish(),
895
+ supportChunkIndices: z.array(z.number()).nullish(),
896
+ confidenceScores: z.array(z.number()).nullish(),
897
+ confidenceScore: z.array(z.number()).nullish(),
898
+ }),
899
+ )
900
+ .nullish(),
901
+ retrievalMetadata: z
902
+ .union([
903
+ z.object({
904
+ webDynamicRetrievalScore: z.number(),
905
+ }),
906
+ z.object({}),
907
+ ])
908
+ .nullish(),
909
+ });
910
+
911
+ const getContentSchema = () =>
912
+ z.object({
913
+ parts: z
914
+ .array(
915
+ z.union([
916
+ // note: order matters since text can be fully empty
917
+ z.object({
918
+ functionCall: z.object({
919
+ name: z.string(),
920
+ args: z.unknown(),
921
+ }),
922
+ thoughtSignature: z.string().nullish(),
923
+ }),
924
+ z.object({
925
+ inlineData: z.object({
926
+ mimeType: z.string(),
927
+ data: z.string(),
928
+ }),
929
+ thoughtSignature: z.string().nullish(),
930
+ }),
931
+ z.object({
932
+ executableCode: z
933
+ .object({
934
+ language: z.string(),
935
+ code: z.string(),
936
+ })
937
+ .nullish(),
938
+ codeExecutionResult: z
939
+ .object({
940
+ outcome: z.string(),
941
+ output: z.string().nullish(),
942
+ })
943
+ .nullish(),
944
+ text: z.string().nullish(),
945
+ thought: z.boolean().nullish(),
946
+ thoughtSignature: z.string().nullish(),
947
+ }),
948
+ ]),
949
+ )
950
+ .nullish(),
951
+ });
952
+
953
+ // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters
954
+ const getSafetyRatingSchema = () =>
955
+ z.object({
956
+ category: z.string().nullish(),
957
+ probability: z.string().nullish(),
958
+ probabilityScore: z.number().nullish(),
959
+ severity: z.string().nullish(),
960
+ severityScore: z.number().nullish(),
961
+ blocked: z.boolean().nullish(),
962
+ });
963
+
964
+ const usageSchema = z.object({
965
+ cachedContentTokenCount: z.number().nullish(),
966
+ thoughtsTokenCount: z.number().nullish(),
967
+ promptTokenCount: z.number().nullish(),
968
+ candidatesTokenCount: z.number().nullish(),
969
+ totalTokenCount: z.number().nullish(),
970
+ // https://cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/GenerateContentResponse#TrafficType
971
+ trafficType: z.string().nullish(),
972
+ });
973
+
974
+ // https://ai.google.dev/api/generate-content#UrlRetrievalMetadata
975
+ export const getUrlContextMetadataSchema = () =>
976
+ z.object({
977
+ urlMetadata: z
978
+ .array(
979
+ z.object({
980
+ retrievedUrl: z.string(),
981
+ urlRetrievalStatus: z.string(),
982
+ }),
983
+ )
984
+ .nullish(),
985
+ });
986
+
987
+ const responseSchema = lazySchema(() =>
988
+ zodSchema(
989
+ z.object({
990
+ candidates: z.array(
991
+ z.object({
992
+ content: getContentSchema().nullish().or(z.object({}).strict()),
993
+ finishReason: z.string().nullish(),
994
+ safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
995
+ groundingMetadata: getGroundingMetadataSchema().nullish(),
996
+ urlContextMetadata: getUrlContextMetadataSchema().nullish(),
997
+ }),
998
+ ),
999
+ usageMetadata: usageSchema.nullish(),
1000
+ promptFeedback: z
1001
+ .object({
1002
+ blockReason: z.string().nullish(),
1003
+ safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1004
+ })
1005
+ .nullish(),
1006
+ }),
1007
+ ),
1008
+ );
1009
+
1010
+ type ContentSchema = NonNullable<
1011
+ InferSchema<typeof responseSchema>['candidates'][number]['content']
1012
+ >;
1013
+ export type GroundingMetadataSchema = NonNullable<
1014
+ InferSchema<typeof responseSchema>['candidates'][number]['groundingMetadata']
1015
+ >;
1016
+
1017
+ type GroundingChunkSchema = NonNullable<
1018
+ GroundingMetadataSchema['groundingChunks']
1019
+ >[number];
1020
+
1021
+ export type UrlContextMetadataSchema = NonNullable<
1022
+ InferSchema<typeof responseSchema>['candidates'][number]['urlContextMetadata']
1023
+ >;
1024
+
1025
+ export type SafetyRatingSchema = NonNullable<
1026
+ InferSchema<typeof responseSchema>['candidates'][number]['safetyRatings']
1027
+ >[number];
1028
+
1029
+ // limited version of the schema, focussed on what is needed for the implementation
1030
+ // this approach limits breakages when the API changes and increases efficiency
1031
+ const chunkSchema = lazySchema(() =>
1032
+ zodSchema(
1033
+ z.object({
1034
+ candidates: z
1035
+ .array(
1036
+ z.object({
1037
+ content: getContentSchema().nullish(),
1038
+ finishReason: z.string().nullish(),
1039
+ safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1040
+ groundingMetadata: getGroundingMetadataSchema().nullish(),
1041
+ urlContextMetadata: getUrlContextMetadataSchema().nullish(),
1042
+ }),
1043
+ )
1044
+ .nullish(),
1045
+ usageMetadata: usageSchema.nullish(),
1046
+ promptFeedback: z
1047
+ .object({
1048
+ blockReason: z.string().nullish(),
1049
+ safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1050
+ })
1051
+ .nullish(),
1052
+ }),
1053
+ ),
1054
+ );
1055
+
1056
+ type ChunkSchema = InferSchema<typeof chunkSchema>;