@vegintech/langchain-react-agent 0.0.34 → 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
@@ -150,9 +150,11 @@ interface ToolCallInput {
150
150
  args: Record<string, unknown>;
151
151
  /** 后端工具执行结果(来自 ToolMessage) */
152
152
  result?: unknown;
153
+ /** 后端工具执行状态(来自 ToolMessage.status) */
154
+ status?: "success" | "error";
153
155
  }
154
156
  /** 工具执行状态 */
155
- type ToolExecutionStatus = "pending" | "running" | "success" | "error";
157
+ type ToolExecutionStatus = "pending" | "running" | "success" | "error" | "cancelled";
156
158
  /** 工具执行结果 */
157
159
  interface ToolExecutionRecord {
158
160
  callId: string;
package/dist/index.mjs CHANGED
@@ -356,7 +356,7 @@ const renderToolCalls = (toolCalls, tools, toolExecutions, isLoading) => {
356
356
  callId: call.id,
357
357
  name: call.name,
358
358
  args: call.args,
359
- status: call.result !== void 0 ? "success" : "pending",
359
+ status: call.status || (call.result !== void 0 ? "success" : "pending"),
360
360
  result: call.result
361
361
  };
362
362
  return /* @__PURE__ */ jsx(ToolCallRenderer, {
@@ -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
- completedToolResults.forEach((result, callId) => {
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;
@@ -578,7 +584,7 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
578
584
  callId,
579
585
  name: call.name,
580
586
  args: call.args,
581
- status: "success",
587
+ status: call.status || "success",
582
588
  result
583
589
  });
584
590
  });
@@ -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
@@ -749,10 +785,14 @@ function toChatMessage(message, toolResults) {
749
785
  if (message.type === "human") msgType = "human";
750
786
  const toolCalls = extractToolCalls(message);
751
787
  let toolCallsWithResult = toolCalls;
752
- if (toolCalls) toolCallsWithResult = toolCalls.map((tc) => ({
753
- ...tc,
754
- result: toolResults.get(tc.id)
755
- }));
788
+ if (toolCalls) toolCallsWithResult = toolCalls.map((tc) => {
789
+ const toolResult = toolResults.get(tc.id);
790
+ return {
791
+ ...tc,
792
+ result: toolResult?.result,
793
+ status: toolResult?.status
794
+ };
795
+ });
756
796
  const reasoningContent = additionalKwargs.reasoning_content;
757
797
  return {
758
798
  id: message.id || crypto.randomUUID(),
@@ -765,7 +805,7 @@ function toChatMessage(message, toolResults) {
765
805
  };
766
806
  }
767
807
  /**
768
- * 预处理消息列表:建立 tool_call_id -> result 映射,并过滤 ToolMessage
808
+ * 预处理消息列表:建立 tool_call_id -> { result, status } 映射,并过滤 ToolMessage
769
809
  */
770
810
  function processMessages(rawMessages) {
771
811
  const toolResults = /* @__PURE__ */ new Map();
@@ -773,10 +813,17 @@ function processMessages(rawMessages) {
773
813
  const toolCallId = message.tool_call_id || message.additional_kwargs?.tool_call_id;
774
814
  if (toolCallId) {
775
815
  const textContent = extractTextFromContent(extractContent(message));
816
+ const status = message.status;
776
817
  try {
777
- toolResults.set(toolCallId, JSON.parse(textContent));
818
+ toolResults.set(toolCallId, {
819
+ result: JSON.parse(textContent),
820
+ status
821
+ });
778
822
  } catch {
779
- toolResults.set(toolCallId, textContent);
823
+ toolResults.set(toolCallId, {
824
+ result: textContent,
825
+ status
826
+ });
780
827
  }
781
828
  }
782
829
  }
@@ -940,7 +987,6 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
940
987
  onSubmit: stream.submit
941
988
  });
942
989
  const [toolExecutions, setToolExecutions] = useState(/* @__PURE__ */ new Map());
943
- const isLoading = stream.isLoading;
944
990
  const { messages, toolResults } = useMemo(() => {
945
991
  return processMessages(stream.messages);
946
992
  }, [stream.messages]);
@@ -957,6 +1003,12 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
957
1003
  const frontendTools = useMemo(() => {
958
1004
  return tools?.filter(isFrontendTool) || [];
959
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;
960
1012
  const submitToStream = useCallback(async (submitMessages) => {
961
1013
  await stream.submit({
962
1014
  ...agentState,
@@ -1001,10 +1053,7 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1001
1053
  if (messages.length === 0) return;
1002
1054
  await submitToStream(messages);
1003
1055
  }, [onPreSend, submitToStream]);
1004
- const handleStop = useCallback(async () => {
1005
- await stream.stop();
1006
- }, [stream]);
1007
- useToolExecution({
1056
+ const { cancel: cancelToolExecution } = useToolExecution({
1008
1057
  tools,
1009
1058
  toolCalls: allToolCalls,
1010
1059
  isLoading: stream.isLoading,
@@ -1012,6 +1061,10 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1012
1061
  onToolResultsBatch: handleToolResultsBatch,
1013
1062
  completedToolResults: toolResults
1014
1063
  });
1064
+ const handleStop = useCallback(async () => {
1065
+ cancelToolExecution();
1066
+ await stream.stop();
1067
+ }, [stream, cancelToolExecution]);
1015
1068
  const shouldRenderWelcome = messages.length === 0 && !isLoading && welcome;
1016
1069
  return /* @__PURE__ */ jsxs("div", {
1017
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.34",
3
+ "version": "0.0.36",
4
4
  "description": "LangChain Agent UI component library for React",
5
5
  "license": "MIT",
6
6
  "files": [