@convex-dev/agent 0.7.1 → 0.7.3

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 (90) hide show
  1. package/README.md +5 -8
  2. package/dist/client/messages.d.ts +10 -0
  3. package/dist/client/messages.d.ts.map +1 -1
  4. package/dist/client/messages.js +1 -0
  5. package/dist/client/messages.js.map +1 -1
  6. package/dist/component/_generated/component.d.ts +10 -8
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/messages.d.ts +340 -338
  9. package/dist/component/messages.d.ts.map +1 -1
  10. package/dist/component/messages.js +74 -14
  11. package/dist/component/messages.js.map +1 -1
  12. package/dist/component/schema.d.ts +726 -622
  13. package/dist/component/schema.d.ts.map +1 -1
  14. package/dist/component/streams.d.ts +6 -1
  15. package/dist/component/streams.d.ts.map +1 -1
  16. package/dist/component/streams.js +14 -15
  17. package/dist/component/streams.js.map +1 -1
  18. package/dist/component/threads.d.ts.map +1 -1
  19. package/dist/component/threads.js +1 -0
  20. package/dist/component/threads.js.map +1 -1
  21. package/dist/component/vector/index.d.ts +9 -9
  22. package/dist/component/vector/index.d.ts.map +1 -1
  23. package/dist/component/vector/tables.d.ts +5 -5
  24. package/dist/component/vector/tables.d.ts.map +1 -1
  25. package/dist/component/vector/tables.js +1 -1
  26. package/dist/component/vector/tables.js.map +1 -1
  27. package/dist/errors.d.ts +2 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +106 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/validators.d.ts +1952 -1952
  32. package/dist/vercel/client/definePlaygroundAPI.d.ts +132 -132
  33. package/dist/vercel/client/messages.d.ts +50 -32
  34. package/dist/vercel/client/messages.d.ts.map +1 -1
  35. package/dist/vercel/client/messages.js +2 -0
  36. package/dist/vercel/client/messages.js.map +1 -1
  37. package/dist/vercel/client/search.d.ts +475 -32
  38. package/dist/vercel/client/search.d.ts.map +1 -1
  39. package/dist/vercel/client/search.js +34 -9
  40. package/dist/vercel/client/search.js.map +1 -1
  41. package/dist/vercel/client/start.d.ts.map +1 -1
  42. package/dist/vercel/client/start.js +1 -0
  43. package/dist/vercel/client/start.js.map +1 -1
  44. package/dist/vercel/client/streamText.d.ts +1 -2
  45. package/dist/vercel/client/streamText.d.ts.map +1 -1
  46. package/dist/vercel/client/streamText.js +79 -22
  47. package/dist/vercel/client/streamText.js.map +1 -1
  48. package/dist/vercel/client/streaming.d.ts +326 -305
  49. package/dist/vercel/client/streaming.d.ts.map +1 -1
  50. package/dist/vercel/client/streaming.js +140 -25
  51. package/dist/vercel/client/streaming.js.map +1 -1
  52. package/dist/vercel/client/types.d.ts +2 -0
  53. package/dist/vercel/client/types.d.ts.map +1 -1
  54. package/dist/vercel/client/types.js.map +1 -1
  55. package/dist/vercel/client/utils.d.ts +9 -5
  56. package/dist/vercel/client/utils.d.ts.map +1 -1
  57. package/dist/vercel/client/utils.js +13 -13
  58. package/dist/vercel/client/utils.js.map +1 -1
  59. package/dist/vercel/index.d.ts +91 -90
  60. package/dist/vercel/index.d.ts.map +1 -1
  61. package/dist/vercel/index.js +2 -0
  62. package/dist/vercel/index.js.map +1 -1
  63. package/package.json +16 -9
  64. package/src/client/messages.ts +12 -0
  65. package/src/component/_generated/component.ts +10 -0
  66. package/src/component/messages.test.ts +404 -0
  67. package/src/component/messages.ts +94 -12
  68. package/src/component/streams.test.ts +54 -1
  69. package/src/component/streams.ts +20 -17
  70. package/src/component/threads.test.ts +39 -0
  71. package/src/component/threads.ts +1 -0
  72. package/src/component/vector/tables.ts +1 -1
  73. package/src/errors.test.ts +76 -0
  74. package/src/errors.ts +113 -0
  75. package/src/vercel/client/deltaFlush.test.ts +106 -0
  76. package/src/vercel/client/index.test.ts +47 -0
  77. package/src/vercel/client/messages.ts +20 -0
  78. package/src/vercel/client/search.test.ts +183 -8
  79. package/src/vercel/client/search.ts +46 -12
  80. package/src/vercel/client/start.ts +1 -0
  81. package/src/vercel/client/streamText.test.ts +577 -18
  82. package/src/vercel/client/streamText.ts +84 -32
  83. package/src/vercel/client/streaming.integration.test.ts +40 -3
  84. package/src/vercel/client/streaming.test.ts +101 -7
  85. package/src/vercel/client/streaming.throttle.test.ts +144 -0
  86. package/src/vercel/client/streaming.ts +161 -25
  87. package/src/vercel/client/types.ts +2 -0
  88. package/src/vercel/client/utils.test.ts +97 -0
  89. package/src/vercel/client/utils.ts +15 -15
  90. package/src/vercel/index.ts +2 -0
@@ -197,24 +197,29 @@ function publicStreamMessage(m: Doc<"streamingMessages">): StreamMessage {
197
197
  };
198
198
  }
199
199
 
200
+ export async function abortStreamsAtOrder(
201
+ ctx: MutationCtx,
202
+ args: { threadId: Id<"threads">; order: number; reason: string },
203
+ ) {
204
+ const streams = await ctx.db
205
+ .query("streamingMessages")
206
+ .withIndex("threadId_state_order_stepOrder", (q) =>
207
+ q
208
+ .eq("threadId", args.threadId)
209
+ .eq("state.kind", "streaming")
210
+ .eq("order", args.order),
211
+ )
212
+ .take(100);
213
+ for (const stream of streams) {
214
+ await abortById(ctx, { streamId: stream._id, reason: args.reason });
215
+ }
216
+ return streams.length > 0;
217
+ }
218
+
200
219
  export const abortByOrder = mutation({
201
220
  args: { threadId: v.id("threads"), order: v.number(), reason: v.string() },
202
221
  returns: v.boolean(),
203
- handler: async (ctx, args) => {
204
- const streams = await ctx.db
205
- .query("streamingMessages")
206
- .withIndex("threadId_state_order_stepOrder", (q) =>
207
- q
208
- .eq("threadId", args.threadId)
209
- .eq("state.kind", "streaming")
210
- .eq("order", args.order),
211
- )
212
- .take(100);
213
- for (const stream of streams) {
214
- await abortById(ctx, { streamId: stream._id, reason: args.reason });
215
- }
216
- return streams.length > 0;
217
- },
222
+ handler: abortStreamsAtOrder,
218
223
  });
219
224
 
220
225
  export const abort = mutation({
@@ -465,8 +470,6 @@ export async function deleteStreamsPageForThreadId(
465
470
  );
466
471
  let deltaCursor = args.deltaCursor;
467
472
  const streamMessage = await mergedStream(allStreamMessages, [
468
- "threadId",
469
- "state.kind",
470
473
  "order",
471
474
  "stepOrder",
472
475
  ]).first();
@@ -458,6 +458,45 @@ describe("threads", () => {
458
458
  vi.useRealTimers();
459
459
  });
460
460
 
461
+ test("deleteAllForThreadIdAsync preserves its limit across continuations", async () => {
462
+ vi.useFakeTimers();
463
+
464
+ const t = convexTest(schema, modules);
465
+ const thread = await t.mutation(api.threads.createThread, {
466
+ userId: "boundedDeleteUser",
467
+ title: "Bounded Delete Thread",
468
+ });
469
+ const threadId = thread._id as Id<"threads">;
470
+ await t.mutation(api.messages.addMessages, {
471
+ threadId,
472
+ messages: Array.from({ length: 4 }, (_, i) => ({
473
+ message: { role: "user" as const, content: `Message ${i}` },
474
+ })),
475
+ });
476
+
477
+ await t.mutation(api.threads.deleteAllForThreadIdAsync, {
478
+ threadId,
479
+ limit: 1,
480
+ });
481
+
482
+ vi.runOnlyPendingTimers();
483
+ await t.finishInProgressScheduledFunctions();
484
+ const afterOneContinuation = await t.query(
485
+ api.messages.listMessagesByThreadId,
486
+ {
487
+ threadId,
488
+ order: "asc",
489
+ paginationOpts: { cursor: null, numItems: 10 },
490
+ },
491
+ );
492
+
493
+ await t.finishAllScheduledFunctions(vi.runAllTimers);
494
+ vi.useRealTimers();
495
+
496
+ expect(afterOneContinuation.page).toHaveLength(2);
497
+ expect(await t.query(api.threads.getThread, { threadId })).toBeNull();
498
+ });
499
+
461
500
  test("deleteAllForThreadIdSync handles thread with no messages", async () => {
462
501
  const t = convexTest(schema, modules);
463
502
 
@@ -207,6 +207,7 @@ export const deleteAllForThreadIdAsync = mutation({
207
207
  if (!isDone) {
208
208
  await ctx.scheduler.runAfter(0, api.threads.deleteAllForThreadIdAsync, {
209
209
  threadId: args.threadId,
210
+ limit: args.limit,
210
211
  cursor: messagesResult.cursor,
211
212
  messagesDone: messagesResult.isDone,
212
213
  streamsDone: streamResult.isDone,
@@ -68,7 +68,7 @@ export type VectorSchema = SchemaDefinition<
68
68
  >;
69
69
 
70
70
  export const VectorDimensions = [
71
- 128, 256, 512, 768, 1024, 1408, 1536, 2048, 3072, 4096,
71
+ 128, 256, 512, 768, 1024, 1408, 1536, 2048, 2560, 3072, 4096,
72
72
  ] as const;
73
73
  export function validateVectorDimension(
74
74
  dimension: number,
@@ -0,0 +1,76 @@
1
+ import { APICallError } from "@ai-sdk/provider";
2
+ import { describe, expect, test } from "vitest";
3
+ import { errorToString } from "./errors.js";
4
+
5
+ describe("errorToString", () => {
6
+ test("preserves provider error classifications", () => {
7
+ const details = {
8
+ error: {
9
+ code: "invalid_prompt",
10
+ message: "Invalid prompt: flagged by policy",
11
+ },
12
+ };
13
+ const apiError = new APICallError({
14
+ message: "Invalid prompt: flagged by policy",
15
+ url: "https://api.example.test",
16
+ requestBodyValues: {},
17
+ statusCode: 400,
18
+ data: details,
19
+ });
20
+
21
+ expect(errorToString(details)).toBe(
22
+ "invalid_prompt: Invalid prompt: flagged by policy",
23
+ );
24
+ expect(errorToString(apiError)).toBe(
25
+ "invalid_prompt: Invalid prompt: flagged by policy",
26
+ );
27
+ expect(errorToString(new Error())).toBe("Error");
28
+ expect(errorToString(new TypeError())).toBe("TypeError");
29
+ const systemError = Object.assign(new Error("socket hang up"), {
30
+ code: "ECONNRESET",
31
+ });
32
+ expect(errorToString(systemError)).toBe("socket hang up");
33
+ const codeOnly = Object.assign(new Error("Request failed"), {
34
+ data: { code: "rate_limit" },
35
+ });
36
+ expect(errorToString(codeOnly)).toBe("rate_limit: Request failed");
37
+ });
38
+
39
+ test("serializes objects without mistaking shared values for cycles", () => {
40
+ const shared = { detail: "provider disconnected" };
41
+ const circular: Record<string, unknown> = { shared };
42
+ circular.self = circular;
43
+
44
+ expect(errorToString({ x: shared, y: shared })).toBe(
45
+ '{"x":{"detail":"provider disconnected"},"y":{"detail":"provider disconnected"}}',
46
+ );
47
+ expect(errorToString(circular)).toBe(
48
+ '{"shared":{"detail":"provider disconnected"},"self":"[Circular]"}',
49
+ );
50
+ });
51
+
52
+ test("bounds stored error text without splitting surrogate pairs", () => {
53
+ const serialized = errorToString(`${"x".repeat(1022)}😀tail`);
54
+
55
+ expect(serialized.length).toBeLessThanOrEqual(1024);
56
+ expect(serialized.endsWith("x…")).toBe(true);
57
+ });
58
+
59
+ test("does not throw when Error properties are hostile accessors", () => {
60
+ const error = new Error();
61
+ Object.defineProperties(error, {
62
+ message: {
63
+ get() {
64
+ throw new Error("message getter failed");
65
+ },
66
+ },
67
+ name: {
68
+ get() {
69
+ throw new Error("name getter failed");
70
+ },
71
+ },
72
+ });
73
+
74
+ expect(errorToString(error)).toBe("Unknown error");
75
+ });
76
+ });
package/src/errors.ts ADDED
@@ -0,0 +1,113 @@
1
+ const MAX_ERROR_LENGTH = 1024;
2
+
3
+ export function errorToString(error: unknown): string {
4
+ return truncateError(describeError(error));
5
+ }
6
+
7
+ function describeError(error: unknown): string {
8
+ if (typeof error === "string") return error;
9
+ if (error instanceof Error) {
10
+ const message = property(error, "message");
11
+ if (typeof message !== "string" || message.length === 0) {
12
+ const name = property(error, "name");
13
+ return typeof name === "string" && name.length > 0
14
+ ? name
15
+ : safeString(error);
16
+ }
17
+ const nested = errorDetails(
18
+ property(error, "error") ?? property(error, "data"),
19
+ );
20
+ return (
21
+ formatDetails({
22
+ message: nested.message ?? message,
23
+ code: nested.code,
24
+ }) ?? message
25
+ );
26
+ }
27
+
28
+ const details = formatDetails(errorDetails(error));
29
+ if (details) return details;
30
+
31
+ if (error && typeof error === "object") {
32
+ try {
33
+ const ancestors: object[] = [];
34
+ const serialized = JSON.stringify(error, function (_key, value: unknown) {
35
+ if (typeof value === "bigint") return value.toString();
36
+ if (!value || typeof value !== "object") return value;
37
+ while (ancestors.length > 0 && ancestors.at(-1) !== this) {
38
+ ancestors.pop();
39
+ }
40
+ if (ancestors.includes(value)) return "[Circular]";
41
+ ancestors.push(value);
42
+ return value;
43
+ });
44
+ if (serialized) return serialized;
45
+ } catch {
46
+ return safeString(error);
47
+ }
48
+ }
49
+
50
+ return safeString(error);
51
+ }
52
+
53
+ function safeString(error: unknown): string {
54
+ try {
55
+ return String(error);
56
+ } catch {
57
+ return "Unknown error";
58
+ }
59
+ }
60
+
61
+ function errorDetails(error: unknown): { message?: string; code?: string } {
62
+ let current = error;
63
+ let message: string | undefined;
64
+ let code: string | undefined;
65
+ for (let depth = 0; depth < 3; depth++) {
66
+ if (typeof current === "string") {
67
+ message ??= current;
68
+ break;
69
+ }
70
+ if (!current || typeof current !== "object") break;
71
+
72
+ const currentMessage = property(current, "message");
73
+ if (typeof currentMessage === "string" && currentMessage.length > 0) {
74
+ message ??= currentMessage;
75
+ }
76
+ const currentCode = property(current, "code");
77
+ if (typeof currentCode === "string" || typeof currentCode === "number") {
78
+ code ??= String(currentCode);
79
+ }
80
+ if (message && code) break;
81
+ current = property(current, "error") ?? property(current, "data");
82
+ }
83
+ return { message, code };
84
+ }
85
+
86
+ function property(value: object, key: string): unknown {
87
+ try {
88
+ return (value as Record<string, unknown>)[key];
89
+ } catch {
90
+ return undefined;
91
+ }
92
+ }
93
+
94
+ function formatDetails({
95
+ message,
96
+ code,
97
+ }: {
98
+ message?: string;
99
+ code?: string;
100
+ }): string | undefined {
101
+ if (message && code) {
102
+ return message.startsWith(`${code}:`) ? message : `${code}: ${message}`;
103
+ }
104
+ return message ?? code;
105
+ }
106
+
107
+ function truncateError(error: string): string {
108
+ if (error.length <= MAX_ERROR_LENGTH) return error;
109
+ let truncated = error.slice(0, MAX_ERROR_LENGTH - 1);
110
+ const last = truncated.charCodeAt(truncated.length - 1);
111
+ if (last >= 0xd800 && last <= 0xdbff) truncated = truncated.slice(0, -1);
112
+ return `${truncated}…`;
113
+ }
@@ -0,0 +1,106 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { z } from "zod";
3
+ import { stepCountIs } from "ai";
4
+ import { Agent, createTool, createThread } from "../index.js";
5
+ import {
6
+ actionGeneric,
7
+ anyApi,
8
+ defineSchema,
9
+ type ActionBuilder,
10
+ type ApiFromModules,
11
+ type DataModelFromSchemaDefinition,
12
+ } from "convex/server";
13
+ import { v } from "convex/values";
14
+ import { components, initConvexTest } from "./setup.test.js";
15
+ import { mockModel } from "./mockModel.js";
16
+
17
+ const schema = defineSchema({});
18
+ type DataModel = DataModelFromSchemaDefinition<typeof schema>;
19
+ const action = actionGeneric as ActionBuilder<DataModel, "public">;
20
+
21
+ // The AI SDK emits tool-input-available before the tool runs. The whole point
22
+ // of issue #221 is that a client can see it while the tool is still working,
23
+ // so the tool itself is where the delta log has to be read.
24
+ const partsSeenDuringTool: string[] = [];
25
+
26
+ const sleepTool = createTool({
27
+ description: "sleep",
28
+ inputSchema: z.object({ seconds: z.number() }),
29
+ execute: async (ctx) => {
30
+ const readTypes = async () => {
31
+ const streams = await ctx.runQuery(components.agent.streams.list, {
32
+ threadId: ctx.threadId!,
33
+ statuses: ["streaming"],
34
+ });
35
+ const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
36
+ threadId: ctx.threadId!,
37
+ cursors: streams.map((s) => ({ streamId: s.streamId, cursor: 0 })),
38
+ });
39
+ return deltas.flatMap((d) => d.parts.map((p) => p.type));
40
+ };
41
+ // Wait on the condition rather than on a fixed sleep chosen to outrun the
42
+ // throttle: there is no margin to tune, so machine load cannot decide the
43
+ // outcome. Without a scheduled flush this simply never arrives.
44
+ const deadline = Date.now() + 2_000;
45
+ let types = await readTypes();
46
+ while (!types.includes("tool-input-available") && Date.now() < deadline) {
47
+ await new Promise((resolve) => setTimeout(resolve, 5));
48
+ types = await readTypes();
49
+ }
50
+ partsSeenDuringTool.push(...types);
51
+ return { slept: 3 };
52
+ },
53
+ });
54
+
55
+ const agent = new Agent(components.agent, {
56
+ name: "slow-tool",
57
+ languageModel: mockModel({
58
+ contentSteps: [
59
+ [
60
+ {
61
+ type: "tool-call",
62
+ toolCallId: "t1",
63
+ toolName: "sleepTool",
64
+ input: JSON.stringify({ seconds: 3 }),
65
+ },
66
+ ],
67
+ [{ type: "text", text: "done" }],
68
+ ],
69
+ }),
70
+ tools: { sleepTool },
71
+ });
72
+
73
+ export const run = action({
74
+ args: { threadId: v.string() },
75
+ handler: async (ctx, { threadId }) => {
76
+ const r = await agent.streamText(
77
+ ctx,
78
+ { threadId },
79
+ { prompt: "go", stopWhen: stepCountIs(3) },
80
+ { saveStreamDeltas: { chunking: "word", throttleMs: 50 } },
81
+ );
82
+ await r.consumeStream();
83
+ return { ok: true };
84
+ },
85
+ });
86
+
87
+ const testApi: ApiFromModules<{ fns: { run: typeof run } }>["fns"] =
88
+ anyApi["deltaFlush.test"] as unknown as ApiFromModules<{
89
+ fns: { run: typeof run };
90
+ }>["fns"];
91
+
92
+ describe("throttled deltas flush on time (issue #221)", () => {
93
+ test("the tool call is queryable while the tool is still running", async () => {
94
+ partsSeenDuringTool.length = 0;
95
+ const t = initConvexTest(schema);
96
+ const threadId = await t.run(async (ctx) =>
97
+ createThread(ctx, components.agent, { userId: "u" }),
98
+ );
99
+
100
+ await t.action(testApi.run, { threadId });
101
+ await t.finishAllScheduledFunctions(() => {});
102
+
103
+ expect(partsSeenDuringTool).toContain("tool-input-available");
104
+ expect(partsSeenDuringTool).not.toContain("tool-output-available");
105
+ });
106
+ });
@@ -4,6 +4,8 @@ import {
4
4
  createThread,
5
5
  createTool,
6
6
  filterOutOrphanedToolMessages,
7
+ saveMessage,
8
+ toUIMessages,
7
9
  type MessageDoc,
8
10
  } from "../index.js";
9
11
  import type { DataModelFromSchemaDefinition } from "convex/server";
@@ -458,6 +460,51 @@ describe("Agent message operations", () => {
458
460
  expect(messages.length).toBe(2);
459
461
  expect(messages[1]._id).toBeDefined();
460
462
  });
463
+
464
+ test("saveMessage can place a standalone assistant message on a new order", async () => {
465
+ const t = initConvexTest(schema);
466
+ const threadId = await t.run(async (ctx) =>
467
+ createThread(ctx, components.agent, { userId: "operator-test" }),
468
+ );
469
+ const { message: agentReply } = await t.run(async (ctx) =>
470
+ agent.saveMessage(ctx, {
471
+ threadId,
472
+ message: {
473
+ role: "assistant",
474
+ content: [
475
+ {
476
+ type: "tool-call",
477
+ toolCallId: "handoff-1",
478
+ toolName: "handoff",
479
+ input: {},
480
+ },
481
+ ],
482
+ },
483
+ }),
484
+ );
485
+ const { message: operatorReply } = await t.run(async (ctx) =>
486
+ saveMessage(ctx, components.agent, {
487
+ threadId,
488
+ order: "next",
489
+ agentName: "human:Alex",
490
+ message: { role: "assistant", content: "Operator reply" },
491
+ }),
492
+ );
493
+ const uiMessages = toUIMessages([agentReply, operatorReply]);
494
+
495
+ expect(agentReply).toMatchObject({ order: 0, stepOrder: 0 });
496
+ expect(operatorReply).toMatchObject({
497
+ order: 1,
498
+ stepOrder: 0,
499
+ agentName: "human:Alex",
500
+ });
501
+ expect(uiMessages).toHaveLength(2);
502
+ expect(uiMessages[1]).toMatchObject({
503
+ order: 1,
504
+ agentName: "human:Alex",
505
+ text: "Operator reply",
506
+ });
507
+ });
461
508
  });
462
509
 
463
510
  describe("Agent text/object generation", () => {
@@ -11,6 +11,7 @@ import { toUIMessages, type UIMessage } from "../UIMessages.js";
11
11
  import {
12
12
  listMessages,
13
13
  saveMessages as saveCanonicalMessages,
14
+ type MessageOrder,
14
15
  } from "../../client/messages.js";
15
16
  import type {
16
17
  AgentComponent,
@@ -36,6 +37,15 @@ export async function listUIMessages(
36
37
  export type SaveMessagesArgs = {
37
38
  threadId: string;
38
39
  userId?: string | null;
40
+ /**
41
+ * Save the first message at this order. Pass `"next"` to allocate a new
42
+ * order after the current latest message. If the numeric order already
43
+ * contains messages, the message is appended at the next stepOrder.
44
+ * Numeric orders must be non-negative safe integers less than
45
+ * Number.MAX_SAFE_INTEGER.
46
+ * Cannot be combined with promptMessageId or pendingMessageId.
47
+ */
48
+ order?: MessageOrder;
39
49
  /**
40
50
  * The message that these messages are in response to. They will be
41
51
  * the same "order" as this message, at increasing stepOrder(s).
@@ -86,6 +96,7 @@ export async function saveMessages(
86
96
  userId: args.userId ?? undefined,
87
97
  agentName: args.agentName,
88
98
  promptMessageId: args.promptMessageId,
99
+ order: args.order,
89
100
  pendingMessageId: args.pendingMessageId,
90
101
  embeddings: args.embeddings,
91
102
  messages: serialized.map(({ message }) => message),
@@ -105,6 +116,14 @@ export async function saveMessages(
105
116
  export type SaveMessageArgs = {
106
117
  threadId: string;
107
118
  userId?: string | null;
119
+ /**
120
+ * Save the message at this order. Pass `"next"` to allocate a new order
121
+ * after the current latest message. If the numeric order already contains
122
+ * messages, the message is appended at the next stepOrder. Numeric orders
123
+ * must be non-negative safe integers less than Number.MAX_SAFE_INTEGER.
124
+ * Cannot be combined with promptMessageId or pendingMessageId.
125
+ */
126
+ order?: MessageOrder;
108
127
  /**
109
128
  * The message that these messages are in response to. They will be
110
129
  * the same "order" as this message, at increasing stepOrder(s).
@@ -169,6 +188,7 @@ export async function saveMessage(
169
188
  userId: args.userId ?? undefined,
170
189
  agentName: args.agentName,
171
190
  promptMessageId: args.promptMessageId,
191
+ order: args.order,
172
192
  pendingMessageId: args.pendingMessageId,
173
193
  messages:
174
194
  args.prompt !== undefined