@convex-dev/agent 0.2.0-alpha.2 → 0.2.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 (100) hide show
  1. package/dist/client/createTool.d.ts +4 -6
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +0 -1
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +28 -28
  6. package/dist/client/files.d.ts +9 -2
  7. package/dist/client/files.d.ts.map +1 -1
  8. package/dist/client/files.js +61 -6
  9. package/dist/client/files.js.map +1 -1
  10. package/dist/client/index.d.ts +232 -242
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +375 -383
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/messages.d.ts +91 -373
  15. package/dist/client/messages.d.ts.map +1 -1
  16. package/dist/client/messages.js +7 -1
  17. package/dist/client/messages.js.map +1 -1
  18. package/dist/client/search.d.ts +15 -15
  19. package/dist/client/search.d.ts.map +1 -1
  20. package/dist/client/search.js +3 -3
  21. package/dist/client/search.js.map +1 -1
  22. package/dist/client/streaming.d.ts +30 -22
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +36 -40
  25. package/dist/client/streaming.js.map +1 -1
  26. package/dist/client/types.d.ts +148 -61
  27. package/dist/client/types.d.ts.map +1 -1
  28. package/dist/component/_generated/api.d.ts +47 -357
  29. package/dist/component/messages.d.ts +157 -150
  30. package/dist/component/messages.d.ts.map +1 -1
  31. package/dist/component/messages.js +124 -70
  32. package/dist/component/messages.js.map +1 -1
  33. package/dist/component/schema.d.ts +1147 -2687
  34. package/dist/component/schema.d.ts.map +1 -1
  35. package/dist/component/schema.js +5 -4
  36. package/dist/component/schema.js.map +1 -1
  37. package/dist/component/streams.d.ts +29 -323
  38. package/dist/component/streams.d.ts.map +1 -1
  39. package/dist/component/streams.js +104 -60
  40. package/dist/component/streams.js.map +1 -1
  41. package/dist/component/threads.d.ts +4 -4
  42. package/dist/component/users.d.ts +3 -3
  43. package/dist/mapping.d.ts +4 -1
  44. package/dist/mapping.d.ts.map +1 -1
  45. package/dist/mapping.js +17 -1
  46. package/dist/mapping.js.map +1 -1
  47. package/dist/parts.d.ts +3 -0
  48. package/dist/parts.d.ts.map +1 -0
  49. package/dist/parts.js +34 -0
  50. package/dist/parts.js.map +1 -0
  51. package/dist/react/SmoothText.d.ts +5 -0
  52. package/dist/react/SmoothText.d.ts.map +1 -0
  53. package/dist/react/SmoothText.js +6 -0
  54. package/dist/react/SmoothText.js.map +1 -0
  55. package/dist/react/deltas.d.ts +3 -2
  56. package/dist/react/deltas.d.ts.map +1 -1
  57. package/dist/react/deltas.js +19 -79
  58. package/dist/react/deltas.js.map +1 -1
  59. package/dist/react/index.d.ts +1 -0
  60. package/dist/react/index.d.ts.map +1 -1
  61. package/dist/react/index.js +31 -11
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  64. package/dist/react/optimisticallySendMessage.js +2 -8
  65. package/dist/react/optimisticallySendMessage.js.map +1 -1
  66. package/dist/react/useSmoothText.d.ts +13 -12
  67. package/dist/react/useSmoothText.d.ts.map +1 -1
  68. package/dist/react/useSmoothText.js +1 -1
  69. package/dist/react/useSmoothText.js.map +1 -1
  70. package/dist/shared.d.ts +3 -3
  71. package/dist/shared.d.ts.map +1 -1
  72. package/dist/shared.js.map +1 -1
  73. package/dist/validators.d.ts +109 -1590
  74. package/dist/validators.d.ts.map +1 -1
  75. package/dist/validators.js +4 -81
  76. package/dist/validators.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/client/createTool.ts +5 -10
  79. package/src/client/files.ts +89 -11
  80. package/src/client/index.test.ts +25 -35
  81. package/src/client/index.ts +686 -652
  82. package/src/client/messages.ts +16 -2
  83. package/src/client/search.ts +10 -7
  84. package/src/client/streaming.ts +62 -57
  85. package/src/client/types.ts +227 -97
  86. package/src/component/_generated/api.d.ts +47 -357
  87. package/src/component/messages.ts +157 -96
  88. package/src/component/schema.ts +4 -4
  89. package/src/component/streams.ts +180 -69
  90. package/src/mapping.ts +23 -2
  91. package/src/parts.ts +39 -0
  92. package/src/react/SmoothText.tsx +9 -0
  93. package/src/react/deltas.test.ts +38 -42
  94. package/src/react/deltas.ts +24 -86
  95. package/src/react/index.ts +35 -13
  96. package/src/react/optimisticallySendMessage.ts +2 -8
  97. package/src/react/useSmoothText.ts +14 -15
  98. package/src/shared.ts +3 -3
  99. package/src/validators.test.ts +0 -17
  100. package/src/validators.ts +8 -101
@@ -74,6 +74,10 @@ export type SaveMessagesArgs = {
74
74
  * The embeddings to save with the messages.
75
75
  */
76
76
  embeddings?: MessageEmbeddings;
77
+ /**
78
+ * A pending message ID to replace when adding messages.
79
+ */
80
+ pendingMessageId?: string;
77
81
  };
78
82
 
79
83
  /**
@@ -88,7 +92,7 @@ export async function saveMessages(
88
92
  */
89
93
  agentName?: string;
90
94
  },
91
- ) {
95
+ ): Promise<{ messages: MessageDoc[] }> {
92
96
  let embeddings: MessageEmbeddingsWithDimension | undefined;
93
97
  if (args.embeddings) {
94
98
  const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
@@ -106,14 +110,19 @@ export async function saveMessages(
106
110
  userId: args.userId ?? undefined,
107
111
  agentName: args.agentName,
108
112
  promptMessageId: args.promptMessageId,
113
+ pendingMessageId: args.pendingMessageId,
109
114
  embeddings,
110
115
  messages: await Promise.all(
111
116
  args.messages.map(async (m, i) => {
112
117
  const { message, fileIds } = await serializeMessage(ctx, component, m);
118
+ const allFileIds = args.metadata?.[i]?.fileIds ?? [];
119
+ if (fileIds) {
120
+ allFileIds.push(...fileIds);
121
+ }
113
122
  return parse(vMessageWithMetadata, {
114
123
  ...args.metadata?.[i],
115
124
  message,
116
- fileIds,
125
+ fileIds: allFileIds.length > 0 ? allFileIds : undefined,
117
126
  });
118
127
  }),
119
128
  ),
@@ -134,6 +143,10 @@ export type SaveMessageArgs = {
134
143
  * The embedding to save with the message.
135
144
  */
136
145
  embedding?: { vector: number[]; model: string };
146
+ /**
147
+ * A pending message ID to replace with this message.
148
+ */
149
+ pendingMessageId?: string;
137
150
  } & (
138
151
  | {
139
152
  prompt?: undefined;
@@ -179,6 +192,7 @@ export async function saveMessage(
179
192
  threadId: args.threadId,
180
193
  userId: args.userId ?? undefined,
181
194
  agentName: args.agentName,
195
+ pendingMessageId: args.pendingMessageId,
182
196
  messages:
183
197
  args.prompt !== undefined
184
198
  ? [{ role: "user", content: args.prompt }]
@@ -16,9 +16,11 @@ import type { Message } from "../validators.js";
16
16
 
17
17
  const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
18
18
 
19
- export type GetEmbedding = (text: string) => Promise<{
19
+ export type GetEmbedding = (
20
+ text: string,
21
+ ) => Promise<{
20
22
  embedding: number[];
21
- embeddingModel: string | EmbeddingModel<string>;
23
+ textEmbeddingModel: string | EmbeddingModel<string>;
22
24
  }>;
23
25
 
24
26
  /**
@@ -96,9 +98,10 @@ export async function fetchContextMessages(
96
98
  "You must provide an embedding and embeddingModel to use vector search",
97
99
  );
98
100
  }
99
- const embeddingFields = opts.searchOptions?.vectorSearch
100
- ? await args.getEmbedding?.(text)
101
- : undefined;
101
+ const embeddingFields =
102
+ opts.searchOptions?.vectorSearch && text
103
+ ? await args.getEmbedding?.(text)
104
+ : undefined;
102
105
  const searchMessages = await ctx.runAction(
103
106
  component.messages.searchMessages,
104
107
  {
@@ -123,8 +126,8 @@ export async function fetchContextMessages(
123
126
  opts.searchOptions?.vectorScoreThreshold ??
124
127
  DEFAULT_VECTOR_SCORE_THRESHOLD,
125
128
  embedding: embeddingFields?.embedding,
126
- embeddingModel: embeddingFields?.embeddingModel
127
- ? getModelName(embeddingFields.embeddingModel)
129
+ embeddingModel: embeddingFields?.textEmbeddingModel
130
+ ? getModelName(embeddingFields.textEmbeddingModel)
128
131
  : undefined,
129
132
  },
130
133
  );
@@ -2,6 +2,7 @@ import {
2
2
  type ChunkDetector,
3
3
  smoothStream,
4
4
  type StreamTextTransform,
5
+ type TextStreamPart,
5
6
  type ToolSet,
6
7
  } from "ai";
7
8
  import type {
@@ -9,9 +10,7 @@ import type {
9
10
  StreamArgs,
10
11
  StreamDelta,
11
12
  StreamMessage,
12
- vTextStreamPartV5,
13
13
  } from "../validators.js";
14
- import type { MessageDoc } from "../component/schema.js";
15
14
  import type {
16
15
  AgentComponent,
17
16
  RunActionCtx,
@@ -20,7 +19,7 @@ import type {
20
19
  SyncStreamsReturnValue,
21
20
  } from "./types.js";
22
21
  import { omit } from "convex-helpers";
23
- import type { Infer } from "convex/values";
22
+ import { serializeTextStreamingPartsV5 } from "../parts.js";
24
23
 
25
24
  /**
26
25
  * A function that handles fetching stream deltas, used with the React hooks
@@ -65,9 +64,10 @@ export async function syncStreams(
65
64
  export async function abortStream(
66
65
  ctx: RunMutationCtx,
67
66
  component: AgentComponent,
68
- args: {
69
- reason: string;
70
- } & ({ streamId: string } | { threadId: string; order: number }),
67
+ args: { reason: string } & (
68
+ | { streamId: string }
69
+ | { threadId: string; order: number }
70
+ ),
71
71
  ): Promise<boolean> {
72
72
  if ("streamId" in args) {
73
73
  return await ctx.runMutation(component.streams.abort, {
@@ -126,11 +126,20 @@ export type StreamingOptions = {
126
126
  * Defaults to 250.
127
127
  */
128
128
  throttleMs?: number;
129
+ /**
130
+ * If set to true, this will return immediately, as it would if you weren't
131
+ * saving the deltas. Otherwise, the call will "consume" the stream with
132
+ * .consumeStream(), which waits for the stream to finish before returning.
133
+ *
134
+ * When saving deltas, you're often not interactin with the stream otherwise.
135
+ */
136
+ returnImmediately?: boolean;
129
137
  };
130
138
  export const DEFAULT_STREAMING_OPTIONS = {
131
139
  // This chunks by sentences / clauses. Punctuation followed by whitespace.
132
140
  chunking: /[\p{P}\s]/u,
133
141
  throttleMs: 250,
142
+ returnImmediately: false,
134
143
  } satisfies StreamingOptions;
135
144
 
136
145
  export function mergeTransforms<TOOLS extends ToolSet>(
@@ -159,9 +168,7 @@ export function mergeTransforms<TOOLS extends ToolSet>(
159
168
  export class DeltaStreamer {
160
169
  public streamId: string | undefined;
161
170
  public readonly options: Required<StreamingOptions>;
162
- #nextParts: Infer<typeof vTextStreamPartV5>[] = [];
163
- #nextOrder: number;
164
- #nextStepOrder: number;
171
+ #nextParts: TextStreamPart<ToolSet>[] = [];
165
172
  #latestWrite: number = 0;
166
173
  #ongoingWrite: Promise<void> | undefined;
167
174
  #cursor: number = 0;
@@ -173,51 +180,46 @@ export class DeltaStreamer {
173
180
  options: true | StreamingOptions,
174
181
  public readonly metadata: {
175
182
  threadId: string;
176
- agentName: string | undefined;
177
- model: string | undefined;
178
- provider: string | undefined;
179
- providerOptions: ProviderOptions | undefined;
180
- userId: string | undefined;
181
- order: number | undefined;
182
- stepOrder: number | undefined;
183
- abortSignal: AbortSignal | undefined;
183
+ userId?: string;
184
+ order: number;
185
+ stepOrder: number;
186
+ agentName?: string;
187
+ model?: string;
188
+ provider?: string;
189
+ providerOptions?: ProviderOptions;
190
+ abortSignal?: AbortSignal;
184
191
  },
185
192
  ) {
186
193
  this.options =
187
194
  typeof options === "boolean"
188
195
  ? DEFAULT_STREAMING_OPTIONS
189
- : {
190
- ...DEFAULT_STREAMING_OPTIONS,
191
- ...options,
192
- };
196
+ : { ...DEFAULT_STREAMING_OPTIONS, ...options };
193
197
  this.#nextParts = [];
194
- this.#nextOrder = metadata.order ?? 0;
195
- this.#nextStepOrder = (metadata.stepOrder ?? 0) + 1;
196
198
  this.abortController = new AbortController();
197
199
  if (metadata.abortSignal) {
198
200
  metadata.abortSignal.addEventListener("abort", async () => {
199
201
  if (this.streamId) {
202
+ this.abortController.abort();
203
+ const finalDelta = this.#createDelta();
204
+ await this.#ongoingWrite;
200
205
  await this.ctx.runMutation(this.component.streams.abort, {
201
206
  streamId: this.streamId,
202
207
  reason: "abortSignal",
208
+ finalDelta,
203
209
  });
204
210
  }
205
- this.abortController.abort();
206
211
  });
207
212
  }
208
213
  }
209
- public async addParts(parts: Infer<typeof vTextStreamPartV5>[]) {
214
+
215
+ public async addParts(parts: TextStreamPart<ToolSet>[]) {
210
216
  if (this.abortController.signal.aborted) {
211
217
  return;
212
218
  }
213
219
  if (!this.streamId) {
214
220
  this.streamId = await this.ctx.runMutation(
215
221
  this.component.streams.create,
216
- {
217
- ...omit(this.metadata, ["abortSignal"]),
218
- order: this.#nextOrder,
219
- stepOrder: this.#nextStepOrder,
220
- },
222
+ omit(this.metadata, ["abortSignal"]),
221
223
  );
222
224
  }
223
225
  this.#nextParts.push(...parts);
@@ -234,6 +236,9 @@ export class DeltaStreamer {
234
236
  return;
235
237
  }
236
238
  const delta = this.#createDelta();
239
+ if (!delta) {
240
+ return;
241
+ }
237
242
  this.#latestWrite = Date.now();
238
243
  try {
239
244
  const success = await this.ctx.runMutation(
@@ -259,46 +264,46 @@ export class DeltaStreamer {
259
264
  }
260
265
  }
261
266
 
262
- #createDelta(): StreamDelta {
267
+ #createDelta(): StreamDelta | undefined {
268
+ if (this.#nextParts.length === 0) {
269
+ return undefined;
270
+ }
263
271
  const start = this.#cursor;
264
272
  const end = start + this.#nextParts.length;
265
273
  this.#cursor = end;
266
- const parts = this.#nextParts;
274
+ const parts = serializeTextStreamingPartsV5(this.#nextParts);
267
275
  this.#nextParts = [];
268
276
  if (!this.streamId) {
269
277
  throw new Error("Creating a delta before the stream is created");
270
278
  }
271
- return {
279
+ return { streamId: this.streamId, start, end, parts };
280
+ }
281
+
282
+ public async finish() {
283
+ if (!this.streamId) {
284
+ return;
285
+ }
286
+ const finalDelta = this.#createDelta();
287
+ await this.#ongoingWrite;
288
+ await this.ctx.runMutation(this.component.streams.finish, {
272
289
  streamId: this.streamId,
273
- start,
274
- end,
275
- parts,
276
- };
290
+ finalDelta,
291
+ });
277
292
  }
278
293
 
279
- public async finish(messages: MessageDoc[]) {
280
- if (this.#ongoingWrite) {
281
- await this.#ongoingWrite;
282
- this.#ongoingWrite = undefined;
294
+ public async fail(reason: string) {
295
+ if (this.abortController.signal.aborted) {
296
+ return;
283
297
  }
298
+ this.abortController.abort();
284
299
  if (!this.streamId) {
285
- throw new Error("Finish called before stream is created");
286
- }
287
- const lastMessage = messages.at(-1);
288
- if (lastMessage) {
289
- this.#nextOrder = lastMessage.order;
290
- this.#nextStepOrder = lastMessage.stepOrder + 1;
291
- } else {
292
- console.warn("Step finished without generating a message");
300
+ return;
293
301
  }
294
- const finalDelta =
295
- this.#nextParts.length > 0 ? this.#createDelta() : undefined;
296
- this.#nextParts = [];
297
- const streamId = this.streamId;
298
- this.streamId = undefined;
299
- this.#cursor = 0;
300
- await this.ctx.runMutation(this.component.streams.finish, {
301
- streamId,
302
+ const finalDelta = this.#createDelta();
303
+ await this.#ongoingWrite;
304
+ await this.ctx.runMutation(this.component.streams.abort, {
305
+ streamId: this.streamId,
306
+ reason,
302
307
  finalDelta,
303
308
  });
304
309
  }