@ai-sdk/google 4.0.0-beta.4 → 4.0.0-beta.40

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.
@@ -1,14 +1,15 @@
1
1
  import {
2
- LanguageModelV3,
3
- LanguageModelV3CallOptions,
4
- LanguageModelV3Content,
5
- LanguageModelV3FinishReason,
6
- LanguageModelV3GenerateResult,
7
- LanguageModelV3Source,
8
- LanguageModelV3StreamPart,
9
- LanguageModelV3StreamResult,
10
- SharedV3ProviderMetadata,
11
- SharedV3Warning,
2
+ LanguageModelV4,
3
+ LanguageModelV4CallOptions,
4
+ LanguageModelV4Content,
5
+ LanguageModelV4FinishReason,
6
+ LanguageModelV4GenerateResult,
7
+ LanguageModelV4Source,
8
+ LanguageModelV4StreamPart,
9
+ LanguageModelV4StreamResult,
10
+ JSONObject,
11
+ SharedV4ProviderMetadata,
12
+ SharedV4Warning,
12
13
  } from '@ai-sdk/provider';
13
14
  import {
14
15
  combineHeaders,
@@ -17,12 +18,18 @@ import {
17
18
  FetchFunction,
18
19
  generateId,
19
20
  InferSchema,
21
+ isCustomReasoning,
20
22
  lazySchema,
23
+ mapReasoningToProviderBudget,
24
+ mapReasoningToProviderEffort,
21
25
  parseProviderOptions,
22
26
  ParseResult,
23
27
  postJsonToApi,
24
28
  Resolvable,
25
29
  resolve,
30
+ serializeModelOptions,
31
+ WORKFLOW_SERIALIZE,
32
+ WORKFLOW_DESERIALIZE,
26
33
  zodSchema,
27
34
  } from '@ai-sdk/provider-utils';
28
35
  import { z } from 'zod/v4';
@@ -37,32 +44,48 @@ import { googleFailedResponseHandler } from './google-error';
37
44
  import {
38
45
  GoogleGenerativeAIModelId,
39
46
  googleLanguageModelOptions,
47
+ VertexServiceTierMap,
40
48
  } from './google-generative-ai-options';
41
- import { GoogleGenerativeAIContentPart } from './google-generative-ai-prompt';
49
+ import { GoogleGenerativeAIProviderMetadata } from './google-generative-ai-prompt';
42
50
  import { prepareTools } from './google-prepare-tools';
51
+ import { GoogleJSONAccumulator, PartialArg } from './google-json-accumulator';
43
52
  import { mapGoogleGenerativeAIFinishReason } from './map-google-generative-ai-finish-reason';
44
53
 
45
54
  type GoogleGenerativeAIConfig = {
46
55
  provider: string;
47
56
  baseURL: string;
48
- headers: Resolvable<Record<string, string | undefined>>;
57
+ headers?: Resolvable<Record<string, string | undefined>>;
49
58
  fetch?: FetchFunction;
50
59
  generateId: () => string;
51
60
 
52
61
  /**
53
62
  * The supported URLs for the model.
54
63
  */
55
- supportedUrls?: () => LanguageModelV3['supportedUrls'];
64
+ supportedUrls?: () => LanguageModelV4['supportedUrls'];
56
65
  };
57
66
 
58
- export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
59
- readonly specificationVersion = 'v3';
67
+ export class GoogleGenerativeAILanguageModel implements LanguageModelV4 {
68
+ readonly specificationVersion = 'v4';
60
69
 
61
70
  readonly modelId: GoogleGenerativeAIModelId;
62
71
 
63
72
  private readonly config: GoogleGenerativeAIConfig;
64
73
  private readonly generateId: () => string;
65
74
 
75
+ static [WORKFLOW_SERIALIZE](model: GoogleGenerativeAILanguageModel) {
76
+ return serializeModelOptions({
77
+ modelId: model.modelId,
78
+ config: model.config,
79
+ });
80
+ }
81
+
82
+ static [WORKFLOW_DESERIALIZE](options: {
83
+ modelId: string;
84
+ config: GoogleGenerativeAIConfig;
85
+ }) {
86
+ return new GoogleGenerativeAILanguageModel(options.modelId, options.config);
87
+ }
88
+
66
89
  constructor(
67
90
  modelId: GoogleGenerativeAIModelId,
68
91
  config: GoogleGenerativeAIConfig,
@@ -80,22 +103,26 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
80
103
  return this.config.supportedUrls?.() ?? {};
81
104
  }
82
105
 
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[] = [];
106
+ private async getArgs(
107
+ {
108
+ prompt,
109
+ maxOutputTokens,
110
+ temperature,
111
+ topP,
112
+ topK,
113
+ frequencyPenalty,
114
+ presencePenalty,
115
+ stopSequences,
116
+ responseFormat,
117
+ seed,
118
+ tools,
119
+ toolChoice,
120
+ reasoning,
121
+ providerOptions,
122
+ }: LanguageModelV4CallOptions,
123
+ { isStreaming = false }: { isStreaming?: boolean } = {},
124
+ ) {
125
+ const warnings: SharedV4Warning[] = [];
99
126
 
100
127
  const providerOptionsName = this.config.provider.includes('vertex')
101
128
  ? 'vertex'
@@ -115,12 +142,14 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
115
142
  }
116
143
 
117
144
  // Add warning if Vertex rag tools are used with a non-Vertex Google provider
145
+ const isVertexProvider = this.config.provider.startsWith('google.vertex.');
146
+
118
147
  if (
119
148
  tools?.some(
120
149
  tool =>
121
150
  tool.type === 'provider' && tool.id === 'google.vertex_rag_store',
122
151
  ) &&
123
- !this.config.provider.startsWith('google.vertex.')
152
+ !isVertexProvider
124
153
  ) {
125
154
  warnings.push({
126
155
  type: 'other',
@@ -131,11 +160,32 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
131
160
  });
132
161
  }
133
162
 
163
+ if (googleOptions?.streamFunctionCallArguments && !isVertexProvider) {
164
+ warnings.push({
165
+ type: 'other',
166
+ message:
167
+ "'streamFunctionCallArguments' is only supported on the Vertex AI API " +
168
+ 'and will be ignored with the current Google provider ' +
169
+ `(${this.config.provider}). See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc`,
170
+ });
171
+ }
172
+
173
+ // Vertex API requires another service tier format.
174
+ let sanitizedServiceTier: string | undefined = googleOptions?.serviceTier;
175
+ if (googleOptions?.serviceTier && isVertexProvider) {
176
+ sanitizedServiceTier = VertexServiceTierMap[googleOptions.serviceTier];
177
+ }
178
+
134
179
  const isGemmaModel = this.modelId.toLowerCase().startsWith('gemma-');
180
+ const supportsFunctionResponseParts = this.modelId.startsWith('gemini-3');
135
181
 
136
182
  const { contents, systemInstruction } = convertToGoogleGenerativeAIMessages(
137
183
  prompt,
138
- { isGemmaModel, providerOptionsName },
184
+ {
185
+ isGemmaModel,
186
+ providerOptionsName,
187
+ supportsFunctionResponseParts,
188
+ },
139
189
  );
140
190
 
141
191
  const {
@@ -148,6 +198,39 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
148
198
  modelId: this.modelId,
149
199
  });
150
200
 
201
+ const resolvedThinking = resolveThinkingConfig({
202
+ reasoning,
203
+ modelId: this.modelId,
204
+ warnings,
205
+ });
206
+ const thinkingConfig =
207
+ googleOptions?.thinkingConfig || resolvedThinking
208
+ ? { ...resolvedThinking, ...googleOptions?.thinkingConfig }
209
+ : undefined;
210
+
211
+ const streamFunctionCallArguments =
212
+ isStreaming && isVertexProvider
213
+ ? (googleOptions?.streamFunctionCallArguments ?? false)
214
+ : undefined;
215
+
216
+ const toolConfig =
217
+ googleToolConfig ||
218
+ streamFunctionCallArguments ||
219
+ googleOptions?.retrievalConfig
220
+ ? {
221
+ ...googleToolConfig,
222
+ ...(streamFunctionCallArguments && {
223
+ functionCallingConfig: {
224
+ ...googleToolConfig?.functionCallingConfig,
225
+ streamFunctionCallArguments: true as const,
226
+ },
227
+ }),
228
+ ...(googleOptions?.retrievalConfig && {
229
+ retrievalConfig: googleOptions.retrievalConfig,
230
+ }),
231
+ }
232
+ : undefined;
233
+
151
234
  return {
152
235
  args: {
153
236
  generationConfig: {
@@ -179,7 +262,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
179
262
 
180
263
  // provider options:
181
264
  responseModalities: googleOptions?.responseModalities,
182
- thinkingConfig: googleOptions?.thinkingConfig,
265
+ thinkingConfig,
183
266
  ...(googleOptions?.mediaResolution && {
184
267
  mediaResolution: googleOptions.mediaResolution,
185
268
  }),
@@ -191,14 +274,10 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
191
274
  systemInstruction: isGemmaModel ? undefined : systemInstruction,
192
275
  safetySettings: googleOptions?.safetySettings,
193
276
  tools: googleTools,
194
- toolConfig: googleOptions?.retrievalConfig
195
- ? {
196
- ...googleToolConfig,
197
- retrievalConfig: googleOptions.retrievalConfig,
198
- }
199
- : googleToolConfig,
277
+ toolConfig,
200
278
  cachedContent: googleOptions?.cachedContent,
201
279
  labels: googleOptions?.labels,
280
+ serviceTier: sanitizedServiceTier,
202
281
  },
203
282
  warnings: [...warnings, ...toolWarnings],
204
283
  providerOptionsName,
@@ -206,12 +285,12 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
206
285
  }
207
286
 
208
287
  async doGenerate(
209
- options: LanguageModelV3CallOptions,
210
- ): Promise<LanguageModelV3GenerateResult> {
288
+ options: LanguageModelV4CallOptions,
289
+ ): Promise<LanguageModelV4GenerateResult> {
211
290
  const { args, warnings, providerOptionsName } = await this.getArgs(options);
212
291
 
213
292
  const mergedHeaders = combineHeaders(
214
- await resolve(this.config.headers),
293
+ this.config.headers ? await resolve(this.config.headers) : undefined,
215
294
  options.headers,
216
295
  );
217
296
 
@@ -232,7 +311,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
232
311
  });
233
312
 
234
313
  const candidate = response.candidates[0];
235
- const content: Array<LanguageModelV3Content> = [];
314
+ const content: Array<LanguageModelV4Content> = [];
236
315
 
237
316
  // map ordered parts to content:
238
317
  const parts = candidate.content?.parts ?? [];
@@ -241,6 +320,8 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
241
320
 
242
321
  // Associates a code execution result with its preceding call.
243
322
  let lastCodeExecutionToolCallId: string | undefined;
323
+ // Associates a server-side tool response with its preceding call (tool combination).
324
+ let lastServerToolCallId: string | undefined;
244
325
 
245
326
  // Build content array from all parts
246
327
  for (const part of parts) {
@@ -289,7 +370,11 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
289
370
  providerMetadata: thoughtSignatureMetadata,
290
371
  });
291
372
  }
292
- } else if ('functionCall' in part) {
373
+ } else if (
374
+ 'functionCall' in part &&
375
+ part.functionCall.name != null &&
376
+ part.functionCall.args != null
377
+ ) {
293
378
  content.push({
294
379
  type: 'tool-call' as const,
295
380
  toolCallId: this.config.generateId(),
@@ -307,21 +392,68 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
307
392
  const hasThought = part.thought === true;
308
393
  const hasThoughtSignature = !!part.thoughtSignature;
309
394
  content.push({
310
- type: 'file' as const,
395
+ type: hasThought ? 'reasoning-file' : 'file',
311
396
  data: part.inlineData.data,
312
397
  mediaType: part.inlineData.mimeType,
313
- providerMetadata:
314
- hasThought || hasThoughtSignature
315
- ? {
316
- [providerOptionsName]: {
317
- ...(hasThought ? { thought: true } : {}),
318
- ...(hasThoughtSignature
319
- ? { thoughtSignature: part.thoughtSignature }
320
- : {}),
321
- },
322
- }
323
- : undefined,
398
+ providerMetadata: hasThoughtSignature
399
+ ? {
400
+ [providerOptionsName]: {
401
+ thoughtSignature: part.thoughtSignature,
402
+ },
403
+ }
404
+ : undefined,
405
+ });
406
+ } else if ('toolCall' in part && part.toolCall) {
407
+ const toolCallId = part.toolCall.id ?? this.config.generateId();
408
+ lastServerToolCallId = toolCallId;
409
+ content.push({
410
+ type: 'tool-call',
411
+ toolCallId,
412
+ toolName: `server:${part.toolCall.toolType}`,
413
+ input: JSON.stringify(part.toolCall.args ?? {}),
414
+ providerExecuted: true,
415
+ dynamic: true,
416
+ providerMetadata: part.thoughtSignature
417
+ ? {
418
+ [providerOptionsName]: {
419
+ thoughtSignature: part.thoughtSignature,
420
+ serverToolCallId: toolCallId,
421
+ serverToolType: part.toolCall.toolType,
422
+ },
423
+ }
424
+ : {
425
+ [providerOptionsName]: {
426
+ serverToolCallId: toolCallId,
427
+ serverToolType: part.toolCall.toolType,
428
+ },
429
+ },
324
430
  });
431
+ } else if ('toolResponse' in part && part.toolResponse) {
432
+ const responseToolCallId =
433
+ lastServerToolCallId ??
434
+ part.toolResponse.id ??
435
+ this.config.generateId();
436
+ content.push({
437
+ type: 'tool-result',
438
+ toolCallId: responseToolCallId,
439
+ toolName: `server:${part.toolResponse.toolType}`,
440
+ result: (part.toolResponse.response ?? {}) as JSONObject,
441
+ providerMetadata: part.thoughtSignature
442
+ ? {
443
+ [providerOptionsName]: {
444
+ thoughtSignature: part.thoughtSignature,
445
+ serverToolCallId: responseToolCallId,
446
+ serverToolType: part.toolResponse.toolType,
447
+ },
448
+ }
449
+ : {
450
+ [providerOptionsName]: {
451
+ serverToolCallId: responseToolCallId,
452
+ serverToolType: part.toolResponse.toolType,
453
+ },
454
+ },
455
+ });
456
+ lastServerToolCallId = undefined;
325
457
  }
326
458
  }
327
459
 
@@ -355,7 +487,9 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
355
487
  urlContextMetadata: candidate.urlContextMetadata ?? null,
356
488
  safetyRatings: candidate.safetyRatings ?? null,
357
489
  usageMetadata: usageMetadata ?? null,
358
- },
490
+ finishMessage: candidate.finishMessage ?? null,
491
+ serviceTier: response.serviceTier ?? null,
492
+ } satisfies GoogleGenerativeAIProviderMetadata,
359
493
  },
360
494
  request: { body: args },
361
495
  response: {
@@ -367,12 +501,15 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
367
501
  }
368
502
 
369
503
  async doStream(
370
- options: LanguageModelV3CallOptions,
371
- ): Promise<LanguageModelV3StreamResult> {
372
- const { args, warnings, providerOptionsName } = await this.getArgs(options);
504
+ options: LanguageModelV4CallOptions,
505
+ ): Promise<LanguageModelV4StreamResult> {
506
+ const { args, warnings, providerOptionsName } = await this.getArgs(
507
+ options,
508
+ { isStreaming: true },
509
+ );
373
510
 
374
511
  const headers = combineHeaders(
375
- await resolve(this.config.headers),
512
+ this.config.headers ? await resolve(this.config.headers) : undefined,
376
513
  options.headers,
377
514
  );
378
515
 
@@ -388,14 +525,15 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
388
525
  fetch: this.config.fetch,
389
526
  });
390
527
 
391
- let finishReason: LanguageModelV3FinishReason = {
528
+ let finishReason: LanguageModelV4FinishReason = {
392
529
  unified: 'other',
393
530
  raw: undefined,
394
531
  };
395
532
  let usage: GoogleGenerativeAIUsageMetadata | undefined = undefined;
396
- let providerMetadata: SharedV3ProviderMetadata | undefined = undefined;
533
+ let providerMetadata: SharedV4ProviderMetadata | undefined = undefined;
397
534
  let lastGroundingMetadata: GroundingMetadataSchema | null = null;
398
535
  let lastUrlContextMetadata: UrlContextMetadataSchema | null = null;
536
+ let serviceTier: string | null = null;
399
537
 
400
538
  const generateId = this.config.generateId;
401
539
  let hasToolCalls = false;
@@ -409,12 +547,21 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
409
547
  const emittedSourceUrls = new Set<string>();
410
548
  // Associates a code execution result with its preceding call.
411
549
  let lastCodeExecutionToolCallId: string | undefined;
550
+ // Associates a server-side tool response with its preceding call (tool combination).
551
+ let lastServerToolCallId: string | undefined;
552
+
553
+ const activeStreamingToolCalls: Array<{
554
+ toolCallId: string;
555
+ toolName: string;
556
+ accumulator: GoogleJSONAccumulator;
557
+ providerMetadata?: SharedV4ProviderMetadata;
558
+ }> = [];
412
559
 
413
560
  return {
414
561
  stream: response.pipeThrough(
415
562
  new TransformStream<
416
563
  ParseResult<ChunkSchema>,
417
- LanguageModelV3StreamPart
564
+ LanguageModelV4StreamPart
418
565
  >({
419
566
  start(controller) {
420
567
  controller.enqueue({ type: 'stream-start', warnings });
@@ -438,6 +585,10 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
438
585
  usage = usageMetadata;
439
586
  }
440
587
 
588
+ if (value.serviceTier != null) {
589
+ serviceTier = value.serviceTier;
590
+ }
591
+
441
592
  const candidate = value.candidates?.[0];
442
593
 
443
594
  // sometimes the API returns an empty candidates array
@@ -598,60 +749,213 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
598
749
 
599
750
  const hasThought = part.thought === true;
600
751
  const hasThoughtSignature = !!part.thoughtSignature;
601
- const fileMeta =
602
- hasThought || hasThoughtSignature
603
- ? {
604
- [providerOptionsName]: {
605
- ...(hasThought ? { thought: true } : {}),
606
- ...(hasThoughtSignature
607
- ? { thoughtSignature: part.thoughtSignature }
608
- : {}),
609
- },
610
- }
611
- : undefined;
752
+ const fileMeta = hasThoughtSignature
753
+ ? {
754
+ [providerOptionsName]: {
755
+ thoughtSignature: part.thoughtSignature,
756
+ },
757
+ }
758
+ : undefined;
612
759
  controller.enqueue({
613
- type: 'file',
760
+ type: hasThought ? 'reasoning-file' : 'file',
614
761
  mediaType: part.inlineData.mimeType,
615
762
  data: part.inlineData.data,
616
763
  providerMetadata: fileMeta,
617
764
  });
765
+ } else if ('toolCall' in part && part.toolCall) {
766
+ const toolCallId = part.toolCall.id ?? generateId();
767
+ lastServerToolCallId = toolCallId;
768
+ const serverMeta = {
769
+ [providerOptionsName]: {
770
+ ...(part.thoughtSignature
771
+ ? { thoughtSignature: part.thoughtSignature }
772
+ : {}),
773
+ serverToolCallId: toolCallId,
774
+ serverToolType: part.toolCall.toolType,
775
+ },
776
+ };
777
+
778
+ controller.enqueue({
779
+ type: 'tool-call',
780
+ toolCallId,
781
+ toolName: `server:${part.toolCall.toolType}`,
782
+ input: JSON.stringify(part.toolCall.args ?? {}),
783
+ providerExecuted: true,
784
+ dynamic: true,
785
+ providerMetadata: serverMeta,
786
+ });
787
+ } else if ('toolResponse' in part && part.toolResponse) {
788
+ const responseToolCallId =
789
+ lastServerToolCallId ??
790
+ part.toolResponse.id ??
791
+ generateId();
792
+ const serverMeta = {
793
+ [providerOptionsName]: {
794
+ ...(part.thoughtSignature
795
+ ? { thoughtSignature: part.thoughtSignature }
796
+ : {}),
797
+ serverToolCallId: responseToolCallId,
798
+ serverToolType: part.toolResponse.toolType,
799
+ },
800
+ };
801
+
802
+ controller.enqueue({
803
+ type: 'tool-result',
804
+ toolCallId: responseToolCallId,
805
+ toolName: `server:${part.toolResponse.toolType}`,
806
+ result: (part.toolResponse.response ?? {}) as JSONObject,
807
+ providerMetadata: serverMeta,
808
+ });
809
+ lastServerToolCallId = undefined;
618
810
  }
619
811
  }
620
812
 
621
- const toolCallDeltas = getToolCallsFromParts({
622
- parts: content.parts,
623
- generateId,
624
- providerOptionsName,
625
- });
813
+ // Handle streaming and complete function calls
814
+ for (const part of parts) {
815
+ if (!('functionCall' in part)) continue;
816
+
817
+ const providerMeta = part.thoughtSignature
818
+ ? {
819
+ [providerOptionsName]: {
820
+ thoughtSignature: part.thoughtSignature,
821
+ },
822
+ }
823
+ : undefined;
824
+
825
+ const isStreamingChunk =
826
+ part.functionCall.partialArgs != null ||
827
+ (part.functionCall.name != null &&
828
+ part.functionCall.willContinue === true);
829
+ const isTerminalChunk =
830
+ part.functionCall.name == null &&
831
+ part.functionCall.args == null &&
832
+ part.functionCall.partialArgs == null &&
833
+ part.functionCall.willContinue == null;
834
+ const isCompleteCall =
835
+ part.functionCall.name != null &&
836
+ part.functionCall.args != null &&
837
+ part.functionCall.partialArgs == null;
838
+
839
+ if (isStreamingChunk) {
840
+ if (
841
+ part.functionCall.name != null &&
842
+ part.functionCall.willContinue === true
843
+ ) {
844
+ const toolCallId = generateId();
845
+ const accumulator = new GoogleJSONAccumulator();
846
+ activeStreamingToolCalls.push({
847
+ toolCallId,
848
+ toolName: part.functionCall.name,
849
+ accumulator,
850
+ providerMetadata: providerMeta,
851
+ });
852
+
853
+ controller.enqueue({
854
+ type: 'tool-input-start',
855
+ id: toolCallId,
856
+ toolName: part.functionCall.name,
857
+ providerMetadata: providerMeta,
858
+ });
859
+
860
+ if (part.functionCall.partialArgs != null) {
861
+ const { textDelta } = accumulator.processPartialArgs(
862
+ part.functionCall.partialArgs as PartialArg[],
863
+ );
864
+ if (textDelta.length > 0) {
865
+ controller.enqueue({
866
+ type: 'tool-input-delta',
867
+ id: toolCallId,
868
+ delta: textDelta,
869
+ providerMetadata: providerMeta,
870
+ });
871
+ }
872
+ }
873
+ } else if (
874
+ part.functionCall.partialArgs != null &&
875
+ activeStreamingToolCalls.length > 0
876
+ ) {
877
+ const active =
878
+ activeStreamingToolCalls[
879
+ activeStreamingToolCalls.length - 1
880
+ ];
881
+ const { textDelta } = active.accumulator.processPartialArgs(
882
+ part.functionCall.partialArgs as PartialArg[],
883
+ );
884
+ if (textDelta.length > 0) {
885
+ controller.enqueue({
886
+ type: 'tool-input-delta',
887
+ id: active.toolCallId,
888
+ delta: textDelta,
889
+ providerMetadata: providerMeta,
890
+ });
891
+ }
892
+ }
893
+ } else if (
894
+ isTerminalChunk &&
895
+ activeStreamingToolCalls.length > 0
896
+ ) {
897
+ const active = activeStreamingToolCalls.pop()!;
898
+ const { finalJSON, closingDelta } =
899
+ active.accumulator.finalize();
900
+
901
+ if (closingDelta.length > 0) {
902
+ controller.enqueue({
903
+ type: 'tool-input-delta',
904
+ id: active.toolCallId,
905
+ delta: closingDelta,
906
+ providerMetadata: active.providerMetadata,
907
+ });
908
+ }
909
+
910
+ controller.enqueue({
911
+ type: 'tool-input-end',
912
+ id: active.toolCallId,
913
+ providerMetadata: active.providerMetadata,
914
+ });
915
+
916
+ controller.enqueue({
917
+ type: 'tool-call',
918
+ toolCallId: active.toolCallId,
919
+ toolName: active.toolName,
920
+ input: finalJSON,
921
+ providerMetadata: active.providerMetadata,
922
+ });
923
+
924
+ hasToolCalls = true;
925
+ } else if (isCompleteCall) {
926
+ const toolCallId = generateId();
927
+ const toolName = part.functionCall.name!;
928
+ const args =
929
+ typeof part.functionCall.args === 'string'
930
+ ? part.functionCall.args
931
+ : JSON.stringify(part.functionCall.args ?? {});
626
932
 
627
- if (toolCallDeltas != null) {
628
- for (const toolCall of toolCallDeltas) {
629
933
  controller.enqueue({
630
934
  type: 'tool-input-start',
631
- id: toolCall.toolCallId,
632
- toolName: toolCall.toolName,
633
- providerMetadata: toolCall.providerMetadata,
935
+ id: toolCallId,
936
+ toolName,
937
+ providerMetadata: providerMeta,
634
938
  });
635
939
 
636
940
  controller.enqueue({
637
941
  type: 'tool-input-delta',
638
- id: toolCall.toolCallId,
639
- delta: toolCall.args,
640
- providerMetadata: toolCall.providerMetadata,
942
+ id: toolCallId,
943
+ delta: args,
944
+ providerMetadata: providerMeta,
641
945
  });
642
946
 
643
947
  controller.enqueue({
644
948
  type: 'tool-input-end',
645
- id: toolCall.toolCallId,
646
- providerMetadata: toolCall.providerMetadata,
949
+ id: toolCallId,
950
+ providerMetadata: providerMeta,
647
951
  });
648
952
 
649
953
  controller.enqueue({
650
954
  type: 'tool-call',
651
- toolCallId: toolCall.toolCallId,
652
- toolName: toolCall.toolName,
653
- input: toolCall.args,
654
- providerMetadata: toolCall.providerMetadata,
955
+ toolCallId,
956
+ toolName,
957
+ input: args,
958
+ providerMetadata: providerMeta,
655
959
  });
656
960
 
657
961
  hasToolCalls = true;
@@ -674,16 +978,11 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
674
978
  groundingMetadata: lastGroundingMetadata,
675
979
  urlContextMetadata: lastUrlContextMetadata,
676
980
  safetyRatings: candidate.safetyRatings ?? null,
677
- },
981
+ usageMetadata: usageMetadata ?? null,
982
+ finishMessage: candidate.finishMessage ?? null,
983
+ serviceTier,
984
+ } satisfies GoogleGenerativeAIProviderMetadata,
678
985
  };
679
- if (usageMetadata != null) {
680
- (
681
- providerMetadata[providerOptionsName] as Record<
682
- string,
683
- unknown
684
- >
685
- ).usageMetadata = usageMetadata;
686
- }
687
986
  }
688
987
  },
689
988
 
@@ -716,39 +1015,107 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
716
1015
  }
717
1016
  }
718
1017
 
719
- function getToolCallsFromParts({
720
- parts,
721
- generateId,
722
- providerOptionsName,
1018
+ function isGemini3Model(modelId: string): boolean {
1019
+ return /gemini-3[\.\-]/i.test(modelId) || /gemini-3$/i.test(modelId);
1020
+ }
1021
+
1022
+ function getMaxOutputTokensForGemini25Model(): number {
1023
+ return 65536;
1024
+ }
1025
+
1026
+ function getMaxThinkingTokensForGemini25Model(modelId: string): number {
1027
+ const id = modelId.toLowerCase();
1028
+ if (id.includes('2.5-pro') || id.includes('gemini-3-pro-image')) {
1029
+ return 32768;
1030
+ }
1031
+ return 24576;
1032
+ }
1033
+
1034
+ type GoogleThinkingConfig = NonNullable<
1035
+ InferSchema<typeof googleLanguageModelOptions>['thinkingConfig']
1036
+ >;
1037
+
1038
+ function resolveThinkingConfig({
1039
+ reasoning,
1040
+ modelId,
1041
+ warnings,
723
1042
  }: {
724
- parts: ContentSchema['parts'];
725
- generateId: () => string;
726
- providerOptionsName: string;
727
- }) {
728
- const functionCallParts = parts?.filter(
729
- part => 'functionCall' in part,
730
- ) as Array<
731
- GoogleGenerativeAIContentPart & {
732
- functionCall: { name: string; args: unknown };
733
- thoughtSignature?: string | null;
734
- }
1043
+ reasoning: LanguageModelV4CallOptions['reasoning'];
1044
+ modelId: string;
1045
+ warnings: SharedV4Warning[];
1046
+ }): Omit<GoogleThinkingConfig, 'includeThoughts'> | undefined {
1047
+ if (!isCustomReasoning(reasoning)) {
1048
+ return undefined;
1049
+ }
1050
+
1051
+ if (isGemini3Model(modelId) && !modelId.includes('gemini-3-pro-image')) {
1052
+ return resolveGemini3ThinkingConfig({ reasoning, warnings });
1053
+ }
1054
+
1055
+ return resolveGemini25ThinkingConfig({ reasoning, modelId, warnings });
1056
+ }
1057
+
1058
+ function resolveGemini3ThinkingConfig({
1059
+ reasoning,
1060
+ warnings,
1061
+ }: {
1062
+ reasoning: Exclude<
1063
+ LanguageModelV4CallOptions['reasoning'],
1064
+ 'provider-default' | undefined
735
1065
  >;
1066
+ warnings: SharedV4Warning[];
1067
+ }): Pick<GoogleThinkingConfig, 'thinkingLevel'> | undefined {
1068
+ if (reasoning === 'none') {
1069
+ // It's not possible to fully disable thinking with Gemini 3.
1070
+ return { thinkingLevel: 'minimal' };
1071
+ }
736
1072
 
737
- return functionCallParts == null || functionCallParts.length === 0
738
- ? undefined
739
- : functionCallParts.map(part => ({
740
- type: 'tool-call' as const,
741
- toolCallId: generateId(),
742
- toolName: part.functionCall.name,
743
- args: JSON.stringify(part.functionCall.args),
744
- providerMetadata: part.thoughtSignature
745
- ? {
746
- [providerOptionsName]: {
747
- thoughtSignature: part.thoughtSignature,
748
- },
749
- }
750
- : undefined,
751
- }));
1073
+ const thinkingLevel = mapReasoningToProviderEffort({
1074
+ reasoning,
1075
+ effortMap: {
1076
+ minimal: 'minimal',
1077
+ low: 'low',
1078
+ medium: 'medium',
1079
+ high: 'high',
1080
+ xhigh: 'high',
1081
+ },
1082
+ warnings,
1083
+ });
1084
+
1085
+ if (thinkingLevel == null) {
1086
+ return undefined;
1087
+ }
1088
+
1089
+ return { thinkingLevel };
1090
+ }
1091
+
1092
+ function resolveGemini25ThinkingConfig({
1093
+ reasoning,
1094
+ modelId,
1095
+ warnings,
1096
+ }: {
1097
+ reasoning: Exclude<
1098
+ LanguageModelV4CallOptions['reasoning'],
1099
+ 'provider-default' | undefined
1100
+ >;
1101
+ modelId: string;
1102
+ warnings: SharedV4Warning[];
1103
+ }): Pick<GoogleThinkingConfig, 'thinkingBudget'> | undefined {
1104
+ if (reasoning === 'none') {
1105
+ return { thinkingBudget: 0 };
1106
+ }
1107
+
1108
+ const thinkingBudget = mapReasoningToProviderBudget({
1109
+ reasoning,
1110
+ maxOutputTokens: getMaxOutputTokensForGemini25Model(),
1111
+ maxReasoningBudget: getMaxThinkingTokensForGemini25Model(modelId),
1112
+ minReasoningBudget: 0,
1113
+ warnings,
1114
+ });
1115
+ if (thinkingBudget == null) {
1116
+ return undefined;
1117
+ }
1118
+ return { thinkingBudget };
752
1119
  }
753
1120
 
754
1121
  function extractSources({
@@ -757,12 +1124,12 @@ function extractSources({
757
1124
  }: {
758
1125
  groundingMetadata: GroundingMetadataSchema | undefined | null;
759
1126
  generateId: () => string;
760
- }): undefined | LanguageModelV3Source[] {
1127
+ }): undefined | LanguageModelV4Source[] {
761
1128
  if (!groundingMetadata?.groundingChunks) {
762
1129
  return undefined;
763
1130
  }
764
1131
 
765
- const sources: LanguageModelV3Source[] = [];
1132
+ const sources: LanguageModelV4Source[] = [];
766
1133
 
767
1134
  for (const chunk of groundingMetadata.groundingChunks) {
768
1135
  if (chunk.web != null) {
@@ -928,6 +1295,15 @@ export const getGroundingMetadataSchema = () =>
928
1295
  .nullish(),
929
1296
  });
930
1297
 
1298
+ const partialArgSchema = z.object({
1299
+ jsonPath: z.string(),
1300
+ stringValue: z.string().nullish(),
1301
+ numberValue: z.number().nullish(),
1302
+ boolValue: z.boolean().nullish(),
1303
+ nullValue: z.unknown().nullish(),
1304
+ willContinue: z.boolean().nullish(),
1305
+ });
1306
+
931
1307
  const getContentSchema = () =>
932
1308
  z.object({
933
1309
  parts: z
@@ -936,8 +1312,10 @@ const getContentSchema = () =>
936
1312
  // note: order matters since text can be fully empty
937
1313
  z.object({
938
1314
  functionCall: z.object({
939
- name: z.string(),
940
- args: z.unknown(),
1315
+ name: z.string().nullish(),
1316
+ args: z.unknown().nullish(),
1317
+ partialArgs: z.array(partialArgSchema).nullish(),
1318
+ willContinue: z.boolean().nullish(),
941
1319
  }),
942
1320
  thoughtSignature: z.string().nullish(),
943
1321
  }),
@@ -949,6 +1327,22 @@ const getContentSchema = () =>
949
1327
  thought: z.boolean().nullish(),
950
1328
  thoughtSignature: z.string().nullish(),
951
1329
  }),
1330
+ z.object({
1331
+ toolCall: z.object({
1332
+ toolType: z.string(),
1333
+ args: z.unknown().nullish(),
1334
+ id: z.string(),
1335
+ }),
1336
+ thoughtSignature: z.string().nullish(),
1337
+ }),
1338
+ z.object({
1339
+ toolResponse: z.object({
1340
+ toolType: z.string(),
1341
+ response: z.unknown().nullish(),
1342
+ id: z.string(),
1343
+ }),
1344
+ thoughtSignature: z.string().nullish(),
1345
+ }),
952
1346
  z.object({
953
1347
  executableCode: z
954
1348
  .object({
@@ -982,6 +1376,15 @@ const getSafetyRatingSchema = () =>
982
1376
  blocked: z.boolean().nullish(),
983
1377
  });
984
1378
 
1379
+ const tokenDetailsSchema = z
1380
+ .array(
1381
+ z.object({
1382
+ modality: z.string(),
1383
+ tokenCount: z.number(),
1384
+ }),
1385
+ )
1386
+ .nullish();
1387
+
985
1388
  const usageSchema = z.object({
986
1389
  cachedContentTokenCount: z.number().nullish(),
987
1390
  thoughtsTokenCount: z.number().nullish(),
@@ -990,6 +1393,9 @@ const usageSchema = z.object({
990
1393
  totalTokenCount: z.number().nullish(),
991
1394
  // https://cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/GenerateContentResponse#TrafficType
992
1395
  trafficType: z.string().nullish(),
1396
+ // https://ai.google.dev/api/generate-content#Modality
1397
+ promptTokensDetails: tokenDetailsSchema,
1398
+ candidatesTokensDetails: tokenDetailsSchema,
993
1399
  });
994
1400
 
995
1401
  // https://ai.google.dev/api/generate-content#UrlRetrievalMetadata
@@ -1012,6 +1418,7 @@ const responseSchema = lazySchema(() =>
1012
1418
  z.object({
1013
1419
  content: getContentSchema().nullish().or(z.object({}).strict()),
1014
1420
  finishReason: z.string().nullish(),
1421
+ finishMessage: z.string().nullish(),
1015
1422
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1016
1423
  groundingMetadata: getGroundingMetadataSchema().nullish(),
1017
1424
  urlContextMetadata: getUrlContextMetadataSchema().nullish(),
@@ -1024,21 +1431,15 @@ const responseSchema = lazySchema(() =>
1024
1431
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1025
1432
  })
1026
1433
  .nullish(),
1434
+ serviceTier: z.string().nullish(),
1027
1435
  }),
1028
1436
  ),
1029
1437
  );
1030
1438
 
1031
- type ContentSchema = NonNullable<
1032
- InferSchema<typeof responseSchema>['candidates'][number]['content']
1033
- >;
1034
1439
  export type GroundingMetadataSchema = NonNullable<
1035
1440
  InferSchema<typeof responseSchema>['candidates'][number]['groundingMetadata']
1036
1441
  >;
1037
1442
 
1038
- type GroundingChunkSchema = NonNullable<
1039
- GroundingMetadataSchema['groundingChunks']
1040
- >[number];
1041
-
1042
1443
  export type UrlContextMetadataSchema = NonNullable<
1043
1444
  InferSchema<typeof responseSchema>['candidates'][number]['urlContextMetadata']
1044
1445
  >;
@@ -1047,6 +1448,14 @@ export type SafetyRatingSchema = NonNullable<
1047
1448
  InferSchema<typeof responseSchema>['candidates'][number]['safetyRatings']
1048
1449
  >[number];
1049
1450
 
1451
+ export type PromptFeedbackSchema = NonNullable<
1452
+ InferSchema<typeof responseSchema>['promptFeedback']
1453
+ >;
1454
+
1455
+ export type UsageMetadataSchema = NonNullable<
1456
+ InferSchema<typeof responseSchema>['usageMetadata']
1457
+ >;
1458
+
1050
1459
  // limited version of the schema, focussed on what is needed for the implementation
1051
1460
  // this approach limits breakages when the API changes and increases efficiency
1052
1461
  const chunkSchema = lazySchema(() =>
@@ -1057,6 +1466,7 @@ const chunkSchema = lazySchema(() =>
1057
1466
  z.object({
1058
1467
  content: getContentSchema().nullish(),
1059
1468
  finishReason: z.string().nullish(),
1469
+ finishMessage: z.string().nullish(),
1060
1470
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1061
1471
  groundingMetadata: getGroundingMetadataSchema().nullish(),
1062
1472
  urlContextMetadata: getUrlContextMetadataSchema().nullish(),
@@ -1070,6 +1480,7 @@ const chunkSchema = lazySchema(() =>
1070
1480
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1071
1481
  })
1072
1482
  .nullish(),
1483
+ serviceTier: z.string().nullish(),
1073
1484
  }),
1074
1485
  ),
1075
1486
  );