@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.
- package/CHANGELOG.md +279 -4
- package/README.md +2 -0
- package/dist/index.d.ts +85 -26
- package/dist/index.js +1524 -469
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +62 -19
- package/dist/internal/index.js +1135 -361
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google-generative-ai.mdx +36 -3
- package/package.json +9 -12
- package/src/convert-google-generative-ai-usage.ts +9 -2
- package/src/convert-to-google-generative-ai-messages.ts +329 -50
- package/src/google-generative-ai-embedding-model.ts +64 -15
- package/src/google-generative-ai-embedding-options.ts +24 -0
- package/src/google-generative-ai-files.ts +228 -0
- package/src/google-generative-ai-image-model.ts +39 -15
- package/src/google-generative-ai-language-model.ts +557 -146
- package/src/google-generative-ai-options.ts +25 -2
- package/src/google-generative-ai-prompt.ts +48 -4
- package/src/google-generative-ai-video-model.ts +7 -7
- package/src/google-generative-ai-video-settings.ts +1 -0
- package/src/google-json-accumulator.ts +336 -0
- package/src/google-prepare-tools.ts +65 -9
- package/src/google-provider.ts +31 -18
- package/src/index.ts +1 -0
- package/src/map-google-generative-ai-finish-reason.ts +2 -2
- package/dist/index.d.mts +0 -368
- package/dist/index.mjs +0 -2482
- package/dist/index.mjs.map +0 -1
- package/dist/internal/index.d.mts +0 -284
- package/dist/internal/index.mjs +0 -1706
- package/dist/internal/index.mjs.map +0 -1
|
@@ -7,13 +7,11 @@ export type GoogleGenerativeAIModelId =
|
|
|
7
7
|
| 'gemini-2.0-flash'
|
|
8
8
|
| 'gemini-2.0-flash-001'
|
|
9
9
|
| 'gemini-2.0-flash-lite'
|
|
10
|
-
| 'gemini-2.0-flash-exp-image-generation'
|
|
11
10
|
| 'gemini-2.0-flash-lite-001'
|
|
12
11
|
| 'gemini-2.5-pro'
|
|
13
12
|
| 'gemini-2.5-flash'
|
|
14
13
|
| 'gemini-2.5-flash-image'
|
|
15
14
|
| 'gemini-2.5-flash-lite'
|
|
16
|
-
| 'gemini-2.5-flash-lite-preview-09-2025'
|
|
17
15
|
| 'gemini-2.5-flash-preview-tts'
|
|
18
16
|
| 'gemini-2.5-pro-preview-tts'
|
|
19
17
|
| 'gemini-2.5-flash-native-audio-latest'
|
|
@@ -27,6 +25,7 @@ export type GoogleGenerativeAIModelId =
|
|
|
27
25
|
| 'gemini-3.1-pro-preview-customtools'
|
|
28
26
|
| 'gemini-3.1-flash-image-preview'
|
|
29
27
|
| 'gemini-3.1-flash-lite-preview'
|
|
28
|
+
| 'gemini-3.1-flash-tts-preview'
|
|
30
29
|
// latest version
|
|
31
30
|
// https://ai.google.dev/gemini-api/docs/models#latest
|
|
32
31
|
| 'gemini-pro-latest'
|
|
@@ -189,6 +188,23 @@ export const googleLanguageModelOptions = lazySchema(() =>
|
|
|
189
188
|
.optional(),
|
|
190
189
|
})
|
|
191
190
|
.optional(),
|
|
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
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Optional. The service tier to use for the request.
|
|
206
|
+
*/
|
|
207
|
+
serviceTier: z.enum(['standard', 'flex', 'priority']).optional(),
|
|
192
208
|
}),
|
|
193
209
|
),
|
|
194
210
|
);
|
|
@@ -196,3 +212,10 @@ export const googleLanguageModelOptions = lazySchema(() =>
|
|
|
196
212
|
export type GoogleLanguageModelOptions = InferSchema<
|
|
197
213
|
typeof googleLanguageModelOptions
|
|
198
214
|
>;
|
|
215
|
+
|
|
216
|
+
// Vertex API requires another service tier format.
|
|
217
|
+
export const VertexServiceTierMap = {
|
|
218
|
+
standard: 'SERVICE_TIER_STANDARD',
|
|
219
|
+
flex: 'SERVICE_TIER_FLEX',
|
|
220
|
+
priority: 'SERVICE_TIER_PRIORITY',
|
|
221
|
+
} as const;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GroundingMetadataSchema,
|
|
3
|
+
PromptFeedbackSchema,
|
|
3
4
|
UrlContextMetadataSchema,
|
|
5
|
+
type SafetyRatingSchema,
|
|
6
|
+
UsageMetadataSchema,
|
|
4
7
|
} from './google-generative-ai-language-model';
|
|
5
|
-
import { type SafetyRatingSchema } from './google-generative-ai-language-model';
|
|
6
8
|
|
|
7
9
|
export type GoogleGenerativeAIPrompt = {
|
|
8
10
|
systemInstruction?: GoogleGenerativeAISystemInstruction;
|
|
@@ -20,10 +22,44 @@ export type GoogleGenerativeAIContent = {
|
|
|
20
22
|
|
|
21
23
|
export type GoogleGenerativeAIContentPart =
|
|
22
24
|
| { text: string; thought?: boolean; thoughtSignature?: string }
|
|
23
|
-
| {
|
|
25
|
+
| {
|
|
26
|
+
inlineData: { mimeType: string; data: string };
|
|
27
|
+
thought?: boolean;
|
|
28
|
+
thoughtSignature?: string;
|
|
29
|
+
}
|
|
24
30
|
| { functionCall: { name: string; args: unknown }; thoughtSignature?: string }
|
|
25
|
-
| {
|
|
26
|
-
|
|
31
|
+
| {
|
|
32
|
+
functionResponse: {
|
|
33
|
+
name: string;
|
|
34
|
+
response: unknown;
|
|
35
|
+
parts?: Array<GoogleGenerativeAIFunctionResponsePart>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
fileData: { mimeType: string; fileUri: string };
|
|
40
|
+
thought?: boolean;
|
|
41
|
+
thoughtSignature?: string;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
toolCall: {
|
|
45
|
+
toolType: string;
|
|
46
|
+
args?: unknown;
|
|
47
|
+
id: string;
|
|
48
|
+
};
|
|
49
|
+
thoughtSignature?: string;
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
toolResponse: {
|
|
53
|
+
toolType: string;
|
|
54
|
+
response?: unknown;
|
|
55
|
+
id: string;
|
|
56
|
+
};
|
|
57
|
+
thoughtSignature?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type GoogleGenerativeAIFunctionResponsePart = {
|
|
61
|
+
inlineData: { mimeType: string; data: string };
|
|
62
|
+
};
|
|
27
63
|
|
|
28
64
|
export type GoogleGenerativeAIGroundingMetadata = GroundingMetadataSchema;
|
|
29
65
|
|
|
@@ -31,8 +67,16 @@ export type GoogleGenerativeAIUrlContextMetadata = UrlContextMetadataSchema;
|
|
|
31
67
|
|
|
32
68
|
export type GoogleGenerativeAISafetyRating = SafetyRatingSchema;
|
|
33
69
|
|
|
70
|
+
export type GoogleGenerativeAIPromptFeedback = PromptFeedbackSchema;
|
|
71
|
+
|
|
72
|
+
export type GoogleGenerativeAIUsageMetadata = UsageMetadataSchema;
|
|
73
|
+
|
|
34
74
|
export interface GoogleGenerativeAIProviderMetadata {
|
|
75
|
+
promptFeedback: GoogleGenerativeAIPromptFeedback | null;
|
|
35
76
|
groundingMetadata: GoogleGenerativeAIGroundingMetadata | null;
|
|
36
77
|
urlContextMetadata: GoogleGenerativeAIUrlContextMetadata | null;
|
|
37
78
|
safetyRatings: GoogleGenerativeAISafetyRating[] | null;
|
|
79
|
+
usageMetadata: GoogleGenerativeAIUsageMetadata | null;
|
|
80
|
+
finishMessage: string | null;
|
|
81
|
+
serviceTier: string | null;
|
|
38
82
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AISDKError,
|
|
3
|
-
type
|
|
4
|
-
type
|
|
3
|
+
type Experimental_VideoModelV4,
|
|
4
|
+
type SharedV4Warning,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
6
|
import {
|
|
7
7
|
combineHeaders,
|
|
@@ -50,8 +50,8 @@ interface GoogleGenerativeAIVideoModelConfig {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export class GoogleGenerativeAIVideoModel implements
|
|
54
|
-
readonly specificationVersion = '
|
|
53
|
+
export class GoogleGenerativeAIVideoModel implements Experimental_VideoModelV4 {
|
|
54
|
+
readonly specificationVersion = 'v4';
|
|
55
55
|
|
|
56
56
|
get provider(): string {
|
|
57
57
|
return this.config.provider;
|
|
@@ -68,10 +68,10 @@ export class GoogleGenerativeAIVideoModel implements Experimental_VideoModelV3 {
|
|
|
68
68
|
) {}
|
|
69
69
|
|
|
70
70
|
async doGenerate(
|
|
71
|
-
options: Parameters<
|
|
72
|
-
): Promise<Awaited<ReturnType<
|
|
71
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
72
|
+
): Promise<Awaited<ReturnType<Experimental_VideoModelV4['doGenerate']>>> {
|
|
73
73
|
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
74
|
-
const warnings:
|
|
74
|
+
const warnings: SharedV4Warning[] = [];
|
|
75
75
|
|
|
76
76
|
const googleOptions = (await parseProviderOptions({
|
|
77
77
|
provider: 'google',
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
LanguageModelV4CallOptions,
|
|
3
|
+
SharedV4Warning,
|
|
4
4
|
UnsupportedFunctionalityError,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
6
|
import { convertJSONSchemaToOpenAPISchema } from './convert-json-schema-to-openapi-schema';
|
|
@@ -11,8 +11,8 @@ export function prepareTools({
|
|
|
11
11
|
toolChoice,
|
|
12
12
|
modelId,
|
|
13
13
|
}: {
|
|
14
|
-
tools:
|
|
15
|
-
toolChoice?:
|
|
14
|
+
tools: LanguageModelV4CallOptions['tools'];
|
|
15
|
+
toolChoice?: LanguageModelV4CallOptions['toolChoice'];
|
|
16
16
|
modelId: GoogleGenerativeAIModelId;
|
|
17
17
|
}): {
|
|
18
18
|
tools:
|
|
@@ -30,17 +30,19 @@ export function prepareTools({
|
|
|
30
30
|
toolConfig:
|
|
31
31
|
| undefined
|
|
32
32
|
| {
|
|
33
|
-
functionCallingConfig
|
|
33
|
+
functionCallingConfig?: {
|
|
34
34
|
mode: 'AUTO' | 'NONE' | 'ANY' | 'VALIDATED';
|
|
35
35
|
allowedFunctionNames?: string[];
|
|
36
|
+
streamFunctionCallArguments?: boolean;
|
|
36
37
|
};
|
|
38
|
+
includeServerSideToolInvocations?: boolean;
|
|
37
39
|
};
|
|
38
|
-
toolWarnings:
|
|
40
|
+
toolWarnings: SharedV4Warning[];
|
|
39
41
|
} {
|
|
40
42
|
// when the tools array is empty, change it to undefined to prevent errors:
|
|
41
43
|
tools = tools?.length ? tools : undefined;
|
|
42
44
|
|
|
43
|
-
const toolWarnings:
|
|
45
|
+
const toolWarnings: SharedV4Warning[] = [];
|
|
44
46
|
|
|
45
47
|
const isLatest = (
|
|
46
48
|
[
|
|
@@ -54,6 +56,7 @@ export function prepareTools({
|
|
|
54
56
|
modelId.includes('gemini-3') ||
|
|
55
57
|
modelId.includes('nano-banana') ||
|
|
56
58
|
isLatest;
|
|
59
|
+
const isGemini3orNewer = modelId.includes('gemini-3');
|
|
57
60
|
const supportsFileSearch =
|
|
58
61
|
modelId.includes('gemini-2.5') || modelId.includes('gemini-3');
|
|
59
62
|
|
|
@@ -65,7 +68,7 @@ export function prepareTools({
|
|
|
65
68
|
const hasFunctionTools = tools.some(tool => tool.type === 'function');
|
|
66
69
|
const hasProviderTools = tools.some(tool => tool.type === 'provider');
|
|
67
70
|
|
|
68
|
-
if (hasFunctionTools && hasProviderTools) {
|
|
71
|
+
if (hasFunctionTools && hasProviderTools && !isGemini3orNewer) {
|
|
69
72
|
toolWarnings.push({
|
|
70
73
|
type: 'unsupported',
|
|
71
74
|
feature: `combination of function and provider-defined tools`,
|
|
@@ -120,7 +123,7 @@ export function prepareTools({
|
|
|
120
123
|
type: 'unsupported',
|
|
121
124
|
feature: `provider-defined tool ${tool.id}`,
|
|
122
125
|
details:
|
|
123
|
-
'The code execution
|
|
126
|
+
'The code execution tool is not supported with other Gemini models than Gemini 2.',
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
129
|
break;
|
|
@@ -178,6 +181,59 @@ export function prepareTools({
|
|
|
178
181
|
}
|
|
179
182
|
});
|
|
180
183
|
|
|
184
|
+
if (hasFunctionTools && isGemini3orNewer && googleTools.length > 0) {
|
|
185
|
+
const functionDeclarations: Array<{
|
|
186
|
+
name: string;
|
|
187
|
+
description: string;
|
|
188
|
+
parameters: unknown;
|
|
189
|
+
}> = [];
|
|
190
|
+
for (const tool of tools) {
|
|
191
|
+
if (tool.type === 'function') {
|
|
192
|
+
functionDeclarations.push({
|
|
193
|
+
name: tool.name,
|
|
194
|
+
description: tool.description ?? '',
|
|
195
|
+
parameters: convertJSONSchemaToOpenAPISchema(tool.inputSchema),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const combinedToolConfig: {
|
|
201
|
+
functionCallingConfig: {
|
|
202
|
+
mode: 'VALIDATED' | 'ANY' | 'NONE';
|
|
203
|
+
allowedFunctionNames?: string[];
|
|
204
|
+
};
|
|
205
|
+
includeServerSideToolInvocations: true;
|
|
206
|
+
} = {
|
|
207
|
+
functionCallingConfig: { mode: 'VALIDATED' },
|
|
208
|
+
includeServerSideToolInvocations: true,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (toolChoice != null) {
|
|
212
|
+
switch (toolChoice.type) {
|
|
213
|
+
case 'auto':
|
|
214
|
+
break;
|
|
215
|
+
case 'none':
|
|
216
|
+
combinedToolConfig.functionCallingConfig = { mode: 'NONE' };
|
|
217
|
+
break;
|
|
218
|
+
case 'required':
|
|
219
|
+
combinedToolConfig.functionCallingConfig = { mode: 'ANY' };
|
|
220
|
+
break;
|
|
221
|
+
case 'tool':
|
|
222
|
+
combinedToolConfig.functionCallingConfig = {
|
|
223
|
+
mode: 'ANY',
|
|
224
|
+
allowedFunctionNames: [toolChoice.toolName],
|
|
225
|
+
};
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
tools: [...googleTools, { functionDeclarations }],
|
|
232
|
+
toolConfig: combinedToolConfig,
|
|
233
|
+
toolWarnings,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
181
237
|
return {
|
|
182
238
|
tools: googleTools.length > 0 ? googleTools : undefined,
|
|
183
239
|
toolConfig: undefined,
|