@convex-dev/agent 0.1.3 → 0.1.4

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 (108) hide show
  1. package/dist/commonjs/client/index.d.ts +6 -5
  2. package/dist/commonjs/client/index.d.ts.map +1 -1
  3. package/dist/commonjs/client/index.js +37 -11
  4. package/dist/commonjs/client/index.js.map +1 -1
  5. package/dist/commonjs/client/streaming.d.ts +2 -2
  6. package/dist/commonjs/client/streaming.d.ts.map +1 -1
  7. package/dist/commonjs/client/streaming.js +2 -1
  8. package/dist/commonjs/client/streaming.js.map +1 -1
  9. package/dist/commonjs/component/messages.d.ts +3 -3
  10. package/dist/commonjs/component/messages.d.ts.map +1 -1
  11. package/dist/commonjs/component/messages.js +16 -14
  12. package/dist/commonjs/component/messages.js.map +1 -1
  13. package/dist/commonjs/component/streams.d.ts.map +1 -1
  14. package/dist/commonjs/component/streams.js +1 -4
  15. package/dist/commonjs/component/streams.js.map +1 -1
  16. package/dist/commonjs/component/users.d.ts +3 -0
  17. package/dist/commonjs/component/users.d.ts.map +1 -1
  18. package/dist/commonjs/component/users.js +11 -1
  19. package/dist/commonjs/component/users.js.map +1 -1
  20. package/dist/commonjs/component/vector/index.d.ts +3 -1
  21. package/dist/commonjs/component/vector/index.d.ts.map +1 -1
  22. package/dist/commonjs/component/vector/index.js +6 -2
  23. package/dist/commonjs/component/vector/index.js.map +1 -1
  24. package/dist/commonjs/react/deltas.d.ts +29 -0
  25. package/dist/commonjs/react/deltas.d.ts.map +1 -0
  26. package/dist/commonjs/react/deltas.js +267 -0
  27. package/dist/commonjs/react/deltas.js.map +1 -0
  28. package/dist/commonjs/react/index.d.ts +4 -49
  29. package/dist/commonjs/react/index.d.ts.map +1 -1
  30. package/dist/commonjs/react/index.js +15 -446
  31. package/dist/commonjs/react/index.js.map +1 -1
  32. package/dist/commonjs/react/optimisticallySendMessage.d.ts +8 -0
  33. package/dist/commonjs/react/optimisticallySendMessage.d.ts.map +1 -0
  34. package/dist/commonjs/react/optimisticallySendMessage.js +40 -0
  35. package/dist/commonjs/react/optimisticallySendMessage.js.map +1 -0
  36. package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
  37. package/dist/commonjs/react/toUIMessages.js +4 -2
  38. package/dist/commonjs/react/toUIMessages.js.map +1 -1
  39. package/dist/commonjs/react/types.d.ts +26 -0
  40. package/dist/commonjs/react/types.d.ts.map +1 -0
  41. package/dist/commonjs/react/types.js +2 -0
  42. package/dist/commonjs/react/types.js.map +1 -0
  43. package/dist/commonjs/react/useSmoothText.d.ts +20 -0
  44. package/dist/commonjs/react/useSmoothText.d.ts.map +1 -0
  45. package/dist/commonjs/react/useSmoothText.js +50 -0
  46. package/dist/commonjs/react/useSmoothText.js.map +1 -0
  47. package/dist/esm/client/index.d.ts +6 -5
  48. package/dist/esm/client/index.d.ts.map +1 -1
  49. package/dist/esm/client/index.js +37 -11
  50. package/dist/esm/client/index.js.map +1 -1
  51. package/dist/esm/client/streaming.d.ts +2 -2
  52. package/dist/esm/client/streaming.d.ts.map +1 -1
  53. package/dist/esm/client/streaming.js +2 -1
  54. package/dist/esm/client/streaming.js.map +1 -1
  55. package/dist/esm/component/messages.d.ts +3 -3
  56. package/dist/esm/component/messages.d.ts.map +1 -1
  57. package/dist/esm/component/messages.js +16 -14
  58. package/dist/esm/component/messages.js.map +1 -1
  59. package/dist/esm/component/streams.d.ts.map +1 -1
  60. package/dist/esm/component/streams.js +1 -4
  61. package/dist/esm/component/streams.js.map +1 -1
  62. package/dist/esm/component/users.d.ts +3 -0
  63. package/dist/esm/component/users.d.ts.map +1 -1
  64. package/dist/esm/component/users.js +11 -1
  65. package/dist/esm/component/users.js.map +1 -1
  66. package/dist/esm/component/vector/index.d.ts +3 -1
  67. package/dist/esm/component/vector/index.d.ts.map +1 -1
  68. package/dist/esm/component/vector/index.js +6 -2
  69. package/dist/esm/component/vector/index.js.map +1 -1
  70. package/dist/esm/react/deltas.d.ts +29 -0
  71. package/dist/esm/react/deltas.d.ts.map +1 -0
  72. package/dist/esm/react/deltas.js +267 -0
  73. package/dist/esm/react/deltas.js.map +1 -0
  74. package/dist/esm/react/index.d.ts +4 -49
  75. package/dist/esm/react/index.d.ts.map +1 -1
  76. package/dist/esm/react/index.js +15 -446
  77. package/dist/esm/react/index.js.map +1 -1
  78. package/dist/esm/react/optimisticallySendMessage.d.ts +8 -0
  79. package/dist/esm/react/optimisticallySendMessage.d.ts.map +1 -0
  80. package/dist/esm/react/optimisticallySendMessage.js +40 -0
  81. package/dist/esm/react/optimisticallySendMessage.js.map +1 -0
  82. package/dist/esm/react/toUIMessages.d.ts.map +1 -1
  83. package/dist/esm/react/toUIMessages.js +4 -2
  84. package/dist/esm/react/toUIMessages.js.map +1 -1
  85. package/dist/esm/react/types.d.ts +26 -0
  86. package/dist/esm/react/types.d.ts.map +1 -0
  87. package/dist/esm/react/types.js +2 -0
  88. package/dist/esm/react/types.js.map +1 -0
  89. package/dist/esm/react/useSmoothText.d.ts +20 -0
  90. package/dist/esm/react/useSmoothText.d.ts.map +1 -0
  91. package/dist/esm/react/useSmoothText.js +50 -0
  92. package/dist/esm/react/useSmoothText.js.map +1 -0
  93. package/package.json +4 -1
  94. package/src/client/index.ts +41 -23
  95. package/src/client/streaming.ts +3 -2
  96. package/src/component/_generated/api.d.ts +2 -2
  97. package/src/component/messages.ts +26 -15
  98. package/src/component/streams.ts +3 -4
  99. package/src/component/users.ts +12 -0
  100. package/src/component/vector/index.ts +9 -3
  101. package/src/react/deltas.test.ts +298 -0
  102. package/src/react/deltas.ts +340 -0
  103. package/src/react/index.ts +26 -589
  104. package/src/react/optimisticallySendMessage.ts +46 -0
  105. package/src/react/toUIMessages.test.ts +301 -0
  106. package/src/react/toUIMessages.ts +6 -2
  107. package/src/react/types.ts +52 -0
  108. package/src/react/useSmoothText.ts +76 -0
@@ -0,0 +1,26 @@
1
+ import type { BetterOmit, Expand } from "convex-helpers";
2
+ import type { FunctionArgs, FunctionReference, PaginationOptions, PaginationResult } from "convex/server";
3
+ import type { MessageDoc } from "../client";
4
+ import type { SyncStreamsReturnValue } from "../client/types";
5
+ import type { StreamArgs } from "../validators";
6
+ export type ThreadQuery<Args = unknown, M extends MessageDoc = MessageDoc> = FunctionReference<"query", "public", {
7
+ threadId: string;
8
+ paginationOpts: PaginationOptions;
9
+ /**
10
+ * If { stream: true } is passed, it will also query for stream deltas.
11
+ * In order for this to work, the query must take as an argument streamArgs.
12
+ */
13
+ streamArgs?: StreamArgs;
14
+ } & Args, PaginationResult<M> & {
15
+ streams?: SyncStreamsReturnValue;
16
+ }>;
17
+ export type ThreadStreamQuery<Args = Record<string, unknown>, M extends MessageDoc = MessageDoc> = FunctionReference<"query", "public", {
18
+ threadId: string;
19
+ paginationOpts: PaginationOptions;
20
+ streamArgs?: StreamArgs;
21
+ } & Args, PaginationResult<M> & {
22
+ streams: SyncStreamsReturnValue;
23
+ }>;
24
+ export type ThreadMessagesArgs<Query extends ThreadQuery<unknown, MessageDoc>> = Query extends ThreadQuery<unknown, MessageDoc> ? Expand<BetterOmit<FunctionArgs<Query>, "paginationOpts" | "streamArgs">> : never;
25
+ export type ThreadMessagesResult<Query extends ThreadQuery<unknown, MessageDoc>> = Query extends ThreadQuery<unknown, infer M> ? M : never;
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,MAAM,WAAW,CACrB,IAAI,GAAG,OAAO,EACd,CAAC,SAAS,UAAU,GAAG,UAAU,IAC/B,iBAAiB,CACnB,OAAO,EACP,QAAQ,EACR;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,iBAAiB,CAAC;IAElC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,EACR,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAC3D,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAC3B,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,CAAC,SAAS,UAAU,GAAG,UAAU,IAC/B,iBAAiB,CACnB,OAAO,EACP,QAAQ,EACR;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,EACR,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,OAAO,EAAE,sBAAsB,CAAA;CAAE,CAC1D,CAAC;AAEF,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,IAC3E,KAAK,SAAS,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,GAC1C,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,YAAY,CAAC,CAAC,GACxE,KAAK,CAAC;AAEZ,MAAM,MAAM,oBAAoB,CAC9B,KAAK,SAAS,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,IAC5C,KAAK,SAAS,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/react/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A hook that smoothly displays text as it is streamed.
3
+ *
4
+ * @param text The text to display. Pass in the full text each time.
5
+ * @param charsPerSec The number of characters to display per second.
6
+ * @returns A tuple of the visible text and the state of the smooth text,
7
+ * including the current cursor position and whether it's still streaming.
8
+ * This allows you to decide if it's too far behind and you want to adjust
9
+ * the charsPerSec or just prefer the full text.
10
+ */
11
+ export declare function useSmoothText(text: string, { charsPerSec, }?: {
12
+ /**
13
+ * The number of characters to display per second.
14
+ */
15
+ charsPerSec?: number;
16
+ }): [string, {
17
+ cursor: number;
18
+ isStreaming: boolean;
19
+ }];
20
+ //# sourceMappingURL=useSmoothText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSmoothText.d.ts","sourceRoot":"","sources":["../../../src/react/useSmoothText.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,EACE,WAAiB,GAClB,GAAE;IACD;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAkDpD"}
@@ -0,0 +1,50 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ const FPS = 20;
3
+ const MS_PER_FRAME = 1000 / FPS;
4
+ const MAX_TIME_JUMP_MS = 250;
5
+ /**
6
+ * A hook that smoothly displays text as it is streamed.
7
+ *
8
+ * @param text The text to display. Pass in the full text each time.
9
+ * @param charsPerSec The number of characters to display per second.
10
+ * @returns A tuple of the visible text and the state of the smooth text,
11
+ * including the current cursor position and whether it's still streaming.
12
+ * This allows you to decide if it's too far behind and you want to adjust
13
+ * the charsPerSec or just prefer the full text.
14
+ */
15
+ export function useSmoothText(text, { charsPerSec = 256, } = {}) {
16
+ const [visibleText, setVisibleText] = useState(text);
17
+ const smoothState = useRef({
18
+ tick: Date.now() + (text.length * 1000) / charsPerSec,
19
+ cursor: text.length,
20
+ start: Date.now(),
21
+ initialLength: text.length,
22
+ charsPerMs: charsPerSec / 1000,
23
+ });
24
+ const isStreaming = smoothState.current.cursor < text.length;
25
+ useEffect(() => {
26
+ if (!isStreaming) {
27
+ return;
28
+ }
29
+ const latestCharsPerMs = (text.length - smoothState.current.initialLength) /
30
+ (Date.now() - smoothState.current.start);
31
+ // Smooth out the charsPerSec by averaging it with the previous value.
32
+ smoothState.current.charsPerMs = Math.min((2 * latestCharsPerMs + smoothState.current.charsPerMs) / 3, smoothState.current.charsPerMs * 2);
33
+ function update() {
34
+ if (smoothState.current.cursor >= text.length) {
35
+ return;
36
+ }
37
+ const now = Date.now();
38
+ const timeSinceLastUpdate = Math.min(MAX_TIME_JUMP_MS, now - smoothState.current.tick);
39
+ const chars = Math.floor(timeSinceLastUpdate * smoothState.current.charsPerMs);
40
+ smoothState.current.cursor = Math.min(smoothState.current.cursor + chars, text.length);
41
+ smoothState.current.tick = now;
42
+ setVisibleText(text.slice(0, smoothState.current.cursor));
43
+ }
44
+ update();
45
+ const interval = setInterval(update, MS_PER_FRAME);
46
+ return () => clearInterval(interval);
47
+ }, [text, isStreaming, charsPerSec]);
48
+ return [visibleText, { cursor: smoothState.current.cursor, isStreaming }];
49
+ }
50
+ //# sourceMappingURL=useSmoothText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSmoothText.js","sourceRoot":"","sources":["../../../src/react/useSmoothText.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD,MAAM,GAAG,GAAG,EAAE,CAAC;AACf,MAAM,YAAY,GAAG,IAAI,GAAG,GAAG,CAAC;AAChC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,EACE,WAAW,GAAG,GAAG,MAMf,EAAE;IAEN,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,MAAM,CAAC;QACzB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,WAAW;QACrD,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE;QACjB,aAAa,EAAE,IAAI,CAAC,MAAM;QAC1B,UAAU,EAAE,WAAW,GAAG,IAAI;KAC/B,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,gBAAgB,GACpB,CAAC,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC;YACjD,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,sEAAsE;QACtE,WAAW,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CACvC,CAAC,CAAC,GAAG,gBAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAC3D,WAAW,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,CACnC,CAAC;QAEF,SAAS,MAAM;YACb,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9C,OAAO;YACT,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAClC,gBAAgB,EAChB,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAC/B,CAAC;YACF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CACtB,mBAAmB,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CACrD,CAAC;YACF,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CACnC,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,EAClC,IAAI,CAAC,MAAM,CACZ,CAAC;YACF,WAAW,CAAC,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC;YAC/B,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACnD,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAErC,OAAO,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AAC5E,CAAC"}
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "email": "support@convex.dev",
8
8
  "url": "https://github.com/get-convex/agent/issues"
9
9
  },
10
- "version": "0.1.3",
10
+ "version": "0.1.4",
11
11
  "license": "Apache-2.0",
12
12
  "keywords": [
13
13
  "convex",
@@ -105,6 +105,9 @@
105
105
  "chokidar-cli": "^3.0.0",
106
106
  "convex-test": "^0.0.37",
107
107
  "eslint": "^9.24.0",
108
+ "eslint-plugin-jsx-a11y": "^6.8.0",
109
+ "eslint-plugin-react": "^7.34.0",
110
+ "eslint-plugin-react-hooks": "^5.2.0",
108
111
  "globals": "^15.15.0",
109
112
  "prettier": "3.2.5",
110
113
  "typescript": "^5.8.3",
@@ -464,7 +464,15 @@ export class Agent<AgentTools extends ToolSet> {
464
464
  const searchMessages = await ctx.runAction(
465
465
  this.component.messages.searchMessages,
466
466
  {
467
- userId: opts?.searchOtherThreads ? args.userId : undefined,
467
+ searchAllMessagesForUserId: opts?.searchOtherThreads
468
+ ? args.userId ??
469
+ (args.threadId &&
470
+ (
471
+ await ctx.runQuery(this.component.threads.getThread, {
472
+ threadId: args.threadId,
473
+ })
474
+ )?.userId)
475
+ : undefined,
468
476
  threadId: args.threadId,
469
477
  beforeMessageId: args.upToAndIncludingMessageId,
470
478
  ...(await this._searchOptionsWithDefaults(opts, messagesToSearch)),
@@ -752,7 +760,7 @@ export class Agent<AgentTools extends ToolSet> {
752
760
  >(
753
761
  ctx: RunActionCtx,
754
762
  {
755
- userId,
763
+ userId: argsUserId,
756
764
  threadId,
757
765
  usageHandler,
758
766
  tools: threadTools,
@@ -773,11 +781,12 @@ export class Agent<AgentTools extends ToolSet> {
773
781
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
774
782
  GenerationOutputMetadata
775
783
  > {
776
- const { args: aiArgs, messageId } = await this._saveMessagesAndFetchContext(
777
- ctx,
778
- args,
779
- { userId, threadId, ...options }
780
- );
784
+ const context = await this._saveMessagesAndFetchContext(ctx, args, {
785
+ userId: argsUserId,
786
+ threadId,
787
+ ...options,
788
+ });
789
+ const { args: aiArgs, messageId, userId } = context;
781
790
  const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
782
791
  const tools = wrapTools(
783
792
  toolCtx,
@@ -848,7 +857,7 @@ export class Agent<AgentTools extends ToolSet> {
848
857
  >(
849
858
  ctx: RunActionCtx,
850
859
  {
851
- userId,
860
+ userId: argsUserId,
852
861
  threadId,
853
862
  usageHandler,
854
863
  /**
@@ -891,11 +900,11 @@ export class Agent<AgentTools extends ToolSet> {
891
900
  GenerationOutputMetadata
892
901
  > {
893
902
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
894
- userId,
903
+ userId: argsUserId,
895
904
  threadId,
896
905
  ...options,
897
906
  });
898
- const { args: aiArgs, messageId, order, stepOrder } = context;
907
+ const { args: aiArgs, messageId, order, stepOrder, userId } = context;
899
908
  const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
900
909
  const tools = wrapTools(
901
910
  toolCtx,
@@ -993,7 +1002,7 @@ export class Agent<AgentTools extends ToolSet> {
993
1002
  ctx: RunActionCtx | RunMutationCtx,
994
1003
  args: T,
995
1004
  {
996
- userId,
1005
+ userId: argsUserId,
997
1006
  threadId,
998
1007
  contextOptions,
999
1008
  storageOptions,
@@ -1003,6 +1012,7 @@ export class Agent<AgentTools extends ToolSet> {
1003
1012
  } & Options
1004
1013
  ): Promise<{
1005
1014
  args: T & { model: LanguageModelV1 };
1015
+ userId: string | undefined;
1006
1016
  messageId: string | undefined;
1007
1017
  order: number | undefined;
1008
1018
  stepOrder: number | undefined;
@@ -1013,6 +1023,11 @@ export class Agent<AgentTools extends ToolSet> {
1013
1023
  const messages = args.promptMessageId
1014
1024
  ? []
1015
1025
  : promptOrMessagesToCoreMessages(args);
1026
+ const userId =
1027
+ argsUserId ??
1028
+ (threadId &&
1029
+ (await ctx.runQuery(this.component.threads.getThread, { threadId }))
1030
+ ?.userId);
1016
1031
  assert(
1017
1032
  !args.promptMessageId || !(args.prompt || args.messages),
1018
1033
  "you can't specify a prompt or message if you specify a promptMessageId"
@@ -1063,6 +1078,7 @@ export class Agent<AgentTools extends ToolSet> {
1063
1078
  ...messages,
1064
1079
  ],
1065
1080
  } as T & { model: LanguageModelV1 },
1081
+ userId,
1066
1082
  messageId,
1067
1083
  order,
1068
1084
  stepOrder,
@@ -1079,7 +1095,7 @@ export class Agent<AgentTools extends ToolSet> {
1079
1095
  async generateObject<T>(
1080
1096
  ctx: RunActionCtx,
1081
1097
  {
1082
- userId,
1098
+ userId: argsUserId,
1083
1099
  threadId,
1084
1100
  usageHandler,
1085
1101
  }: { userId?: string; threadId?: string; usageHandler?: UsageHandler },
@@ -1093,11 +1109,12 @@ export class Agent<AgentTools extends ToolSet> {
1093
1109
  */
1094
1110
  options?: Options
1095
1111
  ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
1096
- const { args: aiArgs, messageId } = await this._saveMessagesAndFetchContext(
1097
- ctx,
1098
- args,
1099
- { userId, threadId, ...options }
1100
- );
1112
+ const context = await this._saveMessagesAndFetchContext(ctx, args, {
1113
+ userId: argsUserId,
1114
+ threadId,
1115
+ ...options,
1116
+ });
1117
+ const { args: aiArgs, messageId, userId } = context;
1101
1118
  const trackUsage = usageHandler ?? this.options.usageHandler;
1102
1119
  const saveOutputMessages =
1103
1120
  options?.storageOptions?.saveOutputMessages ??
@@ -1150,7 +1167,7 @@ export class Agent<AgentTools extends ToolSet> {
1150
1167
  async streamObject<T>(
1151
1168
  ctx: RunActionCtx,
1152
1169
  {
1153
- userId,
1170
+ userId: argsUserId,
1154
1171
  threadId,
1155
1172
  usageHandler,
1156
1173
  }: { userId?: string; threadId?: string; usageHandler?: UsageHandler },
@@ -1167,11 +1184,12 @@ export class Agent<AgentTools extends ToolSet> {
1167
1184
  StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
1168
1185
  > {
1169
1186
  // TODO: unify all this shared code between all the generate* and stream* functions
1170
- const { args: aiArgs, messageId } = await this._saveMessagesAndFetchContext(
1171
- ctx,
1172
- args,
1173
- { userId, threadId, ...options }
1174
- );
1187
+ const context = await this._saveMessagesAndFetchContext(ctx, args, {
1188
+ userId: argsUserId,
1189
+ threadId,
1190
+ ...options,
1191
+ });
1192
+ const { args: aiArgs, messageId, userId } = context;
1175
1193
  const trackUsage = usageHandler ?? this.options.usageHandler;
1176
1194
  const saveOutputMessages =
1177
1195
  options?.storageOptions?.saveOutputMessages ??
@@ -9,7 +9,7 @@ export type StreamingOptions = {
9
9
  * Note: this is not a guarantee that every delta will be exactly one line.
10
10
  * E.g. if "line" is specified, it won't save any deltas until it encounters
11
11
  * a newline character.
12
- * Defaults to "word".
12
+ * Defaults to a regex that chunks by punctuation followed by whitespace.
13
13
  */
14
14
  chunking?: "word" | "line" | RegExp | ChunkDetector;
15
15
  /**
@@ -19,7 +19,8 @@ export type StreamingOptions = {
19
19
  throttleMs?: number;
20
20
  };
21
21
  export const DEFAULT_STREAMING_OPTIONS = {
22
- chunking: "word",
22
+ // This chunks by sentences / clauses. Punctuation followed by whitespace.
23
+ chunking: /[\p{P}\s]/u,
23
24
  throttleMs: 250,
24
25
  } satisfies StreamingOptions;
25
26
 
@@ -1375,9 +1375,9 @@ export type Mounts = {
1375
1375
  beforeMessageId?: string;
1376
1376
  limit: number;
1377
1377
  messageRange?: { after: number; before: number };
1378
+ searchAllMessagesForUserId?: string;
1378
1379
  text?: string;
1379
1380
  threadId?: string;
1380
- userId?: string;
1381
1381
  vector?: Array<number>;
1382
1382
  vectorModel?: string;
1383
1383
  vectorScoreThreshold?: number;
@@ -1529,9 +1529,9 @@ export type Mounts = {
1529
1529
  {
1530
1530
  beforeMessageId?: string;
1531
1531
  limit: number;
1532
+ searchAllMessagesForUserId?: string;
1532
1533
  text: string;
1533
1534
  threadId?: string;
1534
- userId?: string;
1535
1535
  },
1536
1536
  Array<{
1537
1537
  _creationTime: number;
@@ -454,19 +454,22 @@ export const getThreadMessages = query({
454
454
 
455
455
  export const searchMessages = action({
456
456
  args: {
457
- userId: v.optional(v.string()),
458
457
  threadId: v.optional(v.id("threads")),
458
+ searchAllMessagesForUserId: v.optional(v.string()),
459
459
  beforeMessageId: v.optional(v.id("messages")),
460
460
  ...vSearchOptions.fields,
461
461
  },
462
462
  returns: v.array(vMessageDoc),
463
463
  handler: async (ctx, args): Promise<MessageDoc[]> => {
464
- assert(args.userId || args.threadId, "Specify userId or threadId");
464
+ assert(
465
+ args.searchAllMessagesForUserId || args.threadId,
466
+ "Specify userId or threadId"
467
+ );
465
468
  const limit = args.limit;
466
469
  let textSearchMessages: MessageDoc[] | undefined;
467
470
  if (args.text) {
468
471
  textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
469
- userId: args.userId,
472
+ searchAllMessagesForUserId: args.searchAllMessagesForUserId,
470
473
  threadId: args.threadId,
471
474
  text: args.text,
472
475
  limit,
@@ -483,7 +486,7 @@ export const searchMessages = action({
483
486
  dimension,
484
487
  model: args.vectorModel ?? "unknown",
485
488
  table: "messages",
486
- userId: args.userId,
489
+ searchAllMessagesForUserId: args.searchAllMessagesForUserId,
487
490
  threadId: args.threadId,
488
491
  limit,
489
492
  })
@@ -503,7 +506,7 @@ export const searchMessages = action({
503
506
  const messages: MessageDoc[] = await ctx.runQuery(
504
507
  internal.messages._fetchSearchMessages,
505
508
  {
506
- userId: args.userId,
509
+ searchAllMessagesForUserId: args.searchAllMessagesForUserId,
507
510
  threadId: args.threadId,
508
511
  vectorIds,
509
512
  textSearchMessages: textSearchMessages?.filter(
@@ -522,9 +525,9 @@ export const searchMessages = action({
522
525
 
523
526
  export const _fetchSearchMessages = internalQuery({
524
527
  args: {
525
- userId: v.optional(v.string()),
526
528
  threadId: v.optional(v.id("threads")),
527
529
  vectorIds: v.array(vVectorId),
530
+ searchAllMessagesForUserId: v.optional(v.string()),
528
531
  textSearchMessages: v.optional(v.array(vMessageDoc)),
529
532
  messageRange: v.object({ before: v.number(), after: v.number() }),
530
533
  beforeMessageId: v.optional(v.id("messages")),
@@ -534,8 +537,11 @@ export const _fetchSearchMessages = internalQuery({
534
537
  handler: async (ctx, args): Promise<MessageDoc[]> => {
535
538
  const beforeMessage =
536
539
  args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
537
- const { userId, threadId } = args;
538
- assert(userId || threadId, "Specify userId or threadId to search");
540
+ const { searchAllMessagesForUserId, threadId } = args;
541
+ assert(
542
+ searchAllMessagesForUserId || threadId,
543
+ "Specify searchAllMessagesForUserId or threadId to search"
544
+ );
539
545
  let messages: MessageDoc[] = (
540
546
  await Promise.all(
541
547
  args.vectorIds.map((embeddingId) =>
@@ -543,9 +549,9 @@ export const _fetchSearchMessages = internalQuery({
543
549
  .query("messages")
544
550
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
545
551
  .filter((q) =>
546
- userId
547
- ? q.eq(q.field("userId"), userId)
548
- : q.eq(q.field("threadId"), threadId)
552
+ searchAllMessagesForUserId
553
+ ? q.eq(q.field("userId"), searchAllMessagesForUserId)
554
+ : q.eq(q.field("threadId"), threadId!)
549
555
  )
550
556
  // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
551
557
  .filter((q) => q.eq(q.field("status"), "success"))
@@ -632,21 +638,26 @@ export const _fetchSearchMessages = internalQuery({
632
638
  export const textSearch = query({
633
639
  args: {
634
640
  threadId: v.optional(v.id("threads")),
635
- userId: v.optional(v.string()),
641
+ searchAllMessagesForUserId: v.optional(v.string()),
636
642
  text: v.string(),
637
643
  limit: v.number(),
638
644
  beforeMessageId: v.optional(v.id("messages")),
639
645
  },
640
646
  handler: async (ctx, args) => {
641
- assert(args.userId || args.threadId, "Specify userId or threadId");
647
+ assert(
648
+ args.searchAllMessagesForUserId || args.threadId,
649
+ "Specify userId or threadId"
650
+ );
642
651
  const beforeMessage =
643
652
  args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
644
653
  const order = beforeMessage?.order;
645
654
  const messages = await ctx.db
646
655
  .query("messages")
647
656
  .withSearchIndex("text_search", (q) =>
648
- args.userId
649
- ? q.search("text", args.text).eq("userId", args.userId)
657
+ args.searchAllMessagesForUserId
658
+ ? q
659
+ .search("text", args.text)
660
+ .eq("userId", args.searchAllMessagesForUserId)
650
661
  : q.search("text", args.text).eq("threadId", args.threadId!)
651
662
  )
652
663
  // Just in case tool messages slip through
@@ -51,10 +51,9 @@ export const listDeltas = query({
51
51
  );
52
52
  totalDeltas += streamDeltas.length;
53
53
  deltas.push(
54
- ...streamDeltas.map((d) => ({
55
- streamId: d.streamId.toString(),
56
- ...pick(d, ["start", "end", "parts"]),
57
- }))
54
+ ...streamDeltas.map((d) =>
55
+ pick(d, ["streamId", "start", "end", "parts"])
56
+ )
58
57
  );
59
58
  if (totalDeltas >= MAX_DELTAS_PER_REQUEST) {
60
59
  break;
@@ -5,6 +5,7 @@ import { internal } from "./_generated/api.js";
5
5
  import {
6
6
  action,
7
7
  internalMutation,
8
+ internalQuery,
8
9
  mutation,
9
10
  MutationCtx,
10
11
  query,
@@ -168,3 +169,14 @@ async function deletePageForUserId(
168
169
  isDone: false,
169
170
  };
170
171
  }
172
+
173
+ export const getThreadUserId = internalQuery({
174
+ args: {
175
+ threadId: v.id("threads"),
176
+ },
177
+ returns: v.union(v.string(), v.null()),
178
+ handler: async (ctx, args) => {
179
+ const thread = await ctx.db.get(args.threadId);
180
+ return thread?.userId ?? null;
181
+ },
182
+ });
@@ -12,6 +12,7 @@ import {
12
12
  import schema from "../schema";
13
13
  import { mergedStream } from "convex-helpers/server/stream";
14
14
  import { stream } from "convex-helpers/server/stream";
15
+ import { Id } from "../_generated/dataModel";
15
16
 
16
17
  export const paginate = query({
17
18
  args: {
@@ -124,7 +125,8 @@ export function searchVectors(
124
125
  model: string;
125
126
  table: string;
126
127
  userId?: string;
127
- threadId?: string;
128
+ threadId?: Id<"threads">;
129
+ searchAllMessagesForUserId?: string;
128
130
  limit?: number;
129
131
  }
130
132
  ) {
@@ -133,8 +135,12 @@ export function searchVectors(
133
135
  vector,
134
136
  // TODO: to support more tables, add more "OR" clauses for each.
135
137
  filter: (q) =>
136
- args.userId
137
- ? q.eq("model_table_userId", [args.model, args.table, args.userId])
138
+ args.searchAllMessagesForUserId
139
+ ? q.eq("model_table_userId", [
140
+ args.model,
141
+ args.table,
142
+ args.searchAllMessagesForUserId,
143
+ ])
138
144
  : q.eq("model_table_threadId", [
139
145
  args.model,
140
146
  args.table,