@mastra/react 0.0.0-error-handler-fix-20251020202607 → 0.0.0-esbuild-bundle-worker-20260807182016
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/CHANGELOG.md +4402 -3
- package/LICENSE.md +15 -0
- package/dist/agent/extract-tasks.d.ts +10 -0
- package/dist/agent/extract-tasks.d.ts.map +1 -0
- package/dist/agent/extractRunIdFromMessages.d.ts +10 -0
- package/dist/agent/extractRunIdFromMessages.d.ts.map +1 -0
- package/dist/agent/hooks.d.ts +98 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/signal-data.d.ts +5 -0
- package/dist/agent/signal-data.d.ts.map +1 -0
- package/dist/{src/agent → agent}/types.d.ts +7 -1
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/index.cjs +3314 -1506
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3243 -1444
- package/dist/index.js.map +1 -1
- package/dist/lib/mastra-db/accumulator.d.ts +48 -0
- package/dist/lib/mastra-db/accumulator.d.ts.map +1 -0
- package/dist/lib/mastra-db/formatCompletionFeedback.d.ts +19 -0
- package/dist/lib/mastra-db/formatCompletionFeedback.d.ts.map +1 -0
- package/dist/lib/mastra-db/fromCoreUserMessage.d.ts +14 -0
- package/dist/lib/mastra-db/fromCoreUserMessage.d.ts.map +1 -0
- package/dist/lib/mastra-db/index.d.ts +6 -0
- package/dist/lib/mastra-db/index.d.ts.map +1 -0
- package/dist/lib/mastra-db/types.d.ts +171 -0
- package/dist/lib/mastra-db/types.d.ts.map +1 -0
- package/dist/lib/use-mutation.d.ts +28 -0
- package/dist/lib/use-mutation.d.ts.map +1 -0
- package/dist/mastra-client-context.d.ts +26 -0
- package/dist/mastra-client-context.d.ts.map +1 -0
- package/dist/mastra-react-provider.d.ts +5 -0
- package/dist/mastra-react-provider.d.ts.map +1 -0
- package/dist/react.css +74 -267
- package/dist/{src/ui → ui}/Code/Code.d.ts +5 -3
- package/dist/ui/Code/Code.d.ts.map +1 -0
- package/dist/ui/Code/highlight.d.ts +4 -0
- package/dist/ui/Code/highlight.d.ts.map +1 -0
- package/dist/ui/Code/index.d.ts +2 -0
- package/dist/ui/Code/index.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Entity/Entity.d.ts +6 -5
- package/dist/ui/Entity/Entity.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Entity/Entry.d.ts +4 -3
- package/dist/ui/Entity/Entry.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Entity/ToolApproval.d.ts +6 -5
- package/dist/ui/Entity/ToolApproval.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Entity/context.d.ts +4 -3
- package/dist/ui/Entity/context.d.ts.map +1 -0
- package/dist/ui/Entity/index.d.ts +5 -0
- package/dist/ui/Entity/index.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Entity/types.d.ts +1 -0
- package/dist/ui/Entity/types.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Icon/Icon.d.ts +3 -2
- package/dist/ui/Icon/Icon.d.ts.map +1 -0
- package/dist/ui/Icon/index.d.ts +2 -0
- package/dist/ui/Icon/index.d.ts.map +1 -0
- package/dist/{src/ui → ui}/IconButton/IconButton.d.ts +3 -2
- package/dist/ui/IconButton/IconButton.d.ts.map +1 -0
- package/dist/ui/IconButton/index.d.ts +2 -0
- package/dist/ui/IconButton/index.d.ts.map +1 -0
- package/dist/ui/Icons/AgentIcon.d.ts +3 -0
- package/dist/ui/Icons/AgentIcon.d.ts.map +1 -0
- package/dist/ui/Icons/ToolsIcon.d.ts +3 -0
- package/dist/ui/Icons/ToolsIcon.d.ts.map +1 -0
- package/dist/ui/Icons/WorkflowIcon.d.ts +3 -0
- package/dist/ui/Icons/WorkflowIcon.d.ts.map +1 -0
- package/dist/ui/Icons/index.d.ts +4 -0
- package/dist/ui/Icons/index.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Message/Message.d.ts +11 -10
- package/dist/ui/Message/Message.d.ts.map +1 -0
- package/dist/ui/Message/index.d.ts +2 -0
- package/dist/ui/Message/index.d.ts.map +1 -0
- package/dist/ui/MessageFactory/MessageFactory.d.ts +22 -0
- package/dist/ui/MessageFactory/MessageFactory.d.ts.map +1 -0
- package/dist/ui/MessageFactory/index.d.ts +3 -0
- package/dist/ui/MessageFactory/index.d.ts.map +1 -0
- package/dist/ui/MessageFactory/types.d.ts +181 -0
- package/dist/ui/MessageFactory/types.d.ts.map +1 -0
- package/dist/{src/ui → ui}/Tooltip/Tooltip.d.ts +5 -4
- package/dist/ui/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/ui/Tooltip/index.d.ts +2 -0
- package/dist/ui/Tooltip/index.d.ts.map +1 -0
- package/dist/ui/index.cjs +57 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui-Bd2cu1B9.js +497 -0
- package/dist/ui-Bd2cu1B9.js.map +1 -0
- package/dist/ui-DL8WaLmH.cjs +849 -0
- package/dist/ui-DL8WaLmH.cjs.map +1 -0
- package/dist/voice/index.d.ts +5 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/play-stream-with-web-audio.d.ts +2 -0
- package/dist/voice/play-stream-with-web-audio.d.ts.map +1 -0
- package/dist/voice/record-mic-to-file.d.ts +2 -0
- package/dist/voice/record-mic-to-file.d.ts.map +1 -0
- package/dist/voice/use-speech-recognition.d.ts +20 -0
- package/dist/voice/use-speech-recognition.d.ts.map +1 -0
- package/dist/workflows/WorkflowStepFactory/WorkflowStepFactory.d.ts +8 -0
- package/dist/workflows/WorkflowStepFactory/WorkflowStepFactory.d.ts.map +1 -0
- package/dist/workflows/WorkflowStepFactory/index.d.ts +3 -0
- package/dist/workflows/WorkflowStepFactory/index.d.ts.map +1 -0
- package/dist/workflows/WorkflowStepFactory/types.d.ts +90 -0
- package/dist/workflows/WorkflowStepFactory/types.d.ts.map +1 -0
- package/dist/workflows/hooks.d.ts +34 -0
- package/dist/workflows/hooks.d.ts.map +1 -0
- package/dist/workflows/index.d.ts +4 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/types.d.ts +122 -0
- package/dist/workflows/types.d.ts.map +1 -0
- package/dist/workflows/use-stream-workflow.d.ts +39 -0
- package/dist/workflows/use-stream-workflow.d.ts.map +1 -0
- package/package.json +52 -30
- package/dist/src/agent/hooks.d.ts +0 -46
- package/dist/src/index.d.ts +0 -6
- package/dist/src/lib/ai-sdk/index.d.ts +0 -3
- package/dist/src/lib/ai-sdk/memory/resolveInitialMessages.d.ts +0 -2
- package/dist/src/lib/ai-sdk/transformers/AISdkNetworkTransformer.d.ts +0 -10
- package/dist/src/lib/ai-sdk/transformers/types.d.ts +0 -10
- package/dist/src/lib/ai-sdk/types.d.ts +0 -14
- package/dist/src/lib/ai-sdk/utils/toAssistantUIMessage.d.ts +0 -14
- package/dist/src/lib/ai-sdk/utils/toAssistantUIMessage.test.d.ts +0 -1
- package/dist/src/lib/ai-sdk/utils/toUIMessage.d.ts +0 -17
- package/dist/src/mastra-client-context.d.ts +0 -10
- package/dist/src/mastra-react-provider.d.ts +0 -4
- package/dist/src/ui/Code/highlight.d.ts +0 -3
- package/dist/src/ui/Code/index.d.ts +0 -1
- package/dist/src/ui/Entity/Entity.stories.d.ts +0 -22
- package/dist/src/ui/Entity/index.d.ts +0 -4
- package/dist/src/ui/Icon/index.d.ts +0 -1
- package/dist/src/ui/IconButton/IconButton.stories.d.ts +0 -12
- package/dist/src/ui/IconButton/index.d.ts +0 -1
- package/dist/src/ui/Icons/AgentIcon.d.ts +0 -2
- package/dist/src/ui/Icons/ToolsIcon.d.ts +0 -2
- package/dist/src/ui/Icons/WorkflowIcon.d.ts +0 -2
- package/dist/src/ui/Icons/index.d.ts +0 -3
- package/dist/src/ui/Message/Message.stories.d.ts +0 -13
- package/dist/src/ui/Message/index.d.ts +0 -1
- package/dist/src/ui/Tooltip/Tooltip.stories.d.ts +0 -12
- package/dist/src/ui/Tooltip/index.d.ts +0 -1
- package/dist/src/ui/index.d.ts +0 -7
package/dist/index.cjs
CHANGED
|
@@ -1,1549 +1,3357 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const client = createMastraClient(baseUrl, headers);
|
|
17
|
-
return /* @__PURE__ */ jsxRuntime.jsx(MastraClientContext.Provider, { value: client, children });
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_ui = require("./ui-DL8WaLmH.cjs");
|
|
3
|
+
let _mastra_client_js = require("@mastra/client-js");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let _lukeed_uuid = require("@lukeed/uuid");
|
|
7
|
+
let _mastra_core_agent_message_list = require("@mastra/core/agent/message-list");
|
|
8
|
+
//#region src/mastra-client-context.tsx
|
|
9
|
+
const MastraClientContext = (0, react.createContext)({});
|
|
10
|
+
const MastraClientProvider = ({ children, baseUrl, headers, apiPrefix, credentials = "include", customFetch }) => {
|
|
11
|
+
const client = createMastraClient(baseUrl, headers, apiPrefix, credentials, customFetch);
|
|
12
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MastraClientContext.Provider, {
|
|
13
|
+
value: client,
|
|
14
|
+
children
|
|
15
|
+
});
|
|
18
16
|
};
|
|
19
|
-
const useMastraClient = () => react.useContext(MastraClientContext);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
17
|
+
const useMastraClient = () => (0, react.useContext)(MastraClientContext);
|
|
18
|
+
const IPV4_LOOPBACK_RE = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
19
|
+
const isIPv4Loopback = (hostname) => {
|
|
20
|
+
const m = IPV4_LOOPBACK_RE.exec(hostname);
|
|
21
|
+
if (!m) return false;
|
|
22
|
+
return +m[1] <= 255 && +m[2] <= 255 && +m[3] <= 255;
|
|
26
23
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const isLocalUrl = (url) => {
|
|
25
|
+
if (!url) return true;
|
|
26
|
+
try {
|
|
27
|
+
const { hostname } = new URL(url);
|
|
28
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || isIPv4Loopback(hostname) || hostname === "::1" || hostname === "[::1]";
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
30
32
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
...prev,
|
|
43
|
-
status: "canceled"
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (chunk.type === "workflow-finish") {
|
|
47
|
-
const finalStatus = chunk.payload.workflowStatus;
|
|
48
|
-
const prevSteps = prev?.steps ?? {};
|
|
49
|
-
const lastStep = Object.values(prevSteps).pop();
|
|
50
|
-
return {
|
|
51
|
-
...prev,
|
|
52
|
-
status: chunk.payload.workflowStatus,
|
|
53
|
-
...finalStatus === "success" && lastStep?.status === "success" ? { result: lastStep?.output } : finalStatus === "failed" && lastStep?.status === "failed" ? { error: lastStep?.error } : {}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
const { stepCallId, stepName, ...newPayload } = chunk.payload ?? {};
|
|
57
|
-
const newSteps = {
|
|
58
|
-
...prev?.steps,
|
|
59
|
-
[chunk.payload.id]: {
|
|
60
|
-
...prev?.steps?.[chunk.payload.id],
|
|
61
|
-
...newPayload
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
if (chunk.type === "workflow-step-start") {
|
|
65
|
-
return {
|
|
66
|
-
...prev,
|
|
67
|
-
steps: newSteps
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
if (chunk.type === "workflow-step-suspended") {
|
|
71
|
-
const suspendedStepIds = Object.entries(newSteps).flatMap(
|
|
72
|
-
([stepId, stepResult]) => {
|
|
73
|
-
if (stepResult?.status === "suspended") {
|
|
74
|
-
const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
|
|
75
|
-
return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
|
|
76
|
-
}
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
return {
|
|
81
|
-
...prev,
|
|
82
|
-
status: "suspended",
|
|
83
|
-
steps: newSteps,
|
|
84
|
-
suspendPayload: chunk.payload.suspendPayload,
|
|
85
|
-
suspended: suspendedStepIds
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (chunk.type === "workflow-step-waiting") {
|
|
89
|
-
return {
|
|
90
|
-
...prev,
|
|
91
|
-
status: "waiting",
|
|
92
|
-
steps: newSteps
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
if (chunk.type === "workflow-step-result") {
|
|
96
|
-
return {
|
|
97
|
-
...prev,
|
|
98
|
-
steps: newSteps
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return prev;
|
|
33
|
+
const createMastraClient = (baseUrl, mastraClientHeaders = {}, apiPrefix, credentials = "include", customFetch) => {
|
|
34
|
+
return new _mastra_client_js.MastraClient({
|
|
35
|
+
baseUrl: baseUrl || "",
|
|
36
|
+
headers: isLocalUrl(baseUrl) ? {
|
|
37
|
+
...mastraClientHeaders,
|
|
38
|
+
"x-mastra-dev-playground": "true"
|
|
39
|
+
} : mastraClientHeaders,
|
|
40
|
+
apiPrefix,
|
|
41
|
+
credentials,
|
|
42
|
+
fetch: customFetch
|
|
43
|
+
});
|
|
102
44
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
],
|
|
116
|
-
metadata: {
|
|
117
|
-
...metadata,
|
|
118
|
-
status: "warning"
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
return [...result, newMessage];
|
|
122
|
-
}
|
|
123
|
-
case "start": {
|
|
124
|
-
const newMessage = {
|
|
125
|
-
id: `start-${chunk.runId + Date.now()}`,
|
|
126
|
-
role: "assistant",
|
|
127
|
-
parts: [],
|
|
128
|
-
metadata
|
|
129
|
-
};
|
|
130
|
-
return [...result, newMessage];
|
|
131
|
-
}
|
|
132
|
-
case "text-start":
|
|
133
|
-
case "text-delta": {
|
|
134
|
-
const lastMessage = result[result.length - 1];
|
|
135
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
136
|
-
const parts = [...lastMessage.parts];
|
|
137
|
-
let textPartIndex = parts.findIndex((part) => part.type === "text");
|
|
138
|
-
if (chunk.type === "text-start") {
|
|
139
|
-
if (textPartIndex === -1) {
|
|
140
|
-
parts.push({
|
|
141
|
-
type: "text",
|
|
142
|
-
text: "",
|
|
143
|
-
state: "streaming",
|
|
144
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
if (textPartIndex === -1) {
|
|
149
|
-
parts.push({
|
|
150
|
-
type: "text",
|
|
151
|
-
text: chunk.payload.text,
|
|
152
|
-
state: "streaming",
|
|
153
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
154
|
-
});
|
|
155
|
-
} else {
|
|
156
|
-
const textPart = parts[textPartIndex];
|
|
157
|
-
if (textPart.type === "text") {
|
|
158
|
-
parts[textPartIndex] = {
|
|
159
|
-
...textPart,
|
|
160
|
-
text: textPart.text + chunk.payload.text,
|
|
161
|
-
state: "streaming"
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return [
|
|
167
|
-
...result.slice(0, -1),
|
|
168
|
-
{
|
|
169
|
-
...lastMessage,
|
|
170
|
-
parts
|
|
171
|
-
}
|
|
172
|
-
];
|
|
173
|
-
}
|
|
174
|
-
case "reasoning-delta": {
|
|
175
|
-
const lastMessage = result[result.length - 1];
|
|
176
|
-
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
177
|
-
const newMessage = {
|
|
178
|
-
id: `reasoning-${chunk.runId + Date.now()}`,
|
|
179
|
-
role: "assistant",
|
|
180
|
-
parts: [
|
|
181
|
-
{
|
|
182
|
-
type: "reasoning",
|
|
183
|
-
text: chunk.payload.text,
|
|
184
|
-
state: "streaming",
|
|
185
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
186
|
-
}
|
|
187
|
-
],
|
|
188
|
-
metadata
|
|
189
|
-
};
|
|
190
|
-
return [...result, newMessage];
|
|
191
|
-
}
|
|
192
|
-
const parts = [...lastMessage.parts];
|
|
193
|
-
let reasoningPartIndex = parts.findIndex((part) => part.type === "reasoning");
|
|
194
|
-
if (reasoningPartIndex === -1) {
|
|
195
|
-
parts.push({
|
|
196
|
-
type: "reasoning",
|
|
197
|
-
text: chunk.payload.text,
|
|
198
|
-
state: "streaming",
|
|
199
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
200
|
-
});
|
|
201
|
-
} else {
|
|
202
|
-
const reasoningPart = parts[reasoningPartIndex];
|
|
203
|
-
if (reasoningPart.type === "reasoning") {
|
|
204
|
-
parts[reasoningPartIndex] = {
|
|
205
|
-
...reasoningPart,
|
|
206
|
-
text: reasoningPart.text + chunk.payload.text,
|
|
207
|
-
state: "streaming"
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return [
|
|
212
|
-
...result.slice(0, -1),
|
|
213
|
-
{
|
|
214
|
-
...lastMessage,
|
|
215
|
-
parts
|
|
216
|
-
}
|
|
217
|
-
];
|
|
218
|
-
}
|
|
219
|
-
case "tool-call": {
|
|
220
|
-
const lastMessage = result[result.length - 1];
|
|
221
|
-
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
222
|
-
const newMessage = {
|
|
223
|
-
id: `tool-call-${chunk.runId + Date.now()}`,
|
|
224
|
-
role: "assistant",
|
|
225
|
-
parts: [
|
|
226
|
-
{
|
|
227
|
-
type: "dynamic-tool",
|
|
228
|
-
toolName: chunk.payload.toolName,
|
|
229
|
-
toolCallId: chunk.payload.toolCallId,
|
|
230
|
-
state: "input-available",
|
|
231
|
-
input: chunk.payload.args,
|
|
232
|
-
callProviderMetadata: chunk.payload.providerMetadata
|
|
233
|
-
}
|
|
234
|
-
],
|
|
235
|
-
metadata
|
|
236
|
-
};
|
|
237
|
-
return [...result, newMessage];
|
|
238
|
-
}
|
|
239
|
-
const parts = [...lastMessage.parts];
|
|
240
|
-
parts.push({
|
|
241
|
-
type: "dynamic-tool",
|
|
242
|
-
toolName: chunk.payload.toolName,
|
|
243
|
-
toolCallId: chunk.payload.toolCallId,
|
|
244
|
-
state: "input-available",
|
|
245
|
-
input: chunk.payload.args,
|
|
246
|
-
callProviderMetadata: chunk.payload.providerMetadata
|
|
247
|
-
});
|
|
248
|
-
return [
|
|
249
|
-
...result.slice(0, -1),
|
|
250
|
-
{
|
|
251
|
-
...lastMessage,
|
|
252
|
-
parts
|
|
253
|
-
}
|
|
254
|
-
];
|
|
255
|
-
}
|
|
256
|
-
case "tool-result": {
|
|
257
|
-
const lastMessage = result[result.length - 1];
|
|
258
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
259
|
-
const parts = [...lastMessage.parts];
|
|
260
|
-
const toolPartIndex = parts.findIndex(
|
|
261
|
-
(part) => part.type === "dynamic-tool" && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
262
|
-
);
|
|
263
|
-
if (toolPartIndex !== -1) {
|
|
264
|
-
const toolPart = parts[toolPartIndex];
|
|
265
|
-
if (toolPart.type === "dynamic-tool") {
|
|
266
|
-
if (chunk.payload.isError) {
|
|
267
|
-
parts[toolPartIndex] = {
|
|
268
|
-
type: "dynamic-tool",
|
|
269
|
-
toolName: toolPart.toolName,
|
|
270
|
-
toolCallId: toolPart.toolCallId,
|
|
271
|
-
state: "output-error",
|
|
272
|
-
input: toolPart.input,
|
|
273
|
-
errorText: String(chunk.payload.result),
|
|
274
|
-
callProviderMetadata: chunk.payload.providerMetadata
|
|
275
|
-
};
|
|
276
|
-
} else {
|
|
277
|
-
const isWorkflow = Boolean(chunk.payload.result?.result?.steps);
|
|
278
|
-
parts[toolPartIndex] = {
|
|
279
|
-
type: "dynamic-tool",
|
|
280
|
-
toolName: toolPart.toolName,
|
|
281
|
-
toolCallId: toolPart.toolCallId,
|
|
282
|
-
state: "output-available",
|
|
283
|
-
input: toolPart.input,
|
|
284
|
-
output: isWorkflow ? chunk.payload.result?.result : chunk.payload.result,
|
|
285
|
-
callProviderMetadata: chunk.payload.providerMetadata
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return [
|
|
291
|
-
...result.slice(0, -1),
|
|
292
|
-
{
|
|
293
|
-
...lastMessage,
|
|
294
|
-
parts
|
|
295
|
-
}
|
|
296
|
-
];
|
|
297
|
-
}
|
|
298
|
-
case "tool-output": {
|
|
299
|
-
const lastMessage = result[result.length - 1];
|
|
300
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
301
|
-
const parts = [...lastMessage.parts];
|
|
302
|
-
const toolPartIndex = parts.findIndex(
|
|
303
|
-
(part) => part.type === "dynamic-tool" && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
304
|
-
);
|
|
305
|
-
if (toolPartIndex !== -1) {
|
|
306
|
-
const toolPart = parts[toolPartIndex];
|
|
307
|
-
if (toolPart.type === "dynamic-tool") {
|
|
308
|
-
if (chunk.payload.output?.type?.startsWith("workflow-")) {
|
|
309
|
-
const existingWorkflowState = toolPart.output || {};
|
|
310
|
-
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(
|
|
311
|
-
existingWorkflowState,
|
|
312
|
-
chunk.payload.output
|
|
313
|
-
);
|
|
314
|
-
parts[toolPartIndex] = {
|
|
315
|
-
...toolPart,
|
|
316
|
-
output: updatedWorkflowState
|
|
317
|
-
};
|
|
318
|
-
} else {
|
|
319
|
-
const currentOutput = toolPart.output || [];
|
|
320
|
-
const existingOutput = Array.isArray(currentOutput) ? currentOutput : [];
|
|
321
|
-
parts[toolPartIndex] = {
|
|
322
|
-
...toolPart,
|
|
323
|
-
output: [...existingOutput, chunk.payload.output]
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return [
|
|
329
|
-
...result.slice(0, -1),
|
|
330
|
-
{
|
|
331
|
-
...lastMessage,
|
|
332
|
-
parts
|
|
333
|
-
}
|
|
334
|
-
];
|
|
335
|
-
}
|
|
336
|
-
case "source": {
|
|
337
|
-
const lastMessage = result[result.length - 1];
|
|
338
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
339
|
-
const parts = [...lastMessage.parts];
|
|
340
|
-
if (chunk.payload.sourceType === "url") {
|
|
341
|
-
parts.push({
|
|
342
|
-
type: "source-url",
|
|
343
|
-
sourceId: chunk.payload.id,
|
|
344
|
-
url: chunk.payload.url || "",
|
|
345
|
-
title: chunk.payload.title,
|
|
346
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
347
|
-
});
|
|
348
|
-
} else if (chunk.payload.sourceType === "document") {
|
|
349
|
-
parts.push({
|
|
350
|
-
type: "source-document",
|
|
351
|
-
sourceId: chunk.payload.id,
|
|
352
|
-
mediaType: chunk.payload.mimeType || "application/octet-stream",
|
|
353
|
-
title: chunk.payload.title,
|
|
354
|
-
filename: chunk.payload.filename,
|
|
355
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
return [
|
|
359
|
-
...result.slice(0, -1),
|
|
360
|
-
{
|
|
361
|
-
...lastMessage,
|
|
362
|
-
parts
|
|
363
|
-
}
|
|
364
|
-
];
|
|
365
|
-
}
|
|
366
|
-
case "file": {
|
|
367
|
-
const lastMessage = result[result.length - 1];
|
|
368
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
369
|
-
const parts = [...lastMessage.parts];
|
|
370
|
-
let url;
|
|
371
|
-
if (typeof chunk.payload.data === "string") {
|
|
372
|
-
url = chunk.payload.base64 ? `data:${chunk.payload.mimeType};base64,${chunk.payload.data}` : `data:${chunk.payload.mimeType},${encodeURIComponent(chunk.payload.data)}`;
|
|
373
|
-
} else {
|
|
374
|
-
const base64 = btoa(String.fromCharCode(...chunk.payload.data));
|
|
375
|
-
url = `data:${chunk.payload.mimeType};base64,${base64}`;
|
|
376
|
-
}
|
|
377
|
-
parts.push({
|
|
378
|
-
type: "file",
|
|
379
|
-
mediaType: chunk.payload.mimeType,
|
|
380
|
-
url,
|
|
381
|
-
providerMetadata: chunk.payload.providerMetadata
|
|
382
|
-
});
|
|
383
|
-
return [
|
|
384
|
-
...result.slice(0, -1),
|
|
385
|
-
{
|
|
386
|
-
...lastMessage,
|
|
387
|
-
parts
|
|
388
|
-
}
|
|
389
|
-
];
|
|
390
|
-
}
|
|
391
|
-
case "finish": {
|
|
392
|
-
const lastMessage = result[result.length - 1];
|
|
393
|
-
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
394
|
-
const parts = lastMessage.parts.map((part) => {
|
|
395
|
-
if (part.type === "text" && part.state === "streaming") {
|
|
396
|
-
return { ...part, state: "done" };
|
|
397
|
-
}
|
|
398
|
-
if (part.type === "reasoning" && part.state === "streaming") {
|
|
399
|
-
return { ...part, state: "done" };
|
|
400
|
-
}
|
|
401
|
-
return part;
|
|
402
|
-
});
|
|
403
|
-
return [
|
|
404
|
-
...result.slice(0, -1),
|
|
405
|
-
{
|
|
406
|
-
...lastMessage,
|
|
407
|
-
parts
|
|
408
|
-
}
|
|
409
|
-
];
|
|
410
|
-
}
|
|
411
|
-
case "error": {
|
|
412
|
-
const newMessage = {
|
|
413
|
-
id: `error-${chunk.runId + Date.now()}`,
|
|
414
|
-
role: "assistant",
|
|
415
|
-
parts: [
|
|
416
|
-
{
|
|
417
|
-
type: "text",
|
|
418
|
-
text: typeof chunk.payload.error === "string" ? chunk.payload.error : JSON.stringify(chunk.payload.error)
|
|
419
|
-
}
|
|
420
|
-
],
|
|
421
|
-
metadata: {
|
|
422
|
-
...metadata,
|
|
423
|
-
status: "error"
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
return [...result, newMessage];
|
|
427
|
-
}
|
|
428
|
-
// For all other chunk types, return conversation unchanged
|
|
429
|
-
default:
|
|
430
|
-
return result;
|
|
431
|
-
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/mastra-react-provider.tsx
|
|
47
|
+
const MastraReactProvider = ({ children, baseUrl, headers, apiPrefix, credentials, customFetch }) => {
|
|
48
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MastraClientProvider, {
|
|
49
|
+
baseUrl,
|
|
50
|
+
headers,
|
|
51
|
+
apiPrefix,
|
|
52
|
+
credentials,
|
|
53
|
+
customFetch,
|
|
54
|
+
children
|
|
55
|
+
});
|
|
432
56
|
};
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
text: part.text,
|
|
441
|
-
metadata: message.metadata
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
if (part.type === "reasoning") {
|
|
445
|
-
return {
|
|
446
|
-
type: "reasoning",
|
|
447
|
-
text: part.text,
|
|
448
|
-
metadata: message.metadata
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
if (part.type === "source-url") {
|
|
452
|
-
return {
|
|
453
|
-
type: "source",
|
|
454
|
-
sourceType: "url",
|
|
455
|
-
id: part.sourceId,
|
|
456
|
-
url: part.url,
|
|
457
|
-
title: part.title,
|
|
458
|
-
metadata: message.metadata
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
if (part.type === "source-document") {
|
|
462
|
-
return {
|
|
463
|
-
type: "file",
|
|
464
|
-
filename: part.filename,
|
|
465
|
-
mimeType: part.mediaType,
|
|
466
|
-
data: "",
|
|
467
|
-
// Source documents don't have inline data
|
|
468
|
-
metadata: message.metadata
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
if (part.type === "file") {
|
|
472
|
-
return {
|
|
473
|
-
type: "file",
|
|
474
|
-
mimeType: part.mediaType,
|
|
475
|
-
data: part.url,
|
|
476
|
-
// Use URL as data source
|
|
477
|
-
metadata: message.metadata
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
if (part.type === "dynamic-tool") {
|
|
481
|
-
const baseToolCall = {
|
|
482
|
-
type: "tool-call",
|
|
483
|
-
toolCallId: part.toolCallId,
|
|
484
|
-
toolName: part.toolName,
|
|
485
|
-
argsText: JSON.stringify(part.input),
|
|
486
|
-
args: part.input,
|
|
487
|
-
metadata: message.metadata
|
|
488
|
-
};
|
|
489
|
-
if (part.state === "output-error" && "errorText" in part) {
|
|
490
|
-
return { ...baseToolCall, result: part.errorText, isError: true };
|
|
491
|
-
}
|
|
492
|
-
if ("output" in part) {
|
|
493
|
-
return { ...baseToolCall, result: part.output };
|
|
494
|
-
}
|
|
495
|
-
return baseToolCall;
|
|
496
|
-
}
|
|
497
|
-
if (part.type.startsWith("tool-") && part.state !== "input-available") {
|
|
498
|
-
const toolName = "toolName" in part && typeof part.toolName === "string" ? part.toolName : part.type.substring(5);
|
|
499
|
-
const baseToolCall = {
|
|
500
|
-
type: "tool-call",
|
|
501
|
-
toolCallId: "toolCallId" in part && typeof part.toolCallId === "string" ? part.toolCallId : "",
|
|
502
|
-
toolName,
|
|
503
|
-
argsText: "input" in part ? JSON.stringify(part.input) : "{}",
|
|
504
|
-
args: "input" in part ? part.input : {},
|
|
505
|
-
metadata: message.metadata
|
|
506
|
-
};
|
|
507
|
-
if ("output" in part) {
|
|
508
|
-
return { ...baseToolCall, result: part.output };
|
|
509
|
-
} else if ("error" in part) {
|
|
510
|
-
return { ...baseToolCall, result: part.error, isError: true };
|
|
511
|
-
}
|
|
512
|
-
return baseToolCall;
|
|
513
|
-
}
|
|
514
|
-
return {
|
|
515
|
-
type: "text",
|
|
516
|
-
text: "",
|
|
517
|
-
metadata: message.metadata
|
|
518
|
-
};
|
|
519
|
-
});
|
|
520
|
-
let status;
|
|
521
|
-
if (message.role === "assistant" && content.length > 0) {
|
|
522
|
-
const hasStreamingParts = message.parts.some(
|
|
523
|
-
(part) => part.type === "text" && "state" in part && part.state === "streaming" || part.type === "reasoning" && "state" in part && part.state === "streaming"
|
|
524
|
-
);
|
|
525
|
-
const hasToolCalls = message.parts.some((part) => part.type === "dynamic-tool" || part.type.startsWith("tool-"));
|
|
526
|
-
const hasInputAvailableTools = message.parts.some(
|
|
527
|
-
(part) => part.type === "dynamic-tool" && part.state === "input-available"
|
|
528
|
-
);
|
|
529
|
-
const hasErrorTools = message.parts.some(
|
|
530
|
-
(part) => part.type === "dynamic-tool" && part.state === "output-error" || part.type.startsWith("tool-") && "error" in part
|
|
531
|
-
);
|
|
532
|
-
if (hasStreamingParts) {
|
|
533
|
-
status = { type: "running" };
|
|
534
|
-
} else if (hasInputAvailableTools && hasToolCalls) {
|
|
535
|
-
status = { type: "requires-action", reason: "tool-calls" };
|
|
536
|
-
} else if (hasErrorTools) {
|
|
537
|
-
status = { type: "incomplete", reason: "error" };
|
|
538
|
-
} else {
|
|
539
|
-
status = { type: "complete", reason: "stop" };
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
const threadMessage = {
|
|
543
|
-
role: message.role,
|
|
544
|
-
content,
|
|
545
|
-
id: message.id,
|
|
546
|
-
createdAt: extendedMessage.createdAt,
|
|
547
|
-
status,
|
|
548
|
-
attachments: extendedMessage.experimental_attachments
|
|
549
|
-
};
|
|
550
|
-
return threadMessage;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/agent/signal-data.ts
|
|
59
|
+
const uint8ArrayToBase64 = (bytes) => {
|
|
60
|
+
const chunkSize = 32768;
|
|
61
|
+
let binary = "";
|
|
62
|
+
for (let i = 0; i < bytes.length; i += chunkSize) binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
63
|
+
return btoa(binary);
|
|
551
64
|
};
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (chunk.type === "routing-agent-text-delta") {
|
|
557
|
-
return this.handleRoutingAgentConversation(chunk, newConversation);
|
|
558
|
-
}
|
|
559
|
-
if (chunk.type.startsWith("agent-execution-")) {
|
|
560
|
-
return this.handleAgentConversation(chunk, newConversation, metadata);
|
|
561
|
-
}
|
|
562
|
-
if (chunk.type.startsWith("workflow-execution-")) {
|
|
563
|
-
return this.handleWorkflowConversation(chunk, newConversation, metadata);
|
|
564
|
-
}
|
|
565
|
-
if (chunk.type.startsWith("tool-execution-")) {
|
|
566
|
-
return this.handleToolConversation(chunk, newConversation, metadata);
|
|
567
|
-
}
|
|
568
|
-
if (chunk.type === "network-execution-event-step-finish") {
|
|
569
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
570
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
571
|
-
const agentChunk = chunk.payload;
|
|
572
|
-
const parts = [...lastMessage.parts];
|
|
573
|
-
const textPartIndex = parts.findIndex((part) => part.type === "text");
|
|
574
|
-
if (textPartIndex === -1) {
|
|
575
|
-
parts.push({
|
|
576
|
-
type: "text",
|
|
577
|
-
text: agentChunk.result,
|
|
578
|
-
state: "done"
|
|
579
|
-
});
|
|
580
|
-
return [
|
|
581
|
-
...newConversation.slice(0, -1),
|
|
582
|
-
{
|
|
583
|
-
...lastMessage,
|
|
584
|
-
parts
|
|
585
|
-
}
|
|
586
|
-
];
|
|
587
|
-
}
|
|
588
|
-
const textPart = parts[textPartIndex];
|
|
589
|
-
if (textPart.type === "text") {
|
|
590
|
-
parts[textPartIndex] = {
|
|
591
|
-
...textPart,
|
|
592
|
-
state: "done"
|
|
593
|
-
};
|
|
594
|
-
return [
|
|
595
|
-
...newConversation.slice(0, -1),
|
|
596
|
-
{
|
|
597
|
-
...lastMessage,
|
|
598
|
-
parts
|
|
599
|
-
}
|
|
600
|
-
];
|
|
601
|
-
}
|
|
602
|
-
return newConversation;
|
|
603
|
-
}
|
|
604
|
-
return newConversation;
|
|
605
|
-
}
|
|
606
|
-
handleRoutingAgentConversation = (chunk, newConversation) => {
|
|
607
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
608
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
609
|
-
const agentChunk = chunk.payload;
|
|
610
|
-
const parts = [...lastMessage.parts];
|
|
611
|
-
const textPartIndex = parts.findIndex((part) => part.type === "text");
|
|
612
|
-
if (textPartIndex === -1) {
|
|
613
|
-
parts.push({
|
|
614
|
-
type: "text",
|
|
615
|
-
text: agentChunk.text,
|
|
616
|
-
state: "streaming"
|
|
617
|
-
});
|
|
618
|
-
return [
|
|
619
|
-
...newConversation.slice(0, -1),
|
|
620
|
-
{
|
|
621
|
-
...lastMessage,
|
|
622
|
-
parts
|
|
623
|
-
}
|
|
624
|
-
];
|
|
625
|
-
}
|
|
626
|
-
const textPart = parts[textPartIndex];
|
|
627
|
-
if (textPart.type === "text") {
|
|
628
|
-
parts[textPartIndex] = {
|
|
629
|
-
...textPart,
|
|
630
|
-
text: textPart.text + agentChunk.text,
|
|
631
|
-
state: "streaming"
|
|
632
|
-
};
|
|
633
|
-
return [
|
|
634
|
-
...newConversation.slice(0, -1),
|
|
635
|
-
{
|
|
636
|
-
...lastMessage,
|
|
637
|
-
parts
|
|
638
|
-
}
|
|
639
|
-
];
|
|
640
|
-
}
|
|
641
|
-
return newConversation;
|
|
642
|
-
};
|
|
643
|
-
handleAgentConversation = (chunk, newConversation, metadata) => {
|
|
644
|
-
if (chunk.type === "agent-execution-start") {
|
|
645
|
-
const primitiveId = chunk.payload?.args?.primitiveId;
|
|
646
|
-
const runId = chunk.payload.runId;
|
|
647
|
-
if (!primitiveId || !runId) return newConversation;
|
|
648
|
-
const newMessage = {
|
|
649
|
-
id: `agent-execution-start-${runId}-${Date.now()}`,
|
|
650
|
-
role: "assistant",
|
|
651
|
-
parts: [
|
|
652
|
-
{
|
|
653
|
-
type: "dynamic-tool",
|
|
654
|
-
toolName: primitiveId,
|
|
655
|
-
toolCallId: runId,
|
|
656
|
-
state: "input-available",
|
|
657
|
-
input: chunk.payload.args
|
|
658
|
-
}
|
|
659
|
-
],
|
|
660
|
-
metadata: {
|
|
661
|
-
...metadata,
|
|
662
|
-
selectionReason: chunk.payload?.args?.selectionReason || "",
|
|
663
|
-
agentInput: chunk.payload?.args?.task,
|
|
664
|
-
mode: "network",
|
|
665
|
-
from: "AGENT"
|
|
666
|
-
}
|
|
667
|
-
};
|
|
668
|
-
return [...newConversation, newMessage];
|
|
669
|
-
}
|
|
670
|
-
if (chunk.type === "agent-execution-end") {
|
|
671
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
672
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
673
|
-
const parts = [...lastMessage.parts];
|
|
674
|
-
const toolPartIndex = parts.findIndex((part) => part.type === "dynamic-tool");
|
|
675
|
-
if (toolPartIndex !== -1) {
|
|
676
|
-
const toolPart = parts[toolPartIndex];
|
|
677
|
-
if (toolPart.type === "dynamic-tool") {
|
|
678
|
-
const currentOutput = toolPart.output;
|
|
679
|
-
parts[toolPartIndex] = {
|
|
680
|
-
type: "dynamic-tool",
|
|
681
|
-
toolName: toolPart.toolName,
|
|
682
|
-
toolCallId: toolPart.toolCallId,
|
|
683
|
-
state: "output-available",
|
|
684
|
-
input: toolPart.input,
|
|
685
|
-
output: {
|
|
686
|
-
...currentOutput,
|
|
687
|
-
result: currentOutput?.result || chunk.payload?.result || ""
|
|
688
|
-
}
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return [
|
|
693
|
-
...newConversation.slice(0, -1),
|
|
694
|
-
{
|
|
695
|
-
...lastMessage,
|
|
696
|
-
parts
|
|
697
|
-
}
|
|
698
|
-
];
|
|
699
|
-
}
|
|
700
|
-
if (chunk.type.startsWith("agent-execution-event-")) {
|
|
701
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
702
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
703
|
-
const agentChunk = chunk.payload;
|
|
704
|
-
const parts = [...lastMessage.parts];
|
|
705
|
-
const toolPartIndex = parts.findIndex((part) => part.type === "dynamic-tool");
|
|
706
|
-
if (toolPartIndex === -1) return newConversation;
|
|
707
|
-
const toolPart = parts[toolPartIndex];
|
|
708
|
-
if (agentChunk.type === "text-delta") {
|
|
709
|
-
const childMessages = toolPart?.output?.childMessages || [];
|
|
710
|
-
const lastChildMessage = childMessages[childMessages.length - 1];
|
|
711
|
-
const textMessage = { type: "text", content: (lastChildMessage?.content || "") + agentChunk.payload.text };
|
|
712
|
-
const nextMessages = lastChildMessage?.type === "text" ? [...childMessages.slice(0, -1), textMessage] : [...childMessages, textMessage];
|
|
713
|
-
parts[toolPartIndex] = {
|
|
714
|
-
...toolPart,
|
|
715
|
-
output: {
|
|
716
|
-
childMessages: nextMessages
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
} else if (agentChunk.type === "tool-call") {
|
|
720
|
-
const childMessages = toolPart?.output?.childMessages || [];
|
|
721
|
-
parts[toolPartIndex] = {
|
|
722
|
-
...toolPart,
|
|
723
|
-
output: {
|
|
724
|
-
...toolPart?.output,
|
|
725
|
-
childMessages: [
|
|
726
|
-
...childMessages,
|
|
727
|
-
{
|
|
728
|
-
type: "tool",
|
|
729
|
-
toolCallId: agentChunk.payload.toolCallId,
|
|
730
|
-
toolName: agentChunk.payload.toolName,
|
|
731
|
-
args: agentChunk.payload.args
|
|
732
|
-
}
|
|
733
|
-
]
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
} else if (agentChunk.type === "tool-output") {
|
|
737
|
-
if (agentChunk.payload?.output?.type?.startsWith("workflow-")) {
|
|
738
|
-
const childMessages = toolPart?.output?.childMessages || [];
|
|
739
|
-
const lastToolIndex = childMessages.length - 1;
|
|
740
|
-
const currentMessage = childMessages[lastToolIndex];
|
|
741
|
-
const actualExistingWorkflowState = currentMessage?.toolOutput || {};
|
|
742
|
-
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(
|
|
743
|
-
actualExistingWorkflowState,
|
|
744
|
-
agentChunk.payload.output
|
|
745
|
-
);
|
|
746
|
-
if (lastToolIndex >= 0 && childMessages[lastToolIndex]?.type === "tool") {
|
|
747
|
-
parts[toolPartIndex] = {
|
|
748
|
-
...toolPart,
|
|
749
|
-
output: {
|
|
750
|
-
...toolPart?.output,
|
|
751
|
-
childMessages: [
|
|
752
|
-
...childMessages.slice(0, -1),
|
|
753
|
-
{
|
|
754
|
-
...currentMessage,
|
|
755
|
-
toolOutput: updatedWorkflowState
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
} else if (agentChunk.type === "tool-result") {
|
|
763
|
-
const childMessages = toolPart?.output?.childMessages || [];
|
|
764
|
-
const lastToolIndex = childMessages.length - 1;
|
|
765
|
-
const isWorkflow = Boolean(agentChunk.payload?.result?.result?.steps);
|
|
766
|
-
if (lastToolIndex >= 0 && childMessages[lastToolIndex]?.type === "tool") {
|
|
767
|
-
parts[toolPartIndex] = {
|
|
768
|
-
...toolPart,
|
|
769
|
-
output: {
|
|
770
|
-
...toolPart?.output,
|
|
771
|
-
childMessages: [
|
|
772
|
-
...childMessages.slice(0, -1),
|
|
773
|
-
{
|
|
774
|
-
...childMessages[lastToolIndex],
|
|
775
|
-
toolOutput: isWorkflow ? agentChunk.payload.result.result : agentChunk.payload.result
|
|
776
|
-
}
|
|
777
|
-
]
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return [
|
|
783
|
-
...newConversation.slice(0, -1),
|
|
784
|
-
{
|
|
785
|
-
...lastMessage,
|
|
786
|
-
parts
|
|
787
|
-
}
|
|
788
|
-
];
|
|
789
|
-
}
|
|
790
|
-
return newConversation;
|
|
791
|
-
};
|
|
792
|
-
handleWorkflowConversation = (chunk, newConversation, metadata) => {
|
|
793
|
-
if (chunk.type === "workflow-execution-start") {
|
|
794
|
-
const primitiveId = chunk.payload?.args?.primitiveId;
|
|
795
|
-
const runId = chunk.payload.runId;
|
|
796
|
-
if (!primitiveId || !runId) return newConversation;
|
|
797
|
-
let agentInput;
|
|
798
|
-
try {
|
|
799
|
-
agentInput = JSON.parse(chunk?.payload?.args?.prompt);
|
|
800
|
-
} catch (e) {
|
|
801
|
-
agentInput = chunk?.payload?.args?.prompt;
|
|
802
|
-
}
|
|
803
|
-
const newMessage = {
|
|
804
|
-
id: `workflow-start-${runId}-${Date.now()}`,
|
|
805
|
-
role: "assistant",
|
|
806
|
-
parts: [
|
|
807
|
-
{
|
|
808
|
-
type: "dynamic-tool",
|
|
809
|
-
toolName: primitiveId,
|
|
810
|
-
toolCallId: runId,
|
|
811
|
-
state: "input-available",
|
|
812
|
-
input: chunk.payload.args
|
|
813
|
-
}
|
|
814
|
-
],
|
|
815
|
-
metadata: {
|
|
816
|
-
...metadata,
|
|
817
|
-
selectionReason: chunk.payload?.args?.selectionReason || "",
|
|
818
|
-
from: "WORKFLOW",
|
|
819
|
-
mode: "network",
|
|
820
|
-
agentInput
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
return [...newConversation, newMessage];
|
|
824
|
-
}
|
|
825
|
-
if (chunk.type.startsWith("workflow-execution-event-")) {
|
|
826
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
827
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
828
|
-
const parts = [...lastMessage.parts];
|
|
829
|
-
const toolPartIndex = parts.findIndex((part) => part.type === "dynamic-tool");
|
|
830
|
-
if (toolPartIndex === -1) return newConversation;
|
|
831
|
-
const toolPart = parts[toolPartIndex];
|
|
832
|
-
if (toolPart.type !== "dynamic-tool") return newConversation;
|
|
833
|
-
const existingWorkflowState = toolPart.output || {};
|
|
834
|
-
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(existingWorkflowState, chunk.payload);
|
|
835
|
-
parts[toolPartIndex] = {
|
|
836
|
-
...toolPart,
|
|
837
|
-
output: updatedWorkflowState
|
|
838
|
-
};
|
|
839
|
-
return [
|
|
840
|
-
...newConversation.slice(0, -1),
|
|
841
|
-
{
|
|
842
|
-
...lastMessage,
|
|
843
|
-
parts
|
|
844
|
-
}
|
|
845
|
-
];
|
|
846
|
-
}
|
|
847
|
-
return newConversation;
|
|
848
|
-
};
|
|
849
|
-
handleToolConversation = (chunk, newConversation, metadata) => {
|
|
850
|
-
if (chunk.type === "tool-execution-start") {
|
|
851
|
-
const { args: argsData } = chunk.payload;
|
|
852
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
853
|
-
const nestedArgs = argsData.args || {};
|
|
854
|
-
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
855
|
-
const newMessage = {
|
|
856
|
-
id: `tool-start-${chunk.runId}-${Date.now()}`,
|
|
857
|
-
role: "assistant",
|
|
858
|
-
parts: [
|
|
859
|
-
{
|
|
860
|
-
type: "dynamic-tool",
|
|
861
|
-
toolName: argsData.toolName || "unknown",
|
|
862
|
-
toolCallId: argsData.toolCallId || "unknown",
|
|
863
|
-
state: "input-available",
|
|
864
|
-
input: nestedArgs
|
|
865
|
-
}
|
|
866
|
-
],
|
|
867
|
-
metadata: {
|
|
868
|
-
...metadata,
|
|
869
|
-
selectionReason: metadata?.mode === "network" ? metadata.selectionReason || argsData.selectionReason : "",
|
|
870
|
-
mode: "network",
|
|
871
|
-
agentInput: nestedArgs
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
return [...newConversation, newMessage];
|
|
875
|
-
}
|
|
876
|
-
const parts = [...lastMessage.parts];
|
|
877
|
-
parts.push({
|
|
878
|
-
type: "dynamic-tool",
|
|
879
|
-
toolName: argsData.toolName || "unknown",
|
|
880
|
-
toolCallId: argsData.toolCallId || "unknown",
|
|
881
|
-
state: "input-available",
|
|
882
|
-
input: nestedArgs
|
|
883
|
-
});
|
|
884
|
-
return [
|
|
885
|
-
...newConversation.slice(0, -1),
|
|
886
|
-
{
|
|
887
|
-
...lastMessage,
|
|
888
|
-
parts
|
|
889
|
-
}
|
|
890
|
-
];
|
|
891
|
-
}
|
|
892
|
-
if (chunk.type === "tool-execution-end") {
|
|
893
|
-
const lastMessage = newConversation[newConversation.length - 1];
|
|
894
|
-
if (!lastMessage || lastMessage.role !== "assistant") return newConversation;
|
|
895
|
-
const parts = [...lastMessage.parts];
|
|
896
|
-
const toolPartIndex = parts.findIndex(
|
|
897
|
-
(part) => part.type === "dynamic-tool" && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
898
|
-
);
|
|
899
|
-
if (toolPartIndex !== -1) {
|
|
900
|
-
const toolPart = parts[toolPartIndex];
|
|
901
|
-
if (toolPart.type === "dynamic-tool") {
|
|
902
|
-
const currentOutput = toolPart.output;
|
|
903
|
-
parts[toolPartIndex] = {
|
|
904
|
-
type: "dynamic-tool",
|
|
905
|
-
toolName: toolPart.toolName,
|
|
906
|
-
toolCallId: toolPart.toolCallId,
|
|
907
|
-
state: "output-available",
|
|
908
|
-
input: toolPart.input,
|
|
909
|
-
output: currentOutput?.result || chunk.payload?.result || ""
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return [
|
|
914
|
-
...newConversation.slice(0, -1),
|
|
915
|
-
{
|
|
916
|
-
...lastMessage,
|
|
917
|
-
parts
|
|
918
|
-
}
|
|
919
|
-
];
|
|
920
|
-
}
|
|
921
|
-
return newConversation;
|
|
922
|
-
};
|
|
65
|
+
function convertSignalDataToBase64String(content) {
|
|
66
|
+
if (typeof content === "string") return content;
|
|
67
|
+
const bytes = content instanceof ArrayBuffer ? new Uint8Array(content) : content;
|
|
68
|
+
return uint8ArrayToBase64(bytes);
|
|
923
69
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
toolCallId: toolCall.payload.toolCallId,
|
|
954
|
-
toolName: toolCall.payload.toolName,
|
|
955
|
-
args: toolCall.payload.args,
|
|
956
|
-
toolOutput: isWorkflow ? toolResult?.result?.result : toolResult?.result
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
if (finalResult && finalResult.text) {
|
|
961
|
-
childMessages.push({
|
|
962
|
-
type: "text",
|
|
963
|
-
content: finalResult.text
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
const result = {
|
|
967
|
-
childMessages,
|
|
968
|
-
result: finalResult?.text || ""
|
|
969
|
-
};
|
|
970
|
-
console.log("json", json);
|
|
971
|
-
const nextMessage = {
|
|
972
|
-
role: "assistant",
|
|
973
|
-
parts: [
|
|
974
|
-
{
|
|
975
|
-
type: "dynamic-tool",
|
|
976
|
-
toolCallId: primitiveId,
|
|
977
|
-
toolName: primitiveId,
|
|
978
|
-
state: "output-available",
|
|
979
|
-
input: json.input,
|
|
980
|
-
output: result
|
|
981
|
-
}
|
|
982
|
-
],
|
|
983
|
-
id: message.id,
|
|
984
|
-
metadata: {
|
|
985
|
-
...message.metadata,
|
|
986
|
-
mode: "network",
|
|
987
|
-
selectionReason,
|
|
988
|
-
agentInput: json.input,
|
|
989
|
-
from: primitiveType === "agent" ? "AGENT" : "WORKFLOW"
|
|
990
|
-
}
|
|
991
|
-
};
|
|
992
|
-
return nextMessage;
|
|
993
|
-
}
|
|
994
|
-
} catch (error) {
|
|
995
|
-
return message;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
return message;
|
|
999
|
-
});
|
|
70
|
+
/** Canonical DB `file` part `data` for optimistic UI and memory-shaped storage. */
|
|
71
|
+
function encodeFilePartDataForStorage(data, mimeType) {
|
|
72
|
+
if (typeof data === "string") return data;
|
|
73
|
+
if (data instanceof URL) return data.toString();
|
|
74
|
+
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
75
|
+
const base64 = uint8ArrayToBase64(bytes);
|
|
76
|
+
if (base64.startsWith("data:")) return base64;
|
|
77
|
+
return `data:${mimeType};base64,${base64}`;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/lib/mastra-db/formatCompletionFeedback.ts
|
|
81
|
+
const formatBaseCompletionFeedback = (result, maxIterationReached, formatScorerHeading, incompleteMessage) => {
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push("#### Completion Check Results");
|
|
84
|
+
lines.push("");
|
|
85
|
+
lines.push(`Overall: ${result.complete ? "✅ COMPLETE" : "❌ NOT COMPLETE"}`);
|
|
86
|
+
lines.push(`Duration: ${result.totalDuration}ms`);
|
|
87
|
+
if (result.timedOut) lines.push("⚠️ Scoring timed out");
|
|
88
|
+
lines.push("");
|
|
89
|
+
for (const scorer of result.scorers) {
|
|
90
|
+
lines.push(formatScorerHeading(scorer));
|
|
91
|
+
lines.push(`Score: ${scorer.score} ${scorer.passed ? "✅" : "❌"}`);
|
|
92
|
+
if (scorer.reason) lines.push(`Reason: ${scorer.reason}`);
|
|
93
|
+
lines.push("");
|
|
94
|
+
}
|
|
95
|
+
if (result.complete) lines.push("✅ The task is complete.");
|
|
96
|
+
else if (maxIterationReached) lines.push("⚠️ Max iterations reached.");
|
|
97
|
+
else lines.push(incompleteMessage);
|
|
98
|
+
return lines.join("\n");
|
|
1000
99
|
};
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const [messages, setMessages] = react.useState(
|
|
1004
|
-
() => resolveInitialMessages(initializeMessages?.() || [])
|
|
1005
|
-
);
|
|
1006
|
-
const baseClient = useMastraClient();
|
|
1007
|
-
const [isRunning, setIsRunning] = react.useState(false);
|
|
1008
|
-
const generate = async ({
|
|
1009
|
-
coreUserMessages,
|
|
1010
|
-
runtimeContext,
|
|
1011
|
-
threadId,
|
|
1012
|
-
modelSettings,
|
|
1013
|
-
signal,
|
|
1014
|
-
onFinish
|
|
1015
|
-
}) => {
|
|
1016
|
-
const {
|
|
1017
|
-
frequencyPenalty,
|
|
1018
|
-
presencePenalty,
|
|
1019
|
-
maxRetries,
|
|
1020
|
-
maxTokens,
|
|
1021
|
-
temperature,
|
|
1022
|
-
topK,
|
|
1023
|
-
topP,
|
|
1024
|
-
instructions,
|
|
1025
|
-
providerOptions,
|
|
1026
|
-
maxSteps
|
|
1027
|
-
} = modelSettings || {};
|
|
1028
|
-
setIsRunning(true);
|
|
1029
|
-
const clientWithAbort = new clientJs.MastraClient({
|
|
1030
|
-
...baseClient.options,
|
|
1031
|
-
abortSignal: signal
|
|
1032
|
-
});
|
|
1033
|
-
const agent = clientWithAbort.getAgent(agentId);
|
|
1034
|
-
const response = await agent.generate({
|
|
1035
|
-
messages: coreUserMessages,
|
|
1036
|
-
runId: agentId,
|
|
1037
|
-
maxSteps,
|
|
1038
|
-
modelSettings: {
|
|
1039
|
-
frequencyPenalty,
|
|
1040
|
-
presencePenalty,
|
|
1041
|
-
maxRetries,
|
|
1042
|
-
maxOutputTokens: maxTokens,
|
|
1043
|
-
temperature,
|
|
1044
|
-
topK,
|
|
1045
|
-
topP
|
|
1046
|
-
},
|
|
1047
|
-
instructions,
|
|
1048
|
-
runtimeContext,
|
|
1049
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1050
|
-
providerOptions
|
|
1051
|
-
});
|
|
1052
|
-
setIsRunning(false);
|
|
1053
|
-
if (response && "uiMessages" in response.response && response.response.uiMessages) {
|
|
1054
|
-
onFinish?.(response.response.uiMessages);
|
|
1055
|
-
const mastraUIMessages = (response.response.uiMessages || []).map((message) => ({
|
|
1056
|
-
...message,
|
|
1057
|
-
metadata: {
|
|
1058
|
-
mode: "generate"
|
|
1059
|
-
}
|
|
1060
|
-
}));
|
|
1061
|
-
setMessages((prev) => [...prev, ...mastraUIMessages]);
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
const stream = async ({ coreUserMessages, runtimeContext, threadId, onChunk, modelSettings, signal }) => {
|
|
1065
|
-
const {
|
|
1066
|
-
frequencyPenalty,
|
|
1067
|
-
presencePenalty,
|
|
1068
|
-
maxRetries,
|
|
1069
|
-
maxTokens,
|
|
1070
|
-
temperature,
|
|
1071
|
-
topK,
|
|
1072
|
-
topP,
|
|
1073
|
-
instructions,
|
|
1074
|
-
providerOptions,
|
|
1075
|
-
maxSteps
|
|
1076
|
-
} = modelSettings || {};
|
|
1077
|
-
setIsRunning(true);
|
|
1078
|
-
const clientWithAbort = new clientJs.MastraClient({
|
|
1079
|
-
...baseClient.options,
|
|
1080
|
-
abortSignal: signal
|
|
1081
|
-
});
|
|
1082
|
-
const agent = clientWithAbort.getAgent(agentId);
|
|
1083
|
-
const response = await agent.stream({
|
|
1084
|
-
messages: coreUserMessages,
|
|
1085
|
-
runId: agentId,
|
|
1086
|
-
maxSteps,
|
|
1087
|
-
modelSettings: {
|
|
1088
|
-
frequencyPenalty,
|
|
1089
|
-
presencePenalty,
|
|
1090
|
-
maxRetries,
|
|
1091
|
-
maxOutputTokens: maxTokens,
|
|
1092
|
-
temperature,
|
|
1093
|
-
topK,
|
|
1094
|
-
topP
|
|
1095
|
-
},
|
|
1096
|
-
instructions,
|
|
1097
|
-
runtimeContext,
|
|
1098
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1099
|
-
providerOptions
|
|
1100
|
-
});
|
|
1101
|
-
if (!response.body) {
|
|
1102
|
-
setIsRunning(false);
|
|
1103
|
-
throw new Error("[Stream] No response body");
|
|
1104
|
-
}
|
|
1105
|
-
await response.processDataStream({
|
|
1106
|
-
onChunk: async (chunk) => {
|
|
1107
|
-
setMessages((prev) => toUIMessage({ chunk, conversation: prev, metadata: { mode: "stream" } }));
|
|
1108
|
-
onChunk?.(chunk);
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
1111
|
-
setIsRunning(false);
|
|
1112
|
-
};
|
|
1113
|
-
const network = async ({
|
|
1114
|
-
coreUserMessages,
|
|
1115
|
-
runtimeContext,
|
|
1116
|
-
threadId,
|
|
1117
|
-
onNetworkChunk,
|
|
1118
|
-
modelSettings,
|
|
1119
|
-
signal
|
|
1120
|
-
}) => {
|
|
1121
|
-
const { frequencyPenalty, presencePenalty, maxRetries, maxTokens, temperature, topK, topP, maxSteps } = modelSettings || {};
|
|
1122
|
-
setIsRunning(true);
|
|
1123
|
-
const clientWithAbort = new clientJs.MastraClient({
|
|
1124
|
-
...baseClient.options,
|
|
1125
|
-
abortSignal: signal
|
|
1126
|
-
});
|
|
1127
|
-
const agent = clientWithAbort.getAgent(agentId);
|
|
1128
|
-
const response = await agent.network({
|
|
1129
|
-
messages: coreUserMessages,
|
|
1130
|
-
maxSteps,
|
|
1131
|
-
modelSettings: {
|
|
1132
|
-
frequencyPenalty,
|
|
1133
|
-
presencePenalty,
|
|
1134
|
-
maxRetries,
|
|
1135
|
-
maxOutputTokens: maxTokens,
|
|
1136
|
-
temperature,
|
|
1137
|
-
topK,
|
|
1138
|
-
topP
|
|
1139
|
-
},
|
|
1140
|
-
runId: agentId,
|
|
1141
|
-
runtimeContext,
|
|
1142
|
-
...threadId ? { thread: threadId, resourceId: agentId } : {}
|
|
1143
|
-
});
|
|
1144
|
-
const transformer = new AISdkNetworkTransformer();
|
|
1145
|
-
await response.processDataStream({
|
|
1146
|
-
onChunk: async (chunk) => {
|
|
1147
|
-
setMessages((prev) => transformer.transform({ chunk, conversation: prev, metadata: { mode: "network" } }));
|
|
1148
|
-
onNetworkChunk?.(chunk);
|
|
1149
|
-
}
|
|
1150
|
-
});
|
|
1151
|
-
setIsRunning(false);
|
|
1152
|
-
};
|
|
1153
|
-
const sendMessage = async ({ mode = "stream", ...args }) => {
|
|
1154
|
-
const nextMessage = { role: "user", content: [{ type: "text", text: args.message }] };
|
|
1155
|
-
const messages2 = args.coreUserMessages ? [nextMessage, ...args.coreUserMessages] : [nextMessage];
|
|
1156
|
-
setMessages((s) => [...s, { role: "user", parts: [{ type: "text", text: args.message }] }]);
|
|
1157
|
-
if (mode === "generate") {
|
|
1158
|
-
await generate({ ...args, coreUserMessages: messages2 });
|
|
1159
|
-
} else if (mode === "stream") {
|
|
1160
|
-
await stream({ ...args, coreUserMessages: messages2 });
|
|
1161
|
-
} else if (mode === "network") {
|
|
1162
|
-
await network({ ...args, coreUserMessages: messages2 });
|
|
1163
|
-
}
|
|
1164
|
-
};
|
|
1165
|
-
return {
|
|
1166
|
-
setMessages,
|
|
1167
|
-
sendMessage,
|
|
1168
|
-
isRunning,
|
|
1169
|
-
messages,
|
|
1170
|
-
cancelRun: () => setIsRunning(false)
|
|
1171
|
-
};
|
|
100
|
+
const formatCompletionFeedback = (result, maxIterationReached) => {
|
|
101
|
+
return formatBaseCompletionFeedback(result, maxIterationReached, (scorer) => `###### ${scorer.scorerName} (${scorer.scorerId})`, "🔄 Will continue working on the task.");
|
|
1172
102
|
};
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
103
|
+
const formatStreamCompletionFeedback = (result, maxIterationReached) => {
|
|
104
|
+
return formatBaseCompletionFeedback(result, maxIterationReached, (scorer) => `**${scorer.scorerName}** (${scorer.scorerId})`, "🔄 The task is not yet complete. Please continue working based on the feedback above.");
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/lib/mastra-db/types.ts
|
|
108
|
+
/**
|
|
109
|
+
* Canonical metadata block stored under `MastraDBMessage.content.metadata`.
|
|
110
|
+
*
|
|
111
|
+
* Every UX hint the React accumulator needs to surface lives here. Mode-specific
|
|
112
|
+
* fields are all optional so a single record can carry the union without forcing
|
|
113
|
+
* narrowing on the consumer side.
|
|
114
|
+
*/
|
|
115
|
+
/**
|
|
116
|
+
* Metadata key carrying a client-generated correlation id. The optimistic
|
|
117
|
+
* pending user bubble and the outgoing `sendMessage` request both stamp this
|
|
118
|
+
* id; the server echoes it back on the `data-user-message` data part so the
|
|
119
|
+
* accumulator can reconcile the pending bubble deterministically (decoupled
|
|
120
|
+
* from the server-assigned signal id). This is transient client state and is
|
|
121
|
+
* stripped once the echo is reconciled and on reload.
|
|
122
|
+
*/
|
|
123
|
+
const CLIENT_MESSAGE_ID_KEY = "clientMessageId";
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/lib/mastra-db/accumulator.ts
|
|
126
|
+
const cloneMetadata = (metadata) => metadata ? { ...metadata } : {};
|
|
127
|
+
const withParts = (message, parts) => ({
|
|
128
|
+
...message,
|
|
129
|
+
content: {
|
|
130
|
+
...message.content,
|
|
131
|
+
parts
|
|
132
|
+
}
|
|
1180
133
|
});
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
134
|
+
const withMetadata = (message, metadata) => ({
|
|
135
|
+
...message,
|
|
136
|
+
content: {
|
|
137
|
+
...message.content,
|
|
138
|
+
metadata
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const clearPendingStatus = (message) => {
|
|
142
|
+
const { status: _status, [CLIENT_MESSAGE_ID_KEY]: _clientMessageId, ...rest } = message.content.metadata ?? {};
|
|
143
|
+
return withMetadata(message, rest);
|
|
1188
144
|
};
|
|
1189
|
-
const
|
|
1190
|
-
|
|
145
|
+
const clearPendingStatusKeepClientId = (message) => {
|
|
146
|
+
const { status: _status, ...rest } = message.content.metadata ?? {};
|
|
147
|
+
return withMetadata(message, rest);
|
|
1191
148
|
};
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
149
|
+
const replaceLast = (conversation, message) => [...conversation.slice(0, -1), message];
|
|
150
|
+
const replaceAt = (conversation, index, message) => [
|
|
151
|
+
...conversation.slice(0, index),
|
|
152
|
+
message,
|
|
153
|
+
...conversation.slice(index + 1)
|
|
154
|
+
];
|
|
155
|
+
const newAssistantMessage = (id, parts, metadata) => ({
|
|
156
|
+
id,
|
|
157
|
+
role: "assistant",
|
|
158
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
159
|
+
content: {
|
|
160
|
+
format: 2,
|
|
161
|
+
parts,
|
|
162
|
+
metadata: cloneMetadata(metadata)
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
const appendAssistantMessage = (conversation, id, parts, metadata) => [...conversation, newAssistantMessage(id, parts, metadata)];
|
|
166
|
+
const isToolPart = (part) => part.type === "tool-invocation";
|
|
167
|
+
const partTextId = (part) => part.type === "text" ? part.textId : void 0;
|
|
168
|
+
const partState = (part) => part.state;
|
|
169
|
+
/**
|
|
170
|
+
* Set any streaming text/reasoning parts on the trailing assistant message to
|
|
171
|
+
* `state: 'done'`. Mirrors the previous `finishStreamingAssistantMessage` from
|
|
172
|
+
* the AI-SDK accumulator.
|
|
173
|
+
*/
|
|
174
|
+
const finishStreamingAssistantMessage = (conversation) => {
|
|
175
|
+
const lastMessage = conversation[conversation.length - 1];
|
|
176
|
+
if (!lastMessage || lastMessage.role !== "assistant") return conversation;
|
|
177
|
+
if (lastMessage.content.parts.length === 0) return conversation.slice(0, -1);
|
|
178
|
+
const nextParts = lastMessage.content.parts.map((part) => {
|
|
179
|
+
if ((part.type === "text" || part.type === "reasoning") && partState(part) === "streaming") return {
|
|
180
|
+
...part,
|
|
181
|
+
state: "done"
|
|
182
|
+
};
|
|
183
|
+
return part;
|
|
184
|
+
});
|
|
185
|
+
return replaceLast(conversation, withParts(lastMessage, nextParts));
|
|
1202
186
|
};
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
);
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Locate the assistant message + tool part owning `toolCallId`. Prefers the
|
|
189
|
+
* most recent assistant message and walks back up to 10 messages, matching the
|
|
190
|
+
* historical accumulator's lookup behavior.
|
|
191
|
+
*/
|
|
192
|
+
const locateToolPart = (messages, toolCallId, allowMetadataOnlyMatch) => {
|
|
193
|
+
const findIndex = (parts) => parts.findIndex((part) => isToolPart(part) && part.toolInvocation.toolCallId === toolCallId);
|
|
194
|
+
const lastMessage = messages[messages.length - 1];
|
|
195
|
+
if (lastMessage && lastMessage.role === "assistant") {
|
|
196
|
+
const idx = findIndex(lastMessage.content.parts);
|
|
197
|
+
if (idx !== -1) return {
|
|
198
|
+
messageIndex: messages.length - 1,
|
|
199
|
+
toolPartIndex: idx
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
let count = 0;
|
|
203
|
+
const maxMessagesBack = 10;
|
|
204
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
205
|
+
if (count > maxMessagesBack) break;
|
|
206
|
+
const message = messages[i];
|
|
207
|
+
if (message.role !== "assistant") continue;
|
|
208
|
+
const idx = findIndex(message.content.parts);
|
|
209
|
+
if (idx !== -1) return {
|
|
210
|
+
messageIndex: i,
|
|
211
|
+
toolPartIndex: idx
|
|
212
|
+
};
|
|
213
|
+
count++;
|
|
214
|
+
}
|
|
215
|
+
if (!allowMetadataOnlyMatch) return null;
|
|
216
|
+
for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "assistant") return {
|
|
217
|
+
messageIndex: i,
|
|
218
|
+
toolPartIndex: -1
|
|
219
|
+
};
|
|
220
|
+
return null;
|
|
1216
221
|
};
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Merge per-toolCallId background-task bookkeeping onto message metadata.
|
|
224
|
+
*/
|
|
225
|
+
const mergeBgTaskMetadata = (existing, mode, args, otherMetadata) => {
|
|
226
|
+
const base = cloneMetadata(existing);
|
|
227
|
+
const existingBgTasks = base.backgroundTasks ?? {};
|
|
228
|
+
const nextBgTasks = { ...existingBgTasks };
|
|
229
|
+
if (args.perTaskEntry) {
|
|
230
|
+
const { toolCallId, startedAt, completedAt, taskId, suspendedAt } = args.perTaskEntry;
|
|
231
|
+
nextBgTasks[toolCallId] = {
|
|
232
|
+
...existingBgTasks[toolCallId] ?? { taskId },
|
|
233
|
+
taskId,
|
|
234
|
+
...startedAt !== void 0 ? { startedAt } : {},
|
|
235
|
+
...completedAt !== void 0 ? { completedAt } : {},
|
|
236
|
+
...suspendedAt !== void 0 ? { suspendedAt } : {}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const merged = {
|
|
240
|
+
...base,
|
|
241
|
+
...otherMetadata ?? {},
|
|
242
|
+
mode,
|
|
243
|
+
backgroundTasks: nextBgTasks
|
|
244
|
+
};
|
|
245
|
+
if (args.resetRunningCount) merged.runningBackgroundTasksCount = void 0;
|
|
246
|
+
return merged;
|
|
1235
247
|
};
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
)
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Workflow chunk accumulation. Mirrors
|
|
250
|
+
* `mapWorkflowStreamChunkToWatchResult` from the previous accumulator.
|
|
251
|
+
*/
|
|
252
|
+
const mapWorkflowStreamChunkToWatchResult = (prev, chunk) => {
|
|
253
|
+
if (chunk.type === "workflow-start") return {
|
|
254
|
+
input: prev?.input,
|
|
255
|
+
status: "running",
|
|
256
|
+
steps: prev?.steps || {}
|
|
257
|
+
};
|
|
258
|
+
if (chunk.type === "workflow-canceled") return {
|
|
259
|
+
...prev,
|
|
260
|
+
status: "canceled"
|
|
261
|
+
};
|
|
262
|
+
if (chunk.type === "workflow-finish") {
|
|
263
|
+
const finalStatus = chunk.payload.workflowStatus;
|
|
264
|
+
const prevSteps = prev?.steps ?? {};
|
|
265
|
+
const lastStep = Object.values(prevSteps).pop();
|
|
266
|
+
return {
|
|
267
|
+
...prev,
|
|
268
|
+
status: chunk.payload.workflowStatus,
|
|
269
|
+
...finalStatus === "success" && lastStep?.status === "success" ? { result: lastStep?.output } : finalStatus === "failed" && lastStep?.status === "failed" ? { error: lastStep?.error } : finalStatus === "tripwire" && chunk.payload.tripwire ? { tripwire: chunk.payload.tripwire } : {}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const { stepCallId: _stepCallId, stepName: _stepName, ...newPayload } = chunk.payload ?? {};
|
|
273
|
+
const newSteps = {
|
|
274
|
+
...prev?.steps,
|
|
275
|
+
[chunk.payload.id]: {
|
|
276
|
+
...prev?.steps?.[chunk.payload.id],
|
|
277
|
+
...newPayload
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
if (chunk.type === "workflow-step-start") return {
|
|
281
|
+
...prev,
|
|
282
|
+
steps: newSteps
|
|
283
|
+
};
|
|
284
|
+
if (chunk.type === "workflow-step-suspended") {
|
|
285
|
+
const suspendedStepIds = Object.entries(newSteps).flatMap(([stepId, stepResult]) => {
|
|
286
|
+
if (stepResult?.status === "suspended") {
|
|
287
|
+
const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
|
|
288
|
+
return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
|
|
289
|
+
}
|
|
290
|
+
return [];
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
...prev,
|
|
294
|
+
status: "suspended",
|
|
295
|
+
steps: newSteps,
|
|
296
|
+
suspendPayload: chunk.payload.suspendPayload,
|
|
297
|
+
suspended: suspendedStepIds
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (chunk.type === "workflow-step-waiting") return {
|
|
301
|
+
...prev,
|
|
302
|
+
status: "waiting",
|
|
303
|
+
steps: newSteps
|
|
304
|
+
};
|
|
305
|
+
if (chunk.type === "workflow-step-progress") return {
|
|
306
|
+
...prev,
|
|
307
|
+
steps: {
|
|
308
|
+
...prev?.steps,
|
|
309
|
+
[chunk.payload.id]: {
|
|
310
|
+
...prev?.steps?.[chunk.payload.id],
|
|
311
|
+
foreachProgress: {
|
|
312
|
+
completedCount: chunk.payload.completedCount,
|
|
313
|
+
totalCount: chunk.payload.totalCount,
|
|
314
|
+
currentIndex: chunk.payload.currentIndex,
|
|
315
|
+
iterationStatus: chunk.payload.iterationStatus,
|
|
316
|
+
iterationOutput: chunk.payload.iterationOutput
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
if (chunk.type === "workflow-step-result") return {
|
|
322
|
+
...prev,
|
|
323
|
+
steps: newSteps
|
|
324
|
+
};
|
|
325
|
+
return prev;
|
|
1245
326
|
};
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
327
|
+
const signalContentsToUserMessages = (contents, metadata) => {
|
|
328
|
+
const makeUserMessage = (parts) => ({
|
|
329
|
+
id: `signal-${Date.now()}`,
|
|
330
|
+
role: "user",
|
|
331
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
332
|
+
content: {
|
|
333
|
+
format: 2,
|
|
334
|
+
parts,
|
|
335
|
+
metadata: cloneMetadata(metadata)
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
const toMessagePart = (part) => {
|
|
339
|
+
if (!part || typeof part !== "object") return [];
|
|
340
|
+
const typedPart = part;
|
|
341
|
+
if (typedPart.type === "text" && typeof typedPart.text === "string") return [{
|
|
342
|
+
type: "text",
|
|
343
|
+
text: typedPart.text
|
|
344
|
+
}];
|
|
345
|
+
if (typedPart.type === "image") {
|
|
346
|
+
const image = typedPart.image;
|
|
347
|
+
const mimeType = typeof typedPart.mediaType === "string" ? typedPart.mediaType : typeof typedPart.mimeType === "string" ? typedPart.mimeType : "image/*";
|
|
348
|
+
if (typeof image === "string" || image instanceof URL || image instanceof ArrayBuffer || image instanceof Uint8Array) return [{
|
|
349
|
+
type: "file",
|
|
350
|
+
mimeType,
|
|
351
|
+
data: encodeFilePartDataForStorage(image, mimeType)
|
|
352
|
+
}];
|
|
353
|
+
return [{
|
|
354
|
+
type: "file",
|
|
355
|
+
mimeType,
|
|
356
|
+
data: ""
|
|
357
|
+
}];
|
|
358
|
+
}
|
|
359
|
+
if (typedPart.type === "file") {
|
|
360
|
+
const data = typedPart.data;
|
|
361
|
+
const mimeType = typeof typedPart.mediaType === "string" ? typedPart.mediaType : typeof typedPart.mimeType === "string" ? typedPart.mimeType : "application/octet-stream";
|
|
362
|
+
if (typeof data === "string" || data instanceof URL || data instanceof ArrayBuffer || data instanceof Uint8Array) return [{
|
|
363
|
+
type: "file",
|
|
364
|
+
mimeType,
|
|
365
|
+
data: encodeFilePartDataForStorage(data, mimeType),
|
|
366
|
+
...typeof typedPart.filename === "string" ? { filename: typedPart.filename } : {}
|
|
367
|
+
}];
|
|
368
|
+
return [{
|
|
369
|
+
type: "file",
|
|
370
|
+
mimeType,
|
|
371
|
+
data: typeof typedPart.url === "string" ? typedPart.url : "",
|
|
372
|
+
...typeof typedPart.filename === "string" ? { filename: typedPart.filename } : {}
|
|
373
|
+
}];
|
|
374
|
+
}
|
|
375
|
+
return [];
|
|
376
|
+
};
|
|
377
|
+
if (typeof contents === "string") return [makeUserMessage([{
|
|
378
|
+
type: "text",
|
|
379
|
+
text: contents
|
|
380
|
+
}])];
|
|
381
|
+
if (Array.isArray(contents)) {
|
|
382
|
+
const parts = contents.flatMap(toMessagePart);
|
|
383
|
+
return parts.length ? [makeUserMessage(parts)] : contents.flatMap((content) => signalContentsToUserMessages(content, metadata));
|
|
384
|
+
}
|
|
385
|
+
if (!contents || typeof contents !== "object") return [];
|
|
386
|
+
const message = contents;
|
|
387
|
+
if (message.role && message.role !== "user") return [];
|
|
388
|
+
const content = message.content;
|
|
389
|
+
if (typeof content === "string") return [makeUserMessage([{
|
|
390
|
+
type: "text",
|
|
391
|
+
text: content
|
|
392
|
+
}])];
|
|
393
|
+
if (!Array.isArray(content)) return [];
|
|
394
|
+
const parts = content.flatMap(toMessagePart);
|
|
395
|
+
return parts.length ? [makeUserMessage(parts)] : [];
|
|
1259
396
|
};
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
397
|
+
const reconcilePendingUserEcho = (message, echoedContents, metadata, options) => {
|
|
398
|
+
const echoedParts = signalContentsToUserMessages(echoedContents, metadata)[0]?.content.parts;
|
|
399
|
+
let reconciled = typeof options.signalId === "string" ? {
|
|
400
|
+
...message,
|
|
401
|
+
id: options.signalId
|
|
402
|
+
} : message;
|
|
403
|
+
if (echoedParts?.length) reconciled = {
|
|
404
|
+
...reconciled,
|
|
405
|
+
content: {
|
|
406
|
+
...reconciled.content,
|
|
407
|
+
parts: echoedParts
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
return options.keepClientMessageId ? clearPendingStatusKeepClientId(reconciled) : clearPendingStatus(reconciled);
|
|
1267
411
|
};
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
412
|
+
const makeToolInvocationPart = (invocation) => ({
|
|
413
|
+
type: "tool-invocation",
|
|
414
|
+
toolInvocation: invocation
|
|
415
|
+
});
|
|
416
|
+
/**
|
|
417
|
+
* Narrow the chunk to the template-literal passthrough variants from
|
|
418
|
+
* `NetworkChunkType`. Encoded as a type guard so the surrounding `switch`
|
|
419
|
+
* statement can stay exhaustive over the remaining string-literal cases.
|
|
420
|
+
*/
|
|
421
|
+
const isTemplateLiteralPassthrough = (chunk) => chunk.type.startsWith("agent-execution-event-") || chunk.type.startsWith("workflow-execution-event-");
|
|
422
|
+
/**
|
|
423
|
+
* Narrow the chunk to the `data-${string}` family. Used so the trailing
|
|
424
|
+
* exhaustiveness check can prove the switch covers every remaining variant.
|
|
425
|
+
*/
|
|
426
|
+
const isDataChunk$2 = (chunk) => chunk.type.startsWith("data-");
|
|
427
|
+
/**
|
|
428
|
+
* Reduce a single stream chunk into the running `MastraDBMessage[]`
|
|
429
|
+
* conversation. The accumulator owns the entire chunk→DB mapping for the
|
|
430
|
+
* React SDK; consumers downstream (e.g. `toAISdkV5Messages` in the playground)
|
|
431
|
+
* translate the result into AI SDK / assistant-ui shapes as needed.
|
|
432
|
+
*/
|
|
433
|
+
const accumulateChunk = ({ chunk, conversation, metadata }) => {
|
|
434
|
+
const result = [...conversation];
|
|
435
|
+
if (isTemplateLiteralPassthrough(chunk)) return result;
|
|
436
|
+
if (isDataChunk$2(chunk)) {
|
|
437
|
+
if (chunk.type === "data-user-message" && "data" in chunk && (chunk.data?.type === "user-message" || chunk.data?.type === "user")) {
|
|
438
|
+
const signalId = chunk.data.id;
|
|
439
|
+
const echoedClientMessageId = chunk.data?.metadata?.[CLIENT_MESSAGE_ID_KEY];
|
|
440
|
+
if (typeof echoedClientMessageId === "string" && result.some((message) => message.content.metadata?.status === "pending" && message.content.metadata["clientMessageId"] === echoedClientMessageId)) return finishStreamingAssistantMessage(result.map((message) => message.content.metadata?.status === "pending" && message.content.metadata["clientMessageId"] === echoedClientMessageId ? reconcilePendingUserEcho(message, chunk.data.contents, metadata, {
|
|
441
|
+
signalId: typeof signalId === "string" ? signalId : void 0,
|
|
442
|
+
keepClientMessageId: true
|
|
443
|
+
}) : message));
|
|
444
|
+
if (typeof signalId === "string" && result.some((message) => message.id === signalId)) return finishStreamingAssistantMessage(result.map((message) => message.id === signalId && message.content.metadata?.status === "pending" ? reconcilePendingUserEcho(message, chunk.data.contents, metadata, { keepClientMessageId: false }) : message));
|
|
445
|
+
const userMessages = signalContentsToUserMessages(chunk.data.contents, metadata);
|
|
446
|
+
if (!userMessages.length) return result;
|
|
447
|
+
const conversationWithFinishedAssistant = finishStreamingAssistantMessage(result);
|
|
448
|
+
const messageIdPrefix = typeof signalId === "string" ? signalId : `signal-${chunk.runId}-${Date.now()}`;
|
|
449
|
+
return [...conversationWithFinishedAssistant, ...userMessages.map((message, index) => ({
|
|
450
|
+
...message,
|
|
451
|
+
id: index === 0 ? messageIdPrefix : `${messageIdPrefix}-${index}`
|
|
452
|
+
}))];
|
|
453
|
+
}
|
|
454
|
+
const dataPart = {
|
|
455
|
+
type: chunk.type,
|
|
456
|
+
data: "data" in chunk ? chunk.data : void 0,
|
|
457
|
+
..."id" in chunk && typeof chunk.id === "string" ? { id: chunk.id } : {}
|
|
458
|
+
};
|
|
459
|
+
const lastMessage = result[result.length - 1];
|
|
460
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `data-${chunk.runId}-${Date.now()}`, [dataPart], metadata);
|
|
461
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, dataPart]));
|
|
462
|
+
}
|
|
463
|
+
switch (chunk.type) {
|
|
464
|
+
case "tripwire": {
|
|
465
|
+
const newMessage = newAssistantMessage(`tripwire-${chunk.runId + Date.now()}`, [{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: chunk.payload.reason
|
|
468
|
+
}], {
|
|
469
|
+
...metadata,
|
|
470
|
+
status: "tripwire",
|
|
471
|
+
tripwire: {
|
|
472
|
+
reason: chunk.payload.reason,
|
|
473
|
+
retry: chunk.payload.retry,
|
|
474
|
+
metadata: chunk.payload.metadata,
|
|
475
|
+
processorId: chunk.payload.processorId
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
return [...result, newMessage];
|
|
479
|
+
}
|
|
480
|
+
case "start": {
|
|
481
|
+
const messageId = typeof chunk.payload.messageId === "string" ? chunk.payload.messageId : void 0;
|
|
482
|
+
if (messageId && result.some((message) => message.id === messageId)) return result;
|
|
483
|
+
return [...result, newAssistantMessage(messageId ?? `start-${chunk.runId + Date.now()}`, [], metadata)];
|
|
484
|
+
}
|
|
485
|
+
case "text-start": {
|
|
486
|
+
const lastMessage = result[result.length - 1];
|
|
487
|
+
const textId = chunk.payload.id || `text-${Date.now()}`;
|
|
488
|
+
const tailPart = lastMessage?.content.parts[lastMessage.content.parts.length - 1];
|
|
489
|
+
if (chunk.payload.id && lastMessage?.role === "assistant" && tailPart?.type === "text" && partTextId(tailPart) === textId) return result;
|
|
490
|
+
const newTextPart = {
|
|
491
|
+
type: "text",
|
|
492
|
+
text: "",
|
|
493
|
+
state: "streaming",
|
|
494
|
+
textId,
|
|
495
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
496
|
+
};
|
|
497
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `start-${chunk.runId}-${Date.now()}`, [newTextPart], metadata);
|
|
498
|
+
if (lastMessage.content.metadata?.completionResult) return appendAssistantMessage(result, `start-${chunk.runId}-${Date.now()}`, [newTextPart], metadata);
|
|
499
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, newTextPart]));
|
|
500
|
+
}
|
|
501
|
+
case "background-task-progress": {
|
|
502
|
+
const lastMessage = result[result.length - 1];
|
|
503
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
504
|
+
return replaceLast(result, withMetadata(lastMessage, {
|
|
505
|
+
mode: metadata.mode,
|
|
506
|
+
...lastMessage.content.metadata,
|
|
507
|
+
runningBackgroundTasksCount: chunk.payload.runningCount
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
case "text-delta": {
|
|
511
|
+
const lastMessage = result[result.length - 1];
|
|
512
|
+
const textId = chunk.payload.id;
|
|
513
|
+
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
514
|
+
const newTextPart = {
|
|
515
|
+
type: "text",
|
|
516
|
+
text: chunk.payload.text,
|
|
517
|
+
state: "streaming",
|
|
518
|
+
textId,
|
|
519
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
520
|
+
};
|
|
521
|
+
return appendAssistantMessage(result, `text-${chunk.runId}-${Date.now()}`, [newTextPart], metadata);
|
|
522
|
+
}
|
|
523
|
+
const parts = [...lastMessage.content.parts];
|
|
524
|
+
let textPartIndex = textId ? parts.findLastIndex((part) => part.type === "text" && partTextId(part) === textId) : -1;
|
|
525
|
+
if (textPartIndex === -1) textPartIndex = parts.findLastIndex((part) => part.type === "text" && partState(part) === "streaming");
|
|
526
|
+
if (textPartIndex !== -1 && parts.some((part, index) => index > textPartIndex && part.type !== "text")) textPartIndex = -1;
|
|
527
|
+
if (textPartIndex === -1) {
|
|
528
|
+
const newTextPart = {
|
|
529
|
+
type: "text",
|
|
530
|
+
text: chunk.payload.text,
|
|
531
|
+
state: "streaming",
|
|
532
|
+
textId,
|
|
533
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
534
|
+
};
|
|
535
|
+
parts.push(newTextPart);
|
|
536
|
+
} else {
|
|
537
|
+
const textPart = parts[textPartIndex];
|
|
538
|
+
parts[textPartIndex] = {
|
|
539
|
+
...textPart,
|
|
540
|
+
text: textPart.text + chunk.payload.text,
|
|
541
|
+
state: "streaming"
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
545
|
+
}
|
|
546
|
+
case "text-end": return result;
|
|
547
|
+
case "reasoning-start": {
|
|
548
|
+
const lastMessage = result[result.length - 1];
|
|
549
|
+
const newReasoningPart = {
|
|
550
|
+
type: "reasoning",
|
|
551
|
+
reasoning: "",
|
|
552
|
+
state: "streaming",
|
|
553
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
554
|
+
};
|
|
555
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `reasoning-${chunk.runId + Date.now()}`, [newReasoningPart], metadata);
|
|
556
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, newReasoningPart]));
|
|
557
|
+
}
|
|
558
|
+
case "reasoning-delta": {
|
|
559
|
+
const lastMessage = result[result.length - 1];
|
|
560
|
+
if (!lastMessage || lastMessage.role !== "assistant") {
|
|
561
|
+
const newReasoningPart = {
|
|
562
|
+
type: "reasoning",
|
|
563
|
+
reasoning: chunk.payload.text,
|
|
564
|
+
state: "streaming",
|
|
565
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
566
|
+
};
|
|
567
|
+
return appendAssistantMessage(result, `reasoning-${chunk.runId + Date.now()}`, [newReasoningPart], metadata);
|
|
568
|
+
}
|
|
569
|
+
const parts = [...lastMessage.content.parts];
|
|
570
|
+
const lastIndex = parts.length - 1;
|
|
571
|
+
const lastPart = parts[lastIndex];
|
|
572
|
+
if (lastPart?.type === "reasoning") {
|
|
573
|
+
const reasoningPart = lastPart;
|
|
574
|
+
parts[lastIndex] = {
|
|
575
|
+
...reasoningPart,
|
|
576
|
+
reasoning: reasoningPart.reasoning + chunk.payload.text,
|
|
577
|
+
state: "streaming"
|
|
578
|
+
};
|
|
579
|
+
} else {
|
|
580
|
+
const newReasoningPart = {
|
|
581
|
+
type: "reasoning",
|
|
582
|
+
reasoning: chunk.payload.text,
|
|
583
|
+
state: "streaming",
|
|
584
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
585
|
+
};
|
|
586
|
+
parts.push(newReasoningPart);
|
|
587
|
+
}
|
|
588
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
589
|
+
}
|
|
590
|
+
case "reasoning-end": {
|
|
591
|
+
const lastMessage = result[result.length - 1];
|
|
592
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
593
|
+
const parts = [...lastMessage.content.parts];
|
|
594
|
+
const reasoningIndex = parts.findLastIndex((part) => part.type === "reasoning" && partState(part) === "streaming");
|
|
595
|
+
if (reasoningIndex === -1) return result;
|
|
596
|
+
const reasoningPart = parts[reasoningIndex];
|
|
597
|
+
const existingMeta = reasoningPart.providerMetadata;
|
|
598
|
+
const endMeta = chunk.payload.providerMetadata;
|
|
599
|
+
parts[reasoningIndex] = {
|
|
600
|
+
...reasoningPart,
|
|
601
|
+
state: "done",
|
|
602
|
+
...existingMeta || endMeta ? { providerMetadata: {
|
|
603
|
+
...existingMeta ?? {},
|
|
604
|
+
...endMeta ?? {}
|
|
605
|
+
} } : {}
|
|
606
|
+
};
|
|
607
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
608
|
+
}
|
|
609
|
+
case "reasoning-signature": {
|
|
610
|
+
const lastMessage = result[result.length - 1];
|
|
611
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
612
|
+
const parts = [...lastMessage.content.parts];
|
|
613
|
+
const reasoningIndex = parts.findLastIndex((part) => part.type === "reasoning");
|
|
614
|
+
if (reasoningIndex === -1) return result;
|
|
615
|
+
const reasoningPart = parts[reasoningIndex];
|
|
616
|
+
const existingMeta = reasoningPart.providerMetadata;
|
|
617
|
+
const sigMeta = chunk.payload.providerMetadata;
|
|
618
|
+
parts[reasoningIndex] = {
|
|
619
|
+
...reasoningPart,
|
|
620
|
+
...existingMeta || sigMeta ? { providerMetadata: {
|
|
621
|
+
...existingMeta ?? {},
|
|
622
|
+
...sigMeta ?? {}
|
|
623
|
+
} } : {}
|
|
624
|
+
};
|
|
625
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
626
|
+
}
|
|
627
|
+
case "redacted-reasoning": {
|
|
628
|
+
const lastMessage = result[result.length - 1];
|
|
629
|
+
const redactedData = chunk.payload.data;
|
|
630
|
+
const redactedPart = {
|
|
631
|
+
type: "reasoning",
|
|
632
|
+
reasoning: typeof redactedData === "string" ? redactedData : "",
|
|
633
|
+
state: "done",
|
|
634
|
+
redacted: true,
|
|
635
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
636
|
+
};
|
|
637
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `redacted-reasoning-${chunk.runId + Date.now()}`, [redactedPart], metadata);
|
|
638
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, redactedPart]));
|
|
639
|
+
}
|
|
640
|
+
case "tool-call": {
|
|
641
|
+
const invocation = {
|
|
642
|
+
state: "call",
|
|
643
|
+
toolCallId: chunk.payload.toolCallId,
|
|
644
|
+
toolName: chunk.payload.toolName,
|
|
645
|
+
args: chunk.payload.args
|
|
646
|
+
};
|
|
647
|
+
const newPart = {
|
|
648
|
+
...makeToolInvocationPart(invocation),
|
|
649
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
650
|
+
};
|
|
651
|
+
const existing = locateToolPart(result, chunk.payload.toolCallId, false);
|
|
652
|
+
if (existing && existing.toolPartIndex >= 0) {
|
|
653
|
+
const { messageIndex, toolPartIndex } = existing;
|
|
654
|
+
const targetMessage = result[messageIndex];
|
|
655
|
+
if (targetMessage && targetMessage.role === "assistant") {
|
|
656
|
+
const parts = [...targetMessage.content.parts];
|
|
657
|
+
const prev = parts[toolPartIndex];
|
|
658
|
+
if (isToolPart(prev)) {
|
|
659
|
+
const { argsText: _argsText, ...rest } = prev;
|
|
660
|
+
parts[toolPartIndex] = {
|
|
661
|
+
...rest,
|
|
662
|
+
toolInvocation: {
|
|
663
|
+
...prev.toolInvocation,
|
|
664
|
+
state: "call",
|
|
665
|
+
toolName: chunk.payload.toolName,
|
|
666
|
+
toolCallId: chunk.payload.toolCallId,
|
|
667
|
+
args: chunk.payload.args
|
|
668
|
+
},
|
|
669
|
+
providerMetadata: chunk.payload.providerMetadata ?? prev.providerMetadata
|
|
670
|
+
};
|
|
671
|
+
return replaceAt(result, messageIndex, withParts(targetMessage, parts));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const lastMessage = result[result.length - 1];
|
|
676
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `tool-call-${chunk.runId + Date.now()}`, [newPart], metadata);
|
|
677
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, newPart]));
|
|
678
|
+
}
|
|
679
|
+
case "tool-call-input-streaming-start": {
|
|
680
|
+
const lastMessage = result[result.length - 1];
|
|
681
|
+
const invocation = {
|
|
682
|
+
state: "partial-call",
|
|
683
|
+
toolCallId: chunk.payload.toolCallId,
|
|
684
|
+
toolName: chunk.payload.toolName,
|
|
685
|
+
args: {}
|
|
686
|
+
};
|
|
687
|
+
const newPart = {
|
|
688
|
+
...makeToolInvocationPart(invocation),
|
|
689
|
+
argsText: ""
|
|
690
|
+
};
|
|
691
|
+
if (!lastMessage || lastMessage.role !== "assistant") return appendAssistantMessage(result, `tool-call-streaming-${chunk.runId + Date.now()}`, [newPart], metadata);
|
|
692
|
+
return replaceLast(result, withParts(lastMessage, [...lastMessage.content.parts, newPart]));
|
|
693
|
+
}
|
|
694
|
+
case "tool-call-delta": {
|
|
695
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, false);
|
|
696
|
+
if (!location || location.toolPartIndex < 0) return result;
|
|
697
|
+
const { messageIndex, toolPartIndex } = location;
|
|
698
|
+
const targetMessage = result[messageIndex];
|
|
699
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
700
|
+
const parts = [...targetMessage.content.parts];
|
|
701
|
+
const toolPart = parts[toolPartIndex];
|
|
702
|
+
if (!isToolPart(toolPart)) return result;
|
|
703
|
+
const nextArgsText = (toolPart.argsText ?? "") + (chunk.payload.argsTextDelta ?? "");
|
|
704
|
+
parts[toolPartIndex] = {
|
|
705
|
+
...toolPart,
|
|
706
|
+
argsText: nextArgsText,
|
|
707
|
+
toolInvocation: {
|
|
708
|
+
...toolPart.toolInvocation,
|
|
709
|
+
state: "partial-call"
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
return replaceAt(result, messageIndex, withParts(targetMessage, parts));
|
|
713
|
+
}
|
|
714
|
+
case "tool-call-input-streaming-end": {
|
|
715
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, false);
|
|
716
|
+
if (!location || location.toolPartIndex < 0) return result;
|
|
717
|
+
const { messageIndex, toolPartIndex } = location;
|
|
718
|
+
const targetMessage = result[messageIndex];
|
|
719
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
720
|
+
const parts = [...targetMessage.content.parts];
|
|
721
|
+
const toolPart = parts[toolPartIndex];
|
|
722
|
+
if (!isToolPart(toolPart)) return result;
|
|
723
|
+
let parsedArgs = {};
|
|
724
|
+
const argsText = toolPart.argsText;
|
|
725
|
+
if (typeof argsText === "string" && argsText.length > 0) try {
|
|
726
|
+
const maybe = JSON.parse(argsText);
|
|
727
|
+
if (maybe && typeof maybe === "object" && !Array.isArray(maybe)) parsedArgs = maybe;
|
|
728
|
+
} catch {
|
|
729
|
+
parsedArgs = {};
|
|
730
|
+
}
|
|
731
|
+
parts[toolPartIndex] = {
|
|
732
|
+
...toolPart,
|
|
733
|
+
toolInvocation: {
|
|
734
|
+
...toolPart.toolInvocation,
|
|
735
|
+
state: "call",
|
|
736
|
+
args: parsedArgs
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
return replaceAt(result, messageIndex, withParts(targetMessage, parts));
|
|
740
|
+
}
|
|
741
|
+
case "tool-output-denied": {
|
|
742
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, false);
|
|
743
|
+
if (!location || location.toolPartIndex < 0) return result;
|
|
744
|
+
const { messageIndex, toolPartIndex } = location;
|
|
745
|
+
const targetMessage = result[messageIndex];
|
|
746
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
747
|
+
const parts = [...targetMessage.content.parts];
|
|
748
|
+
const toolPart = parts[toolPartIndex];
|
|
749
|
+
if (!isToolPart(toolPart)) return result;
|
|
750
|
+
parts[toolPartIndex] = {
|
|
751
|
+
...toolPart,
|
|
752
|
+
toolInvocation: {
|
|
753
|
+
...toolPart.toolInvocation,
|
|
754
|
+
state: "output-denied",
|
|
755
|
+
toolName: chunk.payload.toolName,
|
|
756
|
+
args: chunk.payload.args ?? toolPart.toolInvocation.args,
|
|
757
|
+
approval: chunk.payload.approval
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
return replaceAt(result, messageIndex, withParts(targetMessage, parts));
|
|
761
|
+
}
|
|
762
|
+
case "tool-error":
|
|
763
|
+
case "tool-result":
|
|
764
|
+
case "background-task-completed":
|
|
765
|
+
case "background-task-failed": {
|
|
766
|
+
const isBgTaskEvent = chunk.type === "background-task-completed" || chunk.type === "background-task-failed";
|
|
767
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, isBgTaskEvent);
|
|
768
|
+
if (!location) return result;
|
|
769
|
+
const { messageIndex, toolPartIndex } = location;
|
|
770
|
+
const targetMessage = result[messageIndex];
|
|
771
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
772
|
+
const parts = [...targetMessage.content.parts];
|
|
773
|
+
const toolPart = toolPartIndex >= 0 ? parts[toolPartIndex] : void 0;
|
|
774
|
+
let payloadResult;
|
|
775
|
+
let payloadError;
|
|
776
|
+
let payloadIsError = false;
|
|
777
|
+
let payloadProviderMetadata;
|
|
778
|
+
let payloadCompletedAt;
|
|
779
|
+
let payloadTaskId;
|
|
780
|
+
switch (chunk.type) {
|
|
781
|
+
case "tool-result":
|
|
782
|
+
payloadResult = chunk.payload.result;
|
|
783
|
+
payloadIsError = Boolean(chunk.payload.isError);
|
|
784
|
+
payloadProviderMetadata = chunk.payload.providerMetadata;
|
|
785
|
+
break;
|
|
786
|
+
case "tool-error":
|
|
787
|
+
payloadError = chunk.payload.error;
|
|
788
|
+
payloadProviderMetadata = chunk.payload.providerMetadata;
|
|
789
|
+
break;
|
|
790
|
+
case "background-task-completed":
|
|
791
|
+
payloadResult = chunk.payload.result;
|
|
792
|
+
payloadCompletedAt = chunk.payload.completedAt;
|
|
793
|
+
payloadTaskId = chunk.payload.taskId;
|
|
794
|
+
break;
|
|
795
|
+
case "background-task-failed":
|
|
796
|
+
payloadError = chunk.payload.error;
|
|
797
|
+
payloadCompletedAt = chunk.payload.completedAt;
|
|
798
|
+
payloadTaskId = chunk.payload.taskId;
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
if (toolPart && isToolPart(toolPart)) {
|
|
802
|
+
const { toolName, toolCallId, args } = toolPart.toolInvocation;
|
|
803
|
+
const providerMeta = payloadProviderMetadata ?? toolPart.providerMetadata;
|
|
804
|
+
if (chunk.type === "tool-error" || chunk.type === "background-task-failed" || payloadIsError) {
|
|
805
|
+
const error = chunk.type === "tool-error" || chunk.type === "background-task-failed" ? payloadError : payloadResult;
|
|
806
|
+
const errorText = typeof error === "string" ? error : error instanceof Error ? error.message : error?.message ?? String(error);
|
|
807
|
+
parts[toolPartIndex] = {
|
|
808
|
+
...toolPart,
|
|
809
|
+
providerMetadata: providerMeta,
|
|
810
|
+
toolInvocation: {
|
|
811
|
+
state: "output-error",
|
|
812
|
+
toolCallId,
|
|
813
|
+
toolName,
|
|
814
|
+
args,
|
|
815
|
+
errorText
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
} else {
|
|
819
|
+
const resultObj = payloadResult;
|
|
820
|
+
const existingResult = toolPart.toolInvocation.state === "partial-call" || toolPart.toolInvocation.state === "result" ? toolPart.toolInvocation.result : void 0;
|
|
821
|
+
const existingLooksLikeWorkflow = Boolean(existingResult && typeof existingResult === "object" && "steps" in existingResult);
|
|
822
|
+
const isWorkflow = Boolean(resultObj?.result?.steps) || toolName?.startsWith("workflow-") || existingLooksLikeWorkflow;
|
|
823
|
+
const isAgent = chunk.from === "AGENT";
|
|
824
|
+
let output;
|
|
825
|
+
if (isWorkflow) {
|
|
826
|
+
const accumulated = existingLooksLikeWorkflow && existingResult && typeof existingResult === "object" ? existingResult : void 0;
|
|
827
|
+
const payloadWorkflow = resultObj?.result && typeof resultObj.result === "object" ? resultObj.result : void 0;
|
|
828
|
+
if (accumulated || payloadWorkflow) output = {
|
|
829
|
+
...accumulated ?? {},
|
|
830
|
+
...payloadWorkflow ?? {},
|
|
831
|
+
steps: payloadWorkflow?.steps ?? accumulated?.steps ?? [],
|
|
832
|
+
status: payloadWorkflow?.status ?? accumulated?.status ?? "success",
|
|
833
|
+
output: payloadResult
|
|
834
|
+
};
|
|
835
|
+
else output = payloadResult;
|
|
836
|
+
} else if (isAgent) {
|
|
837
|
+
const existingOutput = toolPart.toolInvocation.state === "result" ? toolPart.toolInvocation.result : void 0;
|
|
838
|
+
const existingChild = existingOutput?.childMessages;
|
|
839
|
+
output = existingOutput ? {
|
|
840
|
+
...payloadResult,
|
|
841
|
+
childMessages: existingChild?.length ? existingChild : resultObj?.childMessages
|
|
842
|
+
} : payloadResult;
|
|
843
|
+
} else output = payloadResult;
|
|
844
|
+
parts[toolPartIndex] = {
|
|
845
|
+
...toolPart,
|
|
846
|
+
providerMetadata: providerMeta,
|
|
847
|
+
toolInvocation: {
|
|
848
|
+
state: "result",
|
|
849
|
+
toolCallId,
|
|
850
|
+
toolName,
|
|
851
|
+
args,
|
|
852
|
+
result: output
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const nextMetadata = mergeBgTaskMetadata(targetMessage.content.metadata, metadata.mode, {
|
|
858
|
+
resetRunningCount: isBgTaskEvent,
|
|
859
|
+
perTaskEntry: isBgTaskEvent && payloadTaskId ? {
|
|
860
|
+
toolCallId: chunk.payload.toolCallId,
|
|
861
|
+
completedAt: payloadCompletedAt,
|
|
862
|
+
taskId: payloadTaskId
|
|
863
|
+
} : void 0
|
|
864
|
+
});
|
|
865
|
+
const nextMessage = {
|
|
866
|
+
...targetMessage,
|
|
867
|
+
content: {
|
|
868
|
+
...targetMessage.content,
|
|
869
|
+
parts,
|
|
870
|
+
metadata: nextMetadata
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
return replaceAt(result, messageIndex, nextMessage);
|
|
874
|
+
}
|
|
875
|
+
case "background-task-running": {
|
|
876
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, true);
|
|
877
|
+
if (!location) return result;
|
|
878
|
+
const { messageIndex } = location;
|
|
879
|
+
const targetMessage = result[messageIndex];
|
|
880
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
881
|
+
const nextMetadata = mergeBgTaskMetadata(targetMessage.content.metadata, metadata.mode, { perTaskEntry: {
|
|
882
|
+
toolCallId: chunk.payload.toolCallId,
|
|
883
|
+
startedAt: chunk.payload.startedAt,
|
|
884
|
+
taskId: chunk.payload.taskId
|
|
885
|
+
} });
|
|
886
|
+
return replaceAt(result, messageIndex, withMetadata(targetMessage, nextMetadata));
|
|
887
|
+
}
|
|
888
|
+
case "tool-output":
|
|
889
|
+
case "background-task-output": {
|
|
890
|
+
const isBgTaskOutput = chunk.type === "background-task-output";
|
|
891
|
+
const location = locateToolPart(result, chunk.payload.toolCallId, isBgTaskOutput);
|
|
892
|
+
if (!location || location.toolPartIndex < 0) return result;
|
|
893
|
+
const { messageIndex, toolPartIndex } = location;
|
|
894
|
+
const targetMessage = result[messageIndex];
|
|
895
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
896
|
+
const parts = [...targetMessage.content.parts];
|
|
897
|
+
const toolPart = parts[toolPartIndex];
|
|
898
|
+
if (!isToolPart(toolPart)) return result;
|
|
899
|
+
const { toolName, toolCallId, args } = toolPart.toolInvocation;
|
|
900
|
+
const payloadOutput = chunk.type === "background-task-output" ? chunk.payload.payload.payload.output : chunk.payload.output;
|
|
901
|
+
if (payloadOutput?.type?.startsWith("workflow-")) {
|
|
902
|
+
const existingWorkflowState = toolPart.toolInvocation.result || {};
|
|
903
|
+
const updated = mapWorkflowStreamChunkToWatchResult(existingWorkflowState, payloadOutput);
|
|
904
|
+
parts[toolPartIndex] = {
|
|
905
|
+
...toolPart,
|
|
906
|
+
toolInvocation: {
|
|
907
|
+
state: "partial-call",
|
|
908
|
+
toolCallId,
|
|
909
|
+
toolName,
|
|
910
|
+
args,
|
|
911
|
+
result: updated
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
} else if (payloadOutput?.from === "AGENT" || payloadOutput?.from === "USER" && payloadOutput?.payload?.output?.type?.startsWith("workflow-")) return accumulateAgentChunk(payloadOutput, result, metadata, toolCallId, toolName);
|
|
915
|
+
else {
|
|
916
|
+
const currentResult = toolPart.toolInvocation.result;
|
|
917
|
+
const existing = Array.isArray(currentResult) ? currentResult : [];
|
|
918
|
+
parts[toolPartIndex] = {
|
|
919
|
+
...toolPart,
|
|
920
|
+
toolInvocation: {
|
|
921
|
+
state: "partial-call",
|
|
922
|
+
toolCallId,
|
|
923
|
+
toolName,
|
|
924
|
+
args,
|
|
925
|
+
result: [...existing, payloadOutput]
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return replaceAt(result, messageIndex, withParts(targetMessage, parts));
|
|
930
|
+
}
|
|
931
|
+
case "is-task-complete": {
|
|
932
|
+
if (chunk.payload.suppressFeedback) return result;
|
|
933
|
+
const feedback = formatStreamCompletionFeedback({
|
|
934
|
+
complete: chunk.payload.passed,
|
|
935
|
+
scorers: chunk.payload.results,
|
|
936
|
+
totalDuration: chunk.payload.duration,
|
|
937
|
+
timedOut: chunk.payload.timedOut,
|
|
938
|
+
completionReason: chunk.payload.reason
|
|
939
|
+
}, chunk.payload.maxIterationReached);
|
|
940
|
+
const newMessage = newAssistantMessage(`is-task-complete-${chunk.runId + Date.now()}`, [{
|
|
941
|
+
type: "text",
|
|
942
|
+
text: feedback
|
|
943
|
+
}], {
|
|
944
|
+
...metadata,
|
|
945
|
+
completionResult: { passed: chunk.payload.passed }
|
|
946
|
+
});
|
|
947
|
+
return [...result, newMessage];
|
|
948
|
+
}
|
|
949
|
+
case "source": {
|
|
950
|
+
const lastMessage = result[result.length - 1];
|
|
951
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
952
|
+
const parts = [...lastMessage.content.parts];
|
|
953
|
+
if (chunk.payload.sourceType === "url") {
|
|
954
|
+
const sourceUrlPart = {
|
|
955
|
+
type: "source-url",
|
|
956
|
+
sourceId: chunk.payload.id,
|
|
957
|
+
url: chunk.payload.url || "",
|
|
958
|
+
title: chunk.payload.title,
|
|
959
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
960
|
+
};
|
|
961
|
+
parts.push(sourceUrlPart);
|
|
962
|
+
} else if (chunk.payload.sourceType === "document") parts.push({
|
|
963
|
+
type: "source-document",
|
|
964
|
+
sourceId: chunk.payload.id,
|
|
965
|
+
mediaType: chunk.payload.mimeType || "application/octet-stream",
|
|
966
|
+
title: chunk.payload.title,
|
|
967
|
+
filename: chunk.payload.filename,
|
|
968
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
969
|
+
});
|
|
970
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
971
|
+
}
|
|
972
|
+
case "file": {
|
|
973
|
+
const lastMessage = result[result.length - 1];
|
|
974
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
975
|
+
const parts = [...lastMessage.content.parts];
|
|
976
|
+
let data;
|
|
977
|
+
if (typeof chunk.payload.data === "string") data = chunk.payload.base64 ? `data:${chunk.payload.mimeType};base64,${chunk.payload.data}` : `data:${chunk.payload.mimeType},${encodeURIComponent(chunk.payload.data)}`;
|
|
978
|
+
else {
|
|
979
|
+
const base64 = uint8ArrayToBase64(chunk.payload.data);
|
|
980
|
+
data = `data:${chunk.payload.mimeType};base64,${base64}`;
|
|
981
|
+
}
|
|
982
|
+
parts.push({
|
|
983
|
+
type: "file",
|
|
984
|
+
mimeType: chunk.payload.mimeType,
|
|
985
|
+
data,
|
|
986
|
+
providerMetadata: chunk.payload.providerMetadata
|
|
987
|
+
});
|
|
988
|
+
return replaceLast(result, withParts(lastMessage, parts));
|
|
989
|
+
}
|
|
990
|
+
case "tool-call-approval": {
|
|
991
|
+
const lastMessage = result[result.length - 1];
|
|
992
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
993
|
+
const existingMeta = lastMessage.content.metadata ?? {};
|
|
994
|
+
const lastRequireApproval = existingMeta.mode === "stream" ? existingMeta.requireApprovalMetadata ?? {} : {};
|
|
995
|
+
return replaceLast(result, {
|
|
996
|
+
...lastMessage,
|
|
997
|
+
content: {
|
|
998
|
+
...lastMessage.content,
|
|
999
|
+
metadata: {
|
|
1000
|
+
...existingMeta,
|
|
1001
|
+
mode: "stream",
|
|
1002
|
+
requireApprovalMetadata: {
|
|
1003
|
+
...lastRequireApproval,
|
|
1004
|
+
[chunk.payload.toolName]: {
|
|
1005
|
+
toolCallId: chunk.payload.toolCallId,
|
|
1006
|
+
toolName: chunk.payload.toolName,
|
|
1007
|
+
args: chunk.payload.args
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
case "tool-call-suspended":
|
|
1015
|
+
case "background-task-suspended": {
|
|
1016
|
+
const isBgTaskEvent = chunk.type === "background-task-suspended";
|
|
1017
|
+
let suspToolCallId;
|
|
1018
|
+
let suspToolName;
|
|
1019
|
+
let suspArgs;
|
|
1020
|
+
let suspPayload;
|
|
1021
|
+
let suspSuspendedAt;
|
|
1022
|
+
let suspTaskId;
|
|
1023
|
+
if (chunk.type === "background-task-suspended") {
|
|
1024
|
+
suspToolCallId = chunk.payload.toolCallId;
|
|
1025
|
+
suspToolName = chunk.payload.toolName;
|
|
1026
|
+
suspArgs = chunk.payload.args;
|
|
1027
|
+
suspPayload = chunk.payload.suspendPayload;
|
|
1028
|
+
suspSuspendedAt = chunk.payload.suspendedAt;
|
|
1029
|
+
suspTaskId = chunk.payload.taskId;
|
|
1030
|
+
} else {
|
|
1031
|
+
suspToolCallId = chunk.payload.toolCallId;
|
|
1032
|
+
suspToolName = chunk.payload.toolName;
|
|
1033
|
+
suspArgs = chunk.payload.args;
|
|
1034
|
+
suspPayload = chunk.payload.suspendPayload;
|
|
1035
|
+
}
|
|
1036
|
+
const location = isBgTaskEvent ? locateToolPart(result, suspToolCallId, true) : { messageIndex: result.length - 1 };
|
|
1037
|
+
if (!location) return result;
|
|
1038
|
+
const { messageIndex } = location;
|
|
1039
|
+
const targetMessage = result[messageIndex];
|
|
1040
|
+
if (!targetMessage || targetMessage.role !== "assistant") return result;
|
|
1041
|
+
const existingMeta = targetMessage.content.metadata ?? {};
|
|
1042
|
+
const lastSuspendedTools = existingMeta.mode === "stream" ? existingMeta.suspendedTools ?? {} : {};
|
|
1043
|
+
const nextMetadata = mergeBgTaskMetadata(existingMeta, "stream", {
|
|
1044
|
+
resetRunningCount: isBgTaskEvent,
|
|
1045
|
+
perTaskEntry: isBgTaskEvent && suspTaskId ? {
|
|
1046
|
+
toolCallId: suspToolCallId,
|
|
1047
|
+
suspendedAt: suspSuspendedAt,
|
|
1048
|
+
taskId: suspTaskId
|
|
1049
|
+
} : void 0
|
|
1050
|
+
}, { suspendedTools: {
|
|
1051
|
+
...lastSuspendedTools,
|
|
1052
|
+
[suspToolName]: {
|
|
1053
|
+
toolCallId: suspToolCallId,
|
|
1054
|
+
toolName: suspToolName,
|
|
1055
|
+
args: suspArgs,
|
|
1056
|
+
suspendPayload: suspPayload,
|
|
1057
|
+
runId: chunk.runId
|
|
1058
|
+
}
|
|
1059
|
+
} });
|
|
1060
|
+
return replaceAt(result, messageIndex, withMetadata(targetMessage, nextMetadata));
|
|
1061
|
+
}
|
|
1062
|
+
case "finish":
|
|
1063
|
+
case "abort": return finishStreamingAssistantMessage(result);
|
|
1064
|
+
case "error": {
|
|
1065
|
+
const newMessage = newAssistantMessage(`error-${chunk.runId + Date.now()}`, [{
|
|
1066
|
+
type: "text",
|
|
1067
|
+
text: typeof chunk.payload.error === "string" ? chunk.payload.error : JSON.stringify(chunk.payload.error)
|
|
1068
|
+
}], {
|
|
1069
|
+
...metadata,
|
|
1070
|
+
status: "error"
|
|
1071
|
+
});
|
|
1072
|
+
return [...result, newMessage];
|
|
1073
|
+
}
|
|
1074
|
+
case "step-start": {
|
|
1075
|
+
const stepMessageId = typeof chunk.payload?.messageId === "string" ? chunk.payload.messageId : void 0;
|
|
1076
|
+
if (!stepMessageId) return result;
|
|
1077
|
+
const lastMessage = result[result.length - 1];
|
|
1078
|
+
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
1079
|
+
if (result.some((message) => message.id === stepMessageId)) return result;
|
|
1080
|
+
if (!lastMessage.content.parts.some((part) => !String(part.type).startsWith("data-"))) return replaceLast(result, {
|
|
1081
|
+
...lastMessage,
|
|
1082
|
+
id: stepMessageId
|
|
1083
|
+
});
|
|
1084
|
+
return appendAssistantMessage(finishStreamingAssistantMessage(result), stepMessageId, [], metadata);
|
|
1085
|
+
}
|
|
1086
|
+
case "step-finish":
|
|
1087
|
+
case "step-output":
|
|
1088
|
+
case "raw":
|
|
1089
|
+
case "watch":
|
|
1090
|
+
case "response-metadata": return result;
|
|
1091
|
+
case "goal": return result;
|
|
1092
|
+
case "object":
|
|
1093
|
+
case "object-result": return result;
|
|
1094
|
+
case "background-task-started":
|
|
1095
|
+
case "background-task-cancelled":
|
|
1096
|
+
case "background-task-resumed": return result;
|
|
1097
|
+
case "workflow-start":
|
|
1098
|
+
case "workflow-finish":
|
|
1099
|
+
case "workflow-canceled":
|
|
1100
|
+
case "workflow-paused":
|
|
1101
|
+
case "workflow-step-start":
|
|
1102
|
+
case "workflow-step-finish":
|
|
1103
|
+
case "workflow-step-suspended":
|
|
1104
|
+
case "workflow-step-waiting":
|
|
1105
|
+
case "workflow-step-output":
|
|
1106
|
+
case "workflow-step-progress":
|
|
1107
|
+
case "workflow-step-result": return result;
|
|
1108
|
+
case "agent-execution-start":
|
|
1109
|
+
case "agent-execution-approval":
|
|
1110
|
+
case "agent-execution-suspended":
|
|
1111
|
+
case "agent-execution-end":
|
|
1112
|
+
case "agent-execution-abort":
|
|
1113
|
+
case "tool-execution-start":
|
|
1114
|
+
case "tool-execution-end":
|
|
1115
|
+
case "tool-execution-approval":
|
|
1116
|
+
case "tool-execution-suspended":
|
|
1117
|
+
case "tool-execution-abort":
|
|
1118
|
+
case "routing-agent-start":
|
|
1119
|
+
case "routing-agent-text-delta":
|
|
1120
|
+
case "routing-agent-text-start":
|
|
1121
|
+
case "routing-agent-end":
|
|
1122
|
+
case "routing-agent-abort":
|
|
1123
|
+
case "workflow-execution-start":
|
|
1124
|
+
case "workflow-execution-end":
|
|
1125
|
+
case "workflow-execution-suspended":
|
|
1126
|
+
case "workflow-execution-abort":
|
|
1127
|
+
case "network-execution-event-step-finish":
|
|
1128
|
+
case "network-execution-event-finish":
|
|
1129
|
+
case "network-validation-start":
|
|
1130
|
+
case "network-validation-end":
|
|
1131
|
+
case "network-object":
|
|
1132
|
+
case "network-object-result":
|
|
1133
|
+
case "tool-output-denied": return result;
|
|
1134
|
+
default: return assertExhaustive(chunk, result);
|
|
1135
|
+
}
|
|
1271
1136
|
};
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1137
|
+
/**
|
|
1138
|
+
* Compile-time exhaustiveness helper. At runtime, returns the conversation
|
|
1139
|
+
* unchanged so unexpected chunk variants never throw inside the React stream
|
|
1140
|
+
* pump; TypeScript will fail to compile if `ChunkType` ever grows a new branch
|
|
1141
|
+
* that isn't enumerated above.
|
|
1142
|
+
*/
|
|
1143
|
+
const assertExhaustive = (_chunk, fallback) => fallback;
|
|
1144
|
+
const accumulateAgentChunk = (chunk, conversation, _metadata, parentToolCallId, parentToolName) => {
|
|
1145
|
+
const lastMessage = conversation[conversation.length - 1];
|
|
1146
|
+
if (!lastMessage || lastMessage.role !== "assistant") return conversation;
|
|
1147
|
+
const parts = [...lastMessage.content.parts];
|
|
1148
|
+
const findToolPartIndex = () => parts.findIndex((part) => isToolPart(part) && (parentToolCallId && part.toolInvocation.toolCallId === parentToolCallId || parentToolName && part.toolInvocation.toolName === parentToolName));
|
|
1149
|
+
if (chunk.type === "text-delta") {
|
|
1150
|
+
const agentChunk = chunk.payload;
|
|
1151
|
+
const toolPartIndex = findToolPartIndex();
|
|
1152
|
+
if (toolPartIndex === -1) return conversation;
|
|
1153
|
+
const toolPart = parts[toolPartIndex];
|
|
1154
|
+
const existingResult = toolPart.toolInvocation.result || {};
|
|
1155
|
+
const childMessages = existingResult.childMessages || [];
|
|
1156
|
+
const lastChildMessage = childMessages[childMessages.length - 1];
|
|
1157
|
+
const textMessage = {
|
|
1158
|
+
type: "text",
|
|
1159
|
+
content: (lastChildMessage?.content || "") + agentChunk.text
|
|
1160
|
+
};
|
|
1161
|
+
const nextChildren = lastChildMessage?.type === "text" ? [...childMessages.slice(0, -1), textMessage] : [...childMessages, textMessage];
|
|
1162
|
+
parts[toolPartIndex] = {
|
|
1163
|
+
...toolPart,
|
|
1164
|
+
toolInvocation: {
|
|
1165
|
+
...toolPart.toolInvocation,
|
|
1166
|
+
result: {
|
|
1167
|
+
...existingResult,
|
|
1168
|
+
childMessages: nextChildren
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
} else if (chunk.type === "tool-call") {
|
|
1173
|
+
const agentChunk = chunk.payload;
|
|
1174
|
+
const toolPartIndex = findToolPartIndex();
|
|
1175
|
+
if (toolPartIndex === -1) return conversation;
|
|
1176
|
+
const toolPart = parts[toolPartIndex];
|
|
1177
|
+
const existingResult = toolPart.toolInvocation.result || {};
|
|
1178
|
+
const childMessages = existingResult.childMessages || [];
|
|
1179
|
+
parts[toolPartIndex] = {
|
|
1180
|
+
...toolPart,
|
|
1181
|
+
toolInvocation: {
|
|
1182
|
+
...toolPart.toolInvocation,
|
|
1183
|
+
result: {
|
|
1184
|
+
...existingResult,
|
|
1185
|
+
childMessages: [...childMessages, {
|
|
1186
|
+
type: "tool",
|
|
1187
|
+
toolCallId: agentChunk.toolCallId,
|
|
1188
|
+
toolName: agentChunk.toolName,
|
|
1189
|
+
args: agentChunk.args
|
|
1190
|
+
}]
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
} else if (chunk.type === "tool-output") {
|
|
1195
|
+
const agentChunk = chunk.payload;
|
|
1196
|
+
const toolPartIndex = findToolPartIndex();
|
|
1197
|
+
if (toolPartIndex === -1) return conversation;
|
|
1198
|
+
const toolPart = parts[toolPartIndex];
|
|
1199
|
+
if (agentChunk?.output?.type?.startsWith("workflow-")) {
|
|
1200
|
+
const existingResult = toolPart.toolInvocation.result || {};
|
|
1201
|
+
const childMessages = existingResult.childMessages || [];
|
|
1202
|
+
const lastIndex = childMessages.length - 1;
|
|
1203
|
+
const currentMessage = childMessages[lastIndex];
|
|
1204
|
+
const actualExistingWorkflowState = currentMessage?.toolOutput || {};
|
|
1205
|
+
const updated = mapWorkflowStreamChunkToWatchResult(actualExistingWorkflowState, agentChunk.output);
|
|
1206
|
+
if (lastIndex >= 0 && childMessages[lastIndex]?.type === "tool") parts[toolPartIndex] = {
|
|
1207
|
+
...toolPart,
|
|
1208
|
+
toolInvocation: {
|
|
1209
|
+
...toolPart.toolInvocation,
|
|
1210
|
+
result: {
|
|
1211
|
+
...existingResult,
|
|
1212
|
+
childMessages: [...childMessages.slice(0, -1), {
|
|
1213
|
+
...currentMessage,
|
|
1214
|
+
toolOutput: {
|
|
1215
|
+
...updated,
|
|
1216
|
+
runId: agentChunk.output.runId
|
|
1217
|
+
}
|
|
1218
|
+
}]
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
} else if (chunk.type === "tool-result") {
|
|
1224
|
+
const agentChunk = chunk.payload;
|
|
1225
|
+
const toolPartIndex = findToolPartIndex();
|
|
1226
|
+
if (toolPartIndex === -1) return conversation;
|
|
1227
|
+
const toolPart = parts[toolPartIndex];
|
|
1228
|
+
const existingResult = toolPart.toolInvocation.result || {};
|
|
1229
|
+
const childMessages = existingResult.childMessages || [];
|
|
1230
|
+
const lastIndex = childMessages.length - 1;
|
|
1231
|
+
const isWorkflow = agentChunk?.toolName?.startsWith("workflow-");
|
|
1232
|
+
if (lastIndex >= 0 && childMessages[lastIndex]?.type === "tool") parts[toolPartIndex] = {
|
|
1233
|
+
...toolPart,
|
|
1234
|
+
toolInvocation: {
|
|
1235
|
+
...toolPart.toolInvocation,
|
|
1236
|
+
result: {
|
|
1237
|
+
...existingResult,
|
|
1238
|
+
childMessages: [...childMessages.slice(0, -1), {
|
|
1239
|
+
...childMessages[lastIndex],
|
|
1240
|
+
toolOutput: isWorkflow ? {
|
|
1241
|
+
...agentChunk.result?.result,
|
|
1242
|
+
runId: agentChunk.result?.runId
|
|
1243
|
+
} : agentChunk.result
|
|
1244
|
+
}]
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1278
1250
|
};
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1251
|
+
const networkMode = (metadata) => ({
|
|
1252
|
+
...metadata,
|
|
1253
|
+
mode: "network"
|
|
1254
|
+
});
|
|
1255
|
+
const findPartIndex = (parts, predicate) => parts.findIndex(predicate);
|
|
1256
|
+
const isDynamicToolPart = (part) => part.type === "dynamic-tool";
|
|
1257
|
+
const lastAssistant = (conversation) => {
|
|
1258
|
+
const last = conversation[conversation.length - 1];
|
|
1259
|
+
return last && last.role === "assistant" ? last : void 0;
|
|
1282
1260
|
};
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1261
|
+
/**
|
|
1262
|
+
* Try to parse the buffered routing-agent text as a JSON object. Returns the
|
|
1263
|
+
* parsed object on success, or `null` while the buffer is still incomplete or
|
|
1264
|
+
* not JSON at all.
|
|
1265
|
+
*/
|
|
1266
|
+
const tryParseRoutingDecision = (buffered) => {
|
|
1267
|
+
const trimmed = buffered.trim();
|
|
1268
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
1269
|
+
try {
|
|
1270
|
+
const parsed = JSON.parse(trimmed);
|
|
1271
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1272
|
+
return null;
|
|
1273
|
+
} catch {
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1286
1276
|
};
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Routing-agent text deltas describe the network's routing decision (often a
|
|
1279
|
+
* JSON object such as `{ "isNetwork": true, "agentId": "...", ... }`). The raw
|
|
1280
|
+
* payload is never useful in the rendered thread, so we buffer it in
|
|
1281
|
+
* message-level metadata, promote it to `routingDecision` when it parses as
|
|
1282
|
+
* JSON, and fall back to `routingDecisionText` for non-JSON routing models.
|
|
1283
|
+
* No visible text part is produced.
|
|
1284
|
+
*/
|
|
1285
|
+
const handleRoutingAgentDelta = (chunk, conversation, metadata) => {
|
|
1286
|
+
const delta = chunk.payload?.text ?? "";
|
|
1287
|
+
if (!delta) return conversation;
|
|
1288
|
+
const lastMessage = lastAssistant(conversation);
|
|
1289
|
+
const mergeRoutingMetadata = (existing) => {
|
|
1290
|
+
const buffered = (existing.routingDecisionBuffer ?? "") + delta;
|
|
1291
|
+
const next = {
|
|
1292
|
+
...cloneMetadata(existing),
|
|
1293
|
+
mode: "network"
|
|
1294
|
+
};
|
|
1295
|
+
const parsed = tryParseRoutingDecision(buffered);
|
|
1296
|
+
if (parsed) {
|
|
1297
|
+
next.routingDecision = parsed;
|
|
1298
|
+
delete next.routingDecisionBuffer;
|
|
1299
|
+
delete next.routingDecisionText;
|
|
1300
|
+
} else {
|
|
1301
|
+
next.routingDecisionBuffer = buffered;
|
|
1302
|
+
next.routingDecisionText = buffered;
|
|
1303
|
+
}
|
|
1304
|
+
return next;
|
|
1305
|
+
};
|
|
1306
|
+
if (!lastMessage) {
|
|
1307
|
+
const seed = mergeRoutingMetadata({});
|
|
1308
|
+
return appendAssistantMessage(conversation, `routing-agent-${chunk.payload?.runId ?? "unknown"}-${Date.now()}`, [], {
|
|
1309
|
+
...networkMode(metadata),
|
|
1310
|
+
...seed
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
return replaceLast(conversation, withMetadata(lastMessage, mergeRoutingMetadata(lastMessage.content.metadata ?? {})));
|
|
1291
1314
|
};
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1315
|
+
const handleAgentNetworkChunk = (chunk, conversation, metadata) => {
|
|
1316
|
+
if (chunk.type === "agent-execution-start") {
|
|
1317
|
+
const primitiveId = chunk.payload?.args?.primitiveId;
|
|
1318
|
+
const runId = chunk.payload.runId;
|
|
1319
|
+
if (!primitiveId || !runId) return conversation;
|
|
1320
|
+
const toolPart = {
|
|
1321
|
+
type: "dynamic-tool",
|
|
1322
|
+
toolName: primitiveId,
|
|
1323
|
+
toolCallId: runId,
|
|
1324
|
+
state: "input-available",
|
|
1325
|
+
input: chunk.payload.args
|
|
1326
|
+
};
|
|
1327
|
+
return appendAssistantMessage(conversation, `agent-execution-start-${runId}-${Date.now()}`, [toolPart], {
|
|
1328
|
+
...networkMode(metadata),
|
|
1329
|
+
selectionReason: chunk.payload?.args?.selectionReason || "",
|
|
1330
|
+
agentInput: chunk.payload?.args?.task,
|
|
1331
|
+
from: "AGENT"
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
if (chunk.type === "agent-execution-end") {
|
|
1335
|
+
const lastMessage = lastAssistant(conversation);
|
|
1336
|
+
if (!lastMessage) return conversation;
|
|
1337
|
+
const parts = [...lastMessage.content.parts];
|
|
1338
|
+
const toolPartIndex = findPartIndex(parts, (part) => isDynamicToolPart(part));
|
|
1339
|
+
if (toolPartIndex !== -1) {
|
|
1340
|
+
const toolPart = parts[toolPartIndex];
|
|
1341
|
+
const currentOutput = toolPart.output;
|
|
1342
|
+
parts[toolPartIndex] = {
|
|
1343
|
+
type: "dynamic-tool",
|
|
1344
|
+
toolName: toolPart.toolName,
|
|
1345
|
+
toolCallId: toolPart.toolCallId,
|
|
1346
|
+
state: "output-available",
|
|
1347
|
+
input: toolPart.input,
|
|
1348
|
+
output: {
|
|
1349
|
+
...currentOutput,
|
|
1350
|
+
result: currentOutput?.result || chunk.payload?.result || ""
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1355
|
+
}
|
|
1356
|
+
if (chunk.type.startsWith("agent-execution-event-")) {
|
|
1357
|
+
const lastMessage = lastAssistant(conversation);
|
|
1358
|
+
if (!lastMessage) return conversation;
|
|
1359
|
+
const agentChunk = chunk.payload;
|
|
1360
|
+
const parts = [...lastMessage.content.parts];
|
|
1361
|
+
const toolPartIndex = findPartIndex(parts, (part) => isDynamicToolPart(part));
|
|
1362
|
+
if (toolPartIndex === -1) return conversation;
|
|
1363
|
+
const toolPart = parts[toolPartIndex];
|
|
1364
|
+
if (agentChunk.type === "text-delta") {
|
|
1365
|
+
const childMessages = toolPart?.output?.childMessages || [];
|
|
1366
|
+
const lastChildMessage = childMessages[childMessages.length - 1];
|
|
1367
|
+
const textMessage = {
|
|
1368
|
+
type: "text",
|
|
1369
|
+
content: (lastChildMessage?.content || "") + agentChunk.payload.text
|
|
1370
|
+
};
|
|
1371
|
+
const nextMessages = lastChildMessage?.type === "text" ? [...childMessages.slice(0, -1), textMessage] : [...childMessages, textMessage];
|
|
1372
|
+
parts[toolPartIndex] = {
|
|
1373
|
+
...toolPart,
|
|
1374
|
+
output: { childMessages: nextMessages }
|
|
1375
|
+
};
|
|
1376
|
+
} else if (agentChunk.type === "tool-call") {
|
|
1377
|
+
const childMessages = toolPart?.output?.childMessages || [];
|
|
1378
|
+
parts[toolPartIndex] = {
|
|
1379
|
+
...toolPart,
|
|
1380
|
+
output: {
|
|
1381
|
+
...toolPart?.output,
|
|
1382
|
+
childMessages: [...childMessages, {
|
|
1383
|
+
type: "tool",
|
|
1384
|
+
toolCallId: agentChunk.payload.toolCallId,
|
|
1385
|
+
toolName: agentChunk.payload.toolName,
|
|
1386
|
+
args: agentChunk.payload.args
|
|
1387
|
+
}]
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
} else if (agentChunk.type === "tool-output") {
|
|
1391
|
+
if (agentChunk.payload?.output?.type?.startsWith("workflow-")) {
|
|
1392
|
+
const childMessages = toolPart?.output?.childMessages || [];
|
|
1393
|
+
const lastToolIndex = childMessages.length - 1;
|
|
1394
|
+
const currentMessage = childMessages[lastToolIndex];
|
|
1395
|
+
const actualExistingWorkflowState = currentMessage?.toolOutput || {};
|
|
1396
|
+
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(actualExistingWorkflowState, agentChunk.payload.output);
|
|
1397
|
+
if (lastToolIndex >= 0 && childMessages[lastToolIndex]?.type === "tool") parts[toolPartIndex] = {
|
|
1398
|
+
...toolPart,
|
|
1399
|
+
output: {
|
|
1400
|
+
...toolPart?.output,
|
|
1401
|
+
childMessages: [...childMessages.slice(0, -1), {
|
|
1402
|
+
...currentMessage,
|
|
1403
|
+
toolOutput: updatedWorkflowState
|
|
1404
|
+
}]
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
} else if (agentChunk.type === "tool-result") {
|
|
1409
|
+
const childMessages = toolPart?.output?.childMessages || [];
|
|
1410
|
+
const lastToolIndex = childMessages.length - 1;
|
|
1411
|
+
const isWorkflow = Boolean(agentChunk.payload?.result?.result?.steps);
|
|
1412
|
+
if (lastToolIndex >= 0 && childMessages[lastToolIndex]?.type === "tool") parts[toolPartIndex] = {
|
|
1413
|
+
...toolPart,
|
|
1414
|
+
output: {
|
|
1415
|
+
...toolPart?.output,
|
|
1416
|
+
childMessages: [...childMessages.slice(0, -1), {
|
|
1417
|
+
...childMessages[lastToolIndex],
|
|
1418
|
+
toolOutput: isWorkflow ? agentChunk.payload.result.result : agentChunk.payload.result
|
|
1419
|
+
}]
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1424
|
+
}
|
|
1425
|
+
return conversation;
|
|
1295
1426
|
};
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1427
|
+
const handleWorkflowNetworkChunk = (chunk, conversation, metadata) => {
|
|
1428
|
+
if (chunk.type === "workflow-execution-start") {
|
|
1429
|
+
const primitiveId = chunk.payload?.args?.primitiveId;
|
|
1430
|
+
const runId = chunk.payload.runId;
|
|
1431
|
+
if (!primitiveId || !runId) return conversation;
|
|
1432
|
+
let agentInput;
|
|
1433
|
+
try {
|
|
1434
|
+
agentInput = JSON.parse(chunk.payload?.args?.prompt);
|
|
1435
|
+
} catch {
|
|
1436
|
+
agentInput = chunk.payload?.args?.prompt;
|
|
1437
|
+
}
|
|
1438
|
+
const toolPart = {
|
|
1439
|
+
type: "dynamic-tool",
|
|
1440
|
+
toolName: primitiveId,
|
|
1441
|
+
toolCallId: runId,
|
|
1442
|
+
state: "input-available",
|
|
1443
|
+
input: chunk.payload.args
|
|
1444
|
+
};
|
|
1445
|
+
return appendAssistantMessage(conversation, `workflow-start-${runId}-${Date.now()}`, [toolPart], {
|
|
1446
|
+
...networkMode(metadata),
|
|
1447
|
+
selectionReason: chunk.payload?.args?.selectionReason || "",
|
|
1448
|
+
from: "WORKFLOW",
|
|
1449
|
+
agentInput
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
if (chunk.type === "workflow-execution-suspended") {
|
|
1453
|
+
const lastMessage = lastAssistant(conversation);
|
|
1454
|
+
if (!lastMessage) return conversation;
|
|
1455
|
+
const existing = lastMessage.content.metadata?.suspendedTools ?? {};
|
|
1456
|
+
return replaceLast(conversation, withMetadata(lastMessage, {
|
|
1457
|
+
...cloneMetadata(lastMessage.content.metadata),
|
|
1458
|
+
mode: "network",
|
|
1459
|
+
suspendedTools: {
|
|
1460
|
+
...existing,
|
|
1461
|
+
[chunk.payload.toolName]: {
|
|
1462
|
+
toolCallId: chunk.payload.toolCallId,
|
|
1463
|
+
toolName: chunk.payload.toolName,
|
|
1464
|
+
args: chunk.payload.args,
|
|
1465
|
+
suspendPayload: chunk.payload.suspendPayload,
|
|
1466
|
+
runId: chunk.payload.runId
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}));
|
|
1470
|
+
}
|
|
1471
|
+
if (chunk.type.startsWith("workflow-execution-event-")) {
|
|
1472
|
+
const lastMessage = lastAssistant(conversation);
|
|
1473
|
+
if (!lastMessage) return conversation;
|
|
1474
|
+
const parts = [...lastMessage.content.parts];
|
|
1475
|
+
const toolPartIndex = findPartIndex(parts, (part) => isDynamicToolPart(part));
|
|
1476
|
+
if (toolPartIndex === -1) return conversation;
|
|
1477
|
+
const toolPart = parts[toolPartIndex];
|
|
1478
|
+
const existingWorkflowState = toolPart.output || {};
|
|
1479
|
+
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(existingWorkflowState, chunk.payload);
|
|
1480
|
+
parts[toolPartIndex] = {
|
|
1481
|
+
...toolPart,
|
|
1482
|
+
output: updatedWorkflowState
|
|
1483
|
+
};
|
|
1484
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1485
|
+
}
|
|
1486
|
+
return conversation;
|
|
1311
1487
|
};
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1488
|
+
const handleToolNetworkChunk = (chunk, conversation, metadata) => {
|
|
1489
|
+
if (chunk.type === "tool-execution-start") {
|
|
1490
|
+
const argsData = chunk.payload.args;
|
|
1491
|
+
const nestedArgs = argsData.args || {};
|
|
1492
|
+
const lastMessage = lastAssistant(conversation);
|
|
1493
|
+
const toolPart = {
|
|
1494
|
+
type: "dynamic-tool",
|
|
1495
|
+
toolName: argsData.toolName || "unknown",
|
|
1496
|
+
toolCallId: argsData.toolCallId || "unknown",
|
|
1497
|
+
state: "input-available",
|
|
1498
|
+
input: nestedArgs
|
|
1499
|
+
};
|
|
1500
|
+
if (!lastMessage) return appendAssistantMessage(conversation, `tool-start-${chunk.payload.runId}-${Date.now()}`, [toolPart], {
|
|
1501
|
+
...networkMode(metadata),
|
|
1502
|
+
selectionReason: metadata.mode === "network" ? metadata.selectionReason || argsData.selectionReason : "",
|
|
1503
|
+
agentInput: nestedArgs
|
|
1504
|
+
});
|
|
1505
|
+
const parts = [...lastMessage.content.parts, toolPart];
|
|
1506
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1507
|
+
}
|
|
1508
|
+
if (chunk.type === "tool-execution-approval") {
|
|
1509
|
+
const lastMessage = lastAssistant(conversation);
|
|
1510
|
+
if (!lastMessage) return conversation;
|
|
1511
|
+
const existing = lastMessage.content.metadata?.requireApprovalMetadata ?? {};
|
|
1512
|
+
return replaceLast(conversation, withMetadata(lastMessage, {
|
|
1513
|
+
...cloneMetadata(lastMessage.content.metadata),
|
|
1514
|
+
mode: "network",
|
|
1515
|
+
requireApprovalMetadata: {
|
|
1516
|
+
...existing,
|
|
1517
|
+
[chunk.payload.toolName]: {
|
|
1518
|
+
toolCallId: chunk.payload.toolCallId,
|
|
1519
|
+
toolName: chunk.payload.toolName,
|
|
1520
|
+
args: chunk.payload.args,
|
|
1521
|
+
runId: chunk.payload.runId
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}));
|
|
1525
|
+
}
|
|
1526
|
+
if (chunk.type === "tool-execution-suspended") {
|
|
1527
|
+
const lastMessage = lastAssistant(conversation);
|
|
1528
|
+
if (!lastMessage) return conversation;
|
|
1529
|
+
const existing = lastMessage.content.metadata?.suspendedTools ?? {};
|
|
1530
|
+
return replaceLast(conversation, withMetadata(lastMessage, {
|
|
1531
|
+
...cloneMetadata(lastMessage.content.metadata),
|
|
1532
|
+
mode: "network",
|
|
1533
|
+
suspendedTools: {
|
|
1534
|
+
...existing,
|
|
1535
|
+
[chunk.payload.toolName]: {
|
|
1536
|
+
toolCallId: chunk.payload.toolCallId,
|
|
1537
|
+
toolName: chunk.payload.toolName,
|
|
1538
|
+
args: chunk.payload.args,
|
|
1539
|
+
suspendPayload: chunk.payload.suspendPayload,
|
|
1540
|
+
runId: chunk.payload.runId
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
if (chunk.type === "tool-execution-end") {
|
|
1546
|
+
const lastMessage = lastAssistant(conversation);
|
|
1547
|
+
if (!lastMessage) return conversation;
|
|
1548
|
+
const parts = [...lastMessage.content.parts];
|
|
1549
|
+
const toolPartIndex = findPartIndex(parts, (part) => isDynamicToolPart(part) && part.toolCallId === chunk.payload.toolCallId);
|
|
1550
|
+
if (toolPartIndex !== -1) {
|
|
1551
|
+
const toolPart = parts[toolPartIndex];
|
|
1552
|
+
const currentOutput = toolPart.output;
|
|
1553
|
+
parts[toolPartIndex] = {
|
|
1554
|
+
type: "dynamic-tool",
|
|
1555
|
+
toolName: toolPart.toolName,
|
|
1556
|
+
toolCallId: toolPart.toolCallId,
|
|
1557
|
+
state: "output-available",
|
|
1558
|
+
input: toolPart.input,
|
|
1559
|
+
output: currentOutput?.result || chunk.payload?.result || ""
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
return replaceLast(conversation, withParts(lastMessage, parts));
|
|
1563
|
+
}
|
|
1564
|
+
return conversation;
|
|
1315
1565
|
};
|
|
1316
|
-
|
|
1317
|
-
|
|
1566
|
+
/**
|
|
1567
|
+
* Reduce a single network-mode chunk into the running `MastraDBMessage[]`
|
|
1568
|
+
* conversation. Ports the historical `AISdkNetworkTransformer` so the playground
|
|
1569
|
+
* keeps rendering network badges (routing text, agent/workflow/tool execution,
|
|
1570
|
+
* suspensions, approvals, completion feedback) until the next major.
|
|
1571
|
+
*/
|
|
1572
|
+
const accumulateNetworkChunk = ({ chunk, conversation, metadata }) => {
|
|
1573
|
+
const newConversation = [...conversation];
|
|
1574
|
+
if (chunk.type === "routing-agent-text-delta") return handleRoutingAgentDelta(chunk, newConversation, metadata);
|
|
1575
|
+
if (chunk.type.startsWith("agent-execution-")) return handleAgentNetworkChunk(chunk, newConversation, metadata);
|
|
1576
|
+
if (chunk.type.startsWith("workflow-execution-")) return handleWorkflowNetworkChunk(chunk, newConversation, metadata);
|
|
1577
|
+
if (chunk.type.startsWith("tool-execution-")) return handleToolNetworkChunk(chunk, newConversation, metadata);
|
|
1578
|
+
if (chunk.type === "network-validation-end") {
|
|
1579
|
+
if (chunk.payload.suppressFeedback) return newConversation;
|
|
1580
|
+
const textPart = {
|
|
1581
|
+
type: "text",
|
|
1582
|
+
text: formatCompletionFeedback({
|
|
1583
|
+
complete: chunk.payload.passed,
|
|
1584
|
+
scorers: chunk.payload.results,
|
|
1585
|
+
totalDuration: chunk.payload.duration,
|
|
1586
|
+
timedOut: chunk.payload.timedOut,
|
|
1587
|
+
completionReason: chunk.payload.reason
|
|
1588
|
+
}, chunk.payload.maxIterationReached)
|
|
1589
|
+
};
|
|
1590
|
+
return appendAssistantMessage(newConversation, `network-validation-end-${chunk.payload.runId}-${Date.now()}`, [textPart], {
|
|
1591
|
+
...networkMode(metadata),
|
|
1592
|
+
completionResult: { passed: chunk.payload.passed }
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
if (chunk.type === "network-execution-event-step-finish") {
|
|
1596
|
+
const lastMessage = lastAssistant(newConversation);
|
|
1597
|
+
if (!lastMessage) return newConversation;
|
|
1598
|
+
const agentChunk = chunk.payload;
|
|
1599
|
+
const parts = [...lastMessage.content.parts];
|
|
1600
|
+
const textPartIndex = findPartIndex(parts, (part) => part.type === "text");
|
|
1601
|
+
if (textPartIndex === -1) {
|
|
1602
|
+
parts.push({
|
|
1603
|
+
type: "text",
|
|
1604
|
+
text: agentChunk.result,
|
|
1605
|
+
state: "done"
|
|
1606
|
+
});
|
|
1607
|
+
return replaceLast(newConversation, withParts(lastMessage, parts));
|
|
1608
|
+
}
|
|
1609
|
+
const textPart = parts[textPartIndex];
|
|
1610
|
+
if (textPart.type === "text") {
|
|
1611
|
+
parts[textPartIndex] = {
|
|
1612
|
+
...textPart,
|
|
1613
|
+
state: "done"
|
|
1614
|
+
};
|
|
1615
|
+
return replaceLast(newConversation, withParts(lastMessage, parts));
|
|
1616
|
+
}
|
|
1617
|
+
return newConversation;
|
|
1618
|
+
}
|
|
1619
|
+
return newConversation;
|
|
1318
1620
|
};
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1621
|
+
//#endregion
|
|
1622
|
+
//#region src/lib/mastra-db/fromCoreUserMessage.ts
|
|
1623
|
+
/**
|
|
1624
|
+
* Convert a CoreUserMessage into a canonical `MastraDBMessage` (`format: 2`).
|
|
1625
|
+
*
|
|
1626
|
+
* Image and file inputs are emitted as the canonical V4 `file` part shape
|
|
1627
|
+
* (`{ type: 'file', mimeType, data }`) — the exact shape memory resolves on
|
|
1628
|
+
* reload (see `AIV4Adapter`), so optimistic-send and reload render identically.
|
|
1629
|
+
*
|
|
1630
|
+
* Handles all CoreUserMessage content types:
|
|
1631
|
+
* - String content → single text part
|
|
1632
|
+
* - Array content with text/image/file parts → corresponding `MastraMessagePart`s
|
|
1633
|
+
*/
|
|
1634
|
+
const coreUserMessageToParts = (coreUserMessage) => typeof coreUserMessage.content === "string" ? [{
|
|
1635
|
+
type: "text",
|
|
1636
|
+
text: coreUserMessage.content
|
|
1637
|
+
}] : coreUserMessage.content.map((part) => {
|
|
1638
|
+
switch (part.type) {
|
|
1639
|
+
case "text": return {
|
|
1640
|
+
type: "text",
|
|
1641
|
+
text: part.text
|
|
1642
|
+
};
|
|
1643
|
+
case "image": {
|
|
1644
|
+
const mimeType = part.mimeType ?? "image/*";
|
|
1645
|
+
return {
|
|
1646
|
+
type: "file",
|
|
1647
|
+
mimeType,
|
|
1648
|
+
data: encodeFilePartDataForStorage(part.image, mimeType)
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
case "file": return {
|
|
1652
|
+
type: "file",
|
|
1653
|
+
mimeType: part.mimeType,
|
|
1654
|
+
data: encodeFilePartDataForStorage(part.data, part.mimeType),
|
|
1655
|
+
...part.filename !== void 0 ? { filename: part.filename } : {}
|
|
1656
|
+
};
|
|
1657
|
+
default: throw new Error(`Unhandled content part type: ${part.type}`);
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
const newUserMessage = (parts) => ({
|
|
1661
|
+
id: `user-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
1662
|
+
role: "user",
|
|
1663
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1664
|
+
content: {
|
|
1665
|
+
format: 2,
|
|
1666
|
+
parts
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
const fromCoreUserMessageToMastraDBMessage = (coreUserMessage) => newUserMessage(coreUserMessageToParts(coreUserMessage));
|
|
1670
|
+
/**
|
|
1671
|
+
* Merge multiple `CoreUserMessage`s into a single canonical `MastraDBMessage`.
|
|
1672
|
+
*
|
|
1673
|
+
* A user turn that carries attachments arrives as several `CoreUserMessage`s
|
|
1674
|
+
* (one for the text, one per attachment). Memory/reload persists and resolves
|
|
1675
|
+
* that whole turn as a single multi-part user message, so the optimistic
|
|
1676
|
+
* streaming display must do the same — flatten every message's parts into one
|
|
1677
|
+
* `parts` array — to render identically (one bubble, not one per message).
|
|
1678
|
+
*/
|
|
1679
|
+
const fromCoreUserMessagesToMastraDBMessage = (coreUserMessages) => newUserMessage(coreUserMessages.flatMap(coreUserMessageToParts));
|
|
1680
|
+
//#endregion
|
|
1681
|
+
//#region src/agent/extract-tasks.ts
|
|
1682
|
+
const TASK_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1683
|
+
"task_write",
|
|
1684
|
+
"task_update",
|
|
1685
|
+
"task_complete",
|
|
1686
|
+
"task_check"
|
|
1687
|
+
]);
|
|
1688
|
+
const TASK_SIGNAL_ID = "tasks";
|
|
1689
|
+
const TASK_TAG_NAMES = /* @__PURE__ */ new Set(["current-task-list", "task-list-update"]);
|
|
1690
|
+
const TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
1691
|
+
"pending",
|
|
1692
|
+
"in_progress",
|
|
1693
|
+
"completed"
|
|
1694
|
+
]);
|
|
1695
|
+
const isDataChunk$1 = (chunk) => typeof chunk.type === "string" && chunk.type.startsWith("data-");
|
|
1696
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1697
|
+
const isTaskStatus = (value) => typeof value === "string" && TASK_STATUSES.has(value);
|
|
1698
|
+
const isTaskItemArray = (value) => Array.isArray(value) && value.every((item) => isRecord$1(item) && typeof item.id === "string" && typeof item.content === "string" && isTaskStatus(item.status) && typeof item.activeForm === "string");
|
|
1699
|
+
const isTaskSignal = (value) => {
|
|
1700
|
+
if (!isRecord$1(value)) return false;
|
|
1701
|
+
return value.id === TASK_SIGNAL_ID || typeof value.tagName === "string" && TASK_TAG_NAMES.has(value.tagName);
|
|
1333
1702
|
};
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: className || CodeBlockClass, children: [
|
|
1342
|
-
nodes ?? null,
|
|
1343
|
-
cta
|
|
1344
|
-
] });
|
|
1703
|
+
const extractTasksFromSignalData = (data) => {
|
|
1704
|
+
if (!isTaskSignal(data) || !isRecord$1(data)) return void 0;
|
|
1705
|
+
const metadata = data.metadata;
|
|
1706
|
+
if (!isRecord$1(metadata)) return void 0;
|
|
1707
|
+
const value = metadata.value;
|
|
1708
|
+
if (!isRecord$1(value)) return void 0;
|
|
1709
|
+
return isTaskItemArray(value.tasks) ? value.tasks : void 0;
|
|
1345
1710
|
};
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1711
|
+
const parseTasksFromResult = (raw) => {
|
|
1712
|
+
if (isRecord$1(raw) && isTaskItemArray(raw.tasks)) return raw.tasks;
|
|
1713
|
+
if (typeof raw !== "string") return void 0;
|
|
1714
|
+
try {
|
|
1715
|
+
const parsed = JSON.parse(raw);
|
|
1716
|
+
return isRecord$1(parsed) && isTaskItemArray(parsed.tasks) ? parsed.tasks : void 0;
|
|
1717
|
+
} catch {
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1354
1720
|
};
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
{
|
|
1359
|
-
width: "17",
|
|
1360
|
-
height: "16",
|
|
1361
|
-
viewBox: "0 0 17 16",
|
|
1362
|
-
fill: "none",
|
|
1363
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
1364
|
-
...props,
|
|
1365
|
-
className: tailwindMerge.twMerge("mastra-icon", className),
|
|
1366
|
-
children: [
|
|
1367
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1368
|
-
"path",
|
|
1369
|
-
{
|
|
1370
|
-
fillRule: "evenodd",
|
|
1371
|
-
clipRule: "evenodd",
|
|
1372
|
-
d: "M8.5 15C10.3565 15 12.137 14.2625 13.4497 12.9497C14.7625 11.637 15.5 9.85652 15.5 8C15.5 6.14348 14.7625 4.36301 13.4497 3.05025C12.137 1.7375 10.3565 1 8.5 1C6.64348 1 4.86301 1.7375 3.55025 3.05025C2.2375 4.36301 1.5 6.14348 1.5 8C1.5 9.85652 2.2375 11.637 3.55025 12.9497C4.86301 14.2625 6.64348 15 8.5 15ZM5.621 10.879L4.611 11.889C3.84179 11.1198 3.31794 10.1398 3.1057 9.07291C2.89346 8.00601 3.00236 6.90013 3.41864 5.89512C3.83491 4.89012 4.53986 4.03112 5.44434 3.42676C6.34881 2.8224 7.41219 2.49983 8.5 2.49983C9.58781 2.49983 10.6512 2.8224 11.5557 3.42676C12.4601 4.03112 13.1651 4.89012 13.5814 5.89512C13.9976 6.90013 14.1065 8.00601 13.8943 9.07291C13.6821 10.1398 13.1582 11.1198 12.389 11.889L11.379 10.879C11.1004 10.6003 10.7696 10.3792 10.4055 10.2284C10.0414 10.0776 9.6511 9.99995 9.257 10H7.743C7.3489 9.99995 6.95865 10.0776 6.59455 10.2284C6.23045 10.3792 5.89963 10.6003 5.621 10.879Z",
|
|
1373
|
-
fill: "currentColor"
|
|
1374
|
-
}
|
|
1375
|
-
),
|
|
1376
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1377
|
-
"path",
|
|
1378
|
-
{
|
|
1379
|
-
d: "M8.5 4C7.96957 4 7.46086 4.21071 7.08579 4.58579C6.71071 4.96086 6.5 5.46957 6.5 6V6.5C6.5 7.03043 6.71071 7.53914 7.08579 7.91421C7.46086 8.28929 7.96957 8.5 8.5 8.5C9.03043 8.5 9.53914 8.28929 9.91421 7.91421C10.2893 7.53914 10.5 7.03043 10.5 6.5V6C10.5 5.46957 10.2893 4.96086 9.91421 4.58579C9.53914 4.21071 9.03043 4 8.5 4Z",
|
|
1380
|
-
fill: "currentColor"
|
|
1381
|
-
}
|
|
1382
|
-
)
|
|
1383
|
-
]
|
|
1384
|
-
}
|
|
1385
|
-
);
|
|
1386
|
-
|
|
1387
|
-
const ToolsIcon = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1388
|
-
"svg",
|
|
1389
|
-
{
|
|
1390
|
-
width: "17",
|
|
1391
|
-
height: "16",
|
|
1392
|
-
viewBox: "0 0 17 16",
|
|
1393
|
-
fill: "none",
|
|
1394
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
1395
|
-
...props,
|
|
1396
|
-
className: tailwindMerge.twMerge("mastra-icon", className),
|
|
1397
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1398
|
-
"path",
|
|
1399
|
-
{
|
|
1400
|
-
fillRule: "evenodd",
|
|
1401
|
-
clipRule: "evenodd",
|
|
1402
|
-
d: "M7.5605 1.42351C8.0791 0.904904 8.92215 0.906157 9.4395 1.42351L10.6922 2.67617C11.2108 3.19477 11.2095 4.03782 10.6922 4.55517L9.4395 5.80783C8.9209 6.32643 8.07785 6.32518 7.5605 5.80783L6.30784 4.55517C5.78923 4.03656 5.79049 3.19352 6.30784 2.67617L7.5605 1.42351ZM3.17618 5.80783C3.69478 5.28923 4.53782 5.29048 5.05517 5.80783L6.30784 7.0605C6.82644 7.5791 6.82519 8.42214 6.30784 8.93949L5.05517 10.1922C4.53657 10.7108 3.69353 10.7095 3.17618 10.1922L1.92351 8.93949C1.40491 8.42089 1.40616 7.57785 1.92351 7.0605L3.17618 5.80783ZM11.9448 5.80783C12.4634 5.28923 13.3065 5.29048 13.8238 5.80783L15.0765 7.0605C15.5951 7.5791 15.5938 8.42214 15.0765 8.93949L13.8238 10.1922C13.3052 10.7108 12.4622 10.7095 11.9448 10.1922L10.6922 8.93949C10.1736 8.42089 10.1748 7.57785 10.6922 7.0605L11.9448 5.80783ZM7.5605 10.1922C8.0791 9.67355 8.92215 9.67481 9.4395 10.1922L10.6922 11.4448C11.2108 11.9634 11.2095 12.8065 10.6922 13.3238L9.4395 14.5765C8.9209 15.0951 8.07785 15.0938 7.5605 14.5765L6.30784 13.3238C5.78923 12.8052 5.79049 11.9622 6.30784 11.4448L7.5605 10.1922Z",
|
|
1403
|
-
fill: "currentColor"
|
|
1404
|
-
}
|
|
1405
|
-
)
|
|
1406
|
-
}
|
|
1407
|
-
);
|
|
1408
|
-
|
|
1409
|
-
const WorkflowIcon = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1410
|
-
"svg",
|
|
1411
|
-
{
|
|
1412
|
-
width: "17",
|
|
1413
|
-
height: "16",
|
|
1414
|
-
viewBox: "0 0 17 16",
|
|
1415
|
-
fill: "none",
|
|
1416
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
1417
|
-
...props,
|
|
1418
|
-
className: tailwindMerge.twMerge("mastra-icon", className),
|
|
1419
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1420
|
-
"path",
|
|
1421
|
-
{
|
|
1422
|
-
fillRule: "evenodd",
|
|
1423
|
-
clipRule: "evenodd",
|
|
1424
|
-
d: "M6.24388 2.4018C6.24388 2.0394 6.53767 1.74561 6.90008 1.74561H10.0991C10.4614 1.74561 10.7553 2.0394 10.7553 2.4018V4.57546C10.7553 4.93787 10.4614 5.23166 10.0991 5.23166H9.31982V7.35469L10.0033 9.22664C9.90442 9.20146 9.80035 9.1761 9.6915 9.14986L9.62652 9.13422C9.30473 9.05687 8.92256 8.96501 8.61993 8.84491C8.5819 8.82981 8.54147 8.81292 8.49957 8.79391C8.45767 8.81292 8.41724 8.82981 8.3792 8.84491C8.07657 8.96501 7.6944 9.05687 7.37261 9.13422L7.30763 9.14986C7.19879 9.1761 7.09471 9.20146 6.99577 9.22664L7.67932 7.35469V5.23166H6.90008C6.53767 5.23166 6.24388 4.93787 6.24388 4.57546V2.4018ZM6.99577 9.22664C6.99577 9.22664 6.99578 9.22664 6.99577 9.22664L6.43283 10.7683H6.81806C7.18047 10.7683 7.47426 11.0622 7.47426 11.4245V13.5982C7.47426 13.9606 7.18047 14.2544 6.81806 14.2544H3.61909C3.25668 14.2544 2.96289 13.9606 2.96289 13.5982V11.4245C2.96289 11.0622 3.25668 10.7683 3.61909 10.7683H4.26617C4.2921 10.4663 4.32783 10.1494 4.37744 9.85171C4.43762 9.49063 4.52982 9.08135 4.68998 8.76102C4.93975 8.2615 5.44743 8.01751 5.7771 7.88788C6.14684 7.74249 6.57537 7.63889 6.92317 7.55505C7.24707 7.47696 7.49576 7.41679 7.67932 7.35469L6.99577 9.22664ZM6.43283 10.7683L6.99577 9.22664C6.75846 9.28705 6.55067 9.34646 6.37745 9.41458C6.22784 9.47341 6.1623 9.51712 6.14023 9.53254C6.09752 9.63631 6.04409 9.83055 5.99562 10.1214C5.96201 10.3231 5.93498 10.5439 5.91341 10.7683H6.43283ZM10.0033 9.22664L9.31982 7.35469C9.50338 7.41679 9.75206 7.47696 10.076 7.55505C10.4238 7.63889 10.8523 7.74249 11.2221 7.88788C11.5517 8.01751 12.0594 8.2615 12.3091 8.76102C12.4693 9.08135 12.5615 9.49063 12.6217 9.85171C12.6713 10.1494 12.707 10.4663 12.733 10.7683H13.38C13.7424 10.7683 14.0362 11.0622 14.0362 11.4245V13.5982C14.0362 13.9606 13.7424 14.2544 13.38 14.2544H10.1811C9.81867 14.2544 9.52488 13.9606 9.52488 13.5982V11.4245C9.52488 11.0622 9.81867 10.7683 10.1811 10.7683H10.5663L10.0033 9.22664ZM10.0033 9.22664L10.5663 10.7683H11.0857C11.0642 10.5439 11.0372 10.3231 11.0035 10.1214C10.9551 9.83055 10.9016 9.63631 10.8589 9.53254C10.8369 9.51712 10.7713 9.47341 10.6217 9.41458C10.4485 9.34646 10.2407 9.28705 10.0033 9.22664Z",
|
|
1425
|
-
fill: "currentColor"
|
|
1426
|
-
}
|
|
1427
|
-
)
|
|
1428
|
-
}
|
|
1429
|
-
);
|
|
1430
|
-
|
|
1431
|
-
const MessageClass = "mastra:flex mastra:flex-col mastra:w-full mastra:py-4 mastra:gap-2 mastra:group";
|
|
1432
|
-
const Message = ({ position, className, children, ...props }) => {
|
|
1433
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1434
|
-
"div",
|
|
1435
|
-
{
|
|
1436
|
-
className: className || tailwindMerge.twMerge(
|
|
1437
|
-
MessageClass,
|
|
1438
|
-
position === "left" ? "" : "mastra:items-end mastra:[&_.mastra-message-content]:bg-surface4 mastra:[&_.mastra-message-content]:px-4"
|
|
1439
|
-
),
|
|
1440
|
-
...props,
|
|
1441
|
-
children
|
|
1442
|
-
}
|
|
1443
|
-
);
|
|
1721
|
+
const extractTasksFromSignalChunk = (chunk) => {
|
|
1722
|
+
if (!isDataChunk$1(chunk)) return void 0;
|
|
1723
|
+
return extractTasksFromSignalData(chunk.data);
|
|
1444
1724
|
};
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
] });
|
|
1725
|
+
const extractTasksFromToolResultChunk = (chunk) => {
|
|
1726
|
+
if (chunk.type !== "tool-result") return void 0;
|
|
1727
|
+
const payload = chunk.payload;
|
|
1728
|
+
if (!isRecord$1(payload) || typeof payload.toolName !== "string" || !TASK_TOOL_NAMES.has(payload.toolName)) return;
|
|
1729
|
+
return parseTasksFromResult(payload.result);
|
|
1451
1730
|
};
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1731
|
+
const extractTasksFromToolInvocationPart = (part) => {
|
|
1732
|
+
if (!isRecord$1(part) || part.type !== "tool-invocation" || !isRecord$1(part.toolInvocation)) return void 0;
|
|
1733
|
+
const toolInvocation = part.toolInvocation;
|
|
1734
|
+
if (typeof toolInvocation.toolName !== "string" || !TASK_TOOL_NAMES.has(toolInvocation.toolName)) return void 0;
|
|
1735
|
+
return parseTasksFromResult(toolInvocation.result);
|
|
1455
1736
|
};
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1737
|
+
const extractTasksFromDataPart = (part) => {
|
|
1738
|
+
if (!isRecord$1(part) || typeof part.type !== "string" || !part.type.startsWith("data-")) return void 0;
|
|
1739
|
+
return extractTasksFromSignalData(part.data);
|
|
1459
1740
|
};
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1741
|
+
const extractTasksFromSignalMessage = (message) => {
|
|
1742
|
+
if (message.role !== "signal") return void 0;
|
|
1743
|
+
const metadata = message.content?.metadata;
|
|
1744
|
+
if (!isRecord$1(metadata)) return void 0;
|
|
1745
|
+
return extractTasksFromSignalData(metadata.signal);
|
|
1463
1746
|
};
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1747
|
+
const extractLatestTasksFromMessages = (messages) => {
|
|
1748
|
+
let latest;
|
|
1749
|
+
for (const message of messages) {
|
|
1750
|
+
for (const part of message.content?.parts ?? []) {
|
|
1751
|
+
const toolTasks = extractTasksFromToolInvocationPart(part);
|
|
1752
|
+
if (toolTasks !== void 0) latest = toolTasks;
|
|
1753
|
+
}
|
|
1754
|
+
const signalTasks = extractTasksFromSignalMessage(message);
|
|
1755
|
+
if (signalTasks !== void 0) latest = signalTasks;
|
|
1756
|
+
for (const part of message.content?.parts ?? []) {
|
|
1757
|
+
const dataTasks = extractTasksFromDataPart(part);
|
|
1758
|
+
if (dataTasks !== void 0) latest = dataTasks;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return latest ?? [];
|
|
1467
1762
|
};
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1763
|
+
//#endregion
|
|
1764
|
+
//#region src/agent/extractRunIdFromMessages.ts
|
|
1765
|
+
const isRecord = (value) => value !== null && typeof value === "object";
|
|
1766
|
+
const runIdMetadataKeys = [
|
|
1767
|
+
"pendingToolApprovals",
|
|
1768
|
+
"requireApprovalMetadata",
|
|
1769
|
+
"suspendedTools"
|
|
1770
|
+
];
|
|
1771
|
+
const isRunIdMetadataSource = (value) => isRecord(value) && Object.values(value).every((entry) => isRecord(entry));
|
|
1772
|
+
const getRunIdMetadataSources = (metadata) => {
|
|
1773
|
+
if (!isRecord(metadata)) return [];
|
|
1774
|
+
const sources = [];
|
|
1775
|
+
for (const key of runIdMetadataKeys) {
|
|
1776
|
+
const source = metadata[key];
|
|
1777
|
+
if (isRunIdMetadataSource(source)) sources.push(source);
|
|
1778
|
+
}
|
|
1779
|
+
return sources;
|
|
1471
1780
|
};
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
});
|
|
1482
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: className || MessageListClass, ...props, ref: listRef, children });
|
|
1781
|
+
/**
|
|
1782
|
+
* Scan initial DB-shape messages for any pending approvals, suspended tools, or
|
|
1783
|
+
* `requireApprovalMetadata` entries and return the first non-empty `runId`.
|
|
1784
|
+
*
|
|
1785
|
+
* Metadata is read off `message.content.metadata`, the canonical location for
|
|
1786
|
+
* MastraDBMessage UX hints.
|
|
1787
|
+
*/
|
|
1788
|
+
const extractRunIdFromMessages = (messages) => {
|
|
1789
|
+
for (const message of messages) for (const source of getRunIdMetadataSources(message.content?.metadata)) for (const entry of Object.values(source)) if (isRecord(entry) && typeof entry.runId === "string" && entry.runId.length > 0) return entry.runId;
|
|
1483
1790
|
};
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1791
|
+
//#endregion
|
|
1792
|
+
//#region src/agent/hooks.ts
|
|
1793
|
+
const extractPendingToolApprovalIdsFromMessages = (messages) => {
|
|
1794
|
+
const pendingToolApprovalIds = /* @__PURE__ */ new Set();
|
|
1795
|
+
for (const message of messages) {
|
|
1796
|
+
const metadata = message.content?.metadata;
|
|
1797
|
+
if (!metadata) continue;
|
|
1798
|
+
const metadataSources = [
|
|
1799
|
+
metadata.pendingToolApprovals,
|
|
1800
|
+
metadata.requireApprovalMetadata,
|
|
1801
|
+
metadata.suspendedTools
|
|
1802
|
+
];
|
|
1803
|
+
for (const source of metadataSources) {
|
|
1804
|
+
if (!source || typeof source !== "object") continue;
|
|
1805
|
+
for (const suspensionData of Object.values(source)) {
|
|
1806
|
+
const toolCallId = suspensionData?.toolCallId;
|
|
1807
|
+
if (typeof toolCallId === "string" && toolCallId.length > 0) pendingToolApprovalIds.add(toolCallId);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return pendingToolApprovalIds;
|
|
1487
1812
|
};
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1813
|
+
const toolCallHasOutput = (parts, toolCallId) => parts.some((part) => {
|
|
1814
|
+
if (part.type !== "tool-invocation") return false;
|
|
1815
|
+
const invocation = part.toolInvocation;
|
|
1816
|
+
if (invocation.toolCallId !== toolCallId) return false;
|
|
1817
|
+
return invocation.state === "result" || invocation.result != null;
|
|
1818
|
+
});
|
|
1819
|
+
/**
|
|
1820
|
+
* Normalize persisted initial messages back into the stream-friendly shape the
|
|
1821
|
+
* UI renders from. Mirrors `main`'s `resolveInitialMessages`:
|
|
1822
|
+
*
|
|
1823
|
+
* - Converts persisted `pendingToolApprovals` (DB shape) into
|
|
1824
|
+
* `requireApprovalMetadata` (stream shape) so reloaded threads still render
|
|
1825
|
+
* approve/decline buttons, filtering out approvals whose tool already
|
|
1826
|
+
* produced output, and marks the message `mode: 'stream'`.
|
|
1827
|
+
* - Drops assistant completion messages flagged `suppressFeedback`, which are
|
|
1828
|
+
* persisted by the supervisor agent but must stay hidden on reload.
|
|
1829
|
+
*/
|
|
1830
|
+
const resolveInitialMessages = (messages) => messages.filter((message) => {
|
|
1831
|
+
const metadata = message.content?.metadata;
|
|
1832
|
+
if (metadata?.completionResult?.suppressFeedback || metadata?.isTaskCompleteResult?.suppressFeedback) return false;
|
|
1833
|
+
return true;
|
|
1834
|
+
}).map((message) => {
|
|
1835
|
+
const metadata = message.content?.metadata;
|
|
1836
|
+
const normalizedMessage = metadata && (metadata.status === "pending" || "clientMessageId" in metadata) ? (() => {
|
|
1837
|
+
const { [CLIENT_MESSAGE_ID_KEY]: _omitClientMessageId, ...rest } = metadata;
|
|
1838
|
+
const { status: _omitStatus, ...restWithoutStatus } = rest;
|
|
1839
|
+
return {
|
|
1840
|
+
...message,
|
|
1841
|
+
content: {
|
|
1842
|
+
...message.content,
|
|
1843
|
+
metadata: metadata.status === "pending" ? restWithoutStatus : rest
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
})() : message;
|
|
1847
|
+
const normalizedMetadata = normalizedMessage.content?.metadata;
|
|
1848
|
+
const pendingToolApprovals = normalizedMetadata?.pendingToolApprovals;
|
|
1849
|
+
if (!pendingToolApprovals || typeof pendingToolApprovals !== "object") return normalizedMessage;
|
|
1850
|
+
const stillPending = Object.fromEntries(Object.entries(pendingToolApprovals).filter(([, approval]) => approval && typeof approval === "object" && typeof approval.toolCallId === "string" && !toolCallHasOutput(normalizedMessage.content.parts, approval.toolCallId)));
|
|
1851
|
+
const { pendingToolApprovals: _omit, ...restMetadata } = normalizedMetadata;
|
|
1852
|
+
const hasStillPending = Object.keys(stillPending).length > 0;
|
|
1853
|
+
return {
|
|
1854
|
+
...normalizedMessage,
|
|
1855
|
+
content: {
|
|
1856
|
+
...normalizedMessage.content,
|
|
1857
|
+
metadata: {
|
|
1858
|
+
...restMetadata,
|
|
1859
|
+
mode: "stream",
|
|
1860
|
+
...hasStillPending ? {
|
|
1861
|
+
pendingToolApprovals: stillPending,
|
|
1862
|
+
requireApprovalMetadata: stillPending
|
|
1863
|
+
} : {}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
});
|
|
1868
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
1869
|
+
const getErrorName = (error) => isObject(error) && typeof error.name === "string" ? error.name : void 0;
|
|
1870
|
+
const isAbortError = (error) => getErrorName(error) === "AbortError";
|
|
1871
|
+
const isThreadSignalUnsupportedError = (error) => {
|
|
1872
|
+
if (!isObject(error)) return false;
|
|
1873
|
+
const status = error.status;
|
|
1874
|
+
if (status === 404 || status === 405 || status === 501) return true;
|
|
1875
|
+
return status === 400 && typeof error.message === "string" && error.message.includes("No active agent run found for signal target");
|
|
1876
|
+
};
|
|
1877
|
+
const isDataChunk = (chunk) => typeof chunk.type === "string" && chunk.type.startsWith("data-");
|
|
1878
|
+
/**
|
|
1879
|
+
* Convert AI-SDK v5 UIMessages returned by the server (generate mode) into
|
|
1880
|
+
* `MastraDBMessage[]`, stamping the supplied metadata onto each message's
|
|
1881
|
+
* `content.metadata`. Private helper — `useChat` never exposes the AI-SDK
|
|
1882
|
+
* shape to consumers.
|
|
1883
|
+
*/
|
|
1884
|
+
const dbFromServerUiMessages = (uiMessages, metadata) => uiMessages.map((uiMsg) => {
|
|
1885
|
+
const dbMsg = _mastra_core_agent_message_list.AIV5Adapter.fromUIMessage(uiMsg);
|
|
1886
|
+
return {
|
|
1887
|
+
...dbMsg,
|
|
1888
|
+
content: {
|
|
1889
|
+
...dbMsg.content,
|
|
1890
|
+
metadata: {
|
|
1891
|
+
...dbMsg.content.metadata ?? {},
|
|
1892
|
+
...metadata
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
});
|
|
1897
|
+
const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContext: propsRequestContext, clientTools: hookClientTools, onSignalSent, onSignalEcho, onThreadSignalsUnsupported, enableThreadSignals = false }) => {
|
|
1898
|
+
const threadSignalsDisabled = enableThreadSignals === false;
|
|
1899
|
+
const _currentRunId = (0, react.useRef)(void 0);
|
|
1900
|
+
const _onChunk = (0, react.useRef)(void 0);
|
|
1901
|
+
const _networkRunId = (0, react.useRef)(void 0);
|
|
1902
|
+
const _onNetworkChunk = (0, react.useRef)(void 0);
|
|
1903
|
+
const _activeContinuation = (0, react.useRef)({ requestContext: propsRequestContext });
|
|
1904
|
+
const _streamAbortRef = (0, react.useRef)(null);
|
|
1905
|
+
const _threadSubscriptionAbortRef = (0, react.useRef)(null);
|
|
1906
|
+
const _threadSubscriptionRef = (0, react.useRef)(null);
|
|
1907
|
+
const _threadSubscriptionKeyRef = (0, react.useRef)(void 0);
|
|
1908
|
+
const _threadSubscriptionPromiseRef = (0, react.useRef)(null);
|
|
1909
|
+
const _threadSignalsUnsupportedRef = (0, react.useRef)(false);
|
|
1910
|
+
const [messages, setMessages] = (0, react.useState)([]);
|
|
1911
|
+
const [tasks, setTasks] = (0, react.useState)([]);
|
|
1912
|
+
const [toolCallApprovals, setToolCallApprovals] = (0, react.useState)({});
|
|
1913
|
+
const [networkToolCallApprovals, setNetworkToolCallApprovals] = (0, react.useState)({});
|
|
1914
|
+
const pendingToolApprovalIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1915
|
+
const [isAwaitingToolApproval, setIsAwaitingToolApproval] = (0, react.useState)(false);
|
|
1916
|
+
const baseClient = useMastraClient();
|
|
1917
|
+
const [isRunning, setIsRunning] = (0, react.useState)(false);
|
|
1918
|
+
(0, react.useEffect)(() => {
|
|
1919
|
+
const formattedMessages = resolveInitialMessages(initialMessages ?? []);
|
|
1920
|
+
setMessages(formattedMessages);
|
|
1921
|
+
setTasks(extractLatestTasksFromMessages(formattedMessages));
|
|
1922
|
+
pendingToolApprovalIdsRef.current = extractPendingToolApprovalIdsFromMessages(formattedMessages);
|
|
1923
|
+
setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
|
|
1924
|
+
_currentRunId.current = extractRunIdFromMessages(formattedMessages);
|
|
1925
|
+
}, [initialMessages]);
|
|
1926
|
+
(0, react.useEffect)(() => {
|
|
1927
|
+
_activeContinuation.current = {
|
|
1928
|
+
..._activeContinuation.current,
|
|
1929
|
+
requestContext: propsRequestContext
|
|
1930
|
+
};
|
|
1931
|
+
}, [propsRequestContext]);
|
|
1932
|
+
const normalizeSignalFileData = (data) => {
|
|
1933
|
+
if (data instanceof URL) return data.toString();
|
|
1934
|
+
return convertSignalDataToBase64String(data);
|
|
1935
|
+
};
|
|
1936
|
+
const getSignalContents = (coreUserMessages) => {
|
|
1937
|
+
const parts = coreUserMessages.reduce((allParts, message) => {
|
|
1938
|
+
if (typeof message.content === "string") {
|
|
1939
|
+
allParts.push({
|
|
1940
|
+
type: "text",
|
|
1941
|
+
text: message.content
|
|
1942
|
+
});
|
|
1943
|
+
return allParts;
|
|
1944
|
+
}
|
|
1945
|
+
for (const part of message.content) if (part.type === "text") allParts.push({
|
|
1946
|
+
type: "text",
|
|
1947
|
+
text: part.text
|
|
1948
|
+
});
|
|
1949
|
+
else if (part.type === "file") allParts.push({
|
|
1950
|
+
type: "file",
|
|
1951
|
+
data: normalizeSignalFileData(part.data),
|
|
1952
|
+
mediaType: part.mimeType,
|
|
1953
|
+
...part.filename ? { filename: part.filename } : {}
|
|
1954
|
+
});
|
|
1955
|
+
else if (part.type === "image") allParts.push({
|
|
1956
|
+
type: "file",
|
|
1957
|
+
data: normalizeSignalFileData(part.image),
|
|
1958
|
+
mediaType: part.mimeType ?? "image/png"
|
|
1959
|
+
});
|
|
1960
|
+
return allParts;
|
|
1961
|
+
}, []);
|
|
1962
|
+
return parts.length === 1 && parts[0]?.type === "text" ? parts[0].text : parts;
|
|
1963
|
+
};
|
|
1964
|
+
const markThreadSignalsUnsupported = (0, react.useCallback)(() => {
|
|
1965
|
+
_threadSignalsUnsupportedRef.current = true;
|
|
1966
|
+
onThreadSignalsUnsupported?.();
|
|
1967
|
+
}, [onThreadSignalsUnsupported]);
|
|
1968
|
+
const getSignalPreview = (coreUserMessages) => {
|
|
1969
|
+
return coreUserMessages.flatMap((message) => {
|
|
1970
|
+
if (typeof message.content === "string") return [message.content];
|
|
1971
|
+
return message.content.map((part) => {
|
|
1972
|
+
if (part.type === "text") return part.text;
|
|
1973
|
+
if (part.type === "image") return "Image";
|
|
1974
|
+
return part.filename ? `File: ${part.filename}` : "File";
|
|
1975
|
+
});
|
|
1976
|
+
}).join(" ").replace(/\s+/g, " ").trim() || "Attachment";
|
|
1977
|
+
};
|
|
1978
|
+
const closeThreadSubscription = (0, react.useCallback)(() => {
|
|
1979
|
+
const subscription = _threadSubscriptionRef.current;
|
|
1980
|
+
if (subscription?.unsubscribe) subscription.unsubscribe();
|
|
1981
|
+
else _threadSubscriptionAbortRef.current?.abort();
|
|
1982
|
+
_threadSubscriptionRef.current = null;
|
|
1983
|
+
_threadSubscriptionAbortRef.current = null;
|
|
1984
|
+
_threadSubscriptionKeyRef.current = void 0;
|
|
1985
|
+
_threadSubscriptionPromiseRef.current = null;
|
|
1986
|
+
}, []);
|
|
1987
|
+
const processStreamChunk = (0, react.useCallback)(async (chunk, onChunk) => {
|
|
1988
|
+
setMessages((prev) => accumulateChunk({
|
|
1989
|
+
chunk,
|
|
1990
|
+
conversation: prev,
|
|
1991
|
+
metadata: { mode: "stream" }
|
|
1992
|
+
}));
|
|
1993
|
+
const signalTasks = extractTasksFromSignalChunk(chunk);
|
|
1994
|
+
if (signalTasks !== void 0) setTasks(signalTasks);
|
|
1995
|
+
const toolTasks = extractTasksFromToolResultChunk(chunk);
|
|
1996
|
+
if (toolTasks !== void 0) setTasks(toolTasks);
|
|
1997
|
+
if (chunk.type === "data-user-message" && isDataChunk(chunk) && (chunk.data?.type === "user-message" || chunk.data?.type === "user") && typeof chunk.data?.id === "string") onSignalEcho?.(chunk.data.id);
|
|
1998
|
+
if (chunk.type === "start") {
|
|
1999
|
+
setIsRunning(true);
|
|
2000
|
+
if ("runId" in chunk && typeof chunk.runId === "string") _currentRunId.current = chunk.runId;
|
|
2001
|
+
}
|
|
2002
|
+
if (chunk.type === "tool-call-approval" || chunk.type === "tool-call-suspended") {
|
|
2003
|
+
const toolCallId = chunk.payload?.toolCallId;
|
|
2004
|
+
if (typeof toolCallId === "string") {
|
|
2005
|
+
pendingToolApprovalIdsRef.current.add(toolCallId);
|
|
2006
|
+
setIsAwaitingToolApproval(true);
|
|
2007
|
+
}
|
|
2008
|
+
setIsRunning(false);
|
|
2009
|
+
}
|
|
2010
|
+
if (chunk.type === "finish" || chunk.type === "abort" || chunk.type === "error") {
|
|
2011
|
+
pendingToolApprovalIdsRef.current.clear();
|
|
2012
|
+
setIsAwaitingToolApproval(false);
|
|
2013
|
+
setIsRunning(false);
|
|
2014
|
+
}
|
|
2015
|
+
(onChunk ?? _onChunk.current)?.(chunk);
|
|
2016
|
+
}, [onSignalEcho]);
|
|
2017
|
+
const ensureThreadSubscription = (0, react.useCallback)(async ({ threadId, resourceId }) => {
|
|
2018
|
+
const subscriptionKey = `${agentId}:${resourceId ?? ""}:${threadId}`;
|
|
2019
|
+
if (_threadSubscriptionKeyRef.current === subscriptionKey && _threadSubscriptionPromiseRef.current) {
|
|
2020
|
+
await _threadSubscriptionPromiseRef.current;
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
closeThreadSubscription();
|
|
2024
|
+
const subscriptionAbort = new AbortController();
|
|
2025
|
+
_threadSubscriptionAbortRef.current = subscriptionAbort;
|
|
2026
|
+
_threadSubscriptionKeyRef.current = subscriptionKey;
|
|
2027
|
+
const releaseSubscriptionRefs = () => {
|
|
2028
|
+
if (_threadSubscriptionAbortRef.current !== subscriptionAbort) return;
|
|
2029
|
+
_threadSubscriptionRef.current = null;
|
|
2030
|
+
_threadSubscriptionAbortRef.current = null;
|
|
2031
|
+
_threadSubscriptionKeyRef.current = void 0;
|
|
2032
|
+
_threadSubscriptionPromiseRef.current = null;
|
|
2033
|
+
};
|
|
2034
|
+
const subscriptionAgent = new _mastra_client_js.MastraClient({
|
|
2035
|
+
...baseClient.options,
|
|
2036
|
+
abortSignal: subscriptionAbort.signal
|
|
2037
|
+
}).getAgent(agentId);
|
|
2038
|
+
_threadSubscriptionPromiseRef.current = subscriptionAgent.subscribeToThread({
|
|
2039
|
+
resourceId,
|
|
2040
|
+
threadId
|
|
2041
|
+
}).then((response) => {
|
|
2042
|
+
const subscription = response;
|
|
2043
|
+
if (_threadSubscriptionAbortRef.current !== subscriptionAbort) {
|
|
2044
|
+
subscription.unsubscribe();
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
_threadSubscriptionRef.current = subscription;
|
|
2048
|
+
subscription.processDataStream({ onChunk: (chunk) => processStreamChunk(chunk) }).catch((error) => {
|
|
2049
|
+
if (!isAbortError(error)) {
|
|
2050
|
+
console.error("[useChat] Thread subscription failed", error);
|
|
2051
|
+
setIsRunning(false);
|
|
2052
|
+
}
|
|
2053
|
+
}).finally(() => {
|
|
2054
|
+
if (_threadSubscriptionRef.current === subscription) _threadSubscriptionRef.current = null;
|
|
2055
|
+
releaseSubscriptionRefs();
|
|
2056
|
+
});
|
|
2057
|
+
}).catch((error) => {
|
|
2058
|
+
releaseSubscriptionRefs();
|
|
2059
|
+
if (isThreadSignalUnsupportedError(error)) {
|
|
2060
|
+
markThreadSignalsUnsupported();
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (!isAbortError(error)) {
|
|
2064
|
+
console.error("[useChat] Thread subscription failed", error);
|
|
2065
|
+
setIsRunning(false);
|
|
2066
|
+
}
|
|
2067
|
+
throw error;
|
|
2068
|
+
});
|
|
2069
|
+
await _threadSubscriptionPromiseRef.current;
|
|
2070
|
+
}, [
|
|
2071
|
+
agentId,
|
|
2072
|
+
baseClient,
|
|
2073
|
+
closeThreadSubscription,
|
|
2074
|
+
markThreadSignalsUnsupported,
|
|
2075
|
+
processStreamChunk
|
|
2076
|
+
]);
|
|
2077
|
+
(0, react.useEffect)(() => {
|
|
2078
|
+
_threadSignalsUnsupportedRef.current = false;
|
|
2079
|
+
return closeThreadSubscription;
|
|
2080
|
+
}, [
|
|
2081
|
+
agentId,
|
|
2082
|
+
resourceId,
|
|
2083
|
+
threadId,
|
|
2084
|
+
closeThreadSubscription
|
|
2085
|
+
]);
|
|
2086
|
+
(0, react.useEffect)(() => {
|
|
2087
|
+
if (!threadId || threadSignalsDisabled) {
|
|
2088
|
+
closeThreadSubscription();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
ensureThreadSubscription({
|
|
2092
|
+
threadId,
|
|
2093
|
+
resourceId: resourceId || agentId
|
|
2094
|
+
}).catch((error) => {
|
|
2095
|
+
if (!isAbortError(error)) console.error("[useChat] Thread subscription failed", error);
|
|
2096
|
+
});
|
|
2097
|
+
}, [
|
|
2098
|
+
agentId,
|
|
2099
|
+
closeThreadSubscription,
|
|
2100
|
+
ensureThreadSubscription,
|
|
2101
|
+
resourceId,
|
|
2102
|
+
threadId,
|
|
2103
|
+
threadSignalsDisabled
|
|
2104
|
+
]);
|
|
2105
|
+
const generate = async ({ coreUserMessages, model, requestContext, threadId, modelSettings, signal, onFinish, tracingOptions, clientTools }) => {
|
|
2106
|
+
const { frequencyPenalty, presencePenalty, maxRetries, maxTokens, temperature, topK, topP, instructions, providerOptions, maxSteps, requireToolApproval } = modelSettings || {};
|
|
2107
|
+
const resolvedRequestContext = requestContext ?? propsRequestContext;
|
|
2108
|
+
const resolvedClientTools = clientTools ?? hookClientTools;
|
|
2109
|
+
_activeContinuation.current = {
|
|
2110
|
+
model,
|
|
2111
|
+
requestContext: resolvedRequestContext
|
|
2112
|
+
};
|
|
2113
|
+
setIsRunning(true);
|
|
2114
|
+
const agent = new _mastra_client_js.MastraClient({
|
|
2115
|
+
...baseClient.options,
|
|
2116
|
+
abortSignal: signal
|
|
2117
|
+
}).getAgent(agentId);
|
|
2118
|
+
const runId = (0, _lukeed_uuid.v4)();
|
|
2119
|
+
_currentRunId.current = runId;
|
|
2120
|
+
const response = await agent.generate(coreUserMessages, {
|
|
2121
|
+
model,
|
|
2122
|
+
runId,
|
|
2123
|
+
maxSteps,
|
|
2124
|
+
modelSettings: {
|
|
2125
|
+
frequencyPenalty,
|
|
2126
|
+
presencePenalty,
|
|
2127
|
+
maxRetries,
|
|
2128
|
+
maxOutputTokens: maxTokens,
|
|
2129
|
+
temperature,
|
|
2130
|
+
topK,
|
|
2131
|
+
topP
|
|
2132
|
+
},
|
|
2133
|
+
instructions,
|
|
2134
|
+
requestContext: resolvedRequestContext,
|
|
2135
|
+
...threadId ? { memory: {
|
|
2136
|
+
thread: threadId,
|
|
2137
|
+
resource: resourceId || agentId
|
|
2138
|
+
} } : {},
|
|
2139
|
+
providerOptions,
|
|
2140
|
+
tracingOptions,
|
|
2141
|
+
requireToolApproval,
|
|
2142
|
+
clientTools: resolvedClientTools
|
|
2143
|
+
});
|
|
2144
|
+
if (response.finishReason === "suspended" && response.suspendPayload) {
|
|
2145
|
+
const { toolCallId, toolName, args } = response.suspendPayload;
|
|
2146
|
+
if (response.response?.uiMessages) {
|
|
2147
|
+
const dbMessages = dbFromServerUiMessages(response.response.uiMessages, {
|
|
2148
|
+
mode: "generate",
|
|
2149
|
+
requireApprovalMetadata: { [toolName]: {
|
|
2150
|
+
toolCallId,
|
|
2151
|
+
toolName,
|
|
2152
|
+
args
|
|
2153
|
+
} }
|
|
2154
|
+
});
|
|
2155
|
+
setMessages((prev) => [...prev, ...dbMessages]);
|
|
2156
|
+
}
|
|
2157
|
+
setIsRunning(false);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
setIsRunning(false);
|
|
2161
|
+
if (response && "uiMessages" in response.response && response.response.uiMessages) {
|
|
2162
|
+
const dbMessages = dbFromServerUiMessages(response.response.uiMessages, { mode: "generate" });
|
|
2163
|
+
onFinish?.(dbMessages);
|
|
2164
|
+
setMessages((prev) => [...prev, ...dbMessages]);
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
const stream = async ({ coreUserMessages, model, requestContext, threadId, onChunk, modelSettings, signal, tracingOptions, clientTools, signalId, clientMessageId }) => {
|
|
2168
|
+
const { frequencyPenalty, presencePenalty, maxRetries, maxTokens, temperature, topK, topP, instructions, providerOptions, maxSteps, requireToolApproval } = modelSettings || {};
|
|
2169
|
+
const resolvedRequestContext = requestContext ?? propsRequestContext;
|
|
2170
|
+
const resolvedClientTools = clientTools ?? hookClientTools;
|
|
2171
|
+
const signalContinuationOptions = {
|
|
2172
|
+
model,
|
|
2173
|
+
maxSteps,
|
|
2174
|
+
modelSettings: {
|
|
2175
|
+
frequencyPenalty,
|
|
2176
|
+
presencePenalty,
|
|
2177
|
+
maxRetries,
|
|
2178
|
+
maxOutputTokens: maxTokens,
|
|
2179
|
+
temperature,
|
|
2180
|
+
topK,
|
|
2181
|
+
topP
|
|
2182
|
+
},
|
|
2183
|
+
instructions,
|
|
2184
|
+
providerOptions,
|
|
2185
|
+
requireToolApproval,
|
|
2186
|
+
tracingOptions
|
|
2187
|
+
};
|
|
2188
|
+
_activeContinuation.current = {
|
|
2189
|
+
model,
|
|
2190
|
+
requestContext: resolvedRequestContext
|
|
2191
|
+
};
|
|
2192
|
+
setIsRunning(true);
|
|
2193
|
+
_streamAbortRef.current?.abort();
|
|
2194
|
+
const internalAbort = new AbortController();
|
|
2195
|
+
_streamAbortRef.current = internalAbort;
|
|
2196
|
+
if (signal) if (signal.aborted) internalAbort.abort();
|
|
2197
|
+
else signal.addEventListener("abort", () => internalAbort.abort(), { once: true });
|
|
2198
|
+
const agent = new _mastra_client_js.MastraClient({
|
|
2199
|
+
...baseClient.options,
|
|
2200
|
+
abortSignal: internalAbort.signal
|
|
2201
|
+
}).getAgent(agentId);
|
|
2202
|
+
const streamWithLegacyRoute = async () => {
|
|
2203
|
+
const runId = (0, _lukeed_uuid.v4)();
|
|
2204
|
+
const response = await agent.stream(coreUserMessages, {
|
|
2205
|
+
model,
|
|
2206
|
+
runId,
|
|
2207
|
+
maxSteps,
|
|
2208
|
+
untilIdle: true,
|
|
2209
|
+
modelSettings: {
|
|
2210
|
+
frequencyPenalty,
|
|
2211
|
+
presencePenalty,
|
|
2212
|
+
maxRetries,
|
|
2213
|
+
maxOutputTokens: maxTokens,
|
|
2214
|
+
temperature,
|
|
2215
|
+
topK,
|
|
2216
|
+
topP
|
|
2217
|
+
},
|
|
2218
|
+
instructions,
|
|
2219
|
+
requestContext: resolvedRequestContext,
|
|
2220
|
+
...threadId ? { memory: {
|
|
2221
|
+
thread: threadId,
|
|
2222
|
+
resource: resourceId || agentId
|
|
2223
|
+
} } : {},
|
|
2224
|
+
providerOptions,
|
|
2225
|
+
requireToolApproval,
|
|
2226
|
+
tracingOptions,
|
|
2227
|
+
clientTools: resolvedClientTools
|
|
2228
|
+
});
|
|
2229
|
+
_onChunk.current = onChunk;
|
|
2230
|
+
_currentRunId.current = runId;
|
|
2231
|
+
await response.processDataStream({ onChunk: (chunk) => processStreamChunk(chunk, onChunk) });
|
|
2232
|
+
if (_streamAbortRef.current === internalAbort) _streamAbortRef.current = null;
|
|
2233
|
+
setIsRunning(false);
|
|
2234
|
+
};
|
|
2235
|
+
if (!threadId || _threadSignalsUnsupportedRef.current || threadSignalsDisabled) {
|
|
2236
|
+
await streamWithLegacyRoute();
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
_onChunk.current = onChunk;
|
|
2240
|
+
await ensureThreadSubscription({
|
|
2241
|
+
threadId,
|
|
2242
|
+
resourceId: resourceId || agentId
|
|
2243
|
+
});
|
|
2244
|
+
if (_threadSignalsUnsupportedRef.current) {
|
|
2245
|
+
await streamWithLegacyRoute();
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const resolvedSignalId = signalId ?? (0, _lukeed_uuid.v4)();
|
|
2249
|
+
const messageContents = getSignalContents(coreUserMessages);
|
|
2250
|
+
const requestContextRecord = resolvedRequestContext;
|
|
2251
|
+
const streamOptions = {
|
|
2252
|
+
model,
|
|
2253
|
+
maxSteps,
|
|
2254
|
+
modelSettings: {
|
|
2255
|
+
frequencyPenalty,
|
|
2256
|
+
presencePenalty,
|
|
2257
|
+
maxRetries,
|
|
2258
|
+
maxOutputTokens: maxTokens,
|
|
2259
|
+
temperature,
|
|
2260
|
+
topK,
|
|
2261
|
+
topP
|
|
2262
|
+
},
|
|
2263
|
+
instructions,
|
|
2264
|
+
requestContext: requestContextRecord,
|
|
2265
|
+
providerOptions,
|
|
2266
|
+
requireToolApproval,
|
|
2267
|
+
tracingOptions
|
|
2268
|
+
};
|
|
2269
|
+
try {
|
|
2270
|
+
const result = await agent.sendMessage({
|
|
2271
|
+
message: clientMessageId ? {
|
|
2272
|
+
contents: messageContents,
|
|
2273
|
+
metadata: { [CLIENT_MESSAGE_ID_KEY]: clientMessageId }
|
|
2274
|
+
} : messageContents,
|
|
2275
|
+
resourceId: resourceId || agentId,
|
|
2276
|
+
threadId,
|
|
2277
|
+
ifIdle: { streamOptions: {
|
|
2278
|
+
...signalContinuationOptions,
|
|
2279
|
+
requestContext: requestContextRecord,
|
|
2280
|
+
clientTools: resolvedClientTools
|
|
2281
|
+
} }
|
|
2282
|
+
});
|
|
2283
|
+
const echoedSignalId = result.signal && typeof result.signal === "object" && "id" in result.signal && typeof result.signal.id === "string" ? result.signal.id : resolvedSignalId;
|
|
2284
|
+
onSignalSent?.(echoedSignalId, getSignalPreview(coreUserMessages));
|
|
2285
|
+
if (pendingToolApprovalIdsRef.current.size > 0) setIsRunning(false);
|
|
2286
|
+
} catch (error) {
|
|
2287
|
+
if (isThreadSignalUnsupportedError(error)) {
|
|
2288
|
+
onSignalSent?.(resolvedSignalId, getSignalPreview(coreUserMessages));
|
|
2289
|
+
try {
|
|
2290
|
+
await agent.sendSignal({
|
|
2291
|
+
signal: {
|
|
2292
|
+
id: resolvedSignalId,
|
|
2293
|
+
type: "user-message",
|
|
2294
|
+
contents: messageContents
|
|
2295
|
+
},
|
|
2296
|
+
resourceId: resourceId || agentId,
|
|
2297
|
+
threadId,
|
|
2298
|
+
ifIdle: { streamOptions }
|
|
2299
|
+
});
|
|
2300
|
+
return;
|
|
2301
|
+
} catch (signalError) {
|
|
2302
|
+
onSignalEcho?.(resolvedSignalId);
|
|
2303
|
+
if (isThreadSignalUnsupportedError(signalError)) {
|
|
2304
|
+
markThreadSignalsUnsupported();
|
|
2305
|
+
setMessages((prev) => [...prev, fromCoreUserMessagesToMastraDBMessage(coreUserMessages)]);
|
|
2306
|
+
await streamWithLegacyRoute();
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
throw signalError;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
throw error;
|
|
2313
|
+
}
|
|
2314
|
+
if (_streamAbortRef.current === internalAbort) _streamAbortRef.current = null;
|
|
2315
|
+
};
|
|
2316
|
+
const network = async ({ coreUserMessages, model, requestContext, threadId, onNetworkChunk, modelSettings, signal, tracingOptions }) => {
|
|
2317
|
+
const { frequencyPenalty, presencePenalty, maxRetries, maxTokens, temperature, topK, topP, maxSteps } = modelSettings || {};
|
|
2318
|
+
const resolvedRequestContext = requestContext ?? propsRequestContext;
|
|
2319
|
+
_activeContinuation.current = {
|
|
2320
|
+
model,
|
|
2321
|
+
requestContext: resolvedRequestContext
|
|
2322
|
+
};
|
|
2323
|
+
setIsRunning(true);
|
|
2324
|
+
const agent = new _mastra_client_js.MastraClient({
|
|
2325
|
+
...baseClient.options,
|
|
2326
|
+
abortSignal: signal
|
|
2327
|
+
}).getAgent(agentId);
|
|
2328
|
+
const runId = (0, _lukeed_uuid.v4)();
|
|
2329
|
+
const response = await agent.network(coreUserMessages, {
|
|
2330
|
+
model,
|
|
2331
|
+
maxSteps,
|
|
2332
|
+
modelSettings: {
|
|
2333
|
+
frequencyPenalty,
|
|
2334
|
+
presencePenalty,
|
|
2335
|
+
maxRetries,
|
|
2336
|
+
maxOutputTokens: maxTokens,
|
|
2337
|
+
temperature,
|
|
2338
|
+
topK,
|
|
2339
|
+
topP
|
|
2340
|
+
},
|
|
2341
|
+
runId,
|
|
2342
|
+
requestContext: resolvedRequestContext,
|
|
2343
|
+
...threadId ? { memory: {
|
|
2344
|
+
thread: threadId,
|
|
2345
|
+
resource: resourceId || agentId
|
|
2346
|
+
} } : {},
|
|
2347
|
+
tracingOptions
|
|
2348
|
+
});
|
|
2349
|
+
_onNetworkChunk.current = onNetworkChunk;
|
|
2350
|
+
_networkRunId.current = runId;
|
|
2351
|
+
await response.processDataStream({ onChunk: async (chunk) => {
|
|
2352
|
+
setMessages((prev) => accumulateNetworkChunk({
|
|
2353
|
+
chunk,
|
|
2354
|
+
conversation: prev,
|
|
2355
|
+
metadata: { mode: "network" }
|
|
2356
|
+
}));
|
|
2357
|
+
onNetworkChunk?.(chunk);
|
|
2358
|
+
} });
|
|
2359
|
+
setMessages((prev) => finishStreamingAssistantMessage(prev));
|
|
2360
|
+
setIsRunning(false);
|
|
2361
|
+
};
|
|
2362
|
+
const handleCancelRun = () => {
|
|
2363
|
+
_streamAbortRef.current?.abort();
|
|
2364
|
+
_streamAbortRef.current = null;
|
|
2365
|
+
const threadSubscription = _threadSubscriptionRef.current;
|
|
2366
|
+
Promise.resolve(threadSubscription?.abort?.()).catch((error) => {
|
|
2367
|
+
console.error("[useChat] Failed to abort thread subscription", error);
|
|
2368
|
+
});
|
|
2369
|
+
closeThreadSubscription();
|
|
2370
|
+
setMessages((prev) => finishStreamingAssistantMessage(prev));
|
|
2371
|
+
pendingToolApprovalIdsRef.current.clear();
|
|
2372
|
+
setIsAwaitingToolApproval(false);
|
|
2373
|
+
setIsRunning(false);
|
|
2374
|
+
_currentRunId.current = void 0;
|
|
2375
|
+
_onChunk.current = void 0;
|
|
2376
|
+
_networkRunId.current = void 0;
|
|
2377
|
+
_onNetworkChunk.current = void 0;
|
|
2378
|
+
_activeContinuation.current = {};
|
|
2379
|
+
};
|
|
2380
|
+
const approveToolCall = async (toolCallId, resumeData) => {
|
|
2381
|
+
const onChunk = _onChunk.current;
|
|
2382
|
+
const currentRunId = _currentRunId.current;
|
|
2383
|
+
const continuation = _activeContinuation.current;
|
|
2384
|
+
if (!currentRunId) return console.info("[approveToolCall] approveToolCall can only be called after a stream has started");
|
|
2385
|
+
setIsRunning(true);
|
|
2386
|
+
setToolCallApprovals((prev) => ({
|
|
2387
|
+
...prev,
|
|
2388
|
+
[toolCallId]: { status: "approved" }
|
|
2389
|
+
}));
|
|
2390
|
+
const agent = baseClient.getAgent(agentId);
|
|
2391
|
+
if (_threadSubscriptionKeyRef.current && threadId) {
|
|
2392
|
+
try {
|
|
2393
|
+
await agent.sendToolApproval({
|
|
2394
|
+
resourceId: resourceId || agentId,
|
|
2395
|
+
threadId,
|
|
2396
|
+
toolCallId,
|
|
2397
|
+
approved: true,
|
|
2398
|
+
...continuation.model !== void 0 ? { streamOptions: { model: continuation.model } } : {},
|
|
2399
|
+
...resumeData !== void 0 ? { resumeData } : {},
|
|
2400
|
+
requestContext: continuation.requestContext
|
|
2401
|
+
});
|
|
2402
|
+
pendingToolApprovalIdsRef.current.delete(toolCallId);
|
|
2403
|
+
setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
|
|
2404
|
+
setIsRunning(false);
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
setToolCallApprovals((prev) => {
|
|
2407
|
+
const next = { ...prev };
|
|
2408
|
+
delete next[toolCallId];
|
|
2409
|
+
return next;
|
|
2410
|
+
});
|
|
2411
|
+
setIsRunning(false);
|
|
2412
|
+
throw error;
|
|
2413
|
+
}
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
await (await agent.approveToolCall({
|
|
2417
|
+
runId: currentRunId,
|
|
2418
|
+
toolCallId,
|
|
2419
|
+
...continuation
|
|
2420
|
+
})).processDataStream({ onChunk: async (chunk) => {
|
|
2421
|
+
await processStreamChunk(chunk, onChunk);
|
|
2422
|
+
} });
|
|
2423
|
+
setIsRunning(false);
|
|
2424
|
+
};
|
|
2425
|
+
const declineToolCall = async (toolCallId) => {
|
|
2426
|
+
const onChunk = _onChunk.current;
|
|
2427
|
+
const currentRunId = _currentRunId.current;
|
|
2428
|
+
const continuation = _activeContinuation.current;
|
|
2429
|
+
if (!currentRunId) return console.info("[declineToolCall] declineToolCall can only be called after a stream has started");
|
|
2430
|
+
setIsRunning(true);
|
|
2431
|
+
setToolCallApprovals((prev) => ({
|
|
2432
|
+
...prev,
|
|
2433
|
+
[toolCallId]: { status: "declined" }
|
|
2434
|
+
}));
|
|
2435
|
+
const agent = baseClient.getAgent(agentId);
|
|
2436
|
+
if (_threadSubscriptionKeyRef.current && threadId) {
|
|
2437
|
+
try {
|
|
2438
|
+
await agent.sendToolApproval({
|
|
2439
|
+
resourceId: resourceId || agentId,
|
|
2440
|
+
threadId,
|
|
2441
|
+
toolCallId,
|
|
2442
|
+
approved: false,
|
|
2443
|
+
...continuation.model !== void 0 ? { streamOptions: { model: continuation.model } } : {},
|
|
2444
|
+
requestContext: continuation.requestContext
|
|
2445
|
+
});
|
|
2446
|
+
pendingToolApprovalIdsRef.current.delete(toolCallId);
|
|
2447
|
+
setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
|
|
2448
|
+
setIsRunning(false);
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
setToolCallApprovals((prev) => {
|
|
2451
|
+
const next = { ...prev };
|
|
2452
|
+
delete next[toolCallId];
|
|
2453
|
+
return next;
|
|
2454
|
+
});
|
|
2455
|
+
setIsRunning(false);
|
|
2456
|
+
throw error;
|
|
2457
|
+
}
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
await (await agent.declineToolCall({
|
|
2461
|
+
runId: currentRunId,
|
|
2462
|
+
toolCallId,
|
|
2463
|
+
...continuation
|
|
2464
|
+
})).processDataStream({ onChunk: async (chunk) => {
|
|
2465
|
+
await processStreamChunk(chunk, onChunk);
|
|
2466
|
+
} });
|
|
2467
|
+
setIsRunning(false);
|
|
2468
|
+
};
|
|
2469
|
+
const approveToolCallGenerate = async (toolCallId) => {
|
|
2470
|
+
const currentRunId = _currentRunId.current;
|
|
2471
|
+
const continuation = _activeContinuation.current;
|
|
2472
|
+
if (!currentRunId) return console.info("[approveToolCallGenerate] approveToolCallGenerate can only be called after a generate has started");
|
|
2473
|
+
setIsRunning(true);
|
|
2474
|
+
setToolCallApprovals((prev) => ({
|
|
2475
|
+
...prev,
|
|
2476
|
+
[toolCallId]: { status: "approved" }
|
|
2477
|
+
}));
|
|
2478
|
+
const response = await baseClient.getAgent(agentId).approveToolCallGenerate({
|
|
2479
|
+
runId: currentRunId,
|
|
2480
|
+
toolCallId,
|
|
2481
|
+
...continuation
|
|
2482
|
+
});
|
|
2483
|
+
if (response && "uiMessages" in response.response && response.response.uiMessages) {
|
|
2484
|
+
const dbMessages = dbFromServerUiMessages(response.response.uiMessages, { mode: "generate" });
|
|
2485
|
+
setMessages((prev) => [...prev, ...dbMessages]);
|
|
2486
|
+
}
|
|
2487
|
+
setIsRunning(false);
|
|
2488
|
+
};
|
|
2489
|
+
const declineToolCallGenerate = async (toolCallId) => {
|
|
2490
|
+
const currentRunId = _currentRunId.current;
|
|
2491
|
+
const continuation = _activeContinuation.current;
|
|
2492
|
+
if (!currentRunId) return console.info("[declineToolCallGenerate] declineToolCallGenerate can only be called after a generate has started");
|
|
2493
|
+
setIsRunning(true);
|
|
2494
|
+
setToolCallApprovals((prev) => ({
|
|
2495
|
+
...prev,
|
|
2496
|
+
[toolCallId]: { status: "declined" }
|
|
2497
|
+
}));
|
|
2498
|
+
const response = await baseClient.getAgent(agentId).declineToolCallGenerate({
|
|
2499
|
+
runId: currentRunId,
|
|
2500
|
+
toolCallId,
|
|
2501
|
+
...continuation
|
|
2502
|
+
});
|
|
2503
|
+
if (response && "uiMessages" in response.response && response.response.uiMessages) {
|
|
2504
|
+
const dbMessages = dbFromServerUiMessages(response.response.uiMessages, { mode: "generate" });
|
|
2505
|
+
setMessages((prev) => [...prev, ...dbMessages]);
|
|
2506
|
+
}
|
|
2507
|
+
setIsRunning(false);
|
|
2508
|
+
};
|
|
2509
|
+
const approveNetworkToolCall = async (toolName, runId) => {
|
|
2510
|
+
const onNetworkChunk = _onNetworkChunk.current;
|
|
2511
|
+
const networkRunId = runId || _networkRunId.current;
|
|
2512
|
+
const continuation = _activeContinuation.current;
|
|
2513
|
+
if (!networkRunId) return console.info("[approveNetworkToolCall] approveNetworkToolCall can only be called after a network stream has started");
|
|
2514
|
+
setIsRunning(true);
|
|
2515
|
+
setNetworkToolCallApprovals((prev) => ({
|
|
2516
|
+
...prev,
|
|
2517
|
+
[runId ? `${runId}-${toolName}` : toolName]: { status: "approved" }
|
|
2518
|
+
}));
|
|
2519
|
+
await (await baseClient.getAgent(agentId).approveNetworkToolCall({
|
|
2520
|
+
runId: networkRunId,
|
|
2521
|
+
...continuation
|
|
2522
|
+
})).processDataStream({ onChunk: async (chunk) => {
|
|
2523
|
+
setMessages((prev) => accumulateNetworkChunk({
|
|
2524
|
+
chunk,
|
|
2525
|
+
conversation: prev,
|
|
2526
|
+
metadata: { mode: "network" }
|
|
2527
|
+
}));
|
|
2528
|
+
onNetworkChunk?.(chunk);
|
|
2529
|
+
} });
|
|
2530
|
+
setMessages((prev) => finishStreamingAssistantMessage(prev));
|
|
2531
|
+
setIsRunning(false);
|
|
2532
|
+
};
|
|
2533
|
+
const declineNetworkToolCall = async (toolName, runId) => {
|
|
2534
|
+
const onNetworkChunk = _onNetworkChunk.current;
|
|
2535
|
+
const networkRunId = runId || _networkRunId.current;
|
|
2536
|
+
const continuation = _activeContinuation.current;
|
|
2537
|
+
if (!networkRunId) return console.info("[declineNetworkToolCall] declineNetworkToolCall can only be called after a network stream has started");
|
|
2538
|
+
setIsRunning(true);
|
|
2539
|
+
setNetworkToolCallApprovals((prev) => ({
|
|
2540
|
+
...prev,
|
|
2541
|
+
[runId ? `${runId}-${toolName}` : toolName]: { status: "declined" }
|
|
2542
|
+
}));
|
|
2543
|
+
await (await baseClient.getAgent(agentId).declineNetworkToolCall({
|
|
2544
|
+
runId: networkRunId,
|
|
2545
|
+
...continuation
|
|
2546
|
+
})).processDataStream({ onChunk: async (chunk) => {
|
|
2547
|
+
setMessages((prev) => accumulateNetworkChunk({
|
|
2548
|
+
chunk,
|
|
2549
|
+
conversation: prev,
|
|
2550
|
+
metadata: { mode: "network" }
|
|
2551
|
+
}));
|
|
2552
|
+
onNetworkChunk?.(chunk);
|
|
2553
|
+
} });
|
|
2554
|
+
setMessages((prev) => finishStreamingAssistantMessage(prev));
|
|
2555
|
+
setIsRunning(false);
|
|
2556
|
+
};
|
|
2557
|
+
const sendMessage = async ({ mode = "stream", ...args }) => {
|
|
2558
|
+
const coreUserMessages = [{
|
|
2559
|
+
role: "user",
|
|
2560
|
+
content: [{
|
|
2561
|
+
type: "text",
|
|
2562
|
+
text: args.message
|
|
2563
|
+
}]
|
|
2564
|
+
}];
|
|
2565
|
+
if (args.coreUserMessages) coreUserMessages.push(...args.coreUserMessages);
|
|
2566
|
+
const dbUserMessage = fromCoreUserMessagesToMastraDBMessage(coreUserMessages);
|
|
2567
|
+
const clientSetId = mode === "stream" && args.threadId && !_threadSignalsUnsupportedRef.current && !threadSignalsDisabled ? `client-set-${(0, _lukeed_uuid.v4)()}` : void 0;
|
|
2568
|
+
const signalId = clientSetId;
|
|
2569
|
+
const clientMessageId = clientSetId;
|
|
2570
|
+
if (signalId) {
|
|
2571
|
+
const metadata = {
|
|
2572
|
+
...dbUserMessage.content.metadata,
|
|
2573
|
+
mode: "stream",
|
|
2574
|
+
status: "pending",
|
|
2575
|
+
[CLIENT_MESSAGE_ID_KEY]: clientMessageId
|
|
2576
|
+
};
|
|
2577
|
+
const pendingMessage = {
|
|
2578
|
+
...dbUserMessage,
|
|
2579
|
+
id: clientSetId,
|
|
2580
|
+
content: {
|
|
2581
|
+
...dbUserMessage.content,
|
|
2582
|
+
metadata
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
setMessages((s) => [...s, pendingMessage]);
|
|
2586
|
+
} else setMessages((s) => [...s, dbUserMessage]);
|
|
2587
|
+
try {
|
|
2588
|
+
if (mode === "generate") await generate({
|
|
2589
|
+
...args,
|
|
2590
|
+
coreUserMessages
|
|
2591
|
+
});
|
|
2592
|
+
else if (mode === "stream") await stream({
|
|
2593
|
+
...args,
|
|
2594
|
+
coreUserMessages,
|
|
2595
|
+
signalId,
|
|
2596
|
+
clientMessageId
|
|
2597
|
+
});
|
|
2598
|
+
else if (mode === "network") await network({
|
|
2599
|
+
...args,
|
|
2600
|
+
coreUserMessages
|
|
2601
|
+
});
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
setIsRunning(false);
|
|
2604
|
+
throw error;
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
return {
|
|
2608
|
+
setMessages,
|
|
2609
|
+
sendMessage,
|
|
2610
|
+
isRunning,
|
|
2611
|
+
isAwaitingToolApproval,
|
|
2612
|
+
messages,
|
|
2613
|
+
tasks,
|
|
2614
|
+
approveToolCall,
|
|
2615
|
+
declineToolCall,
|
|
2616
|
+
approveToolCallGenerate,
|
|
2617
|
+
declineToolCallGenerate,
|
|
2618
|
+
cancelRun: handleCancelRun,
|
|
2619
|
+
toolCallApprovals,
|
|
2620
|
+
approveNetworkToolCall,
|
|
2621
|
+
declineNetworkToolCall,
|
|
2622
|
+
networkToolCallApprovals
|
|
2623
|
+
};
|
|
2624
|
+
};
|
|
2625
|
+
//#endregion
|
|
2626
|
+
//#region src/lib/use-mutation.ts
|
|
2627
|
+
/**
|
|
2628
|
+
* Internal helper hook that provides mutation-like functionality without react-query.
|
|
2629
|
+
* Tracks pending, success, and error states for async operations.
|
|
2630
|
+
*/
|
|
2631
|
+
function useMutation(mutationFn) {
|
|
2632
|
+
const [isPending, setIsPending] = (0, react.useState)(false);
|
|
2633
|
+
const [isSuccess, setIsSuccess] = (0, react.useState)(false);
|
|
2634
|
+
const [isError, setIsError] = (0, react.useState)(false);
|
|
2635
|
+
const [error, setError] = (0, react.useState)(null);
|
|
2636
|
+
const [data, setData] = (0, react.useState)(void 0);
|
|
2637
|
+
const mutationFnRef = (0, react.useRef)(mutationFn);
|
|
2638
|
+
mutationFnRef.current = mutationFn;
|
|
2639
|
+
const reset = (0, react.useCallback)(() => {
|
|
2640
|
+
setIsPending(false);
|
|
2641
|
+
setIsSuccess(false);
|
|
2642
|
+
setIsError(false);
|
|
2643
|
+
setError(null);
|
|
2644
|
+
setData(void 0);
|
|
2645
|
+
}, []);
|
|
2646
|
+
const mutateAsync = (0, react.useCallback)(async (variables) => {
|
|
2647
|
+
setIsPending(true);
|
|
2648
|
+
setIsSuccess(false);
|
|
2649
|
+
setIsError(false);
|
|
2650
|
+
setError(null);
|
|
2651
|
+
try {
|
|
2652
|
+
const result = await mutationFnRef.current(variables);
|
|
2653
|
+
setData(result);
|
|
2654
|
+
setIsSuccess(true);
|
|
2655
|
+
return result;
|
|
2656
|
+
} catch (err) {
|
|
2657
|
+
setError(err);
|
|
2658
|
+
setIsError(true);
|
|
2659
|
+
throw err;
|
|
2660
|
+
} finally {
|
|
2661
|
+
setIsPending(false);
|
|
2662
|
+
}
|
|
2663
|
+
}, []);
|
|
2664
|
+
return {
|
|
2665
|
+
mutate: (0, react.useCallback)((variables) => {
|
|
2666
|
+
mutateAsync(variables).catch(() => {});
|
|
2667
|
+
}, [mutateAsync]),
|
|
2668
|
+
mutateAsync,
|
|
2669
|
+
isPending,
|
|
2670
|
+
isSuccess,
|
|
2671
|
+
isError,
|
|
2672
|
+
error,
|
|
2673
|
+
data,
|
|
2674
|
+
reset
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
//#endregion
|
|
2678
|
+
//#region src/workflows/use-stream-workflow.ts
|
|
2679
|
+
/**
|
|
2680
|
+
* Hook for streaming workflow execution with support for observing, resuming, and time-travel.
|
|
2681
|
+
*
|
|
2682
|
+
* @example
|
|
2683
|
+
* ```tsx
|
|
2684
|
+
* const {
|
|
2685
|
+
* streamWorkflow,
|
|
2686
|
+
* streamResult,
|
|
2687
|
+
* isStreaming,
|
|
2688
|
+
* observeWorkflowStream,
|
|
2689
|
+
* closeStreamsAndReset,
|
|
2690
|
+
* resumeWorkflowStream,
|
|
2691
|
+
* timeTravelWorkflowStream,
|
|
2692
|
+
* } = useStreamWorkflow({
|
|
2693
|
+
* debugMode: true,
|
|
2694
|
+
* tracingOptions: { enabled: true },
|
|
2695
|
+
* onError: (error, defaultMessage) => console.error(defaultMessage, error),
|
|
2696
|
+
* });
|
|
2697
|
+
*
|
|
2698
|
+
* // Start streaming a workflow
|
|
2699
|
+
* await streamWorkflow.mutateAsync({
|
|
2700
|
+
* workflowId: 'my-workflow',
|
|
2701
|
+
* runId: 'run-123',
|
|
2702
|
+
* inputData: { key: 'value' },
|
|
2703
|
+
* requestContext: {},
|
|
2704
|
+
* });
|
|
2705
|
+
* ```
|
|
2706
|
+
*/
|
|
2707
|
+
function useStreamWorkflow({ debugMode, tracingOptions, onError }) {
|
|
2708
|
+
const client = useMastraClient();
|
|
2709
|
+
const [streamResult, setStreamResult] = (0, react.useState)({});
|
|
2710
|
+
const [isStreaming, setIsStreaming] = (0, react.useState)(false);
|
|
2711
|
+
const readerRef = (0, react.useRef)(null);
|
|
2712
|
+
const observerRef = (0, react.useRef)(null);
|
|
2713
|
+
const resumeStreamRef = (0, react.useRef)(null);
|
|
2714
|
+
const timeTravelStreamRef = (0, react.useRef)(null);
|
|
2715
|
+
const isMountedRef = (0, react.useRef)(true);
|
|
2716
|
+
(0, react.useEffect)(() => {
|
|
2717
|
+
isMountedRef.current = true;
|
|
2718
|
+
return () => {
|
|
2719
|
+
isMountedRef.current = false;
|
|
2720
|
+
if (readerRef.current) {
|
|
2721
|
+
try {
|
|
2722
|
+
readerRef.current.releaseLock();
|
|
2723
|
+
} catch {}
|
|
2724
|
+
readerRef.current = null;
|
|
2725
|
+
}
|
|
2726
|
+
if (observerRef.current) {
|
|
2727
|
+
try {
|
|
2728
|
+
observerRef.current.releaseLock();
|
|
2729
|
+
} catch {}
|
|
2730
|
+
observerRef.current = null;
|
|
2731
|
+
}
|
|
2732
|
+
if (resumeStreamRef.current) {
|
|
2733
|
+
try {
|
|
2734
|
+
resumeStreamRef.current.releaseLock();
|
|
2735
|
+
} catch {}
|
|
2736
|
+
resumeStreamRef.current = null;
|
|
2737
|
+
}
|
|
2738
|
+
if (timeTravelStreamRef.current) {
|
|
2739
|
+
try {
|
|
2740
|
+
timeTravelStreamRef.current.releaseLock();
|
|
2741
|
+
} catch {}
|
|
2742
|
+
timeTravelStreamRef.current = null;
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
}, []);
|
|
2746
|
+
const handleStreamError = (0, react.useCallback)((err, defaultMessage, setStreamingState) => {
|
|
2747
|
+
if (err instanceof TypeError) return;
|
|
2748
|
+
const error = err instanceof Error ? err : new Error(defaultMessage);
|
|
2749
|
+
onError?.(error, defaultMessage);
|
|
2750
|
+
setStreamingState?.(false);
|
|
2751
|
+
}, [onError]);
|
|
2752
|
+
const handleWorkflowFinish = (0, react.useCallback)((value) => {
|
|
2753
|
+
if (value.type === "workflow-finish") {
|
|
2754
|
+
const streamStatus = value.payload?.workflowStatus;
|
|
2755
|
+
const metadata = value.payload?.metadata;
|
|
2756
|
+
setStreamResult((prev) => ({
|
|
2757
|
+
...prev,
|
|
2758
|
+
status: streamStatus
|
|
2759
|
+
}));
|
|
2760
|
+
if (streamStatus === "failed") throw new Error(metadata?.errorMessage || "Workflow execution failed");
|
|
2761
|
+
}
|
|
2762
|
+
}, []);
|
|
2763
|
+
const streamWorkflow = useMutation(async ({ workflowId, runId, inputData, initialState, requestContext: playgroundRequestContext, perStep }) => {
|
|
2764
|
+
if (readerRef.current) readerRef.current.releaseLock();
|
|
2765
|
+
if (!isMountedRef.current) return;
|
|
2766
|
+
setIsStreaming(true);
|
|
2767
|
+
setStreamResult({ input: inputData });
|
|
2768
|
+
const stream = await (await client.getWorkflow(workflowId).createRun({ runId })).stream({
|
|
2769
|
+
inputData,
|
|
2770
|
+
initialState,
|
|
2771
|
+
requestContext: playgroundRequestContext,
|
|
2772
|
+
closeOnSuspend: true,
|
|
2773
|
+
tracingOptions,
|
|
2774
|
+
perStep: perStep ?? debugMode
|
|
2775
|
+
});
|
|
2776
|
+
if (!stream) return handleStreamError(/* @__PURE__ */ new Error("No stream returned"), "No stream returned", setIsStreaming);
|
|
2777
|
+
const reader = stream.getReader();
|
|
2778
|
+
readerRef.current = reader;
|
|
2779
|
+
try {
|
|
2780
|
+
while (true) {
|
|
2781
|
+
if (!isMountedRef.current) break;
|
|
2782
|
+
const { done, value } = await reader.read();
|
|
2783
|
+
if (done) break;
|
|
2784
|
+
if (isMountedRef.current) {
|
|
2785
|
+
setStreamResult((prev) => {
|
|
2786
|
+
return mapWorkflowStreamChunkToWatchResult(prev, value);
|
|
2787
|
+
});
|
|
2788
|
+
if (value.type === "workflow-step-start") setIsStreaming(true);
|
|
2789
|
+
if (value.type === "workflow-step-suspended") setIsStreaming(false);
|
|
2790
|
+
if (value.type === "workflow-finish") handleWorkflowFinish(value);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
handleStreamError(err, "Error streaming workflow");
|
|
2795
|
+
} finally {
|
|
2796
|
+
if (isMountedRef.current) setIsStreaming(false);
|
|
2797
|
+
if (readerRef.current) {
|
|
2798
|
+
readerRef.current.releaseLock();
|
|
2799
|
+
readerRef.current = null;
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
const observeWorkflowStream = useMutation(async ({ workflowId, runId, storeRunResult }) => {
|
|
2804
|
+
if (observerRef.current) observerRef.current.releaseLock();
|
|
2805
|
+
if (!isMountedRef.current) return;
|
|
2806
|
+
setIsStreaming(true);
|
|
2807
|
+
setStreamResult(storeRunResult || {});
|
|
2808
|
+
if (storeRunResult?.status === "suspended") {
|
|
2809
|
+
setIsStreaming(false);
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
const stream = await (await client.getWorkflow(workflowId).createRun({ runId })).observeStream();
|
|
2813
|
+
if (!stream) return handleStreamError(/* @__PURE__ */ new Error("No stream returned"), "No stream returned", setIsStreaming);
|
|
2814
|
+
const reader = stream.getReader();
|
|
2815
|
+
observerRef.current = reader;
|
|
2816
|
+
try {
|
|
2817
|
+
while (true) {
|
|
2818
|
+
if (!isMountedRef.current) break;
|
|
2819
|
+
const { done, value } = await reader.read();
|
|
2820
|
+
if (done) break;
|
|
2821
|
+
if (isMountedRef.current) {
|
|
2822
|
+
setStreamResult((prev) => {
|
|
2823
|
+
return mapWorkflowStreamChunkToWatchResult(prev, value);
|
|
2824
|
+
});
|
|
2825
|
+
if (value.type === "workflow-step-start") setIsStreaming(true);
|
|
2826
|
+
if (value.type === "workflow-step-suspended") setIsStreaming(false);
|
|
2827
|
+
if (value.type === "workflow-finish") handleWorkflowFinish(value);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
handleStreamError(err, "Error observing workflow");
|
|
2832
|
+
} finally {
|
|
2833
|
+
if (isMountedRef.current) setIsStreaming(false);
|
|
2834
|
+
if (observerRef.current) {
|
|
2835
|
+
observerRef.current.releaseLock();
|
|
2836
|
+
observerRef.current = null;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
const resumeWorkflowStream = useMutation(async ({ workflowId, runId, step, resumeData, requestContext: playgroundRequestContext, perStep }) => {
|
|
2841
|
+
if (resumeStreamRef.current) resumeStreamRef.current.releaseLock();
|
|
2842
|
+
if (!isMountedRef.current) return;
|
|
2843
|
+
setIsStreaming(true);
|
|
2844
|
+
const stream = await (await client.getWorkflow(workflowId).createRun({ runId })).resumeStream({
|
|
2845
|
+
step,
|
|
2846
|
+
resumeData,
|
|
2847
|
+
requestContext: playgroundRequestContext,
|
|
2848
|
+
tracingOptions,
|
|
2849
|
+
perStep: perStep ?? debugMode
|
|
2850
|
+
});
|
|
2851
|
+
if (!stream) return handleStreamError(/* @__PURE__ */ new Error("No stream returned"), "No stream returned", setIsStreaming);
|
|
2852
|
+
const reader = stream.getReader();
|
|
2853
|
+
resumeStreamRef.current = reader;
|
|
2854
|
+
try {
|
|
2855
|
+
while (true) {
|
|
2856
|
+
if (!isMountedRef.current) break;
|
|
2857
|
+
const { done, value } = await reader.read();
|
|
2858
|
+
if (done) break;
|
|
2859
|
+
if (isMountedRef.current) {
|
|
2860
|
+
setStreamResult((prev) => {
|
|
2861
|
+
return mapWorkflowStreamChunkToWatchResult(prev, value);
|
|
2862
|
+
});
|
|
2863
|
+
if (value.type === "workflow-step-start") setIsStreaming(true);
|
|
2864
|
+
if (value.type === "workflow-step-suspended") setIsStreaming(false);
|
|
2865
|
+
if (value.type === "workflow-finish") handleWorkflowFinish(value);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
handleStreamError(err, "Error resuming workflow stream");
|
|
2870
|
+
} finally {
|
|
2871
|
+
if (isMountedRef.current) setIsStreaming(false);
|
|
2872
|
+
if (resumeStreamRef.current) {
|
|
2873
|
+
resumeStreamRef.current.releaseLock();
|
|
2874
|
+
resumeStreamRef.current = null;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
const timeTravelWorkflowStream = useMutation(async ({ workflowId, requestContext: playgroundRequestContext, runId, perStep, ...params }) => {
|
|
2879
|
+
if (timeTravelStreamRef.current) timeTravelStreamRef.current.releaseLock();
|
|
2880
|
+
if (!isMountedRef.current) return;
|
|
2881
|
+
setIsStreaming(true);
|
|
2882
|
+
const stream = await (await client.getWorkflow(workflowId).createRun({ runId })).timeTravelStream({
|
|
2883
|
+
...params,
|
|
2884
|
+
perStep: perStep ?? debugMode,
|
|
2885
|
+
requestContext: playgroundRequestContext,
|
|
2886
|
+
tracingOptions
|
|
2887
|
+
});
|
|
2888
|
+
if (!stream) return handleStreamError(/* @__PURE__ */ new Error("No stream returned"), "No stream returned", setIsStreaming);
|
|
2889
|
+
const reader = stream.getReader();
|
|
2890
|
+
timeTravelStreamRef.current = reader;
|
|
2891
|
+
try {
|
|
2892
|
+
while (true) {
|
|
2893
|
+
if (!isMountedRef.current) break;
|
|
2894
|
+
const { done, value } = await reader.read();
|
|
2895
|
+
if (done) break;
|
|
2896
|
+
if (isMountedRef.current) {
|
|
2897
|
+
setStreamResult((prev) => {
|
|
2898
|
+
return mapWorkflowStreamChunkToWatchResult(prev, value);
|
|
2899
|
+
});
|
|
2900
|
+
if (value.type === "workflow-step-start") setIsStreaming(true);
|
|
2901
|
+
if (value.type === "workflow-step-suspended") setIsStreaming(false);
|
|
2902
|
+
if (value.type === "workflow-finish") handleWorkflowFinish(value);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
} catch (err) {
|
|
2906
|
+
handleStreamError(err, "Error time traveling workflow stream");
|
|
2907
|
+
} finally {
|
|
2908
|
+
if (isMountedRef.current) setIsStreaming(false);
|
|
2909
|
+
if (timeTravelStreamRef.current) {
|
|
2910
|
+
timeTravelStreamRef.current.releaseLock();
|
|
2911
|
+
timeTravelStreamRef.current = null;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
return {
|
|
2916
|
+
streamWorkflow,
|
|
2917
|
+
streamResult,
|
|
2918
|
+
isStreaming,
|
|
2919
|
+
observeWorkflowStream,
|
|
2920
|
+
closeStreamsAndReset: (0, react.useCallback)(() => {
|
|
2921
|
+
setIsStreaming(false);
|
|
2922
|
+
setStreamResult({});
|
|
2923
|
+
if (readerRef.current) {
|
|
2924
|
+
try {
|
|
2925
|
+
readerRef.current.releaseLock();
|
|
2926
|
+
} catch {}
|
|
2927
|
+
readerRef.current = null;
|
|
2928
|
+
}
|
|
2929
|
+
if (observerRef.current) {
|
|
2930
|
+
try {
|
|
2931
|
+
observerRef.current.releaseLock();
|
|
2932
|
+
} catch {}
|
|
2933
|
+
observerRef.current = null;
|
|
2934
|
+
}
|
|
2935
|
+
if (resumeStreamRef.current) {
|
|
2936
|
+
try {
|
|
2937
|
+
resumeStreamRef.current.releaseLock();
|
|
2938
|
+
} catch {}
|
|
2939
|
+
resumeStreamRef.current = null;
|
|
2940
|
+
}
|
|
2941
|
+
if (timeTravelStreamRef.current) {
|
|
2942
|
+
try {
|
|
2943
|
+
timeTravelStreamRef.current.releaseLock();
|
|
2944
|
+
} catch {}
|
|
2945
|
+
timeTravelStreamRef.current = null;
|
|
2946
|
+
}
|
|
2947
|
+
}, []),
|
|
2948
|
+
resumeWorkflowStream,
|
|
2949
|
+
timeTravelWorkflowStream
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
//#endregion
|
|
2953
|
+
//#region src/workflows/hooks.ts
|
|
2954
|
+
/**
|
|
2955
|
+
* Hook for creating workflow runs.
|
|
2956
|
+
* Returns a mutation for creating a new workflow run.
|
|
2957
|
+
*
|
|
2958
|
+
* @example
|
|
2959
|
+
* ```tsx
|
|
2960
|
+
* const createWorkflowRun = useCreateWorkflowRun();
|
|
2961
|
+
*
|
|
2962
|
+
* // Create a run
|
|
2963
|
+
* const { runId } = await createWorkflowRun.mutateAsync({
|
|
2964
|
+
* workflowId: 'my-workflow'
|
|
2965
|
+
* });
|
|
2966
|
+
* ```
|
|
2967
|
+
*/
|
|
2968
|
+
function useCreateWorkflowRun() {
|
|
2969
|
+
const client = useMastraClient();
|
|
2970
|
+
return useMutation(async ({ workflowId, prevRunId }) => {
|
|
2971
|
+
try {
|
|
2972
|
+
const { runId: newRunId } = await client.getWorkflow(workflowId).createRun({ runId: prevRunId });
|
|
2973
|
+
return { runId: newRunId };
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
console.error("Error creating workflow run:", error);
|
|
2976
|
+
throw error;
|
|
2977
|
+
}
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Hook for canceling workflow runs.
|
|
2982
|
+
* Returns a mutation for canceling a running workflow.
|
|
2983
|
+
*
|
|
2984
|
+
* @example
|
|
2985
|
+
* ```tsx
|
|
2986
|
+
* const cancelWorkflowRun = useCancelWorkflowRun();
|
|
2987
|
+
*
|
|
2988
|
+
* // Cancel a run
|
|
2989
|
+
* await cancelWorkflowRun.mutateAsync({
|
|
2990
|
+
* workflowId: 'my-workflow',
|
|
2991
|
+
* runId: 'run-123'
|
|
2992
|
+
* });
|
|
2993
|
+
* ```
|
|
2994
|
+
*/
|
|
2995
|
+
function useCancelWorkflowRun() {
|
|
2996
|
+
const client = useMastraClient();
|
|
2997
|
+
return useMutation(async ({ workflowId, runId }) => {
|
|
2998
|
+
try {
|
|
2999
|
+
return (await client.getWorkflow(workflowId).createRun({ runId })).cancelRun();
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
console.error("Error canceling workflow run:", error);
|
|
3002
|
+
throw error;
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
//#endregion
|
|
3007
|
+
//#region src/workflows/WorkflowStepFactory/WorkflowStepFactory.tsx
|
|
3008
|
+
const renderUnknown = (step, UnknownStep) => UnknownStep?.(step) ?? null;
|
|
3009
|
+
const WorkflowStepFactoryComponent = ({ step, Step, MapStep, AgentStep, ToolStep, ForEachStep, ParallelStep, Conditional, LoopStep, SleepStep, SleepUntilStep, NestedWorkflowStep, UnknownStep }) => {
|
|
3010
|
+
switch (step.kind) {
|
|
3011
|
+
case "step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Step?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3012
|
+
case "map-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: MapStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3013
|
+
case "agent-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: AgentStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3014
|
+
case "tool-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: ToolStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3015
|
+
case "foreach-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: ForEachStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3016
|
+
case "parallel-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: ParallelStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3017
|
+
case "conditional": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Conditional?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3018
|
+
case "loop-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: LoopStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3019
|
+
case "sleep-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: SleepStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3020
|
+
case "sleep-until-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: SleepUntilStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3021
|
+
case "nested-workflow-step": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: NestedWorkflowStep?.(step) ?? renderUnknown(step, UnknownStep) });
|
|
3022
|
+
default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: renderUnknown(step, UnknownStep) });
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
const WorkflowStepFactory = (0, react.memo)(WorkflowStepFactoryComponent);
|
|
3026
|
+
//#endregion
|
|
3027
|
+
//#region src/voice/record-mic-to-file.ts
|
|
3028
|
+
async function recordMicrophoneToFile(onFinish) {
|
|
3029
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
3030
|
+
const mediaRecorder = new MediaRecorder(stream);
|
|
3031
|
+
let chunks = [];
|
|
3032
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
3033
|
+
chunks.push(e.data);
|
|
3034
|
+
};
|
|
3035
|
+
mediaRecorder.onstop = () => {
|
|
3036
|
+
const blob = new Blob(chunks, { type: "audio/webm" });
|
|
3037
|
+
const file = new File([blob], `recording-${Date.now()}.webm`, {
|
|
3038
|
+
type: "audio/webm",
|
|
3039
|
+
lastModified: Date.now()
|
|
3040
|
+
});
|
|
3041
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
3042
|
+
onFinish(file);
|
|
3043
|
+
};
|
|
3044
|
+
return mediaRecorder;
|
|
3045
|
+
}
|
|
3046
|
+
//#endregion
|
|
3047
|
+
//#region src/voice/play-stream-with-web-audio.ts
|
|
3048
|
+
async function playStreamWithWebAudio(stream, onEnded) {
|
|
3049
|
+
const audioContext = new window.AudioContext();
|
|
3050
|
+
const reader = stream.getReader();
|
|
3051
|
+
const chunks = [];
|
|
3052
|
+
try {
|
|
3053
|
+
while (true) {
|
|
3054
|
+
const { done, value } = await reader.read();
|
|
3055
|
+
if (done) break;
|
|
3056
|
+
chunks.push(value);
|
|
3057
|
+
}
|
|
3058
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
3059
|
+
const combinedBuffer = new Uint8Array(totalLength);
|
|
3060
|
+
let offset = 0;
|
|
3061
|
+
for (const chunk of chunks) {
|
|
3062
|
+
combinedBuffer.set(chunk, offset);
|
|
3063
|
+
offset += chunk.length;
|
|
3064
|
+
}
|
|
3065
|
+
const audioBuffer = await audioContext.decodeAudioData(combinedBuffer.buffer);
|
|
3066
|
+
const source = audioContext.createBufferSource();
|
|
3067
|
+
source.buffer = audioBuffer;
|
|
3068
|
+
source.onended = onEnded ?? null;
|
|
3069
|
+
source.connect(audioContext.destination);
|
|
3070
|
+
source.start();
|
|
3071
|
+
return () => {
|
|
3072
|
+
source.onended = null;
|
|
3073
|
+
source.stop();
|
|
3074
|
+
audioContext.close();
|
|
3075
|
+
};
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
await reader.cancel().catch(() => void 0);
|
|
3078
|
+
await audioContext.close().catch(() => void 0);
|
|
3079
|
+
throw error;
|
|
3080
|
+
} finally {
|
|
3081
|
+
reader.releaseLock();
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
//#endregion
|
|
3085
|
+
//#region src/voice/use-speech-recognition.ts
|
|
3086
|
+
const useSpeechRecognition = ({ language = "en-US", agentId, requestContext }) => {
|
|
3087
|
+
const client = useMastraClient();
|
|
3088
|
+
const [agent, setAgent] = (0, react.useState)(null);
|
|
3089
|
+
(0, react.useEffect)(() => {
|
|
3090
|
+
let cancelled = false;
|
|
3091
|
+
if (!agentId) {
|
|
3092
|
+
setAgent(null);
|
|
3093
|
+
return () => {
|
|
3094
|
+
cancelled = true;
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
const agent = client.getAgent(agentId);
|
|
3098
|
+
const check = async () => {
|
|
3099
|
+
try {
|
|
3100
|
+
const speakers = await agent.voice.getSpeakers(requestContext);
|
|
3101
|
+
if (!cancelled) setAgent(speakers.length > 0 ? agent : null);
|
|
3102
|
+
} catch {
|
|
3103
|
+
if (!cancelled) setAgent(null);
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
check();
|
|
3107
|
+
return () => {
|
|
3108
|
+
cancelled = true;
|
|
3109
|
+
};
|
|
3110
|
+
}, [
|
|
3111
|
+
agentId,
|
|
3112
|
+
client,
|
|
3113
|
+
requestContext
|
|
3114
|
+
]);
|
|
3115
|
+
const browserSpeechRecognition = useBrowserSpeechRecognition({ language });
|
|
3116
|
+
const mastraSpeechRecognition = useMastraSpeechToText({
|
|
3117
|
+
agent,
|
|
3118
|
+
language
|
|
3119
|
+
});
|
|
3120
|
+
if (!agent) return browserSpeechRecognition;
|
|
3121
|
+
return mastraSpeechRecognition;
|
|
3122
|
+
};
|
|
3123
|
+
const useBrowserSpeechRecognition = ({ language = "en-US" }) => {
|
|
3124
|
+
const speechRecognitionRef = (0, react.useRef)(null);
|
|
3125
|
+
const [state, setState] = (0, react.useState)({
|
|
3126
|
+
isListening: false,
|
|
3127
|
+
transcript: "",
|
|
3128
|
+
error: null
|
|
3129
|
+
});
|
|
3130
|
+
const start = () => {
|
|
3131
|
+
if (!speechRecognitionRef.current) return;
|
|
3132
|
+
speechRecognitionRef.current.start();
|
|
3133
|
+
};
|
|
3134
|
+
const stop = () => {
|
|
3135
|
+
if (!speechRecognitionRef.current) return;
|
|
3136
|
+
speechRecognitionRef.current.stop();
|
|
3137
|
+
};
|
|
3138
|
+
(0, react.useEffect)(() => {
|
|
3139
|
+
if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) {
|
|
3140
|
+
setState((prev) => ({
|
|
3141
|
+
...prev,
|
|
3142
|
+
error: "Speech Recognition not supported in this browser"
|
|
3143
|
+
}));
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
|
3147
|
+
speechRecognitionRef.current = recognition;
|
|
3148
|
+
recognition.continuous = true;
|
|
3149
|
+
recognition.lang = language;
|
|
3150
|
+
recognition.onstart = () => {
|
|
3151
|
+
setState((prev) => ({
|
|
3152
|
+
...prev,
|
|
3153
|
+
isListening: true,
|
|
3154
|
+
error: null
|
|
3155
|
+
}));
|
|
3156
|
+
};
|
|
3157
|
+
recognition.onresult = (event) => {
|
|
3158
|
+
let finalTranscript = "";
|
|
3159
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
3160
|
+
const transcript = event.results[i][0].transcript;
|
|
3161
|
+
if (event.results[i].isFinal) finalTranscript += transcript + " ";
|
|
3162
|
+
}
|
|
3163
|
+
setState((prev) => ({
|
|
3164
|
+
...prev,
|
|
3165
|
+
transcript: finalTranscript
|
|
3166
|
+
}));
|
|
3167
|
+
};
|
|
3168
|
+
recognition.onerror = (event) => {
|
|
3169
|
+
setState((prev) => ({
|
|
3170
|
+
...prev,
|
|
3171
|
+
error: `Error: ${event.error}`
|
|
3172
|
+
}));
|
|
3173
|
+
};
|
|
3174
|
+
recognition.onend = () => setState((prev) => ({
|
|
3175
|
+
...prev,
|
|
3176
|
+
isListening: false
|
|
3177
|
+
}));
|
|
3178
|
+
return () => {
|
|
3179
|
+
try {
|
|
3180
|
+
recognition.stop();
|
|
3181
|
+
} catch {}
|
|
3182
|
+
recognition.onstart = null;
|
|
3183
|
+
recognition.onresult = null;
|
|
3184
|
+
recognition.onerror = null;
|
|
3185
|
+
recognition.onend = null;
|
|
3186
|
+
speechRecognitionRef.current = null;
|
|
3187
|
+
};
|
|
3188
|
+
}, [language]);
|
|
3189
|
+
return {
|
|
3190
|
+
...state,
|
|
3191
|
+
start,
|
|
3192
|
+
stop
|
|
3193
|
+
};
|
|
3194
|
+
};
|
|
3195
|
+
const useMastraSpeechToText = ({ agent, language }) => {
|
|
3196
|
+
const [state, setState] = (0, react.useState)({
|
|
3197
|
+
isListening: false,
|
|
3198
|
+
transcript: "",
|
|
3199
|
+
error: null
|
|
3200
|
+
});
|
|
3201
|
+
const recorderRef = (0, react.useRef)(null);
|
|
3202
|
+
const sessionRef = (0, react.useRef)(0);
|
|
3203
|
+
const startInFlightRef = (0, react.useRef)(false);
|
|
3204
|
+
(0, react.useEffect)(() => {
|
|
3205
|
+
return () => {
|
|
3206
|
+
sessionRef.current += 1;
|
|
3207
|
+
startInFlightRef.current = false;
|
|
3208
|
+
recorderRef.current?.stop();
|
|
3209
|
+
recorderRef.current = null;
|
|
3210
|
+
};
|
|
3211
|
+
}, [agent]);
|
|
3212
|
+
const handleFinish = (session) => (file) => {
|
|
3213
|
+
if (!agent || session !== sessionRef.current) return;
|
|
3214
|
+
recorderRef.current = null;
|
|
3215
|
+
setState((prev) => ({
|
|
3216
|
+
...prev,
|
|
3217
|
+
isListening: false
|
|
3218
|
+
}));
|
|
3219
|
+
agent.voice.listen(file, { language }).then((res) => {
|
|
3220
|
+
if (session !== sessionRef.current) return;
|
|
3221
|
+
setState((prev) => ({
|
|
3222
|
+
...prev,
|
|
3223
|
+
transcript: res.text,
|
|
3224
|
+
error: null
|
|
3225
|
+
}));
|
|
3226
|
+
}).catch((error) => {
|
|
3227
|
+
if (session !== sessionRef.current) return;
|
|
3228
|
+
const message = error instanceof Error ? error.message : "Failed to transcribe speech";
|
|
3229
|
+
setState((prev) => ({
|
|
3230
|
+
...prev,
|
|
3231
|
+
error: message
|
|
3232
|
+
}));
|
|
3233
|
+
});
|
|
3234
|
+
};
|
|
3235
|
+
const start = () => {
|
|
3236
|
+
if (!agent || startInFlightRef.current || recorderRef.current) return;
|
|
3237
|
+
startInFlightRef.current = true;
|
|
3238
|
+
sessionRef.current += 1;
|
|
3239
|
+
const session = sessionRef.current;
|
|
3240
|
+
recordMicrophoneToFile(handleFinish(session)).then((recorder) => {
|
|
3241
|
+
startInFlightRef.current = false;
|
|
3242
|
+
if (session !== sessionRef.current) {
|
|
3243
|
+
try {
|
|
3244
|
+
recorder.stop();
|
|
3245
|
+
} catch {}
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
recorderRef.current = recorder;
|
|
3249
|
+
setState((prev) => ({
|
|
3250
|
+
...prev,
|
|
3251
|
+
isListening: true,
|
|
3252
|
+
error: null
|
|
3253
|
+
}));
|
|
3254
|
+
recorder.start();
|
|
3255
|
+
}).catch((error) => {
|
|
3256
|
+
startInFlightRef.current = false;
|
|
3257
|
+
if (session !== sessionRef.current) return;
|
|
3258
|
+
const message = error instanceof Error ? error.message : "Failed to start speech recording";
|
|
3259
|
+
setState((prev) => ({
|
|
3260
|
+
...prev,
|
|
3261
|
+
isListening: false,
|
|
3262
|
+
error: message
|
|
3263
|
+
}));
|
|
3264
|
+
});
|
|
3265
|
+
};
|
|
3266
|
+
const stop = () => {
|
|
3267
|
+
if (recorderRef.current) {
|
|
3268
|
+
recorderRef.current.stop();
|
|
3269
|
+
recorderRef.current = null;
|
|
3270
|
+
} else if (startInFlightRef.current) sessionRef.current += 1;
|
|
3271
|
+
startInFlightRef.current = false;
|
|
3272
|
+
setState((prev) => ({
|
|
3273
|
+
...prev,
|
|
3274
|
+
isListening: false
|
|
3275
|
+
}));
|
|
3276
|
+
};
|
|
3277
|
+
return {
|
|
3278
|
+
...state,
|
|
3279
|
+
start,
|
|
3280
|
+
stop
|
|
3281
|
+
};
|
|
3282
|
+
};
|
|
3283
|
+
//#endregion
|
|
3284
|
+
exports.AgentIcon = require_ui.AgentIcon;
|
|
3285
|
+
exports.CLIENT_MESSAGE_ID_KEY = CLIENT_MESSAGE_ID_KEY;
|
|
3286
|
+
exports.CodeBlock = require_ui.CodeBlock;
|
|
3287
|
+
exports.CodeBlockClass = require_ui.CodeBlockClass;
|
|
3288
|
+
exports.CodeCopyButton = require_ui.CodeCopyButton;
|
|
3289
|
+
exports.Entity = require_ui.Entity;
|
|
3290
|
+
exports.EntityCaret = require_ui.EntityCaret;
|
|
3291
|
+
exports.EntityContent = require_ui.EntityContent;
|
|
3292
|
+
exports.EntityContentClass = require_ui.EntityContentClass;
|
|
3293
|
+
exports.EntityTrigger = require_ui.EntityTrigger;
|
|
3294
|
+
exports.EntityTriggerClass = require_ui.EntityTriggerClass;
|
|
3295
|
+
exports.EntityTriggerVariantClasses = require_ui.EntityTriggerVariantClasses;
|
|
3296
|
+
exports.Entry = require_ui.Entry;
|
|
3297
|
+
exports.EntryClass = require_ui.EntryClass;
|
|
3298
|
+
exports.EntryTitle = require_ui.EntryTitle;
|
|
3299
|
+
exports.EntryTitleClass = require_ui.EntryTitleClass;
|
|
3300
|
+
exports.Icon = require_ui.Icon;
|
|
3301
|
+
exports.IconButton = require_ui.IconButton;
|
|
3302
|
+
exports.IconButtonClass = require_ui.IconButtonClass;
|
|
3303
|
+
exports.IconSizes = require_ui.IconSizes;
|
|
1508
3304
|
exports.MastraReactProvider = MastraReactProvider;
|
|
1509
|
-
exports.Message = Message;
|
|
1510
|
-
exports.MessageActions = MessageActions;
|
|
1511
|
-
exports.MessageActionsClass = MessageActionsClass;
|
|
1512
|
-
exports.MessageClass = MessageClass;
|
|
1513
|
-
exports.MessageContent = MessageContent;
|
|
1514
|
-
exports.MessageContentClass = MessageContentClass;
|
|
1515
|
-
exports.
|
|
1516
|
-
exports.
|
|
1517
|
-
exports.
|
|
1518
|
-
exports.
|
|
1519
|
-
exports.
|
|
1520
|
-
exports.
|
|
1521
|
-
exports.
|
|
1522
|
-
exports.
|
|
1523
|
-
exports.
|
|
1524
|
-
exports.
|
|
1525
|
-
exports.
|
|
1526
|
-
exports.
|
|
1527
|
-
exports.
|
|
1528
|
-
exports.
|
|
1529
|
-
exports.
|
|
1530
|
-
exports.
|
|
1531
|
-
exports.
|
|
1532
|
-
exports.
|
|
1533
|
-
exports.
|
|
1534
|
-
exports.
|
|
1535
|
-
exports.
|
|
1536
|
-
exports.
|
|
1537
|
-
exports.
|
|
1538
|
-
exports.
|
|
1539
|
-
exports.
|
|
1540
|
-
exports.
|
|
1541
|
-
exports.
|
|
1542
|
-
exports.
|
|
3305
|
+
exports.Message = require_ui.Message;
|
|
3306
|
+
exports.MessageActions = require_ui.MessageActions;
|
|
3307
|
+
exports.MessageActionsClass = require_ui.MessageActionsClass;
|
|
3308
|
+
exports.MessageClass = require_ui.MessageClass;
|
|
3309
|
+
exports.MessageContent = require_ui.MessageContent;
|
|
3310
|
+
exports.MessageContentClass = require_ui.MessageContentClass;
|
|
3311
|
+
exports.MessageFactory = require_ui.MessageFactory;
|
|
3312
|
+
exports.MessageList = require_ui.MessageList;
|
|
3313
|
+
exports.MessageListClass = require_ui.MessageListClass;
|
|
3314
|
+
exports.MessageStreaming = require_ui.MessageStreaming;
|
|
3315
|
+
exports.MessageStreamingClass = require_ui.MessageStreamingClass;
|
|
3316
|
+
exports.MessageUsage = require_ui.MessageUsage;
|
|
3317
|
+
exports.MessageUsageClass = require_ui.MessageUsageClass;
|
|
3318
|
+
exports.MessageUsageEntry = require_ui.MessageUsageEntry;
|
|
3319
|
+
exports.MessageUsageEntryClass = require_ui.MessageUsageEntryClass;
|
|
3320
|
+
exports.MessageUsageValue = require_ui.MessageUsageValue;
|
|
3321
|
+
exports.MessageUsageValueClass = require_ui.MessageUsageValueClass;
|
|
3322
|
+
exports.MessageUsages = require_ui.MessageUsages;
|
|
3323
|
+
exports.MessageUsagesClass = require_ui.MessageUsagesClass;
|
|
3324
|
+
exports.ToolApproval = require_ui.ToolApproval;
|
|
3325
|
+
exports.ToolApprovalActions = require_ui.ToolApprovalActions;
|
|
3326
|
+
exports.ToolApprovalActionsClass = require_ui.ToolApprovalActionsClass;
|
|
3327
|
+
exports.ToolApprovalClass = require_ui.ToolApprovalClass;
|
|
3328
|
+
exports.ToolApprovalContent = require_ui.ToolApprovalContent;
|
|
3329
|
+
exports.ToolApprovalContentClass = require_ui.ToolApprovalContentClass;
|
|
3330
|
+
exports.ToolApprovalHeader = require_ui.ToolApprovalHeader;
|
|
3331
|
+
exports.ToolApprovalHeaderClass = require_ui.ToolApprovalHeaderClass;
|
|
3332
|
+
exports.ToolApprovalTitle = require_ui.ToolApprovalTitle;
|
|
3333
|
+
exports.ToolApprovalTitleClass = require_ui.ToolApprovalTitleClass;
|
|
3334
|
+
exports.ToolsIcon = require_ui.ToolsIcon;
|
|
3335
|
+
exports.Tooltip = require_ui.Tooltip;
|
|
3336
|
+
exports.TooltipContent = require_ui.TooltipContent;
|
|
3337
|
+
exports.TooltipContentClass = require_ui.TooltipContentClass;
|
|
3338
|
+
exports.TooltipTrigger = require_ui.TooltipTrigger;
|
|
3339
|
+
exports.WorkflowIcon = require_ui.WorkflowIcon;
|
|
3340
|
+
exports.WorkflowStepFactory = WorkflowStepFactory;
|
|
3341
|
+
exports.accumulateChunk = accumulateChunk;
|
|
3342
|
+
exports.accumulateNetworkChunk = accumulateNetworkChunk;
|
|
3343
|
+
exports.finishStreamingAssistantMessage = finishStreamingAssistantMessage;
|
|
3344
|
+
exports.fromCoreUserMessageToMastraDBMessage = fromCoreUserMessageToMastraDBMessage;
|
|
3345
|
+
exports.fromCoreUserMessagesToMastraDBMessage = fromCoreUserMessagesToMastraDBMessage;
|
|
1543
3346
|
exports.mapWorkflowStreamChunkToWatchResult = mapWorkflowStreamChunkToWatchResult;
|
|
1544
|
-
exports.
|
|
1545
|
-
exports.
|
|
3347
|
+
exports.playStreamWithWebAudio = playStreamWithWebAudio;
|
|
3348
|
+
exports.recordMicrophoneToFile = recordMicrophoneToFile;
|
|
3349
|
+
exports.useCancelWorkflowRun = useCancelWorkflowRun;
|
|
1546
3350
|
exports.useChat = useChat;
|
|
1547
|
-
exports.
|
|
3351
|
+
exports.useCreateWorkflowRun = useCreateWorkflowRun;
|
|
3352
|
+
exports.useEntity = require_ui.useEntity;
|
|
1548
3353
|
exports.useMastraClient = useMastraClient;
|
|
1549
|
-
|
|
3354
|
+
exports.useSpeechRecognition = useSpeechRecognition;
|
|
3355
|
+
exports.useStreamWorkflow = useStreamWorkflow;
|
|
3356
|
+
|
|
3357
|
+
//# sourceMappingURL=index.cjs.map
|