@tanstack/ai-gemini 0.10.3 → 0.10.4

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.
@@ -1,4 +1,5 @@
1
1
  import { FinishReason } from '@google/genai'
2
+ import { EventType } from '@tanstack/ai'
2
3
  import { BaseTextAdapter } from '@tanstack/ai/adapters'
3
4
  import { convertToolsToProviderFormat } from '../tools/tool-converter'
4
5
  import {
@@ -39,11 +40,6 @@ import type {
39
40
  } from '../message-types'
40
41
  import type { GeminiClientConfig } from '../utils'
41
42
 
42
- /** Cast an event object to StreamChunk. Adapters construct events with string
43
- * literal types which are structurally compatible with the EventType enum. */
44
- const asChunk = (chunk: Record<string, unknown>) =>
45
- chunk as unknown as StreamChunk
46
-
47
43
  /**
48
44
  * Configuration for Gemini text adapter
49
45
  */
@@ -136,15 +132,14 @@ export class GeminiTextAdapter<
136
132
 
137
133
  yield* this.processStreamChunks(result, options, logger)
138
134
  } catch (error) {
139
- const timestamp = Date.now()
140
135
  logger.errors('gemini.chatStream fatal', {
141
136
  error,
142
137
  source: 'gemini.chatStream',
143
138
  })
144
- yield asChunk({
145
- type: 'RUN_ERROR',
139
+ yield {
140
+ type: EventType.RUN_ERROR,
146
141
  model: options.model,
147
- timestamp,
142
+ timestamp: Date.now(),
148
143
  message:
149
144
  error instanceof Error
150
145
  ? error.message
@@ -155,7 +150,7 @@ export class GeminiTextAdapter<
155
150
  ? error.message
156
151
  : 'An unknown error occurred during the chat stream.',
157
152
  },
158
- })
153
+ }
159
154
  }
160
155
  }
161
156
 
@@ -240,7 +235,6 @@ export class GeminiTextAdapter<
240
235
  logger: InternalLogger,
241
236
  ): AsyncIterable<StreamChunk> {
242
237
  const model = options.model
243
- const timestamp = Date.now()
244
238
  let accumulatedContent = ''
245
239
  let accumulatedThinking = ''
246
240
  const toolCallMap = new Map<
@@ -271,13 +265,13 @@ export class GeminiTextAdapter<
271
265
  // Emit RUN_STARTED on first chunk
272
266
  if (!hasEmittedRunStarted) {
273
267
  hasEmittedRunStarted = true
274
- yield asChunk({
275
- type: 'RUN_STARTED',
268
+ yield {
269
+ type: EventType.RUN_STARTED,
276
270
  runId,
277
271
  threadId,
278
272
  model,
279
- timestamp,
280
- })
273
+ timestamp: Date.now(),
274
+ }
281
275
  }
282
276
 
283
277
  if (chunk.candidates?.[0]?.content?.parts) {
@@ -293,92 +287,92 @@ export class GeminiTextAdapter<
293
287
  reasoningMessageId = generateId(this.name)
294
288
 
295
289
  // Spec REASONING events
296
- yield asChunk({
297
- type: 'REASONING_START',
290
+ yield {
291
+ type: EventType.REASONING_START,
298
292
  messageId: reasoningMessageId,
299
293
  model,
300
- timestamp,
301
- })
302
- yield asChunk({
303
- type: 'REASONING_MESSAGE_START',
294
+ timestamp: Date.now(),
295
+ }
296
+ yield {
297
+ type: EventType.REASONING_MESSAGE_START,
304
298
  messageId: reasoningMessageId,
305
299
  role: 'reasoning' as const,
306
300
  model,
307
- timestamp,
308
- })
301
+ timestamp: Date.now(),
302
+ }
309
303
 
310
304
  // Legacy STEP events (kept during transition)
311
- yield asChunk({
312
- type: 'STEP_STARTED',
305
+ yield {
306
+ type: EventType.STEP_STARTED,
313
307
  stepName: stepId,
314
308
  stepId,
315
309
  model,
316
- timestamp,
310
+ timestamp: Date.now(),
317
311
  stepType: 'thinking',
318
- })
312
+ }
319
313
  }
320
314
 
321
315
  accumulatedThinking += part.text
322
316
 
323
317
  // Spec REASONING content event
324
- yield asChunk({
325
- type: 'REASONING_MESSAGE_CONTENT',
318
+ yield {
319
+ type: EventType.REASONING_MESSAGE_CONTENT,
326
320
  messageId: reasoningMessageId!,
327
321
  delta: part.text,
328
322
  model,
329
- timestamp,
330
- })
323
+ timestamp: Date.now(),
324
+ }
331
325
 
332
326
  // Legacy STEP event
333
- yield asChunk({
334
- type: 'STEP_FINISHED',
327
+ yield {
328
+ type: EventType.STEP_FINISHED,
335
329
  stepName: stepId || generateId(this.name),
336
330
  stepId: stepId || generateId(this.name),
337
331
  model,
338
- timestamp,
332
+ timestamp: Date.now(),
339
333
  delta: part.text,
340
334
  content: accumulatedThinking,
341
- })
335
+ }
342
336
  } else if (part.text.trim()) {
343
337
  // Close reasoning before text starts
344
338
  if (reasoningMessageId && !hasClosedReasoning) {
345
339
  hasClosedReasoning = true
346
- yield asChunk({
347
- type: 'REASONING_MESSAGE_END',
340
+ yield {
341
+ type: EventType.REASONING_MESSAGE_END,
348
342
  messageId: reasoningMessageId,
349
343
  model,
350
- timestamp,
351
- })
352
- yield asChunk({
353
- type: 'REASONING_END',
344
+ timestamp: Date.now(),
345
+ }
346
+ yield {
347
+ type: EventType.REASONING_END,
354
348
  messageId: reasoningMessageId,
355
349
  model,
356
- timestamp,
357
- })
350
+ timestamp: Date.now(),
351
+ }
358
352
  }
359
353
 
360
354
  // Skip whitespace-only text parts (e.g. "\n" during auto-continuation)
361
355
  // Emit TEXT_MESSAGE_START on first text content
362
356
  if (!hasEmittedTextMessageStart) {
363
357
  hasEmittedTextMessageStart = true
364
- yield asChunk({
365
- type: 'TEXT_MESSAGE_START',
358
+ yield {
359
+ type: EventType.TEXT_MESSAGE_START,
366
360
  messageId,
367
361
  model,
368
- timestamp,
362
+ timestamp: Date.now(),
369
363
  role: 'assistant',
370
- })
364
+ }
371
365
  }
372
366
 
373
367
  accumulatedContent += part.text
374
- yield asChunk({
375
- type: 'TEXT_MESSAGE_CONTENT',
368
+ yield {
369
+ type: EventType.TEXT_MESSAGE_CONTENT,
376
370
  messageId,
377
371
  model,
378
- timestamp,
372
+ timestamp: Date.now(),
379
373
  delta: part.text,
380
374
  content: accumulatedContent,
381
- })
375
+ }
382
376
  }
383
377
  }
384
378
 
@@ -430,31 +424,31 @@ export class GeminiTextAdapter<
430
424
  // Emit TOOL_CALL_START if not already started
431
425
  if (!toolCallData.started) {
432
426
  toolCallData.started = true
433
- yield asChunk({
434
- type: 'TOOL_CALL_START',
427
+ yield {
428
+ type: EventType.TOOL_CALL_START,
435
429
  toolCallId,
436
430
  toolCallName: toolCallData.name,
437
431
  toolName: toolCallData.name,
438
432
  model,
439
- timestamp,
433
+ timestamp: Date.now(),
440
434
  index: toolCallData.index,
441
435
  ...(toolCallData.thoughtSignature && {
442
436
  metadata: {
443
437
  thoughtSignature: toolCallData.thoughtSignature,
444
438
  } satisfies GeminiToolCallMetadata,
445
439
  }),
446
- })
440
+ }
447
441
  }
448
442
 
449
443
  // Emit TOOL_CALL_ARGS
450
- yield asChunk({
451
- type: 'TOOL_CALL_ARGS',
444
+ yield {
445
+ type: EventType.TOOL_CALL_ARGS,
452
446
  toolCallId,
453
447
  model,
454
- timestamp,
448
+ timestamp: Date.now(),
455
449
  delta: toolCallData.args,
456
450
  args: toolCallData.args,
457
- })
451
+ }
458
452
  }
459
453
  }
460
454
  } else if (chunk.data && chunk.data.trim()) {
@@ -462,24 +456,24 @@ export class GeminiTextAdapter<
462
456
  // Emit TEXT_MESSAGE_START on first text content
463
457
  if (!hasEmittedTextMessageStart) {
464
458
  hasEmittedTextMessageStart = true
465
- yield asChunk({
466
- type: 'TEXT_MESSAGE_START',
459
+ yield {
460
+ type: EventType.TEXT_MESSAGE_START,
467
461
  messageId,
468
462
  model,
469
- timestamp,
463
+ timestamp: Date.now(),
470
464
  role: 'assistant',
471
- })
465
+ }
472
466
  }
473
467
 
474
468
  accumulatedContent += chunk.data
475
- yield asChunk({
476
- type: 'TEXT_MESSAGE_CONTENT',
469
+ yield {
470
+ type: EventType.TEXT_MESSAGE_CONTENT,
477
471
  messageId,
478
472
  model,
479
- timestamp,
473
+ timestamp: Date.now(),
480
474
  delta: chunk.data,
481
475
  content: accumulatedContent,
482
- })
476
+ }
483
477
  }
484
478
 
485
479
  if (chunk.candidates?.[0]?.finishReason) {
@@ -508,15 +502,15 @@ export class GeminiTextAdapter<
508
502
  })
509
503
 
510
504
  // Emit TOOL_CALL_START
511
- yield asChunk({
512
- type: 'TOOL_CALL_START',
505
+ yield {
506
+ type: EventType.TOOL_CALL_START,
513
507
  toolCallId,
514
508
  toolCallName: functionCall.name || '',
515
509
  toolName: functionCall.name || '',
516
510
  model,
517
- timestamp,
511
+ timestamp: Date.now(),
518
512
  index: nextToolIndex - 1,
519
- })
513
+ }
520
514
 
521
515
  // Emit TOOL_CALL_END with parsed input
522
516
  let parsedInput: unknown = {}
@@ -531,15 +525,15 @@ export class GeminiTextAdapter<
531
525
  parsedInput = {}
532
526
  }
533
527
 
534
- yield asChunk({
535
- type: 'TOOL_CALL_END',
528
+ yield {
529
+ type: EventType.TOOL_CALL_END,
536
530
  toolCallId,
537
531
  toolCallName: functionCall.name || '',
538
532
  toolName: functionCall.name || '',
539
533
  model,
540
- timestamp,
534
+ timestamp: Date.now(),
541
535
  input: parsedInput,
542
- })
536
+ }
543
537
  }
544
538
  }
545
539
  }
@@ -555,15 +549,15 @@ export class GeminiTextAdapter<
555
549
  parsedInput = {}
556
550
  }
557
551
 
558
- yield asChunk({
559
- type: 'TOOL_CALL_END',
552
+ yield {
553
+ type: EventType.TOOL_CALL_END,
560
554
  toolCallId,
561
555
  toolCallName: toolCallData.name,
562
556
  toolName: toolCallData.name,
563
557
  model,
564
- timestamp,
558
+ timestamp: Date.now(),
565
559
  input: parsedInput,
566
- })
560
+ }
567
561
  }
568
562
 
569
563
  // Reset so a new TEXT_MESSAGE_START is emitted if text follows tool calls
@@ -572,11 +566,11 @@ export class GeminiTextAdapter<
572
566
  }
573
567
 
574
568
  if (finishReason === FinishReason.MAX_TOKENS) {
575
- yield asChunk({
576
- type: 'RUN_ERROR',
569
+ yield {
570
+ type: EventType.RUN_ERROR,
577
571
  runId,
578
572
  model,
579
- timestamp,
573
+ timestamp: Date.now(),
580
574
  message:
581
575
  'The response was cut off because the maximum token limit was reached.',
582
576
  code: 'max_tokens',
@@ -585,42 +579,42 @@ export class GeminiTextAdapter<
585
579
  'The response was cut off because the maximum token limit was reached.',
586
580
  code: 'max_tokens',
587
581
  },
588
- })
582
+ }
589
583
  }
590
584
 
591
585
  // Close reasoning events if still open
592
586
  if (reasoningMessageId && !hasClosedReasoning) {
593
587
  hasClosedReasoning = true
594
- yield asChunk({
595
- type: 'REASONING_MESSAGE_END',
588
+ yield {
589
+ type: EventType.REASONING_MESSAGE_END,
596
590
  messageId: reasoningMessageId,
597
591
  model,
598
- timestamp,
599
- })
600
- yield asChunk({
601
- type: 'REASONING_END',
592
+ timestamp: Date.now(),
593
+ }
594
+ yield {
595
+ type: EventType.REASONING_END,
602
596
  messageId: reasoningMessageId,
603
597
  model,
604
- timestamp,
605
- })
598
+ timestamp: Date.now(),
599
+ }
606
600
  }
607
601
 
608
602
  // Emit TEXT_MESSAGE_END if we had text content
609
603
  if (hasEmittedTextMessageStart) {
610
- yield asChunk({
611
- type: 'TEXT_MESSAGE_END',
604
+ yield {
605
+ type: EventType.TEXT_MESSAGE_END,
612
606
  messageId,
613
607
  model,
614
- timestamp,
615
- })
608
+ timestamp: Date.now(),
609
+ }
616
610
  }
617
611
 
618
- yield asChunk({
619
- type: 'RUN_FINISHED',
612
+ yield {
613
+ type: EventType.RUN_FINISHED,
620
614
  runId,
621
615
  threadId,
622
616
  model,
623
- timestamp,
617
+ timestamp: Date.now(),
624
618
  finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',
625
619
  usage: chunk.usageMetadata
626
620
  ? {
@@ -629,7 +623,7 @@ export class GeminiTextAdapter<
629
623
  totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,
630
624
  }
631
625
  : undefined,
632
- })
626
+ }
633
627
  }
634
628
  }
635
629
  }
package/src/index.ts CHANGED
@@ -11,15 +11,12 @@ export {
11
11
  type GeminiTextProviderOptions,
12
12
  } from './adapters/text'
13
13
 
14
- // Summarize adapter
14
+ // Summarize - thin factory functions over @tanstack/ai's ChatStreamSummarizeAdapter
15
15
  export {
16
- GeminiSummarizeAdapter,
17
- GeminiSummarizeModels,
18
16
  createGeminiSummarize,
19
17
  geminiSummarize,
20
- type GeminiSummarizeAdapterOptions,
18
+ type GeminiSummarizeConfig,
21
19
  type GeminiSummarizeModel,
22
- type GeminiSummarizeProviderOptions,
23
20
  } from './adapters/summarize'
24
21
 
25
22
  // Image adapter