@convex-dev/agent 0.0.1-alpha.1 → 0.0.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -6
- package/dist/commonjs/client/index.d.ts +520 -96
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +210 -131
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/client/types.d.ts +3 -0
- package/dist/commonjs/client/types.d.ts.map +1 -1
- package/dist/commonjs/component/messages.d.ts +57 -56
- package/dist/commonjs/component/messages.d.ts.map +1 -1
- package/dist/commonjs/component/messages.js +184 -147
- package/dist/commonjs/component/messages.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +260 -256
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +25 -18
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/vector/index.d.ts +4 -4
- package/dist/commonjs/component/vector/index.d.ts.map +1 -1
- package/dist/commonjs/component/vector/index.js +12 -10
- package/dist/commonjs/component/vector/index.js.map +1 -1
- package/dist/commonjs/component/vector/tables.d.ts +3 -3
- package/dist/commonjs/component/vector/tables.js +6 -6
- package/dist/commonjs/component/vector/tables.js.map +1 -1
- package/dist/commonjs/mapping.d.ts +6 -1
- package/dist/commonjs/mapping.d.ts.map +1 -1
- package/dist/commonjs/mapping.js +25 -0
- package/dist/commonjs/mapping.js.map +1 -1
- package/dist/commonjs/validators.d.ts +1376 -1
- package/dist/commonjs/validators.d.ts.map +1 -1
- package/dist/commonjs/validators.js +28 -1
- package/dist/commonjs/validators.js.map +1 -1
- package/dist/esm/client/index.d.ts +520 -96
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +210 -131
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +3 -0
- package/dist/esm/client/types.d.ts.map +1 -1
- package/dist/esm/component/messages.d.ts +57 -56
- package/dist/esm/component/messages.d.ts.map +1 -1
- package/dist/esm/component/messages.js +184 -147
- package/dist/esm/component/messages.js.map +1 -1
- package/dist/esm/component/schema.d.ts +260 -256
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +25 -18
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/vector/index.d.ts +4 -4
- package/dist/esm/component/vector/index.d.ts.map +1 -1
- package/dist/esm/component/vector/index.js +12 -10
- package/dist/esm/component/vector/index.js.map +1 -1
- package/dist/esm/component/vector/tables.d.ts +3 -3
- package/dist/esm/component/vector/tables.js +6 -6
- package/dist/esm/component/vector/tables.js.map +1 -1
- package/dist/esm/mapping.d.ts +6 -1
- package/dist/esm/mapping.d.ts.map +1 -1
- package/dist/esm/mapping.js +25 -0
- package/dist/esm/mapping.js.map +1 -1
- package/dist/esm/validators.d.ts +1376 -1
- package/dist/esm/validators.d.ts.map +1 -1
- package/dist/esm/validators.js +28 -1
- package/dist/esm/validators.js.map +1 -1
- package/package.json +3 -3
- package/src/client/index.ts +384 -276
- package/src/client/types.ts +4 -0
- package/src/component/_generated/api.d.ts +54 -53
- package/src/component/messages.ts +219 -177
- package/src/component/schema.ts +25 -18
- package/src/component/vector/index.ts +13 -11
- package/src/component/vector/tables.ts +6 -6
- package/src/mapping.ts +46 -11
- package/src/validators.test.ts +9 -0
- package/src/validators.ts +36 -1
|
@@ -2,19 +2,14 @@ import { assert, omit, pick } from "convex-helpers";
|
|
|
2
2
|
import { paginator } from "convex-helpers/server/pagination";
|
|
3
3
|
import { mergedStream, stream } from "convex-helpers/server/stream";
|
|
4
4
|
import { nullable, partial } from "convex-helpers/validators";
|
|
5
|
-
import {
|
|
5
|
+
import { ObjectType } from "convex/values";
|
|
6
6
|
import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
MessageWithFileAndId,
|
|
10
|
-
vAssistantMessage,
|
|
11
|
-
vChatStatus,
|
|
8
|
+
vThreadStatus,
|
|
12
9
|
vMessageStatus,
|
|
13
10
|
vMessageWithFileAndId,
|
|
14
11
|
vSearchOptions,
|
|
15
|
-
vStep,
|
|
16
12
|
vStepWithMessagesWithFileAndId,
|
|
17
|
-
vToolMessage,
|
|
18
13
|
} from "../validators.js";
|
|
19
14
|
import { api, internal } from "./_generated/api.js";
|
|
20
15
|
import { Doc, Id } from "./_generated/dataModel.js";
|
|
@@ -35,27 +30,27 @@ import {
|
|
|
35
30
|
vVectorId,
|
|
36
31
|
} from "./vector/tables.js";
|
|
37
32
|
|
|
38
|
-
export const
|
|
39
|
-
args: {
|
|
33
|
+
export const getThread = query({
|
|
34
|
+
args: { threadId: v.id("threads") },
|
|
40
35
|
handler: async (ctx, args) => {
|
|
41
|
-
return ctx.db.get(args.
|
|
36
|
+
return ctx.db.get(args.threadId);
|
|
42
37
|
},
|
|
43
|
-
returns: v.union(v.doc("
|
|
38
|
+
returns: v.union(v.doc("threads"), v.null()),
|
|
44
39
|
});
|
|
45
40
|
|
|
46
|
-
export const
|
|
41
|
+
export const getThreadsByUserId = query({
|
|
47
42
|
args: {
|
|
48
43
|
userId: v.string(),
|
|
49
44
|
// Note: the other arguments cannot change from when the cursor was created.
|
|
50
45
|
cursor: v.optional(v.union(v.string(), v.null())),
|
|
51
46
|
limit: v.optional(v.number()),
|
|
52
47
|
offset: v.optional(v.number()),
|
|
53
|
-
statuses: v.optional(v.array(
|
|
48
|
+
statuses: v.optional(v.array(vThreadStatus)),
|
|
54
49
|
},
|
|
55
50
|
handler: async (ctx, args) => {
|
|
56
51
|
const streams = (args.statuses ?? ["active"]).map((status) =>
|
|
57
52
|
stream(ctx.db, schema)
|
|
58
|
-
.query("
|
|
53
|
+
.query("threads")
|
|
59
54
|
.withIndex("status_userId_order", (q) =>
|
|
60
55
|
q
|
|
61
56
|
.eq("status", status)
|
|
@@ -63,55 +58,55 @@ export const getChatsByUserId = query({
|
|
|
63
58
|
.gte("order", args.offset ?? 0)
|
|
64
59
|
)
|
|
65
60
|
);
|
|
66
|
-
const
|
|
61
|
+
const threads = await mergedStream(streams, ["order"]).paginate({
|
|
67
62
|
numItems: args.limit ?? 100,
|
|
68
63
|
cursor: args.cursor ?? null,
|
|
69
64
|
});
|
|
70
65
|
return {
|
|
71
|
-
|
|
72
|
-
continueCursor:
|
|
73
|
-
isDone:
|
|
66
|
+
threads: threads.page,
|
|
67
|
+
continueCursor: threads.continueCursor,
|
|
68
|
+
isDone: threads.isDone,
|
|
74
69
|
};
|
|
75
70
|
},
|
|
76
71
|
returns: v.object({
|
|
77
|
-
|
|
72
|
+
threads: v.array(v.doc("threads")),
|
|
78
73
|
continueCursor: v.string(),
|
|
79
74
|
isDone: v.boolean(),
|
|
80
75
|
}),
|
|
81
76
|
});
|
|
82
77
|
|
|
83
|
-
const
|
|
84
|
-
const statuses =
|
|
78
|
+
const vThread = schema.tables.threads.validator;
|
|
79
|
+
const statuses = vThread.fields.status.members.map((m) => m.value);
|
|
85
80
|
|
|
86
|
-
export const
|
|
87
|
-
args: omit(
|
|
81
|
+
export const createThread = mutation({
|
|
82
|
+
args: omit(vThread.fields, ["order", "status"]),
|
|
88
83
|
handler: async (ctx, args) => {
|
|
89
84
|
const streams = statuses.map((status) =>
|
|
90
85
|
stream(ctx.db, schema)
|
|
91
|
-
.query("
|
|
86
|
+
.query("threads")
|
|
92
87
|
.withIndex("status_userId_order", (q) =>
|
|
93
88
|
q.eq("status", status).eq("userId", args.userId)
|
|
94
89
|
)
|
|
95
90
|
.order("desc")
|
|
96
91
|
);
|
|
97
|
-
const
|
|
98
|
-
const order = (
|
|
99
|
-
const
|
|
92
|
+
const latestThread = await mergedStream(streams, ["order"]).first();
|
|
93
|
+
const order = (latestThread?.order ?? -1) + 1;
|
|
94
|
+
const threadId = await ctx.db.insert("threads", {
|
|
100
95
|
...args,
|
|
101
96
|
order,
|
|
102
97
|
status: "active",
|
|
103
98
|
});
|
|
104
|
-
return (await ctx.db.get(
|
|
99
|
+
return (await ctx.db.get(threadId))!;
|
|
105
100
|
},
|
|
106
|
-
returns: v.doc("
|
|
101
|
+
returns: v.doc("threads"),
|
|
107
102
|
});
|
|
108
103
|
|
|
109
|
-
export const
|
|
104
|
+
export const updateThread = mutation({
|
|
110
105
|
args: {
|
|
111
|
-
|
|
106
|
+
threadId: v.id("threads"),
|
|
112
107
|
patch: v.object(
|
|
113
108
|
partial(
|
|
114
|
-
pick(
|
|
109
|
+
pick(vThread.fields, [
|
|
115
110
|
"title",
|
|
116
111
|
"summary",
|
|
117
112
|
"defaultSystemPrompt",
|
|
@@ -121,43 +116,43 @@ export const updateChat = mutation({
|
|
|
121
116
|
),
|
|
122
117
|
},
|
|
123
118
|
handler: async (ctx, args) => {
|
|
124
|
-
const
|
|
125
|
-
assert(
|
|
126
|
-
await ctx.db.patch(args.
|
|
127
|
-
return (await ctx.db.get(args.
|
|
119
|
+
const thread = await ctx.db.get(args.threadId);
|
|
120
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
121
|
+
await ctx.db.patch(args.threadId, args.patch);
|
|
122
|
+
return (await ctx.db.get(args.threadId))!;
|
|
128
123
|
},
|
|
129
|
-
returns: v.doc("
|
|
124
|
+
returns: v.doc("threads"),
|
|
130
125
|
});
|
|
131
126
|
|
|
132
|
-
export const
|
|
133
|
-
args: {
|
|
127
|
+
export const archiveThread = mutation({
|
|
128
|
+
args: { threadId: v.id("threads") },
|
|
134
129
|
handler: async (ctx, args) => {
|
|
135
|
-
const
|
|
136
|
-
assert(
|
|
137
|
-
await ctx.db.patch(args.
|
|
138
|
-
return (await ctx.db.get(args.
|
|
130
|
+
const thread = await ctx.db.get(args.threadId);
|
|
131
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
132
|
+
await ctx.db.patch(args.threadId, { status: "archived" });
|
|
133
|
+
return (await ctx.db.get(args.threadId))!;
|
|
139
134
|
},
|
|
140
|
-
returns: v.doc("
|
|
135
|
+
returns: v.doc("threads"),
|
|
141
136
|
});
|
|
142
137
|
|
|
143
138
|
export const deleteAllForUserId = action({
|
|
144
139
|
args: { userId: v.string() },
|
|
145
140
|
handler: async (ctx, args) => {
|
|
146
141
|
let messagesCursor = null;
|
|
147
|
-
let
|
|
142
|
+
let threadsCursor = null;
|
|
148
143
|
let isDone = false;
|
|
149
144
|
while (!isDone) {
|
|
150
145
|
const result: {
|
|
151
146
|
messagesCursor: string;
|
|
152
|
-
|
|
147
|
+
threadsCursor: string | null;
|
|
153
148
|
isDone: boolean;
|
|
154
149
|
} = await ctx.runMutation(internal.messages._deletePageForUserId, {
|
|
155
150
|
userId: args.userId,
|
|
156
151
|
messagesCursor,
|
|
157
|
-
|
|
152
|
+
threadsCursor,
|
|
158
153
|
});
|
|
159
154
|
messagesCursor = result.messagesCursor;
|
|
160
|
-
|
|
155
|
+
threadsCursor = result.threadsCursor;
|
|
161
156
|
isDone = result.isDone;
|
|
162
157
|
}
|
|
163
158
|
},
|
|
@@ -172,7 +167,7 @@ export const deleteAllForUserIdAsync = mutation({
|
|
|
172
167
|
const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
|
|
173
168
|
userId: args.userId,
|
|
174
169
|
messagesCursor: null,
|
|
175
|
-
|
|
170
|
+
threadsCursor: null,
|
|
176
171
|
});
|
|
177
172
|
return isDone;
|
|
178
173
|
},
|
|
@@ -182,12 +177,12 @@ export const deleteAllForUserIdAsync = mutation({
|
|
|
182
177
|
const deleteAllArgs = {
|
|
183
178
|
userId: v.string(),
|
|
184
179
|
messagesCursor: nullable(v.string()),
|
|
185
|
-
|
|
180
|
+
threadsCursor: nullable(v.string()),
|
|
186
181
|
};
|
|
187
182
|
type DeleteAllArgs = ObjectType<typeof deleteAllArgs>;
|
|
188
183
|
const deleteAllReturns = {
|
|
189
184
|
messagesCursor: v.string(),
|
|
190
|
-
|
|
185
|
+
threadsCursor: nullable(v.string()),
|
|
191
186
|
isDone: v.boolean(),
|
|
192
187
|
};
|
|
193
188
|
type DeleteAllReturns = ObjectType<typeof deleteAllReturns>;
|
|
@@ -210,7 +205,7 @@ async function deleteAllFroUserIdAsyncHandler(
|
|
|
210
205
|
{
|
|
211
206
|
userId: args.userId,
|
|
212
207
|
messagesCursor: result.messagesCursor,
|
|
213
|
-
|
|
208
|
+
threadsCursor: result.threadsCursor,
|
|
214
209
|
}
|
|
215
210
|
);
|
|
216
211
|
}
|
|
@@ -228,20 +223,20 @@ async function deletePageForUserId(
|
|
|
228
223
|
): Promise<DeleteAllReturns> {
|
|
229
224
|
const streams = statuses.map((status) =>
|
|
230
225
|
stream(ctx.db, schema)
|
|
231
|
-
.query("
|
|
226
|
+
.query("threads")
|
|
232
227
|
.withIndex("status_userId_order", (q) =>
|
|
233
228
|
q.eq("status", status).eq("userId", args.userId)
|
|
234
229
|
)
|
|
235
230
|
.order("desc")
|
|
236
231
|
);
|
|
237
|
-
const
|
|
238
|
-
const messages = await
|
|
232
|
+
const threadStreams = mergedStream(streams, ["order"]);
|
|
233
|
+
const messages = await threadStreams
|
|
239
234
|
.flatMap(
|
|
240
235
|
async (c) =>
|
|
241
236
|
stream(ctx.db, schema)
|
|
242
237
|
.query("messages")
|
|
243
|
-
.withIndex("
|
|
244
|
-
q.eq("
|
|
238
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
239
|
+
q.eq("threadId", c._id).eq("status", "success")
|
|
245
240
|
),
|
|
246
241
|
["tool", "order", "stepOrder"]
|
|
247
242
|
)
|
|
@@ -251,20 +246,20 @@ async function deletePageForUserId(
|
|
|
251
246
|
});
|
|
252
247
|
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
253
248
|
if (messages.isDone) {
|
|
254
|
-
const
|
|
249
|
+
const threads = await threadStreams.paginate({
|
|
255
250
|
numItems: 100,
|
|
256
|
-
cursor: args.
|
|
251
|
+
cursor: args.threadsCursor ?? null,
|
|
257
252
|
});
|
|
258
|
-
await Promise.all(
|
|
253
|
+
await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
|
|
259
254
|
return {
|
|
260
255
|
messagesCursor: messages.continueCursor,
|
|
261
|
-
|
|
262
|
-
isDone:
|
|
256
|
+
threadsCursor: threads.continueCursor,
|
|
257
|
+
isDone: threads.isDone,
|
|
263
258
|
};
|
|
264
259
|
}
|
|
265
260
|
return {
|
|
266
261
|
messagesCursor: messages.continueCursor,
|
|
267
|
-
|
|
262
|
+
threadsCursor: null,
|
|
268
263
|
isDone: messages.isDone,
|
|
269
264
|
};
|
|
270
265
|
}
|
|
@@ -279,66 +274,66 @@ async function deleteMessage(ctx: MutationCtx, messageDoc: Doc<"messages">) {
|
|
|
279
274
|
}
|
|
280
275
|
}
|
|
281
276
|
|
|
282
|
-
const
|
|
283
|
-
|
|
277
|
+
const deleteThreadArgs = {
|
|
278
|
+
threadId: v.id("threads"),
|
|
284
279
|
cursor: v.optional(v.string()),
|
|
285
280
|
limit: v.optional(v.number()),
|
|
286
281
|
};
|
|
287
|
-
type
|
|
288
|
-
const
|
|
282
|
+
type DeleteThreadArgs = ObjectType<typeof deleteThreadArgs>;
|
|
283
|
+
const deleteThreadReturns = {
|
|
289
284
|
cursor: v.string(),
|
|
290
285
|
isDone: v.boolean(),
|
|
291
286
|
};
|
|
292
|
-
type
|
|
287
|
+
type DeleteThreadReturns = ObjectType<typeof deleteThreadReturns>;
|
|
293
288
|
|
|
294
|
-
export const
|
|
295
|
-
args:
|
|
289
|
+
export const deleteAllForThreadIdSync = action({
|
|
290
|
+
args: deleteThreadArgs,
|
|
296
291
|
handler: async (ctx, args) => {
|
|
297
|
-
const result:
|
|
298
|
-
internal.messages.
|
|
299
|
-
{
|
|
292
|
+
const result: DeleteThreadReturns = await ctx.runMutation(
|
|
293
|
+
internal.messages._deletePageForThreadId,
|
|
294
|
+
{ threadId: args.threadId, cursor: args.cursor, limit: args.limit }
|
|
300
295
|
);
|
|
301
296
|
return result;
|
|
302
297
|
},
|
|
303
|
-
returns:
|
|
298
|
+
returns: deleteThreadReturns,
|
|
304
299
|
});
|
|
305
300
|
|
|
306
|
-
export const
|
|
307
|
-
args:
|
|
301
|
+
export const deleteAllForThreadIdAsync = mutation({
|
|
302
|
+
args: deleteThreadArgs,
|
|
308
303
|
handler: async (ctx, args) => {
|
|
309
|
-
const result = await
|
|
304
|
+
const result = await deletePageForThreadIdHandler(ctx, args);
|
|
310
305
|
if (!result.isDone) {
|
|
311
|
-
await ctx.scheduler.runAfter(0, api.messages.
|
|
312
|
-
|
|
306
|
+
await ctx.scheduler.runAfter(0, api.messages.deleteAllForThreadIdAsync, {
|
|
307
|
+
threadId: args.threadId,
|
|
313
308
|
cursor: result.cursor,
|
|
314
309
|
});
|
|
315
310
|
}
|
|
316
311
|
return result;
|
|
317
312
|
},
|
|
318
|
-
returns:
|
|
313
|
+
returns: deleteThreadReturns,
|
|
319
314
|
});
|
|
320
315
|
|
|
321
|
-
export const
|
|
322
|
-
args:
|
|
323
|
-
handler:
|
|
324
|
-
returns:
|
|
316
|
+
export const _deletePageForThreadId = internalMutation({
|
|
317
|
+
args: deleteThreadArgs,
|
|
318
|
+
handler: deletePageForThreadIdHandler,
|
|
319
|
+
returns: deleteThreadReturns,
|
|
325
320
|
});
|
|
326
321
|
|
|
327
|
-
async function
|
|
322
|
+
async function deletePageForThreadIdHandler(
|
|
328
323
|
ctx: MutationCtx,
|
|
329
|
-
args:
|
|
330
|
-
): Promise<
|
|
324
|
+
args: DeleteThreadArgs
|
|
325
|
+
): Promise<DeleteThreadReturns> {
|
|
331
326
|
const messages = await stream(ctx.db, schema)
|
|
332
327
|
.query("messages")
|
|
333
|
-
.withIndex("
|
|
334
|
-
q.eq("
|
|
328
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
329
|
+
q.eq("threadId", args.threadId).eq("status", "success")
|
|
335
330
|
)
|
|
336
331
|
.paginate({
|
|
337
332
|
numItems: args.limit ?? 100,
|
|
338
333
|
cursor: args.cursor ?? null,
|
|
339
334
|
});
|
|
340
335
|
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
341
|
-
await ctx.db.delete(args.
|
|
336
|
+
await ctx.db.delete(args.threadId);
|
|
342
337
|
return {
|
|
343
338
|
cursor: messages.continueCursor,
|
|
344
339
|
isDone: messages.isDone,
|
|
@@ -377,7 +372,8 @@ export const messageStatuses = vMessageDoc.fields.status.members.map(
|
|
|
377
372
|
);
|
|
378
373
|
|
|
379
374
|
const addMessagesArgs = {
|
|
380
|
-
|
|
375
|
+
userId: v.optional(v.string()),
|
|
376
|
+
threadId: v.optional(v.id("threads")),
|
|
381
377
|
stepId: v.optional(v.id("steps")),
|
|
382
378
|
parentMessageId: v.optional(v.id("messages")),
|
|
383
379
|
messages: v.array(vMessageWithFileAndId),
|
|
@@ -398,15 +394,22 @@ async function addMessagesHandler(
|
|
|
398
394
|
ctx: MutationCtx,
|
|
399
395
|
args: ObjectType<typeof addMessagesArgs>
|
|
400
396
|
) {
|
|
401
|
-
|
|
402
|
-
|
|
397
|
+
let userId = args.userId;
|
|
398
|
+
const threadId = args.threadId;
|
|
399
|
+
if (!userId && args.threadId) {
|
|
400
|
+
const thread = await ctx.db.get(args.threadId);
|
|
401
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
402
|
+
userId = thread._id;
|
|
403
|
+
}
|
|
403
404
|
const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
|
|
404
405
|
args;
|
|
405
|
-
|
|
406
|
+
const parent = parentMessageId && (await ctx.db.get(parentMessageId));
|
|
407
|
+
if (failPendingSteps && parent?.status !== "pending") {
|
|
408
|
+
assert(args.threadId, "threadId is required to fail pending steps");
|
|
406
409
|
const pendingMessages = await ctx.db
|
|
407
410
|
.query("messages")
|
|
408
|
-
.withIndex("
|
|
409
|
-
q.eq("
|
|
411
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
412
|
+
q.eq("threadId", threadId).eq("status", "pending")
|
|
410
413
|
)
|
|
411
414
|
.collect();
|
|
412
415
|
await Promise.all(
|
|
@@ -415,14 +418,8 @@ async function addMessagesHandler(
|
|
|
415
418
|
)
|
|
416
419
|
);
|
|
417
420
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// If the previous message isn't our parent, we make a new thread.
|
|
421
|
-
const threadId =
|
|
422
|
-
parentMessageId && maxMessage?._id === parentMessageId
|
|
423
|
-
? maxMessage.threadId ?? parentMessageId
|
|
424
|
-
: parentMessageId;
|
|
425
|
-
order = maxMessage?.order ?? -1;
|
|
421
|
+
const maxMessage = await getMaxMessage(ctx, threadId, userId);
|
|
422
|
+
let order = maxMessage?.order ?? -1;
|
|
426
423
|
const toReturn: Doc<"messages">[] = [];
|
|
427
424
|
if (messages.length > 0) {
|
|
428
425
|
for (const { message, fileId, id } of messages) {
|
|
@@ -433,8 +430,8 @@ async function addMessagesHandler(
|
|
|
433
430
|
const text = extractText(message);
|
|
434
431
|
const messageId = await ctx.db.insert("messages", {
|
|
435
432
|
...rest,
|
|
436
|
-
|
|
437
|
-
userId
|
|
433
|
+
parentMessageId,
|
|
434
|
+
userId,
|
|
438
435
|
message,
|
|
439
436
|
id,
|
|
440
437
|
order,
|
|
@@ -449,22 +446,41 @@ async function addMessagesHandler(
|
|
|
449
446
|
return { messages: toReturn };
|
|
450
447
|
}
|
|
451
448
|
|
|
452
|
-
async function getMaxMessage(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
449
|
+
async function getMaxMessage(
|
|
450
|
+
ctx: QueryCtx,
|
|
451
|
+
threadId: Id<"threads"> | undefined,
|
|
452
|
+
userId: string | undefined
|
|
453
|
+
) {
|
|
454
|
+
assert(threadId || userId, "One of threadId or userId is required");
|
|
455
|
+
if (threadId) {
|
|
456
|
+
return mergedStream(
|
|
457
|
+
["success" as const, "pending" as const].map((status) =>
|
|
458
|
+
stream(ctx.db, schema)
|
|
459
|
+
.query("messages")
|
|
460
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
461
|
+
q.eq("threadId", threadId).eq("status", status).eq("tool", false)
|
|
462
|
+
)
|
|
463
|
+
.order("desc")
|
|
464
|
+
),
|
|
465
|
+
["order", "stepOrder"]
|
|
466
|
+
).first();
|
|
467
|
+
} else {
|
|
468
|
+
return mergedStream(
|
|
469
|
+
["success" as const, "pending" as const].map((status) =>
|
|
470
|
+
stream(ctx.db, schema)
|
|
471
|
+
.query("messages")
|
|
472
|
+
.withIndex("userId_status_tool_order_stepOrder", (q) =>
|
|
473
|
+
q.eq("userId", userId).eq("status", status).eq("tool", false)
|
|
474
|
+
)
|
|
475
|
+
.order("desc")
|
|
476
|
+
),
|
|
477
|
+
["order", "stepOrder"]
|
|
478
|
+
).first();
|
|
479
|
+
}
|
|
464
480
|
}
|
|
465
481
|
|
|
466
482
|
const addStepsArgs = {
|
|
467
|
-
|
|
483
|
+
threadId: v.id("threads"),
|
|
468
484
|
messageId: v.id("messages"),
|
|
469
485
|
steps: v.array(vStepWithMessagesWithFileAndId),
|
|
470
486
|
failPendingSteps: v.optional(v.boolean()),
|
|
@@ -501,7 +517,7 @@ async function addStepsHandler(
|
|
|
501
517
|
let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
|
|
502
518
|
for (const { step, messages } of args.steps) {
|
|
503
519
|
const stepId = await ctx.db.insert("steps", {
|
|
504
|
-
|
|
520
|
+
threadId: args.threadId,
|
|
505
521
|
parentMessageId: args.messageId,
|
|
506
522
|
order,
|
|
507
523
|
stepOrder: nextStepOrder,
|
|
@@ -509,7 +525,7 @@ async function addStepsHandler(
|
|
|
509
525
|
step,
|
|
510
526
|
});
|
|
511
527
|
await addMessagesHandler(ctx, {
|
|
512
|
-
|
|
528
|
+
threadId: args.threadId,
|
|
513
529
|
parentMessageId: args.messageId,
|
|
514
530
|
stepId,
|
|
515
531
|
messages,
|
|
@@ -518,7 +534,8 @@ async function addStepsHandler(
|
|
|
518
534
|
pending: step.finishReason === "stop" ? false : true,
|
|
519
535
|
failPendingSteps: false,
|
|
520
536
|
});
|
|
521
|
-
if
|
|
537
|
+
// We don't commit if the parent is still pending.
|
|
538
|
+
if (step.finishReason === "stop" && parentMessage.status === "success") {
|
|
522
539
|
await commitMessageHandler(ctx, { messageId: args.messageId });
|
|
523
540
|
}
|
|
524
541
|
steps.push((await ctx.db.get(stepId))!);
|
|
@@ -573,9 +590,9 @@ async function commitMessageHandler(
|
|
|
573
590
|
[true, false].map((tool) =>
|
|
574
591
|
stream(ctx.db, schema)
|
|
575
592
|
.query("messages")
|
|
576
|
-
.withIndex("
|
|
593
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
577
594
|
q
|
|
578
|
-
.eq("
|
|
595
|
+
.eq("threadId", message.threadId)
|
|
579
596
|
.eq("status", "pending")
|
|
580
597
|
.eq("tool", tool)
|
|
581
598
|
.eq("order", order)
|
|
@@ -585,12 +602,13 @@ async function commitMessageHandler(
|
|
|
585
602
|
).collect();
|
|
586
603
|
for (const message of messages) {
|
|
587
604
|
await ctx.db.patch(message._id, { status: "success" });
|
|
605
|
+
// TODO: recursively commit steps & messages that might depend on this one.
|
|
588
606
|
}
|
|
589
607
|
}
|
|
590
608
|
|
|
591
|
-
export const
|
|
609
|
+
export const getThreadMessages = query({
|
|
592
610
|
args: {
|
|
593
|
-
|
|
611
|
+
threadId: v.id("threads"),
|
|
594
612
|
isTool: v.optional(v.boolean()),
|
|
595
613
|
order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
|
|
596
614
|
limit: v.optional(v.number()),
|
|
@@ -610,9 +628,9 @@ export const getChatMessages = query({
|
|
|
610
628
|
statuses.map((status) =>
|
|
611
629
|
stream(ctx.db, schema)
|
|
612
630
|
.query("messages")
|
|
613
|
-
.withIndex("
|
|
631
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => {
|
|
614
632
|
const qq = q
|
|
615
|
-
.eq("
|
|
633
|
+
.eq("threadId", args.threadId)
|
|
616
634
|
.eq("status", status)
|
|
617
635
|
.eq("tool", tool);
|
|
618
636
|
if (parent) {
|
|
@@ -646,19 +664,19 @@ export const getChatMessages = query({
|
|
|
646
664
|
export const searchMessages = action({
|
|
647
665
|
args: {
|
|
648
666
|
userId: v.optional(v.string()),
|
|
649
|
-
|
|
667
|
+
threadId: v.optional(v.id("threads")),
|
|
650
668
|
parentMessageId: v.optional(v.id("messages")),
|
|
651
669
|
...vSearchOptions.fields,
|
|
652
670
|
},
|
|
653
671
|
returns: v.array(v.doc("messages")),
|
|
654
672
|
handler: async (ctx, args): Promise<Doc<"messages">[]> => {
|
|
655
|
-
assert(args.userId || args.
|
|
673
|
+
assert(args.userId || args.threadId, "Specify userId or threadId");
|
|
656
674
|
const limit = args.limit;
|
|
657
675
|
let textSearchMessages: Doc<"messages">[] | undefined;
|
|
658
676
|
if (args.text) {
|
|
659
677
|
textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
|
|
660
678
|
userId: args.userId,
|
|
661
|
-
|
|
679
|
+
threadId: args.threadId,
|
|
662
680
|
text: args.text,
|
|
663
681
|
limit,
|
|
664
682
|
});
|
|
@@ -675,8 +693,8 @@ export const searchMessages = action({
|
|
|
675
693
|
vector: args.vector,
|
|
676
694
|
filter: (q) =>
|
|
677
695
|
args.userId
|
|
678
|
-
? q.eq("model_kind_userId", [model, "
|
|
679
|
-
: q.eq("
|
|
696
|
+
? q.eq("model_kind_userId", [model, "thread", args.userId])
|
|
697
|
+
: q.eq("model_kind_threadId", [model, "thread", args.threadId!]),
|
|
680
698
|
limit,
|
|
681
699
|
})
|
|
682
700
|
).filter((v) => v._score > 0.5);
|
|
@@ -697,7 +715,7 @@ export const searchMessages = action({
|
|
|
697
715
|
internal.messages._fetchVectorMessages,
|
|
698
716
|
{
|
|
699
717
|
userId: args.userId,
|
|
700
|
-
|
|
718
|
+
threadId: args.threadId,
|
|
701
719
|
vectorIds,
|
|
702
720
|
textSearchMessages: textSearchMessages?.filter(
|
|
703
721
|
(m) => !vectorIds.includes(m.embeddingId!)
|
|
@@ -716,7 +734,7 @@ export const searchMessages = action({
|
|
|
716
734
|
export const _fetchVectorMessages = internalQuery({
|
|
717
735
|
args: {
|
|
718
736
|
userId: v.optional(v.string()),
|
|
719
|
-
|
|
737
|
+
threadId: v.optional(v.id("threads")),
|
|
720
738
|
vectorIds: v.array(vVectorId),
|
|
721
739
|
textSearchMessages: v.optional(v.array(v.doc("messages"))),
|
|
722
740
|
messageRange: v.object({ before: v.number(), after: v.number() }),
|
|
@@ -727,18 +745,19 @@ export const _fetchVectorMessages = internalQuery({
|
|
|
727
745
|
handler: async (ctx, args): Promise<Doc<"messages">[]> => {
|
|
728
746
|
const parent =
|
|
729
747
|
args.parentMessageId && (await ctx.db.get(args.parentMessageId));
|
|
748
|
+
const { userId, threadId } = args;
|
|
749
|
+
assert(userId || threadId, "Specify userId or threadId to search");
|
|
730
750
|
let messages = (
|
|
731
751
|
await Promise.all(
|
|
732
752
|
args.vectorIds.map((embeddingId) =>
|
|
733
753
|
ctx.db
|
|
734
754
|
.query("messages")
|
|
735
755
|
.withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
|
|
736
|
-
.filter(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
q.eq("chatId", args.chatId as any) // not sure why it's failing...
|
|
756
|
+
.filter((q) =>
|
|
757
|
+
userId
|
|
758
|
+
? q.eq("userId", userId)
|
|
759
|
+
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
760
|
+
q.eq("threadId", threadId as any)
|
|
742
761
|
)
|
|
743
762
|
.first()
|
|
744
763
|
)
|
|
@@ -752,48 +771,71 @@ export const _fetchVectorMessages = internalQuery({
|
|
|
752
771
|
messages.sort((a, b) => a.order! - b.order!);
|
|
753
772
|
messages = messages.slice(0, args.limit);
|
|
754
773
|
// Fetch the surrounding messages
|
|
755
|
-
|
|
774
|
+
if (!threadId) {
|
|
775
|
+
return messages.sort((a, b) => a.order - b.order);
|
|
776
|
+
}
|
|
777
|
+
const included: Record<string, Set<number>> = {};
|
|
756
778
|
for (const m of messages) {
|
|
757
|
-
|
|
758
|
-
|
|
779
|
+
const searchId = m.threadId ?? m.userId!;
|
|
780
|
+
if (!included[searchId]) {
|
|
781
|
+
included[searchId] = new Set();
|
|
759
782
|
}
|
|
760
|
-
included[
|
|
783
|
+
included[searchId].add(m.order!);
|
|
761
784
|
}
|
|
762
|
-
const ranges: Record<
|
|
785
|
+
const ranges: Record<string, Doc<"messages">[]> = {};
|
|
763
786
|
const { before, after } = args.messageRange;
|
|
764
787
|
for (const m of messages) {
|
|
788
|
+
const searchId = m.threadId ?? m.userId!;
|
|
765
789
|
const order = m.order!;
|
|
766
790
|
let earliest = order - before;
|
|
767
791
|
let latest = order + after;
|
|
768
792
|
for (; earliest <= latest; earliest++) {
|
|
769
|
-
if (!included[
|
|
793
|
+
if (!included[searchId].has(earliest)) {
|
|
770
794
|
break;
|
|
771
795
|
}
|
|
772
796
|
}
|
|
773
797
|
for (; latest >= earliest; latest--) {
|
|
774
|
-
if (!included[
|
|
798
|
+
if (!included[searchId].has(latest)) {
|
|
775
799
|
break;
|
|
776
800
|
}
|
|
777
801
|
}
|
|
778
802
|
for (let i = earliest; i <= latest; i++) {
|
|
779
|
-
included[
|
|
803
|
+
included[searchId].add(i);
|
|
780
804
|
}
|
|
781
805
|
if (earliest !== latest) {
|
|
782
|
-
|
|
783
|
-
.
|
|
784
|
-
|
|
785
|
-
q
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
ranges[
|
|
806
|
+
if (m.threadId) {
|
|
807
|
+
const surrounding = await ctx.db
|
|
808
|
+
.query("messages")
|
|
809
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
810
|
+
q
|
|
811
|
+
.eq("threadId", m.threadId)
|
|
812
|
+
.eq("status", "success")
|
|
813
|
+
.eq("tool", false)
|
|
814
|
+
.gt("order", earliest)
|
|
815
|
+
.lt("order", latest)
|
|
816
|
+
)
|
|
817
|
+
.collect();
|
|
818
|
+
if (!ranges[searchId]) {
|
|
819
|
+
ranges[searchId] = [];
|
|
820
|
+
}
|
|
821
|
+
ranges[searchId].push(...surrounding);
|
|
822
|
+
} else {
|
|
823
|
+
const surrounding = await ctx.db
|
|
824
|
+
.query("messages")
|
|
825
|
+
.withIndex("userId_status_tool_order_stepOrder", (q) =>
|
|
826
|
+
q
|
|
827
|
+
.eq("userId", m.userId!)
|
|
828
|
+
.eq("status", "success")
|
|
829
|
+
.eq("tool", false)
|
|
830
|
+
.gt("order", earliest)
|
|
831
|
+
.lt("order", latest)
|
|
832
|
+
)
|
|
833
|
+
.collect();
|
|
834
|
+
if (!ranges[searchId]) {
|
|
835
|
+
ranges[searchId] = [];
|
|
836
|
+
}
|
|
837
|
+
ranges[searchId].push(...surrounding);
|
|
795
838
|
}
|
|
796
|
-
ranges[m.chatId].push(...surrounding);
|
|
797
839
|
}
|
|
798
840
|
}
|
|
799
841
|
return Object.values(ranges)
|
|
@@ -806,19 +848,19 @@ export const _fetchVectorMessages = internalQuery({
|
|
|
806
848
|
// excluding duplicates in later ranges.
|
|
807
849
|
export const textSearch = query({
|
|
808
850
|
args: {
|
|
809
|
-
|
|
851
|
+
threadId: v.optional(v.id("threads")),
|
|
810
852
|
userId: v.optional(v.string()),
|
|
811
853
|
text: v.string(),
|
|
812
854
|
limit: v.number(),
|
|
813
855
|
},
|
|
814
856
|
handler: async (ctx, args) => {
|
|
815
|
-
assert(args.userId || args.
|
|
857
|
+
assert(args.userId || args.threadId, "Specify userId or threadId");
|
|
816
858
|
const messages = await ctx.db
|
|
817
859
|
.query("messages")
|
|
818
860
|
.withSearchIndex("text_search", (q) =>
|
|
819
861
|
args.userId
|
|
820
862
|
? q.search("text", args.text).eq("userId", args.userId)
|
|
821
|
-
: q.search("text", args.text).eq("
|
|
863
|
+
: q.search("text", args.text).eq("threadId", args.threadId!)
|
|
822
864
|
)
|
|
823
865
|
.take(args.limit);
|
|
824
866
|
return messages;
|
|
@@ -871,9 +913,9 @@ export const textSearch = query({
|
|
|
871
913
|
|
|
872
914
|
// const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
|
|
873
915
|
|
|
874
|
-
// export const
|
|
916
|
+
// export const getThreadMessagesPage = query({
|
|
875
917
|
// args: {
|
|
876
|
-
//
|
|
918
|
+
// parentMessageId: v.string(),
|
|
877
919
|
// selectBy: v.optional(vSelectBy),
|
|
878
920
|
// // Unimplemented and as far I can tell no storage provider has either.
|
|
879
921
|
// // memoryConfig: v.optional(vMemoryConfig),
|
|
@@ -881,7 +923,7 @@ export const textSearch = query({
|
|
|
881
923
|
// handler: async (ctx, args): Promise<SerializedMessage[]> => {
|
|
882
924
|
// const messages = await ctx.db
|
|
883
925
|
// .query("messages")
|
|
884
|
-
// .withIndex("
|
|
926
|
+
// .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
|
|
885
927
|
// .order("desc")
|
|
886
928
|
// .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
|
|
887
929
|
|
|
@@ -935,9 +977,9 @@ export const textSearch = query({
|
|
|
935
977
|
// ranges.map(async (range) => {
|
|
936
978
|
// return await ctx.db
|
|
937
979
|
// .query("messages")
|
|
938
|
-
// .withIndex("
|
|
980
|
+
// .withIndex("parentMessageId", (q) =>
|
|
939
981
|
// q
|
|
940
|
-
// .eq("
|
|
982
|
+
// .eq("parentMessageId", args.parentMessageId)
|
|
941
983
|
// .gte("threadOrder", range.start)
|
|
942
984
|
// .lte("threadOrder", range.end)
|
|
943
985
|
// )
|
|
@@ -954,21 +996,21 @@ export const textSearch = query({
|
|
|
954
996
|
// export const saveMessages = mutation({
|
|
955
997
|
// args: { messages: v.array(vSerializedMessage) },
|
|
956
998
|
// handler: async (ctx, args) => {
|
|
957
|
-
// const
|
|
999
|
+
// const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
|
|
958
1000
|
// for (const message of args.messages) {
|
|
959
|
-
//
|
|
960
|
-
// ...(
|
|
1001
|
+
// messagesByParentMessageId[message.parentMessageId] = [
|
|
1002
|
+
// ...(messagesByParentMessageId[message.parentMessageId] ?? []),
|
|
961
1003
|
// message,
|
|
962
1004
|
// ];
|
|
963
1005
|
// }
|
|
964
|
-
// for (const
|
|
1006
|
+
// for (const parentMessageId in messagesByParentMessageId) {
|
|
965
1007
|
// const lastMessage = await ctx.db
|
|
966
1008
|
// .query("messages")
|
|
967
|
-
// .withIndex("
|
|
1009
|
+
// .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
|
|
968
1010
|
// .order("desc")
|
|
969
1011
|
// .first();
|
|
970
1012
|
// let threadOrder = lastMessage?.threadOrder ?? 0;
|
|
971
|
-
// for (const message of
|
|
1013
|
+
// for (const message of messagesByParentMessageId[parentMessageId]) {
|
|
972
1014
|
// threadOrder++;
|
|
973
1015
|
// await ctx.db.insert("messages", {
|
|
974
1016
|
// ...message,
|