@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-alpha.1

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 (235) hide show
  1. package/MIGRATION.md +153 -0
  2. package/README.md +32 -27
  3. package/dist/UIMessages.d.ts +46 -0
  4. package/dist/UIMessages.d.ts.map +1 -0
  5. package/dist/UIMessages.js +546 -0
  6. package/dist/UIMessages.js.map +1 -0
  7. package/dist/client/createTool.d.ts +126 -27
  8. package/dist/client/createTool.d.ts.map +1 -1
  9. package/dist/client/createTool.js +67 -12
  10. package/dist/client/createTool.js.map +1 -1
  11. package/dist/client/defaultComponent.d.ts +11 -0
  12. package/dist/client/defaultComponent.d.ts.map +1 -0
  13. package/dist/client/defaultComponent.js +7 -0
  14. package/dist/client/defaultComponent.js.map +1 -0
  15. package/dist/client/definePlaygroundAPI.d.ts +1335 -204
  16. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  17. package/dist/client/definePlaygroundAPI.js +52 -28
  18. package/dist/client/definePlaygroundAPI.js.map +1 -1
  19. package/dist/client/files.d.ts +20 -7
  20. package/dist/client/files.d.ts.map +1 -1
  21. package/dist/client/files.js +68 -11
  22. package/dist/client/files.js.map +1 -1
  23. package/dist/client/index.d.ts +1116 -978
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +332 -747
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/messages.d.ts +461 -0
  28. package/dist/client/messages.d.ts.map +1 -0
  29. package/dist/client/messages.js +106 -0
  30. package/dist/client/messages.js.map +1 -0
  31. package/dist/client/mockModel.d.ts +42 -0
  32. package/dist/client/mockModel.d.ts.map +1 -0
  33. package/dist/client/mockModel.js +182 -0
  34. package/dist/client/mockModel.js.map +1 -0
  35. package/dist/client/saveInputMessages.d.ts +20 -0
  36. package/dist/client/saveInputMessages.d.ts.map +1 -0
  37. package/dist/client/saveInputMessages.js +58 -0
  38. package/dist/client/saveInputMessages.js.map +1 -0
  39. package/dist/client/search.d.ts +350 -39
  40. package/dist/client/search.d.ts.map +1 -1
  41. package/dist/client/search.js +350 -39
  42. package/dist/client/search.js.map +1 -1
  43. package/dist/client/start.d.ts +84 -0
  44. package/dist/client/start.d.ts.map +1 -0
  45. package/dist/client/start.js +185 -0
  46. package/dist/client/start.js.map +1 -0
  47. package/dist/client/streamText.d.ts +46 -0
  48. package/dist/client/streamText.d.ts.map +1 -0
  49. package/dist/client/streamText.js +117 -0
  50. package/dist/client/streamText.js.map +1 -0
  51. package/dist/client/streaming.d.ts +3716 -32
  52. package/dist/client/streaming.d.ts.map +1 -1
  53. package/dist/client/streaming.js +161 -59
  54. package/dist/client/streaming.js.map +1 -1
  55. package/dist/client/threads.d.ts +46 -0
  56. package/dist/client/threads.d.ts.map +1 -0
  57. package/dist/client/threads.js +49 -0
  58. package/dist/client/threads.js.map +1 -0
  59. package/dist/client/types.d.ts +266 -128
  60. package/dist/client/types.d.ts.map +1 -1
  61. package/dist/client/utils.d.ts +4 -0
  62. package/dist/client/utils.d.ts.map +1 -0
  63. package/dist/client/utils.js +21 -0
  64. package/dist/client/utils.js.map +1 -0
  65. package/dist/component/_generated/api.d.ts +24 -2178
  66. package/dist/component/_generated/api.d.ts.map +1 -1
  67. package/dist/component/_generated/api.js +10 -1
  68. package/dist/component/_generated/api.js.map +1 -1
  69. package/dist/component/_generated/component.d.ts +3120 -0
  70. package/dist/component/_generated/component.d.ts.map +1 -0
  71. package/dist/component/_generated/component.js +11 -0
  72. package/dist/component/_generated/component.js.map +1 -0
  73. package/dist/component/_generated/dataModel.d.ts +4 -18
  74. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  75. package/dist/component/_generated/dataModel.js +11 -0
  76. package/dist/component/_generated/dataModel.js.map +1 -0
  77. package/dist/component/_generated/server.d.ts +10 -38
  78. package/dist/component/_generated/server.d.ts.map +1 -1
  79. package/dist/component/_generated/server.js +9 -5
  80. package/dist/component/_generated/server.js.map +1 -1
  81. package/dist/component/files.d.ts +16 -10
  82. package/dist/component/files.d.ts.map +1 -1
  83. package/dist/component/files.js +10 -2
  84. package/dist/component/files.js.map +1 -1
  85. package/dist/component/messages.d.ts +2578 -366
  86. package/dist/component/messages.d.ts.map +1 -1
  87. package/dist/component/messages.js +397 -154
  88. package/dist/component/messages.js.map +1 -1
  89. package/dist/component/schema.d.ts +5697 -3584
  90. package/dist/component/schema.d.ts.map +1 -1
  91. package/dist/component/schema.js +18 -41
  92. package/dist/component/schema.js.map +1 -1
  93. package/dist/component/streams.d.ts +39 -339
  94. package/dist/component/streams.d.ts.map +1 -1
  95. package/dist/component/streams.js +114 -73
  96. package/dist/component/streams.js.map +1 -1
  97. package/dist/component/threads.d.ts +13 -13
  98. package/dist/component/users.d.ts +7 -7
  99. package/dist/component/vector/index.d.ts +1 -1
  100. package/dist/component/vector/index.d.ts.map +1 -1
  101. package/dist/component/vector/index.js +1 -3
  102. package/dist/component/vector/index.js.map +1 -1
  103. package/dist/deltas.d.ts +43 -0
  104. package/dist/deltas.d.ts.map +1 -0
  105. package/dist/deltas.js +446 -0
  106. package/dist/deltas.js.map +1 -0
  107. package/dist/mapping.d.ts +38 -20
  108. package/dist/mapping.d.ts.map +1 -1
  109. package/dist/mapping.js +365 -97
  110. package/dist/mapping.js.map +1 -1
  111. package/dist/react/SmoothText.d.ts +5 -0
  112. package/dist/react/SmoothText.d.ts.map +1 -0
  113. package/dist/react/SmoothText.js +6 -0
  114. package/dist/react/SmoothText.js.map +1 -0
  115. package/dist/react/index.d.ts +5 -77
  116. package/dist/react/index.d.ts.map +1 -1
  117. package/dist/react/index.js +6 -160
  118. package/dist/react/index.js.map +1 -1
  119. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  120. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  121. package/dist/react/optimisticallySendMessage.js +35 -9
  122. package/dist/react/optimisticallySendMessage.js.map +1 -1
  123. package/dist/react/types.d.ts +4 -18
  124. package/dist/react/types.d.ts.map +1 -1
  125. package/dist/react/useDeltaStreams.d.ts +10 -0
  126. package/dist/react/useDeltaStreams.d.ts.map +1 -0
  127. package/dist/react/useDeltaStreams.js +106 -0
  128. package/dist/react/useDeltaStreams.js.map +1 -0
  129. package/dist/react/useSmoothText.d.ts +13 -12
  130. package/dist/react/useSmoothText.d.ts.map +1 -1
  131. package/dist/react/useSmoothText.js +32 -15
  132. package/dist/react/useSmoothText.js.map +1 -1
  133. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  134. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  135. package/dist/react/useStreamingUIMessages.js +92 -0
  136. package/dist/react/useStreamingUIMessages.js.map +1 -0
  137. package/dist/react/useThreadMessages.d.ts +104 -0
  138. package/dist/react/useThreadMessages.d.ts.map +1 -0
  139. package/dist/react/useThreadMessages.js +148 -0
  140. package/dist/react/useThreadMessages.js.map +1 -0
  141. package/dist/react/useUIMessages.d.ts +96 -0
  142. package/dist/react/useUIMessages.d.ts.map +1 -0
  143. package/dist/react/useUIMessages.js +108 -0
  144. package/dist/react/useUIMessages.js.map +1 -0
  145. package/dist/shared.d.ts +20 -4
  146. package/dist/shared.d.ts.map +1 -1
  147. package/dist/shared.js +45 -8
  148. package/dist/shared.js.map +1 -1
  149. package/dist/validators.d.ts +22981 -5666
  150. package/dist/validators.d.ts.map +1 -1
  151. package/dist/validators.js +245 -137
  152. package/dist/validators.js.map +1 -1
  153. package/package.json +101 -51
  154. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  155. package/src/UIMessages.test.ts +273 -0
  156. package/src/UIMessages.ts +739 -0
  157. package/src/client/approval.test.ts +350 -0
  158. package/src/client/createTool.ts +291 -76
  159. package/src/client/defaultComponent.ts +17 -0
  160. package/src/client/definePlaygroundAPI.ts +67 -31
  161. package/src/client/files.ts +100 -20
  162. package/src/client/index.test.ts +40 -85
  163. package/src/client/index.ts +638 -1289
  164. package/src/client/messages.ts +237 -0
  165. package/src/client/mockModel.ts +252 -0
  166. package/src/client/saveInputMessages.test.ts +583 -0
  167. package/src/client/saveInputMessages.ts +101 -0
  168. package/src/client/search.test.ts +1207 -0
  169. package/src/client/search.ts +581 -70
  170. package/src/client/start.ts +327 -0
  171. package/src/client/streamText.ts +187 -0
  172. package/src/client/streaming.test.ts +186 -0
  173. package/src/client/streaming.ts +241 -97
  174. package/src/client/threads.ts +83 -0
  175. package/src/client/types.ts +370 -219
  176. package/src/client/utils.ts +27 -0
  177. package/src/component/_generated/api.ts +64 -0
  178. package/src/component/_generated/component.ts +4902 -0
  179. package/src/component/_generated/{server.d.ts → server.ts} +33 -21
  180. package/src/component/files.ts +11 -2
  181. package/src/component/messages.test.ts +195 -51
  182. package/src/component/messages.ts +500 -201
  183. package/src/component/schema.ts +20 -46
  184. package/src/component/setup.test.ts +7 -0
  185. package/src/component/streams.ts +184 -83
  186. package/src/component/users.test.ts +0 -1
  187. package/src/component/vector/index.ts +1 -3
  188. package/src/deltas.test.ts +626 -0
  189. package/src/deltas.ts +569 -0
  190. package/src/fromUIMessages.test.ts +497 -0
  191. package/src/mapping.test.ts +180 -6
  192. package/src/mapping.ts +479 -162
  193. package/src/react/SmoothText.tsx +9 -0
  194. package/src/react/index.ts +10 -230
  195. package/src/react/optimisticallySendMessage.ts +55 -12
  196. package/src/react/types.ts +6 -39
  197. package/src/react/useDeltaStreams.ts +160 -0
  198. package/src/react/useSmoothText.ts +56 -36
  199. package/src/react/useStreamingUIMessages.ts +143 -0
  200. package/src/react/useThreadMessages.ts +262 -0
  201. package/src/react/useUIMessages.test.ts +255 -0
  202. package/src/react/useUIMessages.ts +195 -0
  203. package/src/shared.ts +88 -12
  204. package/src/test.ts +18 -0
  205. package/src/toUIMessages.test.ts +1269 -0
  206. package/src/validators.test.ts +18 -19
  207. package/src/validators.ts +325 -185
  208. package/dist/client/_generated/_ignore.d.ts +0 -1
  209. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  210. package/dist/client/_generated/_ignore.js +0 -3
  211. package/dist/client/_generated/_ignore.js.map +0 -1
  212. package/dist/client/listMessages.d.ts +0 -22
  213. package/dist/client/listMessages.d.ts.map +0 -1
  214. package/dist/client/listMessages.js +0 -25
  215. package/dist/client/listMessages.js.map +0 -1
  216. package/dist/package.json +0 -3
  217. package/dist/react/deltas.d.ts +0 -26
  218. package/dist/react/deltas.d.ts.map +0 -1
  219. package/dist/react/deltas.js +0 -384
  220. package/dist/react/deltas.js.map +0 -1
  221. package/dist/react/toUIMessages.d.ts +0 -15
  222. package/dist/react/toUIMessages.d.ts.map +0 -1
  223. package/dist/react/toUIMessages.js +0 -211
  224. package/dist/react/toUIMessages.js.map +0 -1
  225. package/src/client/listMessages.ts +0 -38
  226. package/src/component/_generated/api.d.ts +0 -2202
  227. package/src/component/_generated/api.js +0 -23
  228. package/src/component/_generated/server.js +0 -90
  229. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  230. package/src/react/deltas.test.ts +0 -315
  231. package/src/react/deltas.ts +0 -478
  232. package/src/react/toUIMessages.test.ts +0 -420
  233. package/src/react/toUIMessages.ts +0 -253
  234. package/src/vitest.config.ts +0 -7
  235. /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
@@ -0,0 +1,497 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { MessageDoc } from "./client/index.js";
3
+ import type { UIMessage } from "./UIMessages.js";
4
+ import { fromUIMessages, toUIMessages } from "./UIMessages.js";
5
+
6
+ // Helper to create a base message doc
7
+ function baseMessageDoc<T = unknown>(
8
+ overrides: Partial<MessageDoc & { streaming?: boolean; metadata?: T }> = {},
9
+ ): MessageDoc & { streaming?: boolean; metadata?: T } {
10
+ return {
11
+ _id: "msg1",
12
+ _creationTime: Date.now(),
13
+ order: 0,
14
+ stepOrder: 0,
15
+ status: "success",
16
+ threadId: "thread1",
17
+ tool: false,
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ describe("fromUIMessages round-trip tests", () => {
23
+ it("preserves essential data for simple user message", async () => {
24
+ const originalMessages = [
25
+ baseMessageDoc({
26
+ message: {
27
+ role: "user",
28
+ content: "Hello world!",
29
+ },
30
+ text: "Hello world!",
31
+ }),
32
+ ];
33
+
34
+ const uiMessages = toUIMessages(originalMessages);
35
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
36
+ threadId: "thread1",
37
+ });
38
+
39
+ expect(uiMessages).toHaveLength(1);
40
+ expect(uiMessages[0].role).toBe("user");
41
+ expect(uiMessages[0].text).toBe("Hello world!");
42
+
43
+ expect(backToMessageDocs).toHaveLength(1);
44
+ expect(backToMessageDocs[0].text).toBe("Hello world!");
45
+ expect(backToMessageDocs[0].threadId).toBe("thread1");
46
+
47
+ // Content gets normalized to array format
48
+ expect(Array.isArray(backToMessageDocs[0].message?.content)).toBe(true);
49
+ if (Array.isArray(backToMessageDocs[0].message?.content)) {
50
+ expect(backToMessageDocs[0].message.content[0]).toMatchObject({
51
+ type: "text",
52
+ text: "Hello world!",
53
+ });
54
+ }
55
+ });
56
+
57
+ it("preserves essential data for assistant message", async () => {
58
+ const originalMessages = [
59
+ baseMessageDoc({
60
+ message: {
61
+ role: "assistant",
62
+ content: "Hi there! How can I help?",
63
+ },
64
+ text: "Hi there! How can I help?",
65
+ }),
66
+ ];
67
+
68
+ const uiMessages = toUIMessages(originalMessages);
69
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
70
+ threadId: "thread1",
71
+ });
72
+
73
+ expect(uiMessages).toHaveLength(1);
74
+ expect(uiMessages[0].role).toBe("assistant");
75
+ expect(uiMessages[0].text).toBe("Hi there! How can I help?");
76
+
77
+ expect(backToMessageDocs).toHaveLength(1);
78
+ expect(backToMessageDocs[0].text).toBe("Hi there! How can I help?");
79
+ });
80
+
81
+ it("preserves system messages correctly", async () => {
82
+ const originalMessages = [
83
+ baseMessageDoc({
84
+ message: {
85
+ role: "system",
86
+ content: "You are a helpful assistant.",
87
+ },
88
+ text: "You are a helpful assistant.",
89
+ }),
90
+ ];
91
+
92
+ const uiMessages = toUIMessages(originalMessages);
93
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
94
+ threadId: "thread1",
95
+ });
96
+
97
+ expect(uiMessages).toHaveLength(1);
98
+ expect(uiMessages[0].role).toBe("system");
99
+ expect(uiMessages[0].text).toBe("You are a helpful assistant.");
100
+
101
+ expect(backToMessageDocs).toHaveLength(1);
102
+ expect(backToMessageDocs[0].text).toBe("You are a helpful assistant.");
103
+ expect(backToMessageDocs[0].message?.role).toBe("system");
104
+
105
+ // System content stays as string
106
+ expect(backToMessageDocs[0].message?.content).toBe(
107
+ "You are a helpful assistant.",
108
+ );
109
+ });
110
+
111
+ it("preserves reasoning in assistant messages", async () => {
112
+ const originalMessages = [
113
+ baseMessageDoc({
114
+ message: {
115
+ role: "assistant",
116
+ content: [
117
+ {
118
+ type: "reasoning",
119
+ text: "Let me think about this...",
120
+ },
121
+ {
122
+ type: "text",
123
+ text: "Here's my response.",
124
+ },
125
+ ],
126
+ },
127
+ text: "Here's my response.",
128
+ reasoning: "Let me think about this...",
129
+ }),
130
+ ];
131
+
132
+ const uiMessages = toUIMessages(originalMessages);
133
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
134
+ threadId: "thread1",
135
+ });
136
+
137
+ expect(uiMessages).toHaveLength(1);
138
+ expect(uiMessages[0].text).toBe("Here's my response.");
139
+
140
+ // Check that reasoning parts are preserved in UI message
141
+ const reasoningParts = uiMessages[0].parts.filter(
142
+ (part) => part.type === "reasoning",
143
+ );
144
+ expect(reasoningParts).toHaveLength(1);
145
+ expect(reasoningParts[0]).toMatchObject({
146
+ type: "reasoning",
147
+ text: "Let me think about this...",
148
+ });
149
+
150
+ expect(backToMessageDocs).toHaveLength(1);
151
+ expect(backToMessageDocs[0].text).toBe("Here's my response.");
152
+ expect(backToMessageDocs[0].reasoning).toBe("Let me think about this...");
153
+ });
154
+
155
+ it("handles tool calls and groups them correctly", async () => {
156
+ // Tool calls get grouped into single UI message but expanded back to multiple message docs
157
+ const originalMessages = [
158
+ baseMessageDoc({
159
+ _id: "msg1",
160
+ order: 1,
161
+ stepOrder: 1,
162
+ message: {
163
+ role: "assistant",
164
+ content: [
165
+ {
166
+ type: "tool-call",
167
+ toolName: "calculator",
168
+ toolCallId: "call1",
169
+ input: { operation: "add", a: 2, b: 3 },
170
+ args: { operation: "add", a: 2, b: 3 },
171
+ },
172
+ ],
173
+ },
174
+ tool: true,
175
+ }),
176
+ baseMessageDoc({
177
+ _id: "msg2",
178
+ order: 1,
179
+ stepOrder: 2,
180
+ message: {
181
+ role: "tool",
182
+ content: [
183
+ {
184
+ type: "tool-result",
185
+ toolCallId: "call1",
186
+ toolName: "calculator",
187
+ output: {
188
+ type: "json",
189
+ value: { result: 5 },
190
+ },
191
+ },
192
+ ],
193
+ },
194
+ tool: true,
195
+ }),
196
+ ];
197
+ const toTest = [originalMessages, [...originalMessages].reverse()];
198
+ for (const messages of toTest) {
199
+ const uiMessages = toUIMessages(messages);
200
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
201
+ threadId: "thread1",
202
+ });
203
+
204
+ // Should be grouped into single UI message
205
+ expect(uiMessages).toHaveLength(1);
206
+ const uiMessage = uiMessages[0];
207
+ expect(uiMessage.role).toBe("assistant");
208
+ expect(uiMessage.id).toBe("msg1");
209
+
210
+ // Check tool parts exist
211
+ const toolParts = uiMessage.parts.filter(
212
+ (part) => part.type === "tool-calculator",
213
+ );
214
+ expect(toolParts).toHaveLength(1);
215
+ expect(toolParts[0]).toMatchObject({
216
+ type: "tool-calculator",
217
+ toolCallId: "call1",
218
+ state: "output-available",
219
+ input: { operation: "add", a: 2, b: 3 },
220
+ output: { result: 5 },
221
+ });
222
+
223
+ // Should expand back to multiple message docs
224
+ expect(backToMessageDocs.length).toBeGreaterThanOrEqual(1);
225
+
226
+ // Check that tool information is preserved
227
+ const toolMessages = backToMessageDocs.filter((msg) => msg.tool);
228
+ expect(toolMessages.length).toBeGreaterThan(0);
229
+ expect(toolMessages[0].stepOrder).toBe(1);
230
+ expect(toolMessages[1].stepOrder).toBe(2);
231
+ }
232
+ });
233
+
234
+ it("preserves file attachments in user messages", async () => {
235
+ const originalMessages = [
236
+ baseMessageDoc({
237
+ message: {
238
+ role: "user",
239
+ content: [
240
+ {
241
+ type: "text",
242
+ text: "What's in this image?",
243
+ },
244
+ {
245
+ type: "file",
246
+ mimeType: "image/png",
247
+ data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
248
+ },
249
+ ],
250
+ },
251
+ text: "What's in this image?",
252
+ }),
253
+ ];
254
+
255
+ const uiMessages = toUIMessages(originalMessages);
256
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
257
+ threadId: "thread1",
258
+ });
259
+
260
+ expect(uiMessages).toHaveLength(1);
261
+ expect(uiMessages[0].role).toBe("user");
262
+ expect(uiMessages[0].text).toBe("What's in this image?");
263
+
264
+ // Check file parts exist in UI message
265
+ const fileParts = uiMessages[0].parts.filter(
266
+ (part) => part.type === "file",
267
+ );
268
+ expect(fileParts).toHaveLength(1);
269
+
270
+ expect(backToMessageDocs).toHaveLength(1);
271
+ expect(backToMessageDocs[0].text).toBe("What's in this image?");
272
+
273
+ // Check file content is preserved
274
+ const content = backToMessageDocs[0].message?.content;
275
+ expect(Array.isArray(content)).toBe(true);
276
+ if (Array.isArray(content)) {
277
+ const fileContent = content.find((c) => c.type === "file");
278
+ expect(fileContent).toBeDefined();
279
+ expect(fileContent).toMatchObject({
280
+ type: "file",
281
+ mediaType: "image/png",
282
+ });
283
+ }
284
+ });
285
+
286
+ it("preserves sources correctly", async () => {
287
+ const originalMessages = [
288
+ baseMessageDoc({
289
+ message: {
290
+ role: "assistant",
291
+ content: [
292
+ {
293
+ type: "text",
294
+ text: "I found some relevant sources.",
295
+ },
296
+ ],
297
+ },
298
+ text: "I found some relevant sources.",
299
+ sources: [
300
+ {
301
+ type: "source",
302
+ sourceType: "url",
303
+ id: "source1",
304
+ url: "https://example.com",
305
+ title: "Example Source",
306
+ },
307
+ {
308
+ type: "source",
309
+ sourceType: "document",
310
+ id: "source2",
311
+ mediaType: "application/pdf",
312
+ title: "Document Source",
313
+ },
314
+ ],
315
+ }),
316
+ ];
317
+
318
+ const uiMessages = toUIMessages(originalMessages);
319
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
320
+ threadId: "thread1",
321
+ });
322
+
323
+ expect(uiMessages).toHaveLength(1);
324
+
325
+ // Check source parts exist in UI message
326
+ const sourceParts = uiMessages[0].parts.filter(
327
+ (part) => part.type === "source-url" || part.type === "source-document",
328
+ );
329
+ expect(sourceParts).toHaveLength(2);
330
+
331
+ expect(backToMessageDocs).toHaveLength(1);
332
+ expect(backToMessageDocs[0].sources).toHaveLength(2);
333
+ expect(backToMessageDocs[0].sources![0]).toMatchObject({
334
+ type: "source",
335
+ sourceType: "url",
336
+ id: "source1",
337
+ url: "https://example.com",
338
+ title: "Example Source",
339
+ });
340
+ });
341
+
342
+ it("preserves metadata when provided", async () => {
343
+ const testMetadata = {
344
+ customField: "customValue",
345
+ timestamp: Date.now(),
346
+ };
347
+
348
+ const originalMessages = [
349
+ baseMessageDoc({
350
+ message: {
351
+ role: "user",
352
+ content: "Test message",
353
+ },
354
+ text: "Test message",
355
+ metadata: testMetadata,
356
+ }),
357
+ ];
358
+
359
+ const uiMessages = toUIMessages(originalMessages);
360
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
361
+ threadId: "thread1",
362
+ });
363
+
364
+ expect(uiMessages).toHaveLength(1);
365
+ expect(uiMessages[0].metadata).toEqual(testMetadata);
366
+
367
+ expect(backToMessageDocs).toHaveLength(1);
368
+ expect(backToMessageDocs[0].metadata).toEqual(testMetadata);
369
+ });
370
+
371
+ it("handles streaming status correctly", async () => {
372
+ const originalMessages = [
373
+ baseMessageDoc({
374
+ message: {
375
+ role: "assistant",
376
+ content: "Streaming response...",
377
+ },
378
+ text: "Streaming response...",
379
+ streaming: true,
380
+ status: "pending",
381
+ }),
382
+ ];
383
+
384
+ const uiMessages = toUIMessages(originalMessages);
385
+ const backToMessageDocs = await fromUIMessages(uiMessages, {
386
+ threadId: "thread1",
387
+ });
388
+
389
+ expect(uiMessages).toHaveLength(1);
390
+ expect(uiMessages[0].status).toBe("streaming");
391
+
392
+ expect(backToMessageDocs).toHaveLength(1);
393
+ expect(backToMessageDocs[0].streaming).toBe(true);
394
+ expect(backToMessageDocs[0].status).toBe("pending");
395
+ });
396
+ });
397
+
398
+ describe("fromUIMessages functionality tests", () => {
399
+ it("handles empty messages array", async () => {
400
+ const uiMessages: UIMessage[] = [];
401
+ const result = await fromUIMessages(uiMessages, { threadId: "thread1" });
402
+ expect(result).toHaveLength(0);
403
+ });
404
+
405
+ it("correctly assigns thread ID", async () => {
406
+ const uiMessage: UIMessage = {
407
+ id: "test-id",
408
+ _creationTime: Date.now(),
409
+ order: 0,
410
+ stepOrder: 0,
411
+ status: "success",
412
+ key: "test-key",
413
+ text: "Hello",
414
+ role: "user",
415
+ parts: [{ type: "text", text: "Hello" }],
416
+ };
417
+
418
+ const result = await fromUIMessages([uiMessage], {
419
+ threadId: "custom-thread-id",
420
+ });
421
+ expect(result).toHaveLength(1);
422
+ expect(result[0].threadId).toBe("custom-thread-id");
423
+ });
424
+
425
+ it("correctly determines tool status", async () => {
426
+ const toolUIMessage: UIMessage = {
427
+ id: "tool-id",
428
+ _creationTime: Date.now(),
429
+ order: 0,
430
+ stepOrder: 0,
431
+ status: "success",
432
+ key: "tool-key",
433
+ text: "",
434
+ role: "assistant",
435
+ parts: [
436
+ {
437
+ type: "tool-calculator",
438
+ toolCallId: "call1",
439
+ input: { a: 1, b: 2 },
440
+ state: "output-available",
441
+ output: { result: 3 },
442
+ },
443
+ ],
444
+ };
445
+
446
+ const result = await fromUIMessages([toolUIMessage], {
447
+ threadId: "thread1",
448
+ });
449
+ expect(result.length).toBeGreaterThan(0);
450
+
451
+ // Should have tool messages
452
+ const toolMessages = result.filter((msg) => msg.tool);
453
+ expect(toolMessages.length).toBeGreaterThan(0);
454
+ });
455
+
456
+ it("handles tool calls without responses", async () => {
457
+ const toolUIMessage: UIMessage = {
458
+ id: "tool-id",
459
+ _creationTime: Date.now(),
460
+ order: 0,
461
+ stepOrder: 0,
462
+ status: "success",
463
+ key: "tool-key",
464
+ text: "",
465
+ role: "assistant",
466
+ parts: [
467
+ { type: "text", text: "Tool call" },
468
+ {
469
+ type: "tool-calculator",
470
+ toolCallId: "call1",
471
+ input: { a: 1, b: 2 },
472
+ state: "input-available",
473
+ },
474
+ ],
475
+ };
476
+
477
+ const result = await fromUIMessages([toolUIMessage], {
478
+ threadId: "thread1",
479
+ });
480
+ expect(result.length).toBeGreaterThan(0);
481
+
482
+ // Should have tool messages
483
+ const toolMessages = result.filter((msg) => msg.tool);
484
+ expect(toolMessages.length).toBe(1);
485
+ expect(toolMessages[0].message?.role).toBe("assistant");
486
+ expect(toolMessages[0].message?.content[0]).toMatchObject({
487
+ type: "text",
488
+ text: "Tool call",
489
+ });
490
+ expect(toolMessages[0].message?.content[1]).toMatchObject({
491
+ args: { a: 1, b: 2 },
492
+ toolCallId: "call1",
493
+ toolName: "calculator",
494
+ type: "tool-call",
495
+ });
496
+ });
497
+ });