@convex-dev/agent 0.0.15-alpha.1 → 0.0.16

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.
@@ -27,7 +27,7 @@ import { assert } from "convex-helpers";
27
27
  import { internalActionGeneric, internalMutationGeneric } from "convex/server";
28
28
  import { Infer, v } from "convex/values";
29
29
  import { z } from "zod";
30
- import { api } from "../component/_generated/api.js";
30
+ import { Mounts } from "../component/_generated/api.js";
31
31
  import {
32
32
  validateVectorDimension,
33
33
  type VectorDimension,
@@ -174,8 +174,7 @@ export type UsageHandler = (
174
174
 
175
175
  export class Agent<AgentTools extends ToolSet> {
176
176
  constructor(
177
- // public component: UseApi<Mounts>,
178
- public component: UseApi<typeof api>,
177
+ public component: UseApi<Mounts>,
179
178
  public options: {
180
179
  /**
181
180
  * The name for the agent. This will be attributed on each message
@@ -337,7 +336,6 @@ export class Agent<AgentTools extends ToolSet> {
337
336
  const threadDoc = await ctx.runMutation(
338
337
  this.component.messages.createThread,
339
338
  {
340
- defaultSystemPrompt: this.options.instructions,
341
339
  userId: args?.userId,
342
340
  title: args?.title,
343
341
  summary: args?.summary,
@@ -460,13 +458,21 @@ export class Agent<AgentTools extends ToolSet> {
460
458
  statuses: ["success"],
461
459
  }
462
460
  );
463
- contextMessages.push(...page.filter((m) => !included?.has(m._id)));
461
+ contextMessages.push(
462
+ // Reverse since we fetched in descending order
463
+ ...page.filter((m) => !included?.has(m._id)).reverse()
464
+ );
464
465
  }
465
- return contextMessages
466
- .sort((a, b) =>
467
- a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order
468
- )
469
- .map((m) => deserializeMessage(m.message!));
466
+
467
+ // Sort the raw MessageDocs by order and stepOrder
468
+ const sortedDocs = contextMessages.sort((a, b) =>
469
+ a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order
470
+ );
471
+
472
+ // Ensure we don't include tool messages without a corresponding tool call
473
+ return filterOutOrphanedToolMessages(sortedDocs).map((m) =>
474
+ deserializeMessage(m.message!)
475
+ );
470
476
  }
471
477
 
472
478
  /**
@@ -700,14 +706,16 @@ export class Agent<AgentTools extends ToolSet> {
700
706
  */
701
707
  tools?: ToolSet;
702
708
  },
703
- args: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>
709
+ args: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>,
710
+ options?: Options
704
711
  ): Promise<
705
712
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
706
713
  GenerationOutputMetadata
707
714
  > {
708
715
  const { args: aiArgs, messageId } = await this.saveMessagesAndFetchContext(
709
716
  ctx,
710
- { ...args, userId, threadId }
717
+ args,
718
+ { userId, threadId, ...options }
711
719
  );
712
720
  const toolCtx = { ...ctx, userId, threadId, messageId };
713
721
  const tools = wrapTools(
@@ -715,6 +723,7 @@ export class Agent<AgentTools extends ToolSet> {
715
723
  args.tools ?? threadTools ?? this.options.tools
716
724
  ) as TOOLS extends undefined ? AgentTools : TOOLS;
717
725
  const saveOutputMessages =
726
+ options?.storageOptions?.saveOutputMessages ??
718
727
  args.saveOutputMessages ??
719
728
  this.options.storageOptions?.saveOutputMessages;
720
729
  const model = aiArgs.model ?? this.options.chat;
@@ -801,7 +810,8 @@ export class Agent<AgentTools extends ToolSet> {
801
810
  usageHandler?: UsageHandler;
802
811
  tools?: ToolSet;
803
812
  },
804
- args: StreamingTextArgs<AgentTools, TOOLS, OUTPUT, PARTIAL_OUTPUT>
813
+ args: StreamingTextArgs<AgentTools, TOOLS, OUTPUT, PARTIAL_OUTPUT>,
814
+ options?: Options
805
815
  ): Promise<
806
816
  StreamTextResult<
807
817
  TOOLS extends undefined ? AgentTools : TOOLS,
@@ -811,7 +821,8 @@ export class Agent<AgentTools extends ToolSet> {
811
821
  > {
812
822
  const { args: aiArgs, messageId } = await this.saveMessagesAndFetchContext(
813
823
  ctx,
814
- { ...args, userId, threadId }
824
+ args,
825
+ { userId, threadId, ...options }
815
826
  );
816
827
  const toolCtx = { ...ctx, userId, threadId, messageId };
817
828
  const tools = wrapTools(
@@ -819,6 +830,7 @@ export class Agent<AgentTools extends ToolSet> {
819
830
  args.tools ?? threadTools ?? this.options.tools
820
831
  ) as TOOLS extends undefined ? AgentTools : TOOLS;
821
832
  const saveOutputMessages =
833
+ options?.storageOptions?.saveOutputMessages ??
822
834
  args.saveOutputMessages ??
823
835
  this.options.storageOptions?.saveOutputMessages;
824
836
  const model = aiArgs.model ?? this.options.chat;
@@ -886,28 +898,23 @@ export class Agent<AgentTools extends ToolSet> {
886
898
  },
887
899
  >(
888
900
  ctx: RunActionCtx | RunMutationCtx,
901
+ args: T,
889
902
  {
890
- id,
891
903
  userId,
892
904
  threadId,
893
905
  parentMessageId,
894
- system,
895
- ...args
906
+ contextOptions,
907
+ storageOptions,
896
908
  }: {
897
909
  userId: string | undefined;
898
910
  threadId: string | undefined;
899
- parentMessageId?: string;
900
- contextOptions?: ContextOptions;
901
- storageOptions?: StorageOptions;
902
- } & T
911
+ } & Options
903
912
  ): Promise<{
904
913
  args: T;
905
914
  messageId: string | undefined;
906
915
  }> {
907
- const contextOptions: ContextOptions | Record<string, unknown> =
908
- args.contextOptions ?? this.options.contextOptions ?? args;
909
- const storageOptions: StorageOptions | Record<string, unknown> =
910
- args.storageOptions ?? this.options.storageOptions ?? args;
916
+ contextOptions ||= this.options.contextOptions ?? (args as ContextOptions);
917
+ storageOptions ||= this.options.storageOptions ?? (args as StorageOptions);
911
918
  const messages = promptOrMessagesToCoreMessages(args);
912
919
  const contextMessages = await this.fetchContextMessages(ctx, {
913
920
  userId,
@@ -924,7 +931,7 @@ export class Agent<AgentTools extends ToolSet> {
924
931
  threadId,
925
932
  userId,
926
933
  messages: coreMessages,
927
- metadata: coreMessages.length === 1 ? [{ id }] : undefined,
934
+ metadata: coreMessages.length === 1 ? [{ id: args.id }] : undefined,
928
935
  pending: true,
929
936
  // We should just fail if you pass in an ID for the message, fail those children
930
937
  // failPendingSteps: true,
@@ -936,7 +943,7 @@ export class Agent<AgentTools extends ToolSet> {
936
943
  return {
937
944
  args: {
938
945
  ...rest,
939
- system: system ?? this.options.instructions,
946
+ system: args.system ?? this.options.instructions,
940
947
  messages: [...contextMessages, ...messages],
941
948
  } as T,
942
949
  messageId,
@@ -962,15 +969,18 @@ export class Agent<AgentTools extends ToolSet> {
962
969
  threadId,
963
970
  usageHandler,
964
971
  }: { userId?: string; threadId?: string; usageHandler?: UsageHandler },
965
- args: OurObjectArgs<T>
972
+ args: OurObjectArgs<T>,
973
+ options?: Options
966
974
  ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
967
975
  const { args: aiArgs, messageId } = await this.saveMessagesAndFetchContext(
968
976
  ctx,
969
- { ...args, userId, threadId }
977
+ args,
978
+ { userId, threadId, ...options }
970
979
  );
971
980
  const model = aiArgs.model ?? this.options.chat;
972
981
  const trackUsage = usageHandler ?? this.options.usageHandler;
973
982
  const saveOutputMessages =
983
+ options?.storageOptions?.saveOutputMessages ??
974
984
  args.saveOutputMessages ??
975
985
  this.options.storageOptions?.saveOutputMessages;
976
986
  try {
@@ -1028,18 +1038,21 @@ export class Agent<AgentTools extends ToolSet> {
1028
1038
  threadId,
1029
1039
  usageHandler,
1030
1040
  }: { userId?: string; threadId?: string; usageHandler?: UsageHandler },
1031
- args: OurStreamObjectArgs<T>
1041
+ args: OurStreamObjectArgs<T>,
1042
+ options?: Options
1032
1043
  ): Promise<
1033
1044
  StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
1034
1045
  > {
1035
1046
  // TODO: unify all this shared code between all the generate* and stream* functions
1036
1047
  const { args: aiArgs, messageId } = await this.saveMessagesAndFetchContext(
1037
1048
  ctx,
1038
- { ...args, userId, threadId }
1049
+ args,
1050
+ { userId, threadId, ...options }
1039
1051
  );
1040
1052
  const model = aiArgs.model ?? this.options.chat;
1041
1053
  const trackUsage = usageHandler ?? this.options.usageHandler;
1042
1054
  const saveOutputMessages =
1055
+ options?.storageOptions?.saveOutputMessages ??
1043
1056
  args.saveOutputMessages ??
1044
1057
  this.options.storageOptions?.saveOutputMessages;
1045
1058
  const stream = streamObject<T>({
@@ -1227,18 +1240,30 @@ export class Agent<AgentTools extends ToolSet> {
1227
1240
  * @param spec Configuration for the agent acting as an action, including
1228
1241
  * {@link ContextOptions} and maxSteps.
1229
1242
  */
1230
- asTextAction(spec?: { contextOptions?: ContextOptions; maxSteps?: number }) {
1243
+ asTextAction(spec?: {
1244
+ contextOptions?: ContextOptions;
1245
+ maxSteps?: number;
1246
+ storageOptions?: StorageOptions;
1247
+ }) {
1231
1248
  const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1232
- const contextOptions =
1233
- spec?.contextOptions && this.mergedContextOptions(spec.contextOptions);
1234
-
1235
1249
  return internalActionGeneric({
1236
1250
  args: vTextArgs,
1237
1251
  handler: async (ctx, args) => {
1252
+ const { contextOptions, storageOptions, ...rest } = args;
1238
1253
  const value = await this.generateText(
1239
1254
  ctx,
1240
1255
  { userId: args.userId, threadId: args.threadId },
1241
- { maxSteps, ...args, ...contextOptions, ...args.storageOptions }
1256
+ { maxSteps, ...rest },
1257
+ {
1258
+ contextOptions:
1259
+ contextOptions ??
1260
+ spec?.contextOptions ??
1261
+ this.options.contextOptions,
1262
+ storageOptions:
1263
+ storageOptions ??
1264
+ spec?.storageOptions ??
1265
+ this.options.storageOptions,
1266
+ }
1242
1267
  );
1243
1268
  return value.text;
1244
1269
  },
@@ -1251,21 +1276,36 @@ export class Agent<AgentTools extends ToolSet> {
1251
1276
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1252
1277
  * and maxSteps.
1253
1278
  */
1254
- asObjectAction<T>(spec: OurObjectArgs<T> & { maxSteps?: number }) {
1279
+ asObjectAction<T>(
1280
+ spec: OurObjectArgs<T> & { maxSteps?: number },
1281
+ options?: {
1282
+ contextOptions?: ContextOptions;
1283
+ storageOptions?: StorageOptions;
1284
+ }
1285
+ ) {
1255
1286
  const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1256
1287
  return internalActionGeneric({
1257
1288
  args: vSafeObjectArgs,
1258
1289
  handler: async (ctx, args) => {
1290
+ const { contextOptions, storageOptions, ...rest } = args;
1259
1291
  const value = await this.generateObject(
1260
1292
  ctx,
1261
1293
  { userId: args.userId, threadId: args.threadId },
1262
1294
  {
1263
1295
  ...spec,
1264
1296
  maxSteps,
1265
- ...args,
1266
- ...this.mergedContextOptions(spec),
1267
- ...args.storageOptions,
1268
- } as unknown as OurObjectArgs<unknown>
1297
+ ...rest,
1298
+ } as unknown as OurObjectArgs<unknown>,
1299
+ {
1300
+ contextOptions:
1301
+ contextOptions ??
1302
+ options?.contextOptions ??
1303
+ this.options.contextOptions,
1304
+ storageOptions:
1305
+ storageOptions ??
1306
+ options?.storageOptions ??
1307
+ this.options.storageOptions,
1308
+ }
1269
1309
  );
1270
1310
  return value.object as T;
1271
1311
  },
@@ -1273,6 +1313,34 @@ export class Agent<AgentTools extends ToolSet> {
1273
1313
  }
1274
1314
  }
1275
1315
 
1316
+ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
1317
+ const toolCallIds = new Set<string>();
1318
+ const result: MessageDoc[] = [];
1319
+ for (const doc of docs) {
1320
+ if (
1321
+ doc.message?.role === "assistant" &&
1322
+ Array.isArray(doc.message.content)
1323
+ ) {
1324
+ for (const content of doc.message.content) {
1325
+ if (content.type === "tool-call") {
1326
+ toolCallIds.add(content.toolCallId);
1327
+ }
1328
+ }
1329
+ result.push(doc);
1330
+ } else if (doc.message?.role === "tool") {
1331
+ if (doc.message.content.every((c) => toolCallIds.has(c.toolCallId))) {
1332
+ result.push(doc);
1333
+ } else {
1334
+ console.debug("Filtering out orphaned tool message", doc);
1335
+ }
1336
+ } else {
1337
+ result.push(doc);
1338
+ }
1339
+ }
1340
+ return result;
1341
+ }
1342
+
1343
+
1276
1344
  export type ToolCtx = RunActionCtx & {
1277
1345
  userId?: string;
1278
1346
  threadId?: string;
@@ -1373,6 +1441,21 @@ function wrapTools(
1373
1441
  return output;
1374
1442
  }
1375
1443
 
1444
+ type Options = {
1445
+ /**
1446
+ * The parent message id to use for the tool calls.
1447
+ */
1448
+ parentMessageId?: string;
1449
+ /**
1450
+ * The context options to use for passing in message history to the LLM.
1451
+ */
1452
+ contextOptions?: ContextOptions;
1453
+ /**
1454
+ * The storage options to use for saving the input and output messages to the thread.
1455
+ */
1456
+ storageOptions?: StorageOptions;
1457
+ };
1458
+
1376
1459
  type TextArgs<
1377
1460
  AgentTools extends ToolSet,
1378
1461
  TOOLS extends ToolSet | undefined = undefined,
@@ -1403,20 +1486,7 @@ type TextArgs<
1403
1486
  * specified in the tools array. e.g. {toolName: "getWeather", type: "tool"}
1404
1487
  */
1405
1488
  toolChoice?: ToolChoice<TOOLS extends undefined ? AgentTools : TOOLS>;
1406
- // Non-AI SDK args
1407
- /**
1408
- * The parent message id to use for the tool calls.
1409
- */
1410
- parentMessageId?: string;
1411
- /**
1412
- * The context options to use for passing in message history to the LLM.
1413
- */
1414
- contextOptions?: ContextOptions;
1415
- /**
1416
- * The storage options to use for saving the input and output messages to the thread.
1417
- */
1418
- storageOptions?: StorageOptions;
1419
- } & ContextOptions &
1489
+ } & ContextOptions & // DEPRECATED: pass them in the subsequent parameter instead
1420
1490
  StorageOptions;
1421
1491
 
1422
1492
  type StreamingTextArgs<
@@ -1449,19 +1519,6 @@ type StreamingTextArgs<
1449
1519
  * specified in the tools array. e.g. {toolName: "getWeather", type: "tool"}
1450
1520
  */
1451
1521
  toolChoice?: ToolChoice<TOOLS extends undefined ? AgentTools : TOOLS>;
1452
- // Non-AI SDK args
1453
- /**
1454
- * The parent message id to use for the tool calls.
1455
- */
1456
- parentMessageId?: string;
1457
- /**
1458
- * The context options to use for passing in message history to the LLM.
1459
- */
1460
- contextOptions?: ContextOptions;
1461
- /**
1462
- * The storage options to use for saving the input and output messages to the thread.
1463
- */
1464
- storageOptions?: StorageOptions;
1465
1522
  } & ContextOptions &
1466
1523
  StorageOptions;
1467
1524
 
@@ -1493,19 +1550,6 @@ type BaseGenerateObjectOptions = StorageOptions &
1493
1550
  experimental_telemetry?: TelemetrySettings;
1494
1551
  providerOptions?: ProviderOptions;
1495
1552
  experimental_providerMetadata?: ProviderMetadata;
1496
- // Non-AI SDK args
1497
- /**
1498
- * The parent message id to use for the object generation.
1499
- */
1500
- parentMessageId?: string;
1501
- /**
1502
- * The context options to use for passing in message history to the LLM.
1503
- */
1504
- contextOptions?: ContextOptions;
1505
- /**
1506
- * The storage options to use for saving the input and output messages to the thread.
1507
- */
1508
- storageOptions?: StorageOptions;
1509
1553
  };
1510
1554
 
1511
1555
  type GenerateObjectObjectOptions<T extends Record<string, unknown>> =
@@ -1537,6 +1581,8 @@ type GenerateObjectNoSchemaOptions = BaseGenerateObjectOptions & {
1537
1581
  mode?: "json";
1538
1582
  };
1539
1583
 
1584
+ // TODO: simplify this to just use the generateObject args, with an optional
1585
+ // model and tool/toolChoice types
1540
1586
  type GenerateObjectArgs<T> =
1541
1587
  T extends Record<string, unknown>
1542
1588
  ? GenerateObjectObjectOptions<T>
@@ -1570,6 +1616,10 @@ type ThreadOutputMetadata = GenerationOutputMetadata & {
1570
1616
  messageId: string;
1571
1617
  };
1572
1618
 
1619
+ /**
1620
+ * The interface for a thread returned from {@link createThread} or {@link continueThread}.
1621
+ * This is contextual to a thread and/or user.
1622
+ */
1573
1623
  interface Thread<DefaultTools extends ToolSet> {
1574
1624
  /**
1575
1625
  * The target threadId, from the startThread or continueThread initializers.
@@ -1595,7 +1645,8 @@ interface Thread<DefaultTools extends ToolSet> {
1595
1645
  TOOLS,
1596
1646
  OUTPUT,
1597
1647
  OUTPUT_PARTIAL
1598
- >
1648
+ >,
1649
+ options?: Options
1599
1650
  ): Promise<
1600
1651
  GenerateTextResult<TOOLS extends undefined ? DefaultTools : TOOLS, OUTPUT> &
1601
1652
  ThreadOutputMetadata
@@ -1621,7 +1672,8 @@ interface Thread<DefaultTools extends ToolSet> {
1621
1672
  TOOLS,
1622
1673
  OUTPUT,
1623
1674
  PARTIAL_OUTPUT
1624
- >
1675
+ >,
1676
+ options?: Options
1625
1677
  ): Promise<
1626
1678
  StreamTextResult<
1627
1679
  TOOLS extends undefined ? DefaultTools : TOOLS,
@@ -1640,7 +1692,8 @@ interface Thread<DefaultTools extends ToolSet> {
1640
1692
  * @returns The result of the generateObject function.
1641
1693
  */
1642
1694
  generateObject<T>(
1643
- args: OurObjectArgs<T>
1695
+ args: OurObjectArgs<T>,
1696
+ options?: Options
1644
1697
  ): Promise<GenerateObjectResult<T> & ThreadOutputMetadata>;
1645
1698
  /**
1646
1699
  * This behaves like {@link generateObject} from the "ai" package except that
@@ -1653,7 +1706,8 @@ interface Thread<DefaultTools extends ToolSet> {
1653
1706
  * @returns The result of the generateObject function.
1654
1707
  */
1655
1708
  generateObject(
1656
- args: GenerateObjectNoSchemaOptions
1709
+ args: GenerateObjectNoSchemaOptions,
1710
+ options?: Options
1657
1711
  ): Promise<GenerateObjectResult<JSONValue> & ThreadOutputMetadata>;
1658
1712
  /**
1659
1713
  * This behaves like {@link streamObject} from the "ai" package except that
@@ -1666,7 +1720,8 @@ interface Thread<DefaultTools extends ToolSet> {
1666
1720
  * @returns The result of the streamObject function.
1667
1721
  */
1668
1722
  streamObject<T>(
1669
- args: OurStreamObjectArgs<T>
1723
+ args: OurStreamObjectArgs<T>,
1724
+ options?: Options
1670
1725
  ): Promise<
1671
1726
  StreamObjectResult<DeepPartial<T>, T, never> & ThreadOutputMetadata
1672
1727
  >;
@@ -1692,7 +1692,6 @@ export type Mounts = {
1692
1692
  "public",
1693
1693
  {
1694
1694
  patch: {
1695
- defaultSystemPrompt?: string;
1696
1695
  status?: "active" | "archived";
1697
1696
  summary?: string;
1698
1697
  title?: string;
@@ -80,14 +80,7 @@ export const updateThread = mutation({
80
80
  args: {
81
81
  threadId: v.id("threads"),
82
82
  patch: v.object(
83
- partial(
84
- pick(vThread.fields, [
85
- "title",
86
- "summary",
87
- "defaultSystemPrompt",
88
- "status",
89
- ])
90
- )
83
+ partial(pick(vThread.fields, ["title", "summary", "status"]))
91
84
  ),
92
85
  },
93
86
  handler: async (ctx, args) => {
@@ -16,12 +16,11 @@ import vectorTables, { vVectorId } from "./vector/tables.js";
16
16
  export const schema = defineSchema({
17
17
  threads: defineTable({
18
18
  userId: v.optional(v.string()), // Unset for anonymous
19
- // TODO: is this bubbling up in continue?
20
- defaultSystemPrompt: v.optional(v.string()),
21
19
  title: v.optional(v.string()),
22
20
  summary: v.optional(v.string()),
23
21
  status: vThreadStatus,
24
22
  // DEPRECATED
23
+ defaultSystemPrompt: v.optional(v.string()),
25
24
  parentThreadIds: v.optional(v.array(v.id("threads"))),
26
25
  order: /*DEPRECATED*/ v.optional(v.number()),
27
26
  }).index("userId", ["userId"]),