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

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 (48) hide show
  1. package/README.md +32 -693
  2. package/dist/client/index.d.ts +26 -26
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +46 -17
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/search.d.ts +3 -3
  7. package/dist/client/streaming.d.ts +24 -2
  8. package/dist/client/streaming.d.ts.map +1 -1
  9. package/dist/client/streaming.js +35 -4
  10. package/dist/client/streaming.js.map +1 -1
  11. package/dist/client/types.d.ts +2 -3
  12. package/dist/client/types.d.ts.map +1 -1
  13. package/dist/component/_generated/api.d.ts +7 -1
  14. package/dist/component/messages.d.ts +40 -40
  15. package/dist/component/streams.d.ts +8 -3
  16. package/dist/component/streams.d.ts.map +1 -1
  17. package/dist/component/streams.js +40 -14
  18. package/dist/component/streams.js.map +1 -1
  19. package/dist/component/threads.d.ts +18 -18
  20. package/dist/component/users.d.ts +3 -3
  21. package/dist/mapping.d.ts +5 -0
  22. package/dist/mapping.d.ts.map +1 -1
  23. package/dist/mapping.js +8 -1
  24. package/dist/mapping.js.map +1 -1
  25. package/dist/react/deltas.d.ts.map +1 -1
  26. package/dist/react/deltas.js +20 -1
  27. package/dist/react/deltas.js.map +1 -1
  28. package/dist/react/index.js +1 -1
  29. package/dist/react/index.js.map +1 -1
  30. package/dist/react/optimisticallySendMessage.d.ts +1 -0
  31. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  32. package/dist/react/optimisticallySendMessage.js +8 -1
  33. package/dist/react/optimisticallySendMessage.js.map +1 -1
  34. package/dist/react/toUIMessages.d.ts +1 -0
  35. package/dist/react/toUIMessages.d.ts.map +1 -1
  36. package/dist/react/toUIMessages.js +2 -0
  37. package/dist/react/toUIMessages.js.map +1 -1
  38. package/package.json +3 -2
  39. package/src/client/index.ts +63 -26
  40. package/src/client/streaming.ts +55 -4
  41. package/src/client/types.ts +2 -4
  42. package/src/component/_generated/api.d.ts +7 -1
  43. package/src/component/streams.ts +49 -16
  44. package/src/mapping.ts +8 -1
  45. package/src/react/deltas.ts +24 -3
  46. package/src/react/index.ts +1 -1
  47. package/src/react/optimisticallySendMessage.ts +11 -1
  48. package/src/react/toUIMessages.ts +3 -0
@@ -1,5 +1,6 @@
1
1
  import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
2
2
  import type {
3
+ AssistantContent,
3
4
  CoreMessage,
4
5
  DeepPartial,
5
6
  FilePart,
@@ -10,6 +11,7 @@ import type {
10
11
  StreamObjectResult,
11
12
  StreamTextResult,
12
13
  ToolSet,
14
+ UserContent,
13
15
  } from "ai";
14
16
  import { generateObject, generateText, streamObject, streamText } from "ai";
15
17
  import { assert } from "convex-helpers";
@@ -22,6 +24,7 @@ import {
22
24
  } from "convex/server";
23
25
  import { v } from "convex/values";
24
26
  import type { MessageDoc, ThreadDoc } from "../component/schema.js";
27
+ import type { threadFieldsSupportingPatch } from "../component/threads.js";
25
28
  import {
26
29
  validateVectorDimension,
27
30
  type VectorDimension,
@@ -36,23 +39,27 @@ import {
36
39
  } from "../mapping.js";
37
40
  import { extractText, isTool } from "../shared.js";
38
41
  import {
39
- type MessageWithMetadata,
42
+ type MessageEmbeddings,
40
43
  type MessageStatus,
44
+ type MessageWithMetadata,
41
45
  type ProviderMetadata,
42
46
  type StreamArgs,
43
47
  type Usage,
44
48
  vMessageWithMetadata,
45
49
  vSafeObjectArgs,
46
50
  vTextArgs,
47
- type MessageEmbeddings,
48
51
  } from "../validators.js";
49
52
  import { createTool, wrapTools } from "./createTool.js";
53
+ import { listMessages } from "./listMessages.js";
54
+ import { fetchContextMessages } from "./search.js";
50
55
  import {
51
56
  DeltaStreamer,
52
57
  mergeTransforms,
53
58
  type StreamingOptions,
59
+ syncStreams,
54
60
  } from "./streaming.js";
55
61
  import type {
62
+ ActionCtx,
56
63
  AgentComponent,
57
64
  ContextOptions,
58
65
  GenerationOutputMetadata,
@@ -63,7 +70,6 @@ import type {
63
70
  RunActionCtx,
64
71
  RunMutationCtx,
65
72
  RunQueryCtx,
66
- ActionCtx,
67
73
  StorageOptions,
68
74
  StreamingTextArgs,
69
75
  SyncStreamsReturnValue,
@@ -71,14 +77,9 @@ import type {
71
77
  Thread,
72
78
  UsageHandler,
73
79
  } from "./types.js";
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";
78
80
 
79
- export { storeFile, getFile } from "./files.js";
80
- export { serializeDataOrUrl } from "../mapping.js";
81
81
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
82
+ export { serializeDataOrUrl } from "../mapping.js";
82
83
  export {
83
84
  vAssistantMessage,
84
85
  vContextOptions,
@@ -93,7 +94,9 @@ export {
93
94
  vUserMessage,
94
95
  } from "../validators.js";
95
96
  export type { ToolCtx } from "./createTool.js";
97
+ export { getFile, storeFile } from "./files.js";
96
98
  export { filterOutOrphanedToolMessages } from "./search.js";
99
+ export { abortStream, listStreams } from "./streaming.js";
97
100
  export {
98
101
  createTool,
99
102
  extractText,
@@ -421,7 +424,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
421
424
  threadId,
422
425
  ...options,
423
426
  });
424
- const { args: aiArgs, messageId, userId } = context;
427
+ const { args: aiArgs, messageId, order, userId } = context;
425
428
  const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
426
429
  const tools = wrapTools(
427
430
  toolCtx,
@@ -476,6 +479,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
476
479
  > &
477
480
  GenerationOutputMetadata;
478
481
  result.messageId = messageId;
482
+ result.order = order;
479
483
  return result;
480
484
  } catch (error) {
481
485
  if (threadId && messageId) {
@@ -642,6 +646,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
642
646
  > &
643
647
  GenerationOutputMetadata;
644
648
  result.messageId = messageId;
649
+ result.order = order;
645
650
  return result;
646
651
  }
647
652
 
@@ -674,7 +679,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
674
679
  threadId,
675
680
  ...options,
676
681
  });
677
- const { args: aiArgs, messageId, userId } = context;
682
+ const { args: aiArgs, messageId, order, userId } = context;
678
683
  const trackUsage = usageHandler ?? this.options.usageHandler;
679
684
  const saveOutputMessages = this._shouldSaveOutputMessages(
680
685
  options?.storageOptions
@@ -696,6 +701,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
696
701
  });
697
702
  }
698
703
  result.messageId = messageId;
704
+ result.order = order;
699
705
  if (this.options.rawRequestResponseHandler) {
700
706
  await this.options.rawRequestResponseHandler(ctx, {
701
707
  userId,
@@ -760,7 +766,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
760
766
  threadId,
761
767
  ...options,
762
768
  });
763
- const { args: aiArgs, messageId, userId } = context;
769
+ const { args: aiArgs, messageId, order, userId } = context;
764
770
  const trackUsage = usageHandler ?? this.options.usageHandler;
765
771
  const saveOutputMessages = this._shouldSaveOutputMessages(
766
772
  options?.storageOptions
@@ -821,6 +827,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
821
827
  }) as StreamObjectResult<DeepPartial<T>, T, never> &
822
828
  GenerationOutputMetadata;
823
829
  stream.messageId = messageId;
830
+ stream.order = order;
824
831
  return stream;
825
832
  }
826
833
 
@@ -846,6 +853,12 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
846
853
  const { lastMessageId, messages } = await this.saveMessages(ctx, {
847
854
  threadId: args.threadId,
848
855
  userId: args.userId,
856
+ embeddings: args.embedding
857
+ ? {
858
+ model: args.embedding.model,
859
+ vectors: [args.embedding.vector],
860
+ }
861
+ : undefined,
849
862
  messages:
850
863
  args.prompt !== undefined
851
864
  ? [{ role: "user", content: args.prompt }]
@@ -882,7 +895,6 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
882
895
  let embeddings:
883
896
  | {
884
897
  vectors: (number[] | null)[];
885
- dimension: VectorDimension;
886
898
  model: string;
887
899
  }
888
900
  | undefined;
@@ -1678,7 +1690,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1678
1690
  return Promise.all(
1679
1691
  messages.map(async (message): Promise<CoreMessage> => {
1680
1692
  if (
1681
- message.role !== "user" ||
1693
+ (message.role !== "user" && message.role !== "assistant") ||
1682
1694
  typeof message.content === "string" ||
1683
1695
  !Array.isArray(message.content)
1684
1696
  ) {
@@ -1688,6 +1700,10 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1688
1700
  const processedContent = await Promise.all(
1689
1701
  message.content.map(async (part) => {
1690
1702
  if (part.type === "image" && part.image instanceof URL) {
1703
+ assert(
1704
+ message.role === "user",
1705
+ "Images can only be in user messages"
1706
+ );
1691
1707
  if (this._isLocalhostUrl(part.image)) {
1692
1708
  const imageData = await this._downloadFile(part.image);
1693
1709
  return {
@@ -1711,11 +1727,17 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
1711
1727
  return part;
1712
1728
  })
1713
1729
  );
1714
-
1715
- return {
1716
- ...message,
1717
- content: processedContent,
1718
- };
1730
+ if (message.role === "user") {
1731
+ return {
1732
+ ...message,
1733
+ content: processedContent as UserContent,
1734
+ };
1735
+ } else {
1736
+ return {
1737
+ ...message,
1738
+ content: processedContent as AssistantContent,
1739
+ };
1740
+ }
1719
1741
  })
1720
1742
  );
1721
1743
  }
@@ -2017,7 +2039,7 @@ type SaveMessagesArgs = {
2017
2039
  /**
2018
2040
  * The embeddings to save with the messages.
2019
2041
  */
2020
- embeddings?: MessageEmbeddings;
2042
+ embeddings?: Omit<MessageEmbeddings, "dimension">;
2021
2043
  };
2022
2044
 
2023
2045
  /**
@@ -2033,12 +2055,24 @@ export async function saveMessages(
2033
2055
  agentName?: string;
2034
2056
  }
2035
2057
  ) {
2058
+ let embeddings: MessageEmbeddings | undefined;
2059
+ if (args.embeddings) {
2060
+ const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
2061
+ if (dimension) {
2062
+ validateVectorDimension(dimension);
2063
+ embeddings = {
2064
+ model: args.embeddings.model,
2065
+ dimension,
2066
+ vectors: args.embeddings.vectors,
2067
+ };
2068
+ }
2069
+ }
2036
2070
  const result = await ctx.runMutation(component.messages.addMessages, {
2037
2071
  threadId: args.threadId,
2038
2072
  userId: args.userId,
2039
2073
  agentName: args.agentName,
2040
2074
  promptMessageId: args.promptMessageId,
2041
- embeddings: args.embeddings,
2075
+ embeddings,
2042
2076
  messages: await Promise.all(
2043
2077
  args.messages.map(async (m, i) => {
2044
2078
  const { message, fileIds } = await serializeMessage(ctx, component, m);
@@ -2107,19 +2141,22 @@ export async function saveMessage(
2107
2141
  agentName?: string;
2108
2142
  }
2109
2143
  ) {
2110
- let embeddings: MessageEmbeddings | undefined;
2111
- if (args.embedding) {
2112
- const dimension = args.embedding.vector.length;
2113
- validateVectorDimension(dimension);
2144
+ let embeddings:
2145
+ | {
2146
+ vectors: number[][];
2147
+ model: string;
2148
+ }
2149
+ | undefined;
2150
+ if (args.embedding && args.embedding.vector) {
2114
2151
  embeddings = {
2115
2152
  model: args.embedding.model,
2116
- dimension,
2117
2153
  vectors: [args.embedding.vector],
2118
2154
  };
2119
2155
  }
2120
2156
  const { lastMessageId, messages } = await saveMessages(ctx, component, {
2121
2157
  threadId: args.threadId,
2122
2158
  userId: args.userId,
2159
+ agentName: args.agentName,
2123
2160
  messages:
2124
2161
  args.prompt !== undefined
2125
2162
  ? [{ role: "user", content: args.prompt }]
@@ -8,15 +8,18 @@ import type {
8
8
  ProviderOptions,
9
9
  StreamArgs,
10
10
  StreamDelta,
11
+ StreamMessage,
11
12
  TextStreamPart,
12
13
  } from "../validators.js";
13
14
  import type { MessageDoc } from "../component/schema.js";
14
15
  import type {
15
16
  AgentComponent,
16
17
  RunActionCtx,
18
+ RunMutationCtx,
17
19
  RunQueryCtx,
18
20
  SyncStreamsReturnValue,
19
21
  } from "./types.js";
22
+ import { omit } from "convex-helpers";
20
23
 
21
24
  /**
22
25
  * A function that handles fetching stream deltas, used with the React hooks
@@ -41,10 +44,10 @@ export async function syncStreams(
41
44
  if (args.streamArgs.kind === "list") {
42
45
  return {
43
46
  kind: "list",
44
- messages: await ctx.runQuery(component.streams.list, {
47
+ messages: await listStreams(ctx, component, {
45
48
  threadId: args.threadId,
46
49
  startOrder: args.streamArgs.startOrder,
47
- statuses: args.includeStatuses,
50
+ includeStatuses: args.includeStatuses,
48
51
  }),
49
52
  };
50
53
  } else {
@@ -58,6 +61,55 @@ export async function syncStreams(
58
61
  }
59
62
  }
60
63
 
64
+ export async function abortStream(
65
+ ctx: RunMutationCtx,
66
+ component: AgentComponent,
67
+ args: {
68
+ reason: string;
69
+ } & ({ streamId: string } | { threadId: string; order: number })
70
+ ): Promise<boolean> {
71
+ if ("streamId" in args) {
72
+ return await ctx.runMutation(component.streams.abort, {
73
+ reason: args.reason,
74
+ streamId: args.streamId,
75
+ });
76
+ } else {
77
+ return await ctx.runMutation(component.streams.abortByOrder, {
78
+ reason: args.reason,
79
+ threadId: args.threadId,
80
+ order: args.order,
81
+ });
82
+ }
83
+ }
84
+
85
+ /**
86
+ * List the streaming messages for a thread.
87
+ * @param ctx A ctx object from a query, mutation, or action.
88
+ * @param component The agent component, usually `components.agent`.
89
+ * @param args.threadId The thread to list streams for.
90
+ * @param args.startOrder The order of the messages in the thread to start listing from.
91
+ * @param args.includeStatuses The statuses to include in the list.
92
+ * @returns The streams for the thread.
93
+ */
94
+ export async function listStreams(
95
+ ctx: RunQueryCtx,
96
+ component: AgentComponent,
97
+ {
98
+ threadId,
99
+ startOrder,
100
+ includeStatuses,
101
+ }: {
102
+ threadId: string;
103
+ startOrder?: number;
104
+ includeStatuses?: ("streaming" | "finished" | "aborted")[];
105
+ }
106
+ ): Promise<StreamMessage[]> {
107
+ return ctx.runQuery(component.streams.list, {
108
+ threadId,
109
+ startOrder,
110
+ statuses: includeStatuses,
111
+ });
112
+ }
61
113
 
62
114
  export type StreamingOptions = {
63
115
  /**
@@ -137,7 +189,6 @@ export class DeltaStreamer {
137
189
  ...DEFAULT_STREAMING_OPTIONS,
138
190
  ...options,
139
191
  };
140
- this.metadata = metadata;
141
192
  this.#nextParts = [];
142
193
  this.#nextOrder = metadata.order ?? 0;
143
194
  this.#nextStepOrder = (metadata.stepOrder ?? 0) + 1;
@@ -162,7 +213,7 @@ export class DeltaStreamer {
162
213
  this.streamId = await this.ctx.runMutation(
163
214
  this.component.streams.create,
164
215
  {
165
- ...this.metadata,
216
+ ...omit(this.metadata, ["abortSignal"]),
166
217
  order: this.#nextOrder,
167
218
  stepOrder: this.#nextStepOrder,
168
219
  }
@@ -131,7 +131,7 @@ export type StorageOptions = {
131
131
  saveOutputMessages?: boolean;
132
132
  };
133
133
 
134
- export type GenerationOutputMetadata = { messageId?: string };
134
+ export type GenerationOutputMetadata = { messageId?: string; order?: number };
135
135
 
136
136
  export type UsageHandler = (
137
137
  ctx: RunActionCtx,
@@ -333,9 +333,7 @@ export type OurStreamObjectArgs<T> = StreamObjectArgs<T> &
333
333
  "onError" | "onFinish" | "abortSignal"
334
334
  >;
335
335
 
336
- type ThreadOutputMetadata = GenerationOutputMetadata & {
337
- messageId: string;
338
- };
336
+ type ThreadOutputMetadata = Required<GenerationOutputMetadata>;
339
337
 
340
338
  /**
341
339
  * The interface for a thread returned from {@link createThread} or {@link continueThread}.
@@ -1437,7 +1437,13 @@ export type Mounts = {
1437
1437
  "mutation",
1438
1438
  "public",
1439
1439
  { reason: string; streamId: string },
1440
- null
1440
+ boolean
1441
+ >;
1442
+ abortByOrder: FunctionReference<
1443
+ "mutation",
1444
+ "public",
1445
+ { order: number; reason: string; threadId: string },
1446
+ boolean
1441
1447
  >;
1442
1448
  addDelta: FunctionReference<
1443
1449
  "mutation",
@@ -151,30 +151,63 @@ export const list = query({
151
151
  },
152
152
  });
153
153
 
154
- export const abort = mutation({
154
+ export const abortByOrder = mutation({
155
155
  args: {
156
- streamId: v.id("streamingMessages"),
156
+ threadId: v.id("threads"),
157
+ order: v.number(),
157
158
  reason: v.string(),
158
159
  },
159
- returns: v.null(),
160
+ returns: v.boolean(),
160
161
  handler: async (ctx, args) => {
161
- const stream = await ctx.db.get(args.streamId);
162
- if (!stream) {
163
- throw new Error(`Stream not found: ${args.streamId}`);
164
- }
165
- if (stream.state.kind !== "streaming") {
166
- console.warn(
167
- `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
168
- );
169
- return;
162
+ const streams = await ctx.db
163
+ .query("streamingMessages")
164
+ .withIndex("threadId_state_order_stepOrder", (q) =>
165
+ q
166
+ .eq("threadId", args.threadId)
167
+ .eq("state.kind", "streaming")
168
+ .eq("order", args.order)
169
+ )
170
+ .take(100);
171
+ for (const stream of streams) {
172
+ await abortById(ctx, {
173
+ streamId: stream._id,
174
+ reason: args.reason,
175
+ });
170
176
  }
171
- await cleanupTimeoutFn(ctx, stream);
172
- await ctx.db.patch(args.streamId, {
173
- state: { kind: "aborted", reason: args.reason },
174
- });
177
+ return streams.length > 0;
178
+ },
179
+ });
180
+
181
+ export const abort = mutation({
182
+ args: {
183
+ streamId: v.id("streamingMessages"),
184
+ reason: v.string(),
175
185
  },
186
+ returns: v.boolean(),
187
+ handler: abortById,
176
188
  });
177
189
 
190
+ async function abortById(
191
+ ctx: MutationCtx,
192
+ args: { streamId: Id<"streamingMessages">; reason: string }
193
+ ) {
194
+ const stream = await ctx.db.get(args.streamId);
195
+ if (!stream) {
196
+ throw new Error(`Stream not found: ${args.streamId}`);
197
+ }
198
+ if (stream.state.kind !== "streaming") {
199
+ console.warn(
200
+ `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
201
+ );
202
+ return false;
203
+ }
204
+ await cleanupTimeoutFn(ctx, stream);
205
+ await ctx.db.patch(args.streamId, {
206
+ state: { kind: "aborted", reason: args.reason },
207
+ });
208
+ return true;
209
+ }
210
+
178
211
  async function cleanupTimeoutFn(
179
212
  ctx: MutationCtx,
180
213
  stream: Doc<"streamingMessages">
package/src/mapping.ts CHANGED
@@ -125,7 +125,6 @@ export async function serializeObjectResult(
125
125
  const { message, fileIds } = await serializeMessage(ctx, component, {
126
126
  role: "assistant" as const,
127
127
  content: text,
128
- id: result.response.id,
129
128
  });
130
129
  return {
131
130
  messages: [
@@ -187,6 +186,9 @@ export async function serializeContent(
187
186
  }
188
187
  return { ...part, data };
189
188
  }
189
+ case "tool-result": {
190
+ return { ...part, result: part.result ?? null };
191
+ }
190
192
  default:
191
193
  return part;
192
194
  }
@@ -281,6 +283,11 @@ export function guessMimeType(buf: ArrayBuffer | string): string {
281
283
  return "application/octet-stream";
282
284
  }
283
285
 
286
+ /**
287
+ * Serialize an AI SDK `DataContent` or `URL` to a Convex-serializable format.
288
+ * @param dataOrUrl - The data or URL to serialize.
289
+ * @returns The serialized data as an ArrayBuffer or the URL as a string.
290
+ */
284
291
  export function serializeDataOrUrl(
285
292
  dataOrUrl: DataContent | URL
286
293
  ): ArrayBuffer | string {
@@ -2,6 +2,7 @@ import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
2
2
  import type { MessageDoc } from "../client/index.js";
3
3
  import type {
4
4
  Message,
5
+ MessageStatus,
5
6
  StreamDelta,
6
7
  StreamMessage,
7
8
  TextStreamPart,
@@ -99,6 +100,12 @@ export function applyDeltasToStreamMessage(
99
100
  cursor = delta.end;
100
101
  parts.push(...delta.parts);
101
102
  }
103
+ if (existing && existing.messages.length > 0 && !changed) {
104
+ const lastMessage = existing.messages.at(-1)!;
105
+ if (statusFromStreamStatus(streamMessage.status) !== lastMessage.status) {
106
+ changed = true;
107
+ }
108
+ }
102
109
  if (!changed) {
103
110
  return [
104
111
  existing ?? { streamId: streamMessage.streamId, cursor, messages: [] },
@@ -115,6 +122,7 @@ export function applyDeltasToStreamMessage(
115
122
  currentMessage = {
116
123
  ...lastMessage,
117
124
  message: cloneMessageAndContent(lastMessage.message),
125
+ status: statusFromStreamStatus(streamMessage.status),
118
126
  };
119
127
  } else {
120
128
  const newMessage = createStreamingMessage(
@@ -248,6 +256,21 @@ function getLastContent(message: MessageDoc) {
248
256
  return undefined;
249
257
  }
250
258
 
259
+ function statusFromStreamStatus(
260
+ status: StreamMessage["status"]
261
+ ): MessageStatus {
262
+ switch (status) {
263
+ case "streaming":
264
+ return "pending";
265
+ case "finished":
266
+ return "success";
267
+ case "aborted":
268
+ return "failed";
269
+ default:
270
+ return "pending";
271
+ }
272
+ }
273
+
251
274
  export function createStreamingMessage(
252
275
  threadId: string,
253
276
  message: StreamMessage,
@@ -259,9 +282,7 @@ export function createStreamingMessage(
259
282
  ...rest,
260
283
  _id: `${streamId}-${index}`,
261
284
  _creationTime: Date.now(),
262
- status: (
263
- { streaming: "pending", finished: "success", aborted: "failed" } as const
264
- )[message.status],
285
+ status: statusFromStreamStatus(message.status),
265
286
  threadId,
266
287
  tool: false,
267
288
  };
@@ -114,7 +114,7 @@ export function useThreadMessages<
114
114
  const streamListMessages =
115
115
  streamMessages?.map((m) => ({
116
116
  ...m,
117
- streaming: !m.status || m.status === "streaming",
117
+ streaming: !m.status || m.status === "pending",
118
118
  })) ?? [];
119
119
  return {
120
120
  ...paginated,
@@ -28,7 +28,7 @@ export function optimisticallySendMessage(
28
28
  argsToMatch: { threadId: args.threadId, streamArgs: undefined },
29
29
  item: {
30
30
  _creationTime: Date.now(),
31
- _id: crypto.randomUUID(),
31
+ _id: randomUUID(),
32
32
  order,
33
33
  stepOrder,
34
34
  status: "pending",
@@ -44,3 +44,13 @@ export function optimisticallySendMessage(
44
44
  });
45
45
  };
46
46
  }
47
+
48
+ export function randomUUID() {
49
+ if (typeof crypto !== "undefined") {
50
+ return crypto.randomUUID();
51
+ }
52
+ return (
53
+ Math.random().toString(36).substring(2, 15) +
54
+ Math.random().toString(36).substring(2, 15)
55
+ );
56
+ }
@@ -9,6 +9,7 @@ export type UIMessage = AIUIMessage & {
9
9
  order: number;
10
10
  stepOrder: number;
11
11
  status: "streaming" | MessageStatus;
12
+ agentName?: string;
12
13
  };
13
14
 
14
15
  export function toUIMessages(
@@ -36,6 +37,7 @@ export function toUIMessages(
36
37
  ...common,
37
38
  role: "system",
38
39
  content: text,
40
+ agentName: message.agentName,
39
41
  parts: [{ type: "text", text }],
40
42
  });
41
43
  } else if (coreMessage.role === "user") {
@@ -69,6 +71,7 @@ export function toUIMessages(
69
71
  assistantMessage = {
70
72
  ...common,
71
73
  role: "assistant",
74
+ agentName: message.agentName,
72
75
  content: "",
73
76
  parts: [],
74
77
  };