@ai-sdk/google 4.0.0-beta.8 → 4.0.0-canary.50

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 (47) hide show
  1. package/CHANGELOG.md +351 -4
  2. package/README.md +6 -4
  3. package/dist/index.d.ts +97 -54
  4. package/dist/index.js +1644 -580
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +66 -26
  7. package/dist/internal/index.js +1258 -450
  8. package/dist/internal/index.js.map +1 -1
  9. package/docs/{15-google-generative-ai.mdx → 15-google.mdx} +46 -40
  10. package/package.json +13 -14
  11. package/src/{convert-google-generative-ai-usage.ts → convert-google-usage.ts} +12 -5
  12. package/src/convert-json-schema-to-openapi-schema.ts +1 -1
  13. package/src/convert-to-google-messages.ts +577 -0
  14. package/src/{google-generative-ai-embedding-options.ts → google-embedding-model-options.ts} +2 -2
  15. package/src/{google-generative-ai-embedding-model.ts → google-embedding-model.ts} +31 -18
  16. package/src/google-error.ts +1 -1
  17. package/src/google-files.ts +225 -0
  18. package/src/google-image-model-options.ts +23 -0
  19. package/src/{google-generative-ai-image-model.ts → google-image-model.ts} +74 -62
  20. package/src/{google-generative-ai-image-settings.ts → google-image-settings.ts} +2 -2
  21. package/src/google-json-accumulator.ts +336 -0
  22. package/src/{google-generative-ai-options.ts → google-language-model-options.ts} +32 -5
  23. package/src/{google-generative-ai-language-model.ts → google-language-model.ts} +609 -214
  24. package/src/google-prepare-tools.ts +72 -12
  25. package/src/google-prompt.ts +82 -0
  26. package/src/google-provider.ts +63 -54
  27. package/src/google-video-model-options.ts +43 -0
  28. package/src/{google-generative-ai-video-model.ts → google-video-model.ts} +17 -56
  29. package/src/{google-generative-ai-video-settings.ts → google-video-settings.ts} +2 -1
  30. package/src/index.ts +28 -9
  31. package/src/internal/index.ts +2 -2
  32. package/src/{map-google-generative-ai-finish-reason.ts → map-google-finish-reason.ts} +3 -3
  33. package/src/tool/code-execution.ts +2 -2
  34. package/src/tool/enterprise-web-search.ts +9 -3
  35. package/src/tool/file-search.ts +5 -7
  36. package/src/tool/google-maps.ts +3 -2
  37. package/src/tool/google-search.ts +10 -11
  38. package/src/tool/url-context.ts +4 -2
  39. package/src/tool/vertex-rag-store.ts +9 -6
  40. package/dist/index.d.mts +0 -384
  41. package/dist/index.mjs +0 -2519
  42. package/dist/index.mjs.map +0 -1
  43. package/dist/internal/index.d.mts +0 -287
  44. package/dist/internal/index.mjs +0 -1708
  45. package/dist/internal/index.mjs.map +0 -1
  46. package/src/convert-to-google-generative-ai-messages.ts +0 -239
  47. package/src/google-generative-ai-prompt.ts +0 -47
@@ -0,0 +1,336 @@
1
+ export type PartialArg = {
2
+ jsonPath: string;
3
+ stringValue?: string | null;
4
+ numberValue?: number | null;
5
+ boolValue?: boolean | null;
6
+ nullValue?: unknown;
7
+ willContinue?: boolean | null;
8
+ };
9
+
10
+ type PathSegment = string | number;
11
+
12
+ type StackEntry = {
13
+ segment: PathSegment;
14
+ isArray: boolean;
15
+ childCount: number;
16
+ };
17
+
18
+ /**
19
+ * Incrementally builds a JSON object from Google's streaming `partialArgs`
20
+ * chunks emitted during tool-call function calling. Tracks both the structured
21
+ * object and a running JSON text representation so callers can emit text deltas
22
+ * that, when concatenated, form valid nested JSON matching JSON.stringify output.
23
+ *
24
+ * Input: [{jsonPath:"$.location",stringValue:"Boston"}]
25
+ * Output: '{"location":"Boston"', then finalize() → closingDelta='}'
26
+ */
27
+ export class GoogleJSONAccumulator {
28
+ private accumulatedArgs: Record<string, unknown> = {};
29
+ private jsonText = '';
30
+
31
+ /**
32
+ * Stack representing the currently "open" containers in the JSON output.
33
+ * Entry 0 is always the root `{` object once the first value is written.
34
+ */
35
+ private pathStack: StackEntry[] = [];
36
+
37
+ /**
38
+ * Whether a string value is currently "open" (willContinue was true),
39
+ * meaning the closing quote has not yet been emitted.
40
+ */
41
+ private stringOpen = false;
42
+
43
+ /**
44
+ * Input: [{jsonPath:"$.brightness",numberValue:50}]
45
+ * Output: { currentJSON:{brightness:50}, textDelta:'{"brightness":50' }
46
+ */
47
+ processPartialArgs(partialArgs: PartialArg[]): {
48
+ currentJSON: Record<string, unknown>;
49
+ textDelta: string;
50
+ } {
51
+ let delta = '';
52
+
53
+ for (const arg of partialArgs) {
54
+ const rawPath = arg.jsonPath.replace(/^\$\./, '');
55
+ if (!rawPath) continue;
56
+
57
+ const segments = parsePath(rawPath);
58
+
59
+ const existingValue = getNestedValue(this.accumulatedArgs, segments);
60
+ const isStringContinuation =
61
+ arg.stringValue != null && existingValue !== undefined;
62
+
63
+ if (isStringContinuation) {
64
+ const escaped = JSON.stringify(arg.stringValue).slice(1, -1);
65
+ setNestedValue(
66
+ this.accumulatedArgs,
67
+ segments,
68
+ (existingValue as string) + arg.stringValue,
69
+ );
70
+ delta += escaped;
71
+ continue;
72
+ }
73
+
74
+ const resolved = resolvePartialArgValue(arg);
75
+ if (resolved == null) continue;
76
+
77
+ setNestedValue(this.accumulatedArgs, segments, resolved.value);
78
+ delta += this.emitNavigationTo(segments, arg, resolved.json);
79
+ }
80
+
81
+ this.jsonText += delta;
82
+
83
+ return {
84
+ currentJSON: this.accumulatedArgs,
85
+ textDelta: delta,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Input: jsonText='{"brightness":50', accumulatedArgs={brightness:50}
91
+ * Output: { finalJSON:'{"brightness":50}', closingDelta:'}' }
92
+ */
93
+ finalize(): { finalJSON: string; closingDelta: string } {
94
+ const finalArgs = JSON.stringify(this.accumulatedArgs);
95
+ const closingDelta = finalArgs.slice(this.jsonText.length);
96
+ return { finalJSON: finalArgs, closingDelta };
97
+ }
98
+
99
+ /**
100
+ * Input: pathStack=[] (first call) or pathStack=[root,...] (subsequent calls)
101
+ * Output: '{' (first call) or '' (subsequent calls)
102
+ */
103
+ private ensureRoot(): string {
104
+ if (this.pathStack.length === 0) {
105
+ this.pathStack.push({ segment: '', isArray: false, childCount: 0 });
106
+ return '{';
107
+ }
108
+ return '';
109
+ }
110
+
111
+ /**
112
+ * Emits the JSON text fragment needed to navigate from the current open
113
+ * path to the new leaf at `targetSegments`, then writes the value.
114
+ *
115
+ * Input: targetSegments=["recipe","name"], arg={jsonPath:"$.recipe.name",stringValue:"Lasagna"}, valueJson='"Lasagna"'
116
+ * Output: '{"recipe":{"name":"Lasagna"'
117
+ */
118
+ private emitNavigationTo(
119
+ targetSegments: PathSegment[],
120
+ arg: PartialArg,
121
+ valueJson: string,
122
+ ): string {
123
+ let fragment = '';
124
+
125
+ if (this.stringOpen) {
126
+ fragment += '"';
127
+ this.stringOpen = false;
128
+ }
129
+
130
+ fragment += this.ensureRoot();
131
+
132
+ const targetContainerSegments = targetSegments.slice(0, -1);
133
+ const leafSegment = targetSegments[targetSegments.length - 1];
134
+
135
+ const commonDepth = this.findCommonStackDepth(targetContainerSegments);
136
+
137
+ fragment += this.closeDownTo(commonDepth);
138
+ fragment += this.openDownTo(targetContainerSegments, leafSegment);
139
+ fragment += this.emitLeaf(leafSegment, arg, valueJson);
140
+
141
+ return fragment;
142
+ }
143
+
144
+ /**
145
+ * Returns the stack depth to preserve when navigating to a new target
146
+ * container path. Always >= 1 (the root is never popped).
147
+ *
148
+ * Input: stack=[root,"recipe","ingredients",0], target=["recipe","ingredients",1]
149
+ * Output: 3 (keep root+"recipe"+"ingredients")
150
+ */
151
+ private findCommonStackDepth(targetContainer: PathSegment[]): number {
152
+ const maxDepth = Math.min(
153
+ this.pathStack.length - 1,
154
+ targetContainer.length,
155
+ );
156
+ let common = 0;
157
+ for (let i = 0; i < maxDepth; i++) {
158
+ if (this.pathStack[i + 1].segment === targetContainer[i]) {
159
+ common++;
160
+ } else {
161
+ break;
162
+ }
163
+ }
164
+ return common + 1;
165
+ }
166
+
167
+ /**
168
+ * Closes containers from the current stack depth back down to `targetDepth`.
169
+ *
170
+ * Input: this.pathStack=[root,"recipe","ingredients",0], targetDepth=3
171
+ * Output: '}'
172
+ */
173
+ private closeDownTo(targetDepth: number): string {
174
+ let fragment = '';
175
+ while (this.pathStack.length > targetDepth) {
176
+ const entry = this.pathStack.pop()!;
177
+ fragment += entry.isArray ? ']' : '}';
178
+ }
179
+ return fragment;
180
+ }
181
+
182
+ /**
183
+ * Opens containers from the current stack depth down to the full target
184
+ * container path, emitting opening `{`, `[`, keys, and commas as needed.
185
+ * `leafSegment` is used to determine if the innermost container is an array.
186
+ *
187
+ * Input: this.pathStack=[root], targetContainer=["recipe","ingredients"], leafSegment=0
188
+ * Output: '"recipe":{"ingredients":['
189
+ */
190
+ private openDownTo(
191
+ targetContainer: PathSegment[],
192
+ leafSegment: PathSegment,
193
+ ): string {
194
+ let fragment = '';
195
+
196
+ const startIdx = this.pathStack.length - 1;
197
+
198
+ for (let i = startIdx; i < targetContainer.length; i++) {
199
+ const seg = targetContainer[i];
200
+ const parentEntry = this.pathStack[this.pathStack.length - 1];
201
+
202
+ if (parentEntry.childCount > 0) {
203
+ fragment += ',';
204
+ }
205
+ parentEntry.childCount++;
206
+
207
+ if (typeof seg === 'string') {
208
+ fragment += `${JSON.stringify(seg)}:`;
209
+ }
210
+
211
+ const childSeg =
212
+ i + 1 < targetContainer.length ? targetContainer[i + 1] : leafSegment;
213
+ const isArray = typeof childSeg === 'number';
214
+
215
+ fragment += isArray ? '[' : '{';
216
+
217
+ this.pathStack.push({ segment: seg, isArray, childCount: 0 });
218
+ }
219
+
220
+ return fragment;
221
+ }
222
+
223
+ /**
224
+ * Emits the comma, key, and value for a leaf entry in the current container.
225
+ *
226
+ * Input: leafSegment="name", arg={stringValue:"Lasagna"}, valueJson='"Lasagna"'
227
+ * Output: '"name":"Lasagna"' (or ',"name":"Lasagna"' if container.childCount > 0)
228
+ */
229
+ private emitLeaf(
230
+ leafSegment: PathSegment,
231
+ arg: PartialArg,
232
+ valueJson: string,
233
+ ): string {
234
+ let fragment = '';
235
+ const container = this.pathStack[this.pathStack.length - 1];
236
+
237
+ if (container.childCount > 0) {
238
+ fragment += ',';
239
+ }
240
+ container.childCount++;
241
+
242
+ if (typeof leafSegment === 'string') {
243
+ fragment += `${JSON.stringify(leafSegment)}:`;
244
+ }
245
+
246
+ if (arg.stringValue != null && arg.willContinue) {
247
+ fragment += valueJson.slice(0, -1);
248
+ this.stringOpen = true;
249
+ } else {
250
+ fragment += valueJson;
251
+ }
252
+
253
+ return fragment;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Splits a dotted/bracketed JSON path like `recipe.ingredients[0].name` into segments.
259
+ *
260
+ * Input: "recipe.ingredients[0].name"
261
+ * Output: ["recipe", "ingredients", 0, "name"]
262
+ */
263
+ function parsePath(rawPath: string): Array<string | number> {
264
+ const segments: Array<string | number> = [];
265
+ for (const part of rawPath.split('.')) {
266
+ const bracketIdx = part.indexOf('[');
267
+ if (bracketIdx === -1) {
268
+ segments.push(part);
269
+ } else {
270
+ if (bracketIdx > 0) segments.push(part.slice(0, bracketIdx));
271
+ for (const m of part.matchAll(/\[(\d+)\]/g)) {
272
+ segments.push(parseInt(m[1], 10));
273
+ }
274
+ }
275
+ }
276
+ return segments;
277
+ }
278
+
279
+ /**
280
+ * Traverses a nested object along the given path segments and returns the leaf value.
281
+ *
282
+ * Input: ({recipe:{name:"Lasagna"}}, ["recipe","name"])
283
+ * Output: "Lasagna"
284
+ */
285
+ function getNestedValue(
286
+ obj: Record<string, unknown>,
287
+ segments: Array<string | number>,
288
+ ): unknown {
289
+ let current: unknown = obj;
290
+ for (const seg of segments) {
291
+ if (current == null || typeof current !== 'object') return undefined;
292
+ current = (current as Record<string | number, unknown>)[seg];
293
+ }
294
+ return current;
295
+ }
296
+
297
+ /**
298
+ * Sets a value at a nested path, creating intermediate objects or arrays as needed.
299
+ *
300
+ * Input: obj={}, segments=["recipe","ingredients",0,"name"], value="Noodles"
301
+ * Output: {recipe:{ingredients:[{name:"Noodles"}]}}
302
+ */
303
+ function setNestedValue(
304
+ obj: Record<string, unknown>,
305
+ segments: Array<string | number>,
306
+ value: unknown,
307
+ ): void {
308
+ let current: Record<string | number, unknown> = obj;
309
+ for (let i = 0; i < segments.length - 1; i++) {
310
+ const seg = segments[i];
311
+ const nextSeg = segments[i + 1];
312
+ if (current[seg] == null) {
313
+ current[seg] = typeof nextSeg === 'number' ? [] : {};
314
+ }
315
+ current = current[seg] as Record<string | number, unknown>;
316
+ }
317
+ current[segments[segments.length - 1]] = value;
318
+ }
319
+
320
+ /**
321
+ * Extracts the first non-null typed value from a partial arg and returns it with its JSON representation.
322
+ *
323
+ * Input: arg={stringValue:"Boston"} or arg={numberValue:50}
324
+ * Output: {value:"Boston", json:'"Boston"'} or {value:50, json:'50'}
325
+ */
326
+ function resolvePartialArgValue(arg: {
327
+ stringValue?: string | null;
328
+ numberValue?: number | null;
329
+ boolValue?: boolean | null;
330
+ nullValue?: unknown;
331
+ }): { value: unknown; json: string } | undefined {
332
+ const value = arg.stringValue ?? arg.numberValue ?? arg.boolValue;
333
+ if (value != null) return { value, json: JSON.stringify(value) };
334
+ if ('nullValue' in arg) return { value: null, json: 'null' };
335
+ return undefined;
336
+ }
@@ -1,19 +1,21 @@
1
- import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
1
+ import {
2
+ lazySchema,
3
+ zodSchema,
4
+ type InferSchema,
5
+ } from '@ai-sdk/provider-utils';
2
6
  import { z } from 'zod/v4';
3
7
 
4
- export type GoogleGenerativeAIModelId =
8
+ export type GoogleModelId =
5
9
  // Stable models
6
10
  // https://ai.google.dev/gemini-api/docs/models/gemini
7
11
  | 'gemini-2.0-flash'
8
12
  | 'gemini-2.0-flash-001'
9
13
  | 'gemini-2.0-flash-lite'
10
- | 'gemini-2.0-flash-exp-image-generation'
11
14
  | 'gemini-2.0-flash-lite-001'
12
15
  | 'gemini-2.5-pro'
13
16
  | 'gemini-2.5-flash'
14
17
  | 'gemini-2.5-flash-image'
15
18
  | 'gemini-2.5-flash-lite'
16
- | 'gemini-2.5-flash-lite-preview-09-2025'
17
19
  | 'gemini-2.5-flash-preview-tts'
18
20
  | 'gemini-2.5-pro-preview-tts'
19
21
  | 'gemini-2.5-flash-native-audio-latest'
@@ -27,6 +29,7 @@ export type GoogleGenerativeAIModelId =
27
29
  | 'gemini-3.1-pro-preview-customtools'
28
30
  | 'gemini-3.1-flash-image-preview'
29
31
  | 'gemini-3.1-flash-lite-preview'
32
+ | 'gemini-3.1-flash-tts-preview'
30
33
  // latest version
31
34
  // https://ai.google.dev/gemini-api/docs/models#latest
32
35
  | 'gemini-pro-latest'
@@ -74,7 +77,7 @@ export const googleLanguageModelOptions = lazySchema(() =>
74
77
  *
75
78
  * This is useful when the JSON Schema contains elements that are
76
79
  * not supported by the OpenAPI schema version that
77
- * Google Generative AI uses. You can use this to disable
80
+ * Google uses. You can use this to disable
78
81
  * structured outputs if you need to.
79
82
  */
80
83
  structuredOutputs: z.boolean().optional(),
@@ -189,6 +192,23 @@ export const googleLanguageModelOptions = lazySchema(() =>
189
192
  .optional(),
190
193
  })
191
194
  .optional(),
195
+
196
+ /**
197
+ * Optional. When set to true, function call arguments will be streamed
198
+ * incrementally via partialArgs in streaming responses. Only supported
199
+ * on the Vertex AI API (not the Gemini API) and only for Gemini 3+
200
+ * models.
201
+ *
202
+ * @default false
203
+ *
204
+ * https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc
205
+ */
206
+ streamFunctionCallArguments: z.boolean().optional(),
207
+
208
+ /**
209
+ * Optional. The service tier to use for the request.
210
+ */
211
+ serviceTier: z.enum(['standard', 'flex', 'priority']).optional(),
192
212
  }),
193
213
  ),
194
214
  );
@@ -196,3 +216,10 @@ export const googleLanguageModelOptions = lazySchema(() =>
196
216
  export type GoogleLanguageModelOptions = InferSchema<
197
217
  typeof googleLanguageModelOptions
198
218
  >;
219
+
220
+ // Vertex API requires another service tier format.
221
+ export const VertexServiceTierMap = {
222
+ standard: 'SERVICE_TIER_STANDARD',
223
+ flex: 'SERVICE_TIER_FLEX',
224
+ priority: 'SERVICE_TIER_PRIORITY',
225
+ } as const;