@convex-dev/agent 0.1.7-alpha.2 → 0.1.7
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 +55 -21
- package/dist/commonjs/react/usePaginatedQuery.d.ts.map +1 -1
- package/dist/commonjs/react/usePaginatedQuery.js +0 -1
- package/dist/commonjs/react/usePaginatedQuery.js.map +1 -1
- package/dist/commonjs.tsbuildinfo +1 -1
- package/dist/esm/react/usePaginatedQuery.d.ts.map +1 -1
- package/dist/esm/react/usePaginatedQuery.js +0 -1
- package/dist/esm/react/usePaginatedQuery.js.map +1 -1
- package/dist/esm.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/client/index.test.ts +265 -2
- package/src/react/deltas.test.ts +11 -1
- package/src/react/usePaginatedQuery.ts +0 -1
package/src/client/index.test.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { MockLanguageModelV1 } from "ai/test";
|
|
|
19
19
|
import type { LanguageModelV1, LanguageModelV1StreamPart } from "ai";
|
|
20
20
|
import { simulateReadableStream } from "ai";
|
|
21
21
|
import { components, initConvexTest } from "./setup.test";
|
|
22
|
+
import { z } from "zod";
|
|
22
23
|
|
|
23
24
|
const schema = defineSchema({});
|
|
24
25
|
type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
|
@@ -58,6 +59,16 @@ export const createThread = mutation({
|
|
|
58
59
|
},
|
|
59
60
|
});
|
|
60
61
|
|
|
62
|
+
export const createThreadMutation = agent.createThreadMutation();
|
|
63
|
+
export const generateObjectAction = agent.asObjectAction({
|
|
64
|
+
schema: z.object({
|
|
65
|
+
prompt: z.any().describe("The prompt passed in"),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
export const generateTextAction = agent.asTextAction({});
|
|
69
|
+
export const streamTextAction = agent.asTextAction({ stream: true });
|
|
70
|
+
export const saveMessageMutation = agent.asSaveMessagesMutation();
|
|
71
|
+
|
|
61
72
|
export const createAndGenerate = action({
|
|
62
73
|
args: {},
|
|
63
74
|
handler: async (ctx) => {
|
|
@@ -71,11 +82,87 @@ export const createAndGenerate = action({
|
|
|
71
82
|
},
|
|
72
83
|
});
|
|
73
84
|
|
|
85
|
+
export const continueThreadAction = action({
|
|
86
|
+
args: { threadId: v.string(), userId: v.optional(v.string()) },
|
|
87
|
+
handler: async (ctx, args) => {
|
|
88
|
+
const { thread } = await agent.continueThread(ctx, args);
|
|
89
|
+
return { threadId: thread.threadId };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const generateTextWithThread = action({
|
|
94
|
+
args: {
|
|
95
|
+
threadId: v.string(),
|
|
96
|
+
userId: v.optional(v.string()),
|
|
97
|
+
messages: v.array(v.any()),
|
|
98
|
+
contextOptions: v.optional(v.any()),
|
|
99
|
+
storageOptions: v.optional(v.any()),
|
|
100
|
+
},
|
|
101
|
+
handler: async (ctx, args) => {
|
|
102
|
+
const { thread } = await agent.continueThread(ctx, {
|
|
103
|
+
threadId: args.threadId,
|
|
104
|
+
userId: args.userId,
|
|
105
|
+
});
|
|
106
|
+
const result = await thread.generateText(
|
|
107
|
+
{ messages: args.messages },
|
|
108
|
+
{
|
|
109
|
+
contextOptions: args.contextOptions,
|
|
110
|
+
storageOptions: args.storageOptions,
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
return { text: result.text };
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const generateObjectWithThread = action({
|
|
118
|
+
args: {
|
|
119
|
+
threadId: v.string(),
|
|
120
|
+
userId: v.optional(v.string()),
|
|
121
|
+
prompt: v.string(),
|
|
122
|
+
},
|
|
123
|
+
handler: async (ctx, args) => {
|
|
124
|
+
const { thread } = await agent.continueThread(ctx, {
|
|
125
|
+
threadId: args.threadId,
|
|
126
|
+
userId: args.userId,
|
|
127
|
+
});
|
|
128
|
+
const result = await thread.generateObject({
|
|
129
|
+
prompt: args.prompt,
|
|
130
|
+
schema: z.object({ prompt: z.any() }),
|
|
131
|
+
});
|
|
132
|
+
return { object: result.object };
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const fetchContextAction = action({
|
|
137
|
+
args: {
|
|
138
|
+
userId: v.optional(v.string()),
|
|
139
|
+
threadId: v.optional(v.string()),
|
|
140
|
+
messages: v.array(v.any()),
|
|
141
|
+
contextOptions: v.optional(v.any()),
|
|
142
|
+
},
|
|
143
|
+
handler: async (ctx, args) => {
|
|
144
|
+
const context = await agent.fetchContextMessages(ctx, {
|
|
145
|
+
userId: args.userId,
|
|
146
|
+
threadId: args.threadId,
|
|
147
|
+
messages: args.messages,
|
|
148
|
+
contextOptions: args.contextOptions,
|
|
149
|
+
});
|
|
150
|
+
return context;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
74
154
|
const testApi: ApiFromModules<{
|
|
75
155
|
fns: {
|
|
76
156
|
createAndGenerate: typeof createAndGenerate;
|
|
77
157
|
createThread: typeof createThread;
|
|
78
158
|
testQuery: typeof testQuery;
|
|
159
|
+
continueThreadAction: typeof continueThreadAction;
|
|
160
|
+
generateTextWithThread: typeof generateTextWithThread;
|
|
161
|
+
generateObjectWithThread: typeof generateObjectWithThread;
|
|
162
|
+
fetchContextAction: typeof fetchContextAction;
|
|
163
|
+
generateTextAction: typeof generateTextAction;
|
|
164
|
+
generateObjectAction: typeof generateObjectAction;
|
|
165
|
+
saveMessageMutation: typeof saveMessageMutation;
|
|
79
166
|
};
|
|
80
167
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
168
|
}>["fns"] = anyApi["index.test"] as any;
|
|
@@ -90,7 +177,7 @@ describe("Agent thick client", () => {
|
|
|
90
177
|
const t = initConvexTest(schema);
|
|
91
178
|
const result = await t.action(testApi.createAndGenerate, {});
|
|
92
179
|
expect(result).toBeDefined();
|
|
93
|
-
expect(result).toMatch("
|
|
180
|
+
expect(result).toMatch("Hello");
|
|
94
181
|
});
|
|
95
182
|
});
|
|
96
183
|
|
|
@@ -164,12 +251,14 @@ function mockModel(): LanguageModelV1 {
|
|
|
164
251
|
return new MockLanguageModelV1({
|
|
165
252
|
provider: "mock",
|
|
166
253
|
modelId: "mock",
|
|
254
|
+
defaultObjectGenerationMode: "json",
|
|
255
|
+
// supportsStructuredOutputs: true,
|
|
167
256
|
doGenerate: async ({ prompt }) => ({
|
|
168
257
|
finishReason: "stop",
|
|
169
258
|
usage: { completionTokens: 10, promptTokens: 3 },
|
|
170
259
|
logprobs: undefined,
|
|
171
260
|
rawCall: { rawPrompt: null, rawSettings: {} },
|
|
172
|
-
text:
|
|
261
|
+
text: JSON.stringify({ prompt }),
|
|
173
262
|
}),
|
|
174
263
|
doStream: async ({ prompt }) => ({
|
|
175
264
|
stream: simulateReadableStream({
|
|
@@ -192,3 +281,177 @@ function mockModel(): LanguageModelV1 {
|
|
|
192
281
|
}),
|
|
193
282
|
});
|
|
194
283
|
}
|
|
284
|
+
|
|
285
|
+
describe("Agent option variations and normal behavior", () => {
|
|
286
|
+
test("Agent can be constructed with minimal options", () => {
|
|
287
|
+
const a = new Agent(components.agent, { chat: mockModel() });
|
|
288
|
+
expect(a).toBeInstanceOf(Agent);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("Agent can be constructed with all options", () => {
|
|
292
|
+
const a = new Agent(components.agent, {
|
|
293
|
+
name: "full",
|
|
294
|
+
chat: mockModel(),
|
|
295
|
+
instructions: "Test instructions",
|
|
296
|
+
contextOptions: { recentMessages: 5 },
|
|
297
|
+
storageOptions: { saveMessages: "all" },
|
|
298
|
+
maxSteps: 2,
|
|
299
|
+
maxRetries: 1,
|
|
300
|
+
usageHandler: async () => {},
|
|
301
|
+
rawRequestResponseHandler: async () => {},
|
|
302
|
+
});
|
|
303
|
+
expect(a.options.name).toBe("full");
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("Agent thread management", () => {
|
|
308
|
+
test("createThread returns threadId (mutation context)", async () => {
|
|
309
|
+
const t = initConvexTest(schema);
|
|
310
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
311
|
+
agent.createThread(ctx, { userId: "2" })
|
|
312
|
+
);
|
|
313
|
+
expect(threadId).toBeTypeOf("string");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("continueThread returns thread object", async () => {
|
|
317
|
+
const t = initConvexTest(schema);
|
|
318
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
319
|
+
agent.createThread(ctx, { userId: "3" })
|
|
320
|
+
);
|
|
321
|
+
const result = await t.action(testApi.continueThreadAction, {
|
|
322
|
+
threadId,
|
|
323
|
+
userId: "3",
|
|
324
|
+
});
|
|
325
|
+
expect(result.threadId).toBe(threadId);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe("Agent message operations", () => {
|
|
330
|
+
test("saveMessage and saveMessages store messages", async () => {
|
|
331
|
+
const t = initConvexTest(schema);
|
|
332
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
333
|
+
agent.createThread(ctx, { userId: "4" })
|
|
334
|
+
);
|
|
335
|
+
const { messageId } = await t.run(async (ctx) =>
|
|
336
|
+
agent.saveMessage(ctx, {
|
|
337
|
+
threadId,
|
|
338
|
+
userId: "4",
|
|
339
|
+
message: { role: "user", content: "Hello" },
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
expect(messageId).toBeTypeOf("string");
|
|
343
|
+
|
|
344
|
+
const { lastMessageId, messages } = await t.run(async (ctx) =>
|
|
345
|
+
agent.saveMessages(ctx, {
|
|
346
|
+
threadId,
|
|
347
|
+
userId: "4",
|
|
348
|
+
messages: [
|
|
349
|
+
{ role: "user", content: "Hi" },
|
|
350
|
+
{ role: "assistant", content: "Hello!" },
|
|
351
|
+
],
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
expect(messages.length).toBe(2);
|
|
355
|
+
expect(lastMessageId).toBe(messages[1]._id);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe("Agent text/object generation", () => {
|
|
360
|
+
test("generateText with custom context and storage options", async () => {
|
|
361
|
+
const t = initConvexTest(schema);
|
|
362
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
363
|
+
agent.createThread(ctx, { userId: "5" })
|
|
364
|
+
);
|
|
365
|
+
const result = await t.action(testApi.generateTextWithThread, {
|
|
366
|
+
threadId,
|
|
367
|
+
userId: "5",
|
|
368
|
+
messages: [{ role: "user", content: "Test" }],
|
|
369
|
+
contextOptions: { recentMessages: 1 },
|
|
370
|
+
storageOptions: { saveMessages: "all" },
|
|
371
|
+
});
|
|
372
|
+
expect(result.text).toMatch(/Test/);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("generateObject returns object", async () => {
|
|
376
|
+
const t = initConvexTest(schema);
|
|
377
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
378
|
+
agent.createThread(ctx, { userId: "6" })
|
|
379
|
+
);
|
|
380
|
+
const result = await t.action(testApi.generateObjectWithThread, {
|
|
381
|
+
threadId,
|
|
382
|
+
userId: "6",
|
|
383
|
+
prompt: "Object please",
|
|
384
|
+
});
|
|
385
|
+
expect(result.object).toBeDefined();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("Agent-generated mutations/actions/queries", () => {
|
|
390
|
+
test("createThreadMutation works via t.mutation", async () => {
|
|
391
|
+
const t = initConvexTest(schema);
|
|
392
|
+
// This test is for the registered mutation, not the agent method
|
|
393
|
+
const result = await t.mutation(testApi.createThread, {});
|
|
394
|
+
expect(result.threadId).toBeTypeOf("string");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("asTextAction and asObjectAction work via t.action", async () => {
|
|
398
|
+
const t = initConvexTest(schema);
|
|
399
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
400
|
+
agent.createThread(ctx, { userId: "8" })
|
|
401
|
+
);
|
|
402
|
+
const textResult = await t.action(testApi.generateTextAction, {
|
|
403
|
+
userId: "8",
|
|
404
|
+
threadId,
|
|
405
|
+
messages: [{ role: "user", content: "Say hi" }],
|
|
406
|
+
});
|
|
407
|
+
expect(textResult.text).toMatch(/Say hi/);
|
|
408
|
+
|
|
409
|
+
const objResult = await t.action(testApi.generateObjectAction, {
|
|
410
|
+
userId: "8",
|
|
411
|
+
threadId,
|
|
412
|
+
messages: [{ role: "user", content: "Give object" }],
|
|
413
|
+
});
|
|
414
|
+
expect(objResult.object).toBeDefined();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("asSaveMessagesMutation works via t.mutation", async () => {
|
|
418
|
+
const t = initConvexTest(schema);
|
|
419
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
420
|
+
agent.createThread(ctx, { userId: "9" })
|
|
421
|
+
);
|
|
422
|
+
const result = await t.mutation(testApi.saveMessageMutation, {
|
|
423
|
+
threadId,
|
|
424
|
+
messages: [
|
|
425
|
+
{
|
|
426
|
+
message: { role: "user", content: "Saved via mutation" },
|
|
427
|
+
// add more metadata fields as needed
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
});
|
|
431
|
+
expect(result.lastMessageId).toBeDefined();
|
|
432
|
+
expect(result.messageIds.length).toBe(1);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe("Agent context and search options", () => {
|
|
437
|
+
test("fetchContextMessages returns context messages", async () => {
|
|
438
|
+
const t = initConvexTest(schema);
|
|
439
|
+
const { threadId } = await t.run(async (ctx) =>
|
|
440
|
+
agent.createThread(ctx, { userId: "10" })
|
|
441
|
+
);
|
|
442
|
+
await t.run(async (ctx) =>
|
|
443
|
+
agent.saveMessage(ctx, {
|
|
444
|
+
threadId,
|
|
445
|
+
userId: "10",
|
|
446
|
+
message: { role: "user", content: "Context test" },
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
const context = await t.action(testApi.fetchContextAction, {
|
|
450
|
+
userId: "10",
|
|
451
|
+
threadId,
|
|
452
|
+
messages: [{ role: "user", content: "Context test" }],
|
|
453
|
+
contextOptions: { recentMessages: 1 },
|
|
454
|
+
});
|
|
455
|
+
expect(context.length).toBeGreaterThan(0);
|
|
456
|
+
});
|
|
457
|
+
});
|
package/src/react/deltas.test.ts
CHANGED
|
@@ -287,7 +287,17 @@ describe("mergeDeltas", () => {
|
|
|
287
287
|
expect(messages1.map((m) => omit(m, ["_creationTime"]))).toEqual(
|
|
288
288
|
messages2.map((m) => omit(m, ["_creationTime"]))
|
|
289
289
|
);
|
|
290
|
-
expect(
|
|
290
|
+
expect(
|
|
291
|
+
streams1.map((s) => ({
|
|
292
|
+
...s,
|
|
293
|
+
messages: s.messages.map((m) => omit(m, ["_creationTime"])),
|
|
294
|
+
}))
|
|
295
|
+
).toEqual(
|
|
296
|
+
streams2.map((s) => ({
|
|
297
|
+
...s,
|
|
298
|
+
messages: s.messages.map((m) => omit(m, ["_creationTime"])),
|
|
299
|
+
}))
|
|
300
|
+
);
|
|
291
301
|
expect(changed1).toBe(changed2);
|
|
292
302
|
// Inputs should remain unchanged
|
|
293
303
|
expect(streamMessages).toEqual([makeStreamMessage(streamId, 8, 0)]);
|
|
@@ -279,7 +279,6 @@ export function usePaginatedQuery<Query extends PaginatedQueryReference>(
|
|
|
279
279
|
allItems.push(...currResult.page);
|
|
280
280
|
}
|
|
281
281
|
return [allItems, currResult];
|
|
282
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
283
282
|
}, [
|
|
284
283
|
resultsObject,
|
|
285
284
|
currState.pageKeys,
|