@convex-dev/agent 0.1.15-alpha.0 → 0.1.15-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 (50) hide show
  1. package/README.md +13 -12
  2. package/dist/client/index.d.ts +494 -173
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +148 -170
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/listMessages.d.ts +22 -0
  7. package/dist/client/listMessages.d.ts.map +1 -0
  8. package/dist/client/listMessages.js +25 -0
  9. package/dist/client/listMessages.js.map +1 -0
  10. package/dist/client/search.d.ts +162 -0
  11. package/dist/client/search.d.ts.map +1 -0
  12. package/dist/client/search.js +113 -0
  13. package/dist/client/search.js.map +1 -0
  14. package/dist/client/streaming.d.ts +17 -3
  15. package/dist/client/streaming.d.ts.map +1 -1
  16. package/dist/client/streaming.js +32 -0
  17. package/dist/client/streaming.js.map +1 -1
  18. package/dist/client/types.d.ts +5 -1
  19. package/dist/client/types.d.ts.map +1 -1
  20. package/dist/component/_generated/api.d.ts +2 -2
  21. package/dist/component/messages.d.ts +3 -3
  22. package/dist/component/messages.d.ts.map +1 -1
  23. package/dist/component/messages.js +12 -7
  24. package/dist/component/messages.js.map +1 -1
  25. package/dist/component/vector/index.js +3 -3
  26. package/dist/component/vector/index.js.map +1 -1
  27. package/dist/react/optimisticallySendMessage.d.ts +1 -0
  28. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  29. package/dist/react/optimisticallySendMessage.js +8 -1
  30. package/dist/react/optimisticallySendMessage.js.map +1 -1
  31. package/dist/react/toUIMessages.d.ts +1 -0
  32. package/dist/react/toUIMessages.d.ts.map +1 -1
  33. package/dist/react/toUIMessages.js +2 -0
  34. package/dist/react/toUIMessages.js.map +1 -1
  35. package/dist/validators.d.ts +41 -52
  36. package/dist/validators.d.ts.map +1 -1
  37. package/dist/validators.js +1 -8
  38. package/dist/validators.js.map +1 -1
  39. package/package.json +6 -4
  40. package/src/client/index.ts +283 -274
  41. package/src/client/listMessages.ts +38 -0
  42. package/src/client/search.ts +172 -0
  43. package/src/client/streaming.ts +49 -2
  44. package/src/client/types.ts +5 -1
  45. package/src/component/_generated/api.d.ts +2 -2
  46. package/src/component/messages.ts +13 -7
  47. package/src/component/vector/index.ts +4 -4
  48. package/src/react/optimisticallySendMessage.ts +11 -1
  49. package/src/react/toUIMessages.ts +3 -0
  50. package/src/validators.ts +2 -10
@@ -1,5 +1,6 @@
1
1
  import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
2
2
  import type {
3
+ AssistantContent,
3
4
  CoreMessage,
4
5
  DeepPartial,
5
6
  FilePart,
@@ -10,6 +11,7 @@ import type {
10
11
  StreamObjectResult,
11
12
  StreamTextResult,
12
13
  ToolSet,
14
+ UserContent,
13
15
  } from "ai";
14
16
  import { generateObject, generateText, streamObject, streamText } from "ai";
15
17
  import { assert } from "convex-helpers";
@@ -34,22 +36,17 @@ import {
34
36
  serializeNewMessagesInStep,
35
37
  serializeObjectResult,
36
38
  } from "../mapping.js";
37
- import {
38
- DEFAULT_MESSAGE_RANGE,
39
- DEFAULT_RECENT_MESSAGES,
40
- extractText,
41
- isTool,
42
- } from "../shared.js";
39
+ import { extractText, isTool } from "../shared.js";
43
40
  import {
44
41
  type MessageWithMetadata,
45
42
  type MessageStatus,
46
43
  type ProviderMetadata,
47
- type SearchOptions,
48
44
  type StreamArgs,
49
45
  type Usage,
50
46
  vMessageWithMetadata,
51
47
  vSafeObjectArgs,
52
48
  vTextArgs,
49
+ type MessageEmbeddings,
53
50
  } from "../validators.js";
54
51
  import { createTool, wrapTools } from "./createTool.js";
55
52
  import {
@@ -77,6 +74,9 @@ import type {
77
74
  UsageHandler,
78
75
  } from "./types.js";
79
76
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
77
+ import { listMessages } from "./listMessages.js";
78
+ import { syncStreams } from "./streaming.js";
79
+ import { fetchContextMessages } from "./search.js";
80
80
 
81
81
  export { storeFile, getFile } from "./files.js";
82
82
  export { serializeDataOrUrl } from "../mapping.js";
@@ -95,12 +95,21 @@ export {
95
95
  vUserMessage,
96
96
  } from "../validators.js";
97
97
  export type { ToolCtx } from "./createTool.js";
98
- export { createTool, extractText, isTool };
98
+ export { filterOutOrphanedToolMessages } from "./search.js";
99
+ export {
100
+ createTool,
101
+ extractText,
102
+ fetchContextMessages,
103
+ isTool,
104
+ listMessages,
105
+ syncStreams,
106
+ };
99
107
  export type {
100
108
  AgentComponent,
101
109
  ContextOptions,
102
110
  MessageDoc,
103
111
  ProviderMetadata,
112
+ RawRequestResponseHandler,
104
113
  StorageOptions,
105
114
  StreamArgs,
106
115
  SyncStreamsReturnValue,
@@ -276,25 +285,18 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
276
285
  threadId: string;
277
286
  thread?: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
278
287
  }> {
279
- const threadDoc = await ctx.runMutation(
280
- this.component.threads.createThread,
281
- {
282
- userId: args?.userId,
283
- title: args?.title,
284
- summary: args?.summary,
285
- }
286
- );
288
+ const threadId = await createThread(ctx, this.component, args);
287
289
  if (!("runAction" in ctx)) {
288
- return { threadId: threadDoc._id };
290
+ return { threadId };
289
291
  }
290
292
  const { thread } = await this.continueThread(ctx, {
291
- threadId: threadDoc._id,
293
+ threadId,
292
294
  userId: args?.userId,
293
295
  usageHandler: args?.usageHandler,
294
296
  tools: args?.tools,
295
297
  });
296
298
  return {
297
- threadId: threadDoc._id,
299
+ threadId,
298
300
  thread,
299
301
  };
300
302
  }
@@ -833,14 +835,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
833
835
  */
834
836
  async saveMessage(
835
837
  ctx: RunMutationCtx,
836
- args: {
837
- threadId: string;
838
- userId?: string;
839
- /**
840
- * Metadata to save with the messages. Each element corresponds to the
841
- * message at the same index.
842
- */
843
- metadata?: Omit<MessageWithMetadata, "message">;
838
+ args: SaveMessageArgs & {
844
839
  /**
845
840
  * If true, it will not generate embeddings for the message.
846
841
  * Useful if you're saving messages in a mutation where you can't run `fetch`.
@@ -848,26 +843,17 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
848
843
  * action later that calls `agent.generateAndSaveEmbeddings`.
849
844
  */
850
845
  skipEmbeddings?: boolean;
851
- } & (
852
- | {
853
- prompt?: undefined;
854
- /**
855
- * The message to save.
856
- */
857
- message: CoreMessage;
858
- }
859
- | {
860
- /*
861
- * The prompt to save with the message.
862
- */
863
- prompt: string;
864
- message?: undefined;
865
- }
866
- )
846
+ }
867
847
  ) {
868
848
  const { lastMessageId, messages } = await this.saveMessages(ctx, {
869
849
  threadId: args.threadId,
870
850
  userId: args.userId,
851
+ embeddings: args.embedding
852
+ ? {
853
+ model: args.embedding.model,
854
+ vectors: [args.embedding.vector],
855
+ }
856
+ : undefined,
871
857
  messages:
872
858
  args.prompt !== undefined
873
859
  ? [{ role: "user", content: args.prompt }]
@@ -880,40 +866,15 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
880
866
 
881
867
  /**
882
868
  * Explicitly save messages associated with the thread (& user if provided)
869
+ * If you have an embedding model set, it will also generate embeddings for
870
+ * the messages.
883
871
  * @param ctx The ctx parameter to a mutation or action.
884
872
  * @param args The messages and context to save
885
873
  * @returns
886
874
  */
887
875
  async saveMessages(
888
876
  ctx: RunMutationCtx | RunActionCtx,
889
- args: {
890
- threadId: string;
891
- userId?: string;
892
- /**
893
- * The message that these messages are in response to. They will be
894
- * the same "order" as this message, at increasing stepOrder(s).
895
- */
896
- promptMessageId?: string;
897
- /**
898
- * The messages to save.
899
- */
900
- messages: CoreMessageMaybeWithId[];
901
- /**
902
- * Metadata to save with the messages. Each element corresponds to the
903
- * message at the same index.
904
- */
905
- metadata?: Omit<MessageWithMetadata, "message">[];
906
- /**
907
- * If false, it will "commit" the messages immediately.
908
- * If true, it will mark them as pending until the final step has finished.
909
- * Defaults to false.
910
- */
911
- pending?: boolean;
912
- /**
913
- * If true, it will fail any pending steps.
914
- * Defaults to false.
915
- */
916
- failPendingSteps?: boolean;
877
+ args: SaveMessagesArgs & {
917
878
  /**
918
879
  * Skip generating embeddings for the messages. Useful if you're
919
880
  * saving messages in a mutation where you can't run `fetch`.
@@ -929,13 +890,15 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
929
890
  let embeddings:
930
891
  | {
931
892
  vectors: (number[] | null)[];
932
- dimension: VectorDimension;
933
893
  model: string;
934
894
  }
935
895
  | undefined;
936
- if (args.skipEmbeddings || !("runAction" in ctx)) {
896
+ const { skipEmbeddings, ...rest } = args;
897
+ if (args.embeddings) {
898
+ embeddings = args.embeddings;
899
+ } else if (skipEmbeddings || !("runAction" in ctx)) {
937
900
  embeddings = undefined;
938
- if (!args.skipEmbeddings && this.options.textEmbedding) {
901
+ if (!skipEmbeddings && this.options.textEmbedding) {
939
902
  console.warn(
940
903
  "You're trying to save messages and generate embeddings, but you're in a mutation. " +
941
904
  "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
@@ -953,33 +916,11 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
953
916
  args.messages
954
917
  );
955
918
  }
956
- const result = await ctx.runMutation(this.component.messages.addMessages, {
957
- threadId: args.threadId,
958
- userId: args.userId,
919
+ return saveMessages(ctx, this.component, {
920
+ ...rest,
959
921
  agentName: this.options.name,
960
- promptMessageId: args.promptMessageId,
961
922
  embeddings,
962
- messages: await Promise.all(
963
- args.messages.map(async (m, i) => {
964
- const { message, fileIds } = await serializeMessage(
965
- ctx,
966
- this.component,
967
- m
968
- );
969
- return {
970
- ...args.metadata?.[i],
971
- message,
972
- fileIds,
973
- } as MessageWithMetadata;
974
- })
975
- ),
976
- failPendingSteps: args.failPendingSteps ?? false,
977
- pending: args.pending ?? false,
978
923
  });
979
- return {
980
- lastMessageId: result.messages.at(-1)!._id,
981
- messages: result.messages,
982
- };
983
924
  }
984
925
 
985
926
  /**
@@ -1001,17 +942,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1001
942
  statuses?: MessageStatus[];
1002
943
  }
1003
944
  ): Promise<PaginationResult<MessageDoc>> {
1004
- if (args.paginationOpts.numItems === 0) {
1005
- return {
1006
- page: [],
1007
- isDone: true,
1008
- continueCursor: args.paginationOpts.cursor ?? "",
1009
- };
1010
- }
1011
- return ctx.runQuery(this.component.messages.listMessagesByThreadId, {
1012
- order: "desc",
1013
- ...args,
1014
- });
945
+ return listMessages(ctx, this.component, args);
1015
946
  }
1016
947
 
1017
948
  /**
@@ -1031,25 +962,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1031
962
  includeStatuses?: ("streaming" | "finished" | "aborted")[];
1032
963
  }
1033
964
  ): Promise<SyncStreamsReturnValue | undefined> {
1034
- if (!args.streamArgs) return undefined;
1035
- if (args.streamArgs.kind === "list") {
1036
- return {
1037
- kind: "list",
1038
- messages: await ctx.runQuery(this.component.streams.list, {
1039
- threadId: args.threadId,
1040
- startOrder: args.streamArgs.startOrder,
1041
- statuses: args.includeStatuses,
1042
- }),
1043
- };
1044
- } else {
1045
- return {
1046
- kind: "deltas",
1047
- deltas: await ctx.runQuery(this.component.streams.listDeltas, {
1048
- threadId: args.threadId,
1049
- cursors: args.streamArgs.cursors,
1050
- }),
1051
- };
1052
- }
965
+ return syncStreams(ctx, this.component, args);
1053
966
  }
1054
967
 
1055
968
  /**
@@ -1076,76 +989,28 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1076
989
  }
1077
990
  ): Promise<MessageDoc[]> {
1078
991
  assert(args.userId || args.threadId, "Specify userId or threadId");
1079
- // Fetch the latest messages from the thread
1080
- let included: Set<string> | undefined;
1081
992
  const opts = this._mergedContextOptions(args.contextOptions);
1082
- const contextMessages: MessageDoc[] = [];
1083
- if (
1084
- args.threadId &&
1085
- (opts.recentMessages !== 0 || args.upToAndIncludingMessageId)
1086
- ) {
1087
- const { page } = await ctx.runQuery(
1088
- this.component.messages.listMessagesByThreadId,
1089
- {
1090
- threadId: args.threadId,
1091
- excludeToolMessages: opts.excludeToolMessages,
1092
- paginationOpts: {
1093
- numItems: opts.recentMessages ?? DEFAULT_RECENT_MESSAGES,
1094
- cursor: null,
1095
- },
1096
- upToAndIncludingMessageId: args.upToAndIncludingMessageId,
1097
- order: "desc",
1098
- statuses: ["success"],
1099
- }
1100
- );
1101
- included = new Set(page.map((m) => m._id));
1102
- contextMessages.push(
1103
- // Reverse since we fetched in descending order
1104
- ...page.reverse()
1105
- );
1106
- }
1107
- if (opts.searchOptions?.textSearch || opts.searchOptions?.vectorSearch) {
1108
- const targetMessage = contextMessages.find(
1109
- (m) => m._id === args.upToAndIncludingMessageId
1110
- )?.message;
1111
- const messagesToSearch = targetMessage ? [targetMessage] : args.messages;
1112
- if (!("runAction" in ctx)) {
1113
- throw new Error("searchUserMessages only works in an action");
1114
- }
1115
- const searchMessages = await ctx.runAction(
1116
- this.component.messages.searchMessages,
1117
- {
1118
- searchAllMessagesForUserId: opts?.searchOtherThreads
1119
- ? args.userId ??
1120
- (args.threadId &&
1121
- (
1122
- await ctx.runQuery(this.component.threads.getThread, {
1123
- threadId: args.threadId,
1124
- })
1125
- )?.userId)
1126
- : undefined,
1127
- threadId: args.threadId,
1128
- beforeMessageId: args.upToAndIncludingMessageId,
1129
- ...(await this._searchOptionsWithEmbeddingAndDefaults(
1130
- ctx,
1131
- { userId: args.userId, threadId: args.threadId },
1132
- opts,
1133
- messagesToSearch
1134
- )),
1135
- }
1136
- );
1137
- // TODO: track what messages we used for context
1138
- contextMessages.unshift(
1139
- ...searchMessages.filter((m) => !included?.has(m._id))
1140
- );
1141
- }
1142
- // Ensure we don't include tool messages without a corresponding tool call
1143
- return filterOutOrphanedToolMessages(
1144
- contextMessages.sort((a, b) =>
1145
- // Sort the raw MessageDocs by order and stepOrder
1146
- a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order
1147
- )
1148
- );
993
+ return fetchContextMessages(ctx, this.component, {
994
+ ...args,
995
+ contextOptions: opts,
996
+ getEmbedding: async (text) => {
997
+ assert("runAction" in ctx);
998
+ assert(
999
+ this.options.textEmbedding,
1000
+ "A textEmbedding model is required to be set on the Agent that you're doing vector search with"
1001
+ );
1002
+ return {
1003
+ vector: (
1004
+ await this.doEmbed(ctx, {
1005
+ userId: args.userId,
1006
+ threadId: args.threadId,
1007
+ values: [text],
1008
+ })
1009
+ ).embeddings[0],
1010
+ vectorModel: this.options.textEmbedding.modelId,
1011
+ };
1012
+ },
1013
+ });
1149
1014
  }
1150
1015
 
1151
1016
  /**
@@ -1158,13 +1023,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1158
1023
  ctx: RunQueryCtx,
1159
1024
  args: { threadId: string }
1160
1025
  ): Promise<ThreadDoc> {
1161
- const thread = await ctx.runQuery(this.component.threads.getThread, {
1162
- threadId: args.threadId,
1163
- });
1164
- if (!thread) {
1165
- throw new Error("Thread not found");
1166
- }
1167
- return thread;
1026
+ return getThreadMetadata(ctx, this.component, args);
1168
1027
  }
1169
1028
 
1170
1029
  /**
@@ -1769,49 +1628,11 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1769
1628
  ...this.options.contextOptions,
1770
1629
  ...opts,
1771
1630
  searchOptions: searchOptions.limit
1772
- ? (searchOptions as SearchOptions)
1631
+ ? (searchOptions as ContextOptions["searchOptions"])
1773
1632
  : undefined,
1774
1633
  };
1775
1634
  }
1776
1635
 
1777
- async _searchOptionsWithEmbeddingAndDefaults(
1778
- ctx: RunActionCtx,
1779
- { userId, threadId }: { userId?: string; threadId?: string },
1780
- contextOptions: ContextOptions,
1781
- messages: CoreMessage[]
1782
- ): Promise<SearchOptions> {
1783
- assert(
1784
- contextOptions.searchOptions?.textSearch ||
1785
- contextOptions.searchOptions?.vectorSearch,
1786
- "searchOptions is required"
1787
- );
1788
- assert(messages.length > 0, "Core messages cannot be empty");
1789
- const text = extractText(messages.at(-1)!);
1790
- const search: SearchOptions = {
1791
- limit: contextOptions.searchOptions?.limit ?? 10,
1792
- messageRange: {
1793
- ...DEFAULT_MESSAGE_RANGE,
1794
- ...contextOptions.searchOptions?.messageRange,
1795
- },
1796
- text: extractText(messages.at(-1)!),
1797
- };
1798
- if (
1799
- contextOptions.searchOptions?.vectorSearch &&
1800
- text &&
1801
- this.options.textEmbedding
1802
- ) {
1803
- search.vector = (
1804
- await this.doEmbed(ctx, {
1805
- threadId,
1806
- userId,
1807
- values: [text],
1808
- })
1809
- ).embeddings[0];
1810
- search.vectorModel = this.options.textEmbedding.modelId;
1811
- }
1812
- return search;
1813
- }
1814
-
1815
1636
  async doEmbed(
1816
1637
  ctx: RunActionCtx,
1817
1638
  options: {
@@ -1823,7 +1644,10 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1823
1644
  }
1824
1645
  ): Promise<{ embeddings: number[][] }> {
1825
1646
  const embedding = this.options.textEmbedding;
1826
- assert(embedding, "textEmbedding is required");
1647
+ assert(
1648
+ embedding,
1649
+ "a textEmbedding model is required to be set on the Agent that you're doing vector search with"
1650
+ );
1827
1651
  const result = await embedding.doEmbed({
1828
1652
  values: options.values,
1829
1653
  abortSignal: options.abortSignal,
@@ -1861,7 +1685,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1861
1685
  return Promise.all(
1862
1686
  messages.map(async (message): Promise<CoreMessage> => {
1863
1687
  if (
1864
- message.role !== "user" ||
1688
+ (message.role !== "user" && message.role !== "assistant") ||
1865
1689
  typeof message.content === "string" ||
1866
1690
  !Array.isArray(message.content)
1867
1691
  ) {
@@ -1871,6 +1695,10 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1871
1695
  const processedContent = await Promise.all(
1872
1696
  message.content.map(async (part) => {
1873
1697
  if (part.type === "image" && part.image instanceof URL) {
1698
+ assert(
1699
+ message.role === "user",
1700
+ "Images can only be in user messages"
1701
+ );
1874
1702
  if (this._isLocalhostUrl(part.image)) {
1875
1703
  const imageData = await this._downloadFile(part.image);
1876
1704
  return {
@@ -1894,11 +1722,17 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1894
1722
  return part;
1895
1723
  })
1896
1724
  );
1897
-
1898
- return {
1899
- ...message,
1900
- content: processedContent,
1901
- };
1725
+ if (message.role === "user") {
1726
+ return {
1727
+ ...message,
1728
+ content: processedContent as UserContent,
1729
+ };
1730
+ } else {
1731
+ return {
1732
+ ...message,
1733
+ content: processedContent as AssistantContent,
1734
+ };
1735
+ }
1902
1736
  })
1903
1737
  );
1904
1738
  }
@@ -2124,31 +1958,206 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
2124
1958
  }
2125
1959
  }
2126
1960
 
2127
- export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
2128
- const toolCallIds = new Set<string>();
2129
- const result: MessageDoc[] = [];
2130
- for (const doc of docs) {
2131
- if (
2132
- doc.message?.role === "assistant" &&
2133
- Array.isArray(doc.message.content)
2134
- ) {
2135
- for (const content of doc.message.content) {
2136
- if (content.type === "tool-call") {
2137
- toolCallIds.add(content.toolCallId);
2138
- }
2139
- }
2140
- result.push(doc);
2141
- } else if (doc.message?.role === "tool") {
2142
- if (doc.message.content.every((c) => toolCallIds.has(c.toolCallId))) {
2143
- result.push(doc);
2144
- } else {
2145
- console.debug("Filtering out orphaned tool message", doc);
2146
- }
2147
- } else {
2148
- result.push(doc);
1961
+ type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
1962
+
1963
+ /**
1964
+ * Create a thread to store messages with an Agent.
1965
+ * @param ctx The context from a mutation or action.
1966
+ * @param component The Agent component, usually `components.agent`.
1967
+ * @param args The associated thread metadata.
1968
+ * @returns The id of the created thread.
1969
+ */
1970
+ export async function createThread(
1971
+ ctx: RunMutationCtx,
1972
+ component: AgentComponent,
1973
+ args?: {
1974
+ userId?: string;
1975
+ title?: string;
1976
+ summary?: string;
1977
+ }
1978
+ ) {
1979
+ const { _id: threadId } = await ctx.runMutation(
1980
+ component.threads.createThread,
1981
+ { userId: args?.userId, title: args?.title, summary: args?.summary }
1982
+ );
1983
+ return threadId;
1984
+ }
1985
+
1986
+ /**
1987
+ * Get the metadata for a thread.
1988
+ * @param ctx A ctx object from a query, mutation, or action.
1989
+ * @param args.threadId The thread to get the metadata for.
1990
+ * @returns The metadata for the thread.
1991
+ */
1992
+ export async function getThreadMetadata(
1993
+ ctx: RunQueryCtx,
1994
+ component: AgentComponent,
1995
+ args: { threadId: string }
1996
+ ): Promise<ThreadDoc> {
1997
+ const thread = await ctx.runQuery(component.threads.getThread, {
1998
+ threadId: args.threadId,
1999
+ });
2000
+ if (!thread) {
2001
+ throw new Error("Thread not found");
2002
+ }
2003
+ return thread;
2004
+ }
2005
+
2006
+ type SaveMessagesArgs = {
2007
+ threadId: string;
2008
+ userId?: string;
2009
+ /**
2010
+ * The message that these messages are in response to. They will be
2011
+ * the same "order" as this message, at increasing stepOrder(s).
2012
+ */
2013
+ promptMessageId?: string;
2014
+ /**
2015
+ * The messages to save.
2016
+ */
2017
+ messages: CoreMessageMaybeWithId[];
2018
+ /**
2019
+ * Metadata to save with the messages. Each element corresponds to the
2020
+ * message at the same index.
2021
+ */
2022
+ metadata?: Omit<MessageWithMetadata, "message">[];
2023
+ /**
2024
+ * If false, it will "commit" the messages immediately.
2025
+ * If true, it will mark them as pending until the final step has finished.
2026
+ * Defaults to false.
2027
+ */
2028
+ pending?: boolean;
2029
+ /**
2030
+ * If true, it will fail any pending steps.
2031
+ * Defaults to false.
2032
+ */
2033
+ failPendingSteps?: boolean;
2034
+ /**
2035
+ * The embeddings to save with the messages.
2036
+ */
2037
+ embeddings?: Omit<MessageEmbeddings, "dimension">;
2038
+ };
2039
+
2040
+ /**
2041
+ * Explicitly save messages associated with the thread (& user if provided)
2042
+ */
2043
+ export async function saveMessages(
2044
+ ctx: RunMutationCtx,
2045
+ component: AgentComponent,
2046
+ args: SaveMessagesArgs & {
2047
+ /**
2048
+ * The agent name to associate with the messages.
2049
+ */
2050
+ agentName?: string;
2051
+ }
2052
+ ) {
2053
+ let embeddings: MessageEmbeddings | undefined;
2054
+ if (args.embeddings) {
2055
+ const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
2056
+ if (dimension) {
2057
+ validateVectorDimension(dimension);
2058
+ embeddings = {
2059
+ model: args.embeddings.model,
2060
+ dimension,
2061
+ vectors: args.embeddings.vectors,
2062
+ };
2149
2063
  }
2150
2064
  }
2151
- return result;
2065
+ const result = await ctx.runMutation(component.messages.addMessages, {
2066
+ threadId: args.threadId,
2067
+ userId: args.userId,
2068
+ agentName: args.agentName,
2069
+ promptMessageId: args.promptMessageId,
2070
+ embeddings,
2071
+ messages: await Promise.all(
2072
+ args.messages.map(async (m, i) => {
2073
+ const { message, fileIds } = await serializeMessage(ctx, component, m);
2074
+ return {
2075
+ ...args.metadata?.[i],
2076
+ message,
2077
+ fileIds,
2078
+ } as MessageWithMetadata;
2079
+ })
2080
+ ),
2081
+ failPendingSteps: args.failPendingSteps ?? false,
2082
+ pending: args.pending ?? false,
2083
+ });
2084
+ return {
2085
+ lastMessageId: result.messages.at(-1)!._id,
2086
+ messages: result.messages,
2087
+ };
2152
2088
  }
2153
2089
 
2154
- type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
2090
+ type SaveMessageArgs = {
2091
+ threadId: string;
2092
+ userId?: string;
2093
+ /**
2094
+ * Metadata to save with the messages. Each element corresponds to the
2095
+ * message at the same index.
2096
+ */
2097
+ metadata?: Omit<MessageWithMetadata, "message">;
2098
+ /**
2099
+ * The embedding to save with the message.
2100
+ */
2101
+ embedding?: {
2102
+ vector: number[];
2103
+ model: string;
2104
+ };
2105
+ } & (
2106
+ | {
2107
+ prompt?: undefined;
2108
+ /**
2109
+ * The message to save.
2110
+ */
2111
+ message: CoreMessage;
2112
+ }
2113
+ | {
2114
+ /*
2115
+ * The prompt to save with the message.
2116
+ */
2117
+ prompt: string;
2118
+ message?: undefined;
2119
+ }
2120
+ );
2121
+
2122
+ /**
2123
+ * Save a message to the thread.
2124
+ * @param ctx A ctx object from a mutation or action.
2125
+ * @param args The message and what to associate it with (user / thread)
2126
+ * You can pass extra metadata alongside the message, e.g. associated fileIds.
2127
+ * @returns The messageId of the saved message.
2128
+ */
2129
+ export async function saveMessage(
2130
+ ctx: RunMutationCtx,
2131
+ component: AgentComponent,
2132
+ args: SaveMessageArgs & {
2133
+ /**
2134
+ * The agent name to associate with the message.
2135
+ */
2136
+ agentName?: string;
2137
+ }
2138
+ ) {
2139
+ let embeddings:
2140
+ | {
2141
+ vectors: number[][];
2142
+ model: string;
2143
+ }
2144
+ | undefined;
2145
+ if (args.embedding && args.embedding.vector) {
2146
+ embeddings = {
2147
+ model: args.embedding.model,
2148
+ vectors: [args.embedding.vector],
2149
+ };
2150
+ }
2151
+ const { lastMessageId, messages } = await saveMessages(ctx, component, {
2152
+ threadId: args.threadId,
2153
+ userId: args.userId,
2154
+ agentName: args.agentName,
2155
+ messages:
2156
+ args.prompt !== undefined
2157
+ ? [{ role: "user", content: args.prompt }]
2158
+ : [args.message],
2159
+ metadata: args.metadata ? [args.metadata] : undefined,
2160
+ embeddings,
2161
+ });
2162
+ return { messageId: lastMessageId, message: messages.at(-1)! };
2163
+ }