@tanstack/ai-gemini 0.17.3 → 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.
Files changed (81) hide show
  1. package/package.json +5 -4
  2. package/src/experimental/text-interactions/adapter.ts +304 -100
  3. package/src/experimental/text-interactions/events.ts +29 -23
  4. package/src/experimental/text-interactions/provider-options.ts +0 -1
  5. package/dist/esm/adapters/audio.d.ts +0 -92
  6. package/dist/esm/adapters/audio.js +0 -66
  7. package/dist/esm/adapters/audio.js.map +0 -1
  8. package/dist/esm/adapters/image.d.ts +0 -101
  9. package/dist/esm/adapters/image.js +0 -253
  10. package/dist/esm/adapters/image.js.map +0 -1
  11. package/dist/esm/adapters/summarize.d.ts +0 -33
  12. package/dist/esm/adapters/summarize.js +0 -18
  13. package/dist/esm/adapters/summarize.js.map +0 -1
  14. package/dist/esm/adapters/text.d.ts +0 -85
  15. package/dist/esm/adapters/text.js +0 -658
  16. package/dist/esm/adapters/text.js.map +0 -1
  17. package/dist/esm/adapters/tts.d.ts +0 -165
  18. package/dist/esm/adapters/tts.js +0 -192
  19. package/dist/esm/adapters/tts.js.map +0 -1
  20. package/dist/esm/adapters/video.d.ts +0 -108
  21. package/dist/esm/adapters/video.js +0 -227
  22. package/dist/esm/adapters/video.js.map +0 -1
  23. package/dist/esm/experimental/index.d.ts +0 -6
  24. package/dist/esm/experimental/index.js +0 -7
  25. package/dist/esm/experimental/index.js.map +0 -1
  26. package/dist/esm/experimental/text-interactions/adapter.d.ts +0 -75
  27. package/dist/esm/experimental/text-interactions/adapter.js +0 -899
  28. package/dist/esm/experimental/text-interactions/adapter.js.map +0 -1
  29. package/dist/esm/experimental/text-interactions/events.d.ts +0 -141
  30. package/dist/esm/experimental/text-interactions/provider-options.d.ts +0 -15
  31. package/dist/esm/image/image-provider-options.d.ts +0 -181
  32. package/dist/esm/image/image-provider-options.js +0 -67
  33. package/dist/esm/image/image-provider-options.js.map +0 -1
  34. package/dist/esm/index.d.ts +0 -33
  35. package/dist/esm/index.js +0 -38
  36. package/dist/esm/index.js.map +0 -1
  37. package/dist/esm/message-types.d.ts +0 -104
  38. package/dist/esm/model-meta.d.ts +0 -242
  39. package/dist/esm/model-meta.js +0 -159
  40. package/dist/esm/model-meta.js.map +0 -1
  41. package/dist/esm/text/text-provider-options.d.ts +0 -196
  42. package/dist/esm/tools/code-execution-tool.d.ts +0 -10
  43. package/dist/esm/tools/code-execution-tool.js +0 -18
  44. package/dist/esm/tools/code-execution-tool.js.map +0 -1
  45. package/dist/esm/tools/computer-use-tool.d.ts +0 -13
  46. package/dist/esm/tools/computer-use-tool.js +0 -33
  47. package/dist/esm/tools/computer-use-tool.js.map +0 -1
  48. package/dist/esm/tools/file-search-tool.d.ts +0 -10
  49. package/dist/esm/tools/file-search-tool.js +0 -19
  50. package/dist/esm/tools/file-search-tool.js.map +0 -1
  51. package/dist/esm/tools/function-declaration-tool.d.ts +0 -5
  52. package/dist/esm/tools/function-declaration-tool.js +0 -28
  53. package/dist/esm/tools/function-declaration-tool.js.map +0 -1
  54. package/dist/esm/tools/google-maps-tool.d.ts +0 -10
  55. package/dist/esm/tools/google-maps-tool.js +0 -19
  56. package/dist/esm/tools/google-maps-tool.js.map +0 -1
  57. package/dist/esm/tools/google-search-retriveal-tool.d.ts +0 -10
  58. package/dist/esm/tools/google-search-retriveal-tool.js +0 -19
  59. package/dist/esm/tools/google-search-retriveal-tool.js.map +0 -1
  60. package/dist/esm/tools/google-search-tool.d.ts +0 -10
  61. package/dist/esm/tools/google-search-tool.js +0 -19
  62. package/dist/esm/tools/google-search-tool.js.map +0 -1
  63. package/dist/esm/tools/index.d.ts +0 -18
  64. package/dist/esm/tools/index.js +0 -21
  65. package/dist/esm/tools/index.js.map +0 -1
  66. package/dist/esm/tools/tool-converter.d.ts +0 -22
  67. package/dist/esm/tools/tool-converter.js +0 -66
  68. package/dist/esm/tools/tool-converter.js.map +0 -1
  69. package/dist/esm/tools/url-context-tool.d.ts +0 -10
  70. package/dist/esm/tools/url-context-tool.js +0 -18
  71. package/dist/esm/tools/url-context-tool.js.map +0 -1
  72. package/dist/esm/usage.d.ts +0 -67
  73. package/dist/esm/usage.js +0 -94
  74. package/dist/esm/usage.js.map +0 -1
  75. package/dist/esm/utils/client.d.ts +0 -17
  76. package/dist/esm/utils/client.js +0 -30
  77. package/dist/esm/utils/client.js.map +0 -1
  78. package/dist/esm/utils/index.d.ts +0 -1
  79. package/dist/esm/video/video-provider-options.d.ts +0 -90
  80. package/dist/esm/video/video-provider-options.js +0 -15
  81. package/dist/esm/video/video-provider-options.js.map +0 -1
@@ -1,899 +0,0 @@
1
- import { EventType } from "@tanstack/ai";
2
- import { BaseTextAdapter } from "@tanstack/ai/adapters";
3
- import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../../utils/client.js";
4
- class GeminiTextInteractionsAdapter extends BaseTextAdapter {
5
- kind = "text";
6
- name = "gemini-text-interactions";
7
- client;
8
- // Tracks the most recent server-assigned interaction id per threadId
9
- // so the adapter can chain follow-up calls on the same thread without
10
- // the caller having to thread the id manually. Two callers rely on
11
- // this:
12
- // 1. The agent loop's tool-call iterations (each iteration is a new
13
- // `chatStream` call with accumulated tool messages).
14
- // 2. The agentic-structured composition: a `chatStream` run followed
15
- // by `structuredOutput` on the accumulated messages.
16
- // Cross-request chaining is the caller's job via
17
- // `modelOptions.previous_interaction_id`. To keep stale ids from
18
- // chaining a brand-new turn, `chatStream` evicts at the START when
19
- // the caller signals fresh-turn intent (no caller-provided id AND a
20
- // single user message — anything else is a follow-up). Errors evict
21
- // immediately so a failed turn never chains into the next one.
22
- interactionIdByThread = /* @__PURE__ */ new Map();
23
- constructor(config, model) {
24
- super({}, model);
25
- this.client = createGeminiClient(config);
26
- }
27
- async *chatStream(options) {
28
- const runId = options.runId ?? generateId(this.name);
29
- const threadId = options.threadId ?? generateId(this.name);
30
- const timestamp = Date.now();
31
- const { logger } = options;
32
- if (!options.modelOptions?.previous_interaction_id && options.messages.length === 1 && options.messages[0]?.role === "user") {
33
- this.interactionIdByThread.delete(threadId);
34
- }
35
- const effectivePreviousInteractionId = options.modelOptions?.previous_interaction_id ?? this.interactionIdByThread.get(threadId);
36
- let sawTerminalEvent = false;
37
- let completedTryBlock = false;
38
- try {
39
- const request = buildInteractionsRequest({
40
- ...options,
41
- modelOptions: {
42
- ...options.modelOptions,
43
- previous_interaction_id: effectivePreviousInteractionId
44
- }
45
- });
46
- logger.request(
47
- `activity=chat provider=gemini-text-interactions model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
48
- {
49
- provider: "gemini-text-interactions",
50
- model: this.model,
51
- request
52
- }
53
- );
54
- const stream = await this.client.interactions.create(
55
- { ...request, stream: true },
56
- { signal: options.abortController?.signal }
57
- );
58
- for await (const chunk of translateInteractionEvents(
59
- stream,
60
- options.model,
61
- runId,
62
- threadId,
63
- options.parentRunId,
64
- timestamp,
65
- this.name,
66
- logger
67
- )) {
68
- if (chunk.type === EventType.CUSTOM && chunk.name === "gemini.interactionId") {
69
- const value = chunk.value;
70
- this.interactionIdByThread.set(threadId, value.interactionId);
71
- }
72
- if (chunk.type === EventType.RUN_FINISHED) {
73
- sawTerminalEvent = true;
74
- } else if (chunk.type === EventType.RUN_ERROR) {
75
- sawTerminalEvent = true;
76
- this.interactionIdByThread.delete(threadId);
77
- }
78
- yield chunk;
79
- }
80
- if (!sawTerminalEvent) {
81
- this.interactionIdByThread.delete(threadId);
82
- const message = "Gemini Interactions stream ended without a terminal event (no interaction.complete or error)";
83
- logger.errors("gemini-text-interactions.chatStream truncated", {
84
- source: "gemini-text-interactions.chatStream",
85
- runId,
86
- threadId
87
- });
88
- yield {
89
- type: EventType.RUN_ERROR,
90
- runId,
91
- model: options.model,
92
- timestamp,
93
- message,
94
- error: { message }
95
- };
96
- }
97
- completedTryBlock = true;
98
- } catch (error) {
99
- this.interactionIdByThread.delete(threadId);
100
- const message = error instanceof Error ? error.message : "An unknown error occurred during the interactions stream.";
101
- logger.errors("gemini-text-interactions.chatStream fatal", {
102
- error,
103
- source: "gemini-text-interactions.chatStream"
104
- });
105
- yield {
106
- type: EventType.RUN_ERROR,
107
- runId,
108
- model: options.model,
109
- timestamp,
110
- message,
111
- error: { message }
112
- };
113
- } finally {
114
- if (!completedTryBlock) {
115
- this.interactionIdByThread.delete(threadId);
116
- }
117
- }
118
- }
119
- async structuredOutput(options) {
120
- const { chatOptions, outputSchema } = options;
121
- const { logger } = chatOptions;
122
- const threadId = chatOptions.threadId;
123
- const effectivePreviousInteractionId = chatOptions.modelOptions?.previous_interaction_id ?? (threadId ? this.interactionIdByThread.get(threadId) : void 0);
124
- const baseRequest = buildInteractionsRequest({
125
- ...chatOptions,
126
- modelOptions: {
127
- ...chatOptions.modelOptions,
128
- previous_interaction_id: effectivePreviousInteractionId
129
- }
130
- });
131
- const request = {
132
- ...baseRequest,
133
- response_mime_type: "application/json",
134
- response_format: outputSchema
135
- };
136
- try {
137
- logger.request(
138
- `activity=chat provider=gemini-text-interactions model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,
139
- {
140
- provider: "gemini-text-interactions",
141
- model: this.model,
142
- request
143
- }
144
- );
145
- const result = await this.client.interactions.create(
146
- request,
147
- { signal: chatOptions.abortController?.signal }
148
- );
149
- const rawText = extractTextFromInteraction(result);
150
- if (!rawText) {
151
- throw new Error(
152
- `Gemini Interactions returned no text output for structured-output request (status: ${result.status}). The model may have produced only tool calls or non-text content.`
153
- );
154
- }
155
- let parsed;
156
- try {
157
- parsed = JSON.parse(rawText);
158
- } catch {
159
- throw new Error(
160
- `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
161
- );
162
- }
163
- return { data: parsed, rawText };
164
- } catch (error) {
165
- logger.errors("gemini-text-interactions.structuredOutput fatal", {
166
- error,
167
- source: "gemini-text-interactions.structuredOutput"
168
- });
169
- throw new Error(
170
- error instanceof Error ? error.message : "An unknown error occurred during structured output generation.",
171
- { cause: error }
172
- );
173
- }
174
- }
175
- }
176
- function createGeminiTextInteractions(model, apiKey, config) {
177
- return new GeminiTextInteractionsAdapter({ apiKey, ...config }, model);
178
- }
179
- function geminiTextInteractions(model, config) {
180
- const apiKey = getGeminiApiKeyFromEnv();
181
- return createGeminiTextInteractions(model, apiKey, config);
182
- }
183
- function buildInteractionsRequest(options) {
184
- const modelOpts = options.modelOptions;
185
- const systemInstruction = modelOpts?.system_instruction ?? options.systemPrompts?.join("\n");
186
- const generationConfig = {
187
- ...modelOpts?.generation_config
188
- };
189
- const hasGenerationConfig = Object.keys(generationConfig).length > 0;
190
- const input = convertMessagesToInteractionsInput(
191
- options.messages,
192
- modelOpts?.previous_interaction_id !== void 0
193
- );
194
- return {
195
- model: options.model,
196
- input,
197
- previous_interaction_id: modelOpts?.previous_interaction_id,
198
- system_instruction: systemInstruction,
199
- tools: convertToolsToInteractionsFormat(options.tools),
200
- generation_config: hasGenerationConfig ? generationConfig : void 0,
201
- store: modelOpts?.store,
202
- background: modelOpts?.background,
203
- response_modalities: modelOpts?.response_modalities,
204
- response_format: modelOpts?.response_format,
205
- response_mime_type: modelOpts?.response_mime_type
206
- };
207
- }
208
- function convertMessagesToInteractionsInput(messages, hasPreviousInteraction) {
209
- const toolCallIdToName = /* @__PURE__ */ new Map();
210
- for (const msg of messages) {
211
- if (msg.role === "assistant" && msg.toolCalls) {
212
- for (const tc of msg.toolCalls) {
213
- toolCallIdToName.set(tc.id, tc.function.name);
214
- }
215
- }
216
- }
217
- const source = hasPreviousInteraction ? messagesAfterLastAssistant(messages) : messages;
218
- if (hasPreviousInteraction && source.length === 0) {
219
- throw new Error(
220
- "Gemini Interactions adapter: modelOptions.previous_interaction_id was provided but no new messages were found after the last assistant turn. Append at least one user or tool message before chaining."
221
- );
222
- }
223
- if (!hasPreviousInteraction) {
224
- const [only, ...rest] = source;
225
- if (!only) {
226
- throw new Error("Gemini Interactions adapter: no messages to send.");
227
- }
228
- if (rest.length > 0) {
229
- throw new Error(
230
- 'Gemini Interactions adapter: cannot send prior conversation history on a fresh interaction. Either set modelOptions.previous_interaction_id to chain prior turns server-side, or trim the message list to a single new user turn. See docs/adapters/gemini.md ("Wiring with useChat") for the canonical client/server pattern.'
231
- );
232
- }
233
- if (only.role !== "user") {
234
- throw new Error(
235
- `Gemini Interactions adapter: the first message of a fresh interaction must be a user turn (got role="${only.role}"). Set modelOptions.previous_interaction_id to continue an existing interaction.`
236
- );
237
- }
238
- const content = messageToContentBlocks(only);
239
- if (content.length === 0) {
240
- throw new Error(
241
- "Gemini Interactions adapter: the user message produced no content blocks to send."
242
- );
243
- }
244
- return [{ type: "user_input", content }];
245
- }
246
- const steps = [];
247
- for (const msg of source) {
248
- if (msg.role === "tool" && msg.toolCallId) {
249
- const result = serializeToolResultContent(msg.content);
250
- steps.push({
251
- type: "function_result",
252
- call_id: msg.toolCallId,
253
- name: toolCallIdToName.get(msg.toolCallId),
254
- result
255
- });
256
- } else if (msg.role === "user") {
257
- const content = messageToContentBlocks(msg);
258
- if (content.length > 0) {
259
- steps.push({ type: "user_input", content });
260
- }
261
- }
262
- }
263
- if (steps.length === 0) {
264
- throw new Error(
265
- "Gemini Interactions adapter: messages after the last assistant turn produced no steps to send."
266
- );
267
- }
268
- return steps;
269
- }
270
- function serializeToolResultContent(content) {
271
- if (typeof content === "string") return content;
272
- if (content === null || content === void 0) {
273
- throw new Error(
274
- "Gemini Interactions adapter: tool message has no content. The Interactions API requires a string `result` on function_result steps — return a string from your tool implementation (encode JSON/multimodal output yourself)."
275
- );
276
- }
277
- throw new Error(
278
- "Gemini Interactions adapter: tool message content must be a string (got an array of content parts). The Interactions API requires a string `result` on function_result steps — stringify multimodal tool output before returning it from your tool."
279
- );
280
- }
281
- function messageToContentBlocks(msg) {
282
- const blocks = [];
283
- if (Array.isArray(msg.content)) {
284
- for (const part of msg.content) {
285
- blocks.push(contentPartToBlock(part));
286
- }
287
- } else if (typeof msg.content === "string" && msg.content && msg.role !== "tool") {
288
- blocks.push({ type: "text", text: msg.content });
289
- }
290
- return blocks;
291
- }
292
- function messagesAfterLastAssistant(messages) {
293
- for (let i = messages.length - 1; i >= 0; i--) {
294
- if (messages[i]?.role === "assistant") {
295
- return messages.slice(i + 1);
296
- }
297
- }
298
- return messages;
299
- }
300
- function safeParseToolArguments(raw, logger) {
301
- if (!raw) return {};
302
- try {
303
- const parsed = JSON.parse(raw);
304
- return parsed && typeof parsed === "object" ? parsed : {};
305
- } catch (error) {
306
- logger.errors(
307
- "gemini-text-interactions.safeParseToolArguments parse failed",
308
- {
309
- error,
310
- raw,
311
- source: "gemini-text-interactions.chatStream"
312
- }
313
- );
314
- return {};
315
- }
316
- }
317
- const IMAGE_MIME_TYPES = [
318
- "image/png",
319
- "image/jpeg",
320
- "image/webp",
321
- "image/heic",
322
- "image/heif"
323
- ];
324
- const AUDIO_MIME_TYPES = [
325
- "audio/wav",
326
- "audio/mp3",
327
- "audio/aiff",
328
- "audio/aac",
329
- "audio/ogg",
330
- "audio/flac"
331
- ];
332
- const VIDEO_MIME_TYPES = [
333
- "video/mp4",
334
- "video/mpeg",
335
- "video/mpg",
336
- "video/mov",
337
- "video/avi",
338
- "video/x-flv",
339
- "video/webm",
340
- "video/wmv",
341
- "video/3gpp"
342
- ];
343
- const DOCUMENT_MIME_TYPES = [
344
- "application/pdf"
345
- ];
346
- function validateMime(allowed, value, kind) {
347
- if (value === void 0) return void 0;
348
- if (allowed.includes(value)) {
349
- return value;
350
- }
351
- throw new Error(
352
- `Unsupported ${kind} mime type "${value}" for the Gemini Interactions API. Allowed: ${allowed.join(", ")}.`
353
- );
354
- }
355
- function contentPartToBlock(part) {
356
- if (part.type === "text") {
357
- return { type: "text", text: part.content };
358
- }
359
- const isData = part.source.type === "data";
360
- switch (part.type) {
361
- case "image": {
362
- const mime_type = validateMime(
363
- IMAGE_MIME_TYPES,
364
- part.source.mimeType,
365
- "image"
366
- );
367
- return isData ? { type: "image", data: part.source.value, mime_type } : { type: "image", uri: part.source.value, mime_type };
368
- }
369
- case "audio": {
370
- const mime_type = validateMime(
371
- AUDIO_MIME_TYPES,
372
- part.source.mimeType,
373
- "audio"
374
- );
375
- return isData ? { type: "audio", data: part.source.value, mime_type } : { type: "audio", uri: part.source.value, mime_type };
376
- }
377
- case "video": {
378
- const mime_type = validateMime(
379
- VIDEO_MIME_TYPES,
380
- part.source.mimeType,
381
- "video"
382
- );
383
- return isData ? { type: "video", data: part.source.value, mime_type } : { type: "video", uri: part.source.value, mime_type };
384
- }
385
- case "document": {
386
- const mime_type = validateMime(
387
- DOCUMENT_MIME_TYPES,
388
- part.source.mimeType,
389
- "document"
390
- );
391
- return isData ? { type: "document", data: part.source.value, mime_type } : { type: "document", uri: part.source.value, mime_type };
392
- }
393
- }
394
- }
395
- function convertToolsToInteractionsFormat(tools) {
396
- if (!tools || tools.length === 0) return void 0;
397
- const result = [];
398
- for (const tool of tools) {
399
- switch (tool.name) {
400
- case "google_search": {
401
- const metadata = tool.metadata ?? {};
402
- result.push({
403
- type: "google_search",
404
- ...metadata.search_types ? { search_types: metadata.search_types } : {}
405
- });
406
- break;
407
- }
408
- case "code_execution": {
409
- result.push({ type: "code_execution" });
410
- break;
411
- }
412
- case "url_context": {
413
- result.push({ type: "url_context" });
414
- break;
415
- }
416
- case "file_search": {
417
- const metadata = tool.metadata ?? {};
418
- result.push({
419
- type: "file_search",
420
- ...metadata.fileSearchStoreNames ? { file_search_store_names: metadata.fileSearchStoreNames } : {},
421
- ...metadata.topK !== void 0 ? { top_k: metadata.topK } : {},
422
- ...metadata.metadataFilter !== void 0 ? { metadata_filter: metadata.metadataFilter } : {}
423
- });
424
- break;
425
- }
426
- case "computer_use": {
427
- const metadata = tool.metadata ?? {};
428
- if (metadata.environment && metadata.environment !== "browser") {
429
- throw new Error(
430
- `computer_use environment "${metadata.environment}" is not supported on the Gemini Interactions API. Only "browser" is accepted.`
431
- );
432
- }
433
- result.push({
434
- type: "computer_use",
435
- ...metadata.environment ? { environment: metadata.environment } : {},
436
- ...metadata.excludedPredefinedFunctions ? {
437
- excludedPredefinedFunctions: metadata.excludedPredefinedFunctions
438
- } : {}
439
- });
440
- break;
441
- }
442
- case "google_search_retrieval":
443
- throw new Error(
444
- "`google_search_retrieval` is not supported on the Gemini Interactions API. Use `googleSearchTool()` (`google_search`) with `geminiTextInteractions()`, or call `geminiText()` for the legacy retrieval tool."
445
- );
446
- case "google_maps":
447
- throw new Error(
448
- "`google_maps` is not yet supported on the Gemini Interactions API. Use `geminiText()` for Google Maps grounding."
449
- );
450
- case "mcp_server":
451
- throw new Error(
452
- "`mcp_server` is not yet supported on the `geminiTextInteractions()` adapter."
453
- );
454
- default: {
455
- if (!tool.description) {
456
- throw new Error(
457
- `Tool ${tool.name} requires a description for the Gemini Interactions adapter`
458
- );
459
- }
460
- result.push({
461
- type: "function",
462
- name: tool.name,
463
- description: tool.description,
464
- parameters: sanitizeToolParameters(
465
- tool.inputSchema ?? { type: "object", properties: {} }
466
- )
467
- });
468
- }
469
- }
470
- }
471
- return result;
472
- }
473
- function statusToFinishReason(status, sawFunctionCall) {
474
- if (status === "requires_action") return "tool_calls";
475
- if (status === "incomplete") return "length";
476
- if (sawFunctionCall) return "tool_calls";
477
- return "stop";
478
- }
479
- function statusIsError(status) {
480
- return status === "failed" || status === "cancelled";
481
- }
482
- async function* translateInteractionEvents(stream, model, runId, threadId, parentRunId, timestamp, adapterName, logger) {
483
- const messageId = generateId(adapterName);
484
- let hasEmittedRunStarted = false;
485
- let hasEmittedTextMessageStart = false;
486
- let textAccumulated = "";
487
- let interactionId;
488
- let sawFunctionCall = false;
489
- const toolCalls = /* @__PURE__ */ new Map();
490
- let nextToolIndex = 0;
491
- let thinkingStepId = null;
492
- let thinkingAccumulated = "";
493
- let reasoningMessageId = null;
494
- let hasClosedReasoning = false;
495
- const closeReasoningIfNeeded = function* () {
496
- if (reasoningMessageId && !hasClosedReasoning) {
497
- hasClosedReasoning = true;
498
- yield {
499
- type: EventType.REASONING_MESSAGE_END,
500
- messageId: reasoningMessageId,
501
- model,
502
- timestamp
503
- };
504
- yield {
505
- type: EventType.REASONING_END,
506
- messageId: reasoningMessageId,
507
- model,
508
- timestamp
509
- };
510
- thinkingStepId = null;
511
- reasoningMessageId = null;
512
- hasClosedReasoning = false;
513
- }
514
- };
515
- const closeOpenState = function* () {
516
- yield* closeReasoningIfNeeded();
517
- for (const [toolCallId, state] of toolCalls) {
518
- if (state.ended) continue;
519
- state.ended = true;
520
- yield {
521
- type: EventType.TOOL_CALL_END,
522
- toolCallId,
523
- toolName: state.name,
524
- model,
525
- timestamp,
526
- input: state.args
527
- };
528
- }
529
- if (hasEmittedTextMessageStart) {
530
- hasEmittedTextMessageStart = false;
531
- yield {
532
- type: EventType.TEXT_MESSAGE_END,
533
- messageId,
534
- model,
535
- timestamp
536
- };
537
- }
538
- };
539
- const emitRunStartedIfNeeded = function* () {
540
- if (!hasEmittedRunStarted) {
541
- hasEmittedRunStarted = true;
542
- yield {
543
- type: EventType.RUN_STARTED,
544
- runId,
545
- threadId,
546
- model,
547
- timestamp,
548
- parentRunId
549
- };
550
- }
551
- };
552
- for await (const event of stream) {
553
- logger.provider(`provider=gemini-text-interactions`, { event });
554
- switch (event.event_type) {
555
- case "interaction.start": {
556
- interactionId = event.interaction.id;
557
- yield* emitRunStartedIfNeeded();
558
- break;
559
- }
560
- case "content.start": {
561
- yield* emitRunStartedIfNeeded();
562
- break;
563
- }
564
- case "content.delta": {
565
- yield* emitRunStartedIfNeeded();
566
- const delta = event.delta;
567
- switch (delta.type) {
568
- case "text": {
569
- yield* closeReasoningIfNeeded();
570
- if (!hasEmittedTextMessageStart) {
571
- hasEmittedTextMessageStart = true;
572
- yield {
573
- type: EventType.TEXT_MESSAGE_START,
574
- messageId,
575
- model,
576
- timestamp,
577
- role: "assistant"
578
- };
579
- }
580
- textAccumulated += delta.text;
581
- yield {
582
- type: EventType.TEXT_MESSAGE_CONTENT,
583
- messageId,
584
- model,
585
- timestamp,
586
- delta: delta.text,
587
- content: textAccumulated
588
- };
589
- break;
590
- }
591
- case "function_call": {
592
- yield* closeReasoningIfNeeded();
593
- sawFunctionCall = true;
594
- const toolCallId = delta.id;
595
- const deltaArgs = typeof delta.arguments === "string" ? safeParseToolArguments(delta.arguments, logger) : delta.arguments;
596
- let state = toolCalls.get(toolCallId);
597
- if (!state) {
598
- state = {
599
- name: delta.name,
600
- args: { ...deltaArgs },
601
- index: nextToolIndex++,
602
- started: false,
603
- ended: false
604
- };
605
- toolCalls.set(toolCallId, state);
606
- } else {
607
- state.args = { ...state.args, ...deltaArgs };
608
- if (delta.name) state.name = delta.name;
609
- }
610
- if (!state.started) {
611
- state.started = true;
612
- yield {
613
- type: EventType.TOOL_CALL_START,
614
- toolCallId,
615
- toolCallName: state.name,
616
- toolName: state.name,
617
- parentMessageId: messageId,
618
- model,
619
- timestamp,
620
- index: state.index
621
- };
622
- }
623
- yield {
624
- type: EventType.TOOL_CALL_ARGS,
625
- toolCallId,
626
- model,
627
- timestamp,
628
- delta: JSON.stringify(deltaArgs),
629
- args: JSON.stringify(state.args)
630
- };
631
- break;
632
- }
633
- case "google_search_call": {
634
- yield* closeReasoningIfNeeded();
635
- yield {
636
- type: EventType.CUSTOM,
637
- name: "gemini.googleSearchCall",
638
- value: delta,
639
- model,
640
- timestamp
641
- };
642
- break;
643
- }
644
- case "google_search_result": {
645
- yield* closeReasoningIfNeeded();
646
- yield {
647
- type: EventType.CUSTOM,
648
- name: "gemini.googleSearchResult",
649
- value: delta,
650
- model,
651
- timestamp
652
- };
653
- break;
654
- }
655
- case "code_execution_call": {
656
- yield* closeReasoningIfNeeded();
657
- yield {
658
- type: EventType.CUSTOM,
659
- name: "gemini.codeExecutionCall",
660
- value: delta,
661
- model,
662
- timestamp
663
- };
664
- break;
665
- }
666
- case "code_execution_result": {
667
- yield* closeReasoningIfNeeded();
668
- yield {
669
- type: EventType.CUSTOM,
670
- name: "gemini.codeExecutionResult",
671
- value: delta,
672
- model,
673
- timestamp
674
- };
675
- break;
676
- }
677
- case "url_context_call": {
678
- yield* closeReasoningIfNeeded();
679
- yield {
680
- type: EventType.CUSTOM,
681
- name: "gemini.urlContextCall",
682
- value: delta,
683
- model,
684
- timestamp
685
- };
686
- break;
687
- }
688
- case "url_context_result": {
689
- yield* closeReasoningIfNeeded();
690
- yield {
691
- type: EventType.CUSTOM,
692
- name: "gemini.urlContextResult",
693
- value: delta,
694
- model,
695
- timestamp
696
- };
697
- break;
698
- }
699
- case "file_search_call": {
700
- yield* closeReasoningIfNeeded();
701
- yield {
702
- type: EventType.CUSTOM,
703
- name: "gemini.fileSearchCall",
704
- value: delta,
705
- model,
706
- timestamp
707
- };
708
- break;
709
- }
710
- case "file_search_result": {
711
- yield* closeReasoningIfNeeded();
712
- yield {
713
- type: EventType.CUSTOM,
714
- name: "gemini.fileSearchResult",
715
- value: delta,
716
- model,
717
- timestamp
718
- };
719
- break;
720
- }
721
- case "thought_summary": {
722
- const thoughtText = delta.content && "text" in delta.content ? delta.content.text : "";
723
- if (!thoughtText) break;
724
- if (thinkingStepId === null || reasoningMessageId === null) {
725
- thinkingStepId = generateId(adapterName);
726
- reasoningMessageId = generateId(adapterName);
727
- yield {
728
- type: EventType.REASONING_START,
729
- messageId: reasoningMessageId,
730
- model,
731
- timestamp
732
- };
733
- yield {
734
- type: EventType.REASONING_MESSAGE_START,
735
- messageId: reasoningMessageId,
736
- role: "reasoning",
737
- model,
738
- timestamp
739
- };
740
- yield {
741
- type: EventType.STEP_STARTED,
742
- stepName: thinkingStepId,
743
- stepId: thinkingStepId,
744
- model,
745
- timestamp,
746
- stepType: "thinking"
747
- };
748
- }
749
- thinkingAccumulated += thoughtText;
750
- yield {
751
- type: EventType.REASONING_MESSAGE_CONTENT,
752
- messageId: reasoningMessageId,
753
- delta: thoughtText,
754
- model,
755
- timestamp
756
- };
757
- yield {
758
- type: EventType.STEP_FINISHED,
759
- stepName: thinkingStepId,
760
- stepId: thinkingStepId,
761
- model,
762
- timestamp,
763
- delta: thoughtText,
764
- content: thinkingAccumulated
765
- };
766
- break;
767
- }
768
- // The following delta types are valid per the SDK type union
769
- // but aren't yet translated by this adapter (output modalities
770
- // text-only adapter shouldn't see, response-side function_result
771
- // / mcp_server_*, thought_signature). Falling through to the
772
- // observability default so SDK drift is visible.
773
- case "image":
774
- case "audio":
775
- case "video":
776
- case "document":
777
- case "function_result":
778
- case "mcp_server_tool_call":
779
- case "mcp_server_tool_result":
780
- case "thought_signature":
781
- default:
782
- logger.provider(
783
- `gemini-text-interactions unhandled content.delta type`,
784
- { delta }
785
- );
786
- break;
787
- }
788
- break;
789
- }
790
- case "content.stop":
791
- case "interaction.status_update": {
792
- break;
793
- }
794
- case "interaction.complete": {
795
- if (event.interaction.id) {
796
- interactionId = event.interaction.id;
797
- }
798
- yield* closeOpenState();
799
- const status = event.interaction.status;
800
- if (statusIsError(status)) {
801
- const message = `Gemini Interactions ${status}: the interaction ended without a usable response.`;
802
- logger.errors(
803
- "gemini-text-interactions.translateInteractionEvents non-success status",
804
- {
805
- source: "gemini-text-interactions.chatStream",
806
- status,
807
- interactionId
808
- }
809
- );
810
- yield {
811
- type: EventType.RUN_ERROR,
812
- runId,
813
- model,
814
- timestamp,
815
- message,
816
- code: status,
817
- error: { message, code: status }
818
- };
819
- return;
820
- }
821
- const usage = event.interaction.usage;
822
- const finishReason = statusToFinishReason(status, sawFunctionCall);
823
- if (interactionId) {
824
- yield {
825
- type: EventType.CUSTOM,
826
- name: "gemini.interactionId",
827
- value: { interactionId },
828
- model,
829
- timestamp
830
- };
831
- }
832
- yield {
833
- type: EventType.RUN_FINISHED,
834
- runId,
835
- threadId,
836
- model,
837
- timestamp,
838
- finishReason,
839
- usage: usage ? {
840
- promptTokens: usage.total_input_tokens ?? 0,
841
- completionTokens: usage.total_output_tokens ?? 0,
842
- totalTokens: usage.total_tokens ?? 0
843
- } : void 0
844
- };
845
- return;
846
- }
847
- case "error": {
848
- yield* closeOpenState();
849
- const rawMessage = event.error?.message;
850
- const message = typeof rawMessage === "string" && rawMessage.length > 0 ? rawMessage : `Gemini Interactions error (no message): ${JSON.stringify(event.error ?? {})}`;
851
- const rawCode = event.error?.code;
852
- const code = typeof rawCode === "string" || typeof rawCode === "number" ? String(rawCode) : void 0;
853
- yield {
854
- type: EventType.RUN_ERROR,
855
- runId,
856
- model,
857
- timestamp,
858
- message,
859
- code,
860
- error: { message, code }
861
- };
862
- return;
863
- }
864
- default:
865
- logger.provider(`gemini-text-interactions unhandled event_type`, {
866
- event
867
- });
868
- break;
869
- }
870
- }
871
- yield* closeOpenState();
872
- }
873
- function extractTextFromInteraction(interaction) {
874
- let text = "";
875
- for (const output of interaction.outputs ?? []) {
876
- if (output.type === "text") {
877
- text += output.text;
878
- }
879
- }
880
- return text;
881
- }
882
- function sanitizeToolParameters(schema) {
883
- if (!schema || typeof schema !== "object") return schema;
884
- if (Array.isArray(schema)) return schema.map(sanitizeToolParameters);
885
- const out = {};
886
- for (const [key, value] of Object.entries(schema)) {
887
- if (key === "required" && Array.isArray(value) && value.length === 0) {
888
- continue;
889
- }
890
- out[key] = sanitizeToolParameters(value);
891
- }
892
- return out;
893
- }
894
- export {
895
- GeminiTextInteractionsAdapter,
896
- createGeminiTextInteractions,
897
- geminiTextInteractions
898
- };
899
- //# sourceMappingURL=adapter.js.map