@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 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: toolPart.toolName,
272
- toolCallId: toolPart.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: toolPart.toolName,
292
- toolCallId: toolPart.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
- ...toolPart,
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
- ...toolPart,
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 _currentRunId = react.useRef(void 0);
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: agentId,
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 = agentId;
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: agentId,
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({