@convex-dev/agent 0.1.14 → 0.1.15-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/client/index.d.ts +496 -174
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +120 -165
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/listMessages.d.ts +22 -0
  6. package/dist/client/listMessages.d.ts.map +1 -0
  7. package/dist/client/listMessages.js +25 -0
  8. package/dist/client/listMessages.js.map +1 -0
  9. package/dist/client/search.d.ts +162 -0
  10. package/dist/client/search.d.ts.map +1 -0
  11. package/dist/client/search.js +113 -0
  12. package/dist/client/search.js.map +1 -0
  13. package/dist/client/streaming.d.ts +17 -3
  14. package/dist/client/streaming.d.ts.map +1 -1
  15. package/dist/client/streaming.js +39 -1
  16. package/dist/client/streaming.js.map +1 -1
  17. package/dist/client/types.d.ts +5 -1
  18. package/dist/client/types.d.ts.map +1 -1
  19. package/dist/component/_generated/api.d.ts +14 -3
  20. package/dist/component/messages.d.ts +4 -4
  21. package/dist/component/messages.d.ts.map +1 -1
  22. package/dist/component/messages.js +12 -7
  23. package/dist/component/messages.js.map +1 -1
  24. package/dist/component/schema.d.ts +38 -28
  25. package/dist/component/schema.d.ts.map +1 -1
  26. package/dist/component/schema.js +3 -2
  27. package/dist/component/schema.js.map +1 -1
  28. package/dist/component/streams.d.ts +7 -0
  29. package/dist/component/streams.d.ts.map +1 -1
  30. package/dist/component/streams.js +49 -22
  31. package/dist/component/streams.js.map +1 -1
  32. package/dist/component/vector/index.js +3 -3
  33. package/dist/component/vector/index.js.map +1 -1
  34. package/dist/react/deltas.d.ts.map +1 -1
  35. package/dist/react/deltas.js +2 -2
  36. package/dist/react/deltas.js.map +1 -1
  37. package/dist/react/index.d.ts +4 -2
  38. package/dist/react/index.d.ts.map +1 -1
  39. package/dist/react/index.js +22 -9
  40. package/dist/react/index.js.map +1 -1
  41. package/dist/react/optimisticallySendMessage.js +1 -1
  42. package/dist/react/optimisticallySendMessage.js.map +1 -1
  43. package/dist/validators.d.ts +49 -55
  44. package/dist/validators.d.ts.map +1 -1
  45. package/dist/validators.js +3 -8
  46. package/dist/validators.js.map +1 -1
  47. package/package.json +4 -3
  48. package/src/client/index.ts +249 -268
  49. package/src/client/listMessages.ts +38 -0
  50. package/src/client/search.ts +172 -0
  51. package/src/client/streaming.ts +56 -3
  52. package/src/client/types.ts +5 -1
  53. package/src/component/_generated/api.d.ts +14 -3
  54. package/src/component/messages.ts +13 -7
  55. package/src/component/schema.ts +3 -2
  56. package/src/component/streams.ts +85 -39
  57. package/src/component/vector/index.ts +4 -4
  58. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  59. package/src/react/deltas.ts +4 -2
  60. package/src/react/index.ts +23 -11
  61. package/src/react/optimisticallySendMessage.ts +1 -1
  62. package/src/validators.ts +8 -10
@@ -34,22 +34,17 @@ import {
34
34
  serializeNewMessagesInStep,
35
35
  serializeObjectResult,
36
36
  } from "../mapping.js";
37
- import {
38
- DEFAULT_MESSAGE_RANGE,
39
- DEFAULT_RECENT_MESSAGES,
40
- extractText,
41
- isTool,
42
- } from "../shared.js";
37
+ import { extractText, isTool } from "../shared.js";
43
38
  import {
44
39
  type MessageWithMetadata,
45
40
  type MessageStatus,
46
41
  type ProviderMetadata,
47
- type SearchOptions,
48
42
  type StreamArgs,
49
43
  type Usage,
50
44
  vMessageWithMetadata,
51
45
  vSafeObjectArgs,
52
46
  vTextArgs,
47
+ type MessageEmbeddings,
53
48
  } from "../validators.js";
54
49
  import { createTool, wrapTools } from "./createTool.js";
55
50
  import {
@@ -77,6 +72,9 @@ import type {
77
72
  UsageHandler,
78
73
  } from "./types.js";
79
74
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
75
+ import { listMessages } from "./listMessages.js";
76
+ import { syncStreams } from "./streaming.js";
77
+ import { fetchContextMessages } from "./search.js";
80
78
 
81
79
  export { storeFile, getFile } from "./files.js";
82
80
  export { serializeDataOrUrl } from "../mapping.js";
@@ -95,12 +93,21 @@ export {
95
93
  vUserMessage,
96
94
  } from "../validators.js";
97
95
  export type { ToolCtx } from "./createTool.js";
98
- export { createTool, extractText, isTool };
96
+ export { filterOutOrphanedToolMessages } from "./search.js";
97
+ export {
98
+ createTool,
99
+ extractText,
100
+ fetchContextMessages,
101
+ isTool,
102
+ listMessages,
103
+ syncStreams,
104
+ };
99
105
  export type {
100
106
  AgentComponent,
101
107
  ContextOptions,
102
108
  MessageDoc,
103
109
  ProviderMetadata,
110
+ RawRequestResponseHandler,
104
111
  StorageOptions,
105
112
  StreamArgs,
106
113
  SyncStreamsReturnValue,
@@ -110,7 +117,7 @@ export type {
110
117
  UsageHandler,
111
118
  };
112
119
 
113
- export class Agent<AgentTools extends ToolSet> {
120
+ export class Agent<AgentTools extends ToolSet = ToolSet> {
114
121
  constructor(
115
122
  public component: AgentComponent,
116
123
  public options: {
@@ -276,25 +283,18 @@ export class Agent<AgentTools extends ToolSet> {
276
283
  threadId: string;
277
284
  thread?: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
278
285
  }> {
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
- );
286
+ const threadId = await createThread(ctx, this.component, args);
287
287
  if (!("runAction" in ctx)) {
288
- return { threadId: threadDoc._id };
288
+ return { threadId };
289
289
  }
290
290
  const { thread } = await this.continueThread(ctx, {
291
- threadId: threadDoc._id,
291
+ threadId,
292
292
  userId: args?.userId,
293
293
  usageHandler: args?.usageHandler,
294
294
  tools: args?.tools,
295
295
  });
296
296
  return {
297
- threadId: threadDoc._id,
297
+ threadId,
298
298
  thread,
299
299
  };
300
300
  }
@@ -598,6 +598,7 @@ export class Agent<AgentTools extends ToolSet> {
598
598
  error: (error.error as Error).message,
599
599
  });
600
600
  }
601
+ // TODO: update the streamer to error state
601
602
  return args.onError?.(error);
602
603
  },
603
604
  onStepFinish: async (step) => {
@@ -611,7 +612,6 @@ export class Agent<AgentTools extends ToolSet> {
611
612
  promptMessageId: messageId,
612
613
  step,
613
614
  });
614
- // TODO: figure out pending/not
615
615
  await streamer?.finish(saved.messages);
616
616
  }
617
617
  if (this.options.rawRequestResponseHandler) {
@@ -833,14 +833,7 @@ export class Agent<AgentTools extends ToolSet> {
833
833
  */
834
834
  async saveMessage(
835
835
  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">;
836
+ args: SaveMessageArgs & {
844
837
  /**
845
838
  * If true, it will not generate embeddings for the message.
846
839
  * Useful if you're saving messages in a mutation where you can't run `fetch`.
@@ -848,22 +841,7 @@ export class Agent<AgentTools extends ToolSet> {
848
841
  * action later that calls `agent.generateAndSaveEmbeddings`.
849
842
  */
850
843
  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
- )
844
+ }
867
845
  ) {
868
846
  const { lastMessageId, messages } = await this.saveMessages(ctx, {
869
847
  threadId: args.threadId,
@@ -880,40 +858,15 @@ export class Agent<AgentTools extends ToolSet> {
880
858
 
881
859
  /**
882
860
  * Explicitly save messages associated with the thread (& user if provided)
861
+ * If you have an embedding model set, it will also generate embeddings for
862
+ * the messages.
883
863
  * @param ctx The ctx parameter to a mutation or action.
884
864
  * @param args The messages and context to save
885
865
  * @returns
886
866
  */
887
867
  async saveMessages(
888
868
  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;
869
+ args: SaveMessagesArgs & {
917
870
  /**
918
871
  * Skip generating embeddings for the messages. Useful if you're
919
872
  * saving messages in a mutation where you can't run `fetch`.
@@ -933,9 +886,12 @@ export class Agent<AgentTools extends ToolSet> {
933
886
  model: string;
934
887
  }
935
888
  | undefined;
936
- if (args.skipEmbeddings || !("runAction" in ctx)) {
889
+ const { skipEmbeddings, ...rest } = args;
890
+ if (args.embeddings) {
891
+ embeddings = args.embeddings;
892
+ } else if (skipEmbeddings || !("runAction" in ctx)) {
937
893
  embeddings = undefined;
938
- if (!args.skipEmbeddings && this.options.textEmbedding) {
894
+ if (!skipEmbeddings && this.options.textEmbedding) {
939
895
  console.warn(
940
896
  "You're trying to save messages and generate embeddings, but you're in a mutation. " +
941
897
  "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
@@ -953,33 +909,11 @@ export class Agent<AgentTools extends ToolSet> {
953
909
  args.messages
954
910
  );
955
911
  }
956
- const result = await ctx.runMutation(this.component.messages.addMessages, {
957
- threadId: args.threadId,
958
- userId: args.userId,
912
+ return saveMessages(ctx, this.component, {
913
+ ...rest,
959
914
  agentName: this.options.name,
960
- promptMessageId: args.promptMessageId,
961
915
  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
916
  });
979
- return {
980
- lastMessageId: result.messages.at(-1)!._id,
981
- messages: result.messages,
982
- };
983
917
  }
984
918
 
985
919
  /**
@@ -1001,17 +935,7 @@ export class Agent<AgentTools extends ToolSet> {
1001
935
  statuses?: MessageStatus[];
1002
936
  }
1003
937
  ): 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
- });
938
+ return listMessages(ctx, this.component, args);
1015
939
  }
1016
940
 
1017
941
  /**
@@ -1027,25 +951,11 @@ export class Agent<AgentTools extends ToolSet> {
1027
951
  args: {
1028
952
  threadId: string;
1029
953
  streamArgs: StreamArgs | undefined;
954
+ // By default, only streaming messages are included.
955
+ includeStatuses?: ("streaming" | "finished" | "aborted")[];
1030
956
  }
1031
957
  ): Promise<SyncStreamsReturnValue | undefined> {
1032
- if (!args.streamArgs) return undefined;
1033
- if (args.streamArgs.kind === "list") {
1034
- return {
1035
- kind: "list",
1036
- messages: await ctx.runQuery(this.component.streams.list, {
1037
- threadId: args.threadId,
1038
- }),
1039
- };
1040
- } else {
1041
- return {
1042
- kind: "deltas",
1043
- deltas: await ctx.runQuery(this.component.streams.listDeltas, {
1044
- threadId: args.threadId,
1045
- cursors: args.streamArgs.cursors,
1046
- }),
1047
- };
1048
- }
958
+ return syncStreams(ctx, this.component, args);
1049
959
  }
1050
960
 
1051
961
  /**
@@ -1072,76 +982,28 @@ export class Agent<AgentTools extends ToolSet> {
1072
982
  }
1073
983
  ): Promise<MessageDoc[]> {
1074
984
  assert(args.userId || args.threadId, "Specify userId or threadId");
1075
- // Fetch the latest messages from the thread
1076
- let included: Set<string> | undefined;
1077
985
  const opts = this._mergedContextOptions(args.contextOptions);
1078
- const contextMessages: MessageDoc[] = [];
1079
- if (
1080
- args.threadId &&
1081
- (opts.recentMessages !== 0 || args.upToAndIncludingMessageId)
1082
- ) {
1083
- const { page } = await ctx.runQuery(
1084
- this.component.messages.listMessagesByThreadId,
1085
- {
1086
- threadId: args.threadId,
1087
- excludeToolMessages: opts.excludeToolMessages,
1088
- paginationOpts: {
1089
- numItems: opts.recentMessages ?? DEFAULT_RECENT_MESSAGES,
1090
- cursor: null,
1091
- },
1092
- upToAndIncludingMessageId: args.upToAndIncludingMessageId,
1093
- order: "desc",
1094
- statuses: ["success"],
1095
- }
1096
- );
1097
- included = new Set(page.map((m) => m._id));
1098
- contextMessages.push(
1099
- // Reverse since we fetched in descending order
1100
- ...page.reverse()
1101
- );
1102
- }
1103
- if (opts.searchOptions?.textSearch || opts.searchOptions?.vectorSearch) {
1104
- const targetMessage = contextMessages.find(
1105
- (m) => m._id === args.upToAndIncludingMessageId
1106
- )?.message;
1107
- const messagesToSearch = targetMessage ? [targetMessage] : args.messages;
1108
- if (!("runAction" in ctx)) {
1109
- throw new Error("searchUserMessages only works in an action");
1110
- }
1111
- const searchMessages = await ctx.runAction(
1112
- this.component.messages.searchMessages,
1113
- {
1114
- searchAllMessagesForUserId: opts?.searchOtherThreads
1115
- ? args.userId ??
1116
- (args.threadId &&
1117
- (
1118
- await ctx.runQuery(this.component.threads.getThread, {
1119
- threadId: args.threadId,
1120
- })
1121
- )?.userId)
1122
- : undefined,
1123
- threadId: args.threadId,
1124
- beforeMessageId: args.upToAndIncludingMessageId,
1125
- ...(await this._searchOptionsWithEmbeddingAndDefaults(
1126
- ctx,
1127
- { userId: args.userId, threadId: args.threadId },
1128
- opts,
1129
- messagesToSearch
1130
- )),
1131
- }
1132
- );
1133
- // TODO: track what messages we used for context
1134
- contextMessages.unshift(
1135
- ...searchMessages.filter((m) => !included?.has(m._id))
1136
- );
1137
- }
1138
- // Ensure we don't include tool messages without a corresponding tool call
1139
- return filterOutOrphanedToolMessages(
1140
- contextMessages.sort((a, b) =>
1141
- // Sort the raw MessageDocs by order and stepOrder
1142
- a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order
1143
- )
1144
- );
986
+ return fetchContextMessages(ctx, this.component, {
987
+ ...args,
988
+ contextOptions: opts,
989
+ getEmbedding: async (text) => {
990
+ assert("runAction" in ctx);
991
+ assert(
992
+ this.options.textEmbedding,
993
+ "A textEmbedding model is required to be set on the Agent that you're doing vector search with"
994
+ );
995
+ return {
996
+ vector: (
997
+ await this.doEmbed(ctx, {
998
+ userId: args.userId,
999
+ threadId: args.threadId,
1000
+ values: [text],
1001
+ })
1002
+ ).embeddings[0],
1003
+ vectorModel: this.options.textEmbedding.modelId,
1004
+ };
1005
+ },
1006
+ });
1145
1007
  }
1146
1008
 
1147
1009
  /**
@@ -1154,13 +1016,7 @@ export class Agent<AgentTools extends ToolSet> {
1154
1016
  ctx: RunQueryCtx,
1155
1017
  args: { threadId: string }
1156
1018
  ): Promise<ThreadDoc> {
1157
- const thread = await ctx.runQuery(this.component.threads.getThread, {
1158
- threadId: args.threadId,
1159
- });
1160
- if (!thread) {
1161
- throw new Error("Thread not found");
1162
- }
1163
- return thread;
1019
+ return getThreadMetadata(ctx, this.component, args);
1164
1020
  }
1165
1021
 
1166
1022
  /**
@@ -1765,49 +1621,11 @@ export class Agent<AgentTools extends ToolSet> {
1765
1621
  ...this.options.contextOptions,
1766
1622
  ...opts,
1767
1623
  searchOptions: searchOptions.limit
1768
- ? (searchOptions as SearchOptions)
1624
+ ? (searchOptions as ContextOptions["searchOptions"])
1769
1625
  : undefined,
1770
1626
  };
1771
1627
  }
1772
1628
 
1773
- async _searchOptionsWithEmbeddingAndDefaults(
1774
- ctx: RunActionCtx,
1775
- { userId, threadId }: { userId?: string; threadId?: string },
1776
- contextOptions: ContextOptions,
1777
- messages: CoreMessage[]
1778
- ): Promise<SearchOptions> {
1779
- assert(
1780
- contextOptions.searchOptions?.textSearch ||
1781
- contextOptions.searchOptions?.vectorSearch,
1782
- "searchOptions is required"
1783
- );
1784
- assert(messages.length > 0, "Core messages cannot be empty");
1785
- const text = extractText(messages.at(-1)!);
1786
- const search: SearchOptions = {
1787
- limit: contextOptions.searchOptions?.limit ?? 10,
1788
- messageRange: {
1789
- ...DEFAULT_MESSAGE_RANGE,
1790
- ...contextOptions.searchOptions?.messageRange,
1791
- },
1792
- text: extractText(messages.at(-1)!),
1793
- };
1794
- if (
1795
- contextOptions.searchOptions?.vectorSearch &&
1796
- text &&
1797
- this.options.textEmbedding
1798
- ) {
1799
- search.vector = (
1800
- await this.doEmbed(ctx, {
1801
- threadId,
1802
- userId,
1803
- values: [text],
1804
- })
1805
- ).embeddings[0];
1806
- search.vectorModel = this.options.textEmbedding.modelId;
1807
- }
1808
- return search;
1809
- }
1810
-
1811
1629
  async doEmbed(
1812
1630
  ctx: RunActionCtx,
1813
1631
  options: {
@@ -1819,7 +1637,10 @@ export class Agent<AgentTools extends ToolSet> {
1819
1637
  }
1820
1638
  ): Promise<{ embeddings: number[][] }> {
1821
1639
  const embedding = this.options.textEmbedding;
1822
- assert(embedding, "textEmbedding is required");
1640
+ assert(
1641
+ embedding,
1642
+ "a textEmbedding model is required to be set on the Agent that you're doing vector search with"
1643
+ );
1823
1644
  const result = await embedding.doEmbed({
1824
1645
  values: options.values,
1825
1646
  abortSignal: options.abortSignal,
@@ -2120,31 +1941,191 @@ export class Agent<AgentTools extends ToolSet> {
2120
1941
  }
2121
1942
  }
2122
1943
 
2123
- export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
2124
- const toolCallIds = new Set<string>();
2125
- const result: MessageDoc[] = [];
2126
- for (const doc of docs) {
2127
- if (
2128
- doc.message?.role === "assistant" &&
2129
- Array.isArray(doc.message.content)
2130
- ) {
2131
- for (const content of doc.message.content) {
2132
- if (content.type === "tool-call") {
2133
- toolCallIds.add(content.toolCallId);
2134
- }
2135
- }
2136
- result.push(doc);
2137
- } else if (doc.message?.role === "tool") {
2138
- if (doc.message.content.every((c) => toolCallIds.has(c.toolCallId))) {
2139
- result.push(doc);
2140
- } else {
2141
- console.debug("Filtering out orphaned tool message", doc);
2142
- }
2143
- } else {
2144
- result.push(doc);
2145
- }
1944
+ type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
1945
+
1946
+ /**
1947
+ * Create a thread to store messages with an Agent.
1948
+ * @param ctx The context from a mutation or action.
1949
+ * @param component The Agent component, usually `components.agent`.
1950
+ * @param args The associated thread metadata.
1951
+ * @returns The id of the created thread.
1952
+ */
1953
+ export async function createThread(
1954
+ ctx: RunMutationCtx,
1955
+ component: AgentComponent,
1956
+ args?: {
1957
+ userId?: string;
1958
+ title?: string;
1959
+ summary?: string;
2146
1960
  }
2147
- return result;
1961
+ ) {
1962
+ const { _id: threadId } = await ctx.runMutation(
1963
+ component.threads.createThread,
1964
+ { userId: args?.userId, title: args?.title, summary: args?.summary }
1965
+ );
1966
+ return threadId;
2148
1967
  }
2149
1968
 
2150
- type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
1969
+ /**
1970
+ * Get the metadata for a thread.
1971
+ * @param ctx A ctx object from a query, mutation, or action.
1972
+ * @param args.threadId The thread to get the metadata for.
1973
+ * @returns The metadata for the thread.
1974
+ */
1975
+ export async function getThreadMetadata(
1976
+ ctx: RunQueryCtx,
1977
+ component: AgentComponent,
1978
+ args: { threadId: string }
1979
+ ): Promise<ThreadDoc> {
1980
+ const thread = await ctx.runQuery(component.threads.getThread, {
1981
+ threadId: args.threadId,
1982
+ });
1983
+ if (!thread) {
1984
+ throw new Error("Thread not found");
1985
+ }
1986
+ return thread;
1987
+ }
1988
+
1989
+ type SaveMessagesArgs = {
1990
+ threadId: string;
1991
+ userId?: string;
1992
+ /**
1993
+ * The message that these messages are in response to. They will be
1994
+ * the same "order" as this message, at increasing stepOrder(s).
1995
+ */
1996
+ promptMessageId?: string;
1997
+ /**
1998
+ * The messages to save.
1999
+ */
2000
+ messages: CoreMessageMaybeWithId[];
2001
+ /**
2002
+ * Metadata to save with the messages. Each element corresponds to the
2003
+ * message at the same index.
2004
+ */
2005
+ metadata?: Omit<MessageWithMetadata, "message">[];
2006
+ /**
2007
+ * If false, it will "commit" the messages immediately.
2008
+ * If true, it will mark them as pending until the final step has finished.
2009
+ * Defaults to false.
2010
+ */
2011
+ pending?: boolean;
2012
+ /**
2013
+ * If true, it will fail any pending steps.
2014
+ * Defaults to false.
2015
+ */
2016
+ failPendingSteps?: boolean;
2017
+ /**
2018
+ * The embeddings to save with the messages.
2019
+ */
2020
+ embeddings?: MessageEmbeddings;
2021
+ };
2022
+
2023
+ /**
2024
+ * Explicitly save messages associated with the thread (& user if provided)
2025
+ */
2026
+ export async function saveMessages(
2027
+ ctx: RunMutationCtx,
2028
+ component: AgentComponent,
2029
+ args: SaveMessagesArgs & {
2030
+ /**
2031
+ * The agent name to associate with the messages.
2032
+ */
2033
+ agentName?: string;
2034
+ }
2035
+ ) {
2036
+ const result = await ctx.runMutation(component.messages.addMessages, {
2037
+ threadId: args.threadId,
2038
+ userId: args.userId,
2039
+ agentName: args.agentName,
2040
+ promptMessageId: args.promptMessageId,
2041
+ embeddings: args.embeddings,
2042
+ messages: await Promise.all(
2043
+ args.messages.map(async (m, i) => {
2044
+ const { message, fileIds } = await serializeMessage(ctx, component, m);
2045
+ return {
2046
+ ...args.metadata?.[i],
2047
+ message,
2048
+ fileIds,
2049
+ } as MessageWithMetadata;
2050
+ })
2051
+ ),
2052
+ failPendingSteps: args.failPendingSteps ?? false,
2053
+ pending: args.pending ?? false,
2054
+ });
2055
+ return {
2056
+ lastMessageId: result.messages.at(-1)!._id,
2057
+ messages: result.messages,
2058
+ };
2059
+ }
2060
+
2061
+ type SaveMessageArgs = {
2062
+ threadId: string;
2063
+ userId?: string;
2064
+ /**
2065
+ * Metadata to save with the messages. Each element corresponds to the
2066
+ * message at the same index.
2067
+ */
2068
+ metadata?: Omit<MessageWithMetadata, "message">;
2069
+ /**
2070
+ * The embedding to save with the message.
2071
+ */
2072
+ embedding?: {
2073
+ vector: number[];
2074
+ model: string;
2075
+ };
2076
+ } & (
2077
+ | {
2078
+ prompt?: undefined;
2079
+ /**
2080
+ * The message to save.
2081
+ */
2082
+ message: CoreMessage;
2083
+ }
2084
+ | {
2085
+ /*
2086
+ * The prompt to save with the message.
2087
+ */
2088
+ prompt: string;
2089
+ message?: undefined;
2090
+ }
2091
+ );
2092
+
2093
+ /**
2094
+ * Save a message to the thread.
2095
+ * @param ctx A ctx object from a mutation or action.
2096
+ * @param args The message and what to associate it with (user / thread)
2097
+ * You can pass extra metadata alongside the message, e.g. associated fileIds.
2098
+ * @returns The messageId of the saved message.
2099
+ */
2100
+ export async function saveMessage(
2101
+ ctx: RunMutationCtx,
2102
+ component: AgentComponent,
2103
+ args: SaveMessageArgs & {
2104
+ /**
2105
+ * The agent name to associate with the message.
2106
+ */
2107
+ agentName?: string;
2108
+ }
2109
+ ) {
2110
+ let embeddings: MessageEmbeddings | undefined;
2111
+ if (args.embedding) {
2112
+ const dimension = args.embedding.vector.length;
2113
+ validateVectorDimension(dimension);
2114
+ embeddings = {
2115
+ model: args.embedding.model,
2116
+ dimension,
2117
+ vectors: [args.embedding.vector],
2118
+ };
2119
+ }
2120
+ const { lastMessageId, messages } = await saveMessages(ctx, component, {
2121
+ threadId: args.threadId,
2122
+ userId: args.userId,
2123
+ messages:
2124
+ args.prompt !== undefined
2125
+ ? [{ role: "user", content: args.prompt }]
2126
+ : [args.message],
2127
+ metadata: args.metadata ? [args.metadata] : undefined,
2128
+ embeddings,
2129
+ });
2130
+ return { messageId: lastMessageId, message: messages.at(-1)! };
2131
+ }