@convex-dev/agent 0.2.8-alpha.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/deltas.test.ts +262 -0
package/package.json
CHANGED
package/src/deltas.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
blankUIMessage,
|
|
4
|
+
combineUIMessages,
|
|
4
5
|
deriveUIMessagesFromTextStreamParts,
|
|
5
6
|
updateFromTextStreamParts,
|
|
6
7
|
updateFromUIMessageChunks,
|
|
@@ -534,3 +535,264 @@ describe("mergeDeltas", () => {
|
|
|
534
535
|
]);
|
|
535
536
|
});
|
|
536
537
|
});
|
|
538
|
+
|
|
539
|
+
describe("combineUIMessages", () => {
|
|
540
|
+
it("combines messages spanning two pages correctly", () => {
|
|
541
|
+
const message1 = {
|
|
542
|
+
id: "msg1",
|
|
543
|
+
key: "msg1-key",
|
|
544
|
+
_creationTime: Date.now(),
|
|
545
|
+
order: 1,
|
|
546
|
+
stepOrder: 1,
|
|
547
|
+
status: "success" as const,
|
|
548
|
+
role: "assistant" as const,
|
|
549
|
+
text: "",
|
|
550
|
+
parts: [
|
|
551
|
+
{
|
|
552
|
+
type: "dynamic-tool" as const,
|
|
553
|
+
state: "input-available" as const,
|
|
554
|
+
toolCallId: "call_123",
|
|
555
|
+
toolName: "calculator",
|
|
556
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const message2 = {
|
|
562
|
+
id: "msg2",
|
|
563
|
+
key: "msg2-key",
|
|
564
|
+
_creationTime: Date.now() + 1,
|
|
565
|
+
order: 1,
|
|
566
|
+
stepOrder: 2,
|
|
567
|
+
status: "success" as const,
|
|
568
|
+
role: "assistant" as const,
|
|
569
|
+
text: "The result is 5.",
|
|
570
|
+
parts: [
|
|
571
|
+
{
|
|
572
|
+
type: "tool-calculator" as const,
|
|
573
|
+
state: "output-available" as const,
|
|
574
|
+
toolCallId: "call_123",
|
|
575
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
576
|
+
output: { result: 5 },
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
type: "text" as const,
|
|
580
|
+
text: "The result is 5.",
|
|
581
|
+
state: "done" as const,
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const combined = combineUIMessages([message1, message2]);
|
|
587
|
+
|
|
588
|
+
expect(combined).toHaveLength(1);
|
|
589
|
+
expect(combined[0].role).toBe("assistant");
|
|
590
|
+
expect(combined[0].text).toBe("The result is 5.");
|
|
591
|
+
expect(combined[0].parts).toHaveLength(2);
|
|
592
|
+
|
|
593
|
+
const toolPart = combined[0].parts.find(p => p.type === "tool-calculator");
|
|
594
|
+
expect(toolPart).toMatchObject({
|
|
595
|
+
type: "tool-calculator",
|
|
596
|
+
state: "output-available",
|
|
597
|
+
toolCallId: "call_123",
|
|
598
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
599
|
+
output: { result: 5 },
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const textPart = combined[0].parts.find(p => p.type === "text");
|
|
603
|
+
expect(textPart).toMatchObject({
|
|
604
|
+
type: "text",
|
|
605
|
+
text: "The result is 5.",
|
|
606
|
+
state: "done",
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("preserves separate messages with different roles", () => {
|
|
611
|
+
const userMessage = {
|
|
612
|
+
id: "user1",
|
|
613
|
+
key: "user1-key",
|
|
614
|
+
_creationTime: Date.now(),
|
|
615
|
+
order: 1,
|
|
616
|
+
stepOrder: 0,
|
|
617
|
+
status: "success" as const,
|
|
618
|
+
role: "user" as const,
|
|
619
|
+
text: "Calculate 2 + 3",
|
|
620
|
+
parts: [{ type: "text" as const, text: "Calculate 2 + 3" }],
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const assistantMessage = {
|
|
624
|
+
id: "assistant1",
|
|
625
|
+
key: "assistant1-key",
|
|
626
|
+
_creationTime: Date.now() + 1,
|
|
627
|
+
order: 2,
|
|
628
|
+
stepOrder: 0,
|
|
629
|
+
status: "success" as const,
|
|
630
|
+
role: "assistant" as const,
|
|
631
|
+
text: "The result is 5.",
|
|
632
|
+
parts: [{ type: "text" as const, text: "The result is 5.", state: "done" as const }],
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const combined = combineUIMessages([userMessage, assistantMessage]);
|
|
636
|
+
|
|
637
|
+
expect(combined).toHaveLength(2);
|
|
638
|
+
expect(combined[0]).toEqual(userMessage);
|
|
639
|
+
expect(combined[1]).toEqual(assistantMessage);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("combines multiple tool calls across pages", () => {
|
|
643
|
+
const message1 = {
|
|
644
|
+
id: "msg1",
|
|
645
|
+
key: "msg1-key",
|
|
646
|
+
_creationTime: Date.now(),
|
|
647
|
+
order: 1,
|
|
648
|
+
stepOrder: 1,
|
|
649
|
+
status: "success" as const,
|
|
650
|
+
role: "assistant" as const,
|
|
651
|
+
text: "",
|
|
652
|
+
parts: [
|
|
653
|
+
{
|
|
654
|
+
type: "dynamic-tool" as const,
|
|
655
|
+
state: "input-available" as const,
|
|
656
|
+
toolCallId: "call_1",
|
|
657
|
+
toolName: "calculator",
|
|
658
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
type: "dynamic-tool" as const,
|
|
662
|
+
state: "input-available" as const,
|
|
663
|
+
toolCallId: "call_2",
|
|
664
|
+
toolName: "formatter",
|
|
665
|
+
input: { text: "result" },
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const message2 = {
|
|
671
|
+
id: "msg2",
|
|
672
|
+
key: "msg2-key",
|
|
673
|
+
_creationTime: Date.now() + 1,
|
|
674
|
+
order: 1,
|
|
675
|
+
stepOrder: 2,
|
|
676
|
+
status: "success" as const,
|
|
677
|
+
role: "assistant" as const,
|
|
678
|
+
text: "The formatted result is: 5",
|
|
679
|
+
parts: [
|
|
680
|
+
{
|
|
681
|
+
type: "tool-calculator" as const,
|
|
682
|
+
state: "output-available" as const,
|
|
683
|
+
toolCallId: "call_1",
|
|
684
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
685
|
+
output: { result: 5 },
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
type: "tool-formatter" as const,
|
|
689
|
+
state: "output-available" as const,
|
|
690
|
+
toolCallId: "call_2",
|
|
691
|
+
input: { text: "result" },
|
|
692
|
+
output: { formatted: "The formatted result is: 5" },
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
type: "text" as const,
|
|
696
|
+
text: "The formatted result is: 5",
|
|
697
|
+
state: "done" as const,
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const combined = combineUIMessages([message1, message2]);
|
|
703
|
+
|
|
704
|
+
expect(combined).toHaveLength(1);
|
|
705
|
+
expect(combined[0].role).toBe("assistant");
|
|
706
|
+
expect(combined[0].text).toBe("The formatted result is: 5");
|
|
707
|
+
expect(combined[0].parts).toHaveLength(3);
|
|
708
|
+
|
|
709
|
+
const calculatorPart = combined[0].parts.find(p =>
|
|
710
|
+
p.type === "tool-calculator" && "toolCallId" in p && p.toolCallId === "call_1"
|
|
711
|
+
);
|
|
712
|
+
expect(calculatorPart).toMatchObject({
|
|
713
|
+
type: "tool-calculator",
|
|
714
|
+
state: "output-available",
|
|
715
|
+
toolCallId: "call_1",
|
|
716
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
717
|
+
output: { result: 5 },
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const formatterPart = combined[0].parts.find(p =>
|
|
721
|
+
p.type === "tool-formatter" && "toolCallId" in p && p.toolCallId === "call_2"
|
|
722
|
+
);
|
|
723
|
+
expect(formatterPart).toMatchObject({
|
|
724
|
+
type: "tool-formatter",
|
|
725
|
+
state: "output-available",
|
|
726
|
+
toolCallId: "call_2",
|
|
727
|
+
input: { text: "result" },
|
|
728
|
+
output: { formatted: "The formatted result is: 5" },
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("handles tool call without corresponding output", () => {
|
|
733
|
+
const message1 = {
|
|
734
|
+
id: "msg1",
|
|
735
|
+
key: "msg1-key",
|
|
736
|
+
_creationTime: Date.now(),
|
|
737
|
+
order: 1,
|
|
738
|
+
stepOrder: 1,
|
|
739
|
+
status: "success" as const,
|
|
740
|
+
role: "assistant" as const,
|
|
741
|
+
text: "",
|
|
742
|
+
parts: [
|
|
743
|
+
{
|
|
744
|
+
type: "dynamic-tool" as const,
|
|
745
|
+
state: "input-available" as const,
|
|
746
|
+
toolCallId: "call_orphan",
|
|
747
|
+
toolName: "calculator",
|
|
748
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const message2 = {
|
|
754
|
+
id: "msg2",
|
|
755
|
+
key: "msg2-key",
|
|
756
|
+
_creationTime: Date.now() + 1,
|
|
757
|
+
order: 1,
|
|
758
|
+
stepOrder: 2,
|
|
759
|
+
status: "success" as const,
|
|
760
|
+
role: "assistant" as const,
|
|
761
|
+
text: "Still processing...",
|
|
762
|
+
parts: [
|
|
763
|
+
{
|
|
764
|
+
type: "text" as const,
|
|
765
|
+
text: "Still processing...",
|
|
766
|
+
state: "done" as const,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const combined = combineUIMessages([message1, message2]);
|
|
772
|
+
|
|
773
|
+
expect(combined).toHaveLength(1);
|
|
774
|
+
expect(combined[0].role).toBe("assistant");
|
|
775
|
+
expect(combined[0].text).toBe("Still processing...");
|
|
776
|
+
expect(combined[0].parts).toHaveLength(2);
|
|
777
|
+
|
|
778
|
+
const toolPart = combined[0].parts.find(p =>
|
|
779
|
+
p.type === "dynamic-tool"
|
|
780
|
+
);
|
|
781
|
+
expect(toolPart).toMatchObject({
|
|
782
|
+
type: "dynamic-tool",
|
|
783
|
+
state: "input-available",
|
|
784
|
+
toolCallId: "call_orphan",
|
|
785
|
+
toolName: "calculator",
|
|
786
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const textPart = combined[0].parts.find(p =>
|
|
790
|
+
p.type === "text"
|
|
791
|
+
);
|
|
792
|
+
expect(textPart).toMatchObject({
|
|
793
|
+
type: "text",
|
|
794
|
+
text: "Still processing...",
|
|
795
|
+
state: "done",
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
});
|