@librechat/agents 3.3.11 → 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 +246 -25
- package/dist/cjs/graphs/Graph.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/invoke.cjs +146 -36
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +2 -2
- 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 +8 -0
- package/dist/cjs/run.cjs +6 -2
- 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 +103 -12
- 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 +76 -3
- 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 +16 -2
- 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 +1 -0
- 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 +245 -24
- package/dist/esm/graphs/Graph.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/invoke.mjs +146 -36
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +2 -2
- 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 +4 -2
- package/dist/esm/run.mjs +6 -2
- 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 +103 -12
- 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 +76 -3
- 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 +16 -2
- 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 +1 -0
- 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/llm/invoke.d.ts +25 -4
- package/dist/types/llm/openai/index.d.ts +3 -0
- package/dist/types/llm/streamLimits.d.ts +314 -0
- 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/llm/bedrock/index.ts +22 -2
- package/src/llm/invoke.streamLimits.test.ts +142 -0
- package/src/llm/invoke.test.ts +89 -1
- package/src/llm/invoke.ts +197 -20
- package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
- package/src/llm/openai/index.ts +14 -6
- package/src/llm/streamLimits.test.ts +450 -0
- package/src/llm/streamLimits.ts +1158 -0
- package/src/run.ts +4 -0
- package/src/session/AgentSession.ts +5 -0
- 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
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);
|
|
@@ -24,9 +24,9 @@ describe('buildBashExecutionToolDescription', () => {
|
|
|
24
24
|
expect(BashExecutionToolDescription).toContain('heredoc/printf');
|
|
25
25
|
expect(BashExecutionToolDescription).toContain('not bare Python');
|
|
26
26
|
expect(BashExecutionToolDescription).toContain(
|
|
27
|
-
'
|
|
27
|
+
'Failed executions register nothing'
|
|
28
28
|
);
|
|
29
|
-
expect(BashExecutionToolDescription).toContain('
|
|
29
|
+
expect(BashExecutionToolDescription).toContain('`/tmp` never survives the call');
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('appends the tool-output references guide when enabled', () => {
|
|
@@ -51,10 +51,8 @@ describe('ProgrammaticToolCalling', () => {
|
|
|
51
51
|
expect(description).toContain('raw=$(tool');
|
|
52
52
|
expect(description).toContain('direct tool > file may be empty');
|
|
53
53
|
expect(description).toContain('/mnt/data/sf.json');
|
|
54
|
-
expect(description).toContain(
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
expect(description).toContain('not later-call storage');
|
|
54
|
+
expect(description).toContain('Failed executions register nothing');
|
|
55
|
+
expect(description).toContain('`/tmp` never survives the call');
|
|
58
56
|
});
|
|
59
57
|
});
|
|
60
58
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '../subagent';
|
|
21
21
|
import { sanitizeForwardedSubagentUpdateData } from '../subagent/SubagentExecutor';
|
|
22
22
|
import { Constants, Providers, GraphEvents, StepTypes } from '@/common';
|
|
23
|
+
import { StreamLimitExceededError } from '@/llm/streamLimits';
|
|
23
24
|
import { AgentContext } from '@/agents/AgentContext';
|
|
24
25
|
import { HookRegistry } from '@/hooks/HookRegistry';
|
|
25
26
|
import { HandlerRegistry } from '@/events';
|
|
@@ -451,6 +452,131 @@ describe('SubagentExecutor', () => {
|
|
|
451
452
|
expect(result.messages).toEqual([]);
|
|
452
453
|
});
|
|
453
454
|
|
|
455
|
+
it('rethrows a child stream-limit trip instead of converting it to a tool result', async () => {
|
|
456
|
+
const executor = createExecutor({
|
|
457
|
+
createChildGraph: (): StandardGraph =>
|
|
458
|
+
({
|
|
459
|
+
createWorkflow: (): { invoke: jest.Mock } => ({
|
|
460
|
+
invoke: jest.fn().mockRejectedValue(
|
|
461
|
+
new StreamLimitExceededError({
|
|
462
|
+
kind: 'tool_call_args',
|
|
463
|
+
limit: 10,
|
|
464
|
+
observed: 11,
|
|
465
|
+
toolName: 'db_query',
|
|
466
|
+
})
|
|
467
|
+
),
|
|
468
|
+
}),
|
|
469
|
+
clearHeavyState: jest.fn(),
|
|
470
|
+
}) as unknown as StandardGraph,
|
|
471
|
+
});
|
|
472
|
+
await expect(
|
|
473
|
+
executor.execute({ description: 'Do something', subagentType: 'researcher' })
|
|
474
|
+
).rejects.toBeInstanceOf(StreamLimitExceededError);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('binds the child to the batch-captured controller, not the live scope', async () => {
|
|
478
|
+
const batchBreaker = new AbortController();
|
|
479
|
+
const liveBreaker = new AbortController();
|
|
480
|
+
const executor = createExecutor({
|
|
481
|
+
breakerScope: { controller: () => liveBreaker },
|
|
482
|
+
createChildGraph: (): StandardGraph =>
|
|
483
|
+
({
|
|
484
|
+
createWorkflow: (): { invoke: jest.Mock } => ({
|
|
485
|
+
invoke: jest.fn().mockRejectedValue(
|
|
486
|
+
new StreamLimitExceededError({
|
|
487
|
+
kind: 'tool_call_args',
|
|
488
|
+
limit: 10,
|
|
489
|
+
observed: 11,
|
|
490
|
+
toolName: 'db_query',
|
|
491
|
+
})
|
|
492
|
+
),
|
|
493
|
+
}),
|
|
494
|
+
clearHeavyState: jest.fn(),
|
|
495
|
+
}) as unknown as StandardGraph,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
/** `breaker` is the controller the parent TOOL BATCH captured before
|
|
499
|
+
* PreToolUse hooks; a reset during those hooks replaced the live
|
|
500
|
+
* scope, and the child must not revive on — or trip — the new run's
|
|
501
|
+
* controller. */
|
|
502
|
+
await expect(
|
|
503
|
+
executor.execute({
|
|
504
|
+
description: 'Do something',
|
|
505
|
+
subagentType: 'researcher',
|
|
506
|
+
breaker: batchBreaker,
|
|
507
|
+
})
|
|
508
|
+
).rejects.toBeInstanceOf(StreamLimitExceededError);
|
|
509
|
+
expect(batchBreaker.signal.aborted).toBe(true);
|
|
510
|
+
expect(liveBreaker.signal.aborted).toBe(false);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('trips the breaker controller captured at execution start, not the current one', async () => {
|
|
514
|
+
const oldRun = new AbortController();
|
|
515
|
+
const newRun = new AbortController();
|
|
516
|
+
let current = oldRun;
|
|
517
|
+
let releaseChild: (() => void) | undefined;
|
|
518
|
+
const executor = createExecutor({
|
|
519
|
+
breakerScope: { controller: () => current },
|
|
520
|
+
createChildGraph: (): StandardGraph =>
|
|
521
|
+
({
|
|
522
|
+
createWorkflow: (): { invoke: jest.Mock } => ({
|
|
523
|
+
invoke: jest.fn().mockImplementation(
|
|
524
|
+
() =>
|
|
525
|
+
new Promise((_resolve, reject) => {
|
|
526
|
+
releaseChild = (): void =>
|
|
527
|
+
reject(
|
|
528
|
+
new StreamLimitExceededError({
|
|
529
|
+
kind: 'tool_call_args',
|
|
530
|
+
limit: 10,
|
|
531
|
+
observed: 11,
|
|
532
|
+
toolName: 'db_query',
|
|
533
|
+
})
|
|
534
|
+
);
|
|
535
|
+
})
|
|
536
|
+
),
|
|
537
|
+
}),
|
|
538
|
+
clearHeavyState: jest.fn(),
|
|
539
|
+
}) as unknown as StandardGraph,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const pending = executor.execute({
|
|
543
|
+
description: 'Do something',
|
|
544
|
+
subagentType: 'researcher',
|
|
545
|
+
});
|
|
546
|
+
while (releaseChild == null) {
|
|
547
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
548
|
+
}
|
|
549
|
+
/** The failed run's reset installs a fresh controller while the child
|
|
550
|
+
* is still settling; the straggler's trip must land on the controller
|
|
551
|
+
* captured when its execution started. */
|
|
552
|
+
current = newRun;
|
|
553
|
+
releaseChild();
|
|
554
|
+
await expect(pending).rejects.toBeInstanceOf(StreamLimitExceededError);
|
|
555
|
+
expect(oldRun.signal.aborted).toBe(true);
|
|
556
|
+
expect(newRun.signal.aborted).toBe(false);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('threads run-level streamLimits into every child graph input', async () => {
|
|
560
|
+
const childGraphInputs: StandardGraphInput[] = [];
|
|
561
|
+
const noopFactory = makeNoopGraphFactory();
|
|
562
|
+
const executor = createExecutor({
|
|
563
|
+
streamLimits: { maxToolCallArgBytes: 1234, maxDeltaEventsPerTurn: 9 },
|
|
564
|
+
createChildGraph: (input: StandardGraphInput): StandardGraph => {
|
|
565
|
+
childGraphInputs.push(input);
|
|
566
|
+
return noopFactory();
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
await executor.execute({
|
|
570
|
+
description: 'Do something',
|
|
571
|
+
subagentType: 'researcher',
|
|
572
|
+
});
|
|
573
|
+
expect(childGraphInputs).toHaveLength(1);
|
|
574
|
+
expect(childGraphInputs[0].streamLimits).toEqual({
|
|
575
|
+
maxToolCallArgBytes: 1234,
|
|
576
|
+
maxDeltaEventsPerTurn: 9,
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
454
580
|
it('executes child graph and returns filtered content', async () => {
|
|
455
581
|
const { factory, clearHeavyState } = makeStubGraphFactory({
|
|
456
582
|
messages: [
|