@convex-dev/agent 0.3.2 → 0.6.0-alpha.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/dist/UIMessages.d.ts +3 -2
- package/dist/UIMessages.d.ts.map +1 -1
- package/dist/UIMessages.js +100 -19
- package/dist/UIMessages.js.map +1 -1
- package/dist/client/createTool.d.ts +129 -25
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +65 -8
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +560 -35
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +4 -2
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +462 -37
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +26 -7
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +114 -9
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/mockModel.d.ts +17 -17
- package/dist/client/mockModel.d.ts.map +1 -1
- package/dist/client/mockModel.js +8 -6
- package/dist/client/mockModel.js.map +1 -1
- package/dist/client/saveInputMessages.d.ts +1 -1
- package/dist/client/saveInputMessages.d.ts.map +1 -1
- package/dist/client/saveInputMessages.js +1 -1
- package/dist/client/saveInputMessages.js.map +1 -1
- package/dist/client/search.d.ts +132 -14
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +66 -19
- package/dist/client/search.js.map +1 -1
- package/dist/client/streamText.d.ts +4 -4
- package/dist/client/streamText.d.ts.map +1 -1
- package/dist/client/streamText.js.map +1 -1
- package/dist/client/streaming.d.ts +1491 -87
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/types.d.ts +26 -6
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +862 -19
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/files.d.ts +12 -6
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +10 -2
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +1247 -92
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/schema.d.ts +2823 -217
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +3 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.js +1 -1
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +10 -10
- package/dist/component/vector/index.d.ts +1 -1
- package/dist/deltas.d.ts.map +1 -1
- package/dist/deltas.js +40 -5
- package/dist/deltas.js.map +1 -1
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +117 -25
- package/dist/mapping.js.map +1 -1
- package/dist/react/useThreadMessages.d.ts.map +1 -1
- package/dist/react/useThreadMessages.js +17 -9
- package/dist/react/useThreadMessages.js.map +1 -1
- package/dist/shared.d.ts +2 -2
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +12196 -624
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +152 -9
- package/dist/validators.js.map +1 -1
- package/package.json +24 -22
- package/src/UIMessages.combineUIMessages.test.ts +239 -0
- package/src/UIMessages.ts +184 -66
- package/src/client/createTool.ts +291 -67
- package/src/client/files.ts +4 -2
- package/src/client/index.test.ts +1 -0
- package/src/client/index.ts +46 -25
- package/src/client/mockModel.ts +36 -34
- package/src/client/saveInputMessages.ts +2 -2
- package/src/client/search.test.ts +166 -0
- package/src/client/search.ts +121 -38
- package/src/client/streamText.ts +5 -5
- package/src/client/types.ts +31 -18
- package/src/component/_generated/component.ts +2329 -338
- package/src/component/files.ts +11 -2
- package/src/component/messages.test.ts +7 -7
- package/src/component/schema.ts +3 -1
- package/src/component/setup.test.ts +7 -0
- package/src/component/streams.ts +1 -1
- package/src/deltas.test.ts +90 -0
- package/src/deltas.ts +47 -8
- package/src/fromUIMessages.test.ts +32 -27
- package/src/mapping.test.ts +48 -0
- package/src/mapping.ts +234 -142
- package/src/react/useThreadMessages.ts +21 -9
- package/src/shared.ts +2 -0
- package/src/toUIMessages.test.ts +539 -1
- package/src/validators.ts +179 -20
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
package/src/toUIMessages.test.ts
CHANGED
|
@@ -90,6 +90,7 @@ describe("toUIMessages", () => {
|
|
|
90
90
|
type: "tool-call",
|
|
91
91
|
toolName: "myTool",
|
|
92
92
|
toolCallId: "call1",
|
|
93
|
+
input: "an arg",
|
|
93
94
|
args: "an arg",
|
|
94
95
|
},
|
|
95
96
|
],
|
|
@@ -219,6 +220,7 @@ describe("toUIMessages", () => {
|
|
|
219
220
|
},
|
|
220
221
|
{
|
|
221
222
|
type: "tool-call",
|
|
223
|
+
input: "What's the meaning of life?",
|
|
222
224
|
args: "What's the meaning of life?",
|
|
223
225
|
toolCallId: "call1",
|
|
224
226
|
toolName: "myTool",
|
|
@@ -310,6 +312,7 @@ describe("toUIMessages", () => {
|
|
|
310
312
|
type: "tool-call",
|
|
311
313
|
toolName: "myTool",
|
|
312
314
|
toolCallId: "call1",
|
|
315
|
+
input: { query: "test" },
|
|
313
316
|
args: { query: "test" },
|
|
314
317
|
},
|
|
315
318
|
],
|
|
@@ -356,6 +359,7 @@ describe("toUIMessages", () => {
|
|
|
356
359
|
type: "tool-call",
|
|
357
360
|
toolName: "myTool",
|
|
358
361
|
toolCallId: "call1",
|
|
362
|
+
input: "hi",
|
|
359
363
|
args: "hi",
|
|
360
364
|
},
|
|
361
365
|
],
|
|
@@ -387,6 +391,7 @@ describe("toUIMessages", () => {
|
|
|
387
391
|
type: "tool-call",
|
|
388
392
|
toolName: "myTool",
|
|
389
393
|
toolCallId: "call1",
|
|
394
|
+
input: "",
|
|
390
395
|
args: "",
|
|
391
396
|
},
|
|
392
397
|
],
|
|
@@ -448,6 +453,7 @@ describe("toUIMessages", () => {
|
|
|
448
453
|
type: "tool-call",
|
|
449
454
|
toolName: "calculator",
|
|
450
455
|
toolCallId: "call1",
|
|
456
|
+
input: { operation: "add", a: 2, b: 3 },
|
|
451
457
|
args: { operation: "add", a: 2, b: 3 },
|
|
452
458
|
},
|
|
453
459
|
{
|
|
@@ -490,6 +496,7 @@ describe("toUIMessages", () => {
|
|
|
490
496
|
type: "tool-call",
|
|
491
497
|
toolName: "calculator",
|
|
492
498
|
toolCallId: "call1",
|
|
499
|
+
input: { operation: "add", a: 1, b: 2 },
|
|
493
500
|
args: { operation: "add", a: 1, b: 2 },
|
|
494
501
|
},
|
|
495
502
|
],
|
|
@@ -539,7 +546,7 @@ describe("toUIMessages", () => {
|
|
|
539
546
|
);
|
|
540
547
|
expect(toolCallParts).toHaveLength(0);
|
|
541
548
|
});
|
|
542
|
-
it("sets text field correctly
|
|
549
|
+
it("sets text field correctly in multi-step tool-call + final-text flow", () => {
|
|
543
550
|
const messages = [
|
|
544
551
|
baseMessageDoc({
|
|
545
552
|
text: "what time is it in paris?",
|
|
@@ -561,6 +568,9 @@ describe("toUIMessages", () => {
|
|
|
561
568
|
text: "**Finding the Time**\n\nI've pinpointed the core task: obtaining the current time in Paris. It involves using the `dateTime` tool. I've identified \"Europe/Paris\" as the necessary timezone identifier to provide to the tool. My next step is to test the tool.\n\n\n",
|
|
562
569
|
},
|
|
563
570
|
{
|
|
571
|
+
input: {
|
|
572
|
+
timezone: "Europe/Paris",
|
|
573
|
+
},
|
|
564
574
|
args: {
|
|
565
575
|
timezone: "Europe/Paris",
|
|
566
576
|
},
|
|
@@ -690,6 +700,7 @@ describe("toUIMessages", () => {
|
|
|
690
700
|
type: "tool-call",
|
|
691
701
|
toolName: "calculator",
|
|
692
702
|
toolCallId: "call1",
|
|
703
|
+
input: { operation: "add", a: 40, b: 2 },
|
|
693
704
|
args: { operation: "add", a: 40, b: 2 },
|
|
694
705
|
},
|
|
695
706
|
],
|
|
@@ -728,4 +739,531 @@ describe("toUIMessages", () => {
|
|
|
728
739
|
expect(textParts).toHaveLength(1);
|
|
729
740
|
expect(textParts[0].text).toBe("The result is 42.");
|
|
730
741
|
});
|
|
742
|
+
|
|
743
|
+
it("shows output-error state when tool result has isError: true (issue #162)", () => {
|
|
744
|
+
const messages = [
|
|
745
|
+
// Tool call
|
|
746
|
+
baseMessageDoc({
|
|
747
|
+
_id: "msg1",
|
|
748
|
+
order: 1,
|
|
749
|
+
stepOrder: 1,
|
|
750
|
+
tool: true,
|
|
751
|
+
message: {
|
|
752
|
+
role: "assistant",
|
|
753
|
+
content: [
|
|
754
|
+
{
|
|
755
|
+
type: "tool-call",
|
|
756
|
+
toolName: "generateImage",
|
|
757
|
+
toolCallId: "call1",
|
|
758
|
+
input: { id: "invalid-id" },
|
|
759
|
+
args: { id: "invalid-id" },
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
},
|
|
763
|
+
}),
|
|
764
|
+
// Tool result with error
|
|
765
|
+
baseMessageDoc({
|
|
766
|
+
_id: "msg2",
|
|
767
|
+
order: 1,
|
|
768
|
+
stepOrder: 2,
|
|
769
|
+
tool: true,
|
|
770
|
+
message: {
|
|
771
|
+
role: "tool",
|
|
772
|
+
content: [
|
|
773
|
+
{
|
|
774
|
+
type: "tool-result",
|
|
775
|
+
toolCallId: "call1",
|
|
776
|
+
toolName: "generateImage",
|
|
777
|
+
output: {
|
|
778
|
+
type: "text",
|
|
779
|
+
value:
|
|
780
|
+
'ArgumentValidationError: Value does not match validator.\nPath: .id\nValue: "invalid-id"\nValidator: v.id("images")',
|
|
781
|
+
},
|
|
782
|
+
isError: true,
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
},
|
|
786
|
+
}),
|
|
787
|
+
];
|
|
788
|
+
|
|
789
|
+
const uiMessages = toUIMessages(messages);
|
|
790
|
+
|
|
791
|
+
expect(uiMessages).toHaveLength(1);
|
|
792
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
793
|
+
|
|
794
|
+
const toolParts = uiMessages[0].parts.filter(
|
|
795
|
+
(p) => p.type === "tool-generateImage",
|
|
796
|
+
);
|
|
797
|
+
expect(toolParts).toHaveLength(1);
|
|
798
|
+
|
|
799
|
+
const toolPart = toolParts[0] as any;
|
|
800
|
+
expect(toolPart.toolCallId).toBe("call1");
|
|
801
|
+
// Should show output-error, not output-available
|
|
802
|
+
expect(toolPart.state).toBe("output-error");
|
|
803
|
+
expect(toolPart.output).toContain("ArgumentValidationError");
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("shows output-error when tool result has isError: true without tool call present (issue #162)", () => {
|
|
807
|
+
// This simulates the case where the tool-call message wasn't saved
|
|
808
|
+
const messages = [
|
|
809
|
+
baseMessageDoc({
|
|
810
|
+
_id: "msg1",
|
|
811
|
+
order: 1,
|
|
812
|
+
stepOrder: 1,
|
|
813
|
+
tool: true,
|
|
814
|
+
message: {
|
|
815
|
+
role: "tool",
|
|
816
|
+
content: [
|
|
817
|
+
{
|
|
818
|
+
type: "tool-result",
|
|
819
|
+
toolCallId: "call1",
|
|
820
|
+
toolName: "generateImage",
|
|
821
|
+
output: {
|
|
822
|
+
type: "text",
|
|
823
|
+
value: "Error: Something went wrong",
|
|
824
|
+
},
|
|
825
|
+
isError: true,
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
}),
|
|
830
|
+
];
|
|
831
|
+
|
|
832
|
+
const uiMessages = toUIMessages(messages);
|
|
833
|
+
|
|
834
|
+
expect(uiMessages).toHaveLength(1);
|
|
835
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
836
|
+
|
|
837
|
+
const toolParts = uiMessages[0].parts.filter(
|
|
838
|
+
(p) => p.type === "tool-generateImage",
|
|
839
|
+
);
|
|
840
|
+
expect(toolParts).toHaveLength(1);
|
|
841
|
+
|
|
842
|
+
const toolPart = toolParts[0] as any;
|
|
843
|
+
expect(toolPart.state).toBe("output-error");
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
describe("userId preservation", () => {
|
|
847
|
+
it("preserves userId in user messages", () => {
|
|
848
|
+
const messages = [
|
|
849
|
+
baseMessageDoc({
|
|
850
|
+
userId: "user123",
|
|
851
|
+
message: {
|
|
852
|
+
role: "user",
|
|
853
|
+
content: "Hello!",
|
|
854
|
+
},
|
|
855
|
+
text: "Hello!",
|
|
856
|
+
}),
|
|
857
|
+
];
|
|
858
|
+
const uiMessages = toUIMessages(messages);
|
|
859
|
+
expect(uiMessages).toHaveLength(1);
|
|
860
|
+
expect(uiMessages[0].role).toBe("user");
|
|
861
|
+
expect(uiMessages[0].userId).toBe("user123");
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it("preserves userId in system messages", () => {
|
|
865
|
+
const messages = [
|
|
866
|
+
baseMessageDoc({
|
|
867
|
+
userId: "user456",
|
|
868
|
+
message: {
|
|
869
|
+
role: "system",
|
|
870
|
+
content: "System prompt",
|
|
871
|
+
},
|
|
872
|
+
text: "System prompt",
|
|
873
|
+
}),
|
|
874
|
+
];
|
|
875
|
+
const uiMessages = toUIMessages(messages);
|
|
876
|
+
expect(uiMessages).toHaveLength(1);
|
|
877
|
+
expect(uiMessages[0].role).toBe("system");
|
|
878
|
+
expect(uiMessages[0].userId).toBe("user456");
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("preserves userId in assistant messages", () => {
|
|
882
|
+
const messages = [
|
|
883
|
+
baseMessageDoc({
|
|
884
|
+
userId: "user789",
|
|
885
|
+
message: {
|
|
886
|
+
role: "assistant",
|
|
887
|
+
content: "Hi there!",
|
|
888
|
+
},
|
|
889
|
+
text: "Hi there!",
|
|
890
|
+
}),
|
|
891
|
+
];
|
|
892
|
+
const uiMessages = toUIMessages(messages);
|
|
893
|
+
expect(uiMessages).toHaveLength(1);
|
|
894
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
895
|
+
expect(uiMessages[0].userId).toBe("user789");
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("preserves userId from first message in grouped assistant messages", () => {
|
|
899
|
+
const messages = [
|
|
900
|
+
baseMessageDoc({
|
|
901
|
+
_id: "msg1",
|
|
902
|
+
userId: "userA",
|
|
903
|
+
order: 1,
|
|
904
|
+
stepOrder: 1,
|
|
905
|
+
tool: true,
|
|
906
|
+
message: {
|
|
907
|
+
role: "assistant",
|
|
908
|
+
content: [
|
|
909
|
+
{
|
|
910
|
+
type: "tool-call",
|
|
911
|
+
toolName: "myTool",
|
|
912
|
+
toolCallId: "call1",
|
|
913
|
+
input: {},
|
|
914
|
+
args: {},
|
|
915
|
+
},
|
|
916
|
+
],
|
|
917
|
+
},
|
|
918
|
+
text: "",
|
|
919
|
+
}),
|
|
920
|
+
baseMessageDoc({
|
|
921
|
+
_id: "msg2",
|
|
922
|
+
userId: "userA",
|
|
923
|
+
order: 1,
|
|
924
|
+
stepOrder: 2,
|
|
925
|
+
tool: true,
|
|
926
|
+
message: {
|
|
927
|
+
role: "tool",
|
|
928
|
+
content: [
|
|
929
|
+
{
|
|
930
|
+
type: "tool-result",
|
|
931
|
+
toolCallId: "call1",
|
|
932
|
+
toolName: "myTool",
|
|
933
|
+
output: { type: "text", value: "result" },
|
|
934
|
+
},
|
|
935
|
+
],
|
|
936
|
+
},
|
|
937
|
+
text: "",
|
|
938
|
+
}),
|
|
939
|
+
baseMessageDoc({
|
|
940
|
+
_id: "msg3",
|
|
941
|
+
userId: "userA",
|
|
942
|
+
order: 1,
|
|
943
|
+
stepOrder: 3,
|
|
944
|
+
message: {
|
|
945
|
+
role: "assistant",
|
|
946
|
+
content: "Done!",
|
|
947
|
+
},
|
|
948
|
+
text: "Done!",
|
|
949
|
+
}),
|
|
950
|
+
];
|
|
951
|
+
const uiMessages = toUIMessages(messages);
|
|
952
|
+
expect(uiMessages).toHaveLength(1);
|
|
953
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
954
|
+
expect(uiMessages[0].userId).toBe("userA");
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it("handles undefined userId gracefully", () => {
|
|
958
|
+
const messages = [
|
|
959
|
+
baseMessageDoc({
|
|
960
|
+
// No userId provided
|
|
961
|
+
message: {
|
|
962
|
+
role: "user",
|
|
963
|
+
content: "Hello!",
|
|
964
|
+
},
|
|
965
|
+
text: "Hello!",
|
|
966
|
+
}),
|
|
967
|
+
];
|
|
968
|
+
const uiMessages = toUIMessages(messages);
|
|
969
|
+
expect(uiMessages).toHaveLength(1);
|
|
970
|
+
expect(uiMessages[0].userId).toBeUndefined();
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
describe("tool approval workflow", () => {
|
|
975
|
+
it("sets state to approval-requested when tool-approval-request is present", () => {
|
|
976
|
+
const messages = [
|
|
977
|
+
baseMessageDoc({
|
|
978
|
+
_id: "msg1",
|
|
979
|
+
order: 1,
|
|
980
|
+
stepOrder: 1,
|
|
981
|
+
tool: true,
|
|
982
|
+
message: {
|
|
983
|
+
role: "assistant",
|
|
984
|
+
content: [
|
|
985
|
+
{
|
|
986
|
+
type: "tool-call",
|
|
987
|
+
toolName: "dangerousTool",
|
|
988
|
+
toolCallId: "call1",
|
|
989
|
+
input: { action: "delete" },
|
|
990
|
+
args: { action: "delete" },
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
type: "tool-approval-request",
|
|
994
|
+
approvalId: "approval1",
|
|
995
|
+
toolCallId: "call1",
|
|
996
|
+
},
|
|
997
|
+
],
|
|
998
|
+
},
|
|
999
|
+
}),
|
|
1000
|
+
];
|
|
1001
|
+
|
|
1002
|
+
const uiMessages = toUIMessages(messages);
|
|
1003
|
+
|
|
1004
|
+
expect(uiMessages).toHaveLength(1);
|
|
1005
|
+
const toolPart = uiMessages[0].parts.find(
|
|
1006
|
+
(p) => p.type === "tool-dangerousTool",
|
|
1007
|
+
) as any;
|
|
1008
|
+
expect(toolPart).toBeDefined();
|
|
1009
|
+
expect(toolPart.state).toBe("approval-requested");
|
|
1010
|
+
expect(toolPart.approval).toEqual({ id: "approval1" });
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it("sets state to approval-responded when tool-approval-response with approved: true", () => {
|
|
1014
|
+
const messages = [
|
|
1015
|
+
baseMessageDoc({
|
|
1016
|
+
_id: "msg1",
|
|
1017
|
+
order: 1,
|
|
1018
|
+
stepOrder: 1,
|
|
1019
|
+
tool: true,
|
|
1020
|
+
message: {
|
|
1021
|
+
role: "assistant",
|
|
1022
|
+
content: [
|
|
1023
|
+
{
|
|
1024
|
+
type: "tool-call",
|
|
1025
|
+
toolName: "dangerousTool",
|
|
1026
|
+
toolCallId: "call1",
|
|
1027
|
+
input: { action: "delete" },
|
|
1028
|
+
args: { action: "delete" },
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
type: "tool-approval-request",
|
|
1032
|
+
approvalId: "approval1",
|
|
1033
|
+
toolCallId: "call1",
|
|
1034
|
+
},
|
|
1035
|
+
],
|
|
1036
|
+
},
|
|
1037
|
+
}),
|
|
1038
|
+
baseMessageDoc({
|
|
1039
|
+
_id: "msg2",
|
|
1040
|
+
order: 1,
|
|
1041
|
+
stepOrder: 2,
|
|
1042
|
+
tool: true,
|
|
1043
|
+
message: {
|
|
1044
|
+
role: "tool",
|
|
1045
|
+
content: [
|
|
1046
|
+
{
|
|
1047
|
+
type: "tool-approval-response",
|
|
1048
|
+
approvalId: "approval1",
|
|
1049
|
+
approved: true,
|
|
1050
|
+
reason: "User confirmed",
|
|
1051
|
+
},
|
|
1052
|
+
],
|
|
1053
|
+
},
|
|
1054
|
+
}),
|
|
1055
|
+
];
|
|
1056
|
+
|
|
1057
|
+
const uiMessages = toUIMessages(messages);
|
|
1058
|
+
|
|
1059
|
+
expect(uiMessages).toHaveLength(1);
|
|
1060
|
+
const toolPart = uiMessages[0].parts.find(
|
|
1061
|
+
(p) => p.type === "tool-dangerousTool",
|
|
1062
|
+
) as any;
|
|
1063
|
+
expect(toolPart).toBeDefined();
|
|
1064
|
+
expect(toolPart.state).toBe("approval-responded");
|
|
1065
|
+
expect(toolPart.approval).toEqual({
|
|
1066
|
+
id: "approval1",
|
|
1067
|
+
approved: true,
|
|
1068
|
+
reason: "User confirmed",
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it("sets state to output-denied when tool-approval-response with approved: false", () => {
|
|
1073
|
+
const messages = [
|
|
1074
|
+
baseMessageDoc({
|
|
1075
|
+
_id: "msg1",
|
|
1076
|
+
order: 1,
|
|
1077
|
+
stepOrder: 1,
|
|
1078
|
+
tool: true,
|
|
1079
|
+
message: {
|
|
1080
|
+
role: "assistant",
|
|
1081
|
+
content: [
|
|
1082
|
+
{
|
|
1083
|
+
type: "tool-call",
|
|
1084
|
+
toolName: "dangerousTool",
|
|
1085
|
+
toolCallId: "call1",
|
|
1086
|
+
input: { action: "delete" },
|
|
1087
|
+
args: { action: "delete" },
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
type: "tool-approval-request",
|
|
1091
|
+
approvalId: "approval1",
|
|
1092
|
+
toolCallId: "call1",
|
|
1093
|
+
},
|
|
1094
|
+
],
|
|
1095
|
+
},
|
|
1096
|
+
}),
|
|
1097
|
+
baseMessageDoc({
|
|
1098
|
+
_id: "msg2",
|
|
1099
|
+
order: 1,
|
|
1100
|
+
stepOrder: 2,
|
|
1101
|
+
tool: true,
|
|
1102
|
+
message: {
|
|
1103
|
+
role: "tool",
|
|
1104
|
+
content: [
|
|
1105
|
+
{
|
|
1106
|
+
type: "tool-approval-response",
|
|
1107
|
+
approvalId: "approval1",
|
|
1108
|
+
approved: false,
|
|
1109
|
+
reason: "User declined the operation",
|
|
1110
|
+
},
|
|
1111
|
+
],
|
|
1112
|
+
},
|
|
1113
|
+
}),
|
|
1114
|
+
];
|
|
1115
|
+
|
|
1116
|
+
const uiMessages = toUIMessages(messages);
|
|
1117
|
+
|
|
1118
|
+
expect(uiMessages).toHaveLength(1);
|
|
1119
|
+
const toolPart = uiMessages[0].parts.find(
|
|
1120
|
+
(p) => p.type === "tool-dangerousTool",
|
|
1121
|
+
) as any;
|
|
1122
|
+
expect(toolPart).toBeDefined();
|
|
1123
|
+
expect(toolPart.state).toBe("output-denied");
|
|
1124
|
+
expect(toolPart.approval).toEqual({
|
|
1125
|
+
id: "approval1",
|
|
1126
|
+
approved: false,
|
|
1127
|
+
reason: "User declined the operation",
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it("sets state to output-denied when tool-result has execution-denied output", () => {
|
|
1132
|
+
const messages = [
|
|
1133
|
+
baseMessageDoc({
|
|
1134
|
+
_id: "msg1",
|
|
1135
|
+
order: 1,
|
|
1136
|
+
stepOrder: 1,
|
|
1137
|
+
tool: true,
|
|
1138
|
+
message: {
|
|
1139
|
+
role: "assistant",
|
|
1140
|
+
content: [
|
|
1141
|
+
{
|
|
1142
|
+
type: "tool-call",
|
|
1143
|
+
toolName: "dangerousTool",
|
|
1144
|
+
toolCallId: "call1",
|
|
1145
|
+
input: { action: "delete" },
|
|
1146
|
+
args: { action: "delete" },
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
},
|
|
1150
|
+
}),
|
|
1151
|
+
baseMessageDoc({
|
|
1152
|
+
_id: "msg2",
|
|
1153
|
+
order: 1,
|
|
1154
|
+
stepOrder: 2,
|
|
1155
|
+
tool: true,
|
|
1156
|
+
message: {
|
|
1157
|
+
role: "tool",
|
|
1158
|
+
content: [
|
|
1159
|
+
{
|
|
1160
|
+
type: "tool-result",
|
|
1161
|
+
toolCallId: "call1",
|
|
1162
|
+
toolName: "dangerousTool",
|
|
1163
|
+
output: {
|
|
1164
|
+
type: "execution-denied",
|
|
1165
|
+
reason: "Tool execution was denied by the user",
|
|
1166
|
+
},
|
|
1167
|
+
},
|
|
1168
|
+
],
|
|
1169
|
+
},
|
|
1170
|
+
}),
|
|
1171
|
+
];
|
|
1172
|
+
|
|
1173
|
+
const uiMessages = toUIMessages(messages);
|
|
1174
|
+
|
|
1175
|
+
expect(uiMessages).toHaveLength(1);
|
|
1176
|
+
const toolPart = uiMessages[0].parts.find(
|
|
1177
|
+
(p) => p.type === "tool-dangerousTool",
|
|
1178
|
+
) as any;
|
|
1179
|
+
expect(toolPart).toBeDefined();
|
|
1180
|
+
expect(toolPart.state).toBe("output-denied");
|
|
1181
|
+
expect(toolPart.approval).toEqual({
|
|
1182
|
+
id: "",
|
|
1183
|
+
approved: false,
|
|
1184
|
+
reason: "Tool execution was denied by the user",
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
it("handles full approval flow: request → approved → executed → output-available", () => {
|
|
1189
|
+
const messages = [
|
|
1190
|
+
baseMessageDoc({
|
|
1191
|
+
_id: "msg1",
|
|
1192
|
+
order: 1,
|
|
1193
|
+
stepOrder: 1,
|
|
1194
|
+
tool: true,
|
|
1195
|
+
message: {
|
|
1196
|
+
role: "assistant",
|
|
1197
|
+
content: [
|
|
1198
|
+
{
|
|
1199
|
+
type: "tool-call",
|
|
1200
|
+
toolName: "dangerousTool",
|
|
1201
|
+
toolCallId: "call1",
|
|
1202
|
+
input: { action: "delete" },
|
|
1203
|
+
args: { action: "delete" },
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
type: "tool-approval-request",
|
|
1207
|
+
approvalId: "approval1",
|
|
1208
|
+
toolCallId: "call1",
|
|
1209
|
+
},
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
}),
|
|
1213
|
+
baseMessageDoc({
|
|
1214
|
+
_id: "msg2",
|
|
1215
|
+
order: 1,
|
|
1216
|
+
stepOrder: 2,
|
|
1217
|
+
tool: true,
|
|
1218
|
+
message: {
|
|
1219
|
+
role: "tool",
|
|
1220
|
+
content: [
|
|
1221
|
+
{
|
|
1222
|
+
type: "tool-approval-response",
|
|
1223
|
+
approvalId: "approval1",
|
|
1224
|
+
approved: true,
|
|
1225
|
+
},
|
|
1226
|
+
],
|
|
1227
|
+
},
|
|
1228
|
+
}),
|
|
1229
|
+
baseMessageDoc({
|
|
1230
|
+
_id: "msg3",
|
|
1231
|
+
order: 1,
|
|
1232
|
+
stepOrder: 3,
|
|
1233
|
+
tool: true,
|
|
1234
|
+
message: {
|
|
1235
|
+
role: "tool",
|
|
1236
|
+
content: [
|
|
1237
|
+
{
|
|
1238
|
+
type: "tool-result",
|
|
1239
|
+
toolCallId: "call1",
|
|
1240
|
+
toolName: "dangerousTool",
|
|
1241
|
+
output: {
|
|
1242
|
+
type: "json",
|
|
1243
|
+
value: { deleted: true },
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
],
|
|
1247
|
+
},
|
|
1248
|
+
}),
|
|
1249
|
+
];
|
|
1250
|
+
|
|
1251
|
+
const uiMessages = toUIMessages(messages);
|
|
1252
|
+
|
|
1253
|
+
expect(uiMessages).toHaveLength(1);
|
|
1254
|
+
const toolPart = uiMessages[0].parts.find(
|
|
1255
|
+
(p) => p.type === "tool-dangerousTool",
|
|
1256
|
+
) as any;
|
|
1257
|
+
expect(toolPart).toBeDefined();
|
|
1258
|
+
// After tool-result, state should be output-available
|
|
1259
|
+
expect(toolPart.state).toBe("output-available");
|
|
1260
|
+
expect(toolPart.output).toEqual({ deleted: true });
|
|
1261
|
+
// approval should still be preserved from earlier
|
|
1262
|
+
expect(toolPart.approval).toEqual({
|
|
1263
|
+
id: "approval1",
|
|
1264
|
+
approved: true,
|
|
1265
|
+
reason: undefined,
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
731
1269
|
});
|