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