@convex-dev/agent 0.1.18 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/client/createTool.d.ts +30 -11
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -10
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/defaultComponent.d.ts +11 -0
  6. package/dist/client/defaultComponent.d.ts.map +1 -0
  7. package/dist/client/defaultComponent.js +7 -0
  8. package/dist/client/defaultComponent.js.map +1 -0
  9. package/dist/client/definePlaygroundAPI.d.ts +68 -23
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +7 -20
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +14 -4
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +66 -11
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +323 -780
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +445 -543
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +256 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +97 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +35 -12
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +21 -4
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +21 -21
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js +35 -40
  32. package/dist/client/streaming.js.map +1 -1
  33. package/dist/client/threads.d.ts +46 -0
  34. package/dist/client/threads.d.ts.map +1 -0
  35. package/dist/client/threads.js +49 -0
  36. package/dist/client/threads.js.map +1 -0
  37. package/dist/client/types.d.ts +159 -75
  38. package/dist/client/types.d.ts.map +1 -1
  39. package/dist/component/_generated/api.d.ts +261 -209
  40. package/dist/component/messages.d.ts +314 -105
  41. package/dist/component/messages.d.ts.map +1 -1
  42. package/dist/component/messages.js +141 -83
  43. package/dist/component/messages.js.map +1 -1
  44. package/dist/component/schema.d.ts +631 -870
  45. package/dist/component/schema.d.ts.map +1 -1
  46. package/dist/component/schema.js +11 -16
  47. package/dist/component/schema.js.map +1 -1
  48. package/dist/component/streams.d.ts +29 -149
  49. package/dist/component/streams.d.ts.map +1 -1
  50. package/dist/component/streams.js +104 -60
  51. package/dist/component/streams.js.map +1 -1
  52. package/dist/component/threads.d.ts +4 -4
  53. package/dist/component/users.d.ts +3 -3
  54. package/dist/mapping.d.ts +15 -16
  55. package/dist/mapping.d.ts.map +1 -1
  56. package/dist/mapping.js +208 -62
  57. package/dist/mapping.js.map +1 -1
  58. package/dist/parts.d.ts +3 -0
  59. package/dist/parts.d.ts.map +1 -0
  60. package/dist/parts.js +34 -0
  61. package/dist/parts.js.map +1 -0
  62. package/dist/react/SmoothText.d.ts +5 -0
  63. package/dist/react/SmoothText.d.ts.map +1 -0
  64. package/dist/react/SmoothText.js +6 -0
  65. package/dist/react/SmoothText.js.map +1 -0
  66. package/dist/react/deltas.d.ts +3 -5
  67. package/dist/react/deltas.d.ts.map +1 -1
  68. package/dist/react/deltas.js +89 -53
  69. package/dist/react/deltas.js.map +1 -1
  70. package/dist/react/index.d.ts +1 -0
  71. package/dist/react/index.d.ts.map +1 -1
  72. package/dist/react/index.js +31 -11
  73. package/dist/react/index.js.map +1 -1
  74. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  75. package/dist/react/optimisticallySendMessage.js +2 -8
  76. package/dist/react/optimisticallySendMessage.js.map +1 -1
  77. package/dist/react/toUIMessages.d.ts +5 -4
  78. package/dist/react/toUIMessages.d.ts.map +1 -1
  79. package/dist/react/toUIMessages.js +103 -40
  80. package/dist/react/toUIMessages.js.map +1 -1
  81. package/dist/react/useSmoothText.d.ts +13 -12
  82. package/dist/react/useSmoothText.d.ts.map +1 -1
  83. package/dist/react/useSmoothText.js +1 -1
  84. package/dist/react/useSmoothText.js.map +1 -1
  85. package/dist/validators.d.ts +751 -1464
  86. package/dist/validators.d.ts.map +1 -1
  87. package/dist/validators.js +39 -80
  88. package/dist/validators.js.map +1 -1
  89. package/package.json +34 -30
  90. package/src/client/createTool.ts +66 -40
  91. package/src/client/defaultComponent.ts +17 -0
  92. package/src/client/definePlaygroundAPI.ts +17 -31
  93. package/src/client/files.ts +94 -17
  94. package/src/client/index.test.ts +28 -38
  95. package/src/client/index.ts +785 -977
  96. package/src/client/messages.ts +205 -0
  97. package/src/client/search.ts +37 -10
  98. package/src/client/streaming.ts +53 -56
  99. package/src/client/threads.ts +78 -0
  100. package/src/client/types.ts +239 -131
  101. package/src/component/_generated/api.d.ts +261 -209
  102. package/src/component/messages.test.ts +182 -40
  103. package/src/component/messages.ts +165 -108
  104. package/src/component/schema.ts +11 -17
  105. package/src/component/streams.ts +180 -69
  106. package/src/mapping.ts +253 -96
  107. package/src/parts.ts +39 -0
  108. package/src/react/SmoothText.tsx +9 -0
  109. package/src/react/deltas.test.ts +38 -42
  110. package/src/react/deltas.ts +116 -65
  111. package/src/react/index.ts +35 -13
  112. package/src/react/optimisticallySendMessage.ts +2 -8
  113. package/src/react/toUIMessages.test.ts +154 -36
  114. package/src/react/toUIMessages.ts +136 -57
  115. package/src/react/useSmoothText.ts +14 -15
  116. package/src/validators.test.ts +1 -117
  117. package/src/validators.ts +57 -107
  118. package/dist/client/listMessages.d.ts +0 -22
  119. package/dist/client/listMessages.d.ts.map +0 -1
  120. package/dist/client/listMessages.js +0 -25
  121. package/dist/client/listMessages.js.map +0 -1
  122. package/src/client/listMessages.ts +0 -38
@@ -1,27 +1,27 @@
1
1
  import {
2
+ actionGeneric,
3
+ mutationGeneric,
2
4
  paginationOptsValidator,
3
5
  queryGeneric,
4
- mutationGeneric,
5
- actionGeneric,
6
- type GenericDataModel,
7
- type GenericQueryCtx,
8
6
  type ApiFromModules,
9
7
  type GenericActionCtx,
8
+ type GenericDataModel,
9
+ type GenericQueryCtx,
10
10
  } from "convex/server";
11
+ import { v } from "convex/values";
11
12
  import {
13
+ createThread as createThread_,
14
+ listMessages as listMessages_,
15
+ deserializeMessage,
16
+ vContextOptions,
17
+ vMessage,
12
18
  vMessageDoc,
13
- vThreadDoc,
14
19
  vPaginationResult,
15
- vMessage,
16
- vContextOptions,
17
20
  vStorageOptions,
18
- type AgentComponent,
21
+ vThreadDoc,
19
22
  type Agent,
20
- listMessages as listMessages_,
21
- createThread as createThread_,
23
+ type AgentComponent,
22
24
  } from "./index.js";
23
- import { v } from "convex/values";
24
- import { deserializeMessage } from "../mapping.js";
25
25
 
26
26
  export type PlaygroundAPI = ApiFromModules<{
27
27
  playground: ReturnType<typeof definePlaygroundAPI>;
@@ -61,9 +61,7 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
61
61
  }
62
62
 
63
63
  const isApiKeyValid = queryGeneric({
64
- args: {
65
- apiKey: v.string(),
66
- },
64
+ args: { apiKey: v.string() },
67
65
  handler: async (ctx, args) => {
68
66
  try {
69
67
  await validateApiKey(ctx, args.apiKey);
@@ -107,18 +105,14 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
107
105
  instructions: agent.options.instructions,
108
106
  contextOptions: agent.options.contextOptions,
109
107
  storageOptions: agent.options.storageOptions,
110
- maxSteps: agent.options.maxSteps,
111
- maxRetries: agent.options.maxRetries,
108
+ maxRetries: agent.options.callSettings?.maxRetries,
112
109
  tools: agent.options.tools ? Object.keys(agent.options.tools) : [],
113
110
  }));
114
111
  },
115
112
  });
116
113
 
117
114
  const listUsers = queryGeneric({
118
- args: {
119
- apiKey: v.string(),
120
- paginationOpts: paginationOptsValidator,
121
- },
115
+ args: { apiKey: v.string(), paginationOpts: paginationOptsValidator },
122
116
  handler: async (ctx, args) => {
123
117
  await validateApiKey(ctx, args.apiKey);
124
118
  const users = await ctx.runQuery(component.users.listUsersWithThreads, {
@@ -134,12 +128,7 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
134
128
  ),
135
129
  };
136
130
  },
137
- returns: vPaginationResult(
138
- v.object({
139
- _id: v.string(),
140
- name: v.string(),
141
- }),
142
- ),
131
+ returns: vPaginationResult(v.object({ _id: v.string(), name: v.string() })),
143
132
  });
144
133
 
145
134
  // List threads for a user (query)
@@ -168,10 +157,7 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
168
157
  } = await ctx.runQuery(component.messages.listMessagesByThreadId, {
169
158
  threadId: thread._id,
170
159
  order: "desc",
171
- paginationOpts: {
172
- numItems: 1,
173
- cursor: null,
174
- },
160
+ paginationOpts: { numItems: 1, cursor: null },
175
161
  });
176
162
  return {
177
163
  ...thread,
@@ -1,4 +1,10 @@
1
- import type { FilePart, ImagePart } from "ai";
1
+ import type {
2
+ AssistantContent,
3
+ FilePart,
4
+ ImagePart,
5
+ ModelMessage,
6
+ UserContent,
7
+ } from "ai";
2
8
  import type { Id } from "../component/_generated/dataModel.js";
3
9
  import type {
4
10
  ActionCtx,
@@ -6,6 +12,8 @@ import type {
6
12
  QueryCtx,
7
13
  RunMutationCtx,
8
14
  } from "./types.js";
15
+ import type { Message } from "../validators.js";
16
+ import { assert } from "convex-helpers";
9
17
 
10
18
  export const MAX_FILE_SIZE = 1024 * 64;
11
19
 
@@ -22,8 +30,8 @@ type File = {
22
30
  * @param ctx A ctx object from an action.
23
31
  * @param component The agent component.
24
32
  * @param blob The blob to store.
25
- * @param filename The filename to store.
26
- * @param sha256 The sha256 hash of the file. If not provided, it will be
33
+ * @param args.filename The filename to store.
34
+ * @param args.sha256 The sha256 hash of the file. If not provided, it will be
27
35
  * computed. However, to ensure no corruption during transfer, you can
28
36
  * calculate this on the client to enforce integrity.
29
37
  * @returns The URL, fileId, and storageId of the stored file.
@@ -32,8 +40,7 @@ export async function storeFile(
32
40
  ctx: ActionCtx | RunMutationCtx,
33
41
  component: AgentComponent,
34
42
  blob: Blob,
35
- filename?: string,
36
- sha256?: string,
43
+ { filename, sha256 }: { filename?: string; sha256?: string } = {},
37
44
  ): Promise<{
38
45
  file: File;
39
46
  filePart: FilePart;
@@ -148,24 +155,94 @@ export async function getFile(
148
155
 
149
156
  function getParts(
150
157
  url: string,
151
- mimeType: string,
158
+ mediaType: string,
152
159
  filename: string | undefined,
153
- ): {
154
- filePart: FilePart;
155
- imagePart: ImagePart | undefined;
156
- } {
160
+ ): { filePart: FilePart; imagePart: ImagePart | undefined } {
157
161
  const filePart: FilePart = {
158
162
  type: "file",
159
163
  data: new URL(url),
160
- mimeType,
164
+ mediaType,
161
165
  filename,
162
166
  };
163
- const imagePart: ImagePart | undefined = mimeType.startsWith("image/")
164
- ? {
165
- type: "image",
166
- image: new URL(url),
167
- mimeType,
168
- }
167
+ const imagePart: ImagePart | undefined = mediaType.startsWith("image/")
168
+ ? { type: "image", image: new URL(url), mediaType }
169
169
  : undefined;
170
170
  return { filePart, imagePart };
171
171
  }
172
+
173
+ /**
174
+ * Check if a URL points to localhost
175
+ */
176
+ function isLocalhostUrl(url: URL): boolean {
177
+ return (
178
+ url.hostname === "localhost" ||
179
+ url.hostname === "127.0.0.1" ||
180
+ url.hostname === "::1" ||
181
+ url.hostname === "0.0.0.0"
182
+ );
183
+ }
184
+
185
+ /**
186
+ * Download a file from a URL
187
+ */
188
+ async function downloadFile(url: URL): Promise<ArrayBuffer> {
189
+ // Fetch the file
190
+ const response = await fetch(url);
191
+ if (!response.ok) {
192
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
193
+ }
194
+
195
+ return await response.arrayBuffer();
196
+ }
197
+
198
+ /**
199
+ * Process messages to inline file and image URLs that point to localhost
200
+ * by converting them to base64. This solves the problem of LLMs not being
201
+ * able to access localhost URLs.
202
+ */
203
+ export async function inlineMessagesFiles(
204
+ messages: (ModelMessage | Message)[],
205
+ ): Promise<(ModelMessage | Message)[]> {
206
+ // Process each message to convert localhost URLs to base64
207
+ return Promise.all(
208
+ messages.map(async (message): Promise<ModelMessage | Message> => {
209
+ if (
210
+ (message.role !== "user" && message.role !== "assistant") ||
211
+ typeof message.content === "string" ||
212
+ !Array.isArray(message.content)
213
+ ) {
214
+ return message;
215
+ }
216
+
217
+ const processedContent = await Promise.all(
218
+ message.content.map(async (part) => {
219
+ if (part.type === "image" && part.image instanceof URL) {
220
+ assert(
221
+ message.role === "user",
222
+ "Images can only be in user messages",
223
+ );
224
+ if (isLocalhostUrl(part.image)) {
225
+ const imageData = await downloadFile(part.image);
226
+ return { ...part, image: imageData } as ImagePart;
227
+ }
228
+ }
229
+
230
+ // Handle file parts
231
+ if (part.type === "file" && part.data instanceof URL) {
232
+ if (isLocalhostUrl(part.data)) {
233
+ const fileData = await downloadFile(part.data);
234
+ return { ...part, data: fileData } as FilePart;
235
+ }
236
+ }
237
+
238
+ return part;
239
+ }),
240
+ );
241
+ if (message.role === "user") {
242
+ return { ...message, content: processedContent as UserContent };
243
+ } else {
244
+ return { ...message, content: processedContent as AssistantContent };
245
+ }
246
+ }),
247
+ );
248
+ }
@@ -19,11 +19,14 @@ import type {
19
19
  } from "convex/server";
20
20
  import { v } from "convex/values";
21
21
  import { defineSchema } from "convex/server";
22
- import { MockLanguageModelV1 } from "ai/test";
23
- import type { LanguageModelV1, LanguageModelV1StreamPart } from "ai";
24
- import { simulateReadableStream } from "ai";
22
+ import { MockLanguageModelV2 } from "ai/test";
23
+ import type {
24
+ LanguageModelV2,
25
+ LanguageModelV2StreamPart,
26
+ } from "@ai-sdk/provider";
27
+ import { simulateReadableStream, stepCountIs } from "ai";
25
28
  import { components, initConvexTest } from "./setup.test.js";
26
- import { z } from "zod";
29
+ import { z } from "zod/v4";
27
30
 
28
31
  const schema = defineSchema({});
29
32
  type DataModel = DataModelFromSchemaDefinition<typeof schema>;
@@ -36,7 +39,7 @@ const agent = new Agent(components.agent, {
36
39
  name: "test",
37
40
  instructions: "You are a test agent",
38
41
  // TODO: get mock model that works in v8
39
- chat: mockModel(),
42
+ languageModel: mockModel(),
40
43
  });
41
44
 
42
45
  export const testQuery = query({
@@ -44,10 +47,7 @@ export const testQuery = query({
44
47
  handler: async (ctx, args) => {
45
48
  return await agent.listMessages(ctx, {
46
49
  threadId: args.threadId,
47
- paginationOpts: {
48
- cursor: null,
49
- numItems: 10,
50
- },
50
+ paginationOpts: { cursor: null, numItems: 10 },
51
51
  excludeToolMessages: true,
52
52
  statuses: ["success"],
53
53
  });
@@ -57,18 +57,14 @@ export const testQuery = query({
57
57
  export const createThread = mutation({
58
58
  args: {},
59
59
  handler: async (ctx) => {
60
- const { threadId } = await agent.createThread(ctx, {
61
- userId: "1",
62
- });
60
+ const { threadId } = await agent.createThread(ctx, { userId: "1" });
63
61
  return { threadId };
64
62
  },
65
63
  });
66
64
 
67
65
  export const createThreadMutation = agent.createThreadMutation();
68
66
  export const generateObjectAction = agent.asObjectAction({
69
- schema: z.object({
70
- prompt: z.any().describe("The prompt passed in"),
71
- }),
67
+ schema: z.object({ prompt: z.any().describe("The prompt passed in") }),
72
68
  });
73
69
  export const generateTextAction = agent.asTextAction({});
74
70
  export const streamTextAction = agent.asTextAction({ stream: true });
@@ -77,9 +73,7 @@ export const saveMessageMutation = agent.asSaveMessagesMutation();
77
73
  export const createAndGenerate = action({
78
74
  args: {},
79
75
  handler: async (ctx) => {
80
- const { thread } = await agent.createThread(ctx, {
81
- userId: "1",
82
- });
76
+ const { thread } = await agent.createThread(ctx, { userId: "1" });
83
77
  const result = await thread.generateText({
84
78
  messages: [{ role: "user", content: "Hello" }],
85
79
  });
@@ -233,10 +227,7 @@ describe("filterOutOrphanedToolMessages", () => {
233
227
  order: 1,
234
228
  stepOrder: 2,
235
229
  tool: true,
236
- message: {
237
- role: "assistant",
238
- content: [{ type: "text", text: "Hello" }],
239
- },
230
+ message: { role: "assistant", content: [{ type: "text", text: "Hello" }] },
240
231
  status: "success",
241
232
  threadId: "1",
242
233
  };
@@ -252,16 +243,16 @@ describe("filterOutOrphanedToolMessages", () => {
252
243
  });
253
244
  });
254
245
 
255
- function mockModel(): LanguageModelV1 {
256
- return new MockLanguageModelV1({
246
+ function mockModel(): LanguageModelV2 {
247
+ return new MockLanguageModelV2({
257
248
  provider: "mock",
258
249
  modelId: "mock",
259
- defaultObjectGenerationMode: "json",
260
250
  // supportsStructuredOutputs: true,
261
251
  doGenerate: async ({ prompt }) => ({
262
252
  finishReason: "stop",
263
- usage: { completionTokens: 10, promptTokens: 3 },
264
- logprobs: undefined,
253
+ content: [{ type: "text", text: JSON.stringify({ prompt }) }],
254
+ warnings: [],
255
+ usage: { outputTokens: 10, inputTokens: 3, totalTokens: 13 },
265
256
  rawCall: { rawPrompt: null, rawSettings: {} },
266
257
  text: JSON.stringify({ prompt }),
267
258
  }),
@@ -277,10 +268,9 @@ function mockModel(): LanguageModelV1 {
277
268
  {
278
269
  type: "finish",
279
270
  finishReason: "stop",
280
- logprobs: undefined,
281
- usage: { completionTokens: 10, promptTokens: 3 },
271
+ usage: { outputTokens: 10, inputTokens: 3, totalTokens: 13 },
282
272
  },
283
- ] as LanguageModelV1StreamPart[],
273
+ ] as LanguageModelV2StreamPart[],
284
274
  }),
285
275
  rawCall: { rawPrompt: null, rawSettings: {} },
286
276
  }),
@@ -289,19 +279,19 @@ function mockModel(): LanguageModelV1 {
289
279
 
290
280
  describe("Agent option variations and normal behavior", () => {
291
281
  test("Agent can be constructed with minimal options", () => {
292
- const a = new Agent(components.agent, { chat: mockModel() });
282
+ const a = new Agent(components.agent, { languageModel: mockModel() });
293
283
  expect(a).toBeInstanceOf(Agent);
294
284
  });
295
285
 
296
286
  test("Agent can be constructed with all options", () => {
297
287
  const a = new Agent(components.agent, {
298
288
  name: "full",
299
- chat: mockModel(),
289
+ languageModel: mockModel(),
300
290
  instructions: "Test instructions",
301
291
  contextOptions: { recentMessages: 5 },
302
292
  storageOptions: { saveMessages: "all" },
303
- maxSteps: 2,
304
- maxRetries: 1,
293
+ stopWhen: stepCountIs(2),
294
+ callSettings: { maxRetries: 1 },
305
295
  usageHandler: async () => {},
306
296
  rawRequestResponseHandler: async () => {},
307
297
  });
@@ -346,7 +336,7 @@ describe("Agent message operations", () => {
346
336
  );
347
337
  expect(messageId).toBeTypeOf("string");
348
338
 
349
- const { lastMessageId, messages } = await t.run(async (ctx) =>
339
+ const { messages } = await t.run(async (ctx) =>
350
340
  agent.saveMessages(ctx, {
351
341
  threadId,
352
342
  userId: "4",
@@ -357,7 +347,7 @@ describe("Agent message operations", () => {
357
347
  }),
358
348
  );
359
349
  expect(messages.length).toBe(2);
360
- expect(lastMessageId).toBe(messages[1]._id);
350
+ expect(messages[1]._id).toBeDefined();
361
351
  });
362
352
  });
363
353
 
@@ -433,8 +423,8 @@ describe("Agent-generated mutations/actions/queries", () => {
433
423
  },
434
424
  ],
435
425
  });
436
- expect(result.lastMessageId).toBeDefined();
437
- expect(result.messageIds.length).toBe(1);
426
+ expect(result.messages.length).toBe(1);
427
+ expect(result.messages[0]._id).toBeDefined();
438
428
  });
439
429
  });
440
430