@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.
Files changed (99) hide show
  1. package/dist/UIMessages.d.ts +3 -2
  2. package/dist/UIMessages.d.ts.map +1 -1
  3. package/dist/UIMessages.js +100 -19
  4. package/dist/UIMessages.js.map +1 -1
  5. package/dist/client/createTool.d.ts +129 -25
  6. package/dist/client/createTool.d.ts.map +1 -1
  7. package/dist/client/createTool.js +65 -8
  8. package/dist/client/createTool.js.map +1 -1
  9. package/dist/client/definePlaygroundAPI.d.ts +560 -35
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/files.d.ts.map +1 -1
  12. package/dist/client/files.js +4 -2
  13. package/dist/client/files.js.map +1 -1
  14. package/dist/client/index.d.ts +462 -37
  15. package/dist/client/index.d.ts.map +1 -1
  16. package/dist/client/index.js +26 -7
  17. package/dist/client/index.js.map +1 -1
  18. package/dist/client/messages.d.ts +114 -9
  19. package/dist/client/messages.d.ts.map +1 -1
  20. package/dist/client/mockModel.d.ts +17 -17
  21. package/dist/client/mockModel.d.ts.map +1 -1
  22. package/dist/client/mockModel.js +8 -6
  23. package/dist/client/mockModel.js.map +1 -1
  24. package/dist/client/saveInputMessages.d.ts +1 -1
  25. package/dist/client/saveInputMessages.d.ts.map +1 -1
  26. package/dist/client/saveInputMessages.js +1 -1
  27. package/dist/client/saveInputMessages.js.map +1 -1
  28. package/dist/client/search.d.ts +132 -14
  29. package/dist/client/search.d.ts.map +1 -1
  30. package/dist/client/search.js +66 -19
  31. package/dist/client/search.js.map +1 -1
  32. package/dist/client/streamText.d.ts +4 -4
  33. package/dist/client/streamText.d.ts.map +1 -1
  34. package/dist/client/streamText.js.map +1 -1
  35. package/dist/client/streaming.d.ts +1491 -87
  36. package/dist/client/streaming.d.ts.map +1 -1
  37. package/dist/client/types.d.ts +26 -6
  38. package/dist/client/types.d.ts.map +1 -1
  39. package/dist/component/_generated/component.d.ts +862 -19
  40. package/dist/component/_generated/component.d.ts.map +1 -1
  41. package/dist/component/files.d.ts +12 -6
  42. package/dist/component/files.d.ts.map +1 -1
  43. package/dist/component/files.js +10 -2
  44. package/dist/component/files.js.map +1 -1
  45. package/dist/component/messages.d.ts +1247 -92
  46. package/dist/component/messages.d.ts.map +1 -1
  47. package/dist/component/schema.d.ts +2823 -217
  48. package/dist/component/schema.d.ts.map +1 -1
  49. package/dist/component/schema.js +3 -1
  50. package/dist/component/schema.js.map +1 -1
  51. package/dist/component/streams.js +1 -1
  52. package/dist/component/streams.js.map +1 -1
  53. package/dist/component/threads.d.ts +10 -10
  54. package/dist/component/vector/index.d.ts +1 -1
  55. package/dist/deltas.d.ts.map +1 -1
  56. package/dist/deltas.js +40 -5
  57. package/dist/deltas.js.map +1 -1
  58. package/dist/mapping.d.ts.map +1 -1
  59. package/dist/mapping.js +117 -25
  60. package/dist/mapping.js.map +1 -1
  61. package/dist/react/useThreadMessages.d.ts.map +1 -1
  62. package/dist/react/useThreadMessages.js +17 -9
  63. package/dist/react/useThreadMessages.js.map +1 -1
  64. package/dist/shared.d.ts +2 -2
  65. package/dist/shared.d.ts.map +1 -1
  66. package/dist/shared.js.map +1 -1
  67. package/dist/validators.d.ts +12196 -624
  68. package/dist/validators.d.ts.map +1 -1
  69. package/dist/validators.js +152 -9
  70. package/dist/validators.js.map +1 -1
  71. package/package.json +24 -22
  72. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  73. package/src/UIMessages.ts +184 -66
  74. package/src/client/createTool.ts +291 -67
  75. package/src/client/files.ts +4 -2
  76. package/src/client/index.test.ts +1 -0
  77. package/src/client/index.ts +46 -25
  78. package/src/client/mockModel.ts +36 -34
  79. package/src/client/saveInputMessages.ts +2 -2
  80. package/src/client/search.test.ts +166 -0
  81. package/src/client/search.ts +121 -38
  82. package/src/client/streamText.ts +5 -5
  83. package/src/client/types.ts +31 -18
  84. package/src/component/_generated/component.ts +2329 -338
  85. package/src/component/files.ts +11 -2
  86. package/src/component/messages.test.ts +7 -7
  87. package/src/component/schema.ts +3 -1
  88. package/src/component/setup.test.ts +7 -0
  89. package/src/component/streams.ts +1 -1
  90. package/src/deltas.test.ts +90 -0
  91. package/src/deltas.ts +47 -8
  92. package/src/fromUIMessages.test.ts +32 -27
  93. package/src/mapping.test.ts +48 -0
  94. package/src/mapping.ts +234 -142
  95. package/src/react/useThreadMessages.ts +21 -9
  96. package/src/shared.ts +2 -0
  97. package/src/toUIMessages.test.ts +539 -1
  98. package/src/validators.ts +179 -20
  99. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -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 when message has many parts with text part as final part", () => {
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
  });