@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
|
@@ -3,74 +3,74 @@ 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
5
|
import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
|
|
6
|
-
import {
|
|
6
|
+
import { vThreadStatus, vMessageStatus, vMessageWithFileAndId, vSearchOptions, vStepWithMessagesWithFileAndId, } from "../validators.js";
|
|
7
7
|
import { api, internal } from "./_generated/api.js";
|
|
8
8
|
import { action, internalMutation, internalQuery, mutation, query, } from "./_generated/server.js";
|
|
9
9
|
import { schema, v } from "./schema.js";
|
|
10
10
|
import { getVectorTableName, VectorDimensions, vVectorId, } from "./vector/tables.js";
|
|
11
|
-
export const
|
|
12
|
-
args: {
|
|
11
|
+
export const getThread = query({
|
|
12
|
+
args: { threadId: v.id("threads") },
|
|
13
13
|
handler: async (ctx, args) => {
|
|
14
|
-
return ctx.db.get(args.
|
|
14
|
+
return ctx.db.get(args.threadId);
|
|
15
15
|
},
|
|
16
|
-
returns: v.union(v.doc("
|
|
16
|
+
returns: v.union(v.doc("threads"), v.null()),
|
|
17
17
|
});
|
|
18
|
-
export const
|
|
18
|
+
export const getThreadsByUserId = query({
|
|
19
19
|
args: {
|
|
20
20
|
userId: v.string(),
|
|
21
21
|
// Note: the other arguments cannot change from when the cursor was created.
|
|
22
22
|
cursor: v.optional(v.union(v.string(), v.null())),
|
|
23
23
|
limit: v.optional(v.number()),
|
|
24
24
|
offset: v.optional(v.number()),
|
|
25
|
-
statuses: v.optional(v.array(
|
|
25
|
+
statuses: v.optional(v.array(vThreadStatus)),
|
|
26
26
|
},
|
|
27
27
|
handler: async (ctx, args) => {
|
|
28
28
|
const streams = (args.statuses ?? ["active"]).map((status) => stream(ctx.db, schema)
|
|
29
|
-
.query("
|
|
29
|
+
.query("threads")
|
|
30
30
|
.withIndex("status_userId_order", (q) => q
|
|
31
31
|
.eq("status", status)
|
|
32
32
|
.eq("userId", args.userId)
|
|
33
33
|
.gte("order", args.offset ?? 0)));
|
|
34
|
-
const
|
|
34
|
+
const threads = await mergedStream(streams, ["order"]).paginate({
|
|
35
35
|
numItems: args.limit ?? 100,
|
|
36
36
|
cursor: args.cursor ?? null,
|
|
37
37
|
});
|
|
38
38
|
return {
|
|
39
|
-
|
|
40
|
-
continueCursor:
|
|
41
|
-
isDone:
|
|
39
|
+
threads: threads.page,
|
|
40
|
+
continueCursor: threads.continueCursor,
|
|
41
|
+
isDone: threads.isDone,
|
|
42
42
|
};
|
|
43
43
|
},
|
|
44
44
|
returns: v.object({
|
|
45
|
-
|
|
45
|
+
threads: v.array(v.doc("threads")),
|
|
46
46
|
continueCursor: v.string(),
|
|
47
47
|
isDone: v.boolean(),
|
|
48
48
|
}),
|
|
49
49
|
});
|
|
50
|
-
const
|
|
51
|
-
const statuses =
|
|
52
|
-
export const
|
|
53
|
-
args: omit(
|
|
50
|
+
const vThread = schema.tables.threads.validator;
|
|
51
|
+
const statuses = vThread.fields.status.members.map((m) => m.value);
|
|
52
|
+
export const createThread = mutation({
|
|
53
|
+
args: omit(vThread.fields, ["order", "status"]),
|
|
54
54
|
handler: async (ctx, args) => {
|
|
55
55
|
const streams = statuses.map((status) => stream(ctx.db, schema)
|
|
56
|
-
.query("
|
|
56
|
+
.query("threads")
|
|
57
57
|
.withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
|
|
58
58
|
.order("desc"));
|
|
59
|
-
const
|
|
60
|
-
const order = (
|
|
61
|
-
const
|
|
59
|
+
const latestThread = await mergedStream(streams, ["order"]).first();
|
|
60
|
+
const order = (latestThread?.order ?? -1) + 1;
|
|
61
|
+
const threadId = await ctx.db.insert("threads", {
|
|
62
62
|
...args,
|
|
63
63
|
order,
|
|
64
64
|
status: "active",
|
|
65
65
|
});
|
|
66
|
-
return (await ctx.db.get(
|
|
66
|
+
return (await ctx.db.get(threadId));
|
|
67
67
|
},
|
|
68
|
-
returns: v.doc("
|
|
68
|
+
returns: v.doc("threads"),
|
|
69
69
|
});
|
|
70
|
-
export const
|
|
70
|
+
export const updateThread = mutation({
|
|
71
71
|
args: {
|
|
72
|
-
|
|
73
|
-
patch: v.object(partial(pick(
|
|
72
|
+
threadId: v.id("threads"),
|
|
73
|
+
patch: v.object(partial(pick(vThread.fields, [
|
|
74
74
|
"title",
|
|
75
75
|
"summary",
|
|
76
76
|
"defaultSystemPrompt",
|
|
@@ -78,37 +78,37 @@ export const updateChat = mutation({
|
|
|
78
78
|
]))),
|
|
79
79
|
},
|
|
80
80
|
handler: async (ctx, args) => {
|
|
81
|
-
const
|
|
82
|
-
assert(
|
|
83
|
-
await ctx.db.patch(args.
|
|
84
|
-
return (await ctx.db.get(args.
|
|
81
|
+
const thread = await ctx.db.get(args.threadId);
|
|
82
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
83
|
+
await ctx.db.patch(args.threadId, args.patch);
|
|
84
|
+
return (await ctx.db.get(args.threadId));
|
|
85
85
|
},
|
|
86
|
-
returns: v.doc("
|
|
86
|
+
returns: v.doc("threads"),
|
|
87
87
|
});
|
|
88
|
-
export const
|
|
89
|
-
args: {
|
|
88
|
+
export const archiveThread = mutation({
|
|
89
|
+
args: { threadId: v.id("threads") },
|
|
90
90
|
handler: async (ctx, args) => {
|
|
91
|
-
const
|
|
92
|
-
assert(
|
|
93
|
-
await ctx.db.patch(args.
|
|
94
|
-
return (await ctx.db.get(args.
|
|
91
|
+
const thread = await ctx.db.get(args.threadId);
|
|
92
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
93
|
+
await ctx.db.patch(args.threadId, { status: "archived" });
|
|
94
|
+
return (await ctx.db.get(args.threadId));
|
|
95
95
|
},
|
|
96
|
-
returns: v.doc("
|
|
96
|
+
returns: v.doc("threads"),
|
|
97
97
|
});
|
|
98
98
|
export const deleteAllForUserId = action({
|
|
99
99
|
args: { userId: v.string() },
|
|
100
100
|
handler: async (ctx, args) => {
|
|
101
101
|
let messagesCursor = null;
|
|
102
|
-
let
|
|
102
|
+
let threadsCursor = null;
|
|
103
103
|
let isDone = false;
|
|
104
104
|
while (!isDone) {
|
|
105
105
|
const result = await ctx.runMutation(internal.messages._deletePageForUserId, {
|
|
106
106
|
userId: args.userId,
|
|
107
107
|
messagesCursor,
|
|
108
|
-
|
|
108
|
+
threadsCursor,
|
|
109
109
|
});
|
|
110
110
|
messagesCursor = result.messagesCursor;
|
|
111
|
-
|
|
111
|
+
threadsCursor = result.threadsCursor;
|
|
112
112
|
isDone = result.isDone;
|
|
113
113
|
}
|
|
114
114
|
},
|
|
@@ -122,7 +122,7 @@ export const deleteAllForUserIdAsync = mutation({
|
|
|
122
122
|
const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
|
|
123
123
|
userId: args.userId,
|
|
124
124
|
messagesCursor: null,
|
|
125
|
-
|
|
125
|
+
threadsCursor: null,
|
|
126
126
|
});
|
|
127
127
|
return isDone;
|
|
128
128
|
},
|
|
@@ -131,11 +131,11 @@ export const deleteAllForUserIdAsync = mutation({
|
|
|
131
131
|
const deleteAllArgs = {
|
|
132
132
|
userId: v.string(),
|
|
133
133
|
messagesCursor: nullable(v.string()),
|
|
134
|
-
|
|
134
|
+
threadsCursor: nullable(v.string()),
|
|
135
135
|
};
|
|
136
136
|
const deleteAllReturns = {
|
|
137
137
|
messagesCursor: v.string(),
|
|
138
|
-
|
|
138
|
+
threadsCursor: nullable(v.string()),
|
|
139
139
|
isDone: v.boolean(),
|
|
140
140
|
};
|
|
141
141
|
export const _deleteAllForUserIdAsync = internalMutation({
|
|
@@ -149,7 +149,7 @@ async function deleteAllFroUserIdAsyncHandler(ctx, args) {
|
|
|
149
149
|
await ctx.scheduler.runAfter(0, internal.messages._deleteAllForUserIdAsync, {
|
|
150
150
|
userId: args.userId,
|
|
151
151
|
messagesCursor: result.messagesCursor,
|
|
152
|
-
|
|
152
|
+
threadsCursor: result.threadsCursor,
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
return result.isDone;
|
|
@@ -161,34 +161,34 @@ export const _deletePageForUserId = internalMutation({
|
|
|
161
161
|
});
|
|
162
162
|
async function deletePageForUserId(ctx, args) {
|
|
163
163
|
const streams = statuses.map((status) => stream(ctx.db, schema)
|
|
164
|
-
.query("
|
|
164
|
+
.query("threads")
|
|
165
165
|
.withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
|
|
166
166
|
.order("desc"));
|
|
167
|
-
const
|
|
168
|
-
const messages = await
|
|
167
|
+
const threadStreams = mergedStream(streams, ["order"]);
|
|
168
|
+
const messages = await threadStreams
|
|
169
169
|
.flatMap(async (c) => stream(ctx.db, schema)
|
|
170
170
|
.query("messages")
|
|
171
|
-
.withIndex("
|
|
171
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", c._id).eq("status", "success")), ["tool", "order", "stepOrder"])
|
|
172
172
|
.paginate({
|
|
173
173
|
numItems: 100,
|
|
174
174
|
cursor: args.messagesCursor ?? null,
|
|
175
175
|
});
|
|
176
176
|
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
177
177
|
if (messages.isDone) {
|
|
178
|
-
const
|
|
178
|
+
const threads = await threadStreams.paginate({
|
|
179
179
|
numItems: 100,
|
|
180
|
-
cursor: args.
|
|
180
|
+
cursor: args.threadsCursor ?? null,
|
|
181
181
|
});
|
|
182
|
-
await Promise.all(
|
|
182
|
+
await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
|
|
183
183
|
return {
|
|
184
184
|
messagesCursor: messages.continueCursor,
|
|
185
|
-
|
|
186
|
-
isDone:
|
|
185
|
+
threadsCursor: threads.continueCursor,
|
|
186
|
+
isDone: threads.isDone,
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
return {
|
|
190
190
|
messagesCursor: messages.continueCursor,
|
|
191
|
-
|
|
191
|
+
threadsCursor: null,
|
|
192
192
|
isDone: messages.isDone,
|
|
193
193
|
};
|
|
194
194
|
}
|
|
@@ -201,52 +201,52 @@ async function deleteMessage(ctx, messageDoc) {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
204
|
+
const deleteThreadArgs = {
|
|
205
|
+
threadId: v.id("threads"),
|
|
206
206
|
cursor: v.optional(v.string()),
|
|
207
207
|
limit: v.optional(v.number()),
|
|
208
208
|
};
|
|
209
|
-
const
|
|
209
|
+
const deleteThreadReturns = {
|
|
210
210
|
cursor: v.string(),
|
|
211
211
|
isDone: v.boolean(),
|
|
212
212
|
};
|
|
213
|
-
export const
|
|
214
|
-
args:
|
|
213
|
+
export const deleteAllForThreadIdSync = action({
|
|
214
|
+
args: deleteThreadArgs,
|
|
215
215
|
handler: async (ctx, args) => {
|
|
216
|
-
const result = await ctx.runMutation(internal.messages.
|
|
216
|
+
const result = await ctx.runMutation(internal.messages._deletePageForThreadId, { threadId: args.threadId, cursor: args.cursor, limit: args.limit });
|
|
217
217
|
return result;
|
|
218
218
|
},
|
|
219
|
-
returns:
|
|
219
|
+
returns: deleteThreadReturns,
|
|
220
220
|
});
|
|
221
|
-
export const
|
|
222
|
-
args:
|
|
221
|
+
export const deleteAllForThreadIdAsync = mutation({
|
|
222
|
+
args: deleteThreadArgs,
|
|
223
223
|
handler: async (ctx, args) => {
|
|
224
|
-
const result = await
|
|
224
|
+
const result = await deletePageForThreadIdHandler(ctx, args);
|
|
225
225
|
if (!result.isDone) {
|
|
226
|
-
await ctx.scheduler.runAfter(0, api.messages.
|
|
227
|
-
|
|
226
|
+
await ctx.scheduler.runAfter(0, api.messages.deleteAllForThreadIdAsync, {
|
|
227
|
+
threadId: args.threadId,
|
|
228
228
|
cursor: result.cursor,
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
return result;
|
|
232
232
|
},
|
|
233
|
-
returns:
|
|
233
|
+
returns: deleteThreadReturns,
|
|
234
234
|
});
|
|
235
|
-
export const
|
|
236
|
-
args:
|
|
237
|
-
handler:
|
|
238
|
-
returns:
|
|
235
|
+
export const _deletePageForThreadId = internalMutation({
|
|
236
|
+
args: deleteThreadArgs,
|
|
237
|
+
handler: deletePageForThreadIdHandler,
|
|
238
|
+
returns: deleteThreadReturns,
|
|
239
239
|
});
|
|
240
|
-
async function
|
|
240
|
+
async function deletePageForThreadIdHandler(ctx, args) {
|
|
241
241
|
const messages = await stream(ctx.db, schema)
|
|
242
242
|
.query("messages")
|
|
243
|
-
.withIndex("
|
|
243
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", args.threadId).eq("status", "success"))
|
|
244
244
|
.paginate({
|
|
245
245
|
numItems: args.limit ?? 100,
|
|
246
246
|
cursor: args.cursor ?? null,
|
|
247
247
|
});
|
|
248
248
|
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
249
|
-
await ctx.db.delete(args.
|
|
249
|
+
await ctx.db.delete(args.threadId);
|
|
250
250
|
return {
|
|
251
251
|
cursor: messages.continueCursor,
|
|
252
252
|
isDone: messages.isDone,
|
|
@@ -280,7 +280,8 @@ export const getFilesToDelete = query({
|
|
|
280
280
|
export const vMessageDoc = schema.tables.messages.validator;
|
|
281
281
|
export const messageStatuses = vMessageDoc.fields.status.members.map((m) => m.value);
|
|
282
282
|
const addMessagesArgs = {
|
|
283
|
-
|
|
283
|
+
userId: v.optional(v.string()),
|
|
284
|
+
threadId: v.optional(v.id("threads")),
|
|
284
285
|
stepId: v.optional(v.id("steps")),
|
|
285
286
|
parentMessageId: v.optional(v.id("messages")),
|
|
286
287
|
messages: v.array(vMessageWithFileAndId),
|
|
@@ -298,23 +299,25 @@ export const addMessages = mutation({
|
|
|
298
299
|
}),
|
|
299
300
|
});
|
|
300
301
|
async function addMessagesHandler(ctx, args) {
|
|
301
|
-
|
|
302
|
-
|
|
302
|
+
let userId = args.userId;
|
|
303
|
+
const threadId = args.threadId;
|
|
304
|
+
if (!userId && args.threadId) {
|
|
305
|
+
const thread = await ctx.db.get(args.threadId);
|
|
306
|
+
assert(thread, `Thread ${args.threadId} not found`);
|
|
307
|
+
userId = thread._id;
|
|
308
|
+
}
|
|
303
309
|
const { failPendingSteps, pending, messages, parentMessageId, ...rest } = args;
|
|
304
|
-
|
|
310
|
+
const parent = parentMessageId && (await ctx.db.get(parentMessageId));
|
|
311
|
+
if (failPendingSteps && parent?.status !== "pending") {
|
|
312
|
+
assert(args.threadId, "threadId is required to fail pending steps");
|
|
305
313
|
const pendingMessages = await ctx.db
|
|
306
314
|
.query("messages")
|
|
307
|
-
.withIndex("
|
|
315
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", threadId).eq("status", "pending"))
|
|
308
316
|
.collect();
|
|
309
317
|
await Promise.all(pendingMessages.map((m) => ctx.db.patch(m._id, { status: "failed", text: "Restarting" })));
|
|
310
318
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// If the previous message isn't our parent, we make a new thread.
|
|
314
|
-
const threadId = parentMessageId && maxMessage?._id === parentMessageId
|
|
315
|
-
? maxMessage.threadId ?? parentMessageId
|
|
316
|
-
: parentMessageId;
|
|
317
|
-
order = maxMessage?.order ?? -1;
|
|
319
|
+
const maxMessage = await getMaxMessage(ctx, threadId, userId);
|
|
320
|
+
let order = maxMessage?.order ?? -1;
|
|
318
321
|
const toReturn = [];
|
|
319
322
|
if (messages.length > 0) {
|
|
320
323
|
for (const { message, fileId, id } of messages) {
|
|
@@ -325,8 +328,8 @@ async function addMessagesHandler(ctx, args) {
|
|
|
325
328
|
const text = extractText(message);
|
|
326
329
|
const messageId = await ctx.db.insert("messages", {
|
|
327
330
|
...rest,
|
|
328
|
-
|
|
329
|
-
userId
|
|
331
|
+
parentMessageId,
|
|
332
|
+
userId,
|
|
330
333
|
message,
|
|
331
334
|
id,
|
|
332
335
|
order,
|
|
@@ -340,14 +343,23 @@ async function addMessagesHandler(ctx, args) {
|
|
|
340
343
|
}
|
|
341
344
|
return { messages: toReturn };
|
|
342
345
|
}
|
|
343
|
-
async function getMaxMessage(ctx,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
async function getMaxMessage(ctx, threadId, userId) {
|
|
347
|
+
assert(threadId || userId, "One of threadId or userId is required");
|
|
348
|
+
if (threadId) {
|
|
349
|
+
return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
|
|
350
|
+
.query("messages")
|
|
351
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", threadId).eq("status", status).eq("tool", false))
|
|
352
|
+
.order("desc")), ["order", "stepOrder"]).first();
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
|
|
356
|
+
.query("messages")
|
|
357
|
+
.withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", status).eq("tool", false))
|
|
358
|
+
.order("desc")), ["order", "stepOrder"]).first();
|
|
359
|
+
}
|
|
348
360
|
}
|
|
349
361
|
const addStepsArgs = {
|
|
350
|
-
|
|
362
|
+
threadId: v.id("threads"),
|
|
351
363
|
messageId: v.id("messages"),
|
|
352
364
|
steps: v.array(vStepWithMessagesWithFileAndId),
|
|
353
365
|
failPendingSteps: v.optional(v.boolean()),
|
|
@@ -379,7 +391,7 @@ async function addStepsHandler(ctx, args) {
|
|
|
379
391
|
let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
|
|
380
392
|
for (const { step, messages } of args.steps) {
|
|
381
393
|
const stepId = await ctx.db.insert("steps", {
|
|
382
|
-
|
|
394
|
+
threadId: args.threadId,
|
|
383
395
|
parentMessageId: args.messageId,
|
|
384
396
|
order,
|
|
385
397
|
stepOrder: nextStepOrder,
|
|
@@ -387,7 +399,7 @@ async function addStepsHandler(ctx, args) {
|
|
|
387
399
|
step,
|
|
388
400
|
});
|
|
389
401
|
await addMessagesHandler(ctx, {
|
|
390
|
-
|
|
402
|
+
threadId: args.threadId,
|
|
391
403
|
parentMessageId: args.messageId,
|
|
392
404
|
stepId,
|
|
393
405
|
messages,
|
|
@@ -396,7 +408,8 @@ async function addStepsHandler(ctx, args) {
|
|
|
396
408
|
pending: step.finishReason === "stop" ? false : true,
|
|
397
409
|
failPendingSteps: false,
|
|
398
410
|
});
|
|
399
|
-
if
|
|
411
|
+
// We don't commit if the parent is still pending.
|
|
412
|
+
if (step.finishReason === "stop" && parentMessage.status === "success") {
|
|
400
413
|
await commitMessageHandler(ctx, { messageId: args.messageId });
|
|
401
414
|
}
|
|
402
415
|
steps.push((await ctx.db.get(stepId)));
|
|
@@ -441,18 +454,19 @@ async function commitMessageHandler(ctx, { messageId }) {
|
|
|
441
454
|
const order = message.order;
|
|
442
455
|
const messages = await mergedStream([true, false].map((tool) => stream(ctx.db, schema)
|
|
443
456
|
.query("messages")
|
|
444
|
-
.withIndex("
|
|
445
|
-
.eq("
|
|
457
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q
|
|
458
|
+
.eq("threadId", message.threadId)
|
|
446
459
|
.eq("status", "pending")
|
|
447
460
|
.eq("tool", tool)
|
|
448
461
|
.eq("order", order))), ["order", "stepOrder"]).collect();
|
|
449
462
|
for (const message of messages) {
|
|
450
463
|
await ctx.db.patch(message._id, { status: "success" });
|
|
464
|
+
// TODO: recursively commit steps & messages that might depend on this one.
|
|
451
465
|
}
|
|
452
466
|
}
|
|
453
|
-
export const
|
|
467
|
+
export const getThreadMessages = query({
|
|
454
468
|
args: {
|
|
455
|
-
|
|
469
|
+
threadId: v.id("threads"),
|
|
456
470
|
isTool: v.optional(v.boolean()),
|
|
457
471
|
order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
|
|
458
472
|
limit: v.optional(v.number()),
|
|
@@ -468,9 +482,9 @@ export const getChatMessages = query({
|
|
|
468
482
|
const order = args.order ?? "desc";
|
|
469
483
|
const streams = toolOptions.flatMap((tool) => statuses.map((status) => stream(ctx.db, schema)
|
|
470
484
|
.query("messages")
|
|
471
|
-
.withIndex("
|
|
485
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => {
|
|
472
486
|
const qq = q
|
|
473
|
-
.eq("
|
|
487
|
+
.eq("threadId", args.threadId)
|
|
474
488
|
.eq("status", status)
|
|
475
489
|
.eq("tool", tool);
|
|
476
490
|
if (parent) {
|
|
@@ -501,19 +515,19 @@ export const getChatMessages = query({
|
|
|
501
515
|
export const searchMessages = action({
|
|
502
516
|
args: {
|
|
503
517
|
userId: v.optional(v.string()),
|
|
504
|
-
|
|
518
|
+
threadId: v.optional(v.id("threads")),
|
|
505
519
|
parentMessageId: v.optional(v.id("messages")),
|
|
506
520
|
...vSearchOptions.fields,
|
|
507
521
|
},
|
|
508
522
|
returns: v.array(v.doc("messages")),
|
|
509
523
|
handler: async (ctx, args) => {
|
|
510
|
-
assert(args.userId || args.
|
|
524
|
+
assert(args.userId || args.threadId, "Specify userId or threadId");
|
|
511
525
|
const limit = args.limit;
|
|
512
526
|
let textSearchMessages;
|
|
513
527
|
if (args.text) {
|
|
514
528
|
textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
|
|
515
529
|
userId: args.userId,
|
|
516
|
-
|
|
530
|
+
threadId: args.threadId,
|
|
517
531
|
text: args.text,
|
|
518
532
|
limit,
|
|
519
533
|
});
|
|
@@ -528,8 +542,8 @@ export const searchMessages = action({
|
|
|
528
542
|
const vectors = (await ctx.vectorSearch(tableName, "vector", {
|
|
529
543
|
vector: args.vector,
|
|
530
544
|
filter: (q) => args.userId
|
|
531
|
-
? q.eq("model_kind_userId", [model, "
|
|
532
|
-
: q.eq("
|
|
545
|
+
? q.eq("model_kind_userId", [model, "thread", args.userId])
|
|
546
|
+
: q.eq("model_kind_threadId", [model, "thread", args.threadId]),
|
|
533
547
|
limit,
|
|
534
548
|
})).filter((v) => v._score > 0.5);
|
|
535
549
|
// Reciprocal rank fusion
|
|
@@ -545,7 +559,7 @@ export const searchMessages = action({
|
|
|
545
559
|
const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
|
|
546
560
|
const messages = await ctx.runQuery(internal.messages._fetchVectorMessages, {
|
|
547
561
|
userId: args.userId,
|
|
548
|
-
|
|
562
|
+
threadId: args.threadId,
|
|
549
563
|
vectorIds,
|
|
550
564
|
textSearchMessages: textSearchMessages?.filter((m) => !vectorIds.includes(m.embeddingId)),
|
|
551
565
|
messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
|
|
@@ -560,7 +574,7 @@ export const searchMessages = action({
|
|
|
560
574
|
export const _fetchVectorMessages = internalQuery({
|
|
561
575
|
args: {
|
|
562
576
|
userId: v.optional(v.string()),
|
|
563
|
-
|
|
577
|
+
threadId: v.optional(v.id("threads")),
|
|
564
578
|
vectorIds: v.array(vVectorId),
|
|
565
579
|
textSearchMessages: v.optional(v.array(v.doc("messages"))),
|
|
566
580
|
messageRange: v.object({ before: v.number(), after: v.number() }),
|
|
@@ -570,60 +584,83 @@ export const _fetchVectorMessages = internalQuery({
|
|
|
570
584
|
returns: v.array(v.doc("messages")),
|
|
571
585
|
handler: async (ctx, args) => {
|
|
572
586
|
const parent = args.parentMessageId && (await ctx.db.get(args.parentMessageId));
|
|
587
|
+
const { userId, threadId } = args;
|
|
588
|
+
assert(userId || threadId, "Specify userId or threadId to search");
|
|
573
589
|
let messages = (await Promise.all(args.vectorIds.map((embeddingId) => ctx.db
|
|
574
590
|
.query("messages")
|
|
575
591
|
.withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
|
|
576
|
-
.filter((q) =>
|
|
577
|
-
? q.eq("userId",
|
|
592
|
+
.filter((q) => userId
|
|
593
|
+
? q.eq("userId", userId)
|
|
578
594
|
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
579
|
-
q.eq("
|
|
580
|
-
)
|
|
595
|
+
q.eq("threadId", threadId))
|
|
581
596
|
.first()))).filter((m) => m !== undefined && m !== null && (!parent || m.order <= parent.order));
|
|
582
597
|
messages.push(...(args.textSearchMessages ?? []));
|
|
583
598
|
// TODO: prioritize more recent messages
|
|
584
599
|
messages.sort((a, b) => a.order - b.order);
|
|
585
600
|
messages = messages.slice(0, args.limit);
|
|
586
601
|
// Fetch the surrounding messages
|
|
602
|
+
if (!threadId) {
|
|
603
|
+
return messages.sort((a, b) => a.order - b.order);
|
|
604
|
+
}
|
|
587
605
|
const included = {};
|
|
588
606
|
for (const m of messages) {
|
|
589
|
-
|
|
590
|
-
|
|
607
|
+
const searchId = m.threadId ?? m.userId;
|
|
608
|
+
if (!included[searchId]) {
|
|
609
|
+
included[searchId] = new Set();
|
|
591
610
|
}
|
|
592
|
-
included[
|
|
611
|
+
included[searchId].add(m.order);
|
|
593
612
|
}
|
|
594
613
|
const ranges = {};
|
|
595
614
|
const { before, after } = args.messageRange;
|
|
596
615
|
for (const m of messages) {
|
|
616
|
+
const searchId = m.threadId ?? m.userId;
|
|
597
617
|
const order = m.order;
|
|
598
618
|
let earliest = order - before;
|
|
599
619
|
let latest = order + after;
|
|
600
620
|
for (; earliest <= latest; earliest++) {
|
|
601
|
-
if (!included[
|
|
621
|
+
if (!included[searchId].has(earliest)) {
|
|
602
622
|
break;
|
|
603
623
|
}
|
|
604
624
|
}
|
|
605
625
|
for (; latest >= earliest; latest--) {
|
|
606
|
-
if (!included[
|
|
626
|
+
if (!included[searchId].has(latest)) {
|
|
607
627
|
break;
|
|
608
628
|
}
|
|
609
629
|
}
|
|
610
630
|
for (let i = earliest; i <= latest; i++) {
|
|
611
|
-
included[
|
|
631
|
+
included[searchId].add(i);
|
|
612
632
|
}
|
|
613
633
|
if (earliest !== latest) {
|
|
614
|
-
|
|
615
|
-
.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
ranges[
|
|
634
|
+
if (m.threadId) {
|
|
635
|
+
const surrounding = await ctx.db
|
|
636
|
+
.query("messages")
|
|
637
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) => q
|
|
638
|
+
.eq("threadId", m.threadId)
|
|
639
|
+
.eq("status", "success")
|
|
640
|
+
.eq("tool", false)
|
|
641
|
+
.gt("order", earliest)
|
|
642
|
+
.lt("order", latest))
|
|
643
|
+
.collect();
|
|
644
|
+
if (!ranges[searchId]) {
|
|
645
|
+
ranges[searchId] = [];
|
|
646
|
+
}
|
|
647
|
+
ranges[searchId].push(...surrounding);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
const surrounding = await ctx.db
|
|
651
|
+
.query("messages")
|
|
652
|
+
.withIndex("userId_status_tool_order_stepOrder", (q) => q
|
|
653
|
+
.eq("userId", m.userId)
|
|
654
|
+
.eq("status", "success")
|
|
655
|
+
.eq("tool", false)
|
|
656
|
+
.gt("order", earliest)
|
|
657
|
+
.lt("order", latest))
|
|
658
|
+
.collect();
|
|
659
|
+
if (!ranges[searchId]) {
|
|
660
|
+
ranges[searchId] = [];
|
|
661
|
+
}
|
|
662
|
+
ranges[searchId].push(...surrounding);
|
|
625
663
|
}
|
|
626
|
-
ranges[m.chatId].push(...surrounding);
|
|
627
664
|
}
|
|
628
665
|
}
|
|
629
666
|
return Object.values(ranges)
|
|
@@ -635,18 +672,18 @@ export const _fetchVectorMessages = internalQuery({
|
|
|
635
672
|
// excluding duplicates in later ranges.
|
|
636
673
|
export const textSearch = query({
|
|
637
674
|
args: {
|
|
638
|
-
|
|
675
|
+
threadId: v.optional(v.id("threads")),
|
|
639
676
|
userId: v.optional(v.string()),
|
|
640
677
|
text: v.string(),
|
|
641
678
|
limit: v.number(),
|
|
642
679
|
},
|
|
643
680
|
handler: async (ctx, args) => {
|
|
644
|
-
assert(args.userId || args.
|
|
681
|
+
assert(args.userId || args.threadId, "Specify userId or threadId");
|
|
645
682
|
const messages = await ctx.db
|
|
646
683
|
.query("messages")
|
|
647
684
|
.withSearchIndex("text_search", (q) => args.userId
|
|
648
685
|
? q.search("text", args.text).eq("userId", args.userId)
|
|
649
|
-
: q.search("text", args.text).eq("
|
|
686
|
+
: q.search("text", args.text).eq("threadId", args.threadId))
|
|
650
687
|
.take(args.limit);
|
|
651
688
|
return messages;
|
|
652
689
|
},
|
|
@@ -695,9 +732,9 @@ export const textSearch = query({
|
|
|
695
732
|
// ),
|
|
696
733
|
// });
|
|
697
734
|
// const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
|
|
698
|
-
// export const
|
|
735
|
+
// export const getThreadMessagesPage = query({
|
|
699
736
|
// args: {
|
|
700
|
-
//
|
|
737
|
+
// parentMessageId: v.string(),
|
|
701
738
|
// selectBy: v.optional(vSelectBy),
|
|
702
739
|
// // Unimplemented and as far I can tell no storage provider has either.
|
|
703
740
|
// // memoryConfig: v.optional(vMemoryConfig),
|
|
@@ -705,7 +742,7 @@ export const textSearch = query({
|
|
|
705
742
|
// handler: async (ctx, args): Promise<SerializedMessage[]> => {
|
|
706
743
|
// const messages = await ctx.db
|
|
707
744
|
// .query("messages")
|
|
708
|
-
// .withIndex("
|
|
745
|
+
// .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
|
|
709
746
|
// .order("desc")
|
|
710
747
|
// .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
|
|
711
748
|
// const handled: boolean[] = [];
|
|
@@ -758,9 +795,9 @@ export const textSearch = query({
|
|
|
758
795
|
// ranges.map(async (range) => {
|
|
759
796
|
// return await ctx.db
|
|
760
797
|
// .query("messages")
|
|
761
|
-
// .withIndex("
|
|
798
|
+
// .withIndex("parentMessageId", (q) =>
|
|
762
799
|
// q
|
|
763
|
-
// .eq("
|
|
800
|
+
// .eq("parentMessageId", args.parentMessageId)
|
|
764
801
|
// .gte("threadOrder", range.start)
|
|
765
802
|
// .lte("threadOrder", range.end)
|
|
766
803
|
// )
|
|
@@ -776,21 +813,21 @@ export const textSearch = query({
|
|
|
776
813
|
// export const saveMessages = mutation({
|
|
777
814
|
// args: { messages: v.array(vSerializedMessage) },
|
|
778
815
|
// handler: async (ctx, args) => {
|
|
779
|
-
// const
|
|
816
|
+
// const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
|
|
780
817
|
// for (const message of args.messages) {
|
|
781
|
-
//
|
|
782
|
-
// ...(
|
|
818
|
+
// messagesByParentMessageId[message.parentMessageId] = [
|
|
819
|
+
// ...(messagesByParentMessageId[message.parentMessageId] ?? []),
|
|
783
820
|
// message,
|
|
784
821
|
// ];
|
|
785
822
|
// }
|
|
786
|
-
// for (const
|
|
823
|
+
// for (const parentMessageId in messagesByParentMessageId) {
|
|
787
824
|
// const lastMessage = await ctx.db
|
|
788
825
|
// .query("messages")
|
|
789
|
-
// .withIndex("
|
|
826
|
+
// .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
|
|
790
827
|
// .order("desc")
|
|
791
828
|
// .first();
|
|
792
829
|
// let threadOrder = lastMessage?.threadOrder ?? 0;
|
|
793
|
-
// for (const message of
|
|
830
|
+
// for (const message of messagesByParentMessageId[parentMessageId]) {
|
|
794
831
|
// threadOrder++;
|
|
795
832
|
// await ctx.db.insert("messages", {
|
|
796
833
|
// ...message,
|