@convex-dev/agent 0.1.1 → 0.1.2-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 (109) hide show
  1. package/README.md +9 -3
  2. package/dist/commonjs/client/createTool.d.ts +49 -0
  3. package/dist/commonjs/client/createTool.d.ts.map +1 -0
  4. package/dist/commonjs/client/createTool.js +45 -0
  5. package/dist/commonjs/client/createTool.js.map +1 -0
  6. package/dist/commonjs/client/index.d.ts +101 -322
  7. package/dist/commonjs/client/index.d.ts.map +1 -1
  8. package/dist/commonjs/client/index.js +142 -194
  9. package/dist/commonjs/client/index.js.map +1 -1
  10. package/dist/commonjs/client/streaming.d.ts +54 -0
  11. package/dist/commonjs/client/streaming.d.ts.map +1 -0
  12. package/dist/commonjs/client/streaming.js +121 -0
  13. package/dist/commonjs/client/streaming.js.map +1 -0
  14. package/dist/commonjs/client/types.d.ts +290 -2
  15. package/dist/commonjs/client/types.d.ts.map +1 -1
  16. package/dist/commonjs/component/messages.d.ts +104 -138
  17. package/dist/commonjs/component/messages.d.ts.map +1 -1
  18. package/dist/commonjs/component/messages.js +15 -14
  19. package/dist/commonjs/component/messages.js.map +1 -1
  20. package/dist/commonjs/component/schema.d.ts +873 -3
  21. package/dist/commonjs/component/schema.d.ts.map +1 -1
  22. package/dist/commonjs/component/schema.js +39 -1
  23. package/dist/commonjs/component/schema.js.map +1 -1
  24. package/dist/commonjs/component/streams.d.ts +191 -0
  25. package/dist/commonjs/component/streams.d.ts.map +1 -0
  26. package/dist/commonjs/component/streams.js +169 -0
  27. package/dist/commonjs/component/streams.js.map +1 -0
  28. package/dist/commonjs/component/threads.d.ts +1 -1
  29. package/dist/commonjs/component/threads.js +3 -3
  30. package/dist/commonjs/component/threads.js.map +1 -1
  31. package/dist/commonjs/component/users.js +2 -2
  32. package/dist/commonjs/component/users.js.map +1 -1
  33. package/dist/commonjs/mapping.d.ts.map +1 -1
  34. package/dist/commonjs/mapping.js +3 -1
  35. package/dist/commonjs/mapping.js.map +1 -1
  36. package/dist/commonjs/react/index.d.ts +99 -0
  37. package/dist/commonjs/react/index.d.ts.map +1 -1
  38. package/dist/commonjs/react/index.js +505 -1
  39. package/dist/commonjs/react/index.js.map +1 -1
  40. package/dist/commonjs/react/toUIMessages.d.ts +8 -0
  41. package/dist/commonjs/react/toUIMessages.d.ts.map +1 -0
  42. package/dist/commonjs/react/toUIMessages.js +134 -0
  43. package/dist/commonjs/react/toUIMessages.js.map +1 -0
  44. package/dist/commonjs/validators.d.ts +506 -4
  45. package/dist/commonjs/validators.d.ts.map +1 -1
  46. package/dist/commonjs/validators.js +49 -2
  47. package/dist/commonjs/validators.js.map +1 -1
  48. package/dist/esm/client/createTool.d.ts +49 -0
  49. package/dist/esm/client/createTool.d.ts.map +1 -0
  50. package/dist/esm/client/createTool.js +45 -0
  51. package/dist/esm/client/createTool.js.map +1 -0
  52. package/dist/esm/client/index.d.ts +101 -322
  53. package/dist/esm/client/index.d.ts.map +1 -1
  54. package/dist/esm/client/index.js +142 -194
  55. package/dist/esm/client/index.js.map +1 -1
  56. package/dist/esm/client/streaming.d.ts +54 -0
  57. package/dist/esm/client/streaming.d.ts.map +1 -0
  58. package/dist/esm/client/streaming.js +121 -0
  59. package/dist/esm/client/streaming.js.map +1 -0
  60. package/dist/esm/client/types.d.ts +290 -2
  61. package/dist/esm/client/types.d.ts.map +1 -1
  62. package/dist/esm/component/messages.d.ts +104 -138
  63. package/dist/esm/component/messages.d.ts.map +1 -1
  64. package/dist/esm/component/messages.js +15 -14
  65. package/dist/esm/component/messages.js.map +1 -1
  66. package/dist/esm/component/schema.d.ts +873 -3
  67. package/dist/esm/component/schema.d.ts.map +1 -1
  68. package/dist/esm/component/schema.js +39 -1
  69. package/dist/esm/component/schema.js.map +1 -1
  70. package/dist/esm/component/streams.d.ts +191 -0
  71. package/dist/esm/component/streams.d.ts.map +1 -0
  72. package/dist/esm/component/streams.js +169 -0
  73. package/dist/esm/component/streams.js.map +1 -0
  74. package/dist/esm/component/threads.d.ts +1 -1
  75. package/dist/esm/component/threads.js +3 -3
  76. package/dist/esm/component/threads.js.map +1 -1
  77. package/dist/esm/component/users.js +2 -2
  78. package/dist/esm/component/users.js.map +1 -1
  79. package/dist/esm/mapping.d.ts.map +1 -1
  80. package/dist/esm/mapping.js +3 -1
  81. package/dist/esm/mapping.js.map +1 -1
  82. package/dist/esm/react/index.d.ts +99 -0
  83. package/dist/esm/react/index.d.ts.map +1 -1
  84. package/dist/esm/react/index.js +505 -1
  85. package/dist/esm/react/index.js.map +1 -1
  86. package/dist/esm/react/toUIMessages.d.ts +8 -0
  87. package/dist/esm/react/toUIMessages.d.ts.map +1 -0
  88. package/dist/esm/react/toUIMessages.js +134 -0
  89. package/dist/esm/react/toUIMessages.js.map +1 -0
  90. package/dist/esm/validators.d.ts +506 -4
  91. package/dist/esm/validators.d.ts.map +1 -1
  92. package/dist/esm/validators.js +49 -2
  93. package/dist/esm/validators.js.map +1 -1
  94. package/package.json +5 -1
  95. package/src/client/createTool.ts +108 -0
  96. package/src/client/index.ts +270 -689
  97. package/src/client/streaming.ts +166 -0
  98. package/src/client/types.ts +430 -5
  99. package/src/component/_generated/api.d.ts +339 -188
  100. package/src/component/messages.ts +15 -14
  101. package/src/component/schema.ts +46 -0
  102. package/src/component/streams.ts +206 -0
  103. package/src/component/threads.ts +3 -3
  104. package/src/component/users.ts +2 -2
  105. package/src/mapping.ts +3 -1
  106. package/src/react/index.ts +687 -1
  107. package/src/react/toUIMessages.ts +153 -0
  108. package/src/validators.test.ts +18 -0
  109. package/src/validators.ts +72 -2
@@ -14,6 +14,7 @@ import {
14
14
  vReasoningDetails,
15
15
  vFile,
16
16
  vFileWithStringId,
17
+ vTextStreamPart,
17
18
  } from "../validators.js";
18
19
  import { typedV } from "convex-helpers/validators";
19
20
  import vectorTables, { vVectorId } from "./vector/tables.js";
@@ -105,6 +106,51 @@ export const schema = defineSchema({
105
106
  "stepOrder",
106
107
  ]),
107
108
 
109
+ // Status: if it's done, it's deleted, then deltas are vacuumed
110
+ streamingMessages: defineTable({
111
+ // extra metadata?
112
+ userId: v.optional(v.string()),
113
+ agentName: v.optional(v.string()),
114
+ model: v.optional(v.string()),
115
+ provider: v.optional(v.string()),
116
+ providerOptions: v.optional(vProviderOptions), // Sent to model
117
+
118
+ threadId: v.id("threads"),
119
+ order: v.number(),
120
+ /**
121
+ * The step order of the first message in the stream.
122
+ * If the stream ends up with both a tool call and a tool result,
123
+ * the stepOrder of the result will be +1 of the tool call.
124
+ */
125
+ stepOrder: v.number(),
126
+ state: v.union(
127
+ v.object({
128
+ kind: v.literal("streaming"),
129
+ lastHeartbeat: v.number(),
130
+ timeoutFnId: v.optional(v.id("_scheduled_functions")),
131
+ }),
132
+ v.object({
133
+ kind: v.literal("finished"),
134
+ endedAt: v.number(),
135
+ }),
136
+ v.object({
137
+ kind: v.literal("error"),
138
+ error: v.string(),
139
+ })
140
+ ),
141
+ })
142
+ // There should only be one per "order" index
143
+ // If another exists, it's deleted and replaced
144
+ .index("threadId_order_stepOrder", ["threadId", "order", "stepOrder"]),
145
+
146
+ streamDeltas: defineTable({
147
+ streamId: v.id("streamingMessages"),
148
+ // the indexes work like: 0 <first> 1 <second> 2 <third> 3 ...
149
+ start: v.number(), // inclusive
150
+ end: v.number(), // exclusive
151
+ parts: v.array(vTextStreamPart),
152
+ }).index("streamId_start_end", ["streamId", "start", "end"]),
153
+
108
154
  memories: defineTable({
109
155
  threadId: v.optional(v.id("threads")),
110
156
  userId: v.optional(v.string()),
@@ -0,0 +1,206 @@
1
+ import { v } from "convex/values";
2
+ import {
3
+ internalMutation,
4
+ mutation,
5
+ MutationCtx,
6
+ query,
7
+ } from "./_generated/server";
8
+ import { omit, pick } from "convex-helpers";
9
+ import { StreamDelta, vStreamDelta, vStreamMessage } from "../validators";
10
+ import schema from "./schema";
11
+ import { internal } from "./_generated/api";
12
+ import { Id } from "./_generated/dataModel";
13
+
14
+ const MAX_DELTAS_PER_REQUEST = 1000;
15
+ const MAX_DELTAS_PER_STREAM = 100;
16
+ const TIMEOUT_INTERVAL = 1000 * 60; // 1 minute
17
+
18
+ const deltaValidator = schema.tables.streamDeltas.validator;
19
+
20
+ export const addDelta = mutation({
21
+ args: deltaValidator,
22
+ returns: v.null(),
23
+ handler: async (ctx, args) => {
24
+ await ctx.db.insert("streamDeltas", args);
25
+ await heartbeatStream(ctx, { streamId: args.streamId });
26
+ },
27
+ });
28
+
29
+ export const listDeltas = query({
30
+ args: {
31
+ threadId: v.id("threads"),
32
+ cursors: v.array(
33
+ v.object({
34
+ streamId: v.id("streamingMessages"),
35
+ cursor: v.number(),
36
+ })
37
+ ),
38
+ },
39
+ returns: v.array(vStreamDelta),
40
+ handler: async (ctx, args): Promise<StreamDelta[]> => {
41
+ let totalDeltas = 0;
42
+ const deltas: StreamDelta[] = [];
43
+ for (const cursor of args.cursors) {
44
+ const streamDeltas = await ctx.db
45
+ .query("streamDeltas")
46
+ .withIndex("streamId_start_end", (q) =>
47
+ q.eq("streamId", cursor.streamId).gte("start", cursor.cursor)
48
+ )
49
+ .take(
50
+ Math.min(MAX_DELTAS_PER_STREAM, MAX_DELTAS_PER_REQUEST - totalDeltas)
51
+ );
52
+ totalDeltas += streamDeltas.length;
53
+ deltas.push(
54
+ ...streamDeltas.map((d) => ({
55
+ streamId: d.streamId.toString(),
56
+ ...pick(d, ["start", "end", "parts"]),
57
+ }))
58
+ );
59
+ if (totalDeltas >= MAX_DELTAS_PER_REQUEST) {
60
+ break;
61
+ }
62
+ }
63
+ return deltas;
64
+ },
65
+ });
66
+
67
+ export const create = mutation({
68
+ args: omit(schema.tables.streamingMessages.validator.fields, ["state"]),
69
+ returns: v.id("streamingMessages"),
70
+ handler: async (ctx, args) => {
71
+ const state = {
72
+ kind: "streaming" as const,
73
+ lastHeartbeat: Date.now(),
74
+ };
75
+ const streamId = await ctx.db.insert("streamingMessages", {
76
+ ...args,
77
+ state,
78
+ });
79
+ const timeoutFnId = await ctx.scheduler.runAfter(
80
+ TIMEOUT_INTERVAL,
81
+ internal.streams.timeoutStream,
82
+ { streamId }
83
+ );
84
+ await ctx.db.patch(streamId, { state: { ...state, timeoutFnId } });
85
+ return streamId;
86
+ },
87
+ });
88
+
89
+ export const list = query({
90
+ args: {
91
+ threadId: v.id("threads"),
92
+ },
93
+ returns: v.array(vStreamMessage),
94
+ handler: async (ctx, args) => {
95
+ return ctx.db
96
+ .query("streamingMessages")
97
+ .withIndex("threadId_order_stepOrder", (q) =>
98
+ q.eq("threadId", args.threadId)
99
+ )
100
+ .order("desc")
101
+ .take(100)
102
+ .then((msgs) =>
103
+ msgs.map((m) => ({
104
+ streamId: m._id,
105
+ ...pick(m, [
106
+ "order",
107
+ "stepOrder",
108
+ "userId",
109
+ "agentName",
110
+ "model",
111
+ "provider",
112
+ "providerOptions",
113
+ ]),
114
+ }))
115
+ );
116
+ },
117
+ });
118
+
119
+ export const finish = mutation({
120
+ args: {
121
+ streamId: v.id("streamingMessages"),
122
+ finalDelta: v.optional(deltaValidator),
123
+ },
124
+ returns: v.null(),
125
+ handler: async (ctx, args) => {
126
+ if (args.finalDelta) {
127
+ await ctx.db.insert("streamDeltas", args.finalDelta);
128
+ }
129
+ const stream = await ctx.db.get(args.streamId);
130
+ if (!stream) {
131
+ throw new Error(`Stream not found: ${args.streamId}`);
132
+ }
133
+ if (stream.state.kind !== "streaming") {
134
+ throw new Error(`Stream is not streaming: ${args.streamId}`);
135
+ }
136
+ if (stream.state.timeoutFnId) {
137
+ const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
138
+ if (timeoutFn?.state.kind === "pending") {
139
+ await ctx.scheduler.cancel(stream.state.timeoutFnId);
140
+ }
141
+ }
142
+ await ctx.db.patch(args.streamId, {
143
+ state: { kind: "finished", endedAt: Date.now() },
144
+ });
145
+ },
146
+ });
147
+
148
+ async function heartbeatStream(
149
+ ctx: MutationCtx,
150
+ args: { streamId: Id<"streamingMessages"> }
151
+ ) {
152
+ const stream = await ctx.db.get(args.streamId);
153
+ if (!stream) {
154
+ console.warn("Stream not found", args.streamId);
155
+ return;
156
+ }
157
+ if (stream.state.kind !== "streaming") {
158
+ console.warn("Stream is not streaming", args.streamId);
159
+ return;
160
+ }
161
+ if (Date.now() - stream.state.lastHeartbeat < TIMEOUT_INTERVAL / 4) {
162
+ // Debounce heartbeating.
163
+ return;
164
+ }
165
+ if (!stream.state.timeoutFnId) {
166
+ throw new Error("Stream has no timeout function");
167
+ }
168
+ const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
169
+ if (!timeoutFn) {
170
+ throw new Error("Timeout function not found");
171
+ }
172
+ if (timeoutFn.state.kind !== "pending") {
173
+ throw new Error("Timeout function is not pending");
174
+ }
175
+ await ctx.scheduler.cancel(stream.state.timeoutFnId);
176
+ const timeoutFnId = await ctx.scheduler.runAfter(
177
+ TIMEOUT_INTERVAL,
178
+ internal.streams.timeoutStream,
179
+ { streamId: args.streamId }
180
+ );
181
+ await ctx.db.patch(args.streamId, {
182
+ state: {
183
+ kind: "streaming",
184
+ lastHeartbeat: Date.now(),
185
+ timeoutFnId,
186
+ },
187
+ });
188
+ }
189
+
190
+ export const timeoutStream = internalMutation({
191
+ args: { streamId: v.id("streamingMessages") },
192
+ returns: v.null(),
193
+ handler: async (ctx, args) => {
194
+ const stream = await ctx.db.get(args.streamId);
195
+ if (!stream) {
196
+ console.warn("Stream not found", args.streamId);
197
+ return;
198
+ }
199
+ await ctx.db.patch(args.streamId, {
200
+ state: {
201
+ kind: "finished",
202
+ endedAt: Date.now(),
203
+ },
204
+ });
205
+ },
206
+ });
@@ -2,7 +2,7 @@ import { assert, omit, pick } from "convex-helpers";
2
2
  import { paginator } from "convex-helpers/server/pagination";
3
3
  import { partial } from "convex-helpers/validators";
4
4
  import { ObjectType } from "convex/values";
5
- import { paginationResultValidator } from "../validators.js";
5
+ import { vPaginationResult } from "../validators.js";
6
6
  import { api, internal } from "./_generated/api.js";
7
7
  import { Doc } from "./_generated/dataModel.js";
8
8
  import {
@@ -38,7 +38,7 @@ export const getThread = query({
38
38
 
39
39
  export const listThreadsByUserId = query({
40
40
  args: {
41
- userId: v.string(),
41
+ userId: v.optional(v.string()),
42
42
  order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
43
43
  paginationOpts: v.optional(paginationOptsValidator),
44
44
  },
@@ -53,7 +53,7 @@ export const listThreadsByUserId = query({
53
53
  page: threads.page.map(publicThread),
54
54
  };
55
55
  },
56
- returns: paginationResultValidator(vThreadDoc),
56
+ returns: vPaginationResult(vThreadDoc),
57
57
  });
58
58
 
59
59
  const vThread = schema.tables.threads.validator;
@@ -13,7 +13,7 @@ import { schema, v } from "./schema.js";
13
13
  import { deleteMessage } from "./messages.js";
14
14
  import { paginationOptsValidator } from "convex/server";
15
15
  import { stream } from "convex-helpers/server/stream";
16
- import { paginationResultValidator } from "../validators.js";
16
+ import { vPaginationResult } from "../validators.js";
17
17
  import { Id } from "./_generated/dataModel.js";
18
18
 
19
19
  // Note: it only searches for users with threads
@@ -33,7 +33,7 @@ export const listUsersWithThreads = query({
33
33
  page: results.page.map((t) => t.userId).filter((t): t is string => !!t),
34
34
  };
35
35
  },
36
- returns: paginationResultValidator(v.string()),
36
+ returns: vPaginationResult(v.string()),
37
37
  });
38
38
 
39
39
  export const deleteAllForUserId = action({
package/src/mapping.ts CHANGED
@@ -108,7 +108,9 @@ export function serializeNewMessagesInStep<TOOLS extends ToolSet>(
108
108
  ).map(
109
109
  (message): MessageWithMetadata => ({
110
110
  message: serializeMessage(message),
111
- id: message.id,
111
+ // Let's not store the ID by default here. It's being generated internally
112
+ // and not referenced elsewhere that we know of.
113
+ // id: message.id,
112
114
  ...(message.role === "tool" ? toolFields : assistantFields),
113
115
  text: step.text,
114
116
  // fileId: message.fileId,