@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
@@ -8,9 +8,8 @@
8
8
  * @module
9
9
  */
10
10
 
11
- import {
11
+ import type {
12
12
  ActionBuilder,
13
- AnyComponents,
14
13
  HttpActionBuilder,
15
14
  MutationBuilder,
16
15
  QueryBuilder,
@@ -19,15 +18,18 @@ import {
19
18
  GenericQueryCtx,
20
19
  GenericDatabaseReader,
21
20
  GenericDatabaseWriter,
22
- FunctionReference,
21
+ } from "convex/server";
22
+ import {
23
+ actionGeneric,
24
+ httpActionGeneric,
25
+ queryGeneric,
26
+ mutationGeneric,
27
+ internalActionGeneric,
28
+ internalMutationGeneric,
29
+ internalQueryGeneric,
23
30
  } from "convex/server";
24
31
  import type { DataModel } from "./dataModel.js";
25
32
 
26
- type GenericCtx =
27
- | GenericActionCtx<DataModel>
28
- | GenericMutationCtx<DataModel>
29
- | GenericQueryCtx<DataModel>;
30
-
31
33
  /**
32
34
  * Define a query in this Convex app's public API.
33
35
  *
@@ -36,7 +38,7 @@ type GenericCtx =
36
38
  * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
37
39
  * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
38
40
  */
39
- export declare const query: QueryBuilder<DataModel, "public">;
41
+ export const query: QueryBuilder<DataModel, "public"> = queryGeneric;
40
42
 
41
43
  /**
42
44
  * Define a query that is only accessible from other Convex functions (but not from the client).
@@ -46,7 +48,8 @@ export declare const query: QueryBuilder<DataModel, "public">;
46
48
  * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
47
49
  * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
48
50
  */
49
- export declare const internalQuery: QueryBuilder<DataModel, "internal">;
51
+ export const internalQuery: QueryBuilder<DataModel, "internal"> =
52
+ internalQueryGeneric;
50
53
 
51
54
  /**
52
55
  * Define a mutation in this Convex app's public API.
@@ -56,7 +59,7 @@ export declare const internalQuery: QueryBuilder<DataModel, "internal">;
56
59
  * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
57
60
  * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
58
61
  */
59
- export declare const mutation: MutationBuilder<DataModel, "public">;
62
+ export const mutation: MutationBuilder<DataModel, "public"> = mutationGeneric;
60
63
 
61
64
  /**
62
65
  * Define a mutation that is only accessible from other Convex functions (but not from the client).
@@ -66,7 +69,8 @@ export declare const mutation: MutationBuilder<DataModel, "public">;
66
69
  * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
67
70
  * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
68
71
  */
69
- export declare const internalMutation: MutationBuilder<DataModel, "internal">;
72
+ export const internalMutation: MutationBuilder<DataModel, "internal"> =
73
+ internalMutationGeneric;
70
74
 
71
75
  /**
72
76
  * Define an action in this Convex app's public API.
@@ -79,7 +83,7 @@ export declare const internalMutation: MutationBuilder<DataModel, "internal">;
79
83
  * @param func - The action. It receives an {@link ActionCtx} as its first argument.
80
84
  * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
81
85
  */
82
- export declare const action: ActionBuilder<DataModel, "public">;
86
+ export const action: ActionBuilder<DataModel, "public"> = actionGeneric;
83
87
 
84
88
  /**
85
89
  * Define an action that is only accessible from other Convex functions (but not from the client).
@@ -87,19 +91,26 @@ export declare const action: ActionBuilder<DataModel, "public">;
87
91
  * @param func - The function. It receives an {@link ActionCtx} as its first argument.
88
92
  * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
89
93
  */
90
- export declare const internalAction: ActionBuilder<DataModel, "internal">;
94
+ export const internalAction: ActionBuilder<DataModel, "internal"> =
95
+ internalActionGeneric;
91
96
 
92
97
  /**
93
98
  * Define an HTTP action.
94
99
  *
95
- * This function will be used to respond to HTTP requests received by a Convex
96
- * deployment if the requests matches the path and method where this action
97
- * is routed. Be sure to route your action in `convex/http.js`.
100
+ * The wrapped function will be used to respond to HTTP requests received
101
+ * by a Convex deployment if the requests matches the path and method where
102
+ * this action is routed. Be sure to route your httpAction in `convex/http.js`.
98
103
  *
99
- * @param func - The function. It receives an {@link ActionCtx} as its first argument.
104
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument
105
+ * and a Fetch API `Request` object as its second.
100
106
  * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
101
107
  */
102
- export declare const httpAction: HttpActionBuilder;
108
+ export const httpAction: HttpActionBuilder = httpActionGeneric;
109
+
110
+ type GenericCtx =
111
+ | GenericActionCtx<DataModel>
112
+ | GenericMutationCtx<DataModel>
113
+ | GenericQueryCtx<DataModel>;
103
114
 
104
115
  /**
105
116
  * A set of services for use within Convex query functions.
@@ -107,8 +118,7 @@ export declare const httpAction: HttpActionBuilder;
107
118
  * The query context is passed as the first argument to any Convex query
108
119
  * function run on the server.
109
120
  *
110
- * This differs from the {@link MutationCtx} because all of the services are
111
- * read-only.
121
+ * If you're using code generation, use the `QueryCtx` type in `convex/_generated/server.d.ts` instead.
112
122
  */
113
123
  export type QueryCtx = GenericQueryCtx<DataModel>;
114
124
 
@@ -117,6 +127,8 @@ export type QueryCtx = GenericQueryCtx<DataModel>;
117
127
  *
118
128
  * The mutation context is passed as the first argument to any Convex mutation
119
129
  * function run on the server.
130
+ *
131
+ * If you're using code generation, use the `MutationCtx` type in `convex/_generated/server.d.ts` instead.
120
132
  */
121
133
  export type MutationCtx = GenericMutationCtx<DataModel>;
122
134
 
@@ -9,7 +9,9 @@ const addFileArgs = v.object({
9
9
  storageId: v.string(),
10
10
  hash: v.string(),
11
11
  filename: v.optional(v.string()),
12
- mimeType: v.string(),
12
+ mediaType: v.optional(v.string()),
13
+ /** @deprecated Use `mediaType` instead. */
14
+ mimeType: v.optional(v.string()),
13
15
  });
14
16
 
15
17
  export const addFile = mutation({
@@ -25,6 +27,9 @@ export async function addFileHandler(
25
27
  ctx: MutationCtx,
26
28
  args: Infer<typeof addFileArgs>,
27
29
  ) {
30
+ // Support both mediaType (preferred) and mimeType (deprecated)
31
+ const mediaType = args.mediaType ?? args.mimeType;
32
+
28
33
  const existingFile = await ctx.db
29
34
  .query("files")
30
35
  .withIndex("hash", (q) => q.eq("hash", args.hash))
@@ -42,7 +47,11 @@ export async function addFileHandler(
42
47
  };
43
48
  }
44
49
  const fileId = await ctx.db.insert("files", {
45
- ...args,
50
+ storageId: args.storageId,
51
+ hash: args.hash,
52
+ filename: args.filename,
53
+ mediaType,
54
+ mimeType: args.mimeType, // Keep for backwards compatibility
46
55
  // We start out with it unused - when it's saved in a message we increment.
47
56
  refcount: 0,
48
57
  lastTouchedAt: Date.now(),
@@ -6,11 +6,11 @@ import { api } from "./_generated/api.js";
6
6
  import type { Id } from "./_generated/dataModel.js";
7
7
  import { getMaxMessage } from "./messages.js";
8
8
  import schema from "./schema.js";
9
- import { modules } from "./setup.test.js";
9
+ import { initConvexTest, modules } from "./setup.test.js";
10
10
 
11
11
  describe("agent", () => {
12
12
  test("getMaxMessage works for threads", async () => {
13
- const t = convexTest(schema, modules);
13
+ const t = initConvexTest();
14
14
  const thread = await t.mutation(api.threads.createThread, {
15
15
  userId: "test",
16
16
  });
@@ -30,6 +30,29 @@ describe("agent", () => {
30
30
  stepOrder: 1,
31
31
  });
32
32
  });
33
+ test("getMaxMessage works for a specific order", async () => {
34
+ const t = convexTest(schema, modules);
35
+ const thread = await t.mutation(api.threads.createThread, {
36
+ userId: "test",
37
+ });
38
+ const { messages } = await t.mutation(api.messages.addMessages, {
39
+ threadId: thread._id as Id<"threads">,
40
+ messages: [
41
+ { message: { role: "user", content: "hello" } },
42
+ { message: { role: "assistant", content: "step 1" } },
43
+ { message: { role: "user", content: "hello2" } },
44
+ ],
45
+ });
46
+ const maxMessage = await t.run(async (ctx) => {
47
+ return await getMaxMessage(ctx, thread._id as Id<"threads">, 0);
48
+ });
49
+ expect(maxMessage).toMatchObject({
50
+ _id: messages.at(1)!._id,
51
+ order: 0,
52
+ stepOrder: 1,
53
+ });
54
+ });
55
+
33
56
  test("getMaxMessages works when there are tools involved", async () => {
34
57
  const t = convexTest(schema, modules);
35
58
  const thread = await t.mutation(api.threads.createThread, {
@@ -45,7 +68,7 @@ describe("agent", () => {
45
68
  content: [
46
69
  {
47
70
  type: "tool-call",
48
- args: { a: 1 },
71
+ input: { a: 1 },
49
72
  toolCallId: "1",
50
73
  toolName: "tool",
51
74
  },
@@ -78,7 +101,7 @@ describe("agent", () => {
78
101
  });
79
102
  });
80
103
 
81
- test("ordering is incremented on subsequent calls to addMessages", async () => {
104
+ test("ordering is incremented on subsequent calls to addMessages for user messages", async () => {
82
105
  const t = convexTest(schema, modules);
83
106
  const thread = await t.mutation(api.threads.createThread, {
84
107
  userId: "test",
@@ -109,7 +132,38 @@ describe("agent", () => {
109
132
  });
110
133
  });
111
134
 
112
- test("sub order is incremented on subsequent calls to addMessages for the same promptMessageId", async () => {
135
+ test("ordering is incremented on subsequent calls to addMessages for assistant messages", async () => {
136
+ const t = convexTest(schema, modules);
137
+ const thread = await t.mutation(api.threads.createThread, {
138
+ userId: "test",
139
+ });
140
+ const { messages } = await t.mutation(api.messages.addMessages, {
141
+ threadId: thread._id as Id<"threads">,
142
+ messages: [{ message: { role: "user", content: "hello" } }],
143
+ });
144
+ const maxMessage = await t.run(async (ctx) => {
145
+ return await getMaxMessage(ctx, thread._id as Id<"threads">);
146
+ });
147
+ expect(maxMessage).toMatchObject({
148
+ _id: messages.at(-1)!._id,
149
+ order: 0,
150
+ stepOrder: 0,
151
+ });
152
+ const { messages: messages2 } = await t.mutation(api.messages.addMessages, {
153
+ threadId: thread._id as Id<"threads">,
154
+ messages: [{ message: { role: "assistant", content: "hello" } }],
155
+ });
156
+ const maxMessage2 = await t.run(async (ctx) => {
157
+ return await getMaxMessage(ctx, thread._id as Id<"threads">);
158
+ });
159
+ expect(maxMessage2).toMatchObject({
160
+ _id: messages2.at(-1)!._id,
161
+ order: 0,
162
+ stepOrder: 1,
163
+ });
164
+ });
165
+
166
+ test("order is incremented for user messages on to addMessages for the same promptMessageId", async () => {
113
167
  const t = convexTest(schema, modules);
114
168
  const thread = await t.mutation(api.threads.createThread, {
115
169
  userId: "test",
@@ -135,6 +189,39 @@ describe("agent", () => {
135
189
  const maxMessage2 = await t.run(async (ctx) => {
136
190
  return await getMaxMessage(ctx, thread._id as Id<"threads">);
137
191
  });
192
+ expect(maxMessage2).toMatchObject({
193
+ _id: messages2.at(-1)!._id,
194
+ order: 1,
195
+ stepOrder: 0,
196
+ });
197
+ });
198
+
199
+ test("sub order is incremented on subsequent calls to addMessages for the same promptMessageId", async () => {
200
+ const t = convexTest(schema, modules);
201
+ const thread = await t.mutation(api.threads.createThread, {
202
+ userId: "test",
203
+ });
204
+ const { messages } = await t.mutation(api.messages.addMessages, {
205
+ threadId: thread._id as Id<"threads">,
206
+ messages: [{ message: { role: "user", content: "hello" } }],
207
+ });
208
+ const maxMessage = await t.run(async (ctx) => {
209
+ return await getMaxMessage(ctx, thread._id as Id<"threads">);
210
+ });
211
+ expect(maxMessage).toMatchObject({
212
+ _id: messages.at(-1)!._id,
213
+ order: 0,
214
+ stepOrder: 0,
215
+ });
216
+ const { messages: messages2 } = await t.mutation(api.messages.addMessages, {
217
+ threadId: thread._id as Id<"threads">,
218
+ messages: [{ message: { role: "assistant", content: "hello" } }],
219
+ agentName: "test",
220
+ promptMessageId: messages.at(-1)!._id as Id<"messages">,
221
+ });
222
+ const maxMessage2 = await t.run(async (ctx) => {
223
+ return await getMaxMessage(ctx, thread._id as Id<"threads">);
224
+ });
138
225
  expect(maxMessage2).toMatchObject({
139
226
  _id: messages2.at(-1)!._id,
140
227
  order: 0,
@@ -142,6 +229,83 @@ describe("agent", () => {
142
229
  });
143
230
  });
144
231
 
232
+ test("adding multiple messages at a promptMessageId skips later messages", async () => {
233
+ const t = convexTest(schema, modules);
234
+ const thread = await t.mutation(api.threads.createThread, {
235
+ userId: "test",
236
+ });
237
+ const { messages } = await t.mutation(api.messages.addMessages, {
238
+ threadId: thread._id as Id<"threads">,
239
+ messages: [{ message: { role: "user", content: "hello" } }],
240
+ });
241
+
242
+ const { messages: messages2 } = await t.mutation(api.messages.addMessages, {
243
+ threadId: thread._id as Id<"threads">,
244
+ messages: [
245
+ { message: { role: "user", content: "hello2" } },
246
+ { message: { role: "assistant", content: "hello" } },
247
+ ],
248
+ agentName: "test",
249
+ });
250
+ expect(messages2.length).toBe(2);
251
+
252
+ const { messages: messages3 } = await t.mutation(api.messages.addMessages, {
253
+ threadId: thread._id as Id<"threads">,
254
+ messages: [
255
+ {
256
+ message: {
257
+ role: "assistant",
258
+ content: [
259
+ {
260
+ type: "tool-call",
261
+ input: { a: 1 },
262
+ toolCallId: "1",
263
+ toolName: "tool",
264
+ },
265
+ ],
266
+ },
267
+ },
268
+ {
269
+ message: {
270
+ role: "tool",
271
+ content: [
272
+ {
273
+ type: "tool-result",
274
+ toolName: "tool",
275
+ result: "foo",
276
+ toolCallId: "1",
277
+ },
278
+ ],
279
+ },
280
+ },
281
+ { message: { role: "user", content: "bye" } },
282
+ ],
283
+ agentName: "test",
284
+ promptMessageId: messages.at(-1)!._id as Id<"messages">,
285
+ });
286
+
287
+ expect(messages3.length).toBe(3);
288
+
289
+ const allMessages = await t.query(api.messages.listMessagesByThreadId, {
290
+ threadId: thread._id as Id<"threads">,
291
+ order: "asc",
292
+ });
293
+ expect(allMessages.page).toHaveLength(6);
294
+ expect(allMessages.page.map((m) => m.order)).toEqual([0, 0, 0, 1, 1, 2]);
295
+ expect(allMessages.page.map((m) => m.stepOrder)).toEqual([
296
+ 0, 1, 2, 0, 1, 0,
297
+ ]);
298
+ expect(allMessages.page[0]!.message!.role).toBe("user");
299
+ expect(allMessages.page[0]!.message!.content).toBe("hello");
300
+ expect(allMessages.page[1]!.message!.role).toBe("assistant");
301
+ expect(allMessages.page[2]!.message!.role).toBe("tool");
302
+ expect(allMessages.page[3]!.message!.role).toBe("user");
303
+ expect(allMessages.page[3]!.message!.content).toBe("hello2");
304
+ expect(allMessages.page[4]!.message!.role).toBe("assistant");
305
+ expect(allMessages.page[5]!.message!.role).toBe("user");
306
+ expect(allMessages.page[5]!.message!.content).toBe("bye");
307
+ });
308
+
145
309
  test("updateMessage updates message content", async () => {
146
310
  const t = convexTest(schema, modules);
147
311
  const thread = await t.mutation(api.threads.createThread, {
@@ -155,9 +319,7 @@ describe("agent", () => {
155
319
 
156
320
  const updatedMessage = await t.mutation(api.messages.updateMessage, {
157
321
  messageId,
158
- patch: {
159
- message: { role: "user", content: "updated content" },
160
- },
322
+ patch: { message: { role: "user", content: "updated content" } },
161
323
  });
162
324
 
163
325
  expect(updatedMessage.message).toEqual({
@@ -173,8 +335,9 @@ describe("agent", () => {
173
335
  });
174
336
  const { messages } = await t.mutation(api.messages.addMessages, {
175
337
  threadId: thread._id as Id<"threads">,
176
- messages: [{ message: { role: "assistant", content: "hello" } }],
177
- pending: true,
338
+ messages: [
339
+ { message: { role: "assistant", content: "hello" }, status: "pending" },
340
+ ],
178
341
  });
179
342
  const messageId = messages[0]._id as Id<"messages">;
180
343
 
@@ -184,9 +347,7 @@ describe("agent", () => {
184
347
  // Update to success
185
348
  const updatedMessage = await t.mutation(api.messages.updateMessage, {
186
349
  messageId,
187
- patch: {
188
- status: "success",
189
- },
350
+ patch: { status: "success" },
190
351
  });
191
352
 
192
353
  expect(updatedMessage.status).toBe("success");
@@ -199,17 +360,15 @@ describe("agent", () => {
199
360
  });
200
361
  const { messages } = await t.mutation(api.messages.addMessages, {
201
362
  threadId: thread._id as Id<"threads">,
202
- messages: [{ message: { role: "assistant", content: "hello" } }],
203
- pending: true,
363
+ messages: [
364
+ { message: { role: "assistant", content: "hello" }, status: "pending" },
365
+ ],
204
366
  });
205
367
  const messageId = messages[0]._id as Id<"messages">;
206
368
 
207
369
  const updatedMessage = await t.mutation(api.messages.updateMessage, {
208
370
  messageId,
209
- patch: {
210
- status: "failed",
211
- error: "Something went wrong",
212
- },
371
+ patch: { status: "failed", error: "Something went wrong" },
213
372
  });
214
373
 
215
374
  expect(updatedMessage.status).toBe("failed");
@@ -230,7 +389,7 @@ describe("agent", () => {
230
389
  content: [
231
390
  {
232
391
  type: "tool-call",
233
- args: { a: 1 },
392
+ input: { a: 1 },
234
393
  toolCallId: "1",
235
394
  toolName: "tool",
236
395
  },
@@ -249,7 +408,7 @@ describe("agent", () => {
249
408
  content: [
250
409
  {
251
410
  type: "tool-call",
252
- args: { a: 2, b: 3 },
411
+ input: { a: 2, b: 3 },
253
412
  toolCallId: "1",
254
413
  toolName: "tool",
255
414
  },
@@ -263,7 +422,7 @@ describe("agent", () => {
263
422
  content: [
264
423
  {
265
424
  type: "tool-call",
266
- args: { a: 2, b: 3 },
425
+ input: { a: 2, b: 3 },
267
426
  toolCallId: "1",
268
427
  toolName: "tool",
269
428
  },
@@ -277,9 +436,7 @@ describe("agent", () => {
277
436
  await expect(
278
437
  t.mutation(api.messages.updateMessage, {
279
438
  messageId: "invalidId" as Id<"messages">,
280
- patch: {
281
- message: { role: "user", content: "test" },
282
- },
439
+ patch: { message: { role: "user", content: "test" } },
283
440
  }),
284
441
  ).rejects.toThrow();
285
442
  });
@@ -308,10 +465,7 @@ describe("agent", () => {
308
465
  // Verify messages are actually deleted
309
466
  const remainingMessages = await t.query(
310
467
  api.messages.listMessagesByThreadId,
311
- {
312
- threadId: thread._id as Id<"threads">,
313
- order: "asc",
314
- },
468
+ { threadId: thread._id as Id<"threads">, order: "asc" },
315
469
  );
316
470
  expect(remainingMessages.page).toHaveLength(1);
317
471
  expect(remainingMessages.page[0]._id).toBe(messageIds[1]);
@@ -343,10 +497,7 @@ describe("agent", () => {
343
497
  // Verify the valid message was deleted
344
498
  const remainingMessages = await t.query(
345
499
  api.messages.listMessagesByThreadId,
346
- {
347
- threadId: thread._id as Id<"threads">,
348
- order: "asc",
349
- },
500
+ { threadId: thread._id as Id<"threads">, order: "asc" },
350
501
  );
351
502
  expect(remainingMessages.page).toHaveLength(0);
352
503
  });
@@ -385,24 +536,23 @@ describe("agent", () => {
385
536
  const result = await t.mutation(api.messages.deleteByOrder, {
386
537
  threadId: thread._id as Id<"threads">,
387
538
  startOrder: 0,
388
- endOrder: 1,
539
+ endOrder: 2,
389
540
  });
390
541
 
391
542
  expect(result.isDone).toBe(true);
392
- expect(result.lastOrder).toBe(0);
543
+ expect(result.lastOrder).toBe(1);
393
544
  expect(result.lastStepOrder).toBe(1);
394
545
 
395
- // Verify only messages from order 0 were deleted
546
+ // Verify only messages from order 0 & 1 were deleted
396
547
  const remainingMessages = await t.query(
397
548
  api.messages.listMessagesByThreadId,
398
- {
399
- threadId: thread._id as Id<"threads">,
400
- order: "asc",
401
- },
549
+ { threadId: thread._id as Id<"threads">, order: "asc" },
402
550
  );
403
551
 
404
- expect(remainingMessages.page).toHaveLength(3); // Should have messages from order 1 and 2
405
- expect(remainingMessages.page.map((m) => m.order)).toEqual([1, 1, 2]);
552
+ expect(remainingMessages.page).toHaveLength(1); // Should have messages from order 2
553
+ expect(remainingMessages.page[0].message!.content).toBe(
554
+ "message order 2, step 0",
555
+ );
406
556
  });
407
557
 
408
558
  test("deleteByOrder handles step order boundaries correctly", async () => {
@@ -417,7 +567,7 @@ describe("agent", () => {
417
567
  messages: [
418
568
  { message: { role: "user", content: "step 0" } },
419
569
  { message: { role: "assistant", content: "step 1" } },
420
- { message: { role: "user", content: "step 2" } },
570
+ { message: { role: "assistant", content: "step 2" } },
421
571
  { message: { role: "assistant", content: "step 3" } },
422
572
  ],
423
573
  });
@@ -438,10 +588,7 @@ describe("agent", () => {
438
588
  // Verify only step 1 and 2 were deleted (step 3 is excluded by upperBoundInclusive: false)
439
589
  const remainingMessages = await t.query(
440
590
  api.messages.listMessagesByThreadId,
441
- {
442
- threadId: thread._id as Id<"threads">,
443
- order: "asc",
444
- },
591
+ { threadId: thread._id as Id<"threads">, order: "asc" },
445
592
  );
446
593
 
447
594
  expect(remainingMessages.page).toHaveLength(2);
@@ -501,10 +648,7 @@ describe("agent", () => {
501
648
  // Verify original message is still there
502
649
  const remainingMessages = await t.query(
503
650
  api.messages.listMessagesByThreadId,
504
- {
505
- threadId: thread._id as Id<"threads">,
506
- order: "asc",
507
- },
651
+ { threadId: thread._id as Id<"threads">, order: "asc" },
508
652
  );
509
653
  expect(remainingMessages.page).toHaveLength(1);
510
654
  });