@tanstack/ai-gemini 0.17.2 → 0.18.1
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 +5 -4
- package/src/experimental/text-interactions/adapter.ts +304 -100
- package/src/experimental/text-interactions/events.ts +29 -23
- package/src/experimental/text-interactions/provider-options.ts +0 -1
- package/dist/esm/adapters/audio.d.ts +0 -92
- package/dist/esm/adapters/audio.js +0 -66
- package/dist/esm/adapters/audio.js.map +0 -1
- package/dist/esm/adapters/image.d.ts +0 -101
- package/dist/esm/adapters/image.js +0 -253
- package/dist/esm/adapters/image.js.map +0 -1
- package/dist/esm/adapters/summarize.d.ts +0 -33
- package/dist/esm/adapters/summarize.js +0 -18
- package/dist/esm/adapters/summarize.js.map +0 -1
- package/dist/esm/adapters/text.d.ts +0 -85
- package/dist/esm/adapters/text.js +0 -658
- package/dist/esm/adapters/text.js.map +0 -1
- package/dist/esm/adapters/tts.d.ts +0 -165
- package/dist/esm/adapters/tts.js +0 -192
- package/dist/esm/adapters/tts.js.map +0 -1
- package/dist/esm/adapters/video.d.ts +0 -108
- package/dist/esm/adapters/video.js +0 -227
- package/dist/esm/adapters/video.js.map +0 -1
- package/dist/esm/experimental/index.d.ts +0 -6
- package/dist/esm/experimental/index.js +0 -7
- package/dist/esm/experimental/index.js.map +0 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +0 -75
- package/dist/esm/experimental/text-interactions/adapter.js +0 -899
- package/dist/esm/experimental/text-interactions/adapter.js.map +0 -1
- package/dist/esm/experimental/text-interactions/events.d.ts +0 -141
- package/dist/esm/experimental/text-interactions/provider-options.d.ts +0 -15
- package/dist/esm/image/image-provider-options.d.ts +0 -181
- package/dist/esm/image/image-provider-options.js +0 -67
- package/dist/esm/image/image-provider-options.js.map +0 -1
- package/dist/esm/index.d.ts +0 -33
- package/dist/esm/index.js +0 -38
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/message-types.d.ts +0 -104
- package/dist/esm/model-meta.d.ts +0 -242
- package/dist/esm/model-meta.js +0 -159
- package/dist/esm/model-meta.js.map +0 -1
- package/dist/esm/text/text-provider-options.d.ts +0 -196
- package/dist/esm/tools/code-execution-tool.d.ts +0 -10
- package/dist/esm/tools/code-execution-tool.js +0 -18
- package/dist/esm/tools/code-execution-tool.js.map +0 -1
- package/dist/esm/tools/computer-use-tool.d.ts +0 -13
- package/dist/esm/tools/computer-use-tool.js +0 -33
- package/dist/esm/tools/computer-use-tool.js.map +0 -1
- package/dist/esm/tools/file-search-tool.d.ts +0 -10
- package/dist/esm/tools/file-search-tool.js +0 -19
- package/dist/esm/tools/file-search-tool.js.map +0 -1
- package/dist/esm/tools/function-declaration-tool.d.ts +0 -5
- package/dist/esm/tools/function-declaration-tool.js +0 -28
- package/dist/esm/tools/function-declaration-tool.js.map +0 -1
- package/dist/esm/tools/google-maps-tool.d.ts +0 -10
- package/dist/esm/tools/google-maps-tool.js +0 -19
- package/dist/esm/tools/google-maps-tool.js.map +0 -1
- package/dist/esm/tools/google-search-retriveal-tool.d.ts +0 -10
- package/dist/esm/tools/google-search-retriveal-tool.js +0 -19
- package/dist/esm/tools/google-search-retriveal-tool.js.map +0 -1
- package/dist/esm/tools/google-search-tool.d.ts +0 -10
- package/dist/esm/tools/google-search-tool.js +0 -19
- package/dist/esm/tools/google-search-tool.js.map +0 -1
- package/dist/esm/tools/index.d.ts +0 -18
- package/dist/esm/tools/index.js +0 -21
- package/dist/esm/tools/index.js.map +0 -1
- package/dist/esm/tools/tool-converter.d.ts +0 -22
- package/dist/esm/tools/tool-converter.js +0 -66
- package/dist/esm/tools/tool-converter.js.map +0 -1
- package/dist/esm/tools/url-context-tool.d.ts +0 -10
- package/dist/esm/tools/url-context-tool.js +0 -18
- package/dist/esm/tools/url-context-tool.js.map +0 -1
- package/dist/esm/usage.d.ts +0 -67
- package/dist/esm/usage.js +0 -94
- package/dist/esm/usage.js.map +0 -1
- package/dist/esm/utils/client.d.ts +0 -17
- package/dist/esm/utils/client.js +0 -30
- package/dist/esm/utils/client.js.map +0 -1
- package/dist/esm/utils/index.d.ts +0 -1
- package/dist/esm/video/video-provider-options.d.ts +0 -90
- package/dist/esm/video/video-provider-options.js +0 -15
- package/dist/esm/video/video-provider-options.js.map +0 -1
|
@@ -1,658 +0,0 @@
|
|
|
1
|
-
import { FinishReason } from "@google/genai";
|
|
2
|
-
import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
|
|
3
|
-
import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
|
|
4
|
-
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
5
|
-
import { convertToolsToProviderFormat } from "../tools/tool-converter.js";
|
|
6
|
-
import { buildGeminiUsage } from "../usage.js";
|
|
7
|
-
import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
8
|
-
import { GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS } from "../model-meta.js";
|
|
9
|
-
class GeminiTextAdapter extends BaseTextAdapter {
|
|
10
|
-
kind = "text";
|
|
11
|
-
name = "gemini";
|
|
12
|
-
client;
|
|
13
|
-
constructor(config, model) {
|
|
14
|
-
super({}, model);
|
|
15
|
-
this.client = createGeminiClient(config);
|
|
16
|
-
}
|
|
17
|
-
async *chatStream(options) {
|
|
18
|
-
const mappedOptions = this.mapCommonOptionsToGemini(options);
|
|
19
|
-
const { logger } = options;
|
|
20
|
-
try {
|
|
21
|
-
logger.request(
|
|
22
|
-
`activity=chat provider=gemini model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
|
|
23
|
-
{ provider: "gemini", model: this.model }
|
|
24
|
-
);
|
|
25
|
-
const result = await this.client.models.generateContentStream(mappedOptions);
|
|
26
|
-
yield* this.processStreamChunks(result, options, logger);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
const rawEvent = toRunErrorRawEvent(error);
|
|
29
|
-
logger.errors("gemini.chatStream fatal", {
|
|
30
|
-
error,
|
|
31
|
-
source: "gemini.chatStream"
|
|
32
|
-
});
|
|
33
|
-
yield {
|
|
34
|
-
type: EventType.RUN_ERROR,
|
|
35
|
-
model: options.model,
|
|
36
|
-
timestamp: Date.now(),
|
|
37
|
-
message: error instanceof Error ? error.message : "An unknown error occurred during the chat stream.",
|
|
38
|
-
// Forward the provider's structured error body when present (see
|
|
39
|
-
// toRunErrorRawEvent); omitted otherwise.
|
|
40
|
-
...rawEvent !== void 0 && { rawEvent },
|
|
41
|
-
error: {
|
|
42
|
-
message: error instanceof Error ? error.message : "An unknown error occurred during the chat stream."
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Generate structured output using Gemini's native JSON response format.
|
|
49
|
-
* Uses responseMimeType: 'application/json' and responseSchema for structured output.
|
|
50
|
-
* The outputSchema is already JSON Schema (converted in the ai layer).
|
|
51
|
-
*/
|
|
52
|
-
async structuredOutput(options) {
|
|
53
|
-
const { chatOptions, outputSchema } = options;
|
|
54
|
-
const { logger } = chatOptions;
|
|
55
|
-
const mappedOptions = this.mapCommonOptionsToGemini(chatOptions);
|
|
56
|
-
try {
|
|
57
|
-
logger.request(
|
|
58
|
-
`activity=chat provider=gemini model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,
|
|
59
|
-
{ provider: "gemini", model: this.model }
|
|
60
|
-
);
|
|
61
|
-
const result = await this.client.models.generateContent({
|
|
62
|
-
...mappedOptions,
|
|
63
|
-
config: {
|
|
64
|
-
...mappedOptions.config,
|
|
65
|
-
responseMimeType: "application/json",
|
|
66
|
-
responseSchema: outputSchema
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
const rawText = this.extractTextFromResponse(result);
|
|
70
|
-
let parsed;
|
|
71
|
-
try {
|
|
72
|
-
parsed = JSON.parse(rawText);
|
|
73
|
-
} catch {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
data: parsed,
|
|
80
|
-
rawText,
|
|
81
|
-
usage: result.usageMetadata ? buildGeminiUsage(result.usageMetadata) : void 0
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
logger.errors("gemini.structuredOutput fatal", {
|
|
85
|
-
error,
|
|
86
|
-
source: "gemini.structuredOutput"
|
|
87
|
-
});
|
|
88
|
-
throw new Error(
|
|
89
|
-
error instanceof Error ? error.message : "An unknown error occurred during structured output generation."
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Extract text content from a non-streaming response
|
|
95
|
-
*/
|
|
96
|
-
extractTextFromResponse(response) {
|
|
97
|
-
let textContent = "";
|
|
98
|
-
if (response.candidates?.[0]?.content?.parts) {
|
|
99
|
-
for (const part of response.candidates[0].content.parts) {
|
|
100
|
-
if (part.text) {
|
|
101
|
-
textContent += part.text;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return textContent;
|
|
106
|
-
}
|
|
107
|
-
async *processStreamChunks(result, options, logger) {
|
|
108
|
-
const model = options.model;
|
|
109
|
-
let accumulatedContent = "";
|
|
110
|
-
let accumulatedThinking = "";
|
|
111
|
-
const toolCallMap = /* @__PURE__ */ new Map();
|
|
112
|
-
let nextToolIndex = 0;
|
|
113
|
-
const runId = options.runId ?? generateId(this.name);
|
|
114
|
-
const threadId = options.threadId ?? generateId(this.name);
|
|
115
|
-
const messageId = generateId(this.name);
|
|
116
|
-
let stepId = null;
|
|
117
|
-
let reasoningMessageId = null;
|
|
118
|
-
let hasClosedReasoning = false;
|
|
119
|
-
let hasEmittedRunStarted = false;
|
|
120
|
-
let hasEmittedTextMessageStart = false;
|
|
121
|
-
let hasEmittedStepStarted = false;
|
|
122
|
-
for await (const chunk of result) {
|
|
123
|
-
logger.provider(`provider=gemini`, { chunk });
|
|
124
|
-
if (!hasEmittedRunStarted) {
|
|
125
|
-
hasEmittedRunStarted = true;
|
|
126
|
-
yield {
|
|
127
|
-
type: EventType.RUN_STARTED,
|
|
128
|
-
runId,
|
|
129
|
-
threadId,
|
|
130
|
-
model,
|
|
131
|
-
timestamp: Date.now(),
|
|
132
|
-
parentRunId: options.parentRunId
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
if (chunk.candidates?.[0]?.content?.parts) {
|
|
136
|
-
const parts = chunk.candidates[0].content.parts;
|
|
137
|
-
for (const part of parts) {
|
|
138
|
-
if (part.text) {
|
|
139
|
-
if (part.thought) {
|
|
140
|
-
if (!hasEmittedStepStarted) {
|
|
141
|
-
hasEmittedStepStarted = true;
|
|
142
|
-
stepId = generateId(this.name);
|
|
143
|
-
reasoningMessageId = generateId(this.name);
|
|
144
|
-
yield {
|
|
145
|
-
type: EventType.REASONING_START,
|
|
146
|
-
messageId: reasoningMessageId,
|
|
147
|
-
model,
|
|
148
|
-
timestamp: Date.now()
|
|
149
|
-
};
|
|
150
|
-
yield {
|
|
151
|
-
type: EventType.REASONING_MESSAGE_START,
|
|
152
|
-
messageId: reasoningMessageId,
|
|
153
|
-
role: "reasoning",
|
|
154
|
-
model,
|
|
155
|
-
timestamp: Date.now()
|
|
156
|
-
};
|
|
157
|
-
yield {
|
|
158
|
-
type: EventType.STEP_STARTED,
|
|
159
|
-
stepName: stepId,
|
|
160
|
-
stepId,
|
|
161
|
-
model,
|
|
162
|
-
timestamp: Date.now(),
|
|
163
|
-
stepType: "thinking"
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
accumulatedThinking += part.text;
|
|
167
|
-
if (!reasoningMessageId) continue;
|
|
168
|
-
yield {
|
|
169
|
-
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
170
|
-
messageId: reasoningMessageId,
|
|
171
|
-
delta: part.text,
|
|
172
|
-
model,
|
|
173
|
-
timestamp: Date.now()
|
|
174
|
-
};
|
|
175
|
-
yield {
|
|
176
|
-
type: EventType.STEP_FINISHED,
|
|
177
|
-
stepName: stepId || generateId(this.name),
|
|
178
|
-
stepId: stepId || generateId(this.name),
|
|
179
|
-
model,
|
|
180
|
-
timestamp: Date.now(),
|
|
181
|
-
delta: part.text,
|
|
182
|
-
content: accumulatedThinking
|
|
183
|
-
};
|
|
184
|
-
} else if (part.text.trim()) {
|
|
185
|
-
if (reasoningMessageId && !hasClosedReasoning) {
|
|
186
|
-
hasClosedReasoning = true;
|
|
187
|
-
yield {
|
|
188
|
-
type: EventType.REASONING_MESSAGE_END,
|
|
189
|
-
messageId: reasoningMessageId,
|
|
190
|
-
model,
|
|
191
|
-
timestamp: Date.now()
|
|
192
|
-
};
|
|
193
|
-
yield {
|
|
194
|
-
type: EventType.REASONING_END,
|
|
195
|
-
messageId: reasoningMessageId,
|
|
196
|
-
model,
|
|
197
|
-
timestamp: Date.now()
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
if (!hasEmittedTextMessageStart) {
|
|
201
|
-
hasEmittedTextMessageStart = true;
|
|
202
|
-
yield {
|
|
203
|
-
type: EventType.TEXT_MESSAGE_START,
|
|
204
|
-
messageId,
|
|
205
|
-
model,
|
|
206
|
-
timestamp: Date.now(),
|
|
207
|
-
role: "assistant"
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
accumulatedContent += part.text;
|
|
211
|
-
yield {
|
|
212
|
-
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
213
|
-
messageId,
|
|
214
|
-
model,
|
|
215
|
-
timestamp: Date.now(),
|
|
216
|
-
delta: part.text,
|
|
217
|
-
content: accumulatedContent
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const functionCall = part.functionCall;
|
|
222
|
-
if (functionCall) {
|
|
223
|
-
const toolCallId = functionCall.id || `${functionCall.name}_${Date.now()}_${nextToolIndex}`;
|
|
224
|
-
const functionArgs = functionCall.args || {};
|
|
225
|
-
const partThoughtSignature = part.thoughtSignature || void 0;
|
|
226
|
-
let toolCallData = toolCallMap.get(toolCallId);
|
|
227
|
-
if (!toolCallData) {
|
|
228
|
-
toolCallData = {
|
|
229
|
-
name: functionCall.name || "",
|
|
230
|
-
args: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs),
|
|
231
|
-
index: nextToolIndex++,
|
|
232
|
-
started: false,
|
|
233
|
-
// Only set thoughtSignature when present — under EOPT, the
|
|
234
|
-
// optional field cannot accept an explicit `undefined`.
|
|
235
|
-
...partThoughtSignature !== void 0 && {
|
|
236
|
-
thoughtSignature: partThoughtSignature
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
toolCallMap.set(toolCallId, toolCallData);
|
|
240
|
-
} else {
|
|
241
|
-
if (!toolCallData.thoughtSignature && partThoughtSignature) {
|
|
242
|
-
toolCallData.thoughtSignature = partThoughtSignature;
|
|
243
|
-
}
|
|
244
|
-
try {
|
|
245
|
-
const existingArgs = JSON.parse(toolCallData.args);
|
|
246
|
-
const newArgs = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
247
|
-
const mergedArgs = { ...existingArgs, ...newArgs };
|
|
248
|
-
toolCallData.args = JSON.stringify(mergedArgs);
|
|
249
|
-
} catch {
|
|
250
|
-
toolCallData.args = typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (!toolCallData.started) {
|
|
254
|
-
toolCallData.started = true;
|
|
255
|
-
yield {
|
|
256
|
-
type: EventType.TOOL_CALL_START,
|
|
257
|
-
toolCallId,
|
|
258
|
-
toolCallName: toolCallData.name,
|
|
259
|
-
toolName: toolCallData.name,
|
|
260
|
-
parentMessageId: messageId,
|
|
261
|
-
model,
|
|
262
|
-
timestamp: Date.now(),
|
|
263
|
-
index: toolCallData.index,
|
|
264
|
-
...toolCallData.thoughtSignature && {
|
|
265
|
-
metadata: {
|
|
266
|
-
thoughtSignature: toolCallData.thoughtSignature
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
yield {
|
|
272
|
-
type: EventType.TOOL_CALL_ARGS,
|
|
273
|
-
toolCallId,
|
|
274
|
-
model,
|
|
275
|
-
timestamp: Date.now(),
|
|
276
|
-
delta: toolCallData.args,
|
|
277
|
-
args: toolCallData.args
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
} else if (chunk.data && chunk.data.trim()) {
|
|
282
|
-
if (!hasEmittedTextMessageStart) {
|
|
283
|
-
hasEmittedTextMessageStart = true;
|
|
284
|
-
yield {
|
|
285
|
-
type: EventType.TEXT_MESSAGE_START,
|
|
286
|
-
messageId,
|
|
287
|
-
model,
|
|
288
|
-
timestamp: Date.now(),
|
|
289
|
-
role: "assistant"
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
accumulatedContent += chunk.data;
|
|
293
|
-
yield {
|
|
294
|
-
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
295
|
-
messageId,
|
|
296
|
-
model,
|
|
297
|
-
timestamp: Date.now(),
|
|
298
|
-
delta: chunk.data,
|
|
299
|
-
content: accumulatedContent
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
if (chunk.candidates?.[0]?.finishReason) {
|
|
303
|
-
const finishReason = chunk.candidates[0].finishReason;
|
|
304
|
-
if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {
|
|
305
|
-
if (chunk.candidates[0].content?.parts) {
|
|
306
|
-
for (const part of chunk.candidates[0].content.parts) {
|
|
307
|
-
const functionCall = part.functionCall;
|
|
308
|
-
if (functionCall) {
|
|
309
|
-
const toolCallId = functionCall.id || `${functionCall.name}_${Date.now()}_${nextToolIndex}`;
|
|
310
|
-
const functionArgs = functionCall.args || {};
|
|
311
|
-
const argsString = typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs);
|
|
312
|
-
toolCallMap.set(toolCallId, {
|
|
313
|
-
name: functionCall.name || "",
|
|
314
|
-
args: argsString,
|
|
315
|
-
index: nextToolIndex++,
|
|
316
|
-
started: true
|
|
317
|
-
});
|
|
318
|
-
yield {
|
|
319
|
-
type: EventType.TOOL_CALL_START,
|
|
320
|
-
toolCallId,
|
|
321
|
-
toolCallName: functionCall.name || "",
|
|
322
|
-
toolName: functionCall.name || "",
|
|
323
|
-
parentMessageId: messageId,
|
|
324
|
-
model,
|
|
325
|
-
timestamp: Date.now(),
|
|
326
|
-
index: nextToolIndex - 1
|
|
327
|
-
};
|
|
328
|
-
let parsedInput = {};
|
|
329
|
-
try {
|
|
330
|
-
const parsed = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
331
|
-
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
332
|
-
} catch {
|
|
333
|
-
parsedInput = {};
|
|
334
|
-
}
|
|
335
|
-
yield {
|
|
336
|
-
type: EventType.TOOL_CALL_END,
|
|
337
|
-
toolCallId,
|
|
338
|
-
toolCallName: functionCall.name || "",
|
|
339
|
-
toolName: functionCall.name || "",
|
|
340
|
-
model,
|
|
341
|
-
timestamp: Date.now(),
|
|
342
|
-
input: parsedInput
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
for (const [toolCallId, toolCallData] of toolCallMap.entries()) {
|
|
349
|
-
let parsedInput = {};
|
|
350
|
-
try {
|
|
351
|
-
const parsed = JSON.parse(toolCallData.args);
|
|
352
|
-
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
353
|
-
} catch {
|
|
354
|
-
parsedInput = {};
|
|
355
|
-
}
|
|
356
|
-
yield {
|
|
357
|
-
type: EventType.TOOL_CALL_END,
|
|
358
|
-
toolCallId,
|
|
359
|
-
toolCallName: toolCallData.name,
|
|
360
|
-
toolName: toolCallData.name,
|
|
361
|
-
model,
|
|
362
|
-
timestamp: Date.now(),
|
|
363
|
-
input: parsedInput
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
if (toolCallMap.size > 0) {
|
|
367
|
-
hasEmittedTextMessageStart = false;
|
|
368
|
-
}
|
|
369
|
-
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
370
|
-
yield {
|
|
371
|
-
type: EventType.RUN_ERROR,
|
|
372
|
-
runId,
|
|
373
|
-
model,
|
|
374
|
-
timestamp: Date.now(),
|
|
375
|
-
message: "The response was cut off because the maximum token limit was reached.",
|
|
376
|
-
code: "max_tokens",
|
|
377
|
-
error: {
|
|
378
|
-
message: "The response was cut off because the maximum token limit was reached.",
|
|
379
|
-
code: "max_tokens"
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
if (reasoningMessageId && !hasClosedReasoning) {
|
|
384
|
-
hasClosedReasoning = true;
|
|
385
|
-
yield {
|
|
386
|
-
type: EventType.REASONING_MESSAGE_END,
|
|
387
|
-
messageId: reasoningMessageId,
|
|
388
|
-
model,
|
|
389
|
-
timestamp: Date.now()
|
|
390
|
-
};
|
|
391
|
-
yield {
|
|
392
|
-
type: EventType.REASONING_END,
|
|
393
|
-
messageId: reasoningMessageId,
|
|
394
|
-
model,
|
|
395
|
-
timestamp: Date.now()
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
if (hasEmittedTextMessageStart) {
|
|
399
|
-
yield {
|
|
400
|
-
type: EventType.TEXT_MESSAGE_END,
|
|
401
|
-
messageId,
|
|
402
|
-
model,
|
|
403
|
-
timestamp: Date.now()
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
yield {
|
|
407
|
-
type: EventType.RUN_FINISHED,
|
|
408
|
-
runId,
|
|
409
|
-
threadId,
|
|
410
|
-
model,
|
|
411
|
-
timestamp: Date.now(),
|
|
412
|
-
finishReason: toolCallMap.size > 0 ? "tool_calls" : "stop",
|
|
413
|
-
// RunFinishedEvent.usage is `usage?: {...}` (no `| undefined`) under
|
|
414
|
-
// exactOptionalPropertyTypes; only include it when usageMetadata is
|
|
415
|
-
// present rather than assigning an explicit `undefined`.
|
|
416
|
-
...chunk.usageMetadata && {
|
|
417
|
-
usage: buildGeminiUsage(chunk.usageMetadata)
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
convertContentPartToGemini(part) {
|
|
424
|
-
switch (part.type) {
|
|
425
|
-
case "text":
|
|
426
|
-
return { text: part.content };
|
|
427
|
-
case "image":
|
|
428
|
-
case "audio":
|
|
429
|
-
case "video":
|
|
430
|
-
case "document": {
|
|
431
|
-
if (part.source.type === "data") {
|
|
432
|
-
return {
|
|
433
|
-
inlineData: {
|
|
434
|
-
data: part.source.value,
|
|
435
|
-
mimeType: part.source.mimeType
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
} else {
|
|
439
|
-
const defaultMimeType = {
|
|
440
|
-
image: "image/jpeg",
|
|
441
|
-
audio: "audio/mp3",
|
|
442
|
-
video: "video/mp4",
|
|
443
|
-
document: "application/pdf"
|
|
444
|
-
}[part.type];
|
|
445
|
-
return {
|
|
446
|
-
fileData: {
|
|
447
|
-
fileUri: part.source.value,
|
|
448
|
-
mimeType: part.source.mimeType ?? defaultMimeType
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
default: {
|
|
454
|
-
const _exhaustiveCheck = part;
|
|
455
|
-
throw new Error(
|
|
456
|
-
`Unsupported content part type: ${_exhaustiveCheck.type}`
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
formatMessages(messages) {
|
|
462
|
-
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
463
|
-
for (const msg of messages) {
|
|
464
|
-
if (msg.role === "assistant" && msg.toolCalls) {
|
|
465
|
-
for (const tc of msg.toolCalls) {
|
|
466
|
-
toolCallIdToName.set(tc.id, tc.function.name);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
const formatted = messages.map((msg) => {
|
|
471
|
-
const role = msg.role === "assistant" ? "model" : "user";
|
|
472
|
-
const parts = [];
|
|
473
|
-
if (Array.isArray(msg.content)) {
|
|
474
|
-
for (const contentPart of msg.content) {
|
|
475
|
-
parts.push(this.convertContentPartToGemini(contentPart));
|
|
476
|
-
}
|
|
477
|
-
} else if (msg.content && msg.role !== "tool") {
|
|
478
|
-
parts.push({ text: msg.content });
|
|
479
|
-
}
|
|
480
|
-
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
481
|
-
for (const toolCall of msg.toolCalls) {
|
|
482
|
-
let parsedArgs = {};
|
|
483
|
-
try {
|
|
484
|
-
parsedArgs = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
|
|
485
|
-
} catch {
|
|
486
|
-
parsedArgs = {};
|
|
487
|
-
}
|
|
488
|
-
const thoughtSignature = toolCall.metadata?.thoughtSignature;
|
|
489
|
-
const part = {
|
|
490
|
-
functionCall: {
|
|
491
|
-
id: toolCall.id,
|
|
492
|
-
name: toolCall.function.name,
|
|
493
|
-
args: parsedArgs
|
|
494
|
-
}
|
|
495
|
-
};
|
|
496
|
-
if (thoughtSignature) {
|
|
497
|
-
part.thoughtSignature = thoughtSignature;
|
|
498
|
-
}
|
|
499
|
-
parts.push(part);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (msg.role === "tool" && msg.toolCallId) {
|
|
503
|
-
const functionName = toolCallIdToName.get(msg.toolCallId) || msg.toolCallId;
|
|
504
|
-
const toolContent = msg.content;
|
|
505
|
-
if (Array.isArray(toolContent)) {
|
|
506
|
-
const textChunks = [];
|
|
507
|
-
const mediaParts = [];
|
|
508
|
-
for (const part of toolContent) {
|
|
509
|
-
if (part.type === "text") {
|
|
510
|
-
textChunks.push(part.content);
|
|
511
|
-
} else if (part.source.type === "data") {
|
|
512
|
-
mediaParts.push({
|
|
513
|
-
inlineData: {
|
|
514
|
-
data: part.source.value,
|
|
515
|
-
mimeType: part.source.mimeType
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
} else {
|
|
519
|
-
const defaultMimeType = {
|
|
520
|
-
image: "image/jpeg",
|
|
521
|
-
audio: "audio/mp3",
|
|
522
|
-
video: "video/mp4",
|
|
523
|
-
document: "application/pdf"
|
|
524
|
-
}[part.type];
|
|
525
|
-
mediaParts.push({
|
|
526
|
-
fileData: {
|
|
527
|
-
fileUri: part.source.value,
|
|
528
|
-
mimeType: part.source.mimeType ?? defaultMimeType
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
parts.push({
|
|
534
|
-
functionResponse: {
|
|
535
|
-
id: msg.toolCallId,
|
|
536
|
-
name: functionName,
|
|
537
|
-
response: { content: textChunks.join("\n") },
|
|
538
|
-
...mediaParts.length > 0 && { parts: mediaParts }
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
} else {
|
|
542
|
-
parts.push({
|
|
543
|
-
functionResponse: {
|
|
544
|
-
id: msg.toolCallId,
|
|
545
|
-
name: functionName,
|
|
546
|
-
response: { content: toolContent || "" }
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return {
|
|
552
|
-
role,
|
|
553
|
-
parts: parts.length > 0 ? parts : [{ text: "" }]
|
|
554
|
-
};
|
|
555
|
-
});
|
|
556
|
-
return this.mergeConsecutiveSameRoleMessages(formatted);
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Merge consecutive messages of the same role into a single message.
|
|
560
|
-
* Gemini's API requires strictly alternating user/model roles.
|
|
561
|
-
* Tool results are mapped to role:'user', which can collide with actual
|
|
562
|
-
* user messages in multi-turn conversations.
|
|
563
|
-
*
|
|
564
|
-
* Also filters out empty model messages (e.g., from a previous failed request)
|
|
565
|
-
* and deduplicates functionResponse parts with the same name (tool call ID).
|
|
566
|
-
*/
|
|
567
|
-
mergeConsecutiveSameRoleMessages(messages) {
|
|
568
|
-
const merged = [];
|
|
569
|
-
for (const msg of messages) {
|
|
570
|
-
const parts = msg.parts || [];
|
|
571
|
-
if (msg.role === "model") {
|
|
572
|
-
const hasContent = parts.length > 0 && !parts.every(
|
|
573
|
-
(p) => "text" in p && p.text === ""
|
|
574
|
-
);
|
|
575
|
-
if (!hasContent) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const prev = merged[merged.length - 1];
|
|
580
|
-
if (prev && prev.role === msg.role) {
|
|
581
|
-
prev.parts = [...prev.parts || [], ...parts];
|
|
582
|
-
} else {
|
|
583
|
-
merged.push({ ...msg, parts: [...parts] });
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
for (const msg of merged) {
|
|
587
|
-
if (!msg.parts) continue;
|
|
588
|
-
const seenFunctionResponseNames = /* @__PURE__ */ new Set();
|
|
589
|
-
msg.parts = msg.parts.filter((part) => {
|
|
590
|
-
if ("functionResponse" in part && part.functionResponse?.name) {
|
|
591
|
-
if (seenFunctionResponseNames.has(part.functionResponse.name)) {
|
|
592
|
-
return false;
|
|
593
|
-
}
|
|
594
|
-
seenFunctionResponseNames.add(part.functionResponse.name);
|
|
595
|
-
}
|
|
596
|
-
return true;
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
return merged;
|
|
600
|
-
}
|
|
601
|
-
mapCommonOptionsToGemini(options) {
|
|
602
|
-
const { thinkingConfig, ...modelOpts } = options.modelOptions ?? {};
|
|
603
|
-
const mappedThinkingConfig = thinkingConfig ? {
|
|
604
|
-
...thinkingConfig.includeThoughts !== void 0 && {
|
|
605
|
-
includeThoughts: thinkingConfig.includeThoughts
|
|
606
|
-
},
|
|
607
|
-
...thinkingConfig.thinkingBudget !== void 0 && {
|
|
608
|
-
thinkingBudget: thinkingConfig.thinkingBudget
|
|
609
|
-
},
|
|
610
|
-
...thinkingConfig.thinkingLevel ? {
|
|
611
|
-
thinkingLevel: thinkingConfig.thinkingLevel
|
|
612
|
-
} : {}
|
|
613
|
-
} : void 0;
|
|
614
|
-
const normalizedPrompts = normalizeSystemPrompts(options.systemPrompts);
|
|
615
|
-
const systemInstruction = normalizedPrompts.length > 0 ? normalizedPrompts.map((p) => p.content).join("\n") : void 0;
|
|
616
|
-
const combinedSchema = options.outputSchema;
|
|
617
|
-
const combinedSchemaConfig = combinedSchema ? {
|
|
618
|
-
responseMimeType: "application/json",
|
|
619
|
-
responseSchema: combinedSchema
|
|
620
|
-
} : void 0;
|
|
621
|
-
const requestOptions = {
|
|
622
|
-
model: options.model,
|
|
623
|
-
contents: this.formatMessages(options.messages),
|
|
624
|
-
config: {
|
|
625
|
-
...modelOpts,
|
|
626
|
-
...mappedThinkingConfig !== void 0 && {
|
|
627
|
-
thinkingConfig: mappedThinkingConfig
|
|
628
|
-
},
|
|
629
|
-
...systemInstruction !== void 0 && { systemInstruction },
|
|
630
|
-
tools: convertToolsToProviderFormat(options.tools),
|
|
631
|
-
...combinedSchemaConfig ?? {}
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
return requestOptions;
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Gemini 3.x natively combines `tools` + `responseSchema` in a single
|
|
638
|
-
* streaming `generateContentStream` call (issue #605). Gemini 2.x is
|
|
639
|
-
* documented as brittle for the combination and keeps the engine's
|
|
640
|
-
* legacy finalization path.
|
|
641
|
-
*/
|
|
642
|
-
supportsCombinedToolsAndSchema() {
|
|
643
|
-
return GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function createGeminiChat(model, apiKey, config) {
|
|
647
|
-
return new GeminiTextAdapter({ apiKey, ...config }, model);
|
|
648
|
-
}
|
|
649
|
-
function geminiText(model, config) {
|
|
650
|
-
const apiKey = getGeminiApiKeyFromEnv();
|
|
651
|
-
return createGeminiChat(model, apiKey, config);
|
|
652
|
-
}
|
|
653
|
-
export {
|
|
654
|
-
GeminiTextAdapter,
|
|
655
|
-
createGeminiChat,
|
|
656
|
-
geminiText
|
|
657
|
-
};
|
|
658
|
-
//# sourceMappingURL=text.js.map
|