@mastra/react 1.4.12 → 1.4.13-alpha.1

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/dist/index.js CHANGED
@@ -1795,7 +1795,7 @@ const extractRunIdFromMessages = (messages) => {
1795
1795
  };
1796
1796
  //#endregion
1797
1797
  //#region src/agent/hooks.ts
1798
- const extractPendingToolApprovalIdsFromMessages = (messages) => {
1798
+ const extractPendingToolApprovalIdsFromMessages = (messages, runId) => {
1799
1799
  const pendingToolApprovalIds = /* @__PURE__ */ new Set();
1800
1800
  for (const message of messages) {
1801
1801
  const metadata = message.content?.metadata;
@@ -1808,6 +1808,7 @@ const extractPendingToolApprovalIdsFromMessages = (messages) => {
1808
1808
  for (const source of metadataSources) {
1809
1809
  if (!source || typeof source !== "object") continue;
1810
1810
  for (const suspensionData of Object.values(source)) {
1811
+ if (runId && suspensionData?.runId !== runId) continue;
1811
1812
  const toolCallId = suspensionData?.toolCallId;
1812
1813
  if (typeof toolCallId === "string" && toolCallId.length > 0) pendingToolApprovalIds.add(toolCallId);
1813
1814
  }
@@ -1821,6 +1822,11 @@ const toolCallHasOutput = (parts, toolCallId) => parts.some((part) => {
1821
1822
  if (invocation.toolCallId !== toolCallId) return false;
1822
1823
  return invocation.state === "result" || invocation.result != null;
1823
1824
  });
1825
+ const filterUnresolvedApprovals = (entries, parts) => {
1826
+ if (!entries || typeof entries !== "object") return void 0;
1827
+ const pending = Object.fromEntries(Object.entries(entries).filter(([, approval]) => approval && typeof approval === "object" && typeof approval.toolCallId === "string" && !toolCallHasOutput(parts, approval.toolCallId)));
1828
+ return Object.keys(pending).length ? pending : void 0;
1829
+ };
1824
1830
  /**
1825
1831
  * Normalize persisted initial messages back into the stream-friendly shape the
1826
1832
  * UI renders from. Mirrors `main`'s `resolveInitialMessages`:
@@ -1850,22 +1856,25 @@ const resolveInitialMessages = (messages) => messages.filter((message) => {
1850
1856
  };
1851
1857
  })() : message;
1852
1858
  const normalizedMetadata = normalizedMessage.content?.metadata;
1853
- const pendingToolApprovals = normalizedMetadata?.pendingToolApprovals;
1854
- if (!pendingToolApprovals || typeof pendingToolApprovals !== "object") return normalizedMessage;
1855
- const stillPending = Object.fromEntries(Object.entries(pendingToolApprovals).filter(([, approval]) => approval && typeof approval === "object" && typeof approval.toolCallId === "string" && !toolCallHasOutput(normalizedMessage.content.parts, approval.toolCallId)));
1856
- const { pendingToolApprovals: _omit, ...restMetadata } = normalizedMetadata;
1857
- const hasStillPending = Object.keys(stillPending).length > 0;
1859
+ if (!normalizedMetadata?.pendingToolApprovals && !normalizedMetadata?.requireApprovalMetadata && !normalizedMetadata?.suspendedTools) return normalizedMessage;
1860
+ const { pendingToolApprovals, requireApprovalMetadata, suspendedTools, ...restMetadata } = normalizedMetadata;
1861
+ const parts = normalizedMessage.content.parts;
1862
+ const pending = filterUnresolvedApprovals(pendingToolApprovals, parts);
1863
+ const required = {
1864
+ ...filterUnresolvedApprovals(requireApprovalMetadata, parts),
1865
+ ...pending
1866
+ };
1867
+ const suspended = filterUnresolvedApprovals(suspendedTools, parts);
1858
1868
  return {
1859
1869
  ...normalizedMessage,
1860
1870
  content: {
1861
1871
  ...normalizedMessage.content,
1862
1872
  metadata: {
1863
1873
  ...restMetadata,
1864
- mode: "stream",
1865
- ...hasStillPending ? {
1866
- pendingToolApprovals: stillPending,
1867
- requireApprovalMetadata: stillPending
1868
- } : {}
1874
+ ...pendingToolApprovals ? { mode: "stream" } : {},
1875
+ ...pending ? { pendingToolApprovals: pending } : {},
1876
+ ...Object.keys(required).length ? { requireApprovalMetadata: required } : {},
1877
+ ...suspended ? { suspendedTools: suspended } : {}
1869
1878
  }
1870
1879
  }
1871
1880
  };
@@ -1914,20 +1923,64 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
1914
1923
  const _threadSignalsUnsupportedRef = useRef(false);
1915
1924
  const [messages, setMessages] = useState([]);
1916
1925
  const [tasks, setTasks] = useState([]);
1926
+ const liveTasks = useRef(void 0);
1917
1927
  const [toolCallApprovals, setToolCallApprovals] = useState({});
1918
1928
  const [networkToolCallApprovals, setNetworkToolCallApprovals] = useState({});
1919
1929
  const pendingToolApprovalIdsRef = useRef(/* @__PURE__ */ new Set());
1930
+ const liveApprovalIds = useRef(/* @__PURE__ */ new Set());
1931
+ const liveRunId = useRef(void 0);
1932
+ const liveRunFinished = useRef(false);
1920
1933
  const [isAwaitingToolApproval, setIsAwaitingToolApproval] = useState(false);
1921
1934
  const baseClient = useMastraClient();
1922
1935
  const [isRunning, setIsRunning] = useState(false);
1936
+ const lastHydration = useRef(void 0);
1923
1937
  useEffect(() => {
1938
+ const previous = lastHydration.current;
1939
+ const sameThread = previous?.agentId === agentId && previous.resourceId === resourceId && previous.threadId === threadId;
1940
+ if (sameThread && previous.initialMessages === initialMessages) return;
1924
1941
  const formattedMessages = resolveInitialMessages(initialMessages ?? []);
1925
- setMessages(formattedMessages);
1926
- setTasks(extractLatestTasksFromMessages(formattedMessages));
1927
- pendingToolApprovalIdsRef.current = extractPendingToolApprovalIdsFromMessages(formattedMessages);
1928
- setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
1929
- _currentRunId.current = extractRunIdFromMessages(formattedMessages);
1930
- }, [initialMessages]);
1942
+ lastHydration.current = {
1943
+ agentId,
1944
+ resourceId,
1945
+ threadId,
1946
+ initialMessages,
1947
+ formattedMessages
1948
+ };
1949
+ if (sameThread) {
1950
+ const previousById = new Map(previous.formattedMessages.map((message) => [message.id, message]));
1951
+ setMessages((current) => {
1952
+ const live = current.filter((message) => previousById.get(message.id) !== message);
1953
+ const liveById = new Map(live.map((message) => [message.id, message]));
1954
+ const historyIds = new Set(formattedMessages.map((message) => message.id));
1955
+ return [...formattedMessages.map((message) => liveById.get(message.id) ?? message), ...live.filter((message) => !historyIds.has(message.id))];
1956
+ });
1957
+ setTasks(liveTasks.current ?? extractLatestTasksFromMessages(formattedMessages));
1958
+ const historyRunId = extractRunIdFromMessages(formattedMessages);
1959
+ if (liveRunFinished.current) return;
1960
+ if (!liveRunId.current && isRunning && historyRunId !== _currentRunId.current) return;
1961
+ } else {
1962
+ liveTasks.current = void 0;
1963
+ liveApprovalIds.current.clear();
1964
+ liveRunId.current = void 0;
1965
+ liveRunFinished.current = false;
1966
+ if (previous) setIsRunning(false);
1967
+ setMessages(formattedMessages);
1968
+ setTasks(extractLatestTasksFromMessages(formattedMessages));
1969
+ }
1970
+ const pendingApprovals = extractPendingToolApprovalIdsFromMessages(formattedMessages, liveRunId.current);
1971
+ for (const toolCallId of liveApprovalIds.current) if (pendingToolApprovalIdsRef.current.has(toolCallId)) pendingApprovals.add(toolCallId);
1972
+ else pendingApprovals.delete(toolCallId);
1973
+ pendingToolApprovalIdsRef.current = pendingApprovals;
1974
+ setIsAwaitingToolApproval(pendingApprovals.size > 0);
1975
+ _currentRunId.current = liveRunId.current ?? extractRunIdFromMessages(formattedMessages);
1976
+ }, [
1977
+ agentId,
1978
+ resourceId,
1979
+ threadId,
1980
+ initialMessages,
1981
+ isRunning,
1982
+ isAwaitingToolApproval
1983
+ ]);
1931
1984
  useEffect(() => {
1932
1985
  _activeContinuation.current = {
1933
1986
  ..._activeContinuation.current,
@@ -1990,29 +2043,40 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
1990
2043
  _threadSubscriptionPromiseRef.current = null;
1991
2044
  }, []);
1992
2045
  const processStreamChunk = useCallback(async (chunk, onChunk) => {
2046
+ const isTerminal = chunk.type === "finish" || chunk.type === "abort" || chunk.type === "error";
2047
+ if (isTerminal && liveRunId.current && chunk.runId !== liveRunId.current) return;
1993
2048
  setMessages((prev) => accumulateChunk({
1994
2049
  chunk,
1995
2050
  conversation: prev,
1996
2051
  metadata: { mode: "stream" }
1997
2052
  }));
1998
- const signalTasks = extractTasksFromSignalChunk(chunk);
1999
- if (signalTasks !== void 0) setTasks(signalTasks);
2000
- const toolTasks = extractTasksFromToolResultChunk(chunk);
2001
- if (toolTasks !== void 0) setTasks(toolTasks);
2053
+ const streamedTasks = extractTasksFromToolResultChunk(chunk) ?? extractTasksFromSignalChunk(chunk);
2054
+ if (streamedTasks !== void 0) {
2055
+ liveTasks.current = streamedTasks;
2056
+ setTasks(streamedTasks);
2057
+ }
2002
2058
  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);
2003
2059
  if (chunk.type === "start") {
2004
2060
  setIsRunning(true);
2005
- if ("runId" in chunk && typeof chunk.runId === "string") _currentRunId.current = chunk.runId;
2061
+ if ("runId" in chunk && typeof chunk.runId === "string") {
2062
+ if (liveRunId.current !== chunk.runId) liveApprovalIds.current.clear();
2063
+ liveRunFinished.current = false;
2064
+ liveRunId.current = chunk.runId;
2065
+ _currentRunId.current = chunk.runId;
2066
+ }
2006
2067
  }
2007
2068
  if (chunk.type === "tool-call-approval" || chunk.type === "tool-call-suspended") {
2008
2069
  const toolCallId = chunk.payload?.toolCallId;
2009
2070
  if (typeof toolCallId === "string") {
2071
+ liveApprovalIds.current.add(toolCallId);
2010
2072
  pendingToolApprovalIdsRef.current.add(toolCallId);
2011
2073
  setIsAwaitingToolApproval(true);
2012
2074
  }
2013
2075
  setIsRunning(false);
2014
2076
  }
2015
- if (chunk.type === "finish" || chunk.type === "abort" || chunk.type === "error") {
2077
+ if (isTerminal) {
2078
+ if (chunk.runId === liveRunId.current) liveRunFinished.current = true;
2079
+ for (const toolCallId of pendingToolApprovalIdsRef.current) liveApprovalIds.current.add(toolCallId);
2016
2080
  pendingToolApprovalIdsRef.current.clear();
2017
2081
  setIsAwaitingToolApproval(false);
2018
2082
  setIsRunning(false);
@@ -2293,30 +2357,28 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
2293
2357
  onSignalSent?.(echoedSignalId, getSignalPreview(coreUserMessages));
2294
2358
  if (pendingToolApprovalIdsRef.current.size > 0) setIsRunning(false);
2295
2359
  } catch (error) {
2296
- if (isThreadSignalUnsupportedError(error)) {
2360
+ if (isThreadSignalUnsupportedError(error)) try {
2361
+ await agent.sendSignal({
2362
+ signal: {
2363
+ id: resolvedSignalId,
2364
+ type: "user-message",
2365
+ contents: messageContents
2366
+ },
2367
+ resourceId: resourceId || agentId,
2368
+ threadId,
2369
+ ifIdle: { streamOptions }
2370
+ });
2297
2371
  onSignalSent?.(resolvedSignalId, getSignalPreview(coreUserMessages));
2298
- try {
2299
- await agent.sendSignal({
2300
- signal: {
2301
- id: resolvedSignalId,
2302
- type: "user-message",
2303
- contents: messageContents
2304
- },
2305
- resourceId: resourceId || agentId,
2306
- threadId,
2307
- ifIdle: { streamOptions }
2308
- });
2372
+ return;
2373
+ } catch (signalError) {
2374
+ onSignalEcho?.(resolvedSignalId);
2375
+ if (isThreadSignalUnsupportedError(signalError)) {
2376
+ markThreadSignalsUnsupported();
2377
+ setMessages((prev) => [...prev, fromCoreUserMessagesToMastraDBMessage(coreUserMessages)]);
2378
+ await streamWithLegacyRoute();
2309
2379
  return;
2310
- } catch (signalError) {
2311
- onSignalEcho?.(resolvedSignalId);
2312
- if (isThreadSignalUnsupportedError(signalError)) {
2313
- markThreadSignalsUnsupported();
2314
- setMessages((prev) => [...prev, fromCoreUserMessagesToMastraDBMessage(coreUserMessages)]);
2315
- await streamWithLegacyRoute();
2316
- return;
2317
- }
2318
- throw signalError;
2319
2380
  }
2381
+ throw signalError;
2320
2382
  }
2321
2383
  throw error;
2322
2384
  }
@@ -2377,6 +2439,7 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
2377
2439
  });
2378
2440
  closeThreadSubscription();
2379
2441
  setMessages((prev) => finishStreamingAssistantMessage(prev));
2442
+ liveRunFinished.current = true;
2380
2443
  pendingToolApprovalIdsRef.current.clear();
2381
2444
  setIsAwaitingToolApproval(false);
2382
2445
  setIsRunning(false);
@@ -2408,6 +2471,8 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
2408
2471
  ...resumeData !== void 0 ? { resumeData } : {},
2409
2472
  requestContext: continuation.requestContext
2410
2473
  });
2474
+ liveRunId.current ??= currentRunId;
2475
+ liveApprovalIds.current.add(toolCallId);
2411
2476
  pendingToolApprovalIdsRef.current.delete(toolCallId);
2412
2477
  setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
2413
2478
  setIsRunning(false);
@@ -2466,6 +2531,8 @@ const useChat = ({ agentId, resourceId, threadId, initialMessages, requestContex
2466
2531
  ...continuation.model !== void 0 ? { streamOptions: { model: continuation.model } } : {},
2467
2532
  requestContext: continuation.requestContext
2468
2533
  });
2534
+ liveRunId.current ??= currentRunId;
2535
+ liveApprovalIds.current.add(toolCallId);
2469
2536
  pendingToolApprovalIdsRef.current.delete(toolCallId);
2470
2537
  setIsAwaitingToolApproval(pendingToolApprovalIdsRef.current.size > 0);
2471
2538
  setIsRunning(false);