@convex-dev/agent 0.1.18-alpha.0 → 0.5.0-alpha.1

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 (71) hide show
  1. package/dist/client/createTool.d.ts +30 -9
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -9
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +176 -201
  6. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  7. package/dist/client/definePlaygroundAPI.js +12 -19
  8. package/dist/client/definePlaygroundAPI.js.map +1 -1
  9. package/dist/client/files.js +4 -4
  10. package/dist/client/files.js.map +1 -1
  11. package/dist/client/index.d.ts +166 -63
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +75 -45
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/search.d.ts +27 -6
  16. package/dist/client/search.d.ts.map +1 -1
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/streaming.d.ts +3 -2
  19. package/dist/client/streaming.d.ts.map +1 -1
  20. package/dist/client/streaming.js.map +1 -1
  21. package/dist/client/types.d.ts +27 -41
  22. package/dist/client/types.d.ts.map +1 -1
  23. package/dist/component/_generated/api.d.ts +436 -73
  24. package/dist/component/messages.d.ts +246 -43
  25. package/dist/component/messages.d.ts.map +1 -1
  26. package/dist/component/messages.js +2 -2
  27. package/dist/component/messages.js.map +1 -1
  28. package/dist/component/schema.d.ts +1453 -152
  29. package/dist/component/schema.d.ts.map +1 -1
  30. package/dist/component/schema.js +2 -2
  31. package/dist/component/schema.js.map +1 -1
  32. package/dist/component/streams.d.ts +180 -6
  33. package/dist/component/streams.d.ts.map +1 -1
  34. package/dist/mapping.d.ts +12 -14
  35. package/dist/mapping.d.ts.map +1 -1
  36. package/dist/mapping.js +187 -47
  37. package/dist/mapping.js.map +1 -1
  38. package/dist/react/deltas.d.ts +0 -3
  39. package/dist/react/deltas.d.ts.map +1 -1
  40. package/dist/react/deltas.js +140 -44
  41. package/dist/react/deltas.js.map +1 -1
  42. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  43. package/dist/react/optimisticallySendMessage.js +2 -1
  44. package/dist/react/optimisticallySendMessage.js.map +1 -1
  45. package/dist/react/toUIMessages.d.ts +5 -4
  46. package/dist/react/toUIMessages.d.ts.map +1 -1
  47. package/dist/react/toUIMessages.js +103 -40
  48. package/dist/react/toUIMessages.js.map +1 -1
  49. package/dist/validators.d.ts +1798 -259
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +79 -15
  52. package/dist/validators.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/client/createTool.ts +68 -37
  55. package/src/client/definePlaygroundAPI.ts +25 -27
  56. package/src/client/files.ts +4 -4
  57. package/src/client/index.test.ts +14 -12
  58. package/src/client/index.ts +134 -94
  59. package/src/client/search.ts +3 -2
  60. package/src/client/streaming.ts +4 -3
  61. package/src/client/types.ts +34 -70
  62. package/src/component/_generated/api.d.ts +436 -73
  63. package/src/component/messages.ts +2 -2
  64. package/src/component/schema.ts +2 -2
  65. package/src/mapping.ts +228 -75
  66. package/src/react/deltas.ts +165 -52
  67. package/src/react/optimisticallySendMessage.ts +4 -1
  68. package/src/react/toUIMessages.test.ts +154 -36
  69. package/src/react/toUIMessages.ts +136 -57
  70. package/src/validators.test.ts +2 -99
  71. package/src/validators.ts +95 -17
@@ -115,8 +115,8 @@ export const deleteByOrder = mutation({
115
115
  await Promise.all(messages.map((m) => deleteMessage(ctx, m)));
116
116
  return {
117
117
  isDone: messages.length < 64,
118
- lastOrder: messages[messages.length - 1]?.order,
119
- lastStepOrder: messages[messages.length - 1]?.stepOrder,
118
+ lastOrder: messages.at(-1)?.order,
119
+ lastStepOrder: messages.at(-1)?.stepOrder,
120
120
  };
121
121
  },
122
122
  });
@@ -6,7 +6,7 @@ import {
6
6
  vMessageStatus,
7
7
  vUsage,
8
8
  vSource,
9
- vLanguageModelV1CallWarning,
9
+ vLanguageModelCallWarning,
10
10
  vFinishReason,
11
11
  vProviderOptions,
12
12
  vProviderMetadata,
@@ -62,7 +62,7 @@ export const schema = defineSchema({
62
62
  sources: v.optional(v.array(vSource)),
63
63
  reasoning: v.optional(v.string()),
64
64
  reasoningDetails: v.optional(vReasoningDetails),
65
- warnings: v.optional(v.array(vLanguageModelV1CallWarning)),
65
+ warnings: v.optional(v.array(vLanguageModelCallWarning)),
66
66
  finishReason: v.optional(vFinishReason),
67
67
  // DEPRECATED
68
68
  parentMessageId: v.optional(v.id("messages")),
package/src/mapping.ts CHANGED
@@ -1,10 +1,7 @@
1
- import type { FileUIPart } from "@ai-sdk/ui-utils";
2
1
  import {
3
- convertToCoreMessages,
4
- coreMessageSchema,
5
- type Message as AIMessage,
2
+ type UIMessage as AIMessage,
6
3
  type AssistantContent,
7
- type CoreMessage,
4
+ type ModelMessage,
8
5
  type DataContent,
9
6
  type FilePart,
10
7
  type GenerateObjectResult,
@@ -13,12 +10,32 @@ import {
13
10
  type ToolContent,
14
11
  type ToolSet,
15
12
  type UserContent,
13
+ type FileUIPart,
14
+ type LanguageModelUsage,
15
+ type CallWarning,
16
+ type TextPart,
17
+ type ToolCallPart,
18
+ type ToolResultPart,
19
+ type ReasoningUIPart,
16
20
  } from "ai";
17
- import { assert } from "convex-helpers";
18
- import type { MessageWithMetadata } from "./validators.js";
21
+ import type {
22
+ Message,
23
+ MessageWithMetadata,
24
+ Usage,
25
+ vContent,
26
+ vFilePart,
27
+ vImagePart,
28
+ vReasoningPart,
29
+ vRedactedReasoningPart,
30
+ vTextPart,
31
+ vToolCallPart,
32
+ vToolResultPart,
33
+ } from "./validators.js";
19
34
  import type { ActionCtx, AgentComponent } from "./client/types.js";
20
35
  import type { RunMutationCtx } from "./client/types.js";
21
36
  import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
37
+ import type { Infer } from "convex/values";
38
+ import { omit } from "convex-helpers";
22
39
 
23
40
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
24
41
 
@@ -34,16 +51,17 @@ export type SerializeUrlsAndUint8Arrays<T> = T extends URL
34
51
  : T;
35
52
 
36
53
  export type Content = UserContent | AssistantContent | ToolContent;
37
- export type SerializedContent = SerializeUrlsAndUint8Arrays<Content>;
54
+ export type SerializedContent = Message["content"];
38
55
 
39
- export type SerializedMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
56
+ export type SerializedMessage = Message;
40
57
 
41
58
  export async function serializeMessage(
42
59
  ctx: ActionCtx | RunMutationCtx,
43
60
  component: AgentComponent,
44
- messageWithId: CoreMessage & { id?: string },
61
+ messageWithId: (ModelMessage & { id?: string }) | Message,
45
62
  ): Promise<{ message: SerializedMessage; fileIds?: string[] }> {
46
- const { id: _, experimental_providerMetadata, ...message } = messageWithId;
63
+ const message =
64
+ "id" in messageWithId ? omit(messageWithId, ["id"]) : messageWithId;
47
65
  const { content, fileIds } = await serializeContent(
48
66
  ctx,
49
67
  component,
@@ -51,8 +69,6 @@ export async function serializeMessage(
51
69
  );
52
70
  return {
53
71
  message: {
54
- // for backwards compatibility
55
- providerOptions: experimental_providerMetadata,
56
72
  ...message,
57
73
  content,
58
74
  } as SerializedMessage,
@@ -60,11 +76,54 @@ export async function serializeMessage(
60
76
  };
61
77
  }
62
78
 
63
- export function deserializeMessage(message: SerializedMessage): CoreMessage {
79
+ export function deserializeMessage(message: SerializedMessage): ModelMessage {
64
80
  return {
65
81
  ...message,
66
82
  content: deserializeContent(message.content),
67
- } as CoreMessage;
83
+ } as ModelMessage;
84
+ }
85
+
86
+ export function serializeUsage(usage: LanguageModelUsage): Usage {
87
+ return {
88
+ promptTokens: usage.inputTokens ?? 0,
89
+ completionTokens: usage.outputTokens ?? 0,
90
+ totalTokens: usage.totalTokens ?? 0,
91
+ reasoningTokens: usage.reasoningTokens,
92
+ };
93
+ }
94
+
95
+ export function deserializeUsage(usage: Usage): LanguageModelUsage {
96
+ return {
97
+ inputTokens: usage.promptTokens,
98
+ outputTokens: usage.completionTokens,
99
+ totalTokens: usage.totalTokens,
100
+ reasoningTokens: usage.reasoningTokens,
101
+ cachedInputTokens: usage.cachedInputTokens,
102
+ };
103
+ }
104
+
105
+ export function serializeWarnings(
106
+ warnings: CallWarning[] | undefined,
107
+ ): MessageWithMetadata["warnings"] {
108
+ if (!warnings) {
109
+ return undefined;
110
+ }
111
+ return warnings.map((warning) => {
112
+ if (warning.type !== "unsupported-setting") {
113
+ return warning;
114
+ }
115
+ return {
116
+ ...warning,
117
+ setting: warning.setting.toString(),
118
+ };
119
+ });
120
+ }
121
+
122
+ export function deserializeWarnings(
123
+ warnings: MessageWithMetadata["warnings"],
124
+ ): CallWarning[] | undefined {
125
+ // We don't need to do anythign here for now
126
+ return warnings;
68
127
  }
69
128
 
70
129
  export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
@@ -79,13 +138,14 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
79
138
  model: metadata.model,
80
139
  provider: metadata.provider,
81
140
  providerMetadata: step.providerMetadata,
82
- reasoning: step.reasoning,
83
- reasoningDetails: step.reasoningDetails,
84
- usage: step.usage,
85
- warnings: step.warnings,
141
+ reasoning: step.reasoningText,
142
+ reasoningDetails: step.reasoning,
143
+ usage: serializeUsage(step.usage),
144
+ warnings: serializeWarnings(step.warnings),
86
145
  finishReason: step.finishReason,
87
- sources: step.stepType === "tool-result" ? undefined : step.sources,
88
- };
146
+ // Only store the sources on one message
147
+ sources: step.toolResults.length === 0 ? step.sources : undefined,
148
+ } satisfies Omit<MessageWithMetadata, "message" | "text" | "fileIds">;
89
149
  const toolFields = {
90
150
  sources: step.sources,
91
151
  };
@@ -135,8 +195,8 @@ export async function serializeObjectResult(
135
195
  providerMetadata: result.providerMetadata,
136
196
  finishReason: result.finishReason,
137
197
  text,
138
- usage: result.usage,
139
- warnings: result.warnings,
198
+ usage: serializeUsage(result.usage),
199
+ warnings: serializeWarnings(result.warnings),
140
200
  fileIds,
141
201
  },
142
202
  ],
@@ -146,16 +206,31 @@ export async function serializeObjectResult(
146
206
  export async function serializeContent(
147
207
  ctx: ActionCtx | RunMutationCtx,
148
208
  component: AgentComponent,
149
- content: Content,
209
+ content: Content | Message["content"],
150
210
  ): Promise<{ content: SerializedContent; fileIds?: string[] }> {
151
211
  if (typeof content === "string") {
152
212
  return { content };
153
213
  }
154
214
  const fileIds: string[] = [];
215
+ function getMimeType(part: { mediaType?: string; mimeType?: string }) {
216
+ if ("mediaType" in part) {
217
+ return part.mediaType;
218
+ }
219
+ if ("mimeType" in part) {
220
+ return part.mimeType;
221
+ }
222
+ return undefined;
223
+ }
155
224
  const serialized = await Promise.all(
156
- content.map(async ({ experimental_providerMetadata, ...rest }) => {
157
- const part = { providerOptions: experimental_providerMetadata, ...rest };
225
+ content.map(async (part) => {
158
226
  switch (part.type) {
227
+ case "text": {
228
+ return {
229
+ type: part.type,
230
+ text: part.text,
231
+ providerOptions: part.providerOptions,
232
+ } satisfies Infer<typeof vTextPart>;
233
+ }
159
234
  case "image": {
160
235
  let image = serializeDataOrUrl(part.image);
161
236
  if (
@@ -166,13 +241,18 @@ export async function serializeContent(
166
241
  ctx,
167
242
  component,
168
243
  new Blob([image], {
169
- type: part.mimeType || guessMimeType(image),
244
+ type: getMimeType(part) || guessMimeType(image),
170
245
  }),
171
246
  );
172
247
  image = file.url;
173
248
  fileIds.push(file.fileId);
174
249
  }
175
- return { ...part, image };
250
+ return {
251
+ type: part.type,
252
+ mimeType: getMimeType(part),
253
+ providerOptions: part.providerOptions,
254
+ image,
255
+ } satisfies Infer<typeof vImagePart>;
176
256
  }
177
257
  case "file": {
178
258
  let data = serializeDataOrUrl(part.data);
@@ -180,18 +260,56 @@ export async function serializeContent(
180
260
  const { file } = await storeFile(
181
261
  ctx,
182
262
  component,
183
- new Blob([data], { type: part.mimeType }),
263
+ new Blob([data], { type: getMimeType(part) }),
184
264
  );
185
265
  data = file.url;
186
266
  fileIds.push(file.fileId);
187
267
  }
188
- return { ...part, data };
268
+ return {
269
+ type: part.type,
270
+ data,
271
+ filename: part.filename,
272
+ mimeType: getMimeType(part)!,
273
+ providerOptions: part.providerOptions,
274
+ } satisfies Infer<typeof vFilePart>;
275
+ }
276
+ case "tool-call": {
277
+ const args = "input" in part ? part.input : part.args;
278
+ return {
279
+ type: part.type,
280
+ args: args ?? null,
281
+ toolCallId: part.toolCallId,
282
+ toolName: part.toolName,
283
+ providerOptions: part.providerOptions,
284
+ providerExecuted: part.providerExecuted,
285
+ } satisfies Infer<typeof vToolCallPart>;
189
286
  }
190
287
  case "tool-result": {
191
- return { ...part, result: part.result ?? null };
288
+ const result = "output" in part ? part.output : part.result;
289
+ return {
290
+ type: part.type,
291
+ result: result ?? null,
292
+ toolCallId: part.toolCallId,
293
+ toolName: part.toolName,
294
+ providerOptions: part.providerOptions,
295
+ } satisfies Infer<typeof vToolResultPart>;
296
+ }
297
+ case "reasoning": {
298
+ return {
299
+ type: part.type,
300
+ text: part.text,
301
+ providerOptions: part.providerOptions,
302
+ } satisfies Infer<typeof vReasoningPart>;
303
+ }
304
+ case "redacted-reasoning": {
305
+ return {
306
+ type: part.type,
307
+ data: part.data,
308
+ providerOptions: part.providerOptions,
309
+ } satisfies Infer<typeof vRedactedReasoningPart>;
192
310
  }
193
311
  default:
194
- return part;
312
+ return part satisfies Infer<typeof vContent>;
195
313
  }
196
314
  }),
197
315
  );
@@ -207,12 +325,60 @@ export function deserializeContent(content: SerializedContent): Content {
207
325
  }
208
326
  return content.map((part) => {
209
327
  switch (part.type) {
328
+ case "text":
329
+ return {
330
+ type: part.type,
331
+ text: part.text,
332
+ providerOptions: part.providerOptions,
333
+ } satisfies TextPart;
210
334
  case "image":
211
- return { ...part, image: deserializeUrl(part.image) };
335
+ return {
336
+ type: part.type,
337
+ image: deserializeUrl(part.image),
338
+ mediaType: part.mimeType,
339
+ providerOptions: part.providerOptions,
340
+ } satisfies ImagePart;
212
341
  case "file":
213
- return { ...part, data: deserializeUrl(part.data) };
342
+ return {
343
+ type: part.type,
344
+ data: deserializeUrl(part.data),
345
+ filename: part.filename,
346
+ mediaType: part.mimeType,
347
+ providerOptions: part.providerOptions,
348
+ } satisfies FilePart;
349
+ case "tool-call":
350
+ return {
351
+ type: part.type,
352
+ input: part.args ?? null,
353
+ providerExecuted: part.providerExecuted,
354
+ toolCallId: part.toolCallId,
355
+ toolName: part.toolName,
356
+ providerOptions: part.providerOptions,
357
+ } satisfies ToolCallPart;
358
+ case "tool-result":
359
+ return {
360
+ type: part.type,
361
+ output: part.result ?? null,
362
+ toolCallId: part.toolCallId,
363
+ toolName: part.toolName,
364
+ providerOptions: part.providerOptions,
365
+ } satisfies ToolResultPart;
366
+ case "reasoning":
367
+ return {
368
+ type: part.type,
369
+ text: part.text,
370
+ providerMetadata: part.providerOptions,
371
+ state: part.state,
372
+ } satisfies ReasoningUIPart;
373
+ case "redacted-reasoning":
374
+ // TODO: should we just drop this?
375
+ return {
376
+ type: "reasoning",
377
+ text: part.data,
378
+ providerMetadata: part.providerOptions,
379
+ } satisfies ReasoningUIPart;
214
380
  default:
215
- return part;
381
+ return part satisfies Content;
216
382
  }
217
383
  }) as Content;
218
384
  }
@@ -323,15 +489,18 @@ export function deserializeUrl(
323
489
  }
324
490
 
325
491
  export function toUIFilePart(part: ImagePart | FilePart): FileUIPart {
326
- const dataOrUrl = serializeDataOrUrl(
327
- part.type === "image" ? part.image : part.data,
328
- );
492
+ const dataOrUrl = part.type === "image" ? part.image : part.data;
493
+ const url =
494
+ dataOrUrl instanceof ArrayBuffer
495
+ ? encodeBase64(dataOrUrl)
496
+ : dataOrUrl.toString();
329
497
 
330
498
  return {
331
499
  type: "file",
332
- data:
333
- dataOrUrl instanceof ArrayBuffer ? encodeBase64(dataOrUrl) : dataOrUrl,
334
- mimeType: part.mimeType ?? guessMimeType(dataOrUrl),
500
+ mediaType: part.mediaType!,
501
+ filename: part.type === "file" ? part.filename : undefined,
502
+ url,
503
+ providerMetadata: part.providerOptions,
335
504
  };
336
505
  }
337
506
 
@@ -339,37 +508,21 @@ function encodeBase64(data: ArrayBuffer): string {
339
508
  return Buffer.from(data).toString("base64");
340
509
  }
341
510
 
342
- export function promptOrMessagesToCoreMessages(args: {
343
- prompt?: string;
344
- messages?: CoreMessage[] | AIMessageWithoutId[];
345
- promptMessageId?: string;
346
- }): CoreMessage[] {
347
- const messages: CoreMessage[] = [];
348
- assert(
349
- args.prompt || args.messages || args.promptMessageId,
350
- "messages or prompt or promptMessageId is required",
351
- );
352
- if (args.messages) {
353
- if (
354
- args.messages.some(
355
- (m) =>
356
- typeof m === "object" &&
357
- m !== null &&
358
- (m.role === "data" || // UI-only role
359
- "toolInvocations" in m || // UI-specific field
360
- "parts" in m || // UI-specific field
361
- "experimental_attachments" in m),
362
- )
363
- ) {
364
- messages.push(...convertToCoreMessages(args.messages as AIMessage[]));
365
- } else {
366
- messages.push(...coreMessageSchema.array().parse(args.messages));
367
- }
368
- }
369
- // If they specify both a promptMessageId and a prompt, we will replace the
370
- // promptMessageId message with the prompt later.
371
- if (args.prompt && !args.promptMessageId) {
372
- messages.push({ role: "user", content: args.prompt });
373
- }
374
- return messages;
375
- }
511
+ // Currently unused
512
+ // export function toModelMessages(args: {
513
+ // messages?: ModelMessage[] | AIMessageWithoutId[];
514
+ // }): ModelMessage[] {
515
+ // const messages: ModelMessage[] = [];
516
+ // if (args.messages) {
517
+ // if (
518
+ // args.messages.every(
519
+ // (m) => typeof m === "object" && m !== null && "parts" in m,
520
+ // )
521
+ // ) {
522
+ // messages.push(...convertToModelMessages(args.messages));
523
+ // } else {
524
+ // messages.push(...modelMessageSchema.array().parse(args.messages));
525
+ // }
526
+ // }
527
+ // return messages;
528
+ // }