@convex-dev/agent 0.2.6 → 0.2.8-alpha.0

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 (112) hide show
  1. package/dist/UIMessages.d.ts +43 -0
  2. package/dist/UIMessages.d.ts.map +1 -0
  3. package/dist/{react/toUIMessages.js → UIMessages.js} +97 -4
  4. package/dist/UIMessages.js.map +1 -0
  5. package/dist/client/createTool.js +1 -1
  6. package/dist/client/createTool.js.map +1 -1
  7. package/dist/client/index.d.ts +39 -19
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +22 -19
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/mockModel.d.ts +2 -0
  12. package/dist/client/mockModel.d.ts.map +1 -1
  13. package/dist/client/mockModel.js +11 -7
  14. package/dist/client/mockModel.js.map +1 -1
  15. package/dist/client/search.d.ts.map +1 -1
  16. package/dist/client/search.js +2 -0
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/start.d.ts +1 -0
  19. package/dist/client/start.d.ts.map +1 -1
  20. package/dist/client/start.js +0 -1
  21. package/dist/client/start.js.map +1 -1
  22. package/dist/client/streaming.d.ts +23 -4
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +32 -6
  25. package/dist/client/streaming.js.map +1 -1
  26. package/dist/client/types.d.ts +19 -13
  27. package/dist/client/types.d.ts.map +1 -1
  28. package/dist/component/_generated/api.d.ts +2 -0
  29. package/dist/component/messages.js +1 -1
  30. package/dist/component/messages.js.map +1 -1
  31. package/dist/component/schema.d.ts +10 -4
  32. package/dist/component/schema.d.ts.map +1 -1
  33. package/dist/component/schema.js +4 -0
  34. package/dist/component/schema.js.map +1 -1
  35. package/dist/component/streams.d.ts +2 -4
  36. package/dist/component/streams.d.ts.map +1 -1
  37. package/dist/component/streams.js +42 -43
  38. package/dist/component/streams.js.map +1 -1
  39. package/dist/deltas.d.ts +48 -0
  40. package/dist/deltas.d.ts.map +1 -0
  41. package/dist/deltas.js +468 -0
  42. package/dist/deltas.js.map +1 -0
  43. package/dist/react/index.d.ts +4 -77
  44. package/dist/react/index.d.ts.map +1 -1
  45. package/dist/react/index.js +4 -178
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  48. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  49. package/dist/react/optimisticallySendMessage.js +37 -5
  50. package/dist/react/optimisticallySendMessage.js.map +1 -1
  51. package/dist/react/types.d.ts +4 -18
  52. package/dist/react/types.d.ts.map +1 -1
  53. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  54. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  55. package/dist/react/useStreamingUIMessages.js +204 -0
  56. package/dist/react/useStreamingUIMessages.js.map +1 -0
  57. package/dist/react/useThreadMessages.d.ts +103 -0
  58. package/dist/react/useThreadMessages.d.ts.map +1 -0
  59. package/dist/react/useThreadMessages.js +139 -0
  60. package/dist/react/useThreadMessages.js.map +1 -0
  61. package/dist/react/useUIMessages.d.ts +91 -0
  62. package/dist/react/useUIMessages.d.ts.map +1 -0
  63. package/dist/react/useUIMessages.js +115 -0
  64. package/dist/react/useUIMessages.js.map +1 -0
  65. package/dist/validators.d.ts +3 -1
  66. package/dist/validators.d.ts.map +1 -1
  67. package/dist/validators.js +1 -0
  68. package/dist/validators.js.map +1 -1
  69. package/package.json +4 -4
  70. package/src/{react/toUIMessages.ts → UIMessages.ts} +132 -15
  71. package/src/client/createTool.ts +1 -1
  72. package/src/client/index.ts +60 -41
  73. package/src/client/mockModel.ts +13 -8
  74. package/src/client/search.ts +2 -0
  75. package/src/client/start.ts +2 -1
  76. package/src/client/streaming.test.ts +186 -0
  77. package/src/client/streaming.ts +50 -9
  78. package/src/client/types.ts +19 -21
  79. package/src/component/_generated/api.d.ts +2 -0
  80. package/src/component/messages.ts +1 -1
  81. package/src/component/schema.ts +6 -0
  82. package/src/component/streams.ts +49 -66
  83. package/src/{react/deltas.test.ts → deltas.test.ts} +57 -46
  84. package/src/deltas.ts +589 -0
  85. package/src/{react/fromUIMessages.test.ts → fromUIMessages.test.ts} +108 -43
  86. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  87. package/src/react/index.ts +8 -250
  88. package/src/react/optimisticallySendMessage.ts +57 -8
  89. package/src/react/types.ts +6 -39
  90. package/src/react/useStreamingUIMessages.ts +294 -0
  91. package/src/react/useThreadMessages.ts +251 -0
  92. package/src/react/useUIMessages.ts +206 -0
  93. package/src/{react/toUIMessages.test.ts → toUIMessages.test.ts} +3 -3
  94. package/src/validators.ts +3 -0
  95. package/dist/client/textStreamParts.d.ts +0 -5
  96. package/dist/client/textStreamParts.d.ts.map +0 -1
  97. package/dist/client/textStreamParts.js +0 -44
  98. package/dist/client/textStreamParts.js.map +0 -1
  99. package/dist/react/deltas.d.ts +0 -27
  100. package/dist/react/deltas.d.ts.map +0 -1
  101. package/dist/react/deltas.js +0 -627
  102. package/dist/react/deltas.js.map +0 -1
  103. package/dist/react/fromUIMessages.d.ts +0 -13
  104. package/dist/react/fromUIMessages.d.ts.map +0 -1
  105. package/dist/react/fromUIMessages.js +0 -70
  106. package/dist/react/fromUIMessages.js.map +0 -1
  107. package/dist/react/toUIMessages.d.ts +0 -19
  108. package/dist/react/toUIMessages.d.ts.map +0 -1
  109. package/dist/react/toUIMessages.js.map +0 -1
  110. package/src/client/textStreamParts.ts +0 -62
  111. package/src/react/deltas.ts +0 -774
  112. package/src/react/fromUIMessages.ts +0 -85
@@ -21,7 +21,6 @@ import type {
21
21
  Options,
22
22
  UserActionCtx,
23
23
  } from "./types.js";
24
- import { saveMessages } from "./messages.js";
25
24
  import type { Message, MessageDoc } from "../validators.js";
26
25
  import {
27
26
  getModelName,
@@ -105,6 +104,7 @@ export async function start<
105
104
  system?: string;
106
105
  model: LanguageModel;
107
106
  messages: ModelMessage[];
107
+ prompt?: never;
108
108
  tools?: Tools;
109
109
  } & CallSettings;
110
110
  order: number;
@@ -200,6 +200,7 @@ export async function start<
200
200
  } as T & {
201
201
  model: LanguageModel;
202
202
  messages: ModelMessage[];
203
+ prompt?: never;
203
204
  tools?: Tools;
204
205
  _internal?: { generateId?: IdGenerator };
205
206
  } & CallSettings;
@@ -0,0 +1,186 @@
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import { createThread } from "./index.js";
3
+ import type { GenericSchema, SchemaDefinition } from "convex/server";
4
+ import { streamText } from "ai";
5
+ import { components, initConvexTest } from "./setup.test.js";
6
+ import { mockModel } from "./mockModel.js";
7
+ import { DeltaStreamer } from "./streaming.js";
8
+ import { compressUIMessageChunks, getParts } from "../deltas.js";
9
+ import type { TestConvex } from "convex-test";
10
+
11
+ const defaultTestOptions = {
12
+ throttleMs: 0,
13
+ abortSignal: undefined,
14
+ compress: null,
15
+ onAsyncAbort: async () => {
16
+ throw new Error("unexpected");
17
+ },
18
+ };
19
+
20
+ const testMetadata = {
21
+ order: 0,
22
+ stepOrder: 0,
23
+ agentName: "test agent",
24
+ model: "test model",
25
+ provider: "test provider",
26
+ providerOptions: {},
27
+ format: "UIMessageChunk" as const,
28
+ };
29
+
30
+ describe("DeltaStreamer", () => {
31
+ let t: TestConvex<SchemaDefinition<GenericSchema, boolean>>;
32
+ let threadId: string;
33
+ beforeEach(async () => {
34
+ t = initConvexTest();
35
+ await t.run(async (ctx) => {
36
+ threadId = await createThread(ctx, components.agent, {});
37
+ });
38
+ });
39
+ test("should save chunks via DeltaStreamer", async () => {
40
+ await t.run(async (ctx) => {
41
+ const streamer = new DeltaStreamer(
42
+ components.agent,
43
+ ctx,
44
+ { ...defaultTestOptions },
45
+ { ...testMetadata, threadId },
46
+ );
47
+ const result = streamText({
48
+ model: mockModel(),
49
+ prompt: "Test prompt",
50
+ });
51
+ await streamer.consumeStream(result.toUIMessageStream());
52
+ const streamId = streamer.streamId!;
53
+ expect(streamId).toBeDefined();
54
+ const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
55
+ threadId,
56
+ cursors: [{ cursor: 0, streamId }],
57
+ });
58
+ const { parts } = getParts(deltas);
59
+ const stream = result.toUIMessageStream();
60
+ for await (const part of stream) {
61
+ const expected = parts.shift();
62
+ expect(part).toEqual(expected);
63
+ }
64
+ });
65
+ });
66
+ test("should save all parts when throttleMs is 0", async () => {
67
+ await t.run(async (ctx) => {
68
+ const streamer = new DeltaStreamer(
69
+ components.agent,
70
+ ctx,
71
+ { ...defaultTestOptions, throttleMs: 0 },
72
+ { ...testMetadata, threadId },
73
+ );
74
+ const result = streamText({
75
+ model: mockModel({
76
+ content: [
77
+ // The mockModel splits these into deltas based on spaces
78
+ { type: "text", text: "A B C" },
79
+ { type: "reasoning", text: "D E F" },
80
+ ],
81
+ }),
82
+ prompt: "Test prompt",
83
+ });
84
+ await streamer.consumeStream(result.toUIMessageStream());
85
+ const streamId = streamer.streamId!;
86
+ expect(streamId).toBeDefined();
87
+ const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
88
+ threadId,
89
+ cursors: [{ cursor: 0, streamId }],
90
+ });
91
+ const { parts } = getParts(deltas);
92
+ const expected = [
93
+ { type: "start" },
94
+ { type: "start-step" },
95
+ { type: "text-start" },
96
+ { type: "text-delta", delta: "A" },
97
+ { type: "text-delta", delta: " B" },
98
+ { type: "text-delta", delta: " C" },
99
+ { type: "text-end" },
100
+ { type: "reasoning-start" },
101
+ { type: "reasoning-delta", delta: "D" },
102
+ { type: "reasoning-delta", delta: " E" },
103
+ { type: "reasoning-delta", delta: " F" },
104
+ { type: "reasoning-end" },
105
+ { type: "finish-step" },
106
+ { type: "finish" },
107
+ ];
108
+ for (const expectedPart of expected) {
109
+ const part = parts.shift();
110
+ expect(part).toBeDefined();
111
+ expect(part).toMatchObject(expectedPart);
112
+ }
113
+ });
114
+ });
115
+
116
+ test("should save compressed parts via DeltaStreamer", async () => {
117
+ await t.run(async (ctx) => {
118
+ const streamer = new DeltaStreamer(
119
+ components.agent,
120
+ ctx,
121
+ {
122
+ throttleMs: 1000,
123
+ abortSignal: undefined,
124
+ compress: compressUIMessageChunks,
125
+ onAsyncAbort: async () => {
126
+ throw new Error("async abort");
127
+ },
128
+ },
129
+ {
130
+ ...testMetadata,
131
+ threadId,
132
+ },
133
+ );
134
+ const result = streamText({
135
+ model: mockModel({
136
+ content: [
137
+ // The mockModel splits these into deltas based on spaces
138
+ { type: "text", text: "A B C" },
139
+ { type: "text", text: "D E F" },
140
+ { type: "reasoning", text: "J K L" },
141
+ { type: "text", text: "M N O" },
142
+ ],
143
+ }),
144
+ prompt: "Test prompt",
145
+ // experimental_transform: smoothStream({ chunking: "line" }),
146
+ onError: (error) => {
147
+ console.error(error);
148
+ },
149
+ });
150
+ await streamer.consumeStream(result.toUIMessageStream());
151
+ const streamId = streamer.streamId!;
152
+ expect(streamId).toBeDefined();
153
+ const deltas = await ctx.runQuery(components.agent.streams.listDeltas, {
154
+ threadId,
155
+ cursors: [{ cursor: 0, streamId }],
156
+ });
157
+ const { parts } = getParts(deltas);
158
+ const expected = [
159
+ { type: "start" },
160
+ { type: "start-step" },
161
+ { type: "text-start" },
162
+ // These are collapsed into a single delta
163
+ { type: "text-delta", delta: "A B C" },
164
+ { type: "text-end" },
165
+ { type: "text-start" },
166
+ { type: "text-delta", delta: "D E F" },
167
+ { type: "text-end" },
168
+ { type: "reasoning-start" },
169
+ { type: "reasoning-delta", delta: "J K L" },
170
+ { type: "reasoning-end" },
171
+ { type: "text-start" },
172
+ { type: "text-delta", delta: "M N O" },
173
+ { type: "text-end" },
174
+ { type: "finish-step" },
175
+ { type: "finish" },
176
+ ];
177
+ for (const expectedPart of expected) {
178
+ const part = parts.shift();
179
+ expect(part).toBeDefined();
180
+ expect(part).toMatchObject(expectedPart);
181
+ }
182
+ });
183
+ });
184
+ // TODO: test errors & aborted states
185
+ // TODO: test fetching partial stream data - syncStreams w/ cursors
186
+ });
@@ -1,4 +1,10 @@
1
- import { type ChunkDetector } from "ai";
1
+ import {
2
+ type ChunkDetector,
3
+ type AsyncIterableStream,
4
+ type StreamTextTransform,
5
+ type ToolSet,
6
+ smoothStream,
7
+ } from "ai";
2
8
  import {
3
9
  vStreamDelta,
4
10
  vStreamMessage,
@@ -152,6 +158,36 @@ export const DEFAULT_STREAMING_OPTIONS = {
152
158
  returnImmediately: false,
153
159
  } satisfies StreamingOptions;
154
160
 
161
+ /**
162
+ *
163
+ * @param options The options passed to `agent.streamText` to decide whether to
164
+ * save deltas while streaming.
165
+ * @param existing The transforms passed to `agent.streamText` to merge with.
166
+ * @returns The merged transforms to pass to the underlying `streamText` call.
167
+ */
168
+ export function mergeTransforms<TOOLS extends ToolSet>(
169
+ options: { chunking?: StreamingOptions["chunking"] } | boolean | undefined,
170
+ existing:
171
+ | StreamTextTransform<TOOLS>
172
+ | Array<StreamTextTransform<TOOLS>>
173
+ | undefined,
174
+ ) {
175
+ if (!options) {
176
+ return existing;
177
+ }
178
+ const chunking =
179
+ typeof options === "boolean"
180
+ ? DEFAULT_STREAMING_OPTIONS.chunking
181
+ : options.chunking;
182
+ const transforms = Array.isArray(existing)
183
+ ? existing
184
+ : existing
185
+ ? [existing]
186
+ : [];
187
+ transforms.push(smoothStream({ delayInMs: null, chunking }));
188
+ return transforms;
189
+ }
190
+
155
191
  /**
156
192
  * DeltaStreamer can be used to save a stream of "parts" by writing
157
193
  * batches of them in "deltas" to the database so clients can subscribe
@@ -162,7 +198,7 @@ export const DEFAULT_STREAMING_OPTIONS = {
162
198
  export class DeltaStreamer<T> {
163
199
  public streamId: string | undefined;
164
200
  public readonly config: {
165
- stream: Required<StreamingOptions>;
201
+ throttleMs: number;
166
202
  onAsyncAbort: (reason: string) => Promise<void>;
167
203
  compress: ((parts: T[]) => T[]) | null;
168
204
  };
@@ -176,7 +212,7 @@ export class DeltaStreamer<T> {
176
212
  public readonly component: AgentComponent,
177
213
  public readonly ctx: RunMutationCtx,
178
214
  config: {
179
- stream: true | StreamingOptions;
215
+ throttleMs: number | undefined;
180
216
  onAsyncAbort: (reason: string) => Promise<void>;
181
217
  abortSignal: AbortSignal | undefined;
182
218
  compress: ((parts: T[]) => T[]) | null;
@@ -190,13 +226,11 @@ export class DeltaStreamer<T> {
190
226
  model?: string;
191
227
  provider?: string;
192
228
  providerOptions?: ProviderOptions;
229
+ format: "UIMessageChunk" | "TextStreamPart" | undefined;
193
230
  },
194
231
  ) {
195
232
  this.config = {
196
- stream:
197
- config.stream === true
198
- ? DEFAULT_STREAMING_OPTIONS
199
- : { ...DEFAULT_STREAMING_OPTIONS, ...config },
233
+ throttleMs: config.throttleMs ?? DEFAULT_STREAMING_OPTIONS.throttleMs,
200
234
  onAsyncAbort: config.onAsyncAbort,
201
235
  compress: config.compress,
202
236
  };
@@ -232,12 +266,19 @@ export class DeltaStreamer<T> {
232
266
  this.#nextParts.push(...parts);
233
267
  if (
234
268
  !this.#ongoingWrite &&
235
- Date.now() - this.#latestWrite >= this.config.stream.throttleMs
269
+ Date.now() - this.#latestWrite >= this.config.throttleMs
236
270
  ) {
237
271
  this.#ongoingWrite = this.#sendDelta();
238
272
  }
239
273
  }
240
274
 
275
+ public async consumeStream(stream: AsyncIterableStream<T>) {
276
+ for await (const chunk of stream) {
277
+ await this.addParts([chunk]);
278
+ }
279
+ await this.finish();
280
+ }
281
+
241
282
  async #sendDelta() {
242
283
  if (this.abortController.signal.aborted) {
243
284
  return;
@@ -267,7 +308,7 @@ export class DeltaStreamer<T> {
267
308
  // Now that we've sent the delta, check if we need to send another one.
268
309
  if (
269
310
  this.#nextParts.length > 0 &&
270
- Date.now() - this.#latestWrite >= this.config.stream.throttleMs
311
+ Date.now() - this.#latestWrite >= this.config.throttleMs
271
312
  ) {
272
313
  // We send again immediately with the accumulated deltas.
273
314
  this.#ongoingWrite = this.#sendDelta();
@@ -388,7 +388,7 @@ export type GenerateObjectArgs<
388
388
  ? Array<InferSchema<SCHEMA>>
389
389
  : InferSchema<SCHEMA>,
390
390
  > = Omit<CallSettings, "stopSequences"> &
391
- Prompt & {
391
+ Partial<Prompt> & {
392
392
  /**
393
393
  * If provided, this message will be used as the "prompt" for the LLM call,
394
394
  * instead of the prompt or messages.
@@ -555,7 +555,15 @@ export interface Thread<DefaultTools extends ToolSet> {
555
555
  TOOLS,
556
556
  OUTPUT,
557
557
  OUTPUT_PARTIAL
558
- >,
558
+ > & {
559
+ /**
560
+ * If provided, this message will be used as the "prompt" for the LLM call,
561
+ * instead of the prompt or messages.
562
+ * This is useful if you want to first save a user message, then use it as
563
+ * the prompt for the LLM call in another call.
564
+ */
565
+ promptMessageId?: string;
566
+ },
559
567
  options?: Options,
560
568
  ): Promise<
561
569
  GenerateTextResult<TOOLS extends undefined ? DefaultTools : TOOLS, OUTPUT> &
@@ -582,7 +590,15 @@ export interface Thread<DefaultTools extends ToolSet> {
582
590
  TOOLS,
583
591
  OUTPUT,
584
592
  PARTIAL_OUTPUT
585
- >,
593
+ > & {
594
+ /**
595
+ * If provided, this message will be used as the "prompt" for the LLM call,
596
+ * instead of the prompt or messages.
597
+ * This is useful if you want to first save a user message, then use it as
598
+ * the prompt for the LLM call in another call.
599
+ */
600
+ promptMessageId?: string;
601
+ },
586
602
  options?: Options & {
587
603
  /**
588
604
  * Whether to save incremental data (deltas) from streaming responses.
@@ -630,15 +646,6 @@ export interface Thread<DefaultTools extends ToolSet> {
630
646
  * the prompt for the LLM call in another call.
631
647
  */
632
648
  promptMessageId?: string;
633
- /**
634
- * The model to use for the LLM calls. This will override the model specified
635
- * in the Agent constructor.
636
- */
637
- model?: LanguageModel;
638
- /**
639
- * The tools to use for the tool calls. This will override tools specified
640
- * in the Agent constructor or createThread / continueThread.
641
- */
642
649
  },
643
650
  options?: Options,
644
651
  ): Promise<GenerateObjectResult<RESULT> & ThreadOutputMetadata>;
@@ -672,15 +679,6 @@ export interface Thread<DefaultTools extends ToolSet> {
672
679
  * the prompt for the LLM call in another call.
673
680
  */
674
681
  promptMessageId?: string;
675
- /**
676
- * The model to use for the LLM calls. This will override the model specified
677
- * in the Agent constructor.
678
- */
679
- model?: LanguageModel;
680
- /**
681
- * The tools to use for the tool calls. This will override tools specified
682
- * in the Agent constructor or createThread / continueThread.
683
- */
684
682
  },
685
683
  options?: Options,
686
684
  ): Promise<
@@ -2189,6 +2189,7 @@ export type Mounts = {
2189
2189
  "public",
2190
2190
  {
2191
2191
  agentName?: string;
2192
+ format?: "UIMessageChunk" | "TextStreamPart";
2192
2193
  model?: string;
2193
2194
  order: number;
2194
2195
  provider?: string;
@@ -2253,6 +2254,7 @@ export type Mounts = {
2253
2254
  },
2254
2255
  Array<{
2255
2256
  agentName?: string;
2257
+ format?: "UIMessageChunk" | "TextStreamPart";
2256
2258
  model?: string;
2257
2259
  order: number;
2258
2260
  provider?: string;
@@ -344,7 +344,7 @@ export const finalizeMessage = mutation({
344
344
  const message = await ctx.db.get(messageId);
345
345
  assert(message, `Message ${messageId} not found`);
346
346
  if (message.status !== "pending") {
347
- console.log(
347
+ console.debug(
348
348
  "Trying to finalize a message that's already",
349
349
  message.status,
350
350
  );
@@ -91,6 +91,12 @@ export const schema = defineSchema({
91
91
  model: v.optional(v.string()),
92
92
  provider: v.optional(v.string()),
93
93
  providerOptions: v.optional(vProviderOptions), // Sent to model
94
+ // The data format for the deltas. By default, we use UIMessageChunks.
95
+ // This format dictates how the messages are materialized for capturing
96
+ // partial messages during failure, as well as on the client side.
97
+ format: v.optional(
98
+ v.union(v.literal("UIMessageChunk"), v.literal("TextStreamPart")),
99
+ ),
94
100
 
95
101
  threadId: v.id("threads"),
96
102
  order: v.number(),
@@ -20,8 +20,8 @@ import { stream } from "convex-helpers/server/stream";
20
20
  import { mergedStream } from "convex-helpers/server/stream";
21
21
  import { paginator } from "convex-helpers/server/pagination";
22
22
  import type { WithoutSystemFields } from "convex/server";
23
- import { mergeDeltas } from "../react/deltas.js";
24
- import { serializeOrThrow } from "../mapping.js";
23
+ import { deriveUIMessagesFromDeltas } from "../deltas.js";
24
+ import { fromUIMessages } from "../UIMessages.js";
25
25
 
26
26
  const SECOND = 1000;
27
27
  const MINUTE = 60 * SECOND;
@@ -140,6 +140,7 @@ export const list = query({
140
140
  streamId: m._id,
141
141
  status: m.state.kind,
142
142
  ...pick(m, [
143
+ "format",
143
144
  "order",
144
145
  "stepOrder",
145
146
  "userId",
@@ -516,72 +517,54 @@ export async function getStreamingMessagesWithMetadata(
516
517
  );
517
518
  const messages = (
518
519
  await Promise.all(
519
- streamingMessages.map((m) =>
520
- getMessagesWithMetadataForStreamingMessage(
521
- ctx,
520
+ streamingMessages.map(async (streamingMessage) => {
521
+ const deltas = await ctx.db
522
+ .query("streamDeltas")
523
+ .withIndex("streamId_start_end", (q) =>
524
+ q.eq("streamId", streamingMessage._id),
525
+ )
526
+ .take(1000);
527
+ const uiMessages = await deriveUIMessagesFromDeltas(
522
528
  threadId,
523
- stepOrder,
524
- m,
525
- metadata,
526
- ),
527
- ),
529
+ [
530
+ {
531
+ ...streamingMessage,
532
+ status: "streaming",
533
+ streamId: streamingMessage._id,
534
+ },
535
+ ],
536
+ deltas,
537
+ );
538
+ // We don't save messages that have already been saved
539
+ const numToSkip = stepOrder - streamingMessage.stepOrder;
540
+ const messages = await Promise.all(
541
+ fromUIMessages(uiMessages, streamingMessage)
542
+ .slice(numToSkip)
543
+ .filter((m) => m.message !== undefined)
544
+ .map(async (msg) => {
545
+ return {
546
+ ...pick(msg, [
547
+ "message",
548
+ "fileIds",
549
+ "status",
550
+ "finishReason",
551
+ "model",
552
+ "provider",
553
+ "providerMetadata",
554
+ "sources",
555
+ "reasoning",
556
+ "reasoningDetails",
557
+ "usage",
558
+ "warnings",
559
+ "error",
560
+ ]),
561
+ ...metadata,
562
+ } as MessageWithMetadataInternal;
563
+ }),
564
+ );
565
+ return messages;
566
+ }),
528
567
  )
529
568
  ).flat();
530
569
  return messages;
531
570
  }
532
-
533
- export async function getMessagesWithMetadataForStreamingMessage(
534
- ctx: MutationCtx,
535
- threadId: Id<"threads">,
536
- stepOrder: number,
537
- streamingMessage: Doc<"streamingMessages">,
538
- metadata: { status: "success" | "failed"; error?: string },
539
- ): Promise<MessageWithMetadataInternal[]> {
540
- const deltas = await ctx.db
541
- .query("streamDeltas")
542
- .withIndex("streamId_start_end", (q) =>
543
- q.eq("streamId", streamingMessage._id),
544
- )
545
- .take(1000);
546
- const [messageDocs] = mergeDeltas(
547
- threadId,
548
- [
549
- {
550
- ...streamingMessage,
551
- status: "streaming",
552
- streamId: streamingMessage._id,
553
- },
554
- ],
555
- [],
556
- deltas,
557
- );
558
- // We don't save messages that have already been saved
559
- const numToSkip = stepOrder - streamingMessage.stepOrder;
560
- const messages = await Promise.all(
561
- messageDocs
562
- .slice(numToSkip)
563
- .filter((m) => m.message !== undefined)
564
- .map(async (msg) => {
565
- const message = await serializeOrThrow(msg.message!);
566
- return {
567
- message,
568
- ...pick(msg, [
569
- "fileIds",
570
- "status",
571
- "finishReason",
572
- "model",
573
- "provider",
574
- "providerMetadata",
575
- "sources",
576
- "reasoning",
577
- "reasoningDetails",
578
- "usage",
579
- "warnings",
580
- "error",
581
- ]),
582
- ...metadata,
583
- } as MessageWithMetadataInternal;
584
- }),
585
- );
586
- return messages;
587
- }