@vegintech/langchain-react-agent 0.0.35 → 0.0.36

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.d.mts CHANGED
@@ -154,7 +154,7 @@ interface ToolCallInput {
154
154
  status?: "success" | "error";
155
155
  }
156
156
  /** 工具执行状态 */
157
- type ToolExecutionStatus = "pending" | "running" | "success" | "error";
157
+ type ToolExecutionStatus = "pending" | "running" | "success" | "error" | "cancelled";
158
158
  /** 工具执行结果 */
159
159
  interface ToolExecutionRecord {
160
160
  callId: string;
package/dist/index.mjs CHANGED
@@ -563,10 +563,16 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
563
563
  const batchResultsRef = useRef(/* @__PURE__ */ new Map());
564
564
  const isProcessingRef = useRef(false);
565
565
  const batchSubmittedRef = useRef(false);
566
+ const cancelledCallIdsRef = useRef(/* @__PURE__ */ new Set());
567
+ const cancelledExecutionsRef = useRef(/* @__PURE__ */ new Set());
566
568
  useEffect(() => {
567
569
  if (!completedToolResults || completedToolResults.size === 0) return;
568
570
  completedToolResults.forEach(({ result, status }, callId) => {
569
571
  if (notifiedCompletedRef.current.has(callId)) return;
572
+ if (cancelledExecutionsRef.current.has(callId)) {
573
+ notifiedCompletedRef.current.add(callId);
574
+ return;
575
+ }
570
576
  if (executedCallsRef.current.has(callId)) {
571
577
  notifiedCompletedRef.current.add(callId);
572
578
  return;
@@ -630,7 +636,12 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
630
636
  batchResultsRef.current.set(callId, createToolMessage(callId, call.name, null, "error", errorMessage));
631
637
  } finally {
632
638
  executingCallsRef.current.delete(callId);
633
- executedCallsRef.current.add(callId);
639
+ if (cancelledCallIdsRef.current.has(callId)) {
640
+ cancelledCallIdsRef.current.delete(callId);
641
+ cancelledExecutionsRef.current.add(callId);
642
+ batchResultsRef.current.delete(callId);
643
+ batchCallIdsRef.current.delete(callId);
644
+ } else executedCallsRef.current.add(callId);
634
645
  checkAndSubmitBatch();
635
646
  }
636
647
  }, [onExecutionChange, checkAndSubmitBatch]);
@@ -643,7 +654,7 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
643
654
  const frontendCalls = [];
644
655
  for (const call of toolCalls) {
645
656
  const callId = call.id;
646
- if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
657
+ if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId) || cancelledExecutionsRef.current.has(callId)) continue;
647
658
  const tool = findTool(tools, call.name);
648
659
  if (isLoading) {
649
660
  if (!pendingNotifiedRef.current.has(callId)) {
@@ -695,11 +706,36 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
695
706
  executeFrontendTool,
696
707
  onExecutionChange
697
708
  ]);
709
+ /**
710
+ * 取消当前正在执行或待执行的前端工具
711
+ *
712
+ * 由于 JavaScript 无法真正中断异步执行,取消仅做标记:
713
+ * 1. 正在执行的工具完成后会被无视(不提交结果)
714
+ * 2. 尚未执行的工具被直接移除,不再执行
715
+ */
716
+ const cancel = useCallback(() => {
717
+ const callIdsToCancel = new Set([...executingCallsRef.current, ...batchCallIdsRef.current]);
718
+ for (const callId of callIdsToCancel) {
719
+ cancelledCallIdsRef.current.add(callId);
720
+ if (!executingCallsRef.current.has(callId)) {
721
+ cancelledExecutionsRef.current.add(callId);
722
+ batchCallIdsRef.current.delete(callId);
723
+ }
724
+ const call = toolCalls.find((c) => c.id === callId);
725
+ if (call) onExecutionChange?.({
726
+ callId,
727
+ name: call.name,
728
+ args: call.args,
729
+ status: "cancelled"
730
+ });
731
+ }
732
+ }, [toolCalls, onExecutionChange]);
698
733
  useEffect(() => {
699
734
  processToolCalls();
700
735
  }, [useMemo(() => {
701
- return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
736
+ return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id) && !cancelledExecutionsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
702
737
  }, [toolCalls]), isLoading]);
738
+ return { cancel };
703
739
  }
704
740
  //#endregion
705
741
  //#region src/utils/messageUtils.ts
@@ -951,7 +987,6 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
951
987
  onSubmit: stream.submit
952
988
  });
953
989
  const [toolExecutions, setToolExecutions] = useState(/* @__PURE__ */ new Map());
954
- const isLoading = stream.isLoading;
955
990
  const { messages, toolResults } = useMemo(() => {
956
991
  return processMessages(stream.messages);
957
992
  }, [stream.messages]);
@@ -968,6 +1003,12 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
968
1003
  const frontendTools = useMemo(() => {
969
1004
  return tools?.filter(isFrontendTool) || [];
970
1005
  }, [tools]);
1006
+ const frontendToolNames = useMemo(() => new Set(frontendTools.map((t) => t.name)), [frontendTools]);
1007
+ const hasToolExecuting = useMemo(() => {
1008
+ for (const record of toolExecutions.values()) if (frontendToolNames.has(record.name) && (record.status === "running" || record.status === "pending")) return true;
1009
+ return false;
1010
+ }, [toolExecutions, frontendToolNames]);
1011
+ const isLoading = stream.isLoading || hasToolExecuting;
971
1012
  const submitToStream = useCallback(async (submitMessages) => {
972
1013
  await stream.submit({
973
1014
  ...agentState,
@@ -1012,10 +1053,7 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1012
1053
  if (messages.length === 0) return;
1013
1054
  await submitToStream(messages);
1014
1055
  }, [onPreSend, submitToStream]);
1015
- const handleStop = useCallback(async () => {
1016
- await stream.stop();
1017
- }, [stream]);
1018
- useToolExecution({
1056
+ const { cancel: cancelToolExecution } = useToolExecution({
1019
1057
  tools,
1020
1058
  toolCalls: allToolCalls,
1021
1059
  isLoading: stream.isLoading,
@@ -1023,6 +1061,10 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1023
1061
  onToolResultsBatch: handleToolResultsBatch,
1024
1062
  completedToolResults: toolResults
1025
1063
  });
1064
+ const handleStop = useCallback(async () => {
1065
+ cancelToolExecution();
1066
+ await stream.stop();
1067
+ }, [stream, cancelToolExecution]);
1026
1068
  const shouldRenderWelcome = messages.length === 0 && !isLoading && welcome;
1027
1069
  return /* @__PURE__ */ jsxs("div", {
1028
1070
  className: `agent-chat-container ${className}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegintech/langchain-react-agent",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "description": "LangChain Agent UI component library for React",
5
5
  "license": "MIT",
6
6
  "files": [