@mastra/react 0.1.0-beta.3 → 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 CHANGED
@@ -1,5 +1,44 @@
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
+
35
+ ## 0.1.0-beta.4
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [[`6a86fe5`](https://github.com/mastra-ai/mastra/commit/6a86fe56b8ff53ca2eb3ed87ffc0748749ebadce), [`595a3b8`](https://github.com/mastra-ai/mastra/commit/595a3b8727c901f44e333909c09843c711224440)]:
40
+ - @mastra/client-js@1.0.0-beta.4
41
+
3
42
  ## 0.1.0-beta.3
4
43
 
5
44
  ### 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
  }
@@ -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 _currentRunId = react.useRef(void 0);
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: agentId,
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 = agentId;
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: agentId,
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({