@tanstack/ai-gemini 0.17.3 → 0.18.2
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/dist/esm/experimental/text-interactions/adapter.js +248 -95
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/experimental/text-interactions/events.d.ts +29 -23
- package/dist/esm/experimental/text-interactions/provider-options.d.ts +1 -1
- package/package.json +6 -5
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventType } from "@tanstack/ai";
|
|
2
2
|
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
3
|
+
import { parse } from "partial-json";
|
|
3
4
|
import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../../utils/client.js";
|
|
4
5
|
class GeminiTextInteractionsAdapter extends BaseTextAdapter {
|
|
5
6
|
kind = "text";
|
|
@@ -130,8 +131,11 @@ class GeminiTextInteractionsAdapter extends BaseTextAdapter {
|
|
|
130
131
|
});
|
|
131
132
|
const request = {
|
|
132
133
|
...baseRequest,
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
response_format: {
|
|
135
|
+
type: "text",
|
|
136
|
+
mime_type: "application/json",
|
|
137
|
+
schema: outputSchema
|
|
138
|
+
}
|
|
135
139
|
};
|
|
136
140
|
try {
|
|
137
141
|
logger.request(
|
|
@@ -201,8 +205,7 @@ function buildInteractionsRequest(options) {
|
|
|
201
205
|
store: modelOpts?.store,
|
|
202
206
|
background: modelOpts?.background,
|
|
203
207
|
response_modalities: modelOpts?.response_modalities,
|
|
204
|
-
response_format: modelOpts?.response_format
|
|
205
|
-
response_mime_type: modelOpts?.response_mime_type
|
|
208
|
+
response_format: modelOpts?.response_format
|
|
206
209
|
};
|
|
207
210
|
}
|
|
208
211
|
function convertMessagesToInteractionsInput(messages, hasPreviousInteraction) {
|
|
@@ -297,21 +300,13 @@ function messagesAfterLastAssistant(messages) {
|
|
|
297
300
|
}
|
|
298
301
|
return messages;
|
|
299
302
|
}
|
|
300
|
-
function
|
|
301
|
-
if (!raw) return
|
|
303
|
+
function parsePartialToolArguments(raw) {
|
|
304
|
+
if (!raw) return void 0;
|
|
302
305
|
try {
|
|
303
|
-
const parsed =
|
|
304
|
-
return parsed && typeof parsed === "object" ? parsed :
|
|
305
|
-
} catch
|
|
306
|
-
|
|
307
|
-
"gemini-text-interactions.safeParseToolArguments parse failed",
|
|
308
|
-
{
|
|
309
|
-
error,
|
|
310
|
-
raw,
|
|
311
|
-
source: "gemini-text-interactions.chatStream"
|
|
312
|
-
}
|
|
313
|
-
);
|
|
314
|
-
return {};
|
|
306
|
+
const parsed = parse(raw);
|
|
307
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
308
|
+
} catch {
|
|
309
|
+
return void 0;
|
|
315
310
|
}
|
|
316
311
|
}
|
|
317
312
|
const IMAGE_MIME_TYPES = [
|
|
@@ -492,6 +487,8 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
492
487
|
let thinkingAccumulated = "";
|
|
493
488
|
let reasoningMessageId = null;
|
|
494
489
|
let hasClosedReasoning = false;
|
|
490
|
+
const indexToToolCallId = /* @__PURE__ */ new Map();
|
|
491
|
+
const argStringByToolCallId = /* @__PURE__ */ new Map();
|
|
495
492
|
const closeReasoningIfNeeded = function* () {
|
|
496
493
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
497
494
|
hasClosedReasoning = true;
|
|
@@ -552,82 +549,132 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
552
549
|
for await (const event of stream) {
|
|
553
550
|
logger.provider(`provider=gemini-text-interactions`, { event });
|
|
554
551
|
switch (event.event_type) {
|
|
555
|
-
case "interaction.
|
|
552
|
+
case "interaction.created": {
|
|
556
553
|
interactionId = event.interaction.id;
|
|
557
554
|
yield* emitRunStartedIfNeeded();
|
|
558
555
|
break;
|
|
559
556
|
}
|
|
560
|
-
case "
|
|
557
|
+
case "step.start": {
|
|
561
558
|
yield* emitRunStartedIfNeeded();
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const delta = event.delta;
|
|
567
|
-
switch (delta.type) {
|
|
568
|
-
case "text": {
|
|
559
|
+
const step = event.step;
|
|
560
|
+
const index = event.index;
|
|
561
|
+
switch (step.type) {
|
|
562
|
+
case "function_call": {
|
|
569
563
|
yield* closeReasoningIfNeeded();
|
|
570
|
-
|
|
571
|
-
|
|
564
|
+
sawFunctionCall = true;
|
|
565
|
+
const toolCallId = step.id;
|
|
566
|
+
indexToToolCallId.set(index, toolCallId);
|
|
567
|
+
const initialArgs = step.arguments;
|
|
568
|
+
const state = {
|
|
569
|
+
name: step.name,
|
|
570
|
+
args: { ...initialArgs },
|
|
571
|
+
index: nextToolIndex++,
|
|
572
|
+
started: true,
|
|
573
|
+
ended: false
|
|
574
|
+
};
|
|
575
|
+
toolCalls.set(toolCallId, state);
|
|
576
|
+
argStringByToolCallId.set(
|
|
577
|
+
toolCallId,
|
|
578
|
+
Object.keys(initialArgs).length > 0 ? JSON.stringify(initialArgs) : ""
|
|
579
|
+
);
|
|
580
|
+
yield {
|
|
581
|
+
type: EventType.TOOL_CALL_START,
|
|
582
|
+
toolCallId,
|
|
583
|
+
toolCallName: state.name,
|
|
584
|
+
toolName: state.name,
|
|
585
|
+
// Bind the tool call to the same assistant message id the
|
|
586
|
+
// eventual TEXT_MESSAGE_START uses so the message id stays
|
|
587
|
+
// stable when a function_call arrives before any text (#477).
|
|
588
|
+
parentMessageId: messageId,
|
|
589
|
+
model,
|
|
590
|
+
timestamp,
|
|
591
|
+
index: state.index
|
|
592
|
+
};
|
|
593
|
+
if (Object.keys(initialArgs).length > 0) {
|
|
594
|
+
const argsJson = JSON.stringify(initialArgs);
|
|
572
595
|
yield {
|
|
573
|
-
type: EventType.
|
|
574
|
-
|
|
596
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
597
|
+
toolCallId,
|
|
575
598
|
model,
|
|
576
599
|
timestamp,
|
|
577
|
-
|
|
600
|
+
delta: argsJson,
|
|
601
|
+
args: argsJson
|
|
578
602
|
};
|
|
579
603
|
}
|
|
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
604
|
break;
|
|
590
605
|
}
|
|
591
|
-
case "
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
606
|
+
case "thought": {
|
|
607
|
+
if (thinkingStepId === null || reasoningMessageId === null) {
|
|
608
|
+
thinkingStepId = generateId(adapterName);
|
|
609
|
+
reasoningMessageId = generateId(adapterName);
|
|
610
|
+
yield {
|
|
611
|
+
type: EventType.REASONING_START,
|
|
612
|
+
messageId: reasoningMessageId,
|
|
613
|
+
model,
|
|
614
|
+
timestamp
|
|
615
|
+
};
|
|
616
|
+
yield {
|
|
617
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
618
|
+
messageId: reasoningMessageId,
|
|
619
|
+
role: "reasoning",
|
|
620
|
+
model,
|
|
621
|
+
timestamp
|
|
622
|
+
};
|
|
623
|
+
yield {
|
|
624
|
+
type: EventType.STEP_STARTED,
|
|
625
|
+
stepName: thinkingStepId,
|
|
626
|
+
stepId: thinkingStepId,
|
|
627
|
+
model,
|
|
628
|
+
timestamp,
|
|
629
|
+
stepType: "thinking"
|
|
604
630
|
};
|
|
605
|
-
toolCalls.set(toolCallId, state);
|
|
606
|
-
} else {
|
|
607
|
-
state.args = { ...state.args, ...deltaArgs };
|
|
608
|
-
if (delta.name) state.name = delta.name;
|
|
609
631
|
}
|
|
610
|
-
|
|
611
|
-
|
|
632
|
+
for (const part of step.summary ?? []) {
|
|
633
|
+
if (part.type !== "text" || !part.text) continue;
|
|
634
|
+
thinkingAccumulated += part.text;
|
|
612
635
|
yield {
|
|
613
|
-
type: EventType.
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
636
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
637
|
+
messageId: reasoningMessageId,
|
|
638
|
+
delta: part.text,
|
|
639
|
+
model,
|
|
640
|
+
timestamp
|
|
641
|
+
};
|
|
642
|
+
yield {
|
|
643
|
+
type: EventType.STEP_FINISHED,
|
|
644
|
+
stepName: thinkingStepId,
|
|
645
|
+
stepId: thinkingStepId,
|
|
618
646
|
model,
|
|
619
647
|
timestamp,
|
|
620
|
-
|
|
648
|
+
delta: part.text,
|
|
649
|
+
content: thinkingAccumulated
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case "model_output": {
|
|
655
|
+
yield* closeReasoningIfNeeded();
|
|
656
|
+
for (const part of step.content ?? []) {
|
|
657
|
+
if (part.type !== "text" || !part.text) continue;
|
|
658
|
+
if (!hasEmittedTextMessageStart) {
|
|
659
|
+
hasEmittedTextMessageStart = true;
|
|
660
|
+
yield {
|
|
661
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
662
|
+
messageId,
|
|
663
|
+
model,
|
|
664
|
+
timestamp,
|
|
665
|
+
role: "assistant"
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
textAccumulated += part.text;
|
|
669
|
+
yield {
|
|
670
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
671
|
+
messageId,
|
|
672
|
+
model,
|
|
673
|
+
timestamp,
|
|
674
|
+
delta: part.text,
|
|
675
|
+
content: textAccumulated
|
|
621
676
|
};
|
|
622
677
|
}
|
|
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
678
|
break;
|
|
632
679
|
}
|
|
633
680
|
case "google_search_call": {
|
|
@@ -635,7 +682,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
635
682
|
yield {
|
|
636
683
|
type: EventType.CUSTOM,
|
|
637
684
|
name: "gemini.googleSearchCall",
|
|
638
|
-
value:
|
|
685
|
+
value: step,
|
|
639
686
|
model,
|
|
640
687
|
timestamp
|
|
641
688
|
};
|
|
@@ -646,7 +693,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
646
693
|
yield {
|
|
647
694
|
type: EventType.CUSTOM,
|
|
648
695
|
name: "gemini.googleSearchResult",
|
|
649
|
-
value:
|
|
696
|
+
value: step,
|
|
650
697
|
model,
|
|
651
698
|
timestamp
|
|
652
699
|
};
|
|
@@ -657,7 +704,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
657
704
|
yield {
|
|
658
705
|
type: EventType.CUSTOM,
|
|
659
706
|
name: "gemini.codeExecutionCall",
|
|
660
|
-
value:
|
|
707
|
+
value: step,
|
|
661
708
|
model,
|
|
662
709
|
timestamp
|
|
663
710
|
};
|
|
@@ -668,7 +715,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
668
715
|
yield {
|
|
669
716
|
type: EventType.CUSTOM,
|
|
670
717
|
name: "gemini.codeExecutionResult",
|
|
671
|
-
value:
|
|
718
|
+
value: step,
|
|
672
719
|
model,
|
|
673
720
|
timestamp
|
|
674
721
|
};
|
|
@@ -679,7 +726,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
679
726
|
yield {
|
|
680
727
|
type: EventType.CUSTOM,
|
|
681
728
|
name: "gemini.urlContextCall",
|
|
682
|
-
value:
|
|
729
|
+
value: step,
|
|
683
730
|
model,
|
|
684
731
|
timestamp
|
|
685
732
|
};
|
|
@@ -690,7 +737,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
690
737
|
yield {
|
|
691
738
|
type: EventType.CUSTOM,
|
|
692
739
|
name: "gemini.urlContextResult",
|
|
693
|
-
value:
|
|
740
|
+
value: step,
|
|
694
741
|
model,
|
|
695
742
|
timestamp
|
|
696
743
|
};
|
|
@@ -701,7 +748,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
701
748
|
yield {
|
|
702
749
|
type: EventType.CUSTOM,
|
|
703
750
|
name: "gemini.fileSearchCall",
|
|
704
|
-
value:
|
|
751
|
+
value: step,
|
|
705
752
|
model,
|
|
706
753
|
timestamp
|
|
707
754
|
};
|
|
@@ -712,12 +759,83 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
712
759
|
yield {
|
|
713
760
|
type: EventType.CUSTOM,
|
|
714
761
|
name: "gemini.fileSearchResult",
|
|
715
|
-
value:
|
|
762
|
+
value: step,
|
|
716
763
|
model,
|
|
717
764
|
timestamp
|
|
718
765
|
};
|
|
719
766
|
break;
|
|
720
767
|
}
|
|
768
|
+
// Unhandled step types (user_input on GET timelines,
|
|
769
|
+
// mcp_server_*, google_maps_*, function_result) fall through
|
|
770
|
+
// to the observability default so SDK drift is visible.
|
|
771
|
+
case "user_input":
|
|
772
|
+
case "mcp_server_tool_call":
|
|
773
|
+
case "mcp_server_tool_result":
|
|
774
|
+
case "google_maps_call":
|
|
775
|
+
case "google_maps_result":
|
|
776
|
+
case "function_result":
|
|
777
|
+
default:
|
|
778
|
+
logger.provider(`gemini-text-interactions unhandled step.start`, {
|
|
779
|
+
step
|
|
780
|
+
});
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
case "step.delta": {
|
|
786
|
+
yield* emitRunStartedIfNeeded();
|
|
787
|
+
const delta = event.delta;
|
|
788
|
+
const index = event.index;
|
|
789
|
+
switch (delta.type) {
|
|
790
|
+
case "text": {
|
|
791
|
+
yield* closeReasoningIfNeeded();
|
|
792
|
+
if (!hasEmittedTextMessageStart) {
|
|
793
|
+
hasEmittedTextMessageStart = true;
|
|
794
|
+
yield {
|
|
795
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
796
|
+
messageId,
|
|
797
|
+
model,
|
|
798
|
+
timestamp,
|
|
799
|
+
role: "assistant"
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
textAccumulated += delta.text;
|
|
803
|
+
yield {
|
|
804
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
805
|
+
messageId,
|
|
806
|
+
model,
|
|
807
|
+
timestamp,
|
|
808
|
+
delta: delta.text,
|
|
809
|
+
content: textAccumulated
|
|
810
|
+
};
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
case "arguments_delta": {
|
|
814
|
+
const toolCallId = indexToToolCallId.get(index);
|
|
815
|
+
if (!toolCallId) {
|
|
816
|
+
logger.provider(
|
|
817
|
+
`gemini-text-interactions arguments_delta for unknown step index`,
|
|
818
|
+
{ index, delta }
|
|
819
|
+
);
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
const state = toolCalls.get(toolCallId);
|
|
823
|
+
if (!state) break;
|
|
824
|
+
const fragment = delta.arguments ?? "";
|
|
825
|
+
const buffer = (argStringByToolCallId.get(toolCallId) ?? "") + fragment;
|
|
826
|
+
argStringByToolCallId.set(toolCallId, buffer);
|
|
827
|
+
const parsed = parsePartialToolArguments(buffer);
|
|
828
|
+
if (parsed) state.args = parsed;
|
|
829
|
+
yield {
|
|
830
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
831
|
+
toolCallId,
|
|
832
|
+
model,
|
|
833
|
+
timestamp,
|
|
834
|
+
delta: fragment,
|
|
835
|
+
args: buffer
|
|
836
|
+
};
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
721
839
|
case "thought_summary": {
|
|
722
840
|
const thoughtText = delta.content && "text" in delta.content ? delta.content.text : "";
|
|
723
841
|
if (!thoughtText) break;
|
|
@@ -765,33 +883,62 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
765
883
|
};
|
|
766
884
|
break;
|
|
767
885
|
}
|
|
768
|
-
// The
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
886
|
+
// The remaining StepDelta variants (image/audio/video/document
|
|
887
|
+
// for output modalities a text adapter shouldn't see, tool
|
|
888
|
+
// call/result deltas which are surfaced via step.start in this
|
|
889
|
+
// adapter, thought_signature, annotation deltas, mcp/google
|
|
890
|
+
// maps variants) fall through to the observability default.
|
|
773
891
|
case "image":
|
|
774
892
|
case "audio":
|
|
775
893
|
case "video":
|
|
776
894
|
case "document":
|
|
777
|
-
case "
|
|
895
|
+
case "thought_signature":
|
|
896
|
+
case "text_annotation_delta":
|
|
897
|
+
case "code_execution_call":
|
|
898
|
+
case "code_execution_result":
|
|
899
|
+
case "url_context_call":
|
|
900
|
+
case "url_context_result":
|
|
901
|
+
case "google_search_call":
|
|
902
|
+
case "google_search_result":
|
|
903
|
+
case "file_search_call":
|
|
904
|
+
case "file_search_result":
|
|
778
905
|
case "mcp_server_tool_call":
|
|
779
906
|
case "mcp_server_tool_result":
|
|
780
|
-
case "
|
|
907
|
+
case "google_maps_call":
|
|
908
|
+
case "google_maps_result":
|
|
909
|
+
case "function_result":
|
|
781
910
|
default:
|
|
782
911
|
logger.provider(
|
|
783
|
-
`gemini-text-interactions unhandled
|
|
912
|
+
`gemini-text-interactions unhandled step.delta type`,
|
|
784
913
|
{ delta }
|
|
785
914
|
);
|
|
786
915
|
break;
|
|
787
916
|
}
|
|
788
917
|
break;
|
|
789
918
|
}
|
|
790
|
-
case "
|
|
919
|
+
case "step.stop": {
|
|
920
|
+
const toolCallId = indexToToolCallId.get(event.index);
|
|
921
|
+
if (toolCallId) {
|
|
922
|
+
const state = toolCalls.get(toolCallId);
|
|
923
|
+
if (state && !state.ended) {
|
|
924
|
+
state.ended = true;
|
|
925
|
+
yield {
|
|
926
|
+
type: EventType.TOOL_CALL_END,
|
|
927
|
+
toolCallId,
|
|
928
|
+
toolName: state.name,
|
|
929
|
+
model,
|
|
930
|
+
timestamp,
|
|
931
|
+
input: state.args
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
indexToToolCallId.delete(event.index);
|
|
935
|
+
}
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
791
938
|
case "interaction.status_update": {
|
|
792
939
|
break;
|
|
793
940
|
}
|
|
794
|
-
case "interaction.
|
|
941
|
+
case "interaction.completed": {
|
|
795
942
|
if (event.interaction.id) {
|
|
796
943
|
interactionId = event.interaction.id;
|
|
797
944
|
}
|
|
@@ -871,10 +1018,16 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
871
1018
|
yield* closeOpenState();
|
|
872
1019
|
}
|
|
873
1020
|
function extractTextFromInteraction(interaction) {
|
|
1021
|
+
if (typeof interaction.output_text === "string" && interaction.output_text) {
|
|
1022
|
+
return interaction.output_text;
|
|
1023
|
+
}
|
|
874
1024
|
let text = "";
|
|
875
|
-
for (const
|
|
876
|
-
if (
|
|
877
|
-
|
|
1025
|
+
for (const step of interaction.steps) {
|
|
1026
|
+
if (step.type !== "model_output" || !step.content) continue;
|
|
1027
|
+
for (const part of step.content) {
|
|
1028
|
+
if (part.type === "text") {
|
|
1029
|
+
text += part.text;
|
|
1030
|
+
}
|
|
878
1031
|
}
|
|
879
1032
|
}
|
|
880
1033
|
return text;
|