@mastra/react 0.0.20 → 0.0.21
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 +64 -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 +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# @mastra/react-hooks
|
|
2
2
|
|
|
3
|
+
## 0.0.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Configurable resourceId in react useChat ([#10561](https://github.com/mastra-ai/mastra/pull/10561))
|
|
8
|
+
|
|
9
|
+
- fix(agent): persist messages before tool suspension ([#10542](https://github.com/mastra-ai/mastra/pull/10542))
|
|
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 []:
|
|
33
|
+
- @mastra/client-js@0.16.15
|
|
34
|
+
|
|
35
|
+
## 0.0.21-alpha.0
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Configurable resourceId in react useChat ([#10561](https://github.com/mastra-ai/mastra/pull/10561))
|
|
40
|
+
|
|
41
|
+
- fix(agent): persist messages before tool suspension ([#10542](https://github.com/mastra-ai/mastra/pull/10542))
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
**Backend changes (@mastra/core):**
|
|
46
|
+
- Add assistant messages to messageList immediately after LLM execution
|
|
47
|
+
- Flush messages synchronously before suspension to persist state
|
|
48
|
+
- Create thread if it doesn't exist before flushing
|
|
49
|
+
- Add metadata helpers to persist and remove tool approval state
|
|
50
|
+
- Pass saveQueueManager and memory context through workflow for immediate persistence
|
|
51
|
+
|
|
52
|
+
**Frontend changes (@mastra/react):**
|
|
53
|
+
- Extract runId from pending approvals to enable resumption after refresh
|
|
54
|
+
- Convert `pendingToolApprovals` (DB format) to `requireApprovalMetadata` (runtime format)
|
|
55
|
+
- Handle both `dynamic-tool` and `tool-{NAME}` part types for approval state
|
|
56
|
+
- Change runId from hardcoded `agentId` to unique `uuid()`
|
|
57
|
+
|
|
58
|
+
**UI changes (@mastra/playground-ui):**
|
|
59
|
+
- Handle tool calls awaiting approval in message initialization
|
|
60
|
+
- Convert approval metadata format when loading initial messages
|
|
61
|
+
|
|
62
|
+
Fixes #9745, #9906
|
|
63
|
+
|
|
64
|
+
- Updated dependencies []:
|
|
65
|
+
- @mastra/client-js@0.16.15-alpha.0
|
|
66
|
+
|
|
3
67
|
## 0.0.20
|
|
4
68
|
|
|
5
69
|
### 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
|
}
|
|
@@ -646,6 +660,20 @@ const toAssistantUIMessage = (message) => {
|
|
|
646
660
|
}
|
|
647
661
|
return baseToolCall;
|
|
648
662
|
}
|
|
663
|
+
const requireApprovalMetadata = extendedMessage.metadata?.requireApprovalMetadata;
|
|
664
|
+
const partToolCallId = "toolCallId" in part && typeof part.toolCallId === "string" ? part.toolCallId : void 0;
|
|
665
|
+
const suspensionData = partToolCallId ? requireApprovalMetadata?.[partToolCallId] : void 0;
|
|
666
|
+
if (suspensionData) {
|
|
667
|
+
const toolName = "toolName" in part && typeof part.toolName === "string" ? part.toolName : part.type.startsWith("tool-") ? part.type.substring(5) : "";
|
|
668
|
+
return {
|
|
669
|
+
type: "tool-call",
|
|
670
|
+
toolCallId: partToolCallId,
|
|
671
|
+
toolName,
|
|
672
|
+
argsText: "input" in part ? JSON.stringify(part.input) : "{}",
|
|
673
|
+
args: "input" in part ? part.input : {},
|
|
674
|
+
metadata: extendedMessage.metadata
|
|
675
|
+
};
|
|
676
|
+
}
|
|
649
677
|
return {
|
|
650
678
|
type: "text",
|
|
651
679
|
text: "",
|
|
@@ -730,7 +758,6 @@ const resolveInitialMessages = (messages) => {
|
|
|
730
758
|
childMessages,
|
|
731
759
|
result: finalResult?.text || ""
|
|
732
760
|
};
|
|
733
|
-
console.log("json", json);
|
|
734
761
|
const nextMessage = {
|
|
735
762
|
role: "assistant",
|
|
736
763
|
parts: [
|
|
@@ -758,6 +785,18 @@ const resolveInitialMessages = (messages) => {
|
|
|
758
785
|
return message;
|
|
759
786
|
}
|
|
760
787
|
}
|
|
788
|
+
const extendedMessage = message;
|
|
789
|
+
const pendingToolApprovals = extendedMessage.metadata?.pendingToolApprovals;
|
|
790
|
+
if (pendingToolApprovals && typeof pendingToolApprovals === "object") {
|
|
791
|
+
return {
|
|
792
|
+
...message,
|
|
793
|
+
metadata: {
|
|
794
|
+
...message.metadata,
|
|
795
|
+
mode: "stream",
|
|
796
|
+
requireApprovalMetadata: pendingToolApprovals
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
761
800
|
return message;
|
|
762
801
|
});
|
|
763
802
|
};
|
|
@@ -1161,12 +1200,24 @@ class AISdkNetworkTransformer {
|
|
|
1161
1200
|
};
|
|
1162
1201
|
}
|
|
1163
1202
|
|
|
1164
|
-
const useChat = ({ agentId, initializeMessages }) => {
|
|
1165
|
-
const
|
|
1203
|
+
const useChat = ({ agentId, resourceId, initializeMessages }) => {
|
|
1204
|
+
const extractRunIdFromMessages = (messages2) => {
|
|
1205
|
+
for (const message of messages2) {
|
|
1206
|
+
const pendingToolApprovals = message.metadata?.pendingToolApprovals;
|
|
1207
|
+
if (pendingToolApprovals && typeof pendingToolApprovals === "object") {
|
|
1208
|
+
const suspensionData = Object.values(pendingToolApprovals)[0];
|
|
1209
|
+
if (suspensionData?.runId) {
|
|
1210
|
+
return suspensionData.runId;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return void 0;
|
|
1215
|
+
};
|
|
1216
|
+
const initialMessages = initializeMessages?.() || [];
|
|
1217
|
+
const initialRunId = extractRunIdFromMessages(initialMessages);
|
|
1218
|
+
const _currentRunId = react.useRef(initialRunId);
|
|
1166
1219
|
const _onChunk = react.useRef(void 0);
|
|
1167
|
-
const [messages, setMessages] = react.useState(
|
|
1168
|
-
() => resolveInitialMessages(initializeMessages?.() || [])
|
|
1169
|
-
);
|
|
1220
|
+
const [messages, setMessages] = react.useState(() => resolveInitialMessages(initialMessages));
|
|
1170
1221
|
const [toolCallApprovals, setToolCallApprovals] = react.useState({});
|
|
1171
1222
|
const baseClient = useMastraClient();
|
|
1172
1223
|
const [isRunning, setIsRunning] = react.useState(false);
|
|
@@ -1198,7 +1249,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1198
1249
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1199
1250
|
const response = await agent.generate({
|
|
1200
1251
|
messages: coreUserMessages,
|
|
1201
|
-
runId:
|
|
1252
|
+
runId: uuid.v4(),
|
|
1202
1253
|
maxSteps,
|
|
1203
1254
|
modelSettings: {
|
|
1204
1255
|
frequencyPenalty,
|
|
@@ -1211,7 +1262,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1211
1262
|
},
|
|
1212
1263
|
instructions,
|
|
1213
1264
|
runtimeContext,
|
|
1214
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1265
|
+
...threadId ? { threadId, resourceId: resourceId || agentId } : {},
|
|
1215
1266
|
providerOptions
|
|
1216
1267
|
});
|
|
1217
1268
|
setIsRunning(false);
|
|
@@ -1246,7 +1297,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1246
1297
|
abortSignal: signal
|
|
1247
1298
|
});
|
|
1248
1299
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1249
|
-
const runId =
|
|
1300
|
+
const runId = uuid.v4();
|
|
1250
1301
|
const response = await agent.stream({
|
|
1251
1302
|
messages: coreUserMessages,
|
|
1252
1303
|
runId,
|
|
@@ -1262,7 +1313,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1262
1313
|
},
|
|
1263
1314
|
instructions,
|
|
1264
1315
|
runtimeContext,
|
|
1265
|
-
...threadId ? { threadId, resourceId: agentId } : {},
|
|
1316
|
+
...threadId ? { threadId, resourceId: resourceId || agentId } : {},
|
|
1266
1317
|
providerOptions,
|
|
1267
1318
|
requireToolApproval
|
|
1268
1319
|
});
|
|
@@ -1291,6 +1342,7 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1291
1342
|
abortSignal: signal
|
|
1292
1343
|
});
|
|
1293
1344
|
const agent = clientWithAbort.getAgent(agentId);
|
|
1345
|
+
const runId = uuid.v4();
|
|
1294
1346
|
const response = await agent.network({
|
|
1295
1347
|
messages: coreUserMessages,
|
|
1296
1348
|
maxSteps,
|
|
@@ -1303,9 +1355,9 @@ const useChat = ({ agentId, initializeMessages }) => {
|
|
|
1303
1355
|
topK,
|
|
1304
1356
|
topP
|
|
1305
1357
|
},
|
|
1306
|
-
runId
|
|
1358
|
+
runId,
|
|
1307
1359
|
runtimeContext,
|
|
1308
|
-
...threadId ? { thread: threadId, resourceId: agentId } : {}
|
|
1360
|
+
...threadId ? { thread: threadId, resourceId: resourceId || agentId } : {}
|
|
1309
1361
|
});
|
|
1310
1362
|
const transformer = new AISdkNetworkTransformer();
|
|
1311
1363
|
await response.processDataStream({
|