@convex-dev/agent 0.1.9-alpha.10 → 0.1.9-alpha.11

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 (70) hide show
  1. package/dist/commonjs/client/index.d.ts +1 -0
  2. package/dist/commonjs/client/index.d.ts.map +1 -1
  3. package/dist/commonjs/client/index.js +2 -0
  4. package/dist/commonjs/client/index.js.map +1 -1
  5. package/dist/commonjs/client/streaming.d.ts.map +1 -1
  6. package/dist/commonjs/client/streaming.js +7 -1
  7. package/dist/commonjs/client/streaming.js.map +1 -1
  8. package/dist/commonjs/component/_generated/api.d.ts +12 -1
  9. package/dist/commonjs/component/schema.d.ts +26 -26
  10. package/dist/commonjs/component/schema.d.ts.map +1 -1
  11. package/dist/commonjs/component/schema.js +2 -2
  12. package/dist/commonjs/component/schema.js.map +1 -1
  13. package/dist/commonjs/component/streams.d.ts +10 -3
  14. package/dist/commonjs/component/streams.d.ts.map +1 -1
  15. package/dist/commonjs/component/streams.js +48 -17
  16. package/dist/commonjs/component/streams.js.map +1 -1
  17. package/dist/commonjs/react/deltas.d.ts.map +1 -1
  18. package/dist/commonjs/react/deltas.js +2 -2
  19. package/dist/commonjs/react/deltas.js.map +1 -1
  20. package/dist/commonjs/react/index.d.ts +4 -2
  21. package/dist/commonjs/react/index.d.ts.map +1 -1
  22. package/dist/commonjs/react/index.js +22 -9
  23. package/dist/commonjs/react/index.js.map +1 -1
  24. package/dist/commonjs/react/optimisticallySendMessage.js +1 -1
  25. package/dist/commonjs/react/optimisticallySendMessage.js.map +1 -1
  26. package/dist/commonjs/validators.d.ts +8 -3
  27. package/dist/commonjs/validators.d.ts.map +1 -1
  28. package/dist/commonjs/validators.js +2 -0
  29. package/dist/commonjs/validators.js.map +1 -1
  30. package/dist/commonjs.tsbuildinfo +1 -1
  31. package/dist/esm/client/index.d.ts +1 -0
  32. package/dist/esm/client/index.d.ts.map +1 -1
  33. package/dist/esm/client/index.js +2 -0
  34. package/dist/esm/client/index.js.map +1 -1
  35. package/dist/esm/client/streaming.d.ts.map +1 -1
  36. package/dist/esm/client/streaming.js +7 -1
  37. package/dist/esm/client/streaming.js.map +1 -1
  38. package/dist/esm/component/_generated/api.d.ts +12 -1
  39. package/dist/esm/component/schema.d.ts +26 -26
  40. package/dist/esm/component/schema.d.ts.map +1 -1
  41. package/dist/esm/component/schema.js +2 -2
  42. package/dist/esm/component/schema.js.map +1 -1
  43. package/dist/esm/component/streams.d.ts +10 -3
  44. package/dist/esm/component/streams.d.ts.map +1 -1
  45. package/dist/esm/component/streams.js +48 -17
  46. package/dist/esm/component/streams.js.map +1 -1
  47. package/dist/esm/react/deltas.d.ts.map +1 -1
  48. package/dist/esm/react/deltas.js +2 -2
  49. package/dist/esm/react/deltas.js.map +1 -1
  50. package/dist/esm/react/index.d.ts +4 -2
  51. package/dist/esm/react/index.d.ts.map +1 -1
  52. package/dist/esm/react/index.js +22 -9
  53. package/dist/esm/react/index.js.map +1 -1
  54. package/dist/esm/react/optimisticallySendMessage.js +1 -1
  55. package/dist/esm/react/optimisticallySendMessage.js.map +1 -1
  56. package/dist/esm/validators.d.ts +8 -3
  57. package/dist/esm/validators.d.ts.map +1 -1
  58. package/dist/esm/validators.js +2 -0
  59. package/dist/esm/validators.js.map +1 -1
  60. package/dist/esm.tsbuildinfo +1 -1
  61. package/package.json +1 -1
  62. package/src/client/index.ts +4 -0
  63. package/src/client/streaming.ts +7 -1
  64. package/src/component/_generated/api.d.ts +12 -1
  65. package/src/component/schema.ts +2 -2
  66. package/src/component/streams.ts +82 -32
  67. package/src/react/deltas.ts +4 -2
  68. package/src/react/index.ts +23 -11
  69. package/src/react/optimisticallySendMessage.ts +1 -1
  70. package/src/validators.ts +6 -0
@@ -6,7 +6,7 @@ import {
6
6
  vStreamMessage,
7
7
  } from "../validators.js";
8
8
  import { api, internal } from "./_generated/api.js";
9
- import type { Id } from "./_generated/dataModel.js";
9
+ import type { Doc, Id } from "./_generated/dataModel.js";
10
10
  import {
11
11
  internalMutation,
12
12
  mutation,
@@ -106,33 +106,87 @@ export const create = mutation({
106
106
  export const list = query({
107
107
  args: {
108
108
  threadId: v.id("threads"),
109
+ startOrder: v.optional(v.number()),
110
+ statuses: v.optional(
111
+ v.array(
112
+ v.union(
113
+ v.literal("streaming"),
114
+ v.literal("finished"),
115
+ v.literal("aborted")
116
+ )
117
+ )
118
+ ),
109
119
  },
110
120
  returns: v.array(vStreamMessage),
111
121
  handler: async (ctx, args) => {
112
- return ctx.db
113
- .query("streamingMessages")
114
- .withIndex("threadId_state_order_stepOrder", (q) =>
115
- q.eq("threadId", args.threadId).eq("state.kind", "streaming")
116
- )
117
- .order("desc")
118
- .take(100)
119
- .then((msgs) =>
120
- msgs.map((m) => ({
121
- streamId: m._id,
122
- ...pick(m, [
123
- "order",
124
- "stepOrder",
125
- "userId",
126
- "agentName",
127
- "model",
128
- "provider",
129
- "providerOptions",
130
- ]),
131
- }))
122
+ const statuses = args.statuses ?? ["streaming"];
123
+ const messages = await mergedStream(
124
+ statuses.map((status) =>
125
+ stream(ctx.db, schema)
126
+ .query("streamingMessages")
127
+ .withIndex("threadId_state_order_stepOrder", (q) =>
128
+ q
129
+ .eq("threadId", args.threadId)
130
+ .eq("state.kind", status)
131
+ .gte("order", args.startOrder ?? 0)
132
+ )
133
+ .order("desc")
134
+ ),
135
+ ["order", "stepOrder"]
136
+ ).take(100);
137
+
138
+ return messages.map((m) => ({
139
+ streamId: m._id,
140
+ status: m.state.kind,
141
+ ...pick(m, [
142
+ "order",
143
+ "stepOrder",
144
+ "userId",
145
+ "agentName",
146
+ "model",
147
+ "provider",
148
+ "providerOptions",
149
+ ]),
150
+ }));
151
+ },
152
+ });
153
+
154
+ export const abort = mutation({
155
+ args: {
156
+ streamId: v.id("streamingMessages"),
157
+ reason: v.string(),
158
+ },
159
+ returns: v.null(),
160
+ handler: async (ctx, args) => {
161
+ const stream = await ctx.db.get(args.streamId);
162
+ if (!stream) {
163
+ throw new Error(`Stream not found: ${args.streamId}`);
164
+ }
165
+ if (stream.state.kind !== "streaming") {
166
+ console.warn(
167
+ `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
132
168
  );
169
+ return;
170
+ }
171
+ await cleanupTimeoutFn(ctx, stream);
172
+ await ctx.db.patch(args.streamId, {
173
+ state: { kind: "aborted", reason: args.reason },
174
+ });
133
175
  },
134
176
  });
135
177
 
178
+ async function cleanupTimeoutFn(
179
+ ctx: MutationCtx,
180
+ stream: Doc<"streamingMessages">
181
+ ) {
182
+ if (stream.state.kind === "streaming" && stream.state.timeoutFnId) {
183
+ const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
184
+ if (timeoutFn?.state.kind === "pending") {
185
+ await ctx.scheduler.cancel(stream.state.timeoutFnId);
186
+ }
187
+ }
188
+ }
189
+
136
190
  export const finish = mutation({
137
191
  args: {
138
192
  streamId: v.id("streamingMessages"),
@@ -153,12 +207,7 @@ export const finish = mutation({
153
207
  );
154
208
  return;
155
209
  }
156
- if (stream.state.timeoutFnId) {
157
- const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
158
- if (timeoutFn?.state.kind === "pending") {
159
- await ctx.scheduler.cancel(stream.state.timeoutFnId);
160
- }
161
- }
210
+ await cleanupTimeoutFn(ctx, stream);
162
211
  await ctx.db.patch(args.streamId, {
163
212
  state: { kind: "finished", endedAt: Date.now() },
164
213
  });
@@ -223,8 +272,8 @@ export const timeoutStream = internalMutation({
223
272
  }
224
273
  await ctx.db.patch(args.streamId, {
225
274
  state: {
226
- kind: "finished",
227
- endedAt: Date.now(),
275
+ kind: "aborted",
276
+ reason: "timeout",
228
277
  },
229
278
  });
230
279
  },
@@ -242,6 +291,9 @@ async function deletePageForStreamId(
242
291
  cursor: args.cursor ?? null,
243
292
  });
244
293
  await Promise.all(deltas.page.map((d) => ctx.db.delete(d._id)));
294
+ if (deltas.isDone) {
295
+ await ctx.db.delete(args.streamId);
296
+ }
245
297
  return deltas;
246
298
  }
247
299
 
@@ -281,13 +333,12 @@ export async function deleteStreamsPageForThreadId(
281
333
  cursor: deltaCursor,
282
334
  });
283
335
  if (result.isDone) {
284
- await ctx.db.delete(streamMessage._id);
285
336
  deltaCursor = undefined;
286
337
  }
287
338
  return {
288
339
  isDone: false,
289
340
  streamOrder: streamMessage.order,
290
- deltaCursor: result.continueCursor,
341
+ deltaCursor,
291
342
  };
292
343
  }
293
344
 
@@ -346,7 +397,6 @@ export const deleteStreamSync = mutation({
346
397
  cursor: deltas.continueCursor,
347
398
  });
348
399
  }
349
- await ctx.db.delete(args.streamId);
350
400
  },
351
401
  });
352
402
 
@@ -256,12 +256,14 @@ export function createStreamingMessage(
256
256
  ): MessageDoc {
257
257
  const { streamId, ...rest } = message;
258
258
  const metadata: MessageDoc = {
259
+ ...rest,
259
260
  _id: `${streamId}-${index}`,
260
261
  _creationTime: Date.now(),
261
- status: "pending",
262
+ status: (
263
+ { streaming: "pending", finished: "success", aborted: "failed" } as const
264
+ )[message.status],
262
265
  threadId,
263
266
  tool: false,
264
- ...rest,
265
267
  };
266
268
  switch (part.type) {
267
269
  case "text-delta":
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import type { ErrorMessage } from "convex-helpers";
2
+ import { omit, type ErrorMessage } from "convex-helpers";
3
3
  import {
4
4
  type PaginatedQueryArgs,
5
5
  type UsePaginatedQueryResult,
@@ -7,7 +7,7 @@ import {
7
7
  } from "convex/react";
8
8
  import { usePaginatedQuery } from "convex-helpers/react";
9
9
  import type { FunctionArgs } from "convex/server";
10
- import { useMemo, useState } from "react";
10
+ import { useMemo, useRef, useState } from "react";
11
11
  import type { MessageDoc } from "../client/index.js";
12
12
  import type { SyncStreamsReturnValue } from "../client/types.js";
13
13
  import type { StreamArgs } from "../validators.js";
@@ -103,14 +103,18 @@ export function useThreadMessages<
103
103
  ThreadMessagesArgs<Query>,
104
104
  ThreadMessagesResult<Query>
105
105
  >,
106
- !options.stream ? "skip" : args
106
+ !options.stream ||
107
+ args === "skip" ||
108
+ paginated.status === "LoadingFirstPage"
109
+ ? "skip"
110
+ : { ...args, startOrder: paginated.results.at(-1)?.order }
107
111
  );
108
112
 
109
113
  const merged = useMemo(() => {
110
114
  const streamListMessages =
111
115
  streamMessages?.map((m) => ({
112
116
  ...m,
113
- streaming: true,
117
+ streaming: !m.status || m.status === "streaming",
114
118
  })) ?? [];
115
119
  return {
116
120
  ...paginated,
@@ -150,7 +154,7 @@ export function useStreamingThreadMessages<
150
154
  Query extends ThreadStreamQuery<any, any>,
151
155
  >(
152
156
  query: Query,
153
- args: ThreadMessagesArgs<Query> | "skip"
157
+ args: (ThreadMessagesArgs<Query> & { startOrder?: number }) | "skip"
154
158
  ): Array<ThreadMessagesResult<Query>> | undefined {
155
159
  // Invariant: streamMessages[streamId] is comprised of all deltas up to the
156
160
  // cursor. There can be multiple messages in the same stream, e.g. for tool
@@ -158,15 +162,23 @@ export function useStreamingThreadMessages<
158
162
  const [streams, setStreams] = useState<
159
163
  Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>
160
164
  >([]);
165
+ const startOrderRef = useRef<number>(0);
166
+ const queryArgs = args === "skip" ? args : omit(args, ["startOrder"]);
167
+ if (args !== "skip" && !startOrderRef.current && args.startOrder) {
168
+ startOrderRef.current = args.startOrder;
169
+ }
161
170
  // Get all the active streams
162
171
  const streamList = useQuery(
163
172
  query,
164
- args === "skip"
165
- ? args
173
+ queryArgs === "skip"
174
+ ? queryArgs
166
175
  : ({
167
- ...args,
176
+ ...queryArgs,
168
177
  paginationOpts: { cursor: null, numItems: 0 },
169
- streamArgs: { kind: "list" } as StreamArgs,
178
+ streamArgs: {
179
+ kind: "list",
180
+ startOrder: startOrderRef.current,
181
+ } as StreamArgs,
170
182
  } as FunctionArgs<Query>)
171
183
  ) as
172
184
  | { streams: Extract<SyncStreamsReturnValue, { kind: "list" }> }
@@ -186,10 +198,10 @@ export function useStreamingThreadMessages<
186
198
  // Get the deltas for all the active streams, if any.
187
199
  const cursorQuery = useQuery(
188
200
  query,
189
- args === "skip" || !streamList
201
+ queryArgs === "skip" || !streamList
190
202
  ? ("skip" as const)
191
203
  : ({
192
- ...args,
204
+ ...queryArgs,
193
205
  paginationOpts: { cursor: null, numItems: 0 },
194
206
  streamArgs: { kind: "deltas", cursors } as StreamArgs,
195
207
  } as FunctionArgs<Query>)
@@ -11,7 +11,7 @@ export function optimisticallySendMessage(
11
11
  ) => void {
12
12
  return (store, args) => {
13
13
  const queries = store.getAllQueries(query);
14
- let maxOrder = 0;
14
+ let maxOrder = -1;
15
15
  let maxStepOrder = 0;
16
16
  for (const q of queries) {
17
17
  if (q.args?.threadId !== args.threadId) continue;
package/src/validators.ts CHANGED
@@ -460,6 +460,7 @@ export const vStreamArgs = v.optional(
460
460
  v.union(
461
461
  v.object({
462
462
  kind: v.literal("list"),
463
+ startOrder: v.optional(v.number()),
463
464
  }),
464
465
  v.object({
465
466
  kind: v.literal("deltas"),
@@ -471,6 +472,11 @@ export type StreamArgs = Infer<typeof vStreamArgs>;
471
472
 
472
473
  export const vStreamMessage = v.object({
473
474
  streamId: v.string(),
475
+ status: v.union(
476
+ v.literal("streaming"),
477
+ v.literal("finished"),
478
+ v.literal("aborted")
479
+ ),
474
480
  order: v.number(),
475
481
  stepOrder: v.number(),
476
482
  // metadata