@convex-dev/agent 0.0.1-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.
- package/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/commonjs/client/index.d.ts +198 -0
- package/dist/commonjs/client/index.d.ts.map +1 -0
- package/dist/commonjs/client/index.js +365 -0
- package/dist/commonjs/client/index.js.map +1 -0
- package/dist/commonjs/client/types.d.ts +21 -0
- package/dist/commonjs/client/types.d.ts.map +1 -0
- package/dist/commonjs/client/types.js +2 -0
- package/dist/commonjs/client/types.js.map +1 -0
- package/dist/commonjs/component/_generated/api.d.ts +12 -0
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/api.js +22 -0
- package/dist/commonjs/component/_generated/api.js.map +1 -0
- package/dist/commonjs/component/_generated/server.d.ts +64 -0
- package/dist/commonjs/component/_generated/server.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/server.js +74 -0
- package/dist/commonjs/component/_generated/server.js.map +1 -0
- package/dist/commonjs/component/convex.config.d.ts +3 -0
- package/dist/commonjs/component/convex.config.d.ts.map +1 -0
- package/dist/commonjs/component/convex.config.js +3 -0
- package/dist/commonjs/component/convex.config.js.map +1 -0
- package/dist/commonjs/component/lib.d.ts +2 -0
- package/dist/commonjs/component/lib.d.ts.map +1 -0
- package/dist/commonjs/component/lib.js +2 -0
- package/dist/commonjs/component/lib.js.map +1 -0
- package/dist/commonjs/component/messages.d.ts +1913 -0
- package/dist/commonjs/component/messages.d.ts.map +1 -0
- package/dist/commonjs/component/messages.js +787 -0
- package/dist/commonjs/component/messages.js.map +1 -0
- package/dist/commonjs/component/schema.d.ts +5496 -0
- package/dist/commonjs/component/schema.d.ts.map +1 -0
- package/dist/commonjs/component/schema.js +97 -0
- package/dist/commonjs/component/schema.js.map +1 -0
- package/dist/commonjs/component/vector/tables.d.ts +40 -0
- package/dist/commonjs/component/vector/tables.d.ts.map +1 -0
- package/dist/commonjs/component/vector/tables.js +46 -0
- package/dist/commonjs/component/vector/tables.js.map +1 -0
- package/dist/commonjs/mapping.d.ts +26 -0
- package/dist/commonjs/mapping.d.ts.map +1 -0
- package/dist/commonjs/mapping.js +101 -0
- package/dist/commonjs/mapping.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/react/index.d.ts +2 -0
- package/dist/commonjs/react/index.d.ts.map +1 -0
- package/dist/commonjs/react/index.js +8 -0
- package/dist/commonjs/react/index.js.map +1 -0
- package/dist/commonjs/shared.d.ts +9 -0
- package/dist/commonjs/shared.d.ts.map +1 -0
- package/dist/commonjs/shared.js +29 -0
- package/dist/commonjs/shared.js.map +1 -0
- package/dist/commonjs/validators.d.ts +6177 -0
- package/dist/commonjs/validators.d.ts.map +1 -0
- package/dist/commonjs/validators.js +171 -0
- package/dist/commonjs/validators.js.map +1 -0
- package/dist/esm/client/index.d.ts +198 -0
- package/dist/esm/client/index.d.ts.map +1 -0
- package/dist/esm/client/index.js +365 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/types.d.ts +21 -0
- package/dist/esm/client/types.d.ts.map +1 -0
- package/dist/esm/client/types.js +2 -0
- package/dist/esm/client/types.js.map +1 -0
- package/dist/esm/component/_generated/api.d.ts +12 -0
- package/dist/esm/component/_generated/api.d.ts.map +1 -0
- package/dist/esm/component/_generated/api.js +22 -0
- package/dist/esm/component/_generated/api.js.map +1 -0
- package/dist/esm/component/_generated/server.d.ts +64 -0
- package/dist/esm/component/_generated/server.d.ts.map +1 -0
- package/dist/esm/component/_generated/server.js +74 -0
- package/dist/esm/component/_generated/server.js.map +1 -0
- package/dist/esm/component/convex.config.d.ts +3 -0
- package/dist/esm/component/convex.config.d.ts.map +1 -0
- package/dist/esm/component/convex.config.js +3 -0
- package/dist/esm/component/convex.config.js.map +1 -0
- package/dist/esm/component/lib.d.ts +2 -0
- package/dist/esm/component/lib.d.ts.map +1 -0
- package/dist/esm/component/lib.js +2 -0
- package/dist/esm/component/lib.js.map +1 -0
- package/dist/esm/component/messages.d.ts +1913 -0
- package/dist/esm/component/messages.d.ts.map +1 -0
- package/dist/esm/component/messages.js +787 -0
- package/dist/esm/component/messages.js.map +1 -0
- package/dist/esm/component/schema.d.ts +5496 -0
- package/dist/esm/component/schema.d.ts.map +1 -0
- package/dist/esm/component/schema.js +97 -0
- package/dist/esm/component/schema.js.map +1 -0
- package/dist/esm/component/vector/tables.d.ts +40 -0
- package/dist/esm/component/vector/tables.d.ts.map +1 -0
- package/dist/esm/component/vector/tables.js +46 -0
- package/dist/esm/component/vector/tables.js.map +1 -0
- package/dist/esm/mapping.d.ts +26 -0
- package/dist/esm/mapping.d.ts.map +1 -0
- package/dist/esm/mapping.js +101 -0
- package/dist/esm/mapping.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/react/index.d.ts +2 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +8 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/shared.d.ts +9 -0
- package/dist/esm/shared.d.ts.map +1 -0
- package/dist/esm/shared.js +29 -0
- package/dist/esm/shared.js.map +1 -0
- package/dist/esm/validators.d.ts +6177 -0
- package/dist/esm/validators.d.ts.map +1 -0
- package/dist/esm/validators.js +171 -0
- package/dist/esm/validators.js.map +1 -0
- package/package.json +91 -0
- package/react/package.json +5 -0
- package/src/client/index.ts +659 -0
- package/src/client/types.ts +54 -0
- package/src/component/_generated/api.d.ts +1497 -0
- package/src/component/_generated/api.js +23 -0
- package/src/component/_generated/dataModel.d.ts +60 -0
- package/src/component/_generated/server.d.ts +149 -0
- package/src/component/_generated/server.js +90 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +13 -0
- package/src/component/lib.ts +2 -0
- package/src/component/messages.ts +959 -0
- package/src/component/schema.ts +101 -0
- package/src/component/setup.test.ts +5 -0
- package/src/component/vector/tables.ts +92 -0
- package/src/mapping.ts +160 -0
- package/src/react/index.ts +8 -0
- package/src/shared.ts +35 -0
- package/src/validators.test.ts +101 -0
- package/src/validators.ts +258 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
import { assert, omit, pick } from "convex-helpers";
|
|
2
|
+
import { paginator } from "convex-helpers/server/pagination";
|
|
3
|
+
import { mergedStream, stream } from "convex-helpers/server/stream";
|
|
4
|
+
import { nullable, partial } from "convex-helpers/validators";
|
|
5
|
+
import { Infer, ObjectType } from "convex/values";
|
|
6
|
+
import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
|
|
7
|
+
import {
|
|
8
|
+
Message,
|
|
9
|
+
MessageWithFileAndId,
|
|
10
|
+
vAssistantMessage,
|
|
11
|
+
vChatStatus,
|
|
12
|
+
vMessageStatus,
|
|
13
|
+
vMessageWithFileAndId,
|
|
14
|
+
vSearchOptions,
|
|
15
|
+
vStep,
|
|
16
|
+
vStepWithMessagesWithFileAndId,
|
|
17
|
+
vToolMessage,
|
|
18
|
+
} from "../validators.js";
|
|
19
|
+
import { api, internal } from "./_generated/api.js";
|
|
20
|
+
import { Doc, Id } from "./_generated/dataModel.js";
|
|
21
|
+
import {
|
|
22
|
+
action,
|
|
23
|
+
internalMutation,
|
|
24
|
+
internalQuery,
|
|
25
|
+
mutation,
|
|
26
|
+
MutationCtx,
|
|
27
|
+
query,
|
|
28
|
+
QueryCtx,
|
|
29
|
+
} from "./_generated/server.js";
|
|
30
|
+
import { schema, v } from "./schema.js";
|
|
31
|
+
import {
|
|
32
|
+
getVectorTableName,
|
|
33
|
+
VectorDimension,
|
|
34
|
+
VectorDimensions,
|
|
35
|
+
vVectorId,
|
|
36
|
+
} from "./vector/tables.js";
|
|
37
|
+
|
|
38
|
+
export const getChat = query({
|
|
39
|
+
args: { chatId: v.id("chats") },
|
|
40
|
+
handler: async (ctx, args) => {
|
|
41
|
+
return ctx.db.get(args.chatId);
|
|
42
|
+
},
|
|
43
|
+
returns: v.union(v.doc("chats"), v.null()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const getChatsByUserId = query({
|
|
47
|
+
args: {
|
|
48
|
+
userId: v.string(),
|
|
49
|
+
// Note: the other arguments cannot change from when the cursor was created.
|
|
50
|
+
cursor: v.optional(v.union(v.string(), v.null())),
|
|
51
|
+
limit: v.optional(v.number()),
|
|
52
|
+
offset: v.optional(v.number()),
|
|
53
|
+
statuses: v.optional(v.array(vChatStatus)),
|
|
54
|
+
},
|
|
55
|
+
handler: async (ctx, args) => {
|
|
56
|
+
const streams = (args.statuses ?? ["active"]).map((status) =>
|
|
57
|
+
stream(ctx.db, schema)
|
|
58
|
+
.query("chats")
|
|
59
|
+
.withIndex("status_userId_order", (q) =>
|
|
60
|
+
q
|
|
61
|
+
.eq("status", status)
|
|
62
|
+
.eq("userId", args.userId)
|
|
63
|
+
.gte("order", args.offset ?? 0)
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
const chats = await mergedStream(streams, ["order", "stepOrder"]).paginate({
|
|
67
|
+
numItems: args.limit ?? 100,
|
|
68
|
+
cursor: args.cursor ?? null,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
chats: chats.page,
|
|
72
|
+
continueCursor: chats.continueCursor,
|
|
73
|
+
isDone: chats.isDone,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
returns: v.object({
|
|
77
|
+
chats: v.array(v.doc("chats")),
|
|
78
|
+
continueCursor: v.string(),
|
|
79
|
+
isDone: v.boolean(),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const vChat = schema.tables.chats.validator;
|
|
84
|
+
const statuses = vChat.fields.status.members.map((m) => m.value);
|
|
85
|
+
|
|
86
|
+
export const createChat = mutation({
|
|
87
|
+
args: omit(vChat.fields, ["order", "status"]),
|
|
88
|
+
handler: async (ctx, args) => {
|
|
89
|
+
const streams = statuses.map((status) =>
|
|
90
|
+
stream(ctx.db, schema)
|
|
91
|
+
.query("chats")
|
|
92
|
+
.withIndex("status_userId_order", (q) =>
|
|
93
|
+
q.eq("status", status).eq("userId", args.userId)
|
|
94
|
+
)
|
|
95
|
+
.order("desc")
|
|
96
|
+
);
|
|
97
|
+
const latestChat = await mergedStream(streams, ["order"]).first();
|
|
98
|
+
const order = (latestChat?.order ?? -1) + 1;
|
|
99
|
+
const chatId = await ctx.db.insert("chats", {
|
|
100
|
+
...args,
|
|
101
|
+
order,
|
|
102
|
+
status: "active",
|
|
103
|
+
});
|
|
104
|
+
return (await ctx.db.get(chatId))!;
|
|
105
|
+
},
|
|
106
|
+
returns: v.doc("chats"),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const updateChat = mutation({
|
|
110
|
+
args: {
|
|
111
|
+
chatId: v.id("chats"),
|
|
112
|
+
patch: v.object(
|
|
113
|
+
partial(
|
|
114
|
+
pick(vChat.fields, [
|
|
115
|
+
"title",
|
|
116
|
+
"summary",
|
|
117
|
+
"defaultSystemPrompt",
|
|
118
|
+
"status",
|
|
119
|
+
])
|
|
120
|
+
)
|
|
121
|
+
),
|
|
122
|
+
},
|
|
123
|
+
handler: async (ctx, args) => {
|
|
124
|
+
const chat = await ctx.db.get(args.chatId);
|
|
125
|
+
assert(chat, `Chat ${args.chatId} not found`);
|
|
126
|
+
await ctx.db.patch(args.chatId, args.patch);
|
|
127
|
+
return (await ctx.db.get(args.chatId))!;
|
|
128
|
+
},
|
|
129
|
+
returns: v.doc("chats"),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const archiveChat = mutation({
|
|
133
|
+
args: { chatId: v.id("chats") },
|
|
134
|
+
handler: async (ctx, args) => {
|
|
135
|
+
const chat = await ctx.db.get(args.chatId);
|
|
136
|
+
assert(chat, `Chat ${args.chatId} not found`);
|
|
137
|
+
await ctx.db.patch(args.chatId, { status: "archived" });
|
|
138
|
+
return (await ctx.db.get(args.chatId))!;
|
|
139
|
+
},
|
|
140
|
+
returns: v.doc("chats"),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const deleteAllForUserId = action({
|
|
144
|
+
args: { userId: v.string() },
|
|
145
|
+
handler: async (ctx, args) => {
|
|
146
|
+
let messagesCursor = null;
|
|
147
|
+
let chatsCursor = null;
|
|
148
|
+
let isDone = false;
|
|
149
|
+
while (!isDone) {
|
|
150
|
+
const result: {
|
|
151
|
+
messagesCursor: string;
|
|
152
|
+
chatsCursor: string | null;
|
|
153
|
+
isDone: boolean;
|
|
154
|
+
} = await ctx.runMutation(internal.messages._deletePageForUserId, {
|
|
155
|
+
userId: args.userId,
|
|
156
|
+
messagesCursor,
|
|
157
|
+
chatsCursor,
|
|
158
|
+
});
|
|
159
|
+
messagesCursor = result.messagesCursor;
|
|
160
|
+
chatsCursor = result.chatsCursor;
|
|
161
|
+
isDone = result.isDone;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
returns: v.null(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const deleteAllForUserIdAsync = mutation({
|
|
168
|
+
args: {
|
|
169
|
+
userId: v.string(),
|
|
170
|
+
},
|
|
171
|
+
handler: async (ctx, args) => {
|
|
172
|
+
const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
|
|
173
|
+
userId: args.userId,
|
|
174
|
+
messagesCursor: null,
|
|
175
|
+
chatsCursor: null,
|
|
176
|
+
});
|
|
177
|
+
return isDone;
|
|
178
|
+
},
|
|
179
|
+
returns: v.boolean(),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const deleteAllArgs = {
|
|
183
|
+
userId: v.string(),
|
|
184
|
+
messagesCursor: nullable(v.string()),
|
|
185
|
+
chatsCursor: nullable(v.string()),
|
|
186
|
+
};
|
|
187
|
+
type DeleteAllArgs = ObjectType<typeof deleteAllArgs>;
|
|
188
|
+
const deleteAllReturns = {
|
|
189
|
+
messagesCursor: v.string(),
|
|
190
|
+
chatsCursor: nullable(v.string()),
|
|
191
|
+
isDone: v.boolean(),
|
|
192
|
+
};
|
|
193
|
+
type DeleteAllReturns = ObjectType<typeof deleteAllReturns>;
|
|
194
|
+
|
|
195
|
+
export const _deleteAllForUserIdAsync = internalMutation({
|
|
196
|
+
args: deleteAllArgs,
|
|
197
|
+
handler: deleteAllFroUserIdAsyncHandler,
|
|
198
|
+
returns: v.boolean(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
async function deleteAllFroUserIdAsyncHandler(
|
|
202
|
+
ctx: MutationCtx,
|
|
203
|
+
args: DeleteAllArgs
|
|
204
|
+
): Promise<boolean> {
|
|
205
|
+
const result = await deletePageForUserId(ctx, args);
|
|
206
|
+
if (!result.isDone) {
|
|
207
|
+
await ctx.scheduler.runAfter(
|
|
208
|
+
0,
|
|
209
|
+
internal.messages._deleteAllForUserIdAsync,
|
|
210
|
+
{
|
|
211
|
+
userId: args.userId,
|
|
212
|
+
messagesCursor: result.messagesCursor,
|
|
213
|
+
chatsCursor: result.chatsCursor,
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return result.isDone;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const _deletePageForUserId = internalMutation({
|
|
221
|
+
args: deleteAllArgs,
|
|
222
|
+
handler: deletePageForUserId,
|
|
223
|
+
returns: deleteAllReturns,
|
|
224
|
+
});
|
|
225
|
+
async function deletePageForUserId(
|
|
226
|
+
ctx: MutationCtx,
|
|
227
|
+
args: DeleteAllArgs
|
|
228
|
+
): Promise<DeleteAllReturns> {
|
|
229
|
+
const streams = statuses.map((status) =>
|
|
230
|
+
stream(ctx.db, schema)
|
|
231
|
+
.query("chats")
|
|
232
|
+
.withIndex("status_userId_order", (q) =>
|
|
233
|
+
q.eq("status", status).eq("userId", args.userId)
|
|
234
|
+
)
|
|
235
|
+
.order("desc")
|
|
236
|
+
);
|
|
237
|
+
const chatStreams = mergedStream(streams, ["order"]);
|
|
238
|
+
const messages = await chatStreams
|
|
239
|
+
.flatMap(
|
|
240
|
+
async (c) =>
|
|
241
|
+
stream(ctx.db, schema)
|
|
242
|
+
.query("messages")
|
|
243
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
244
|
+
q.eq("chatId", c._id).eq("status", "success")
|
|
245
|
+
),
|
|
246
|
+
["tool", "order", "stepOrder"]
|
|
247
|
+
)
|
|
248
|
+
.paginate({
|
|
249
|
+
numItems: 100,
|
|
250
|
+
cursor: args.messagesCursor ?? null,
|
|
251
|
+
});
|
|
252
|
+
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
253
|
+
if (messages.isDone) {
|
|
254
|
+
const chats = await chatStreams.paginate({
|
|
255
|
+
numItems: 100,
|
|
256
|
+
cursor: args.chatsCursor ?? null,
|
|
257
|
+
});
|
|
258
|
+
await Promise.all(chats.page.map((c) => ctx.db.delete(c._id)));
|
|
259
|
+
return {
|
|
260
|
+
messagesCursor: messages.continueCursor,
|
|
261
|
+
chatsCursor: chats.continueCursor,
|
|
262
|
+
isDone: chats.isDone,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
messagesCursor: messages.continueCursor,
|
|
267
|
+
chatsCursor: null,
|
|
268
|
+
isDone: messages.isDone,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function deleteMessage(ctx: MutationCtx, messageDoc: Doc<"messages">) {
|
|
273
|
+
await ctx.db.delete(messageDoc._id);
|
|
274
|
+
if (messageDoc.fileId) {
|
|
275
|
+
const file = await ctx.db.get(messageDoc.fileId);
|
|
276
|
+
if (file) {
|
|
277
|
+
await ctx.db.patch(messageDoc.fileId, { refcount: file.refcount - 1 });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const deleteChatArgs = {
|
|
283
|
+
chatId: v.id("chats"),
|
|
284
|
+
cursor: v.optional(v.string()),
|
|
285
|
+
limit: v.optional(v.number()),
|
|
286
|
+
};
|
|
287
|
+
type DeleteChatArgs = ObjectType<typeof deleteChatArgs>;
|
|
288
|
+
const deleteChatReturns = {
|
|
289
|
+
cursor: v.string(),
|
|
290
|
+
isDone: v.boolean(),
|
|
291
|
+
};
|
|
292
|
+
type DeleteChatReturns = ObjectType<typeof deleteChatReturns>;
|
|
293
|
+
|
|
294
|
+
export const deleteAllForChatIdSync = action({
|
|
295
|
+
args: deleteChatArgs,
|
|
296
|
+
handler: async (ctx, args) => {
|
|
297
|
+
const result: DeleteChatReturns = await ctx.runMutation(
|
|
298
|
+
internal.messages._deletePageForChatId,
|
|
299
|
+
{ chatId: args.chatId, cursor: args.cursor, limit: args.limit }
|
|
300
|
+
);
|
|
301
|
+
return result;
|
|
302
|
+
},
|
|
303
|
+
returns: deleteChatReturns,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
export const deleteAllForChatIdAsync = mutation({
|
|
307
|
+
args: deleteChatArgs,
|
|
308
|
+
handler: async (ctx, args) => {
|
|
309
|
+
const result = await deletePageForChatIdHandler(ctx, args);
|
|
310
|
+
if (!result.isDone) {
|
|
311
|
+
await ctx.scheduler.runAfter(0, api.messages.deleteAllForChatIdAsync, {
|
|
312
|
+
chatId: args.chatId,
|
|
313
|
+
cursor: result.cursor,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
},
|
|
318
|
+
returns: deleteChatReturns,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
export const _deletePageForChatId = internalMutation({
|
|
322
|
+
args: deleteChatArgs,
|
|
323
|
+
handler: deletePageForChatIdHandler,
|
|
324
|
+
returns: deleteChatReturns,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
async function deletePageForChatIdHandler(
|
|
328
|
+
ctx: MutationCtx,
|
|
329
|
+
args: DeleteChatArgs
|
|
330
|
+
): Promise<DeleteChatReturns> {
|
|
331
|
+
const messages = await stream(ctx.db, schema)
|
|
332
|
+
.query("messages")
|
|
333
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
334
|
+
q.eq("chatId", args.chatId).eq("status", "success")
|
|
335
|
+
)
|
|
336
|
+
.paginate({
|
|
337
|
+
numItems: args.limit ?? 100,
|
|
338
|
+
cursor: args.cursor ?? null,
|
|
339
|
+
});
|
|
340
|
+
await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
|
|
341
|
+
await ctx.db.delete(args.chatId);
|
|
342
|
+
return {
|
|
343
|
+
cursor: messages.continueCursor,
|
|
344
|
+
isDone: messages.isDone,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export const getFilesToDelete = query({
|
|
349
|
+
args: {
|
|
350
|
+
cursor: v.optional(v.string()),
|
|
351
|
+
limit: v.optional(v.number()),
|
|
352
|
+
},
|
|
353
|
+
handler: async (ctx, args) => {
|
|
354
|
+
const files = await paginator(ctx.db, schema)
|
|
355
|
+
.query("files")
|
|
356
|
+
.withIndex("refcount", (q) => q.eq("refcount", 0))
|
|
357
|
+
.paginate({
|
|
358
|
+
numItems: args.limit ?? 100,
|
|
359
|
+
cursor: args.cursor ?? null,
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
files: files.page,
|
|
363
|
+
continueCursor: files.continueCursor,
|
|
364
|
+
isDone: files.isDone,
|
|
365
|
+
};
|
|
366
|
+
},
|
|
367
|
+
returns: v.object({
|
|
368
|
+
files: v.array(v.doc("files")),
|
|
369
|
+
continueCursor: v.string(),
|
|
370
|
+
isDone: v.boolean(),
|
|
371
|
+
}),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
export const vMessageDoc = schema.tables.messages.validator;
|
|
375
|
+
export const messageStatuses = vMessageDoc.fields.status.members.map(
|
|
376
|
+
(m) => m.value
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const addMessagesArgs = {
|
|
380
|
+
chatId: v.id("chats"),
|
|
381
|
+
stepId: v.optional(v.id("steps")),
|
|
382
|
+
parentMessageId: v.optional(v.id("messages")),
|
|
383
|
+
messages: v.array(vMessageWithFileAndId),
|
|
384
|
+
model: v.optional(v.string()),
|
|
385
|
+
agentName: v.optional(v.string()),
|
|
386
|
+
pending: v.optional(v.boolean()),
|
|
387
|
+
failPendingSteps: v.optional(v.boolean()),
|
|
388
|
+
};
|
|
389
|
+
export const addMessages = mutation({
|
|
390
|
+
args: addMessagesArgs,
|
|
391
|
+
handler: addMessagesHandler,
|
|
392
|
+
returns: v.object({
|
|
393
|
+
messages: v.array(v.doc("messages")),
|
|
394
|
+
pending: v.optional(v.doc("messages")),
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
async function addMessagesHandler(
|
|
398
|
+
ctx: MutationCtx,
|
|
399
|
+
args: ObjectType<typeof addMessagesArgs>
|
|
400
|
+
) {
|
|
401
|
+
const chat = await ctx.db.get(args.chatId);
|
|
402
|
+
assert(chat, `Chat ${args.chatId} not found`);
|
|
403
|
+
const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
|
|
404
|
+
args;
|
|
405
|
+
if (failPendingSteps) {
|
|
406
|
+
const pendingMessages = await ctx.db
|
|
407
|
+
.query("messages")
|
|
408
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
409
|
+
q.eq("chatId", args.chatId).eq("status", "pending")
|
|
410
|
+
)
|
|
411
|
+
.collect();
|
|
412
|
+
await Promise.all(
|
|
413
|
+
pendingMessages.map((m) =>
|
|
414
|
+
ctx.db.patch(m._id, { status: "failed", text: "Restarting" })
|
|
415
|
+
)
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
let order: number | undefined;
|
|
419
|
+
const maxMessage = await getMaxMessage(ctx, args.chatId);
|
|
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;
|
|
426
|
+
const toReturn: Doc<"messages">[] = [];
|
|
427
|
+
if (messages.length > 0) {
|
|
428
|
+
for (const { message, fileId, id } of messages) {
|
|
429
|
+
const tool = isTool(message);
|
|
430
|
+
if (!tool) {
|
|
431
|
+
order++;
|
|
432
|
+
}
|
|
433
|
+
const text = extractText(message);
|
|
434
|
+
const messageId = await ctx.db.insert("messages", {
|
|
435
|
+
...rest,
|
|
436
|
+
threadId,
|
|
437
|
+
userId: chat.userId,
|
|
438
|
+
message,
|
|
439
|
+
id,
|
|
440
|
+
order,
|
|
441
|
+
tool,
|
|
442
|
+
text,
|
|
443
|
+
fileId,
|
|
444
|
+
status: pending ? "pending" : "success",
|
|
445
|
+
});
|
|
446
|
+
toReturn.push((await ctx.db.get(messageId))!);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return { messages: toReturn };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function getMaxMessage(ctx: QueryCtx, chatId: Id<"chats">) {
|
|
453
|
+
return mergedStream(
|
|
454
|
+
["success" as const, "pending" as const].map((status) =>
|
|
455
|
+
stream(ctx.db, schema)
|
|
456
|
+
.query("messages")
|
|
457
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
458
|
+
q.eq("chatId", chatId).eq("status", status).eq("tool", false)
|
|
459
|
+
)
|
|
460
|
+
.order("desc")
|
|
461
|
+
),
|
|
462
|
+
["order", "stepOrder"]
|
|
463
|
+
).first();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const addStepsArgs = {
|
|
467
|
+
chatId: v.id("chats"),
|
|
468
|
+
messageId: v.id("messages"),
|
|
469
|
+
steps: v.array(vStepWithMessagesWithFileAndId),
|
|
470
|
+
failPendingSteps: v.optional(v.boolean()),
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
export const addSteps = mutation({
|
|
474
|
+
args: addStepsArgs,
|
|
475
|
+
returns: v.array(v.doc("steps")),
|
|
476
|
+
handler: addStepsHandler,
|
|
477
|
+
});
|
|
478
|
+
async function addStepsHandler(
|
|
479
|
+
ctx: MutationCtx,
|
|
480
|
+
args: ObjectType<typeof addStepsArgs>
|
|
481
|
+
) {
|
|
482
|
+
const parentMessage = await ctx.db.get(args.messageId);
|
|
483
|
+
assert(parentMessage, `Message ${args.messageId} not found`);
|
|
484
|
+
const order = parentMessage.order;
|
|
485
|
+
assert(order !== undefined, `${args.messageId} has no order`);
|
|
486
|
+
let steps = await ctx.db
|
|
487
|
+
.query("steps")
|
|
488
|
+
.withIndex("parentMessageId_order_stepOrder", (q) =>
|
|
489
|
+
// TODO: fetch pending, and commit later
|
|
490
|
+
q.eq("parentMessageId", args.messageId)
|
|
491
|
+
)
|
|
492
|
+
.collect();
|
|
493
|
+
if (args.failPendingSteps) {
|
|
494
|
+
for (const step of steps) {
|
|
495
|
+
if (step.status === "pending") {
|
|
496
|
+
await ctx.db.patch(step._id, { status: "failed" });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
steps = steps.filter((s) => s.status === "success");
|
|
500
|
+
}
|
|
501
|
+
let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
|
|
502
|
+
for (const { step, messages } of args.steps) {
|
|
503
|
+
const stepId = await ctx.db.insert("steps", {
|
|
504
|
+
chatId: args.chatId,
|
|
505
|
+
parentMessageId: args.messageId,
|
|
506
|
+
order,
|
|
507
|
+
stepOrder: nextStepOrder,
|
|
508
|
+
status: step.finishReason === "stop" ? "success" : "pending",
|
|
509
|
+
step,
|
|
510
|
+
});
|
|
511
|
+
await addMessagesHandler(ctx, {
|
|
512
|
+
chatId: args.chatId,
|
|
513
|
+
parentMessageId: args.messageId,
|
|
514
|
+
stepId,
|
|
515
|
+
messages,
|
|
516
|
+
model: parentMessage.model,
|
|
517
|
+
agentName: parentMessage.agentName,
|
|
518
|
+
pending: step.finishReason === "stop" ? false : true,
|
|
519
|
+
failPendingSteps: false,
|
|
520
|
+
});
|
|
521
|
+
if (step.finishReason === "stop") {
|
|
522
|
+
await commitMessageHandler(ctx, { messageId: args.messageId });
|
|
523
|
+
}
|
|
524
|
+
steps.push((await ctx.db.get(stepId))!);
|
|
525
|
+
nextStepOrder++;
|
|
526
|
+
}
|
|
527
|
+
return steps;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export const rollbackMessage = mutation({
|
|
531
|
+
args: {
|
|
532
|
+
messageId: v.id("messages"),
|
|
533
|
+
error: v.optional(v.string()),
|
|
534
|
+
},
|
|
535
|
+
returns: v.null(),
|
|
536
|
+
handler: async (ctx, { messageId, error }) => {
|
|
537
|
+
const message = await ctx.db.get(messageId);
|
|
538
|
+
assert(message, `Message ${messageId} not found`);
|
|
539
|
+
await ctx.db.patch(messageId, {
|
|
540
|
+
status: "failed",
|
|
541
|
+
text: error ?? message.text,
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
export const commitMessage = mutation({
|
|
547
|
+
args: {
|
|
548
|
+
messageId: v.id("messages"),
|
|
549
|
+
},
|
|
550
|
+
returns: v.null(),
|
|
551
|
+
handler: commitMessageHandler,
|
|
552
|
+
});
|
|
553
|
+
async function commitMessageHandler(
|
|
554
|
+
ctx: MutationCtx,
|
|
555
|
+
{ messageId }: { messageId: Id<"messages"> }
|
|
556
|
+
) {
|
|
557
|
+
const message = await ctx.db.get(messageId);
|
|
558
|
+
assert(message, `Message ${messageId} not found`);
|
|
559
|
+
|
|
560
|
+
const allSteps = await ctx.db
|
|
561
|
+
.query("steps")
|
|
562
|
+
.withIndex("parentMessageId_order_stepOrder", (q) =>
|
|
563
|
+
q.eq("parentMessageId", messageId)
|
|
564
|
+
)
|
|
565
|
+
.collect();
|
|
566
|
+
for (const step of allSteps) {
|
|
567
|
+
if (step.status === "pending") {
|
|
568
|
+
await ctx.db.patch(step._id, { status: "success" });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const order = message.order!;
|
|
572
|
+
const messages = await mergedStream(
|
|
573
|
+
[true, false].map((tool) =>
|
|
574
|
+
stream(ctx.db, schema)
|
|
575
|
+
.query("messages")
|
|
576
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
577
|
+
q
|
|
578
|
+
.eq("chatId", message.chatId)
|
|
579
|
+
.eq("status", "pending")
|
|
580
|
+
.eq("tool", tool)
|
|
581
|
+
.eq("order", order)
|
|
582
|
+
)
|
|
583
|
+
),
|
|
584
|
+
["order", "stepOrder"]
|
|
585
|
+
).collect();
|
|
586
|
+
for (const message of messages) {
|
|
587
|
+
await ctx.db.patch(message._id, { status: "success" });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export const getChatMessages = query({
|
|
592
|
+
args: {
|
|
593
|
+
chatId: v.id("chats"),
|
|
594
|
+
isTool: v.optional(v.boolean()),
|
|
595
|
+
order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
|
|
596
|
+
limit: v.optional(v.number()),
|
|
597
|
+
// Note: the other arguments cannot change from when the cursor was created.
|
|
598
|
+
cursor: v.optional(v.string()),
|
|
599
|
+
statuses: v.optional(v.array(vMessageStatus)),
|
|
600
|
+
},
|
|
601
|
+
handler: async (ctx, args) => {
|
|
602
|
+
const statuses = args.statuses ?? ["success"];
|
|
603
|
+
const toolOptions =
|
|
604
|
+
args.isTool === undefined ? [true, false] : [args.isTool];
|
|
605
|
+
const order = args.order ?? "desc";
|
|
606
|
+
const streams = toolOptions.flatMap((tool) =>
|
|
607
|
+
statuses.map((status) =>
|
|
608
|
+
stream(ctx.db, schema)
|
|
609
|
+
.query("messages")
|
|
610
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
611
|
+
q.eq("chatId", args.chatId).eq("status", status).eq("tool", tool)
|
|
612
|
+
)
|
|
613
|
+
.order(order)
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
const messages = await mergedStream(streams, [
|
|
617
|
+
"order",
|
|
618
|
+
"stepOrder",
|
|
619
|
+
]).paginate({
|
|
620
|
+
numItems: args.limit ?? 100,
|
|
621
|
+
cursor: args.cursor ?? null,
|
|
622
|
+
});
|
|
623
|
+
return {
|
|
624
|
+
messages: messages.page,
|
|
625
|
+
continueCursor: messages.continueCursor,
|
|
626
|
+
isDone: messages.isDone,
|
|
627
|
+
};
|
|
628
|
+
},
|
|
629
|
+
returns: v.object({
|
|
630
|
+
messages: v.array(v.doc("messages")),
|
|
631
|
+
continueCursor: v.string(),
|
|
632
|
+
isDone: v.boolean(),
|
|
633
|
+
}),
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
export const searchMessages = action({
|
|
637
|
+
args: {
|
|
638
|
+
userId: v.optional(v.string()),
|
|
639
|
+
chatId: v.optional(v.id("chats")),
|
|
640
|
+
...vSearchOptions.fields,
|
|
641
|
+
},
|
|
642
|
+
returns: v.array(v.doc("messages")),
|
|
643
|
+
handler: async (ctx, args): Promise<Doc<"messages">[]> => {
|
|
644
|
+
assert(args.userId || args.chatId, "Specify userId or chatId");
|
|
645
|
+
const limit = args.limit;
|
|
646
|
+
let textSearchMessages: Doc<"messages">[] | undefined;
|
|
647
|
+
if (args.text) {
|
|
648
|
+
textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
|
|
649
|
+
userId: args.userId,
|
|
650
|
+
chatId: args.chatId,
|
|
651
|
+
text: args.text,
|
|
652
|
+
limit,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (args.vector) {
|
|
656
|
+
const dimension = args.vector.length as VectorDimension;
|
|
657
|
+
if (!VectorDimensions.includes(dimension)) {
|
|
658
|
+
throw new Error(`Unsupported vector dimension: ${dimension}`);
|
|
659
|
+
}
|
|
660
|
+
const model = args.vectorModel ?? "unknown";
|
|
661
|
+
const tableName = getVectorTableName(dimension);
|
|
662
|
+
const vectors = (
|
|
663
|
+
await ctx.vectorSearch(tableName, "vector", {
|
|
664
|
+
vector: args.vector,
|
|
665
|
+
filter: (q) =>
|
|
666
|
+
args.userId
|
|
667
|
+
? q.eq("model_kind_userId", [model, "chat", args.userId])
|
|
668
|
+
: q.eq("model_kind_chatId", [model, "chat", args.chatId!]),
|
|
669
|
+
limit,
|
|
670
|
+
})
|
|
671
|
+
).filter((v) => v._score > 0.5);
|
|
672
|
+
// Reciprocal rank fusion
|
|
673
|
+
const k = 10;
|
|
674
|
+
const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
|
|
675
|
+
const vectorScores = vectors
|
|
676
|
+
.map((v, i) => ({
|
|
677
|
+
id: v._id,
|
|
678
|
+
score:
|
|
679
|
+
1 / (i + k) +
|
|
680
|
+
1 / (textEmbeddingIds?.indexOf(v._id) ?? Infinity + k),
|
|
681
|
+
}))
|
|
682
|
+
.sort((a, b) => b.score - a.score);
|
|
683
|
+
const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
|
|
684
|
+
|
|
685
|
+
const messages: Doc<"messages">[] = await ctx.runQuery(
|
|
686
|
+
internal.messages._fetchVectorMessages,
|
|
687
|
+
{
|
|
688
|
+
userId: args.userId,
|
|
689
|
+
chatId: args.chatId,
|
|
690
|
+
vectorIds,
|
|
691
|
+
textSearchMessages: textSearchMessages
|
|
692
|
+
?.filter((m) => !vectorIds.includes(m.embeddingId!))
|
|
693
|
+
.slice(0, limit - vectorIds.length),
|
|
694
|
+
messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
return messages;
|
|
698
|
+
}
|
|
699
|
+
return textSearchMessages?.flat() ?? [];
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
export const _fetchVectorMessages = internalQuery({
|
|
704
|
+
args: {
|
|
705
|
+
userId: v.optional(v.string()),
|
|
706
|
+
chatId: v.optional(v.id("chats")),
|
|
707
|
+
vectorIds: v.array(vVectorId),
|
|
708
|
+
textSearchMessages: v.optional(v.array(v.doc("messages"))),
|
|
709
|
+
messageRange: v.object({ before: v.number(), after: v.number() }),
|
|
710
|
+
},
|
|
711
|
+
returns: v.array(v.doc("messages")),
|
|
712
|
+
handler: async (ctx, args): Promise<Doc<"messages">[]> => {
|
|
713
|
+
const messages = (
|
|
714
|
+
await Promise.all(
|
|
715
|
+
args.vectorIds.map((embeddingId) =>
|
|
716
|
+
ctx.db
|
|
717
|
+
.query("messages")
|
|
718
|
+
.withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
|
|
719
|
+
.filter(
|
|
720
|
+
(q) =>
|
|
721
|
+
args.userId
|
|
722
|
+
? q.eq("userId", args.userId)
|
|
723
|
+
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
724
|
+
q.eq("chatId", args.chatId as any) // not sure why it's failing...
|
|
725
|
+
)
|
|
726
|
+
.first()
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
).filter((m): m is Doc<"messages"> => m !== undefined);
|
|
730
|
+
messages.push(...(args.textSearchMessages ?? []));
|
|
731
|
+
messages.sort((a, b) => a.order! - b.order!);
|
|
732
|
+
// Fetch the surrounding messages
|
|
733
|
+
const included: Record<Id<"chats">, Set<number>> = {};
|
|
734
|
+
for (const m of messages) {
|
|
735
|
+
if (!included[m.chatId]) {
|
|
736
|
+
included[m.chatId] = new Set();
|
|
737
|
+
}
|
|
738
|
+
included[m.chatId].add(m.order!);
|
|
739
|
+
}
|
|
740
|
+
const ranges: Record<Id<"chats">, Doc<"messages">[]> = {};
|
|
741
|
+
const { before, after } = args.messageRange;
|
|
742
|
+
for (const m of messages) {
|
|
743
|
+
const order = m.order!;
|
|
744
|
+
let earliest = order - before;
|
|
745
|
+
let latest = order + after;
|
|
746
|
+
for (; earliest <= latest; earliest++) {
|
|
747
|
+
if (!included[m.chatId].has(earliest)) {
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
for (; latest >= earliest; latest--) {
|
|
752
|
+
if (!included[m.chatId].has(latest)) {
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
for (let i = earliest; i <= latest; i++) {
|
|
757
|
+
included[m.chatId].add(i);
|
|
758
|
+
}
|
|
759
|
+
if (earliest !== latest) {
|
|
760
|
+
const surrounding = await ctx.db
|
|
761
|
+
.query("messages")
|
|
762
|
+
.withIndex("chatId_status_tool_order_stepOrder", (q) =>
|
|
763
|
+
q
|
|
764
|
+
.eq("chatId", m.chatId)
|
|
765
|
+
.eq("status", "success")
|
|
766
|
+
.eq("tool", false)
|
|
767
|
+
.gt("order", earliest)
|
|
768
|
+
.lt("order", latest)
|
|
769
|
+
)
|
|
770
|
+
.collect();
|
|
771
|
+
if (!ranges[m.chatId]) {
|
|
772
|
+
ranges[m.chatId] = [];
|
|
773
|
+
}
|
|
774
|
+
ranges[m.chatId].push(...surrounding);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return Object.values(ranges)
|
|
778
|
+
.map((r) => r.sort((a, b) => a.order! - b.order!))
|
|
779
|
+
.flat();
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// returns ranges of messages in order of text search relevance,
|
|
784
|
+
// excluding duplicates in later ranges.
|
|
785
|
+
export const textSearch = query({
|
|
786
|
+
args: {
|
|
787
|
+
chatId: v.optional(v.id("chats")),
|
|
788
|
+
userId: v.optional(v.string()),
|
|
789
|
+
text: v.string(),
|
|
790
|
+
limit: v.number(),
|
|
791
|
+
},
|
|
792
|
+
handler: async (ctx, args) => {
|
|
793
|
+
assert(args.userId || args.chatId, "Specify userId or chatId");
|
|
794
|
+
const messages = await ctx.db
|
|
795
|
+
.query("messages")
|
|
796
|
+
.withSearchIndex("text_search", (q) =>
|
|
797
|
+
args.userId
|
|
798
|
+
? q.search("text", args.text).eq("userId", args.userId)
|
|
799
|
+
: q.search("text", args.text).eq("chatId", args.chatId!)
|
|
800
|
+
)
|
|
801
|
+
.take(args.limit);
|
|
802
|
+
return messages;
|
|
803
|
+
},
|
|
804
|
+
returns: v.array(v.doc("messages")),
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// const vMemoryConfig = v.object({
|
|
808
|
+
// lastMessages: v.optional(v.union(v.number(), v.literal(false))),
|
|
809
|
+
// semanticRecall: v.optional(
|
|
810
|
+
// v.union(
|
|
811
|
+
// v.boolean(),
|
|
812
|
+
// v.object({
|
|
813
|
+
// topK: v.number(),
|
|
814
|
+
// messageRange: v.union(
|
|
815
|
+
// v.number(),
|
|
816
|
+
// v.object({ before: v.number(), after: v.number() }),
|
|
817
|
+
// ),
|
|
818
|
+
// }),
|
|
819
|
+
// ),
|
|
820
|
+
// ),
|
|
821
|
+
// workingMemory: v.optional(
|
|
822
|
+
// v.object({
|
|
823
|
+
// enabled: v.boolean(),
|
|
824
|
+
// template: v.optional(v.string()),
|
|
825
|
+
// use: v.optional(
|
|
826
|
+
// v.union(v.literal("text-stream"), v.literal("tool-call")),
|
|
827
|
+
// ),
|
|
828
|
+
// }),
|
|
829
|
+
// ),
|
|
830
|
+
// threads: v.optional(
|
|
831
|
+
// v.object({
|
|
832
|
+
// generateTitle: v.optional(v.boolean()),
|
|
833
|
+
// }),
|
|
834
|
+
// ),
|
|
835
|
+
// });
|
|
836
|
+
// const vSelectBy = v.object({
|
|
837
|
+
// vectorSearchString: v.optional(v.string()),
|
|
838
|
+
// last: v.optional(v.union(v.number(), v.literal(false))),
|
|
839
|
+
// include: v.optional(
|
|
840
|
+
// v.array(
|
|
841
|
+
// v.object({
|
|
842
|
+
// id: v.string(),
|
|
843
|
+
// withPreviousMessages: v.optional(v.number()),
|
|
844
|
+
// withNextMessages: v.optional(v.number()),
|
|
845
|
+
// })
|
|
846
|
+
// )
|
|
847
|
+
// ),
|
|
848
|
+
// });
|
|
849
|
+
|
|
850
|
+
// const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
|
|
851
|
+
|
|
852
|
+
// export const getChatMessagesPage = query({
|
|
853
|
+
// args: {
|
|
854
|
+
// threadId: v.string(),
|
|
855
|
+
// selectBy: v.optional(vSelectBy),
|
|
856
|
+
// // Unimplemented and as far I can tell no storage provider has either.
|
|
857
|
+
// // memoryConfig: v.optional(vMemoryConfig),
|
|
858
|
+
// },
|
|
859
|
+
// handler: async (ctx, args): Promise<SerializedMessage[]> => {
|
|
860
|
+
// const messages = await ctx.db
|
|
861
|
+
// .query("messages")
|
|
862
|
+
// .withIndex("threadId", (q) => q.eq("threadId", args.threadId))
|
|
863
|
+
// .order("desc")
|
|
864
|
+
// .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
|
|
865
|
+
|
|
866
|
+
// const handled: boolean[] = [];
|
|
867
|
+
// const toFetch: number[] = [];
|
|
868
|
+
// for (const m of messages) {
|
|
869
|
+
// handled[m.threadOrder] = true;
|
|
870
|
+
// }
|
|
871
|
+
// await Promise.all(
|
|
872
|
+
// args.selectBy?.include?.map(async (range) => {
|
|
873
|
+
// const includeDoc = await ctx.db
|
|
874
|
+
// .query("messages")
|
|
875
|
+
// .withIndex("id", (q) => q.eq("id", range.id))
|
|
876
|
+
// .unique();
|
|
877
|
+
// if (!includeDoc) {
|
|
878
|
+
// console.warn(`Message ${range.id} not found`);
|
|
879
|
+
// return;
|
|
880
|
+
// }
|
|
881
|
+
// if (!range.withPreviousMessages && !range.withNextMessages) {
|
|
882
|
+
// messages.push(includeDoc);
|
|
883
|
+
// return;
|
|
884
|
+
// }
|
|
885
|
+
// const order = includeDoc.threadOrder;
|
|
886
|
+
// for (
|
|
887
|
+
// let i = order - (range.withPreviousMessages ?? 0);
|
|
888
|
+
// i < order + (range.withNextMessages ?? 0);
|
|
889
|
+
// i++
|
|
890
|
+
// ) {
|
|
891
|
+
// if (!handled[i]) {
|
|
892
|
+
// toFetch.push(i);
|
|
893
|
+
// handled[i] = true;
|
|
894
|
+
// }
|
|
895
|
+
// }
|
|
896
|
+
// }) ?? []
|
|
897
|
+
// );
|
|
898
|
+
// // sort and find unique numbers in toFetch
|
|
899
|
+
// const uniqueToFetch = [...new Set(toFetch)].sort();
|
|
900
|
+
// // find contiguous ranges in uniqueToFetch
|
|
901
|
+
// const ranges: { start: number; end: number }[] = [];
|
|
902
|
+
// for (let i = 0; i < uniqueToFetch.length; i++) {
|
|
903
|
+
// const start = uniqueToFetch[i];
|
|
904
|
+
// let end = start;
|
|
905
|
+
// while (i + 1 < uniqueToFetch.length && uniqueToFetch[i + 1] === end + 1) {
|
|
906
|
+
// end++;
|
|
907
|
+
// i++;
|
|
908
|
+
// }
|
|
909
|
+
// ranges.push({ start, end });
|
|
910
|
+
// }
|
|
911
|
+
// const fetched = (
|
|
912
|
+
// await Promise.all(
|
|
913
|
+
// ranges.map(async (range) => {
|
|
914
|
+
// return await ctx.db
|
|
915
|
+
// .query("messages")
|
|
916
|
+
// .withIndex("threadId", (q) =>
|
|
917
|
+
// q
|
|
918
|
+
// .eq("threadId", args.threadId)
|
|
919
|
+
// .gte("threadOrder", range.start)
|
|
920
|
+
// .lte("threadOrder", range.end)
|
|
921
|
+
// )
|
|
922
|
+
// .collect();
|
|
923
|
+
// })
|
|
924
|
+
// )
|
|
925
|
+
// ).flat();
|
|
926
|
+
// messages.push(...fetched);
|
|
927
|
+
// return messages.map(messageToSerializedMastra);
|
|
928
|
+
// },
|
|
929
|
+
// returns: v.array(vSerializedMessage),
|
|
930
|
+
// });
|
|
931
|
+
|
|
932
|
+
// export const saveMessages = mutation({
|
|
933
|
+
// args: { messages: v.array(vSerializedMessage) },
|
|
934
|
+
// handler: async (ctx, args) => {
|
|
935
|
+
// const messagesByThreadId: Record<string, SerializedMessage[]> = {};
|
|
936
|
+
// for (const message of args.messages) {
|
|
937
|
+
// messagesByThreadId[message.threadId] = [
|
|
938
|
+
// ...(messagesByThreadId[message.threadId] ?? []),
|
|
939
|
+
// message,
|
|
940
|
+
// ];
|
|
941
|
+
// }
|
|
942
|
+
// for (const threadId in messagesByThreadId) {
|
|
943
|
+
// const lastMessage = await ctx.db
|
|
944
|
+
// .query("messages")
|
|
945
|
+
// .withIndex("threadId", (q) => q.eq("threadId", threadId))
|
|
946
|
+
// .order("desc")
|
|
947
|
+
// .first();
|
|
948
|
+
// let threadOrder = lastMessage?.threadOrder ?? 0;
|
|
949
|
+
// for (const message of messagesByThreadId[threadId]) {
|
|
950
|
+
// threadOrder++;
|
|
951
|
+
// await ctx.db.insert("messages", {
|
|
952
|
+
// ...message,
|
|
953
|
+
// threadOrder,
|
|
954
|
+
// });
|
|
955
|
+
// }
|
|
956
|
+
// }
|
|
957
|
+
// },
|
|
958
|
+
// returns: v.null(),
|
|
959
|
+
// });
|