@convex-dev/agent 0.1.18-alpha.1 → 0.2.0-alpha.2

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 (96) hide show
  1. package/dist/client/createTool.d.ts +31 -10
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +25 -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 +174 -199
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +17 -35
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +6 -3
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +7 -7
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +152 -589
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +138 -222
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +538 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +91 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +30 -7
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +20 -3
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +3 -2
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js.map +1 -1
  32. package/dist/client/threads.d.ts +46 -0
  33. package/dist/client/threads.d.ts.map +1 -0
  34. package/dist/client/threads.js +49 -0
  35. package/dist/client/threads.js.map +1 -0
  36. package/dist/client/types.d.ts +39 -42
  37. package/dist/client/types.d.ts.map +1 -1
  38. package/dist/component/_generated/api.d.ts +437 -75
  39. package/dist/component/messages.d.ts +254 -55
  40. package/dist/component/messages.d.ts.map +1 -1
  41. package/dist/component/messages.js +29 -25
  42. package/dist/component/messages.js.map +1 -1
  43. package/dist/component/schema.d.ts +1459 -158
  44. package/dist/component/schema.d.ts.map +1 -1
  45. package/dist/component/schema.js +8 -14
  46. package/dist/component/schema.js.map +1 -1
  47. package/dist/component/streams.d.ts +180 -6
  48. package/dist/component/streams.d.ts.map +1 -1
  49. package/dist/mapping.d.ts +11 -15
  50. package/dist/mapping.d.ts.map +1 -1
  51. package/dist/mapping.js +191 -61
  52. package/dist/mapping.js.map +1 -1
  53. package/dist/react/deltas.d.ts +0 -3
  54. package/dist/react/deltas.d.ts.map +1 -1
  55. package/dist/react/deltas.js +140 -44
  56. package/dist/react/deltas.js.map +1 -1
  57. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  58. package/dist/react/optimisticallySendMessage.js +2 -1
  59. package/dist/react/optimisticallySendMessage.js.map +1 -1
  60. package/dist/react/toUIMessages.d.ts +5 -4
  61. package/dist/react/toUIMessages.d.ts.map +1 -1
  62. package/dist/react/toUIMessages.js +103 -40
  63. package/dist/react/toUIMessages.js.map +1 -1
  64. package/dist/validators.d.ts +1978 -1210
  65. package/dist/validators.d.ts.map +1 -1
  66. package/dist/validators.js +90 -54
  67. package/dist/validators.js.map +1 -1
  68. package/package.json +32 -28
  69. package/src/client/createTool.ts +69 -38
  70. package/src/client/defaultComponent.ts +17 -0
  71. package/src/client/definePlaygroundAPI.ts +29 -43
  72. package/src/client/files.ts +7 -8
  73. package/src/client/index.test.ts +20 -18
  74. package/src/client/index.ts +228 -434
  75. package/src/client/messages.ts +191 -0
  76. package/src/client/search.ts +30 -6
  77. package/src/client/streaming.ts +4 -3
  78. package/src/client/threads.ts +78 -0
  79. package/src/client/types.ts +50 -72
  80. package/src/component/_generated/api.d.ts +437 -75
  81. package/src/component/messages.test.ts +182 -40
  82. package/src/component/messages.ts +30 -32
  83. package/src/component/schema.ts +8 -14
  84. package/src/mapping.ts +230 -94
  85. package/src/react/deltas.ts +165 -52
  86. package/src/react/optimisticallySendMessage.ts +4 -1
  87. package/src/react/toUIMessages.test.ts +154 -36
  88. package/src/react/toUIMessages.ts +136 -57
  89. package/src/validators.test.ts +2 -101
  90. package/src/validators.ts +111 -68
  91. package/dist/client/listMessages.d.ts +0 -22
  92. package/dist/client/listMessages.d.ts.map +0 -1
  93. package/dist/client/listMessages.js +0 -25
  94. package/dist/client/listMessages.js.map +0 -1
  95. package/src/client/listMessages.ts +0 -38
  96. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
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,13 +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";
22
-
37
+ import type { Infer } from "convex/values";
38
+ import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
23
39
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
24
40
 
25
41
  export type SerializeUrlsAndUint8Arrays<T> = T extends URL
@@ -34,16 +50,15 @@ export type SerializeUrlsAndUint8Arrays<T> = T extends URL
34
50
  : T;
35
51
 
36
52
  export type Content = UserContent | AssistantContent | ToolContent;
37
- export type SerializedContent = SerializeUrlsAndUint8Arrays<Content>;
53
+ export type SerializedContent = Message["content"];
38
54
 
39
- export type SerializedMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
55
+ export type SerializedMessage = Message;
40
56
 
41
57
  export async function serializeMessage(
42
58
  ctx: ActionCtx | RunMutationCtx,
43
59
  component: AgentComponent,
44
- messageWithId: CoreMessage & { id?: string },
60
+ message: ModelMessage | Message,
45
61
  ): Promise<{ message: SerializedMessage; fileIds?: string[] }> {
46
- const { id: _, experimental_providerMetadata, ...message } = messageWithId;
47
62
  const { content, fileIds } = await serializeContent(
48
63
  ctx,
49
64
  component,
@@ -51,20 +66,61 @@ export async function serializeMessage(
51
66
  );
52
67
  return {
53
68
  message: {
54
- // for backwards compatibility
55
- providerOptions: experimental_providerMetadata,
56
- ...message,
69
+ role: message.role,
57
70
  content,
71
+ ...(message.providerOptions
72
+ ? { providerOptions: message.providerOptions }
73
+ : {}),
58
74
  } as SerializedMessage,
59
75
  fileIds,
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 { ...warning, setting: warning.setting.toString() };
116
+ });
117
+ }
118
+
119
+ export function deserializeWarnings(
120
+ warnings: MessageWithMetadata["warnings"],
121
+ ): CallWarning[] | undefined {
122
+ // We don't need to do anythign here for now
123
+ return warnings;
68
124
  }
69
125
 
70
126
  export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
@@ -79,31 +135,23 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
79
135
  model: metadata.model,
80
136
  provider: metadata.provider,
81
137
  providerMetadata: step.providerMetadata,
82
- reasoning: step.reasoning,
83
- reasoningDetails: step.reasoningDetails,
84
- usage: step.usage,
85
- warnings: step.warnings,
138
+ reasoning: step.reasoningText,
139
+ reasoningDetails: step.reasoning,
140
+ usage: serializeUsage(step.usage),
141
+ warnings: serializeWarnings(step.warnings),
86
142
  finishReason: step.finishReason,
87
- sources: step.stepType === "tool-result" ? undefined : step.sources,
88
- };
89
- const toolFields = {
90
- sources: step.sources,
91
- };
143
+ // Only store the sources on one message
144
+ sources: step.toolResults.length === 0 ? step.sources : undefined,
145
+ } satisfies Omit<MessageWithMetadata, "message" | "text" | "fileIds">;
146
+ const toolFields = { sources: step.sources };
92
147
  const messages: MessageWithMetadata[] = await Promise.all(
93
148
  (step.toolResults.length > 0
94
149
  ? step.response.messages.slice(-2)
95
150
  : step.response.messages.slice(-1)
96
- ).map(async (messageWithId): Promise<MessageWithMetadata> => {
97
- const { message, fileIds } = await serializeMessage(
98
- ctx,
99
- component,
100
- messageWithId,
101
- );
151
+ ).map(async (msg): Promise<MessageWithMetadata> => {
152
+ const { message, fileIds } = await serializeMessage(ctx, component, msg);
102
153
  return {
103
154
  message,
104
- // Let's not store the ID by default here. It's being generated internally
105
- // and not referenced elsewhere that we know of.
106
- // id: message.id,
107
155
  ...(message.role === "tool" ? toolFields : assistantFields),
108
156
  text: step.text,
109
157
  fileIds,
@@ -129,14 +177,13 @@ export async function serializeObjectResult(
129
177
  messages: [
130
178
  {
131
179
  message,
132
- id: result.response.id,
133
180
  model: metadata.model,
134
181
  provider: metadata.provider,
135
182
  providerMetadata: result.providerMetadata,
136
183
  finishReason: result.finishReason,
137
184
  text,
138
- usage: result.usage,
139
- warnings: result.warnings,
185
+ usage: serializeUsage(result.usage),
186
+ warnings: serializeWarnings(result.warnings),
140
187
  fileIds,
141
188
  },
142
189
  ],
@@ -146,16 +193,31 @@ export async function serializeObjectResult(
146
193
  export async function serializeContent(
147
194
  ctx: ActionCtx | RunMutationCtx,
148
195
  component: AgentComponent,
149
- content: Content,
196
+ content: Content | Message["content"],
150
197
  ): Promise<{ content: SerializedContent; fileIds?: string[] }> {
151
198
  if (typeof content === "string") {
152
199
  return { content };
153
200
  }
154
201
  const fileIds: string[] = [];
202
+ function getMimeType(part: { mediaType?: string; mimeType?: string }) {
203
+ if ("mediaType" in part) {
204
+ return part.mediaType;
205
+ }
206
+ if ("mimeType" in part) {
207
+ return part.mimeType;
208
+ }
209
+ return undefined;
210
+ }
155
211
  const serialized = await Promise.all(
156
- content.map(async ({ experimental_providerMetadata, ...rest }) => {
157
- const part = { providerOptions: experimental_providerMetadata, ...rest };
212
+ content.map(async (part) => {
158
213
  switch (part.type) {
214
+ case "text": {
215
+ return {
216
+ type: part.type,
217
+ text: part.text,
218
+ providerOptions: part.providerOptions,
219
+ } satisfies Infer<typeof vTextPart>;
220
+ }
159
221
  case "image": {
160
222
  let image = serializeDataOrUrl(part.image);
161
223
  if (
@@ -166,13 +228,18 @@ export async function serializeContent(
166
228
  ctx,
167
229
  component,
168
230
  new Blob([image], {
169
- type: part.mimeType || guessMimeType(image),
231
+ type: getMimeType(part) || guessMimeType(image),
170
232
  }),
171
233
  );
172
234
  image = file.url;
173
235
  fileIds.push(file.fileId);
174
236
  }
175
- return { ...part, image };
237
+ return {
238
+ type: part.type,
239
+ mimeType: getMimeType(part),
240
+ providerOptions: part.providerOptions,
241
+ image,
242
+ } satisfies Infer<typeof vImagePart>;
176
243
  }
177
244
  case "file": {
178
245
  let data = serializeDataOrUrl(part.data);
@@ -180,18 +247,56 @@ export async function serializeContent(
180
247
  const { file } = await storeFile(
181
248
  ctx,
182
249
  component,
183
- new Blob([data], { type: part.mimeType }),
250
+ new Blob([data], { type: getMimeType(part) }),
184
251
  );
185
252
  data = file.url;
186
253
  fileIds.push(file.fileId);
187
254
  }
188
- return { ...part, data };
255
+ return {
256
+ type: part.type,
257
+ data,
258
+ filename: part.filename,
259
+ mimeType: getMimeType(part)!,
260
+ providerOptions: part.providerOptions,
261
+ } satisfies Infer<typeof vFilePart>;
262
+ }
263
+ case "tool-call": {
264
+ const args = "input" in part ? part.input : part.args;
265
+ return {
266
+ type: part.type,
267
+ args: args ?? null,
268
+ toolCallId: part.toolCallId,
269
+ toolName: part.toolName,
270
+ providerOptions: part.providerOptions,
271
+ providerExecuted: part.providerExecuted,
272
+ } satisfies Infer<typeof vToolCallPart>;
189
273
  }
190
274
  case "tool-result": {
191
- return { ...part, result: part.result ?? null };
275
+ const result = "output" in part ? part.output : part.result;
276
+ return {
277
+ type: part.type,
278
+ result: result ?? null,
279
+ toolCallId: part.toolCallId,
280
+ toolName: part.toolName,
281
+ providerOptions: part.providerOptions,
282
+ } satisfies Infer<typeof vToolResultPart>;
283
+ }
284
+ case "reasoning": {
285
+ return {
286
+ type: part.type,
287
+ text: part.text,
288
+ providerOptions: part.providerOptions,
289
+ } satisfies Infer<typeof vReasoningPart>;
290
+ }
291
+ case "redacted-reasoning": {
292
+ return {
293
+ type: part.type,
294
+ data: part.data,
295
+ providerOptions: part.providerOptions,
296
+ } satisfies Infer<typeof vRedactedReasoningPart>;
192
297
  }
193
298
  default:
194
- return part;
299
+ return part satisfies Infer<typeof vContent>;
195
300
  }
196
301
  }),
197
302
  );
@@ -207,12 +312,60 @@ export function deserializeContent(content: SerializedContent): Content {
207
312
  }
208
313
  return content.map((part) => {
209
314
  switch (part.type) {
315
+ case "text":
316
+ return {
317
+ type: part.type,
318
+ text: part.text,
319
+ providerOptions: part.providerOptions,
320
+ } satisfies TextPart;
210
321
  case "image":
211
- return { ...part, image: deserializeUrl(part.image) };
322
+ return {
323
+ type: part.type,
324
+ image: deserializeUrl(part.image),
325
+ mediaType: part.mimeType,
326
+ providerOptions: part.providerOptions,
327
+ } satisfies ImagePart;
212
328
  case "file":
213
- return { ...part, data: deserializeUrl(part.data) };
329
+ return {
330
+ type: part.type,
331
+ data: deserializeUrl(part.data),
332
+ filename: part.filename,
333
+ mediaType: part.mimeType,
334
+ providerOptions: part.providerOptions,
335
+ } satisfies FilePart;
336
+ case "tool-call":
337
+ return {
338
+ type: part.type,
339
+ input: part.args ?? null,
340
+ providerExecuted: part.providerExecuted,
341
+ toolCallId: part.toolCallId,
342
+ toolName: part.toolName,
343
+ providerOptions: part.providerOptions,
344
+ } satisfies ToolCallPart;
345
+ case "tool-result":
346
+ return {
347
+ type: part.type,
348
+ output: part.result ?? null,
349
+ toolCallId: part.toolCallId,
350
+ toolName: part.toolName,
351
+ providerOptions: part.providerOptions,
352
+ } satisfies ToolResultPart;
353
+ case "reasoning":
354
+ return {
355
+ type: part.type,
356
+ text: part.text,
357
+ providerMetadata: part.providerOptions,
358
+ state: part.state,
359
+ } satisfies ReasoningUIPart;
360
+ case "redacted-reasoning":
361
+ // TODO: should we just drop this?
362
+ return {
363
+ type: "reasoning",
364
+ text: part.data,
365
+ providerMetadata: part.providerOptions,
366
+ } satisfies ReasoningUIPart;
214
367
  default:
215
- return part;
368
+ return part satisfies Content;
216
369
  }
217
370
  }) as Content;
218
371
  }
@@ -323,53 +476,36 @@ export function deserializeUrl(
323
476
  }
324
477
 
325
478
  export function toUIFilePart(part: ImagePart | FilePart): FileUIPart {
326
- const dataOrUrl = serializeDataOrUrl(
327
- part.type === "image" ? part.image : part.data,
328
- );
479
+ const dataOrUrl = part.type === "image" ? part.image : part.data;
480
+ const url =
481
+ dataOrUrl instanceof ArrayBuffer
482
+ ? convertUint8ArrayToBase64(new Uint8Array(dataOrUrl))
483
+ : dataOrUrl.toString();
329
484
 
330
485
  return {
331
486
  type: "file",
332
- data:
333
- dataOrUrl instanceof ArrayBuffer ? encodeBase64(dataOrUrl) : dataOrUrl,
334
- mimeType: part.mimeType ?? guessMimeType(dataOrUrl),
487
+ mediaType: part.mediaType!,
488
+ filename: part.type === "file" ? part.filename : undefined,
489
+ url,
490
+ providerMetadata: part.providerOptions,
335
491
  };
336
492
  }
337
493
 
338
- function encodeBase64(data: ArrayBuffer): string {
339
- return Buffer.from(data).toString("base64");
340
- }
341
-
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
- }
494
+ // Currently unused
495
+ // export function toModelMessages(args: {
496
+ // messages?: ModelMessage[] | AIMessageWithoutId[];
497
+ // }): ModelMessage[] {
498
+ // const messages: ModelMessage[] = [];
499
+ // if (args.messages) {
500
+ // if (
501
+ // args.messages.every(
502
+ // (m) => typeof m === "object" && m !== null && "parts" in m,
503
+ // )
504
+ // ) {
505
+ // messages.push(...convertToModelMessages(args.messages));
506
+ // } else {
507
+ // messages.push(...modelMessageSchema.array().parse(args.messages));
508
+ // }
509
+ // }
510
+ // return messages;
511
+ // }