@mastra/react 0.1.0-beta.4 → 0.1.0-beta.5
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 +32 -0
- package/dist/index.cjs +74 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +74 -22
- package/dist/index.js.map +1 -1
- package/dist/src/agent/hooks.d.ts +2 -1
- package/dist/src/lib/ai-sdk/types.d.ts +10 -0
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @mastra/react-hooks
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Configurable resourceId in react useChat ([#10461](https://github.com/mastra-ai/mastra/pull/10461))
|
|
8
|
+
|
|
9
|
+
- fix(agent): persist messages before tool suspension ([#10369](https://github.com/mastra-ai/mastra/pull/10369))
|
|
10
|
+
|
|
11
|
+
Fixes issues where thread and messages were not saved before suspension when tools require approval or call suspend() during execution. This caused conversation history to be lost if users refreshed during tool approval or suspension.
|
|
12
|
+
|
|
13
|
+
**Backend changes (@mastra/core):**
|
|
14
|
+
- Add assistant messages to messageList immediately after LLM execution
|
|
15
|
+
- Flush messages synchronously before suspension to persist state
|
|
16
|
+
- Create thread if it doesn't exist before flushing
|
|
17
|
+
- Add metadata helpers to persist and remove tool approval state
|
|
18
|
+
- Pass saveQueueManager and memory context through workflow for immediate persistence
|
|
19
|
+
|
|
20
|
+
**Frontend changes (@mastra/react):**
|
|
21
|
+
- Extract runId from pending approvals to enable resumption after refresh
|
|
22
|
+
- Convert `pendingToolApprovals` (DB format) to `requireApprovalMetadata` (runtime format)
|
|
23
|
+
- Handle both `dynamic-tool` and `tool-{NAME}` part types for approval state
|
|
24
|
+
- Change runId from hardcoded `agentId` to unique `uuid()`
|
|
25
|
+
|
|
26
|
+
**UI changes (@mastra/playground-ui):**
|
|
27
|
+
- Handle tool calls awaiting approval in message initialization
|
|
28
|
+
- Convert approval metadata format when loading initial messages
|
|
29
|
+
|
|
30
|
+
Fixes #9745, #9906
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [[`898a972`](https://github.com/mastra-ai/mastra/commit/898a9727d286c2510d6b702dfd367e6aaf5c6b0f)]:
|
|
33
|
+
- @mastra/client-js@1.0.0-beta.5
|
|
34
|
+
|
|
3
35
|
## 0.1.0-beta.4
|
|
4
36
|
|
|
5
37
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
5
5
|
const jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
const react = require('react');
|
|
7
7
|
const clientJs = require('@mastra/client-js');
|
|
8
|
+
const uuid = require('@lukeed/uuid');
|
|
8
9
|
const lucideReact = require('lucide-react');
|
|
9
10
|
const tailwindMerge = require('tailwind-merge');
|
|
10
11
|
const hastUtilToJsxRuntime = require('hast-util-to-jsx-runtime');
|
|
@@ -259,17 +260,19 @@ const toUIMessage = ({ chunk, conversation, metadata }) => {
|
|
|
259
260
|
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
260
261
|
const parts = [...lastMessage.parts];
|
|
261
262
|
const toolPartIndex = parts.findIndex(
|
|
262
|
-
(part) => part.type === "dynamic-tool" && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
263
|
+
(part) => (part.type === "dynamic-tool" || typeof part.type === "string" && part.type.startsWith("tool-")) && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
263
264
|
);
|
|
264
265
|
if (toolPartIndex !== -1) {
|
|
265
266
|
const toolPart = parts[toolPartIndex];
|
|
266
|
-
if (toolPart.type === "dynamic-tool") {
|
|
267
|
+
if (toolPart.type === "dynamic-tool" || typeof toolPart.type === "string" && toolPart.type.startsWith("tool-")) {
|
|
268
|
+
const toolName = "toolName" in toolPart && typeof toolPart.toolName === "string" ? toolPart.toolName : toolPart.type.startsWith("tool-") ? toolPart.type.substring(5) : "";
|
|
269
|
+
const toolCallId = toolPart.toolCallId;
|
|
267
270
|
if (chunk.type === "tool-result" && chunk.payload.isError || chunk.type === "tool-error") {
|
|
268
271
|
const error = chunk.type === "tool-error" ? chunk.payload.error : chunk.payload.result;
|
|
269
272
|
parts[toolPartIndex] = {
|
|
270
273
|
type: "dynamic-tool",
|
|
271
|
-
toolName
|
|
272
|
-
toolCallId
|
|
274
|
+
toolName,
|
|
275
|
+
toolCallId,
|
|
273
276
|
state: "output-error",
|
|
274
277
|
input: toolPart.input,
|
|
275
278
|
errorText: String(error),
|
|
@@ -288,8 +291,8 @@ const toUIMessage = ({ chunk, conversation, metadata }) => {
|
|
|
288
291
|
}
|
|
289
292
|
parts[toolPartIndex] = {
|
|
290
293
|
type: "dynamic-tool",
|
|
291
|
-
toolName
|
|
292
|
-
toolCallId
|
|
294
|
+
toolName,
|
|
295
|
+
toolCallId,
|
|
293
296
|
state: "output-available",
|
|
294
297
|
input: toolPart.input,
|
|
295
298
|
output,
|
|
@@ -311,11 +314,14 @@ const toUIMessage = ({ chunk, conversation, metadata }) => {
|
|
|
311
314
|
if (!lastMessage || lastMessage.role !== "assistant") return result;
|
|
312
315
|
const parts = [...lastMessage.parts];
|
|
313
316
|
const toolPartIndex = parts.findIndex(
|
|
314
|
-
(part) => part.type === "dynamic-tool" && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
317
|
+
(part) => (part.type === "dynamic-tool" || typeof part.type === "string" && part.type.startsWith("tool-")) && "toolCallId" in part && part.toolCallId === chunk.payload.toolCallId
|
|
315
318
|
);
|
|
316
319
|
if (toolPartIndex !== -1) {
|
|
317
320
|
const toolPart = parts[toolPartIndex];
|
|
318
|
-
if (toolPart.type === "dynamic-tool") {
|
|
321
|
+
if (toolPart.type === "dynamic-tool" || typeof toolPart.type === "string" && toolPart.type.startsWith("tool-")) {
|
|
322
|
+
const toolName = "toolName" in toolPart && typeof toolPart.toolName === "string" ? toolPart.toolName : typeof toolPart.type === "string" && toolPart.type.startsWith("tool-") ? toolPart.type.substring(5) : "";
|
|
323
|
+
const toolCallId = toolPart.toolCallId;
|
|
324
|
+
const input = toolPart.input;
|
|
319
325
|
if (chunk.payload.output?.type?.startsWith("workflow-")) {
|
|
320
326
|
const existingWorkflowState = toolPart.output || {};
|
|
321
327
|
const updatedWorkflowState = mapWorkflowStreamChunkToWatchResult(
|
|
@@ -323,7 +329,11 @@ const toUIMessage = ({ chunk, conversation, metadata }) => {
|
|
|
323
329
|
chunk.payload.output
|
|
324
330
|
);
|
|
325
331
|
parts[toolPartIndex] = {
|
|
326
|
-
|
|
332
|
+
type: "dynamic-tool",
|
|
333
|
+
toolName,
|
|
334
|
+
toolCallId,
|
|
335
|
+
state: "input-streaming",
|
|
336
|
+
input,
|
|
327
337
|
output: updatedWorkflowState
|
|
328
338
|
};
|
|
329
339
|
} else if (chunk.payload.output?.from === "AGENT" || chunk.payload.output?.from === "USER" && chunk.payload.output?.payload?.output?.type?.startsWith("workflow-")) {
|
|
@@ -332,7 +342,11 @@ const toUIMessage = ({ chunk, conversation, metadata }) => {
|
|
|
332
342
|
const currentOutput = toolPart.output || [];
|
|
333
343
|
const existingOutput = Array.isArray(currentOutput) ? currentOutput : [];
|
|
334
344
|
parts[toolPartIndex] = {
|
|
335
|
-
|
|
345
|
+
type: "dynamic-tool",
|
|
346
|
+
toolName,
|
|
347
|
+
toolCallId,
|
|
348
|
+
state: "input-streaming",
|
|
349
|
+
input,
|
|
336
350
|
output: [...existingOutput, chunk.payload.output]
|
|
337
351
|
};
|
|
338
352
|
}
|
|
@@ -655,6 +669,20 @@ const toAssistantUIMessage = (message) => {
|
|
|
655
669
|
}
|
|
656
670
|
return baseToolCall;
|
|
657
671
|
}
|
|
672
|
+
const requireApprovalMetadata = extendedMessage.metadata?.requireApprovalMetadata;
|
|
673
|
+
const partToolCallId = "toolCallId" in part && typeof part.toolCallId === "string" ? part.toolCallId : void 0;
|
|
674
|
+
const suspensionData = partToolCallId ? requireApprovalMetadata?.[partToolCallId] : void 0;
|
|
675
|
+
if (suspensionData) {
|
|
676
|
+
const toolName = "toolName" in part && typeof part.toolName === "string" ? part.toolName : part.type.startsWith("tool-") ? part.type.substring(5) : "";
|
|
677
|
+
return {
|
|
678
|
+
type: "tool-call",
|
|
679
|
+
toolCallId: partToolCallId,
|
|
680
|
+
toolName,
|
|
681
|
+
argsText: "input" in part ? JSON.stringify(part.input) : "{}",
|
|
682
|
+
args: "input" in part ? part.input : {},
|
|
683
|
+
metadata: extendedMessage.metadata
|
|
684
|
+
};
|
|
685
|
+
}
|
|
658
686
|
return {
|
|
659
687
|
type: "text",
|
|
660
688
|
text: "",
|
|
@@ -741,7 +769,6 @@ const resolveInitialMessages = (messages) => {
|
|
|
741
769
|
childMessages,
|
|
742
770
|
result: finalResult?.text || ""
|
|
743
771
|
};
|
|
744
|
-
console.log("json", json);
|
|
745
772
|
const nextMessage = {
|
|
746
773
|
role: "assistant",
|
|
747
774
|
parts: [
|
|
@@ -769,6 +796,18 @@ const resolveInitialMessages = (messages) => {
|
|
|
769
796
|
return message;
|
|
770
797
|
}
|
|
771
798
|
}
|
|
799
|
+
const extendedMessage = message;
|
|
800
|
+
const pendingToolApprovals = extendedMessage.metadata?.pendingToolApprovals;
|
|
801
|
+
if (pendingToolApprovals && typeof pendingToolApprovals === "object") {
|
|
802
|
+
return {
|
|
803
|
+
...message,
|
|
804
|
+
metadata: {
|
|
805
|
+
...message.metadata,
|
|
806
|
+
mode: "stream",
|
|
807
|
+
requireApprovalMetadata: pendingToolApprovals
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
772
811
|
return message;
|
|
773
812
|
});
|
|
774
813
|
};
|
|
@@ -1217,12 +1256,24 @@ const fromCoreUserMessageToUIMessage = (coreUserMessage) => {
|
|
|
1217
1256
|
};
|
|
1218
1257
|
};
|
|
1219
1258
|
|
|
1220
|
-
const useChat = ({ agentId, initializeMessages }) => {
|
|
1221
|
-
const
|
|
1259
|
+
const useChat = ({ agentId, resourceId, initializeMessages }) => {
|
|
1260
|
+
const extractRunIdFromMessages = (messages2) => {
|
|
1261
|
+
for (const message of messages2) {
|
|
1262
|
+
const pendingToolApprovals = message.metadata?.pendingToolApprovals;
|
|
1263
|
+
if (pendingToolApprovals && typeof pendingToolApprovals === "object") {
|
|
1264
|
+
const suspensionData = Object.values(pendingToolApprovals)[0];
|
|
1265
|
+
if (suspensionData?.runId) {
|
|
1266
|
+
return suspensionData.runId;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return void 0;
|
|
1271
|
+
};
|
|
1272
|
+
const initialMessages = initializeMessages?.() || [];
|
|
1273
|
+
const initialRunId = extractRunIdFromMessages(initialMessages);
|
|
1274
|
+
const _currentRunId = react.useRef(initialRunId);
|
|
1222
1275
|
const _onChunk = react.useRef(void 0);
|
|
1223
|
-
const [messages, setMessages] = react.useState(
|
|
1224
|
-
() => resolveInitialMessages(initializeMessages?.() || [])
|
|
1225
|
-
);
|
|
1276
|
+
const [messages, setMessages] = react.useState(() => resolveInitialMessages(initialMessages));
|
|
1226
1277
|
const [toolCallApprovals, setToolCallApprovals] = react.useState({});
|
|
1227
1278
|
const baseClient = useMastraClient();
|
|
1228
1279
|
const [isRunning, setIsRunning] = react.useState(false);
|
|
@@ -1254,7 +1305,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1254
1305
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1255
1306
|
const response = await agent.generate({
|
|
1256
1307
|
messages: coreUserMessages,
|
|
1257
|
-
runId:
|
|
1308
|
+
runId: uuid.v4(),
|
|
1258
1309
|
maxSteps,
|
|
1259
1310
|
modelSettings: {
|
|
1260
1311
|
frequencyPenalty,
|
|
@@ -1267,7 +1318,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1267
1318
|
},
|
|
1268
1319
|
instructions,
|
|
1269
1320
|
requestContext,
|
|
1270
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1321
|
+
...threadId ? { threadId, resourceId: resourceId || agentId } : {},
|
|
1271
1322
|
providerOptions
|
|
1272
1323
|
});
|
|
1273
1324
|
setIsRunning(false);
|
|
@@ -1302,7 +1353,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1302
1353
|
abortSignal: signal
|
|
1303
1354
|
});
|
|
1304
1355
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1305
|
-
const runId =
|
|
1356
|
+
const runId = uuid.v4();
|
|
1306
1357
|
const response = await agent.stream({
|
|
1307
1358
|
messages: coreUserMessages,
|
|
1308
1359
|
runId,
|
|
@@ -1318,7 +1369,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1318
1369
|
},
|
|
1319
1370
|
instructions,
|
|
1320
1371
|
requestContext,
|
|
1321
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1372
|
+
...threadId ? { threadId, resourceId: resourceId || agentId } : {},
|
|
1322
1373
|
providerOptions,
|
|
1323
1374
|
requireToolApproval
|
|
1324
1375
|
});
|
|
@@ -1347,6 +1398,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1347
1398
|
abortSignal: signal
|
|
1348
1399
|
});
|
|
1349
1400
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1401
|
+
const runId = uuid.v4();
|
|
1350
1402
|
const response = await agent.network({
|
|
1351
1403
|
messages: coreUserMessages,
|
|
1352
1404
|
maxSteps,
|
|
@@ -1359,9 +1411,9 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1359
1411
|
topK,
|
|
1360
1412
|
topP
|
|
1361
1413
|
},
|
|
1362
|
-
runId
|
|
1414
|
+
runId,
|
|
1363
1415
|
requestContext,
|
|
1364
|
-
...threadId ? { thread: threadId, resourceId: agentId } : {}
|
|
1416
|
+
...threadId ? { thread: threadId, resourceId: resourceId || agentId } : {}
|
|
1365
1417
|
});
|
|
1366
1418
|
const transformer = new AISdkNetworkTransformer();
|
|
1367
1419
|
await response.processDataStream({
|