@convex-dev/agent 0.7.1 → 0.7.3
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 +5 -8
- package/dist/client/messages.d.ts +10 -0
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +1 -0
- package/dist/client/messages.js.map +1 -1
- package/dist/component/_generated/component.d.ts +10 -8
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/messages.d.ts +340 -338
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +74 -14
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +726 -622
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/streams.d.ts +6 -1
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +14 -15
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +1 -0
- package/dist/component/threads.js.map +1 -1
- package/dist/component/vector/index.d.ts +9 -9
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/tables.d.ts +5 -5
- package/dist/component/vector/tables.d.ts.map +1 -1
- package/dist/component/vector/tables.js +1 -1
- package/dist/component/vector/tables.js.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/validators.d.ts +1952 -1952
- package/dist/vercel/client/definePlaygroundAPI.d.ts +132 -132
- package/dist/vercel/client/messages.d.ts +50 -32
- package/dist/vercel/client/messages.d.ts.map +1 -1
- package/dist/vercel/client/messages.js +2 -0
- package/dist/vercel/client/messages.js.map +1 -1
- package/dist/vercel/client/search.d.ts +475 -32
- package/dist/vercel/client/search.d.ts.map +1 -1
- package/dist/vercel/client/search.js +34 -9
- package/dist/vercel/client/search.js.map +1 -1
- package/dist/vercel/client/start.d.ts.map +1 -1
- package/dist/vercel/client/start.js +1 -0
- package/dist/vercel/client/start.js.map +1 -1
- package/dist/vercel/client/streamText.d.ts +1 -2
- package/dist/vercel/client/streamText.d.ts.map +1 -1
- package/dist/vercel/client/streamText.js +79 -22
- package/dist/vercel/client/streamText.js.map +1 -1
- package/dist/vercel/client/streaming.d.ts +326 -305
- package/dist/vercel/client/streaming.d.ts.map +1 -1
- package/dist/vercel/client/streaming.js +140 -25
- package/dist/vercel/client/streaming.js.map +1 -1
- package/dist/vercel/client/types.d.ts +2 -0
- package/dist/vercel/client/types.d.ts.map +1 -1
- package/dist/vercel/client/types.js.map +1 -1
- package/dist/vercel/client/utils.d.ts +9 -5
- package/dist/vercel/client/utils.d.ts.map +1 -1
- package/dist/vercel/client/utils.js +13 -13
- package/dist/vercel/client/utils.js.map +1 -1
- package/dist/vercel/index.d.ts +91 -90
- package/dist/vercel/index.d.ts.map +1 -1
- package/dist/vercel/index.js +2 -0
- package/dist/vercel/index.js.map +1 -1
- package/package.json +16 -9
- package/src/client/messages.ts +12 -0
- package/src/component/_generated/component.ts +10 -0
- package/src/component/messages.test.ts +404 -0
- package/src/component/messages.ts +94 -12
- package/src/component/streams.test.ts +54 -1
- package/src/component/streams.ts +20 -17
- package/src/component/threads.test.ts +39 -0
- package/src/component/threads.ts +1 -0
- package/src/component/vector/tables.ts +1 -1
- package/src/errors.test.ts +76 -0
- package/src/errors.ts +113 -0
- package/src/vercel/client/deltaFlush.test.ts +106 -0
- package/src/vercel/client/index.test.ts +47 -0
- package/src/vercel/client/messages.ts +20 -0
- package/src/vercel/client/search.test.ts +183 -8
- package/src/vercel/client/search.ts +46 -12
- package/src/vercel/client/start.ts +1 -0
- package/src/vercel/client/streamText.test.ts +577 -18
- package/src/vercel/client/streamText.ts +84 -32
- package/src/vercel/client/streaming.integration.test.ts +40 -3
- package/src/vercel/client/streaming.test.ts +101 -7
- package/src/vercel/client/streaming.throttle.test.ts +144 -0
- package/src/vercel/client/streaming.ts +161 -25
- package/src/vercel/client/types.ts +2 -0
- package/src/vercel/client/utils.test.ts +97 -0
- package/src/vercel/client/utils.ts +15 -15
- package/src/vercel/index.ts +2 -0
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
fetchContextMessages,
|
|
21
21
|
filterOutOrphanedToolMessages,
|
|
22
22
|
getPromptArray,
|
|
23
|
+
trimIncompleteOldestOrder,
|
|
23
24
|
} from "./search.js";
|
|
24
25
|
import { components, initConvexTest } from "./setup.test.js";
|
|
25
26
|
import { createThread } from "../../client/threads.js";
|
|
@@ -31,13 +32,14 @@ const createMockMessageDoc = (
|
|
|
31
32
|
role: "user" | "assistant" | "tool" | "system",
|
|
32
33
|
content: any,
|
|
33
34
|
order: number = 1,
|
|
35
|
+
stepOrder: number = 0,
|
|
34
36
|
): MessageDoc => ({
|
|
35
37
|
_id: id,
|
|
36
38
|
_creationTime: Date.now(),
|
|
37
39
|
userId: "test-user",
|
|
38
40
|
threadId: "test-thread",
|
|
39
41
|
order,
|
|
40
|
-
stepOrder
|
|
42
|
+
stepOrder,
|
|
41
43
|
status: "success",
|
|
42
44
|
tool: false,
|
|
43
45
|
message: { role, content },
|
|
@@ -127,6 +129,27 @@ describe("search.ts", () => {
|
|
|
127
129
|
});
|
|
128
130
|
});
|
|
129
131
|
|
|
132
|
+
describe("trimIncompleteOldestOrder", () => {
|
|
133
|
+
it("keeps a window that begins at the start of an order", () => {
|
|
134
|
+
const messages = [
|
|
135
|
+
createMockMessageDoc("user", "user", "Question", 1, 0),
|
|
136
|
+
createMockMessageDoc("assistant", "assistant", "Answer", 1, 1),
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
expect(trimIncompleteOldestOrder(messages)).toEqual(messages);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("omits only the incomplete oldest order", () => {
|
|
143
|
+
const messages = [
|
|
144
|
+
createMockMessageDoc("partial", "assistant", "Partial", 1, 2),
|
|
145
|
+
createMockMessageDoc("next-user", "user", "Next question", 2, 0),
|
|
146
|
+
createMockMessageDoc("next-answer", "assistant", "Next answer", 2, 1),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
expect(trimIncompleteOldestOrder(messages)).toEqual(messages.slice(1));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
130
153
|
describe("filterOutOrphanedToolMessages", () => {
|
|
131
154
|
it("should keep non-tool messages", () => {
|
|
132
155
|
const messages: MessageDoc[] = [
|
|
@@ -442,6 +465,7 @@ describe("search.ts", () => {
|
|
|
442
465
|
expect(result.length).toBe(2);
|
|
443
466
|
expect(result[0]._id).toBe("1"); // Should be reversed back to asc order
|
|
444
467
|
expect(result[1]._id).toBe("2");
|
|
468
|
+
expect(mockCtx.runQuery).toHaveBeenCalledTimes(1);
|
|
445
469
|
});
|
|
446
470
|
|
|
447
471
|
it("should skip recent messages when recentMessages is 0", async () => {
|
|
@@ -560,11 +584,13 @@ describe("search.ts", () => {
|
|
|
560
584
|
_id: "ctx1",
|
|
561
585
|
message: { role: "user", content: "Context message 1" },
|
|
562
586
|
order: 1,
|
|
587
|
+
stepOrder: 0,
|
|
563
588
|
} as MessageDoc,
|
|
564
589
|
{
|
|
565
590
|
_id: "ctx2",
|
|
566
591
|
message: { role: "assistant", content: "Context response 1" },
|
|
567
592
|
order: 2,
|
|
593
|
+
stepOrder: 0,
|
|
568
594
|
} as MessageDoc,
|
|
569
595
|
];
|
|
570
596
|
|
|
@@ -590,6 +616,143 @@ describe("search.ts", () => {
|
|
|
590
616
|
});
|
|
591
617
|
});
|
|
592
618
|
|
|
619
|
+
it("should complete an order truncated by the recent messages limit", async () => {
|
|
620
|
+
const toolCallId = "call_issue_309";
|
|
621
|
+
vi.mocked(mockCtx.runQuery)
|
|
622
|
+
.mockResolvedValueOnce({
|
|
623
|
+
page: [
|
|
624
|
+
createMockMessageDoc(
|
|
625
|
+
"assistant-final",
|
|
626
|
+
"assistant",
|
|
627
|
+
[{ type: "text", text: "The result is 72." }],
|
|
628
|
+
1,
|
|
629
|
+
3,
|
|
630
|
+
),
|
|
631
|
+
createMockMessageDoc(
|
|
632
|
+
"tool-result",
|
|
633
|
+
"tool",
|
|
634
|
+
[
|
|
635
|
+
{
|
|
636
|
+
type: "tool-result",
|
|
637
|
+
toolCallId,
|
|
638
|
+
toolName: "getWeather",
|
|
639
|
+
output: {
|
|
640
|
+
type: "json",
|
|
641
|
+
value: { temperature: 72 },
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
1,
|
|
646
|
+
2,
|
|
647
|
+
),
|
|
648
|
+
createMockMessageDoc(
|
|
649
|
+
"assistant-tool-call",
|
|
650
|
+
"assistant",
|
|
651
|
+
[
|
|
652
|
+
{
|
|
653
|
+
type: "tool-call",
|
|
654
|
+
toolCallId,
|
|
655
|
+
toolName: "getWeather",
|
|
656
|
+
input: { city: "New York" },
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
1,
|
|
660
|
+
1,
|
|
661
|
+
),
|
|
662
|
+
],
|
|
663
|
+
continueCursor: "complete-order",
|
|
664
|
+
isDone: false,
|
|
665
|
+
})
|
|
666
|
+
.mockResolvedValueOnce({
|
|
667
|
+
page: [
|
|
668
|
+
createMockMessageDoc(
|
|
669
|
+
"user",
|
|
670
|
+
"user",
|
|
671
|
+
"What is the weather?",
|
|
672
|
+
1,
|
|
673
|
+
0,
|
|
674
|
+
),
|
|
675
|
+
],
|
|
676
|
+
continueCursor: "done",
|
|
677
|
+
isDone: true,
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
const result = await fetchContextWithPrompt(
|
|
681
|
+
mockCtx,
|
|
682
|
+
components.agent,
|
|
683
|
+
{
|
|
684
|
+
...baseArgs,
|
|
685
|
+
prompt: "What should I do next?",
|
|
686
|
+
messages: undefined,
|
|
687
|
+
promptMessageId: undefined,
|
|
688
|
+
contextOptions: { recentMessages: 3 },
|
|
689
|
+
},
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
expect(result.messages.map((message) => message.role)).toEqual([
|
|
693
|
+
"user",
|
|
694
|
+
"assistant",
|
|
695
|
+
"tool",
|
|
696
|
+
"assistant",
|
|
697
|
+
"user",
|
|
698
|
+
]);
|
|
699
|
+
expect(mockCtx.runQuery).toHaveBeenNthCalledWith(2, expect.anything(), {
|
|
700
|
+
threadId: "thread123",
|
|
701
|
+
excludeToolMessages: undefined,
|
|
702
|
+
paginationOpts: { numItems: 1, cursor: "complete-order" },
|
|
703
|
+
upToAndIncludingMessageId: undefined,
|
|
704
|
+
order: "desc",
|
|
705
|
+
statuses: ["success"],
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it("should omit an order when its boundary cannot be fetched", async () => {
|
|
710
|
+
vi.mocked(mockCtx.runQuery)
|
|
711
|
+
.mockResolvedValueOnce({
|
|
712
|
+
page: [
|
|
713
|
+
createMockMessageDoc(
|
|
714
|
+
"assistant-final",
|
|
715
|
+
"assistant",
|
|
716
|
+
"Partial answer",
|
|
717
|
+
1,
|
|
718
|
+
2,
|
|
719
|
+
),
|
|
720
|
+
],
|
|
721
|
+
continueCursor: "complete-order",
|
|
722
|
+
isDone: false,
|
|
723
|
+
})
|
|
724
|
+
.mockResolvedValueOnce({
|
|
725
|
+
page: [
|
|
726
|
+
createMockMessageDoc(
|
|
727
|
+
"assistant-step",
|
|
728
|
+
"assistant",
|
|
729
|
+
"Still partial",
|
|
730
|
+
1,
|
|
731
|
+
1,
|
|
732
|
+
),
|
|
733
|
+
],
|
|
734
|
+
continueCursor: "done",
|
|
735
|
+
isDone: true,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const result = await fetchContextWithPrompt(
|
|
739
|
+
mockCtx,
|
|
740
|
+
components.agent,
|
|
741
|
+
{
|
|
742
|
+
...baseArgs,
|
|
743
|
+
prompt: "New prompt",
|
|
744
|
+
messages: undefined,
|
|
745
|
+
promptMessageId: undefined,
|
|
746
|
+
contextOptions: { recentMessages: 1 },
|
|
747
|
+
},
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
expect(result.messages).toEqual([
|
|
751
|
+
{ role: "user", content: "New prompt" },
|
|
752
|
+
]);
|
|
753
|
+
expect(mockCtx.runQuery).toHaveBeenCalledTimes(2);
|
|
754
|
+
});
|
|
755
|
+
|
|
593
756
|
it("should handle input messages correctly", async () => {
|
|
594
757
|
const inputMessages: ModelMessage[] = [
|
|
595
758
|
{ role: "user", content: "Input message 1" },
|
|
@@ -618,16 +781,19 @@ describe("search.ts", () => {
|
|
|
618
781
|
_id: "msg1",
|
|
619
782
|
message: { role: "user", content: "Before prompt" },
|
|
620
783
|
order: 1,
|
|
784
|
+
stepOrder: 0,
|
|
621
785
|
} as MessageDoc,
|
|
622
786
|
{
|
|
623
787
|
_id: "prompt-msg",
|
|
624
788
|
message: { role: "user", content: "Original prompt" },
|
|
625
789
|
order: 2,
|
|
790
|
+
stepOrder: 0,
|
|
626
791
|
} as MessageDoc,
|
|
627
792
|
{
|
|
628
793
|
_id: "msg3",
|
|
629
794
|
message: { role: "assistant", content: "After prompt" },
|
|
630
795
|
order: 3,
|
|
796
|
+
stepOrder: 0,
|
|
631
797
|
} as MessageDoc,
|
|
632
798
|
];
|
|
633
799
|
|
|
@@ -659,16 +825,19 @@ describe("search.ts", () => {
|
|
|
659
825
|
_id: "msg1",
|
|
660
826
|
message: { role: "user", content: "Before prompt" },
|
|
661
827
|
order: 1,
|
|
828
|
+
stepOrder: 0,
|
|
662
829
|
} as MessageDoc,
|
|
663
830
|
{
|
|
664
831
|
_id: "prompt-msg",
|
|
665
832
|
message: { role: "user", content: "Original prompt" },
|
|
666
833
|
order: 2,
|
|
834
|
+
stepOrder: 0,
|
|
667
835
|
} as MessageDoc,
|
|
668
836
|
{
|
|
669
837
|
_id: "msg3",
|
|
670
838
|
message: { role: "assistant", content: "After prompt" },
|
|
671
839
|
order: 3,
|
|
840
|
+
stepOrder: 0,
|
|
672
841
|
} as MessageDoc,
|
|
673
842
|
];
|
|
674
843
|
|
|
@@ -696,16 +865,19 @@ describe("search.ts", () => {
|
|
|
696
865
|
_id: "ctx1",
|
|
697
866
|
message: { role: "user", content: "Context 1" },
|
|
698
867
|
order: 1,
|
|
868
|
+
stepOrder: 0,
|
|
699
869
|
} as MessageDoc,
|
|
700
870
|
{
|
|
701
871
|
_id: "prompt-msg",
|
|
702
872
|
message: { role: "user", content: "Prompt" },
|
|
703
873
|
order: 3,
|
|
874
|
+
stepOrder: 0,
|
|
704
875
|
} as MessageDoc,
|
|
705
876
|
{
|
|
706
877
|
_id: "ctx2",
|
|
707
878
|
message: { role: "assistant", content: "Context 2" },
|
|
708
879
|
order: 5,
|
|
880
|
+
stepOrder: 0,
|
|
709
881
|
} as MessageDoc,
|
|
710
882
|
];
|
|
711
883
|
|
|
@@ -864,10 +1036,10 @@ describe("search.ts", () => {
|
|
|
864
1036
|
});
|
|
865
1037
|
});
|
|
866
1038
|
|
|
867
|
-
it("should
|
|
1039
|
+
it("should extend recentMessages to complete an order", async () => {
|
|
868
1040
|
const threadId = await createTestThread("user999");
|
|
869
1041
|
|
|
870
|
-
//
|
|
1042
|
+
// Start with a two-message window that splits the previous order.
|
|
871
1043
|
await createTestMessages(threadId, "user999", [
|
|
872
1044
|
{ role: "user", content: "Message 1", order: 1 },
|
|
873
1045
|
{ role: "assistant", content: "Response 1", order: 2 },
|
|
@@ -883,13 +1055,16 @@ describe("search.ts", () => {
|
|
|
883
1055
|
prompt: "New prompt",
|
|
884
1056
|
messages: undefined,
|
|
885
1057
|
promptMessageId: undefined,
|
|
886
|
-
contextOptions: { recentMessages: 2 },
|
|
1058
|
+
contextOptions: { recentMessages: 2 },
|
|
887
1059
|
});
|
|
888
1060
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
expect(result.messages
|
|
892
|
-
expect(result.messages[
|
|
1061
|
+
// The limit splits the previous order after its user message, so the
|
|
1062
|
+
// context extends backward to include the whole order.
|
|
1063
|
+
expect(result.messages).toHaveLength(4);
|
|
1064
|
+
expect(result.messages[0].content).toBe("Message 2");
|
|
1065
|
+
expect(result.messages[1].content).toBe("Response 2");
|
|
1066
|
+
expect(result.messages[2].content).toBe("Message 3");
|
|
1067
|
+
expect(result.messages[3]).toEqual({
|
|
893
1068
|
role: "user",
|
|
894
1069
|
content: "New prompt",
|
|
895
1070
|
});
|
|
@@ -36,10 +36,24 @@ import {
|
|
|
36
36
|
} from "../mapping.js";
|
|
37
37
|
|
|
38
38
|
const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
|
|
39
|
+
// Bound the rare boundary-extension query; incomplete orders are trimmed below.
|
|
40
|
+
const MAX_ORDER_COMPLETION_MESSAGES = 1_000;
|
|
39
41
|
// 10k characters should be more than enough for most cases, and stays under
|
|
40
42
|
// the 8k token limit for some models.
|
|
41
43
|
const MAX_EMBEDDING_TEXT_LENGTH = 10_000;
|
|
42
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Omit the oldest order when a descending pagination window split it.
|
|
47
|
+
* Expects the messages to be sorted in ascending order.
|
|
48
|
+
*/
|
|
49
|
+
export function trimIncompleteOldestOrder(docs: MessageDoc[]) {
|
|
50
|
+
const oldest = docs[0];
|
|
51
|
+
if (!oldest || oldest.stepOrder === 0) {
|
|
52
|
+
return docs;
|
|
53
|
+
}
|
|
54
|
+
return docs.filter((doc) => doc.order !== oldest.order);
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
export type GetEmbedding = (text: string) => Promise<
|
|
44
58
|
| {
|
|
45
59
|
embedding: number[];
|
|
@@ -133,25 +147,45 @@ export async function fetchRecentAndSearchMessages(
|
|
|
133
147
|
let included: Set<string> | undefined;
|
|
134
148
|
let recentMessages: MessageDoc[] = [];
|
|
135
149
|
let searchMessages: MessageDoc[] = [];
|
|
150
|
+
const threadId = args.threadId;
|
|
136
151
|
const targetMessageId =
|
|
137
152
|
args.targetMessageId ?? args.upToAndIncludingMessageId;
|
|
138
|
-
if (
|
|
139
|
-
const
|
|
140
|
-
component.messages.listMessagesByThreadId,
|
|
141
|
-
|
|
142
|
-
threadId: args.threadId,
|
|
153
|
+
if (threadId && opts.recentMessages !== 0) {
|
|
154
|
+
const fetchRecentPage = (numItems: number, cursor: string | null) =>
|
|
155
|
+
ctx.runQuery(component.messages.listMessagesByThreadId, {
|
|
156
|
+
threadId,
|
|
143
157
|
excludeToolMessages: opts.excludeToolMessages,
|
|
144
|
-
paginationOpts: {
|
|
145
|
-
numItems: opts.recentMessages ?? DEFAULT_RECENT_MESSAGES,
|
|
146
|
-
cursor: null,
|
|
147
|
-
},
|
|
158
|
+
paginationOpts: { numItems, cursor },
|
|
148
159
|
upToAndIncludingMessageId: targetMessageId,
|
|
149
160
|
order: "desc",
|
|
150
161
|
statuses: ["success"],
|
|
151
|
-
}
|
|
162
|
+
});
|
|
163
|
+
const firstPage = await fetchRecentPage(
|
|
164
|
+
opts.recentMessages ?? DEFAULT_RECENT_MESSAGES,
|
|
165
|
+
null,
|
|
152
166
|
);
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
let page = firstPage.page;
|
|
168
|
+
const oldest = page.at(-1);
|
|
169
|
+
if (
|
|
170
|
+
oldest &&
|
|
171
|
+
oldest.stepOrder > 0 &&
|
|
172
|
+
!firstPage.isDone &&
|
|
173
|
+
firstPage.continueCursor
|
|
174
|
+
) {
|
|
175
|
+
const completionPage = await fetchRecentPage(
|
|
176
|
+
Math.min(oldest.stepOrder, MAX_ORDER_COMPLETION_MESSAGES),
|
|
177
|
+
firstPage.continueCursor,
|
|
178
|
+
);
|
|
179
|
+
page = [
|
|
180
|
+
...page,
|
|
181
|
+
...completionPage.page.filter(
|
|
182
|
+
(message) => message.order === oldest.order,
|
|
183
|
+
),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
const retained = trimIncompleteOldestOrder(sorted(page));
|
|
187
|
+
included = new Set(retained.map((m) => m._id));
|
|
188
|
+
recentMessages = filterOutOrphanedToolMessages(retained);
|
|
155
189
|
}
|
|
156
190
|
if (
|
|
157
191
|
(opts.searchOptions?.textSearch || opts.searchOptions?.vectorSearch) &&
|