@librechat/agents 3.3.10 → 3.3.12
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/cjs/graphs/Graph.cjs +248 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +13 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +160 -43
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +190 -13
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +723 -0
- package/dist/cjs/llm/streamLimits.cjs.map +1 -0
- package/dist/cjs/main.cjs +12 -3
- package/dist/cjs/messages/core.cjs +592 -27
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +105 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +157 -37
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +4 -3
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +77 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +3 -2
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-search.cjs +3 -1
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +7 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-search.cjs +3 -1
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +26 -8
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +30 -10
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +3 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +17 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -1
- package/dist/cjs/utils/misc.cjs +12 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/proxy.cjs +63 -0
- package/dist/cjs/utils/proxy.cjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +247 -26
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +13 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +160 -43
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +192 -15
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +704 -0
- package/dist/esm/llm/streamLimits.mjs.map +1 -0
- package/dist/esm/main.mjs +8 -6
- package/dist/esm/messages/core.mjs +592 -28
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +105 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +157 -37
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +4 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +77 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +3 -2
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +7 -1
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/crw-search.mjs +3 -1
- package/dist/esm/tools/search/crw-search.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +7 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-search.mjs +3 -1
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +26 -8
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +30 -10
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +7 -1
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +3 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +17 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -1
- package/dist/esm/utils/misc.mjs +12 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/proxy.mjs +62 -0
- package/dist/esm/utils/proxy.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +53 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
- package/dist/types/llm/invoke.d.ts +26 -5
- package/dist/types/llm/openai/index.d.ts +3 -0
- package/dist/types/llm/streamLimits.d.ts +314 -0
- package/dist/types/messages/core.d.ts +11 -6
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +27 -2
- package/dist/types/tools/BashExecutor.d.ts +2 -2
- package/dist/types/tools/CodeExecutor.d.ts +3 -3
- package/dist/types/tools/ToolNode.d.ts +11 -1
- package/dist/types/tools/search/crw-scraper.d.ts +2 -0
- package/dist/types/tools/search/firecrawl.d.ts +2 -0
- package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
- package/dist/types/tools/search/rerankers.d.ts +9 -5
- package/dist/types/tools/search/serper-scraper.d.ts +2 -0
- package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
- package/dist/types/tools/search/types.d.ts +28 -33
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/run.d.ts +46 -1
- package/dist/types/types/tools.d.ts +21 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/misc.d.ts +7 -0
- package/dist/types/utils/proxy.d.ts +31 -0
- package/package.json +2 -1
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
- package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
- package/src/__tests__/stream.streamLimits.test.ts +1982 -0
- package/src/graphs/Graph.ts +302 -27
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
- package/src/index.ts +11 -0
- package/src/langfuseToolOutputTracing.ts +410 -14
- package/src/llm/bedrock/index.ts +22 -2
- package/src/llm/custom-chat-models.smoke.test.ts +747 -0
- package/src/llm/invoke.streamLimits.test.ts +142 -0
- package/src/llm/invoke.test.ts +187 -1
- package/src/llm/invoke.ts +231 -43
- package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
- package/src/llm/openai/index.ts +348 -31
- package/src/llm/openai/llm.spec.ts +107 -6
- package/src/llm/streamLimits.test.ts +450 -0
- package/src/llm/streamLimits.ts +1158 -0
- package/src/messages/core.ts +1290 -42
- package/src/messages/formatAgentMessages.test.ts +2623 -0
- package/src/run.ts +4 -0
- package/src/session/AgentSession.ts +5 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
- package/src/specs/preemptSeal.test.ts +374 -5
- package/src/stream.ts +141 -6
- package/src/summarization/__tests__/node.test.ts +269 -0
- package/src/summarization/chunkHandler.test.ts +196 -0
- package/src/summarization/node.ts +203 -6
- package/src/tools/BashExecutor.ts +4 -3
- package/src/tools/CodeExecutor.ts +5 -4
- package/src/tools/ProgrammaticToolCalling.ts +7 -5
- package/src/tools/ToolNode.ts +109 -6
- package/src/tools/ToolSearch.ts +4 -3
- package/src/tools/__tests__/BashExecutor.test.ts +2 -2
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
- package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
- package/src/tools/search/crw-scraper.ts +6 -0
- package/src/tools/search/crw-search.ts +6 -1
- package/src/tools/search/firecrawl.ts +6 -0
- package/src/tools/search/http-agent.test.ts +133 -0
- package/src/tools/search/keenable-scraper.ts +6 -0
- package/src/tools/search/keenable-search.ts +6 -1
- package/src/tools/search/rerankers.ts +36 -10
- package/src/tools/search/search.ts +29 -11
- package/src/tools/search/serper-scraper.ts +6 -0
- package/src/tools/search/tavily-scraper.ts +6 -0
- package/src/tools/search/tavily-search.ts +2 -0
- package/src/tools/search/tool.ts +16 -0
- package/src/tools/search/types.ts +31 -33
- package/src/tools/subagent/SubagentExecutor.ts +96 -3
- package/src/types/graph.ts +7 -0
- package/src/types/run.ts +49 -1
- package/src/types/tools.ts +21 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +19 -0
- package/src/utils/proxy.test.ts +176 -0
- package/src/utils/proxy.ts +93 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from '@langchain/core/messages';
|
|
7
7
|
import type { UsageMetadata, BaseMessage } from '@langchain/core/messages';
|
|
8
8
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
9
|
+
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
9
10
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
10
11
|
import type { HookRegistry } from '@/hooks';
|
|
11
12
|
import type { OnChunk } from '@/llm/invoke';
|
|
@@ -33,6 +34,11 @@ import {
|
|
|
33
34
|
Providers,
|
|
34
35
|
} from '@/common';
|
|
35
36
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
37
|
+
import {
|
|
38
|
+
enforceStreamLimitsForWireChunk,
|
|
39
|
+
StreamLimitExceededError,
|
|
40
|
+
STREAM_LIMIT_EPOCH_KEY,
|
|
41
|
+
} from '@/llm/streamLimits';
|
|
36
42
|
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
37
43
|
import { calculateMaxToolResultChars } from '@/utils/truncation';
|
|
38
44
|
import { createRemoveAllMessage } from '@/messages/reducer';
|
|
@@ -611,6 +617,8 @@ async function executeSummarizationWithFallback(params: {
|
|
|
611
617
|
stepId: string;
|
|
612
618
|
usePromptCache: boolean;
|
|
613
619
|
log: LogFn;
|
|
620
|
+
/** Carries the run's stream limits so the event cap covers summary streams. */
|
|
621
|
+
graph?: StreamLimitState & { getBreakerSignal?: () => AbortSignal };
|
|
614
622
|
}): Promise<{
|
|
615
623
|
text: string;
|
|
616
624
|
usage?: Partial<UsageMetadata>;
|
|
@@ -629,6 +637,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
629
637
|
stepId,
|
|
630
638
|
usePromptCache,
|
|
631
639
|
log,
|
|
640
|
+
graph,
|
|
632
641
|
} = params;
|
|
633
642
|
|
|
634
643
|
const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';
|
|
@@ -659,6 +668,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
659
668
|
stepId,
|
|
660
669
|
provider: clientConfig.provider as Providers,
|
|
661
670
|
reasoningKey: agentContext.reasoningKey,
|
|
671
|
+
graph,
|
|
662
672
|
usePromptCache,
|
|
663
673
|
promptCacheTtl:
|
|
664
674
|
(clientConfig.provider as Providers) === Providers.ANTHROPIC ||
|
|
@@ -686,6 +696,34 @@ async function executeSummarizationWithFallback(params: {
|
|
|
686
696
|
messagesToRefineCount: messages.length,
|
|
687
697
|
});
|
|
688
698
|
|
|
699
|
+
/**
|
|
700
|
+
* A tripped stream circuit breaker is a safety abort, not a
|
|
701
|
+
* summarization failure to recover from: retrying on fallback providers
|
|
702
|
+
* would spend more provider work after the limit fired, and degrading to
|
|
703
|
+
* the metadata stub would let the run continue (and periodic compaction
|
|
704
|
+
* commit the stub) despite the `StreamLimits` contract that a breach
|
|
705
|
+
* rejects the run. Rethrown so the summarize node fails the run with
|
|
706
|
+
* the actionable limit error, consistent with agent turns and subagents.
|
|
707
|
+
*/
|
|
708
|
+
if (primaryError instanceof StreamLimitExceededError) {
|
|
709
|
+
throw primaryError;
|
|
710
|
+
}
|
|
711
|
+
/** A parallel branch's trip aborts the composed summarization signal,
|
|
712
|
+
* and a provider can surface that as a generic abort error; entering
|
|
713
|
+
* fallback recovery or degrading to the metadata stub would continue
|
|
714
|
+
* work after the run-wide safety abort. Checked on BOTH channels: the
|
|
715
|
+
* graph's own breaker, and the composed config signal that carries a
|
|
716
|
+
* parent run's trip into subagent child graphs. */
|
|
717
|
+
{
|
|
718
|
+
const trippedReason = findStreamLimitAbortReason(
|
|
719
|
+
graph?.getBreakerSignal?.(),
|
|
720
|
+
summarizeConfig?.signal
|
|
721
|
+
);
|
|
722
|
+
if (trippedReason != null) {
|
|
723
|
+
throw trippedReason;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
689
727
|
const rawFallbacks = (
|
|
690
728
|
clientConfig.clientOptions as unknown as t.LLMConfig | undefined
|
|
691
729
|
)?.fallbacks;
|
|
@@ -697,6 +735,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
697
735
|
config: traceConfig(summarizeConfig, 'cache_hit_compaction'),
|
|
698
736
|
provider: clientConfig.provider as Providers,
|
|
699
737
|
reasoningKey: agentContext.reasoningKey,
|
|
738
|
+
graph,
|
|
700
739
|
});
|
|
701
740
|
const fbResult = await tryFallbackProviders({
|
|
702
741
|
fallbacks,
|
|
@@ -711,9 +750,13 @@ async function executeSummarizationWithFallback(params: {
|
|
|
711
750
|
)
|
|
712
751
|
),
|
|
713
752
|
],
|
|
714
|
-
config:
|
|
753
|
+
config: withBreakerSignal(
|
|
754
|
+
traceConfig(summarizeConfig, 'cache_hit_compaction'),
|
|
755
|
+
graph
|
|
756
|
+
),
|
|
715
757
|
primaryError,
|
|
716
758
|
onChunk,
|
|
759
|
+
streamLimitState: graph,
|
|
717
760
|
});
|
|
718
761
|
const fbMsg = fbResult?.messages?.[0];
|
|
719
762
|
if (fbMsg) {
|
|
@@ -722,6 +765,9 @@ async function executeSummarizationWithFallback(params: {
|
|
|
722
765
|
);
|
|
723
766
|
}
|
|
724
767
|
} catch (fbErr) {
|
|
768
|
+
if (fbErr instanceof StreamLimitExceededError) {
|
|
769
|
+
throw fbErr;
|
|
770
|
+
}
|
|
725
771
|
const fbDescribed = describeFallbackError(fbErr, fallbacks);
|
|
726
772
|
log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {
|
|
727
773
|
...fbDescribed.data,
|
|
@@ -858,13 +904,64 @@ interface CreateSummarizeNodeParams {
|
|
|
858
904
|
result: t.StepCompleted,
|
|
859
905
|
config?: RunnableConfig
|
|
860
906
|
) => Promise<void>;
|
|
861
|
-
|
|
907
|
+
/** The run's shared breaker signal, composed into every summarization
|
|
908
|
+
* model attempt so a sibling branch tripping a stream limit also
|
|
909
|
+
* cancels in-flight summaries. */
|
|
910
|
+
getBreakerSignal?: () => AbortSignal;
|
|
911
|
+
/** The run's shared breaker controller. Preferred over the bare signal
|
|
912
|
+
* accessor: the node captures it at entry so a breach detected by its
|
|
913
|
+
* own chunk handler trips the run that STARTED the summarization. */
|
|
914
|
+
getBreakerController?: () => AbortController;
|
|
915
|
+
/** The controller's epoch, captured at entry and stamped into summary
|
|
916
|
+
* attempt metadata so the wire consumer epoch-gates old-run summary
|
|
917
|
+
* chunks like model-attempt chunks. */
|
|
918
|
+
getBreakerEpoch?: () => number;
|
|
919
|
+
} & StreamLimitState;
|
|
862
920
|
generateStepId: (stepKey: string) => [string, number];
|
|
863
921
|
}
|
|
864
922
|
|
|
923
|
+
/** Composes the run's breaker signal (when the adapter provides one) into a
|
|
924
|
+
* summarization attempt's config, so breaches in sibling branches cancel
|
|
925
|
+
* summaries too. */
|
|
926
|
+
function withBreakerSignal<T extends { signal?: AbortSignal }>(
|
|
927
|
+
config: T | undefined,
|
|
928
|
+
graph?: { getBreakerSignal?: () => AbortSignal }
|
|
929
|
+
): T | undefined {
|
|
930
|
+
const breakerSignal = graph?.getBreakerSignal?.();
|
|
931
|
+
if (breakerSignal == null) {
|
|
932
|
+
return config;
|
|
933
|
+
}
|
|
934
|
+
if (config == null) {
|
|
935
|
+
return { signal: breakerSignal } as T;
|
|
936
|
+
}
|
|
937
|
+
const existing = config.signal;
|
|
938
|
+
if (existing == null || existing === breakerSignal) {
|
|
939
|
+
return { ...config, signal: breakerSignal };
|
|
940
|
+
}
|
|
941
|
+
return { ...config, signal: AbortSignal.any([existing, breakerSignal]) };
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/** First stream-limit reason found on any of the given signals. Checked
|
|
945
|
+
* alongside the graph's own breaker because a subagent child graph receives
|
|
946
|
+
* a ROOT sibling's trip through its constructor/invocation signal — the
|
|
947
|
+
* child's private breaker never fires for it. */
|
|
948
|
+
function findStreamLimitAbortReason(
|
|
949
|
+
...signals: Array<AbortSignal | undefined>
|
|
950
|
+
): StreamLimitExceededError | undefined {
|
|
951
|
+
for (const signal of signals) {
|
|
952
|
+
if (
|
|
953
|
+
signal?.aborted === true &&
|
|
954
|
+
signal.reason instanceof StreamLimitExceededError
|
|
955
|
+
) {
|
|
956
|
+
return signal.reason;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return undefined;
|
|
960
|
+
}
|
|
961
|
+
|
|
865
962
|
export function createSummarizeNode({
|
|
866
963
|
agentContext,
|
|
867
|
-
graph,
|
|
964
|
+
graph: adapterGraph,
|
|
868
965
|
generateStepId,
|
|
869
966
|
}: CreateSummarizeNodeParams) {
|
|
870
967
|
return async (
|
|
@@ -874,10 +971,45 @@ export function createSummarizeNode({
|
|
|
874
971
|
},
|
|
875
972
|
config?: RunnableConfig
|
|
876
973
|
): Promise<{ summarizationRequest: undefined; messages?: BaseMessage[] }> => {
|
|
974
|
+
/** The breaker signal is captured at node ENTRY, before dispatchRunStep,
|
|
975
|
+
* ON_SUMMARIZE_START, or PreCompact awaits: a sibling's trip plus a
|
|
976
|
+
* prompt next run can reset the controller during one of those awaits,
|
|
977
|
+
* and resolving the live accessor later would bind this summarization
|
|
978
|
+
* to the fresh controller. `Object.create` freezes the signal while
|
|
979
|
+
* DELEGATING every other adapter member — spreading would snapshot the
|
|
980
|
+
* charge-credit accessor pair and detach it from graph resets. */
|
|
981
|
+
const entryBreakerController = adapterGraph.getBreakerController?.();
|
|
982
|
+
const entryBreakerSignal =
|
|
983
|
+
entryBreakerController?.signal ?? adapterGraph.getBreakerSignal?.();
|
|
984
|
+
const entryBreakerEpoch = adapterGraph.getBreakerEpoch?.();
|
|
985
|
+
const graph =
|
|
986
|
+
entryBreakerSignal == null
|
|
987
|
+
? adapterGraph
|
|
988
|
+
: (Object.create(adapterGraph, {
|
|
989
|
+
getBreakerSignal: { value: (): AbortSignal => entryBreakerSignal },
|
|
990
|
+
...(entryBreakerController != null && {
|
|
991
|
+
getBreakerController: {
|
|
992
|
+
value: (): AbortController => entryBreakerController,
|
|
993
|
+
},
|
|
994
|
+
}),
|
|
995
|
+
}) as typeof adapterGraph);
|
|
877
996
|
const request = state.summarizationRequest;
|
|
878
997
|
if (request == null) {
|
|
879
998
|
return { summarizationRequest: undefined };
|
|
880
999
|
}
|
|
1000
|
+
/** Already-tripped-at-entry: a parallel sibling's breach failed the run
|
|
1001
|
+
* before this node was scheduled — via this graph's own breaker or, in
|
|
1002
|
+
* a subagent child graph, the parent trip on the invocation signal.
|
|
1003
|
+
* Rethrow before dispatching steps, hooks, or the model call — an
|
|
1004
|
+
* adapter that doesn't synchronously reject an aborted signal would
|
|
1005
|
+
* otherwise spend summarization provider work on a failed run. */
|
|
1006
|
+
const entryTripReason = findStreamLimitAbortReason(
|
|
1007
|
+
entryBreakerSignal,
|
|
1008
|
+
config?.signal
|
|
1009
|
+
);
|
|
1010
|
+
if (entryTripReason != null) {
|
|
1011
|
+
throw entryTripReason;
|
|
1012
|
+
}
|
|
881
1013
|
|
|
882
1014
|
/**
|
|
883
1015
|
* Overflow recovery routes through this node purely to get back to the
|
|
@@ -1111,10 +1243,31 @@ export function createSummarizeNode({
|
|
|
1111
1243
|
...(clientConfig.modelName != null && clientConfig.modelName !== ''
|
|
1112
1244
|
? { [Constants.INVOKED_MODEL]: clientConfig.modelName }
|
|
1113
1245
|
: {}),
|
|
1246
|
+
/** Entry-captured breaker epoch: the wire consumer epoch-gates
|
|
1247
|
+
* old-run summary chunks exactly like model-attempt chunks —
|
|
1248
|
+
* without the stamp, a straggling summary from a failed run
|
|
1249
|
+
* reads as current and could abort the next run's controller. */
|
|
1250
|
+
...(entryBreakerEpoch != null
|
|
1251
|
+
? { [STREAM_LIMIT_EPOCH_KEY]: entryBreakerEpoch }
|
|
1252
|
+
: {}),
|
|
1114
1253
|
},
|
|
1115
1254
|
}
|
|
1116
1255
|
: undefined;
|
|
1117
1256
|
|
|
1257
|
+
/** Rechecked after the pre-call awaits (dispatchRunStep,
|
|
1258
|
+
* ON_SUMMARIZE_START, PreCompact): a sibling can trip the breaker while
|
|
1259
|
+
* this node is paused in one of them, and a provider that doesn't
|
|
1260
|
+
* synchronously reject an aborted signal would still start the
|
|
1261
|
+
* summarization call. Mirrors the model node's pre-invoke recheck. */
|
|
1262
|
+
{
|
|
1263
|
+
const preCallTrip = findStreamLimitAbortReason(
|
|
1264
|
+
entryBreakerSignal,
|
|
1265
|
+
config?.signal
|
|
1266
|
+
);
|
|
1267
|
+
if (preCallTrip != null) {
|
|
1268
|
+
throw preCallTrip;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1118
1271
|
const {
|
|
1119
1272
|
text: rawText,
|
|
1120
1273
|
usage: summaryUsage,
|
|
@@ -1127,6 +1280,7 @@ export function createSummarizeNode({
|
|
|
1127
1280
|
stepId,
|
|
1128
1281
|
usePromptCache: isSelfSummarizeModel && hasPromptCache,
|
|
1129
1282
|
log,
|
|
1283
|
+
graph,
|
|
1130
1284
|
});
|
|
1131
1285
|
|
|
1132
1286
|
/**
|
|
@@ -1331,21 +1485,58 @@ function buildSummarizationInstruction(
|
|
|
1331
1485
|
}
|
|
1332
1486
|
|
|
1333
1487
|
/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */
|
|
1334
|
-
function createSummarizationChunkHandler({
|
|
1488
|
+
export function createSummarizationChunkHandler({
|
|
1335
1489
|
stepId,
|
|
1336
1490
|
config,
|
|
1337
1491
|
provider,
|
|
1338
1492
|
reasoningKey = 'reasoning_content',
|
|
1493
|
+
graph,
|
|
1339
1494
|
}: {
|
|
1340
1495
|
stepId?: string;
|
|
1341
1496
|
config?: RunnableConfig;
|
|
1342
1497
|
provider?: Providers;
|
|
1343
1498
|
reasoningKey?: 'reasoning_content' | 'reasoning';
|
|
1499
|
+
graph?: StreamLimitState & { getBreakerController?: () => AbortController };
|
|
1344
1500
|
}): OnChunk | undefined {
|
|
1345
1501
|
if (stepId == null || stepId === '' || !config) {
|
|
1346
1502
|
return undefined;
|
|
1347
1503
|
}
|
|
1348
|
-
|
|
1504
|
+
const limitMetadata = config.metadata as Record<string, unknown> | undefined;
|
|
1505
|
+
return (chunk, attemptMetadata) => {
|
|
1506
|
+
/**
|
|
1507
|
+
* Charged as `producer` so it PAIRS with the run's registered stream
|
|
1508
|
+
* handler: inside a live run the provider callbacks route each chunk
|
|
1509
|
+
* through the graph's `on_chat_model_stream` consumer too, and two
|
|
1510
|
+
* same-side consumer claims would charge the chunk twice — halving an
|
|
1511
|
+
* opt-in event cap. Standalone callers (tests, custom hosts, runs with
|
|
1512
|
+
* no registered handler) still get single-sided enforcement from this
|
|
1513
|
+
* one claim. Summarization deliberately passes NO `context` to
|
|
1514
|
+
* `attemptInvoke`, whose onChunk branch would otherwise add a second
|
|
1515
|
+
* producer claim. A breach trips the run's shared breaker (the
|
|
1516
|
+
* ENTRY-captured controller when the adapter provides one) before
|
|
1517
|
+
* rethrowing: this producer claim wins the race, so the wire consumer's
|
|
1518
|
+
* breaker-aborting catch never fires for the same chunk — without the
|
|
1519
|
+
* trip here, sibling model calls, tools, and subagents would keep
|
|
1520
|
+
* consuming quota while the summary branch rejects.
|
|
1521
|
+
* `executeSummarizationWithFallback` then rethrows past fallbacks and
|
|
1522
|
+
* the metadata stub, failing the run like any other breach.
|
|
1523
|
+
*/
|
|
1524
|
+
if (graph != null) {
|
|
1525
|
+
try {
|
|
1526
|
+
enforceStreamLimitsForWireChunk({
|
|
1527
|
+
graph,
|
|
1528
|
+
metadata: attemptMetadata ?? limitMetadata,
|
|
1529
|
+
chunk: chunk as Parameters<
|
|
1530
|
+
typeof enforceStreamLimitsForWireChunk
|
|
1531
|
+
>[0]['chunk'],
|
|
1532
|
+
});
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
if (error instanceof StreamLimitExceededError) {
|
|
1535
|
+
graph.getBreakerController?.().abort(error);
|
|
1536
|
+
}
|
|
1537
|
+
throw error;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1349
1540
|
const chunkAny = chunk as Parameters<typeof getChunkContent>[0]['chunk'];
|
|
1350
1541
|
const raw = getChunkContent({ chunk: chunkAny, provider, reasoningKey });
|
|
1351
1542
|
if (raw == null || (typeof raw === 'string' && !raw)) {
|
|
@@ -1404,6 +1595,7 @@ async function summarizeWithCacheHit({
|
|
|
1404
1595
|
stepId,
|
|
1405
1596
|
provider,
|
|
1406
1597
|
reasoningKey,
|
|
1598
|
+
graph,
|
|
1407
1599
|
usePromptCache,
|
|
1408
1600
|
promptCacheTtl,
|
|
1409
1601
|
log,
|
|
@@ -1417,6 +1609,7 @@ async function summarizeWithCacheHit({
|
|
|
1417
1609
|
stepId?: string;
|
|
1418
1610
|
provider: Providers;
|
|
1419
1611
|
reasoningKey?: 'reasoning_content' | 'reasoning';
|
|
1612
|
+
graph?: StreamLimitState & { getBreakerSignal?: () => AbortSignal };
|
|
1420
1613
|
usePromptCache?: boolean;
|
|
1421
1614
|
promptCacheTtl?: PromptCacheTtl;
|
|
1422
1615
|
log?: LogFn;
|
|
@@ -1443,9 +1636,13 @@ async function summarizeWithCacheHit({
|
|
|
1443
1636
|
config: traceConfig(config, 'cache_hit_compaction'),
|
|
1444
1637
|
provider,
|
|
1445
1638
|
reasoningKey,
|
|
1639
|
+
graph,
|
|
1446
1640
|
}),
|
|
1641
|
+
/** Accounting lease only (summarization passes no `context` so its
|
|
1642
|
+
* producer claim never stacks with the onChunk handler's). */
|
|
1643
|
+
streamLimitState: graph,
|
|
1447
1644
|
},
|
|
1448
|
-
traceConfig(config, 'cache_hit_compaction')
|
|
1645
|
+
withBreakerSignal(traceConfig(config, 'cache_hit_compaction'), graph)
|
|
1449
1646
|
);
|
|
1450
1647
|
|
|
1451
1648
|
const responseMsg = result.messages?.[0];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { config } from 'dotenv';
|
|
2
2
|
import fetch, { RequestInit } from 'node-fetch';
|
|
3
|
-
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
3
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
5
4
|
import type * as t from '@/types';
|
|
6
5
|
import {
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
normalizeCodeApiRequestError,
|
|
17
16
|
resolveCodeApiAuthHeaders,
|
|
18
17
|
} from './CodeExecutor';
|
|
18
|
+
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
19
19
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
20
20
|
import { Constants } from '@/common';
|
|
21
21
|
|
|
@@ -253,8 +253,9 @@ function createBashExecutionTool(
|
|
|
253
253
|
body: JSON.stringify(postData),
|
|
254
254
|
};
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
const proxyAgent = resolveFetchProxyAgent(EXEC_ENDPOINT);
|
|
257
|
+
if (proxyAgent) {
|
|
258
|
+
fetchOptions.agent = proxyAgent;
|
|
258
259
|
}
|
|
259
260
|
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|
|
260
261
|
if (!response.ok) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { config } from 'dotenv';
|
|
2
2
|
import fetch, { RequestInit } from 'node-fetch';
|
|
3
|
-
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
3
|
import { getEnvironmentVariable } from '@langchain/core/utils/env';
|
|
5
4
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
6
5
|
import type * as t from '@/types';
|
|
7
6
|
import { appendCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
7
|
+
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
8
8
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
9
9
|
import { EnvVar, Constants } from '@/common';
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ export const emptyOutputMessage =
|
|
|
23
23
|
'stdout: Empty. Ensure you\'re writing output explicitly.\n';
|
|
24
24
|
|
|
25
25
|
export const CODE_ARTIFACT_PATH_GUIDANCE =
|
|
26
|
-
'
|
|
26
|
+
'Anything a later call needs (data, helper scripts/modules, partial results) MUST be written under `/mnt/data` in the same call that produces it; `/tmp` never survives the call. `/mnt/data` keeps files with recognized extensions, covering common source, text, data, document, image, and archive formats (.py/.sh/.sql/.md/.json/.csv/.parquet/.png/.pdf/.zip and similar); extensionless or unusual extensions are not kept. Failed executions register nothing; fix the error and rerun before relying on new files.';
|
|
27
27
|
|
|
28
28
|
export const BASH_SHELL_GUIDANCE =
|
|
29
29
|
'Bash: multi-line files use heredoc/printf; run Python via python3 -c/heredoc, not bare Python.';
|
|
@@ -426,8 +426,9 @@ function createCodeExecutionTool(
|
|
|
426
426
|
body: JSON.stringify(postData),
|
|
427
427
|
};
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
const proxyAgent = resolveFetchProxyAgent(EXEC_ENDPOINT);
|
|
430
|
+
if (proxyAgent) {
|
|
431
|
+
fetchOptions.agent = proxyAgent;
|
|
431
432
|
}
|
|
432
433
|
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|
|
433
434
|
if (!response.ok) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/tools/ProgrammaticToolCalling.ts
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
import fetch, { RequestInit } from 'node-fetch';
|
|
4
|
-
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
5
4
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
6
5
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
7
6
|
import type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
createCodeApiRunTimeoutSchema,
|
|
25
24
|
resolveCodeApiRunTimeoutMs,
|
|
26
25
|
} from './ptcTimeout';
|
|
26
|
+
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
27
27
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
28
28
|
import { Constants } from '@/common';
|
|
29
29
|
|
|
@@ -438,8 +438,9 @@ export async function fetchSessionFiles(
|
|
|
438
438
|
},
|
|
439
439
|
};
|
|
440
440
|
|
|
441
|
-
|
|
442
|
-
|
|
441
|
+
const proxyAgent = resolveFetchProxyAgent(filesEndpoint, proxy);
|
|
442
|
+
if (proxyAgent) {
|
|
443
|
+
fetchOptions.agent = proxyAgent;
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
const response = await fetch(filesEndpoint, fetchOptions);
|
|
@@ -491,8 +492,9 @@ export async function makeRequest(
|
|
|
491
492
|
body: JSON.stringify(body),
|
|
492
493
|
};
|
|
493
494
|
|
|
494
|
-
|
|
495
|
-
|
|
495
|
+
const proxyAgent = resolveFetchProxyAgent(endpoint, proxy);
|
|
496
|
+
if (proxyAgent) {
|
|
497
|
+
fetchOptions.agent = proxyAgent;
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
const response = await fetch(endpoint, fetchOptions);
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -79,9 +79,26 @@ import {
|
|
|
79
79
|
} from '@/tools/local';
|
|
80
80
|
import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
81
81
|
import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
|
|
82
|
+
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
83
|
+
import {
|
|
84
|
+
StreamLimitExceededError,
|
|
85
|
+
RUN_BREAKER_SCOPE_CONFIG_KEY,
|
|
86
|
+
} from '@/llm/streamLimits';
|
|
87
|
+
|
|
88
|
+
/** Host-facing batch requests must not carry the batch's breaker scope —
|
|
89
|
+
* hosts spread `configurable` into their own run configs. */
|
|
90
|
+
function stripRunBreakerScope(
|
|
91
|
+
configurable: Record<string, unknown> | undefined
|
|
92
|
+
): Record<string, unknown> | undefined {
|
|
93
|
+
if (configurable == null || !(RUN_BREAKER_SCOPE_CONFIG_KEY in configurable)) {
|
|
94
|
+
return configurable;
|
|
95
|
+
}
|
|
96
|
+
const { [RUN_BREAKER_SCOPE_CONFIG_KEY]: _scope, ...rest } = configurable;
|
|
97
|
+
return rest;
|
|
98
|
+
}
|
|
82
99
|
import { convertInjectedMessages } from '@/messages/injected';
|
|
83
100
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
84
|
-
import { RunnableCallable } from '@/utils';
|
|
101
|
+
import { RunnableCallable, composeAbortSignals } from '@/utils';
|
|
85
102
|
import { executeHooks } from '@/hooks';
|
|
86
103
|
|
|
87
104
|
/**
|
|
@@ -636,6 +653,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
636
653
|
private toolOutputRegistry?: ToolOutputReferenceRegistry;
|
|
637
654
|
/** Run-scoped selection for swapping remote code tools to local executors. */
|
|
638
655
|
private toolExecution?: t.ToolExecutionConfig;
|
|
656
|
+
/** Owning graph's run-scoped breaker signal, composed into each batch's config. */
|
|
657
|
+
private getBreakerSignal?: () => AbortSignal | undefined;
|
|
658
|
+
/** Owning graph's immutable run scope; captured once per batch. */
|
|
659
|
+
private getRunScope?: () => RunBreakerScope;
|
|
639
660
|
/**
|
|
640
661
|
* Monotonic counter used to mint a unique scope id for anonymous
|
|
641
662
|
* batches (ones invoked without a `run_id` in
|
|
@@ -677,6 +698,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
677
698
|
toolOutputRegistry,
|
|
678
699
|
toolExecution,
|
|
679
700
|
fileCheckpointer,
|
|
701
|
+
getBreakerSignal,
|
|
702
|
+
getRunScope,
|
|
680
703
|
}: t.ToolNodeConstructorParams) {
|
|
681
704
|
super({
|
|
682
705
|
name: name ?? TOOL_NODE_RUN_NAME,
|
|
@@ -720,6 +743,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
720
743
|
this.hookRegistry = hookRegistry;
|
|
721
744
|
this.humanInTheLoop = humanInTheLoop;
|
|
722
745
|
this.toolExecution = toolExecution;
|
|
746
|
+
this.getBreakerSignal = getBreakerSignal;
|
|
747
|
+
this.getRunScope = getRunScope;
|
|
723
748
|
// Caller-provided checkpointer wins. Graphs use this to share a
|
|
724
749
|
// single per-Run instance across every ToolNode they compile so
|
|
725
750
|
// `Run.rewindFiles()` reaches the same snapshot store regardless
|
|
@@ -1212,6 +1237,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1212
1237
|
(config.configurable?.writer as ToolRuntime<T>['writer']) ??
|
|
1213
1238
|
null,
|
|
1214
1239
|
};
|
|
1240
|
+
/** A sibling can trip the breaker while the PreToolUse hooks above
|
|
1241
|
+
* are awaited; a tool that never inspects its runtime signal would
|
|
1242
|
+
* still run. Recheck at the last moment before execution. */
|
|
1243
|
+
this.throwIfBreakerTripped(config);
|
|
1215
1244
|
const output = await tool.invoke(invokeParams, runtime);
|
|
1216
1245
|
if (isCommand(output)) {
|
|
1217
1246
|
return output;
|
|
@@ -1323,6 +1352,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1323
1352
|
if (isGraphInterrupt(e)) {
|
|
1324
1353
|
throw e;
|
|
1325
1354
|
}
|
|
1355
|
+
/**
|
|
1356
|
+
* A stream circuit breaker tripped by a child run (subagent) is a
|
|
1357
|
+
* safety abort, not a tool failure to report back to the model. It
|
|
1358
|
+
* passes through like an interrupt so the whole run rejects.
|
|
1359
|
+
*/
|
|
1360
|
+
if (e instanceof StreamLimitExceededError) {
|
|
1361
|
+
throw e;
|
|
1362
|
+
}
|
|
1326
1363
|
if (this.errorHandler) {
|
|
1327
1364
|
try {
|
|
1328
1365
|
const dispatched = await this.errorHandler(
|
|
@@ -2237,11 +2274,27 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2237
2274
|
* 4. Injected messages from results are collected and returned alongside
|
|
2238
2275
|
* ToolMessages (appended AFTER to respect provider ordering).
|
|
2239
2276
|
*/
|
|
2277
|
+
/** Rethrows a stream-limit trip carried on the batch config's composed
|
|
2278
|
+
* signal. Rechecked at each later execution/dispatch stage because a tool
|
|
2279
|
+
* that ignores cancellation can complete normally across the trip, and
|
|
2280
|
+
* the next stage would otherwise start fresh side effects on a failed
|
|
2281
|
+
* run. */
|
|
2282
|
+
private throwIfBreakerTripped(config: RunnableConfig): void {
|
|
2283
|
+
const signal = config.signal;
|
|
2284
|
+
if (
|
|
2285
|
+
signal?.aborted === true &&
|
|
2286
|
+
signal.reason instanceof StreamLimitExceededError
|
|
2287
|
+
) {
|
|
2288
|
+
throw signal.reason;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2240
2292
|
private async dispatchToolEvents(
|
|
2241
2293
|
toolCalls: ToolCall[],
|
|
2242
2294
|
config: RunnableConfig,
|
|
2243
2295
|
batchContext: DispatchBatchContext = {}
|
|
2244
2296
|
): Promise<{ toolMessages: ToolMessage[]; injected: BaseMessage[] }> {
|
|
2297
|
+
this.throwIfBreakerTripped(config);
|
|
2245
2298
|
const {
|
|
2246
2299
|
batchIndices,
|
|
2247
2300
|
turn,
|
|
@@ -2941,9 +2994,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2941
2994
|
dispatchRequests.map((request) => [request.id, request])
|
|
2942
2995
|
);
|
|
2943
2996
|
const onResult = (result: t.ToolExecuteResult): void => {
|
|
2997
|
+
/** Runtime-honest widening: hosts may omit the id despite the
|
|
2998
|
+
* field type. */
|
|
2999
|
+
const resultToolCallId = result.toolCallId as string | undefined;
|
|
2944
3000
|
const request =
|
|
2945
|
-
|
|
2946
|
-
? dispatchRequestById.get(
|
|
3001
|
+
resultToolCallId != null
|
|
3002
|
+
? dispatchRequestById.get(resultToolCallId)
|
|
2947
3003
|
: undefined;
|
|
2948
3004
|
if (
|
|
2949
3005
|
request == null ||
|
|
@@ -2966,6 +3022,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2966
3022
|
);
|
|
2967
3023
|
};
|
|
2968
3024
|
|
|
3025
|
+
/** The approval hooks above are awaited; a sibling's trip during them
|
|
3026
|
+
* must stop the approved batch before it reaches a host handler that
|
|
3027
|
+
* may never inspect the request signal. */
|
|
3028
|
+
if (dispatchRequests.length > 0) {
|
|
3029
|
+
this.throwIfBreakerTripped(config);
|
|
3030
|
+
}
|
|
2969
3031
|
const dispatchPromise =
|
|
2970
3032
|
dispatchRequests.length === 0
|
|
2971
3033
|
? Promise.resolve([] as t.ToolExecuteResult[])
|
|
@@ -2988,12 +3050,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2988
3050
|
// the eager path sends `agentContext.agentId` — this must
|
|
2989
3051
|
// match it at the top level too.
|
|
2990
3052
|
agentId: this.executingAgentId,
|
|
2991
|
-
configurable:
|
|
2992
|
-
|
|
2993
|
-
|
|
3053
|
+
configurable: stripRunBreakerScope(
|
|
3054
|
+
config.configurable as Record<string, unknown> | undefined
|
|
3055
|
+
),
|
|
2994
3056
|
metadata: config.metadata as
|
|
2995
3057
|
| Record<string, unknown>
|
|
2996
3058
|
| undefined,
|
|
3059
|
+
signal: config.signal,
|
|
2997
3060
|
resolve: (results): void => {
|
|
2998
3061
|
resultSettled = true;
|
|
2999
3062
|
settledResults = results;
|
|
@@ -3678,6 +3741,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3678
3741
|
outputs[i] = interruptingOutputs[k];
|
|
3679
3742
|
});
|
|
3680
3743
|
|
|
3744
|
+
/** The breaker can trip while the interrupting group is awaited — e.g.
|
|
3745
|
+
* a tool that ignores cancellation and completes normally. Recheck
|
|
3746
|
+
* before starting the non-idempotent siblings. */
|
|
3747
|
+
this.throwIfBreakerTripped(config);
|
|
3748
|
+
|
|
3681
3749
|
// No interrupting call suspended — safe to run the remaining siblings.
|
|
3682
3750
|
const regularOutputs = await Promise.all(
|
|
3683
3751
|
regularPositions.map((i) => runOne(directCalls[i], i))
|
|
@@ -3718,6 +3786,41 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3718
3786
|
|
|
3719
3787
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3720
3788
|
protected async run(input: any, config: RunnableConfig): Promise<T> {
|
|
3789
|
+
/**
|
|
3790
|
+
* Breaker read once at batch entry: every downstream path (direct
|
|
3791
|
+
* `tool.invoke` runtimes and the ON_TOOL_EXECUTE dispatch) inherits this
|
|
3792
|
+
* config, and a graph reset mid-batch must not detach in-flight tools
|
|
3793
|
+
* from an already-tripped signal.
|
|
3794
|
+
*/
|
|
3795
|
+
const breakerSignal = this.getBreakerSignal?.();
|
|
3796
|
+
const composedSignal = composeAbortSignals(config.signal, breakerSignal);
|
|
3797
|
+
/** Immutable batch scope, captured with the signal BEFORE hooks: tools
|
|
3798
|
+
* that spawn runs (subagents) bind their child to this controller, so a
|
|
3799
|
+
* reset during a PreToolUse hook cannot hand them the new run's one. */
|
|
3800
|
+
const batchRunScope = this.getRunScope?.();
|
|
3801
|
+
if (composedSignal !== config.signal || batchRunScope != null) {
|
|
3802
|
+
config = {
|
|
3803
|
+
...config,
|
|
3804
|
+
signal: composedSignal,
|
|
3805
|
+
...(batchRunScope != null && {
|
|
3806
|
+
configurable: {
|
|
3807
|
+
...config.configurable,
|
|
3808
|
+
[RUN_BREAKER_SCOPE_CONFIG_KEY]: batchRunScope,
|
|
3809
|
+
},
|
|
3810
|
+
}),
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
/** Already-tripped-at-entry: a parallel sibling's breach failed the run
|
|
3814
|
+
* before this batch was scheduled. Rethrow before hooks, direct
|
|
3815
|
+
* `tool.invoke` calls, or ON_TOOL_EXECUTE dispatch — a tool or host
|
|
3816
|
+
* handler that doesn't synchronously inspect an aborted signal would
|
|
3817
|
+
* otherwise perform side effects on a failed run. */
|
|
3818
|
+
if (
|
|
3819
|
+
composedSignal?.aborted === true &&
|
|
3820
|
+
composedSignal.reason instanceof StreamLimitExceededError
|
|
3821
|
+
) {
|
|
3822
|
+
throw composedSignal.reason;
|
|
3823
|
+
}
|
|
3721
3824
|
this.toolCallTurns.clear();
|
|
3722
3825
|
/**
|
|
3723
3826
|
* Per-batch local map for resolved (post-substitution) args.
|
package/src/tools/ToolSearch.ts
CHANGED
|
@@ -21,7 +21,7 @@ function getBM25Function(): BM25Fn {
|
|
|
21
21
|
|
|
22
22
|
const BM25 = getBM25Function();
|
|
23
23
|
import fetch, { RequestInit } from 'node-fetch';
|
|
24
|
-
import {
|
|
24
|
+
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
25
25
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
26
26
|
import type * as t from '@/types';
|
|
27
27
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
@@ -1102,8 +1102,9 @@ ${mcpNote}${toolsListSection}
|
|
|
1102
1102
|
body: JSON.stringify(postData),
|
|
1103
1103
|
};
|
|
1104
1104
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1105
|
+
const proxyAgent = resolveFetchProxyAgent(EXEC_ENDPOINT);
|
|
1106
|
+
if (proxyAgent) {
|
|
1107
|
+
fetchOptions.agent = proxyAgent;
|
|
1107
1108
|
}
|
|
1108
1109
|
|
|
1109
1110
|
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|