@convex-dev/agent 0.1.4-alpha.0 → 0.1.5-alpha.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 (165) hide show
  1. package/README.md +24 -35
  2. package/dist/commonjs/client/createTool.d.ts +3 -5
  3. package/dist/commonjs/client/createTool.d.ts.map +1 -1
  4. package/dist/commonjs/client/createTool.js +1 -0
  5. package/dist/commonjs/client/createTool.js.map +1 -1
  6. package/dist/commonjs/client/index.d.ts +6 -6
  7. package/dist/commonjs/client/index.d.ts.map +1 -1
  8. package/dist/commonjs/client/index.js.map +1 -1
  9. package/dist/commonjs/client/streaming.d.ts +5 -5
  10. package/dist/commonjs/client/streaming.d.ts.map +1 -1
  11. package/dist/commonjs/client/streaming.js +3 -2
  12. package/dist/commonjs/client/streaming.js.map +1 -1
  13. package/dist/commonjs/component/files.d.ts +2 -2
  14. package/dist/commonjs/component/files.d.ts.map +1 -1
  15. package/dist/commonjs/component/files.js.map +1 -1
  16. package/dist/commonjs/component/messages.d.ts +24 -24
  17. package/dist/commonjs/component/messages.d.ts.map +1 -1
  18. package/dist/commonjs/component/messages.js +4 -5
  19. package/dist/commonjs/component/messages.js.map +1 -1
  20. package/dist/commonjs/component/schema.d.ts +45 -45
  21. package/dist/commonjs/component/schema.d.ts.map +1 -1
  22. package/dist/commonjs/component/schema.js.map +1 -1
  23. package/dist/commonjs/component/streams.d.ts.map +1 -1
  24. package/dist/commonjs/component/streams.js +4 -7
  25. package/dist/commonjs/component/streams.js.map +1 -1
  26. package/dist/commonjs/component/threads.d.ts +8 -8
  27. package/dist/commonjs/component/threads.js +3 -3
  28. package/dist/commonjs/component/threads.js.map +1 -1
  29. package/dist/commonjs/component/users.js +4 -4
  30. package/dist/commonjs/component/users.js.map +1 -1
  31. package/dist/commonjs/component/vector/index.d.ts +3 -3
  32. package/dist/commonjs/component/vector/index.d.ts.map +1 -1
  33. package/dist/commonjs/component/vector/index.js +4 -5
  34. package/dist/commonjs/component/vector/index.js.map +1 -1
  35. package/dist/commonjs/component/vector/tables.d.ts +5 -7
  36. package/dist/commonjs/component/vector/tables.d.ts.map +1 -1
  37. package/dist/commonjs/mapping.d.ts +2 -2
  38. package/dist/commonjs/mapping.d.ts.map +1 -1
  39. package/dist/commonjs/mapping.js.map +1 -1
  40. package/dist/commonjs/react/deltas.d.ts +29 -0
  41. package/dist/commonjs/react/deltas.d.ts.map +1 -0
  42. package/dist/commonjs/react/deltas.js +267 -0
  43. package/dist/commonjs/react/deltas.js.map +1 -0
  44. package/dist/commonjs/react/index.d.ts +4 -49
  45. package/dist/commonjs/react/index.d.ts.map +1 -1
  46. package/dist/commonjs/react/index.js +15 -446
  47. package/dist/commonjs/react/index.js.map +1 -1
  48. package/dist/commonjs/react/optimisticallySendMessage.d.ts +8 -0
  49. package/dist/commonjs/react/optimisticallySendMessage.d.ts.map +1 -0
  50. package/dist/commonjs/react/optimisticallySendMessage.js +40 -0
  51. package/dist/commonjs/react/optimisticallySendMessage.js.map +1 -0
  52. package/dist/commonjs/react/toUIMessages.d.ts +3 -3
  53. package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
  54. package/dist/commonjs/react/toUIMessages.js +4 -2
  55. package/dist/commonjs/react/toUIMessages.js.map +1 -1
  56. package/dist/commonjs/react/types.d.ts +26 -0
  57. package/dist/commonjs/react/types.d.ts.map +1 -0
  58. package/dist/commonjs/react/types.js +2 -0
  59. package/dist/commonjs/react/types.js.map +1 -0
  60. package/dist/commonjs/react/useSmoothText.d.ts +20 -0
  61. package/dist/commonjs/react/useSmoothText.d.ts.map +1 -0
  62. package/dist/commonjs/react/useSmoothText.js +50 -0
  63. package/dist/commonjs/react/useSmoothText.js.map +1 -0
  64. package/dist/commonjs/shared.d.ts +2 -2
  65. package/dist/commonjs/shared.d.ts.map +1 -1
  66. package/dist/commonjs/validators.d.ts +1 -1
  67. package/dist/commonjs/validators.d.ts.map +1 -1
  68. package/dist/commonjs/validators.js +2 -2
  69. package/dist/commonjs/validators.js.map +1 -1
  70. package/dist/commonjs.tsbuildinfo +1 -0
  71. package/dist/esm/client/createTool.d.ts +3 -5
  72. package/dist/esm/client/createTool.d.ts.map +1 -1
  73. package/dist/esm/client/createTool.js +1 -0
  74. package/dist/esm/client/createTool.js.map +1 -1
  75. package/dist/esm/client/index.d.ts +6 -6
  76. package/dist/esm/client/index.d.ts.map +1 -1
  77. package/dist/esm/client/index.js.map +1 -1
  78. package/dist/esm/client/streaming.d.ts +5 -5
  79. package/dist/esm/client/streaming.d.ts.map +1 -1
  80. package/dist/esm/client/streaming.js +3 -2
  81. package/dist/esm/client/streaming.js.map +1 -1
  82. package/dist/esm/component/files.d.ts +2 -2
  83. package/dist/esm/component/files.d.ts.map +1 -1
  84. package/dist/esm/component/files.js.map +1 -1
  85. package/dist/esm/component/messages.d.ts +24 -24
  86. package/dist/esm/component/messages.d.ts.map +1 -1
  87. package/dist/esm/component/messages.js +4 -5
  88. package/dist/esm/component/messages.js.map +1 -1
  89. package/dist/esm/component/schema.d.ts +45 -45
  90. package/dist/esm/component/schema.d.ts.map +1 -1
  91. package/dist/esm/component/schema.js.map +1 -1
  92. package/dist/esm/component/streams.d.ts.map +1 -1
  93. package/dist/esm/component/streams.js +4 -7
  94. package/dist/esm/component/streams.js.map +1 -1
  95. package/dist/esm/component/threads.d.ts +8 -8
  96. package/dist/esm/component/threads.js +3 -3
  97. package/dist/esm/component/threads.js.map +1 -1
  98. package/dist/esm/component/users.js +4 -4
  99. package/dist/esm/component/users.js.map +1 -1
  100. package/dist/esm/component/vector/index.d.ts +3 -3
  101. package/dist/esm/component/vector/index.d.ts.map +1 -1
  102. package/dist/esm/component/vector/index.js +4 -5
  103. package/dist/esm/component/vector/index.js.map +1 -1
  104. package/dist/esm/component/vector/tables.d.ts +5 -7
  105. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  106. package/dist/esm/mapping.d.ts +2 -2
  107. package/dist/esm/mapping.d.ts.map +1 -1
  108. package/dist/esm/mapping.js.map +1 -1
  109. package/dist/esm/react/deltas.d.ts +29 -0
  110. package/dist/esm/react/deltas.d.ts.map +1 -0
  111. package/dist/esm/react/deltas.js +267 -0
  112. package/dist/esm/react/deltas.js.map +1 -0
  113. package/dist/esm/react/index.d.ts +4 -49
  114. package/dist/esm/react/index.d.ts.map +1 -1
  115. package/dist/esm/react/index.js +15 -446
  116. package/dist/esm/react/index.js.map +1 -1
  117. package/dist/esm/react/optimisticallySendMessage.d.ts +8 -0
  118. package/dist/esm/react/optimisticallySendMessage.d.ts.map +1 -0
  119. package/dist/esm/react/optimisticallySendMessage.js +40 -0
  120. package/dist/esm/react/optimisticallySendMessage.js.map +1 -0
  121. package/dist/esm/react/toUIMessages.d.ts +3 -3
  122. package/dist/esm/react/toUIMessages.d.ts.map +1 -1
  123. package/dist/esm/react/toUIMessages.js +4 -2
  124. package/dist/esm/react/toUIMessages.js.map +1 -1
  125. package/dist/esm/react/types.d.ts +26 -0
  126. package/dist/esm/react/types.d.ts.map +1 -0
  127. package/dist/esm/react/types.js +2 -0
  128. package/dist/esm/react/types.js.map +1 -0
  129. package/dist/esm/react/useSmoothText.d.ts +20 -0
  130. package/dist/esm/react/useSmoothText.d.ts.map +1 -0
  131. package/dist/esm/react/useSmoothText.js +50 -0
  132. package/dist/esm/react/useSmoothText.js.map +1 -0
  133. package/dist/esm/shared.d.ts +2 -2
  134. package/dist/esm/shared.d.ts.map +1 -1
  135. package/dist/esm/validators.d.ts +1 -1
  136. package/dist/esm/validators.d.ts.map +1 -1
  137. package/dist/esm/validators.js +2 -2
  138. package/dist/esm/validators.js.map +1 -1
  139. package/dist/esm.tsbuildinfo +1 -0
  140. package/package.json +5 -1
  141. package/src/client/createTool.ts +3 -5
  142. package/src/client/index.test.ts +1 -1
  143. package/src/client/index.ts +9 -10
  144. package/src/client/streaming.ts +15 -5
  145. package/src/component/files.ts +2 -2
  146. package/src/component/messages.test.ts +4 -4
  147. package/src/component/messages.ts +15 -15
  148. package/src/component/schema.ts +1 -1
  149. package/src/component/streams.ts +8 -9
  150. package/src/component/threads.ts +6 -6
  151. package/src/component/users.ts +7 -7
  152. package/src/component/vector/index.ts +12 -8
  153. package/src/component/vector/tables.ts +10 -10
  154. package/src/mapping.ts +4 -4
  155. package/src/react/deltas.test.ts +298 -0
  156. package/src/react/deltas.ts +340 -0
  157. package/src/react/index.ts +26 -589
  158. package/src/react/optimisticallySendMessage.ts +46 -0
  159. package/src/react/toUIMessages.test.ts +301 -0
  160. package/src/react/toUIMessages.ts +9 -5
  161. package/src/react/types.ts +52 -0
  162. package/src/react/useSmoothText.ts +76 -0
  163. package/src/shared.ts +2 -2
  164. package/src/validators.test.ts +22 -22
  165. package/src/validators.ts +8 -2
@@ -1,31 +1,27 @@
1
- import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
2
- import type { BetterOmit, ErrorMessage, Expand } from "convex-helpers";
1
+ import type { ErrorMessage } from "convex-helpers";
3
2
  import {
4
- insertAtTop,
5
3
  type PaginatedQueryArgs,
6
4
  usePaginatedQuery,
7
5
  type UsePaginatedQueryResult,
8
6
  useQuery,
9
7
  } from "convex/react";
10
- import type {
11
- FunctionArgs,
12
- FunctionReference,
13
- PaginationOptions,
14
- PaginationResult,
15
- } from "convex/server";
16
- import { useEffect, useMemo, useRef, useState } from "react";
8
+ import type { FunctionArgs } from "convex/server";
9
+ import { useMemo, useState } from "react";
17
10
  import type { MessageDoc } from "../client";
18
11
  import type { SyncStreamsReturnValue } from "../client/types";
19
- import type {
20
- StreamArgs,
21
- StreamDelta,
22
- StreamMessage,
23
- TextStreamPart,
24
- } from "../validators";
12
+ import type { StreamArgs } from "../validators";
25
13
  import type { UIMessage } from "./toUIMessages";
26
14
  import { toUIMessages } from "./toUIMessages";
27
- import { OptimisticLocalStore } from "convex/browser";
15
+ import { mergeDeltas } from "./deltas";
16
+ import type {
17
+ ThreadQuery,
18
+ ThreadStreamQuery,
19
+ ThreadMessagesArgs,
20
+ ThreadMessagesResult,
21
+ } from "./types";
28
22
 
23
+ export { optimisticallySendMessage } from "./optimisticallySendMessage";
24
+ export { useSmoothText } from "./useSmoothText";
29
25
  export { toUIMessages, type UIMessage };
30
26
 
31
27
  /**
@@ -102,8 +98,10 @@ export function useThreadMessages<
102
98
 
103
99
  // These are streaming messages that will not include full messages.
104
100
  const streamMessages = useStreamingThreadMessages(
105
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
- query as ThreadStreamQuery<any, any>,
101
+ query as ThreadStreamQuery<
102
+ ThreadMessagesArgs<Query>,
103
+ ThreadMessagesResult<Query>
104
+ >,
107
105
  !options.stream ? "skip" : args
108
106
  );
109
107
 
@@ -157,7 +155,7 @@ export function useStreamingThreadMessages<
157
155
  // cursor. There can be multiple messages in the same stream, e.g. for tool
158
156
  // calls.
159
157
  const [streams, setStreams] = useState<
160
- Array<{ stream: StreamMessage; cursor: number; messages: MessageDoc[] }>
158
+ Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>
161
159
  >([]);
162
160
  // Get all the active streams
163
161
  const streamList = useQuery(
@@ -179,8 +177,7 @@ export function useStreamingThreadMessages<
179
177
  throw new Error("Expected list streams");
180
178
  }
181
179
  return streamList.streams.messages.map(({ streamId }) => {
182
- const stream = streams.find((s) => s.stream.streamId === streamId);
183
- // Because of the invariant, we can just take the last chunk's end.
180
+ const stream = streams.find((s) => s.streamId === streamId);
184
181
  const cursor = stream?.cursor ?? 0;
185
182
  return { streamId, cursor };
186
183
  });
@@ -199,21 +196,22 @@ export function useStreamingThreadMessages<
199
196
  | { streams: Extract<SyncStreamsReturnValue, { kind: "deltas" }> }
200
197
  | undefined;
201
198
  // Merge any deltas into the streamChunks, keeping it unmodified if unchanged.
199
+ const threadId = args === "skip" ? undefined : args.threadId;
202
200
  const [messages, newStreams, changed] = useMemo(() => {
203
- if (args === "skip") return [undefined, streams, false];
204
- if (!streamList) return [undefined, streams, false];
201
+ if (!threadId) return [undefined, [], false];
202
+ if (!streamList) return [undefined, [], false];
205
203
  if (cursorQuery && cursorQuery.streams?.kind !== "deltas") {
206
204
  throw new Error("Expected deltas streams");
207
205
  }
208
206
  return mergeDeltas(
209
- args.threadId,
207
+ threadId,
210
208
  streamList.streams.messages,
211
209
  streams,
212
210
  cursorQuery?.streams?.deltas ?? []
213
211
  );
214
- }, [cursorQuery, streams, streamList]);
212
+ }, [threadId, cursorQuery, streams, streamList]);
215
213
  // Now assemble the chunks into messages
216
- if (args === "skip") {
214
+ if (!threadId) {
217
215
  return undefined;
218
216
  }
219
217
  if (changed) {
@@ -222,567 +220,6 @@ export function useStreamingThreadMessages<
222
220
  return messages as ThreadMessagesResult<Query>[] | undefined;
223
221
  }
224
222
 
225
- /**
226
- * A hook that smoothly displays text as it is streamed.
227
- *
228
- * @param text The text to display. Pass in the full text each time.
229
- * @param charsPerSec The number of characters to display per second.
230
- * @returns A tuple of the visible text and the state of the smooth text,
231
- * including the current cursor position and whether it's still streaming.
232
- * This allows you to decide if it's too far behind and you want to adjust
233
- * the charsPerSec or just prefer the full text.
234
- */
235
- export function useSmoothText(
236
- text: string,
237
- {
238
- charsPerSec = 512,
239
- }: {
240
- /**
241
- * The number of characters to display per second.
242
- */
243
- charsPerSec?: number;
244
- } = {}
245
- ): [string, { cursor: number; isStreaming: boolean }] {
246
- const [visibleText, setVisibleText] = useState(text);
247
- const smoothState = useRef({ lastUpdated: Date.now(), cursor: text.length });
248
-
249
- const isStreaming = smoothState.current.cursor < text.length;
250
-
251
- useEffect(() => {
252
- if (!isStreaming) {
253
- return;
254
- }
255
- function update() {
256
- if (smoothState.current.cursor >= text.length) {
257
- return;
258
- }
259
- const now = Date.now();
260
- const timeSinceLastUpdate = now - smoothState.current.lastUpdated;
261
- const chars = Math.floor((timeSinceLastUpdate * charsPerSec) / 1000);
262
- smoothState.current.cursor = Math.min(
263
- smoothState.current.cursor + chars,
264
- text.length
265
- );
266
- smoothState.current.lastUpdated = now;
267
- setVisibleText(text.slice(0, smoothState.current.cursor));
268
- }
269
- update();
270
- const interval = setInterval(() => {
271
- update();
272
- }, 1000 / 60);
273
- return () => clearInterval(interval);
274
- }, [text, isStreaming, charsPerSec]);
275
-
276
- return [visibleText, { cursor: smoothState.current.cursor, isStreaming }];
277
- }
278
-
279
- export function optimisticallySendMessage(
280
- query: ThreadQuery<unknown, MessageDoc>
281
- ): (
282
- store: OptimisticLocalStore,
283
- args: { threadId: string; prompt: string }
284
- ) => void {
285
- return (store, args) => {
286
- const queries = store.getAllQueries(query);
287
- let maxOrder = 0;
288
- let maxStepOrder = 0;
289
- for (const q of queries) {
290
- if (q.args?.threadId !== args.threadId) continue;
291
- if (q.args.streamArgs) continue;
292
- for (const m of q.value?.page ?? []) {
293
- maxOrder = Math.max(maxOrder, m.order);
294
- maxStepOrder = Math.max(maxStepOrder, m.stepOrder);
295
- }
296
- }
297
- const order = maxOrder + 1;
298
- const stepOrder = 0;
299
- insertAtTop({
300
- paginatedQuery: query,
301
- argsToMatch: { threadId: args.threadId, streamArgs: undefined },
302
- item: {
303
- _creationTime: Date.now(),
304
- _id: crypto.randomUUID(),
305
- order,
306
- stepOrder,
307
- status: "pending",
308
- threadId: args.threadId,
309
- tool: false,
310
- message: {
311
- role: "user",
312
- content: args.prompt,
313
- },
314
- text: args.prompt,
315
- },
316
- localQueryStore: store,
317
- });
318
- };
319
- }
320
-
321
- function mergeDeltas(
322
- threadId: string,
323
- streamMessages: StreamMessage[],
324
- existingStreams: Array<{
325
- stream: StreamMessage;
326
- cursor: number;
327
- messages: MessageDoc[];
328
- }>,
329
- allDeltas: StreamDelta[]
330
- ): [
331
- MessageDoc[],
332
- Array<{ stream: StreamMessage; cursor: number; messages: MessageDoc[] }>,
333
- boolean,
334
- ] {
335
- const newStreams: Array<{
336
- stream: StreamMessage;
337
- cursor: number;
338
- messages: MessageDoc[];
339
- }> = [];
340
- // Seed the existing chunks
341
- let changed = false;
342
- for (const streamMessage of streamMessages) {
343
- const deltas = allDeltas
344
- .filter((d) => d.streamId === streamMessage.streamId)
345
- .sort((a, b) => a.start - b.start);
346
- const existing = existingStreams.find(
347
- (s) => s.stream.streamId === streamMessage.streamId
348
- );
349
- const [newStream, messageChanged] = applyDeltasToStreamMessages(
350
- threadId,
351
- streamMessage,
352
- existing,
353
- deltas
354
- );
355
- newStreams.push(newStream);
356
- if (messageChanged) changed = true;
357
- }
358
- for (const { stream } of existingStreams) {
359
- if (!newStreams.find((s) => s.stream.streamId === stream.streamId)) {
360
- // There's a stream that's no longer active.
361
- changed = true;
362
- }
363
- }
364
- const messages = newStreams
365
- .sort(
366
- (a, b) =>
367
- a.stream.order - b.stream.order ||
368
- a.stream.stepOrder - b.stream.stepOrder
369
- )
370
- .map((s) => s.messages)
371
- .flat();
372
- return [messages, newStreams, changed];
373
- }
374
-
375
- function applyDeltasToStreamMessages(
376
- threadId: string,
377
- streamMessage: StreamMessage,
378
- existing:
379
- | { stream: StreamMessage; cursor: number; messages: MessageDoc[] }
380
- | undefined,
381
- deltas: StreamDelta[]
382
- ): [
383
- { stream: StreamMessage; cursor: number; messages: MessageDoc[] },
384
- boolean,
385
- ] {
386
- let changed = false;
387
- const newStream = {
388
- stream: streamMessage,
389
- cursor: existing?.cursor ?? 0,
390
- messages: existing?.messages ?? [],
391
- };
392
- let parts: TextStreamPart[] = [];
393
- for (const delta of deltas) {
394
- if (delta.parts.length === 0) {
395
- console.warn(`Got delta for stream ${delta.streamId} with no parts`);
396
- continue;
397
- }
398
- if (newStream.cursor !== delta.start) {
399
- if (newStream.cursor >= delta.end) {
400
- console.debug(
401
- `Got duplicate delta for stream ${delta.streamId} at ${delta.start}`
402
- );
403
- continue;
404
- } else if (newStream.cursor < delta.start) {
405
- console.warn(
406
- `Got delta for stream ${delta.streamId} that has a gap ${newStream.cursor} -> ${delta.start}`
407
- );
408
- continue;
409
- } else {
410
- throw new Error(
411
- `Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${newStream.cursor}`
412
- );
413
- }
414
- }
415
- changed = true;
416
- newStream.cursor = delta.end;
417
- parts.push(...delta.parts);
418
- }
419
- if (!changed) {
420
- return [existing ?? newStream, false];
421
- }
422
-
423
- if (!newStream.messages.at(-1)) {
424
- newStream.messages.push(
425
- createStreamingMessage(
426
- threadId,
427
- streamMessage,
428
- parts[0]!,
429
- newStream.messages.length
430
- )
431
- );
432
- parts = parts.slice(1);
433
- }
434
- let currentMessage = newStream.messages.at(-1)!;
435
- let lastContent = getLastContent(currentMessage);
436
- for (const part of parts) {
437
- let contentToAdd:
438
- | TextPart
439
- | ToolCallPart
440
- | { type: "reasoning"; text: string }
441
- | ToolResultPart
442
- | undefined;
443
- const isToolRole = part.type === "source" || part.type === "tool-result";
444
- if (isToolRole !== (currentMessage.message!.role === "tool")) {
445
- currentMessage = createStreamingMessage(
446
- threadId,
447
- streamMessage,
448
- part,
449
- newStream.messages.length
450
- );
451
- lastContent = getLastContent(currentMessage);
452
- newStream.messages.push(currentMessage);
453
- }
454
- switch (part.type) {
455
- case "text-delta":
456
- currentMessage.text += part.textDelta;
457
- if (lastContent?.type === "text") {
458
- lastContent.text += part.textDelta;
459
- } else {
460
- contentToAdd = {
461
- type: "text",
462
- text: part.textDelta,
463
- };
464
- }
465
- break;
466
- case "tool-call-streaming-start":
467
- currentMessage.tool = true;
468
- contentToAdd = {
469
- type: "tool-call",
470
- toolCallId: part.toolCallId,
471
- toolName: part.toolName,
472
- args: "",
473
- };
474
- break;
475
- case "tool-call-delta":
476
- {
477
- currentMessage.tool = true;
478
- if (lastContent?.type !== "tool-call") {
479
- throw new Error("Expected last content to be a tool call");
480
- }
481
- if (typeof lastContent.args !== "string") {
482
- throw new Error("Expected args to be a string");
483
- }
484
- lastContent.args += part.argsTextDelta;
485
- }
486
- break;
487
- case "tool-call":
488
- currentMessage.tool = true;
489
- contentToAdd = part;
490
- break;
491
- case "reasoning":
492
- if (lastContent?.type === "reasoning") {
493
- lastContent.text += part.textDelta;
494
- } else {
495
- contentToAdd = {
496
- type: "reasoning",
497
- text: part.textDelta,
498
- };
499
- }
500
- break;
501
- case "source":
502
- if (!currentMessage.sources) {
503
- currentMessage.sources = [];
504
- }
505
- currentMessage.sources.push(part.source);
506
- break;
507
- case "tool-result":
508
- contentToAdd = part;
509
- break;
510
- default:
511
- console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
512
- break;
513
- }
514
- if (contentToAdd) {
515
- if (!currentMessage.message!.content) {
516
- currentMessage.message!.content = [];
517
- }
518
- if (!Array.isArray(currentMessage.message?.content)) {
519
- throw new Error("Expected message content to be an array");
520
- }
521
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
522
- currentMessage.message.content.push(contentToAdd as any);
523
- lastContent = contentToAdd;
524
- }
525
- }
526
- return [newStream, true];
527
- }
528
-
529
- function getLastContent(message: MessageDoc) {
530
- if (Array.isArray(message.message?.content)) {
531
- return message.message.content.at(-1);
532
- }
533
- return undefined;
534
- }
535
-
536
- function createStreamingMessage(
537
- threadId: string,
538
- message: StreamMessage,
539
- part: TextStreamPart,
540
- index: number
541
- ): MessageDoc {
542
- const { streamId, ...rest } = message;
543
- const metadata: MessageDoc = {
544
- _id: `${streamId}-${index}`,
545
- _creationTime: Date.now(),
546
- status: "pending",
547
- threadId,
548
- tool: false,
549
- ...rest,
550
- };
551
- switch (part.type) {
552
- case "text-delta":
553
- return {
554
- ...metadata,
555
- message: {
556
- role: "assistant",
557
- content: [{ type: "text", text: part.textDelta }],
558
- },
559
- text: part.textDelta,
560
- };
561
- case "tool-call-streaming-start":
562
- return {
563
- ...metadata,
564
- tool: true,
565
- message: {
566
- role: "assistant",
567
- content: [
568
- {
569
- type: "tool-call",
570
- toolName: part.toolName,
571
- toolCallId: part.toolCallId,
572
- args: "", // when it's a string, it's a partial call
573
- },
574
- ],
575
- },
576
- };
577
- case "reasoning":
578
- return {
579
- ...metadata,
580
- message: {
581
- role: "assistant",
582
- content: [{ type: "reasoning", text: part.textDelta }],
583
- },
584
- reasoning: part.textDelta,
585
- };
586
- case "source":
587
- console.warn("Received source part first??");
588
- return {
589
- ...metadata,
590
- tool: true,
591
- message: { role: "tool", content: [] },
592
- sources: [part.source],
593
- };
594
- case "tool-call":
595
- return {
596
- ...metadata,
597
- tool: true,
598
- message: { role: "assistant", content: [part] },
599
- };
600
- case "tool-call-delta":
601
- console.warn("Received tool call delta part first??");
602
- return {
603
- ...metadata,
604
- tool: true,
605
- message: {
606
- role: "assistant",
607
- content: [
608
- {
609
- type: "tool-call",
610
- toolCallId: part.toolCallId,
611
- toolName: part.toolName,
612
- args: part.argsTextDelta,
613
- },
614
- ],
615
- },
616
- };
617
- case "tool-result":
618
- return {
619
- ...metadata,
620
- tool: true,
621
- message: { role: "tool", content: [part] },
622
- };
623
- default:
624
- throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
625
- }
626
- }
627
-
628
- type ThreadQuery<
629
- Args = unknown,
630
- M extends MessageDoc = MessageDoc,
631
- > = FunctionReference<
632
- "query",
633
- "public",
634
- {
635
- threadId: string;
636
- paginationOpts: PaginationOptions;
637
- // TODO: will this allow passing a function that doesn't have this param?
638
- /**
639
- * If { stream: true } is passed, it will also query for stream deltas.
640
- * In order for this to work, the query must take as an argument streamArgs.
641
- */
642
- streamArgs?: StreamArgs;
643
- } & Args,
644
- PaginationResult<M> & { streams?: SyncStreamsReturnValue }
645
- >;
646
-
647
- type ThreadStreamQuery<
648
- Args = Record<string, unknown>,
649
- M extends MessageDoc = MessageDoc,
650
- > = FunctionReference<
651
- "query",
652
- "public",
653
- {
654
- threadId: string;
655
- paginationOpts: PaginationOptions;
656
- streamArgs?: StreamArgs; // required for stream query
657
- } & Args,
658
- PaginationResult<M> & { streams: SyncStreamsReturnValue }
659
- >;
660
-
661
- type ThreadMessagesArgs<Query extends ThreadQuery<unknown, MessageDoc>> =
662
- Query extends ThreadQuery<unknown, MessageDoc>
663
- ? Expand<BetterOmit<FunctionArgs<Query>, "paginationOpts" | "streamArgs">>
664
- : never;
665
-
666
- type ThreadMessagesResult<Query extends ThreadQuery<unknown, MessageDoc>> =
667
- Query extends ThreadQuery<unknown, infer M> ? M : never;
668
-
669
- // TODO: pass in the messages we need to watch? that way it can be consistent..
670
-
671
- // export function streamMessagesToUIMessages(
672
- // messages: StreamsChunk[],
673
- // keyOrder: string[]
674
- // ): UIMessageOrdered[] {
675
- // const uiMessagesByMessageId: Record<string, UIMessageOrdered[]> = {};
676
- // for (const message of messages) {
677
- // if (message.deltas.length === 0) {
678
- // continue;
679
- // }
680
- // if (!uiMessagesByMessageId[message.key]) {
681
- // uiMessagesByMessageId[message.key] = [];
682
- // }
683
- // if (uiMessagesByMessageId[message.key].length === 0) {
684
- // uiMessagesByMessageId[message.key] = [
685
- // createUIMessageFromPart(message.deltas[0], {
686
- // id: message.key,
687
- // createdAt: new Date(),
688
- // }),
689
- // ];
690
- // }
691
- // const currentMessage = uiMessagesByMessageId[message.key].at(-1)!;
692
- // const lastPart = currentMessage.parts.at(-1);
693
- // for (const delta of message.deltas) {
694
- // switch (delta.type) {
695
- // case "text-delta":
696
- // currentMessage.content += delta.textDelta;
697
- // if (lastPart?.type === "text") {
698
- // lastPart.text += delta.textDelta;
699
- // } else {
700
- // currentMessage.parts.push({
701
- // type: "text",
702
- // text: delta.textDelta,
703
- // });
704
- // }
705
- // break;
706
- // case "tool-call-delta": {
707
- // let lastToolInvocation: ToolInvocationUIPart | undefined;
708
- // for (let i = currentMessage.parts.length - 1; i >= 0; i--) {
709
- // const part = currentMessage.parts[i];
710
- // if (
711
- // part.type === "tool-invocation" &&
712
- // part.toolInvocation.state === "partial-call"
713
- // ) {
714
- // lastToolInvocation = part;
715
- // break;
716
- // }
717
- // }
718
- // if (lastToolInvocation) {
719
- // lastToolInvocation.toolInvocation = {
720
- // ...lastToolInvocation.toolInvocation,
721
- // state: "call",
722
- // };
723
- // break;
724
- // }
725
- // console.error(
726
- // `Received a tool call delta without a previous tool invocation: ${JSON.stringify(currentMessage.parts)}, creating one anyways...`
727
- // );
728
- // }
729
- // // fallthrough
730
- // case "tool-call-streaming-start":
731
- // currentMessage.parts.push({
732
- // type: "tool-invocation",
733
- // toolInvocation: {
734
- // toolCallId: delta.toolCallId,
735
- // toolName: delta.toolName,
736
- // args: {},
737
- // state: "partial-call",
738
- // step:
739
- // currentMessage.parts.filter(
740
- // (part) => part.type === "tool-invocation"
741
- // ).length + 1,
742
- // },
743
- // });
744
- // break;
745
- // case "tool-call":
746
- // currentMessage.parts.push({
747
- // type: "tool-invocation",
748
- // toolInvocation: {
749
- // toolCallId: delta.toolCallId,
750
- // toolName: delta.toolName,
751
- // args: delta.args,
752
- // state: "call",
753
- // step:
754
- // currentMessage.parts.filter(
755
- // (part) => part.type === "tool-invocation"
756
- // ).length + 1,
757
- // },
758
- // });
759
- // break;
760
- // case "reasoning":
761
- // if (lastPart?.type === "reasoning") {
762
- // lastPart.reasoning += delta.textDelta;
763
- // } else {
764
- // currentMessage.parts.push({
765
- // type: "reasoning",
766
- // reasoning: delta.textDelta,
767
- // details: [],
768
- // });
769
- // }
770
- // break;
771
- // case "source":
772
- // currentMessage.parts.push({
773
- // type: "source",
774
- // source: delta.source,
775
- // });
776
- // break;
777
- // default:
778
- // console.warn(`Received unexpected part: ${JSON.stringify(delta)}`);
779
- // break;
780
- // }
781
- // }
782
- // }
783
- // return keyOrder.map((key) => uiMessagesByMessageId[key]).flat();
784
- // }
785
-
786
223
  if (typeof window === "undefined") {
787
224
  throw new Error("this is frontend code, but it's running somewhere else!");
788
225
  }
@@ -849,7 +286,7 @@ export function useStreamingText(
849
286
  setLoading(false);
850
287
  }
851
288
  },
852
- [threadId, token]
289
+ [threadId, token, url]
853
290
  );
854
291
  return [{ text, loading, error }, readStream] as const;
855
292
  }