@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-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.
- package/README.md +32 -27
- package/dist/UIMessages.d.ts +46 -0
- package/dist/UIMessages.d.ts.map +1 -0
- package/dist/UIMessages.js +546 -0
- package/dist/UIMessages.js.map +1 -0
- package/dist/client/createTool.d.ts +129 -27
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +66 -12
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/defaultComponent.d.ts +11 -0
- package/dist/client/defaultComponent.d.ts.map +1 -0
- package/dist/client/defaultComponent.js +7 -0
- package/dist/client/defaultComponent.js.map +1 -0
- package/dist/client/definePlaygroundAPI.d.ts +1323 -192
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +52 -28
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.d.ts +20 -7
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +68 -11
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +1056 -965
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +242 -748
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +461 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +106 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/mockModel.d.ts +42 -0
- package/dist/client/mockModel.d.ts.map +1 -0
- package/dist/client/mockModel.js +175 -0
- package/dist/client/mockModel.js.map +1 -0
- package/dist/client/saveInputMessages.d.ts +20 -0
- package/dist/client/saveInputMessages.d.ts.map +1 -0
- package/dist/client/saveInputMessages.js +58 -0
- package/dist/client/saveInputMessages.js.map +1 -0
- package/dist/client/search.d.ts +346 -35
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +350 -39
- package/dist/client/search.js.map +1 -1
- package/dist/client/start.d.ts +84 -0
- package/dist/client/start.d.ts.map +1 -0
- package/dist/client/start.js +171 -0
- package/dist/client/start.js.map +1 -0
- package/dist/client/streamText.d.ts +46 -0
- package/dist/client/streamText.d.ts.map +1 -0
- package/dist/client/streamText.js +93 -0
- package/dist/client/streamText.js.map +1 -0
- package/dist/client/streaming.d.ts +3705 -32
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +141 -59
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/threads.d.ts +46 -0
- package/dist/client/threads.d.ts.map +1 -0
- package/dist/client/threads.js +49 -0
- package/dist/client/threads.js.map +1 -0
- package/dist/client/types.d.ts +265 -128
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/utils.d.ts +4 -0
- package/dist/client/utils.d.ts.map +1 -0
- package/dist/client/utils.js +21 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/component/_generated/api.d.ts +24 -2178
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js +10 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +3119 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +4 -18
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +10 -38
- package/dist/component/_generated/server.d.ts.map +1 -1
- package/dist/component/_generated/server.js +9 -5
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/files.d.ts +16 -10
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +10 -2
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +2553 -342
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +387 -154
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +5697 -3584
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +18 -41
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +35 -335
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +114 -73
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +16 -16
- package/dist/component/users.d.ts +4 -4
- package/dist/component/vector/index.d.ts +1 -1
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/index.js +1 -3
- package/dist/component/vector/index.js.map +1 -1
- package/dist/deltas.d.ts +43 -0
- package/dist/deltas.d.ts.map +1 -0
- package/dist/deltas.js +447 -0
- package/dist/deltas.js.map +1 -0
- package/dist/mapping.d.ts +20 -20
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +313 -96
- package/dist/mapping.js.map +1 -1
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/index.d.ts +5 -77
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +6 -160
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts +36 -3
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +35 -9
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/types.d.ts +4 -18
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useDeltaStreams.d.ts +10 -0
- package/dist/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/react/useDeltaStreams.js +101 -0
- package/dist/react/useDeltaStreams.js.map +1 -0
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +32 -15
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/react/useStreamingUIMessages.d.ts +22 -0
- package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/react/useStreamingUIMessages.js +92 -0
- package/dist/react/useStreamingUIMessages.js.map +1 -0
- package/dist/react/useThreadMessages.d.ts +104 -0
- package/dist/react/useThreadMessages.d.ts.map +1 -0
- package/dist/react/useThreadMessages.js +148 -0
- package/dist/react/useThreadMessages.js.map +1 -0
- package/dist/react/useUIMessages.d.ts +96 -0
- package/dist/react/useUIMessages.d.ts.map +1 -0
- package/dist/react/useUIMessages.js +108 -0
- package/dist/react/useUIMessages.js.map +1 -0
- package/dist/shared.d.ts +20 -4
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +45 -8
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +22981 -5666
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +245 -137
- package/dist/validators.js.map +1 -1
- package/package.json +98 -50
- package/src/UIMessages.combineUIMessages.test.ts +239 -0
- package/src/UIMessages.test.ts +273 -0
- package/src/UIMessages.ts +739 -0
- package/src/client/createTool.ts +293 -76
- package/src/client/defaultComponent.ts +17 -0
- package/src/client/definePlaygroundAPI.ts +67 -31
- package/src/client/files.ts +100 -20
- package/src/client/index.test.ts +40 -85
- package/src/client/index.ts +520 -1290
- package/src/client/messages.ts +237 -0
- package/src/client/mockModel.ts +245 -0
- package/src/client/saveInputMessages.test.ts +583 -0
- package/src/client/saveInputMessages.ts +101 -0
- package/src/client/search.test.ts +1207 -0
- package/src/client/search.ts +577 -70
- package/src/client/start.ts +310 -0
- package/src/client/streamText.ts +163 -0
- package/src/client/streaming.test.ts +186 -0
- package/src/client/streaming.ts +219 -97
- package/src/client/threads.ts +83 -0
- package/src/client/types.ts +368 -219
- package/src/client/utils.ts +27 -0
- package/src/component/_generated/api.ts +64 -0
- package/src/component/_generated/component.ts +4913 -0
- package/src/component/_generated/{server.d.ts → server.ts} +33 -21
- package/src/component/files.ts +11 -2
- package/src/component/messages.test.ts +195 -51
- package/src/component/messages.ts +490 -201
- package/src/component/schema.ts +20 -46
- package/src/component/setup.test.ts +7 -0
- package/src/component/streams.ts +184 -83
- package/src/component/users.test.ts +0 -1
- package/src/component/vector/index.ts +1 -3
- package/src/deltas.test.ts +626 -0
- package/src/deltas.ts +570 -0
- package/src/fromUIMessages.test.ts +497 -0
- package/src/mapping.test.ts +103 -6
- package/src/mapping.ts +422 -161
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/index.ts +10 -230
- package/src/react/optimisticallySendMessage.ts +55 -12
- package/src/react/types.ts +6 -39
- package/src/react/useDeltaStreams.ts +154 -0
- package/src/react/useSmoothText.ts +56 -36
- package/src/react/useStreamingUIMessages.ts +143 -0
- package/src/react/useThreadMessages.ts +262 -0
- package/src/react/useUIMessages.test.ts +255 -0
- package/src/react/useUIMessages.ts +195 -0
- package/src/shared.ts +88 -12
- package/src/test.ts +18 -0
- package/src/toUIMessages.test.ts +1269 -0
- package/src/validators.test.ts +18 -19
- package/src/validators.ts +325 -185
- package/dist/client/_generated/_ignore.d.ts +0 -1
- package/dist/client/_generated/_ignore.d.ts.map +0 -1
- package/dist/client/_generated/_ignore.js +0 -3
- package/dist/client/_generated/_ignore.js.map +0 -1
- package/dist/client/listMessages.d.ts +0 -22
- package/dist/client/listMessages.d.ts.map +0 -1
- package/dist/client/listMessages.js +0 -25
- package/dist/client/listMessages.js.map +0 -1
- package/dist/package.json +0 -3
- package/dist/react/deltas.d.ts +0 -26
- package/dist/react/deltas.d.ts.map +0 -1
- package/dist/react/deltas.js +0 -384
- package/dist/react/deltas.js.map +0 -1
- package/dist/react/toUIMessages.d.ts +0 -15
- package/dist/react/toUIMessages.d.ts.map +0 -1
- package/dist/react/toUIMessages.js +0 -211
- package/dist/react/toUIMessages.js.map +0 -1
- package/src/client/listMessages.ts +0 -38
- package/src/component/_generated/api.d.ts +0 -2202
- package/src/component/_generated/api.js +0 -23
- package/src/component/_generated/server.js +0 -90
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/src/react/deltas.test.ts +0 -315
- package/src/react/deltas.ts +0 -478
- package/src/react/toUIMessages.test.ts +0 -420
- package/src/react/toUIMessages.ts +0 -253
- package/src/vitest.config.ts +0 -7
- /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convertToModelMessages,
|
|
3
|
+
type UIMessage as AIUIMessage,
|
|
4
|
+
type DeepPartial,
|
|
5
|
+
type DynamicToolUIPart,
|
|
6
|
+
type ReasoningUIPart,
|
|
7
|
+
type SourceDocumentUIPart,
|
|
8
|
+
type SourceUrlUIPart,
|
|
9
|
+
type StepStartUIPart,
|
|
10
|
+
type TextUIPart,
|
|
11
|
+
type ToolResultPart,
|
|
12
|
+
type ToolUIPart,
|
|
13
|
+
type UIDataTypes,
|
|
14
|
+
type UITools,
|
|
15
|
+
} from "ai";
|
|
16
|
+
import type { Infer } from "convex/values";
|
|
17
|
+
import { toModelMessage, fromModelMessage, toUIFilePart } from "./mapping.js";
|
|
18
|
+
import {
|
|
19
|
+
extractReasoning,
|
|
20
|
+
extractText,
|
|
21
|
+
isTool,
|
|
22
|
+
joinText,
|
|
23
|
+
sorted,
|
|
24
|
+
} from "./shared.js";
|
|
25
|
+
import type {
|
|
26
|
+
MessageDoc,
|
|
27
|
+
MessageStatus,
|
|
28
|
+
ProviderOptions,
|
|
29
|
+
SourcePart,
|
|
30
|
+
vSource,
|
|
31
|
+
} from "./validators.js";
|
|
32
|
+
import { omit, pick } from "convex-helpers";
|
|
33
|
+
|
|
34
|
+
export type UIStatus = "streaming" | MessageStatus;
|
|
35
|
+
|
|
36
|
+
export type UIMessage<
|
|
37
|
+
METADATA = unknown,
|
|
38
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
39
|
+
TOOLS extends UITools = UITools,
|
|
40
|
+
> = AIUIMessage<METADATA, DATA_PARTS, TOOLS> & {
|
|
41
|
+
key: string;
|
|
42
|
+
order: number;
|
|
43
|
+
stepOrder: number;
|
|
44
|
+
status: UIStatus;
|
|
45
|
+
agentName?: string;
|
|
46
|
+
userId?: string;
|
|
47
|
+
text: string;
|
|
48
|
+
_creationTime: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Converts a list of UIMessages to MessageDocs, along with extra metadata that
|
|
53
|
+
* may be available to associate with the MessageDocs.
|
|
54
|
+
* @param messages - The UIMessages to convert to MessageDocs.
|
|
55
|
+
* @param meta - The metadata to add to the MessageDocs.
|
|
56
|
+
* @returns
|
|
57
|
+
*/
|
|
58
|
+
export async function fromUIMessages<METADATA = unknown>(
|
|
59
|
+
messages: UIMessage<METADATA>[],
|
|
60
|
+
meta: {
|
|
61
|
+
threadId: string;
|
|
62
|
+
userId?: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
provider?: string;
|
|
65
|
+
providerOptions?: ProviderOptions;
|
|
66
|
+
metadata?: METADATA;
|
|
67
|
+
},
|
|
68
|
+
): Promise<(MessageDoc & { streaming: boolean; metadata?: METADATA })[]> {
|
|
69
|
+
const nested = await Promise.all(
|
|
70
|
+
messages.map(async (uiMessage) => {
|
|
71
|
+
const stepOrder = uiMessage.stepOrder;
|
|
72
|
+
const commonFields = {
|
|
73
|
+
...pick(meta, [
|
|
74
|
+
"threadId",
|
|
75
|
+
"userId",
|
|
76
|
+
"model",
|
|
77
|
+
"provider",
|
|
78
|
+
"providerOptions",
|
|
79
|
+
"metadata",
|
|
80
|
+
]),
|
|
81
|
+
...omit(uiMessage, ["parts", "role", "key", "text", "userId"]),
|
|
82
|
+
userId: uiMessage.userId ?? meta.userId,
|
|
83
|
+
status: uiMessage.status === "streaming" ? "pending" : "success",
|
|
84
|
+
streaming: uiMessage.status === "streaming",
|
|
85
|
+
// to override
|
|
86
|
+
_id: uiMessage.id,
|
|
87
|
+
tool: false,
|
|
88
|
+
} satisfies MessageDoc & { streaming: boolean; metadata?: METADATA };
|
|
89
|
+
const modelMessages = await convertToModelMessages([uiMessage]);
|
|
90
|
+
return modelMessages
|
|
91
|
+
.map((modelMessage, i) => {
|
|
92
|
+
if (modelMessage.content.length === 0) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const message = fromModelMessage(modelMessage);
|
|
96
|
+
const tool = isTool(message);
|
|
97
|
+
const doc: MessageDoc & { streaming: boolean; metadata?: METADATA } =
|
|
98
|
+
{
|
|
99
|
+
...commonFields,
|
|
100
|
+
_id: uiMessage.id + `-${i}`,
|
|
101
|
+
stepOrder: stepOrder + i,
|
|
102
|
+
message,
|
|
103
|
+
tool,
|
|
104
|
+
text: extractText(message),
|
|
105
|
+
reasoning: extractReasoning(message),
|
|
106
|
+
finishReason: tool ? "tool-calls" : "stop",
|
|
107
|
+
sources: fromSourceParts(uiMessage.parts),
|
|
108
|
+
};
|
|
109
|
+
if (Array.isArray(modelMessage.content)) {
|
|
110
|
+
// Find a content part with providerOptions (type assertion needed for SDK compatibility)
|
|
111
|
+
const partWithProviderOptions = modelMessage.content.find(
|
|
112
|
+
(c): c is typeof c & { providerOptions: unknown } =>
|
|
113
|
+
"providerOptions" in c && c.providerOptions !== undefined,
|
|
114
|
+
);
|
|
115
|
+
if (partWithProviderOptions?.providerOptions) {
|
|
116
|
+
// convertToModelMessages changes providerMetadata to providerOptions
|
|
117
|
+
const providerOptions =
|
|
118
|
+
partWithProviderOptions.providerOptions as
|
|
119
|
+
| Record<string, Record<string, unknown>>
|
|
120
|
+
| undefined;
|
|
121
|
+
if (providerOptions) {
|
|
122
|
+
doc.providerMetadata = providerOptions;
|
|
123
|
+
doc.providerOptions ??= providerOptions;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return doc;
|
|
128
|
+
})
|
|
129
|
+
.filter((d) => d !== undefined);
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
return nested.flat();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function fromSourceParts(parts: UIMessage["parts"]): Infer<typeof vSource>[] {
|
|
136
|
+
return parts
|
|
137
|
+
.map((part) => {
|
|
138
|
+
if (part.type === "source-url") {
|
|
139
|
+
return {
|
|
140
|
+
type: "source",
|
|
141
|
+
sourceType: "url",
|
|
142
|
+
url: part.url,
|
|
143
|
+
id: part.sourceId,
|
|
144
|
+
providerMetadata: part.providerMetadata,
|
|
145
|
+
title: part.title,
|
|
146
|
+
} satisfies Infer<typeof vSource>;
|
|
147
|
+
}
|
|
148
|
+
if (part.type === "source-document") {
|
|
149
|
+
return {
|
|
150
|
+
type: "source",
|
|
151
|
+
sourceType: "document",
|
|
152
|
+
mediaType: part.mediaType,
|
|
153
|
+
id: part.sourceId,
|
|
154
|
+
providerMetadata: part.providerMetadata,
|
|
155
|
+
title: part.title,
|
|
156
|
+
} satisfies Infer<typeof vSource>;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
})
|
|
160
|
+
.filter((p) => p !== undefined);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type ExtraFields<METADATA = unknown> = {
|
|
164
|
+
streaming?: boolean;
|
|
165
|
+
metadata?: METADATA;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Converts a list of MessageDocs to UIMessages.
|
|
170
|
+
* This is somewhat lossy, as many fields are not supported by UIMessages, e.g.
|
|
171
|
+
* the model, provider, userId, etc.
|
|
172
|
+
* The UIMessage type is the augmented type that includes more fields such as
|
|
173
|
+
* key, order, stepOrder, status, agentName, text, etc.
|
|
174
|
+
*/
|
|
175
|
+
export function toUIMessages<
|
|
176
|
+
METADATA = unknown,
|
|
177
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
178
|
+
TOOLS extends UITools = UITools,
|
|
179
|
+
>(
|
|
180
|
+
messages: (MessageDoc & ExtraFields<METADATA>)[],
|
|
181
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS>[] {
|
|
182
|
+
// Group assistant and tool messages together
|
|
183
|
+
const assistantGroups = groupAssistantMessages(sorted(messages));
|
|
184
|
+
|
|
185
|
+
const uiMessages: UIMessage<METADATA, DATA_PARTS, TOOLS>[] = [];
|
|
186
|
+
for (const group of assistantGroups) {
|
|
187
|
+
if (group.role === "system") {
|
|
188
|
+
uiMessages.push(createSystemUIMessage(group.message));
|
|
189
|
+
} else if (group.role === "user") {
|
|
190
|
+
uiMessages.push(createUserUIMessage(group.message));
|
|
191
|
+
} else {
|
|
192
|
+
// Assistant/tool group
|
|
193
|
+
uiMessages.push(createAssistantUIMessage(group.messages));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return uiMessages;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
type Group<METADATA = unknown> =
|
|
201
|
+
| {
|
|
202
|
+
role: "user";
|
|
203
|
+
message: MessageDoc & ExtraFields<METADATA>;
|
|
204
|
+
}
|
|
205
|
+
| {
|
|
206
|
+
role: "system";
|
|
207
|
+
message: MessageDoc & ExtraFields<METADATA>;
|
|
208
|
+
}
|
|
209
|
+
| {
|
|
210
|
+
role: "assistant";
|
|
211
|
+
messages: (MessageDoc & ExtraFields<METADATA>)[];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
function groupAssistantMessages<METADATA = unknown>(
|
|
215
|
+
messages: (MessageDoc & ExtraFields<METADATA>)[],
|
|
216
|
+
): Group<METADATA>[] {
|
|
217
|
+
const groups: Group<METADATA>[] = [];
|
|
218
|
+
|
|
219
|
+
let currentAssistantGroup: (MessageDoc & ExtraFields<METADATA>)[] = [];
|
|
220
|
+
let currentOrder: number | undefined;
|
|
221
|
+
|
|
222
|
+
for (const message of messages) {
|
|
223
|
+
const coreMessage = message.message && toModelMessage(message.message);
|
|
224
|
+
if (!coreMessage) continue;
|
|
225
|
+
|
|
226
|
+
if (coreMessage.role === "user" || coreMessage.role === "system") {
|
|
227
|
+
// Finish any current assistant group
|
|
228
|
+
if (currentAssistantGroup.length > 0) {
|
|
229
|
+
groups.push({
|
|
230
|
+
role: "assistant",
|
|
231
|
+
messages: currentAssistantGroup,
|
|
232
|
+
});
|
|
233
|
+
currentAssistantGroup = [];
|
|
234
|
+
currentOrder = undefined;
|
|
235
|
+
}
|
|
236
|
+
// Add singleton group
|
|
237
|
+
groups.push({
|
|
238
|
+
role: coreMessage.role,
|
|
239
|
+
message,
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
// Assistant or tool message
|
|
243
|
+
|
|
244
|
+
// Start new group if order changes or this is the first assistant/tool message
|
|
245
|
+
if (currentOrder !== undefined && message.order !== currentOrder) {
|
|
246
|
+
if (currentAssistantGroup.length > 0) {
|
|
247
|
+
groups.push({
|
|
248
|
+
role: "assistant",
|
|
249
|
+
messages: currentAssistantGroup,
|
|
250
|
+
});
|
|
251
|
+
currentAssistantGroup = [];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
currentOrder = message.order;
|
|
256
|
+
currentAssistantGroup.push(message);
|
|
257
|
+
|
|
258
|
+
// End group if this is an assistant message without tool calls
|
|
259
|
+
if (coreMessage.role === "assistant" && !message.tool) {
|
|
260
|
+
groups.push({
|
|
261
|
+
role: "assistant",
|
|
262
|
+
messages: currentAssistantGroup,
|
|
263
|
+
});
|
|
264
|
+
currentAssistantGroup = [];
|
|
265
|
+
currentOrder = undefined;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Add any remaining assistant group
|
|
271
|
+
if (currentAssistantGroup.length > 0) {
|
|
272
|
+
groups.push({
|
|
273
|
+
role: "assistant",
|
|
274
|
+
messages: currentAssistantGroup,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return groups;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function createSystemUIMessage<
|
|
282
|
+
METADATA = unknown,
|
|
283
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
284
|
+
TOOLS extends UITools = UITools,
|
|
285
|
+
>(
|
|
286
|
+
message: MessageDoc & ExtraFields<METADATA>,
|
|
287
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
288
|
+
const text = extractTextFromMessageDoc(message);
|
|
289
|
+
const partCommon = {
|
|
290
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
291
|
+
...(message.providerMetadata
|
|
292
|
+
? { providerMetadata: message.providerMetadata }
|
|
293
|
+
: {}),
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
id: message._id,
|
|
298
|
+
_creationTime: message._creationTime,
|
|
299
|
+
order: message.order,
|
|
300
|
+
stepOrder: message.stepOrder,
|
|
301
|
+
status: message.streaming ? ("streaming" as const) : message.status,
|
|
302
|
+
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
303
|
+
text,
|
|
304
|
+
role: "system",
|
|
305
|
+
agentName: message.agentName,
|
|
306
|
+
userId: message.userId,
|
|
307
|
+
parts: [{ type: "text", text, ...partCommon } satisfies TextUIPart],
|
|
308
|
+
metadata: message.metadata,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function extractTextFromMessageDoc(message: MessageDoc): string {
|
|
313
|
+
return (
|
|
314
|
+
(message.message && extractText(message.message)) || message.text || ""
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function createUserUIMessage<
|
|
319
|
+
METADATA = unknown,
|
|
320
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
321
|
+
TOOLS extends UITools = UITools,
|
|
322
|
+
>(
|
|
323
|
+
message: MessageDoc & ExtraFields<METADATA>,
|
|
324
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
325
|
+
const text = extractTextFromMessageDoc(message);
|
|
326
|
+
const coreMessage = toModelMessage(message.message!);
|
|
327
|
+
const content = coreMessage.content;
|
|
328
|
+
const nonStringContent =
|
|
329
|
+
content && typeof content !== "string" ? content : [];
|
|
330
|
+
|
|
331
|
+
const partCommon = {
|
|
332
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
333
|
+
...(message.providerMetadata
|
|
334
|
+
? { providerMetadata: message.providerMetadata }
|
|
335
|
+
: {}),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const parts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
339
|
+
if (text && !nonStringContent.length) {
|
|
340
|
+
parts.push({ type: "text", text });
|
|
341
|
+
}
|
|
342
|
+
for (const contentPart of nonStringContent) {
|
|
343
|
+
switch (contentPart.type) {
|
|
344
|
+
case "text":
|
|
345
|
+
parts.push({ type: "text", text: contentPart.text, ...partCommon });
|
|
346
|
+
break;
|
|
347
|
+
case "file":
|
|
348
|
+
case "image":
|
|
349
|
+
parts.push(toUIFilePart(contentPart));
|
|
350
|
+
break;
|
|
351
|
+
default:
|
|
352
|
+
console.warn("Unknown content part type for user", contentPart);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
id: message._id,
|
|
359
|
+
_creationTime: message._creationTime,
|
|
360
|
+
order: message.order,
|
|
361
|
+
stepOrder: message.stepOrder,
|
|
362
|
+
status: message.streaming ? ("streaming" as const) : message.status,
|
|
363
|
+
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
364
|
+
text,
|
|
365
|
+
role: "user",
|
|
366
|
+
userId: message.userId,
|
|
367
|
+
parts,
|
|
368
|
+
metadata: message.metadata,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function createAssistantUIMessage<
|
|
373
|
+
METADATA = unknown,
|
|
374
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
375
|
+
TOOLS extends UITools = UITools,
|
|
376
|
+
>(
|
|
377
|
+
groupUnordered: (MessageDoc & ExtraFields<METADATA>)[],
|
|
378
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
379
|
+
const group = sorted(groupUnordered);
|
|
380
|
+
const firstMessage = group[0];
|
|
381
|
+
|
|
382
|
+
// Use first message for special fields
|
|
383
|
+
const common = {
|
|
384
|
+
id: firstMessage._id,
|
|
385
|
+
_creationTime: firstMessage._creationTime,
|
|
386
|
+
order: firstMessage.order,
|
|
387
|
+
stepOrder: firstMessage.stepOrder,
|
|
388
|
+
key: `${firstMessage.threadId}-${firstMessage.order}-${firstMessage.stepOrder}`,
|
|
389
|
+
agentName: firstMessage.agentName,
|
|
390
|
+
userId: firstMessage.userId,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Get status from last message
|
|
394
|
+
const lastMessage = group[group.length - 1];
|
|
395
|
+
const status = lastMessage.streaming
|
|
396
|
+
? ("streaming" as const)
|
|
397
|
+
: lastMessage.status;
|
|
398
|
+
|
|
399
|
+
// Collect all parts from all messages
|
|
400
|
+
const allParts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
401
|
+
|
|
402
|
+
for (const message of group) {
|
|
403
|
+
const coreMessage = message.message && toModelMessage(message.message);
|
|
404
|
+
if (!coreMessage) continue;
|
|
405
|
+
|
|
406
|
+
const content = coreMessage.content;
|
|
407
|
+
const nonStringContent =
|
|
408
|
+
content && typeof content !== "string" ? content : [];
|
|
409
|
+
const text = extractTextFromMessageDoc(message);
|
|
410
|
+
|
|
411
|
+
const partCommon = {
|
|
412
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
413
|
+
...(message.providerMetadata
|
|
414
|
+
? { providerMetadata: message.providerMetadata }
|
|
415
|
+
: {}),
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Add reasoning parts
|
|
419
|
+
if (
|
|
420
|
+
message.reasoning &&
|
|
421
|
+
!nonStringContent.some((c) => c.type === "reasoning")
|
|
422
|
+
) {
|
|
423
|
+
allParts.push({
|
|
424
|
+
type: "reasoning",
|
|
425
|
+
text: message.reasoning,
|
|
426
|
+
...partCommon,
|
|
427
|
+
} satisfies ReasoningUIPart);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Add text parts if no structured content
|
|
431
|
+
if (text && !nonStringContent.length) {
|
|
432
|
+
allParts.push({
|
|
433
|
+
type: "text",
|
|
434
|
+
text: text,
|
|
435
|
+
...partCommon,
|
|
436
|
+
} satisfies TextUIPart);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Add all structured content parts
|
|
440
|
+
for (const contentPart of nonStringContent) {
|
|
441
|
+
switch (contentPart.type) {
|
|
442
|
+
case "text":
|
|
443
|
+
allParts.push({
|
|
444
|
+
...partCommon,
|
|
445
|
+
...contentPart,
|
|
446
|
+
} satisfies TextUIPart);
|
|
447
|
+
break;
|
|
448
|
+
case "reasoning":
|
|
449
|
+
allParts.push({
|
|
450
|
+
...partCommon,
|
|
451
|
+
...contentPart,
|
|
452
|
+
} satisfies ReasoningUIPart);
|
|
453
|
+
break;
|
|
454
|
+
case "file":
|
|
455
|
+
case "image":
|
|
456
|
+
allParts.push(toUIFilePart(contentPart));
|
|
457
|
+
break;
|
|
458
|
+
case "tool-call": {
|
|
459
|
+
allParts.push({
|
|
460
|
+
type: "step-start",
|
|
461
|
+
} satisfies StepStartUIPart);
|
|
462
|
+
const toolPart: ToolUIPart<TOOLS> = {
|
|
463
|
+
type: `tool-${contentPart.toolName as keyof TOOLS & string}`,
|
|
464
|
+
toolCallId: contentPart.toolCallId,
|
|
465
|
+
input: contentPart.input as DeepPartial<
|
|
466
|
+
TOOLS[keyof TOOLS & string]["input"]
|
|
467
|
+
>,
|
|
468
|
+
providerExecuted: contentPart.providerExecuted,
|
|
469
|
+
...(message.streaming
|
|
470
|
+
? { state: "input-streaming" }
|
|
471
|
+
: {
|
|
472
|
+
state: "input-available",
|
|
473
|
+
callProviderMetadata: message.providerMetadata,
|
|
474
|
+
}),
|
|
475
|
+
};
|
|
476
|
+
allParts.push(toolPart);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "tool-result": {
|
|
480
|
+
const typedPart = contentPart as unknown as ToolResultPart & {
|
|
481
|
+
output: { type: string; value?: unknown; reason?: string };
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Check if this is an execution-denied result
|
|
485
|
+
if (typedPart.output?.type === "execution-denied") {
|
|
486
|
+
const call = allParts.find(
|
|
487
|
+
(part) =>
|
|
488
|
+
part.type === `tool-${contentPart.toolName}` &&
|
|
489
|
+
"toolCallId" in part &&
|
|
490
|
+
part.toolCallId === contentPart.toolCallId,
|
|
491
|
+
) as ToolUIPart | undefined;
|
|
492
|
+
|
|
493
|
+
if (call) {
|
|
494
|
+
call.state = "output-denied";
|
|
495
|
+
if (!("approval" in call) || !call.approval) {
|
|
496
|
+
(call as ToolUIPart & { approval?: object }).approval = {
|
|
497
|
+
id: "",
|
|
498
|
+
approved: false,
|
|
499
|
+
reason: typedPart.output.reason,
|
|
500
|
+
};
|
|
501
|
+
} else {
|
|
502
|
+
const approval = (
|
|
503
|
+
call as ToolUIPart & {
|
|
504
|
+
approval: { approved?: boolean; reason?: string };
|
|
505
|
+
}
|
|
506
|
+
).approval;
|
|
507
|
+
approval.approved = false;
|
|
508
|
+
approval.reason = typedPart.output.reason;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const output =
|
|
515
|
+
typeof typedPart.output?.type === "string"
|
|
516
|
+
? typedPart.output.value
|
|
517
|
+
: typedPart.output;
|
|
518
|
+
// Check for error at both the content part level (isError) and message level
|
|
519
|
+
// isError may exist on stored tool results but isn't in ToolResultPart type
|
|
520
|
+
const hasError =
|
|
521
|
+
(contentPart as { isError?: boolean }).isError || message.error;
|
|
522
|
+
const errorText =
|
|
523
|
+
message.error || (hasError ? String(output) : undefined);
|
|
524
|
+
const call = allParts.find(
|
|
525
|
+
(part) =>
|
|
526
|
+
part.type === `tool-${contentPart.toolName}` &&
|
|
527
|
+
"toolCallId" in part &&
|
|
528
|
+
part.toolCallId === contentPart.toolCallId,
|
|
529
|
+
) as ToolUIPart | undefined;
|
|
530
|
+
if (call) {
|
|
531
|
+
if (hasError) {
|
|
532
|
+
call.state = "output-error";
|
|
533
|
+
call.errorText = errorText ?? "Unknown error";
|
|
534
|
+
call.output = output;
|
|
535
|
+
} else {
|
|
536
|
+
call.state = "output-available";
|
|
537
|
+
call.output = output;
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
// Tool call is on a previous page - create standalone tool part
|
|
541
|
+
if (hasError) {
|
|
542
|
+
allParts.push({
|
|
543
|
+
type: `tool-${contentPart.toolName}`,
|
|
544
|
+
toolCallId: contentPart.toolCallId,
|
|
545
|
+
state: "output-error",
|
|
546
|
+
input: undefined,
|
|
547
|
+
errorText: errorText ?? "Unknown error",
|
|
548
|
+
callProviderMetadata: message.providerMetadata,
|
|
549
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
550
|
+
} else {
|
|
551
|
+
allParts.push({
|
|
552
|
+
type: `tool-${contentPart.toolName}`,
|
|
553
|
+
toolCallId: contentPart.toolCallId,
|
|
554
|
+
state: "output-available",
|
|
555
|
+
input: undefined,
|
|
556
|
+
output,
|
|
557
|
+
callProviderMetadata: message.providerMetadata,
|
|
558
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case "tool-approval-request": {
|
|
564
|
+
// Find the matching tool call
|
|
565
|
+
const typedPart = contentPart as {
|
|
566
|
+
toolCallId: string;
|
|
567
|
+
approvalId: string;
|
|
568
|
+
};
|
|
569
|
+
const toolCallPart = allParts.find(
|
|
570
|
+
(part) =>
|
|
571
|
+
"toolCallId" in part && part.toolCallId === typedPart.toolCallId,
|
|
572
|
+
) as ToolUIPart | undefined;
|
|
573
|
+
|
|
574
|
+
if (toolCallPart) {
|
|
575
|
+
toolCallPart.state = "approval-requested";
|
|
576
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
577
|
+
id: typedPart.approvalId,
|
|
578
|
+
};
|
|
579
|
+
} else {
|
|
580
|
+
console.warn(
|
|
581
|
+
"Tool approval request without preceding tool call",
|
|
582
|
+
contentPart,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "tool-approval-response": {
|
|
588
|
+
// Find the tool call that has this approval by matching approval.id
|
|
589
|
+
const typedPart = contentPart as {
|
|
590
|
+
approvalId: string;
|
|
591
|
+
approved: boolean;
|
|
592
|
+
reason?: string;
|
|
593
|
+
};
|
|
594
|
+
const toolCallPart = allParts.find(
|
|
595
|
+
(part) =>
|
|
596
|
+
"approval" in part &&
|
|
597
|
+
(part as ToolUIPart & { approval?: { id: string } }).approval
|
|
598
|
+
?.id === typedPart.approvalId,
|
|
599
|
+
) as ToolUIPart | undefined;
|
|
600
|
+
|
|
601
|
+
if (toolCallPart) {
|
|
602
|
+
if (typedPart.approved) {
|
|
603
|
+
toolCallPart.state = "approval-responded";
|
|
604
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
605
|
+
id: typedPart.approvalId,
|
|
606
|
+
approved: true,
|
|
607
|
+
reason: typedPart.reason,
|
|
608
|
+
};
|
|
609
|
+
} else {
|
|
610
|
+
toolCallPart.state = "output-denied";
|
|
611
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
612
|
+
id: typedPart.approvalId,
|
|
613
|
+
approved: false,
|
|
614
|
+
reason: typedPart.reason,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
} else {
|
|
618
|
+
console.warn(
|
|
619
|
+
"Tool approval response without matching approval request",
|
|
620
|
+
contentPart,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
default: {
|
|
626
|
+
const maybeSource = contentPart as unknown as SourcePart;
|
|
627
|
+
if (maybeSource.type === "source") {
|
|
628
|
+
allParts.push(toSourcePart(maybeSource));
|
|
629
|
+
} else {
|
|
630
|
+
console.warn(
|
|
631
|
+
"Unknown content part type for assistant",
|
|
632
|
+
contentPart,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Add source parts
|
|
640
|
+
for (const source of message.sources ?? []) {
|
|
641
|
+
allParts.push(toSourcePart(source));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
...common,
|
|
647
|
+
role: "assistant",
|
|
648
|
+
text: joinText(allParts),
|
|
649
|
+
status,
|
|
650
|
+
parts: allParts,
|
|
651
|
+
metadata: group.find((m) => m.metadata)?.metadata,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function toSourcePart(
|
|
656
|
+
part: SourcePart | Infer<typeof vSource>,
|
|
657
|
+
): SourceUrlUIPart | SourceDocumentUIPart {
|
|
658
|
+
if (part.sourceType === "url") {
|
|
659
|
+
return {
|
|
660
|
+
type: "source-url",
|
|
661
|
+
url: part.url,
|
|
662
|
+
sourceId: part.id,
|
|
663
|
+
providerMetadata: part.providerMetadata,
|
|
664
|
+
title: part.title,
|
|
665
|
+
} satisfies SourceUrlUIPart;
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
type: "source-document",
|
|
669
|
+
mediaType: part.mediaType,
|
|
670
|
+
sourceId: part.id,
|
|
671
|
+
title: part.title,
|
|
672
|
+
filename: part.filename,
|
|
673
|
+
providerMetadata: part.providerMetadata,
|
|
674
|
+
} satisfies SourceDocumentUIPart;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export function combineUIMessages(messages: UIMessage[]): UIMessage[] {
|
|
678
|
+
const combined = messages.reduce((acc, message) => {
|
|
679
|
+
if (!acc.length) {
|
|
680
|
+
return [message];
|
|
681
|
+
}
|
|
682
|
+
const previous = acc.at(-1)!;
|
|
683
|
+
if (
|
|
684
|
+
message.order !== previous.order ||
|
|
685
|
+
previous.role !== message.role ||
|
|
686
|
+
message.role !== "assistant"
|
|
687
|
+
) {
|
|
688
|
+
acc.push(message);
|
|
689
|
+
return acc;
|
|
690
|
+
}
|
|
691
|
+
// We will replace it with a combined message
|
|
692
|
+
acc.pop();
|
|
693
|
+
const newParts = [...previous.parts];
|
|
694
|
+
for (const part of message.parts) {
|
|
695
|
+
const toolCallId = getToolCallId(part);
|
|
696
|
+
if (!toolCallId) {
|
|
697
|
+
newParts.push(part);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const previousPartIndex = newParts.findIndex(
|
|
701
|
+
(p) => getToolCallId(p) === toolCallId,
|
|
702
|
+
);
|
|
703
|
+
if (previousPartIndex === -1) {
|
|
704
|
+
// Tool call not found in previous parts, add it as new
|
|
705
|
+
newParts.push(part);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
const previousPart = newParts.splice(previousPartIndex, 1)[0];
|
|
709
|
+
newParts.push(mergeParts(previousPart, part));
|
|
710
|
+
}
|
|
711
|
+
acc.push({
|
|
712
|
+
...previous,
|
|
713
|
+
...pick(message, ["status", "metadata", "agentName"]),
|
|
714
|
+
parts: newParts,
|
|
715
|
+
text: joinText(newParts),
|
|
716
|
+
});
|
|
717
|
+
return acc;
|
|
718
|
+
}, [] as UIMessage[]);
|
|
719
|
+
return combined;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function getToolCallId(
|
|
723
|
+
part: UIMessage["parts"][number] & { toolCallId?: string },
|
|
724
|
+
) {
|
|
725
|
+
return part.toolCallId;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function mergeParts(
|
|
729
|
+
previousPart: UIMessage["parts"][number],
|
|
730
|
+
part: UIMessage["parts"][number],
|
|
731
|
+
): UIMessage["parts"][number] {
|
|
732
|
+
const merged: Record<string, unknown> = { ...previousPart };
|
|
733
|
+
for (const [key, value] of Object.entries(part)) {
|
|
734
|
+
if (value !== undefined) {
|
|
735
|
+
merged[key] = value;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return merged as ToolUIPart | DynamicToolUIPart;
|
|
739
|
+
}
|