@ai-sdk/google 3.0.60 → 3.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "3.0.60",
3
+ "version": "3.0.62",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -44,6 +44,7 @@ import {
44
44
  GoogleGenerativeAIProviderMetadata,
45
45
  } from './google-generative-ai-prompt';
46
46
  import { prepareTools } from './google-prepare-tools';
47
+ import { GoogleJSONAccumulator, PartialArg } from './google-json-accumulator';
47
48
  import { mapGoogleGenerativeAIFinishReason } from './map-google-generative-ai-finish-reason';
48
49
 
49
50
  type GoogleGenerativeAIConfig = {
@@ -84,21 +85,24 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
84
85
  return this.config.supportedUrls?.() ?? {};
85
86
  }
86
87
 
87
- private async getArgs({
88
- prompt,
89
- maxOutputTokens,
90
- temperature,
91
- topP,
92
- topK,
93
- frequencyPenalty,
94
- presencePenalty,
95
- stopSequences,
96
- responseFormat,
97
- seed,
98
- tools,
99
- toolChoice,
100
- providerOptions,
101
- }: LanguageModelV3CallOptions) {
88
+ private async getArgs(
89
+ {
90
+ prompt,
91
+ maxOutputTokens,
92
+ temperature,
93
+ topP,
94
+ topK,
95
+ frequencyPenalty,
96
+ presencePenalty,
97
+ stopSequences,
98
+ responseFormat,
99
+ seed,
100
+ tools,
101
+ toolChoice,
102
+ providerOptions,
103
+ }: LanguageModelV3CallOptions,
104
+ { isStreaming = false }: { isStreaming?: boolean } = {},
105
+ ) {
102
106
  const warnings: SharedV3Warning[] = [];
103
107
 
104
108
  const providerOptionsName = this.config.provider.includes('vertex')
@@ -119,12 +123,14 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
119
123
  }
120
124
 
121
125
  // Add warning if Vertex rag tools are used with a non-Vertex Google provider
126
+ const isVertexProvider = this.config.provider.startsWith('google.vertex.');
127
+
122
128
  if (
123
129
  tools?.some(
124
130
  tool =>
125
131
  tool.type === 'provider' && tool.id === 'google.vertex_rag_store',
126
132
  ) &&
127
- !this.config.provider.startsWith('google.vertex.')
133
+ !isVertexProvider
128
134
  ) {
129
135
  warnings.push({
130
136
  type: 'other',
@@ -135,6 +141,16 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
135
141
  });
136
142
  }
137
143
 
144
+ if (googleOptions?.streamFunctionCallArguments && !isVertexProvider) {
145
+ warnings.push({
146
+ type: 'other',
147
+ message:
148
+ "'streamFunctionCallArguments' is only supported on the Vertex AI API " +
149
+ 'and will be ignored with the current Google provider ' +
150
+ `(${this.config.provider}). See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc`,
151
+ });
152
+ }
153
+
138
154
  const isGemmaModel = this.modelId.toLowerCase().startsWith('gemma-');
139
155
  const supportsFunctionResponseParts = this.modelId.startsWith('gemini-3');
140
156
 
@@ -157,6 +173,29 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
157
173
  modelId: this.modelId,
158
174
  });
159
175
 
176
+ const streamFunctionCallArguments =
177
+ isStreaming && isVertexProvider
178
+ ? (googleOptions?.streamFunctionCallArguments ?? false)
179
+ : undefined;
180
+
181
+ const toolConfig =
182
+ googleToolConfig ||
183
+ streamFunctionCallArguments ||
184
+ googleOptions?.retrievalConfig
185
+ ? {
186
+ ...googleToolConfig,
187
+ ...(streamFunctionCallArguments && {
188
+ functionCallingConfig: {
189
+ ...googleToolConfig?.functionCallingConfig,
190
+ streamFunctionCallArguments: true as const,
191
+ },
192
+ }),
193
+ ...(googleOptions?.retrievalConfig && {
194
+ retrievalConfig: googleOptions.retrievalConfig,
195
+ }),
196
+ }
197
+ : undefined;
198
+
160
199
  return {
161
200
  args: {
162
201
  generationConfig: {
@@ -200,12 +239,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
200
239
  systemInstruction: isGemmaModel ? undefined : systemInstruction,
201
240
  safetySettings: googleOptions?.safetySettings,
202
241
  tools: googleTools,
203
- toolConfig: googleOptions?.retrievalConfig
204
- ? {
205
- ...googleToolConfig,
206
- retrievalConfig: googleOptions.retrievalConfig,
207
- }
208
- : googleToolConfig,
242
+ toolConfig,
209
243
  cachedContent: googleOptions?.cachedContent,
210
244
  labels: googleOptions?.labels,
211
245
  serviceTier: googleOptions?.serviceTier,
@@ -301,7 +335,11 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
301
335
  providerMetadata: thoughtSignatureMetadata,
302
336
  });
303
337
  }
304
- } else if ('functionCall' in part) {
338
+ } else if (
339
+ 'functionCall' in part &&
340
+ part.functionCall.name != null &&
341
+ part.functionCall.args != null
342
+ ) {
305
343
  content.push({
306
344
  type: 'tool-call' as const,
307
345
  toolCallId: this.config.generateId(),
@@ -434,7 +472,10 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
434
472
  async doStream(
435
473
  options: LanguageModelV3CallOptions,
436
474
  ): Promise<LanguageModelV3StreamResult> {
437
- const { args, warnings, providerOptionsName } = await this.getArgs(options);
475
+ const { args, warnings, providerOptionsName } = await this.getArgs(
476
+ options,
477
+ { isStreaming: true },
478
+ );
438
479
 
439
480
  const headers = combineHeaders(
440
481
  await resolve(this.config.headers),
@@ -478,6 +519,13 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
478
519
  // Associates a server-side tool response with its preceding call (tool combination).
479
520
  let lastServerToolCallId: string | undefined;
480
521
 
522
+ const activeStreamingToolCalls: Array<{
523
+ toolCallId: string;
524
+ toolName: string;
525
+ accumulator: GoogleJSONAccumulator;
526
+ providerMetadata?: SharedV3ProviderMetadata;
527
+ }> = [];
528
+
481
529
  return {
482
530
  stream: response.pipeThrough(
483
531
  new TransformStream<
@@ -735,40 +783,152 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
735
783
  }
736
784
  }
737
785
 
738
- const toolCallDeltas = getToolCallsFromParts({
739
- parts: content.parts,
740
- generateId,
741
- providerOptionsName,
742
- });
786
+ // Handle streaming and complete function calls
787
+ for (const part of parts) {
788
+ if (!('functionCall' in part)) continue;
789
+
790
+ const providerMeta = part.thoughtSignature
791
+ ? {
792
+ [providerOptionsName]: {
793
+ thoughtSignature: part.thoughtSignature,
794
+ },
795
+ }
796
+ : undefined;
797
+
798
+ const isStreamingChunk =
799
+ part.functionCall.partialArgs != null ||
800
+ (part.functionCall.name != null &&
801
+ part.functionCall.willContinue === true);
802
+ const isTerminalChunk =
803
+ part.functionCall.name == null &&
804
+ part.functionCall.args == null &&
805
+ part.functionCall.partialArgs == null &&
806
+ part.functionCall.willContinue == null;
807
+ const isCompleteCall =
808
+ part.functionCall.name != null &&
809
+ part.functionCall.args != null &&
810
+ part.functionCall.partialArgs == null;
811
+
812
+ if (isStreamingChunk) {
813
+ if (
814
+ part.functionCall.name != null &&
815
+ part.functionCall.willContinue === true
816
+ ) {
817
+ const toolCallId = generateId();
818
+ const accumulator = new GoogleJSONAccumulator();
819
+ activeStreamingToolCalls.push({
820
+ toolCallId,
821
+ toolName: part.functionCall.name,
822
+ accumulator,
823
+ providerMetadata: providerMeta,
824
+ });
825
+
826
+ controller.enqueue({
827
+ type: 'tool-input-start',
828
+ id: toolCallId,
829
+ toolName: part.functionCall.name,
830
+ providerMetadata: providerMeta,
831
+ });
832
+
833
+ if (part.functionCall.partialArgs != null) {
834
+ const { textDelta } = accumulator.processPartialArgs(
835
+ part.functionCall.partialArgs as PartialArg[],
836
+ );
837
+ if (textDelta.length > 0) {
838
+ controller.enqueue({
839
+ type: 'tool-input-delta',
840
+ id: toolCallId,
841
+ delta: textDelta,
842
+ providerMetadata: providerMeta,
843
+ });
844
+ }
845
+ }
846
+ } else if (
847
+ part.functionCall.partialArgs != null &&
848
+ activeStreamingToolCalls.length > 0
849
+ ) {
850
+ const active =
851
+ activeStreamingToolCalls[
852
+ activeStreamingToolCalls.length - 1
853
+ ];
854
+ const { textDelta } = active.accumulator.processPartialArgs(
855
+ part.functionCall.partialArgs as PartialArg[],
856
+ );
857
+ if (textDelta.length > 0) {
858
+ controller.enqueue({
859
+ type: 'tool-input-delta',
860
+ id: active.toolCallId,
861
+ delta: textDelta,
862
+ providerMetadata: providerMeta,
863
+ });
864
+ }
865
+ }
866
+ } else if (
867
+ isTerminalChunk &&
868
+ activeStreamingToolCalls.length > 0
869
+ ) {
870
+ const active = activeStreamingToolCalls.pop()!;
871
+ const { finalJSON, closingDelta } =
872
+ active.accumulator.finalize();
873
+
874
+ if (closingDelta.length > 0) {
875
+ controller.enqueue({
876
+ type: 'tool-input-delta',
877
+ id: active.toolCallId,
878
+ delta: closingDelta,
879
+ providerMetadata: active.providerMetadata,
880
+ });
881
+ }
882
+
883
+ controller.enqueue({
884
+ type: 'tool-input-end',
885
+ id: active.toolCallId,
886
+ providerMetadata: active.providerMetadata,
887
+ });
888
+
889
+ controller.enqueue({
890
+ type: 'tool-call',
891
+ toolCallId: active.toolCallId,
892
+ toolName: active.toolName,
893
+ input: finalJSON,
894
+ providerMetadata: active.providerMetadata,
895
+ });
896
+
897
+ hasToolCalls = true;
898
+ } else if (isCompleteCall) {
899
+ const toolCallId = generateId();
900
+ const toolName = part.functionCall.name!;
901
+ const args =
902
+ typeof part.functionCall.args === 'string'
903
+ ? part.functionCall.args
904
+ : JSON.stringify(part.functionCall.args ?? {});
743
905
 
744
- if (toolCallDeltas != null) {
745
- for (const toolCall of toolCallDeltas) {
746
906
  controller.enqueue({
747
907
  type: 'tool-input-start',
748
- id: toolCall.toolCallId,
749
- toolName: toolCall.toolName,
750
- providerMetadata: toolCall.providerMetadata,
908
+ id: toolCallId,
909
+ toolName,
910
+ providerMetadata: providerMeta,
751
911
  });
752
912
 
753
913
  controller.enqueue({
754
914
  type: 'tool-input-delta',
755
- id: toolCall.toolCallId,
756
- delta: toolCall.args,
757
- providerMetadata: toolCall.providerMetadata,
915
+ id: toolCallId,
916
+ delta: args,
917
+ providerMetadata: providerMeta,
758
918
  });
759
919
 
760
920
  controller.enqueue({
761
921
  type: 'tool-input-end',
762
- id: toolCall.toolCallId,
763
- providerMetadata: toolCall.providerMetadata,
922
+ id: toolCallId,
923
+ providerMetadata: providerMeta,
764
924
  });
765
925
 
766
926
  controller.enqueue({
767
927
  type: 'tool-call',
768
- toolCallId: toolCall.toolCallId,
769
- toolName: toolCall.toolName,
770
- input: toolCall.args,
771
- providerMetadata: toolCall.providerMetadata,
928
+ toolCallId,
929
+ toolName,
930
+ input: args,
931
+ providerMetadata: providerMeta,
772
932
  });
773
933
 
774
934
  hasToolCalls = true;
@@ -1040,6 +1200,15 @@ export const getGroundingMetadataSchema = () =>
1040
1200
  .nullish(),
1041
1201
  });
1042
1202
 
1203
+ const partialArgSchema = z.object({
1204
+ jsonPath: z.string(),
1205
+ stringValue: z.string().nullish(),
1206
+ numberValue: z.number().nullish(),
1207
+ boolValue: z.boolean().nullish(),
1208
+ nullValue: z.unknown().nullish(),
1209
+ willContinue: z.boolean().nullish(),
1210
+ });
1211
+
1043
1212
  const getContentSchema = () =>
1044
1213
  z.object({
1045
1214
  parts: z
@@ -1048,8 +1217,10 @@ const getContentSchema = () =>
1048
1217
  // note: order matters since text can be fully empty
1049
1218
  z.object({
1050
1219
  functionCall: z.object({
1051
- name: z.string(),
1052
- args: z.unknown(),
1220
+ name: z.string().nullish(),
1221
+ args: z.unknown().nullish(),
1222
+ partialArgs: z.array(partialArgSchema).nullish(),
1223
+ willContinue: z.boolean().nullish(),
1053
1224
  }),
1054
1225
  thoughtSignature: z.string().nullish(),
1055
1226
  }),
@@ -189,6 +189,18 @@ export const googleLanguageModelOptions = lazySchema(() =>
189
189
  })
190
190
  .optional(),
191
191
 
192
+ /**
193
+ * Optional. When set to true, function call arguments will be streamed
194
+ * incrementally via partialArgs in streaming responses. Only supported
195
+ * on the Vertex AI API (not the Gemini API) and only for Gemini 3+
196
+ * models.
197
+ *
198
+ * @default false
199
+ *
200
+ * https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc
201
+ */
202
+ streamFunctionCallArguments: z.boolean().optional(),
203
+
192
204
  /**
193
205
  * Optional. The service tier to use for the request.
194
206
  */