@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
package/src/graphs/Graph.ts
CHANGED
|
@@ -17,6 +17,12 @@ import type {
|
|
|
17
17
|
MessageContent,
|
|
18
18
|
} from '@langchain/core/messages';
|
|
19
19
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
20
|
+
import type {
|
|
21
|
+
ResolvedStreamLimits,
|
|
22
|
+
RunBreakerScope,
|
|
23
|
+
StreamedToolCallArgTally,
|
|
24
|
+
StreamDeltaEventTally,
|
|
25
|
+
} from '@/llm/streamLimits';
|
|
20
26
|
import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
|
|
21
27
|
import type { FallbackErrorContext } from '@/llm/invoke';
|
|
22
28
|
import type { HookRegistry } from '@/hooks';
|
|
@@ -69,6 +75,7 @@ import {
|
|
|
69
75
|
isGoogleLike,
|
|
70
76
|
apportionTokenCounts,
|
|
71
77
|
calculateMaxToolResultChars,
|
|
78
|
+
composeAbortSignals,
|
|
72
79
|
joinKeys,
|
|
73
80
|
sleep,
|
|
74
81
|
} from '@/utils';
|
|
@@ -115,6 +122,13 @@ import {
|
|
|
115
122
|
findCallback,
|
|
116
123
|
type CallbackEntry,
|
|
117
124
|
} from '@/utils/callbacks';
|
|
125
|
+
import {
|
|
126
|
+
resolveStreamLimits,
|
|
127
|
+
StreamLimitExceededError,
|
|
128
|
+
sweepStaleStreamLimitEntries,
|
|
129
|
+
STREAM_LIMIT_EPOCH_KEY,
|
|
130
|
+
RUN_BREAKER_SCOPE_CONFIG_KEY,
|
|
131
|
+
} from '@/llm/streamLimits';
|
|
118
132
|
import { partitionAndMarkOpenRouterToolCache } from '@/llm/openrouter/toolCache';
|
|
119
133
|
import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
120
134
|
import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
|
|
@@ -154,25 +168,6 @@ const EMPTY_PREEMPT_BOUNDARY: PreemptBoundaryResult = {
|
|
|
154
168
|
preventContinuation: false,
|
|
155
169
|
};
|
|
156
170
|
|
|
157
|
-
/**
|
|
158
|
-
* One signal that fires when either input fires. `AbortSignal.any` is skipped
|
|
159
|
-
* when the inputs collapse to a single signal — the composite is a fresh
|
|
160
|
-
* object per call, and the common cases (one channel, or the host reusing the
|
|
161
|
-
* same controller for both) don't need one.
|
|
162
|
-
*/
|
|
163
|
-
function composeAbortSignals(
|
|
164
|
-
a: AbortSignal | undefined,
|
|
165
|
-
b: AbortSignal | undefined
|
|
166
|
-
): AbortSignal | undefined {
|
|
167
|
-
if (a == null || a === b) {
|
|
168
|
-
return b;
|
|
169
|
-
}
|
|
170
|
-
if (b == null) {
|
|
171
|
-
return a;
|
|
172
|
-
}
|
|
173
|
-
return AbortSignal.any([a, b]);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
171
|
/** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
|
|
177
172
|
const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
|
|
178
173
|
|
|
@@ -1051,6 +1046,80 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1051
1046
|
subagentScope: boolean;
|
|
1052
1047
|
/** See {@link t.StandardGraphInput.preemption}. */
|
|
1053
1048
|
preemption?: t.StreamPreemption;
|
|
1049
|
+
/**
|
|
1050
|
+
* Stream circuit breakers, resolved once from
|
|
1051
|
+
* {@link t.StandardGraphInput.streamLimits}. The stream handler enforces
|
|
1052
|
+
* these on every streamed chunk event.
|
|
1053
|
+
*/
|
|
1054
|
+
streamLimits: ResolvedStreamLimits;
|
|
1055
|
+
/**
|
|
1056
|
+
* Cumulative streamed argument bytes per in-flight tool call, keyed by
|
|
1057
|
+
* generation key + chunk index (see `resolveGenerationKey`). Per-run
|
|
1058
|
+
* accumulation state, cleared by both reset paths.
|
|
1059
|
+
*/
|
|
1060
|
+
streamedToolCallArgTallies: Map<string, StreamedToolCallArgTally> =
|
|
1061
|
+
new Map();
|
|
1062
|
+
/** Streamed chunk events per model generation, keyed by generation key. */
|
|
1063
|
+
streamDeltaEventCounts: Map<string, StreamDeltaEventTally> = new Map();
|
|
1064
|
+
/** Per-chunk-object, per-generation charge balances (lazily created; see
|
|
1065
|
+
* `StreamLimitState`). Reinitialized by both reset paths: a model may
|
|
1066
|
+
* retain and re-yield one mutable chunk object, whose nested map would
|
|
1067
|
+
* otherwise grow by one attempt-stamped entry per model call for the
|
|
1068
|
+
* graph's lifetime. */
|
|
1069
|
+
streamLimitChargeCredits?: WeakMap<object, Map<string, number>>;
|
|
1070
|
+
/** Run-scoped breaker abort: composed into every model invocation and
|
|
1071
|
+
* every SubagentExecutor child signal, and tripped when a stream circuit
|
|
1072
|
+
* breaker fires anywhere in the run, so parallel agent nodes' in-flight
|
|
1073
|
+
* provider calls and subagents stop consuming quota while the rejection
|
|
1074
|
+
* propagates. Recreated by both reset paths — the abort is one-way within
|
|
1075
|
+
* a run, and a reused graph must start its next run unaborted. */
|
|
1076
|
+
breakerAbort = new AbortController();
|
|
1077
|
+
|
|
1078
|
+
/** Incremented whenever `breakerAbort` is replaced. Stamped into each
|
|
1079
|
+
* model attempt's metadata ({@link STREAM_LIMIT_EPOCH_KEY}) so the stream
|
|
1080
|
+
* handler's consumer-side trip binds to the run that produced the event
|
|
1081
|
+
* rather than whichever controller is live when a straggling chunk is
|
|
1082
|
+
* finally handled. */
|
|
1083
|
+
breakerEpoch = 0;
|
|
1084
|
+
|
|
1085
|
+
/** Immutable snapshot of the run's breaker identity (epoch + controller),
|
|
1086
|
+
* replaced as ONE object whenever `resetValues` installs a fresh
|
|
1087
|
+
* controller. Sites that pause across awaits capture it at entry and
|
|
1088
|
+
* revalidate by REFERENCE afterwards — a single identity comparison
|
|
1089
|
+
* proves no reset happened while suspended, where separate epoch and
|
|
1090
|
+
* controller reads could interleave with one. */
|
|
1091
|
+
runScope: RunBreakerScope = Object.freeze({
|
|
1092
|
+
epoch: 0,
|
|
1093
|
+
controller: this.breakerAbort,
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
/** Generation keys of model attempts still in flight (see
|
|
1097
|
+
* `StreamLimitState.activeStreamLimitGenerations`). Lazily created by the
|
|
1098
|
+
* attempt lease; spans resets on purpose. */
|
|
1099
|
+
activeStreamLimitGenerations?: Set<string>;
|
|
1100
|
+
|
|
1101
|
+
/** The stream-limit error behind an already-fired breaker, whether this
|
|
1102
|
+
* graph's own controller tripped or a parent run's breaker arrived through
|
|
1103
|
+
* the composed constructor signal (child graphs own separate controllers).
|
|
1104
|
+
* Providers can translate either abort into a generic error, and recovery
|
|
1105
|
+
* paths must not run in that state. */
|
|
1106
|
+
protected resolveTrippedBreakerReason(
|
|
1107
|
+
breakerSignal: AbortSignal = this.breakerAbort.signal
|
|
1108
|
+
): StreamLimitExceededError | undefined {
|
|
1109
|
+
if (
|
|
1110
|
+
breakerSignal.aborted &&
|
|
1111
|
+
breakerSignal.reason instanceof StreamLimitExceededError
|
|
1112
|
+
) {
|
|
1113
|
+
return breakerSignal.reason;
|
|
1114
|
+
}
|
|
1115
|
+
if (
|
|
1116
|
+
this.signal?.aborted === true &&
|
|
1117
|
+
this.signal.reason instanceof StreamLimitExceededError
|
|
1118
|
+
) {
|
|
1119
|
+
return this.signal.reason;
|
|
1120
|
+
}
|
|
1121
|
+
return undefined;
|
|
1122
|
+
}
|
|
1054
1123
|
/**
|
|
1055
1124
|
* Seals charged against `preemption.maxSeals`. Per-turn: cleared by both
|
|
1056
1125
|
* reset paths so a fresh turn gets a fresh budget, while a HITL resume —
|
|
@@ -1108,6 +1177,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1108
1177
|
subagentUsageSink,
|
|
1109
1178
|
subagentScope,
|
|
1110
1179
|
preemption,
|
|
1180
|
+
streamLimits,
|
|
1111
1181
|
}: t.StandardGraphInput) {
|
|
1112
1182
|
super();
|
|
1113
1183
|
this.runId = runId;
|
|
@@ -1117,6 +1187,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1117
1187
|
this.subagentUsageSink = subagentUsageSink;
|
|
1118
1188
|
this.subagentScope = subagentScope === true;
|
|
1119
1189
|
this.preemption = preemption;
|
|
1190
|
+
this.streamLimits = resolveStreamLimits(streamLimits);
|
|
1120
1191
|
|
|
1121
1192
|
if (agents.length === 0) {
|
|
1122
1193
|
throw new Error('At least one agent configuration is required');
|
|
@@ -1162,6 +1233,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1162
1233
|
this.clearEagerEventToolUsageCounts();
|
|
1163
1234
|
this.eagerEventToolCallChunks.clear();
|
|
1164
1235
|
this.eagerEventToolSuppressions.clear();
|
|
1236
|
+
/** Grace sweep instead of a clear: producer loops of straggling
|
|
1237
|
+
* attempts use these maps directly and sit outside the consumer-only
|
|
1238
|
+
* epoch gate — clearing would hand a cancellation-ignoring provider a
|
|
1239
|
+
* fresh allowance at every run start. Entries from the epoch that is
|
|
1240
|
+
* ending survive exactly one reset so those stragglers stay on their
|
|
1241
|
+
* original budgets; older entries are removed. */
|
|
1242
|
+
sweepStaleStreamLimitEntries(
|
|
1243
|
+
this.streamedToolCallArgTallies,
|
|
1244
|
+
this.breakerEpoch,
|
|
1245
|
+
this.activeStreamLimitGenerations
|
|
1246
|
+
);
|
|
1247
|
+
sweepStaleStreamLimitEntries(
|
|
1248
|
+
this.streamDeltaEventCounts,
|
|
1249
|
+
this.breakerEpoch,
|
|
1250
|
+
this.activeStreamLimitGenerations
|
|
1251
|
+
);
|
|
1252
|
+
this.streamLimitChargeCredits = undefined;
|
|
1253
|
+
/** Run-start is the only safe replacement point for the breaker:
|
|
1254
|
+
* end-of-run cleanup must leave it in place so straggling parallel
|
|
1255
|
+
* children from the failed run cannot start on a fresh signal.
|
|
1256
|
+
* Replaced UNCONDITIONALLY here — a run that failed on an ordinary
|
|
1257
|
+
* error leaves the controller un-aborted, and stragglers still settling
|
|
1258
|
+
* hold their entry-time capture of it; a late stream-limit trip on that
|
|
1259
|
+
* old controller must not cancel the run starting now. */
|
|
1260
|
+
this.breakerAbort = new AbortController();
|
|
1261
|
+
this.breakerEpoch += 1;
|
|
1262
|
+
this.runScope = Object.freeze({
|
|
1263
|
+
epoch: this.breakerEpoch,
|
|
1264
|
+
controller: this.breakerAbort,
|
|
1265
|
+
});
|
|
1165
1266
|
this.handlerDispatchedStepIds = resetIfNotEmpty(
|
|
1166
1267
|
this.handlerDispatchedStepIds,
|
|
1167
1268
|
new Set()
|
|
@@ -1219,6 +1320,21 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1219
1320
|
super.clearHeavyState();
|
|
1220
1321
|
this.messages = [];
|
|
1221
1322
|
this.overrideModel = undefined;
|
|
1323
|
+
/** Stream-limit accounting (argument tallies, event counts, charge
|
|
1324
|
+
* credits) deliberately SURVIVES cleanup: this runs in `processStream`'s
|
|
1325
|
+
* finally, which an ordinary parallel-branch failure reaches while
|
|
1326
|
+
* sibling attempts are still unwinding on the retained breaker — a
|
|
1327
|
+
* cancellation-ignoring provider's late chunks would otherwise recreate
|
|
1328
|
+
* their budgets from zero and stream another full allowance. The maps
|
|
1329
|
+
* are bounded by in-flight call sizes and `resetValues` clears them at
|
|
1330
|
+
* the next run start, where the epoch bump already drops stamped
|
|
1331
|
+
* straggler events before accounting. */
|
|
1332
|
+
/** Deliberately NOT recreating a tripped breakerAbort here: this runs in
|
|
1333
|
+
* `processStream`'s cleanup, which a rejected parallel batch reaches
|
|
1334
|
+
* while sibling subagents can still be pre-invoke — a fresh controller
|
|
1335
|
+
* would hand them an un-aborted signal and let provider requests start
|
|
1336
|
+
* after the run already failed. `resetValues` recreates it when the next
|
|
1337
|
+
* run begins. */
|
|
1222
1338
|
/**
|
|
1223
1339
|
* Turn state only. The reported totals must outlive cleanup — this runs
|
|
1224
1340
|
* in `processStream`'s `finally`, and the host reads `getPreemptStats()`
|
|
@@ -1501,13 +1617,17 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1501
1617
|
/* Misc.*/
|
|
1502
1618
|
|
|
1503
1619
|
getRunMessages(): BaseMessage[] | undefined {
|
|
1504
|
-
|
|
1620
|
+
/** Runtime-honest widening: a disposed-but-cached graph (HITL
|
|
1621
|
+
* resume/reconnect through a WeakRef cache) can carry null here despite
|
|
1622
|
+
* the field type. */
|
|
1623
|
+
const messages = this.messages as BaseMessage[] | undefined;
|
|
1624
|
+
if (messages == null) {
|
|
1505
1625
|
return this.cachedRunMessages;
|
|
1506
1626
|
}
|
|
1507
|
-
if (
|
|
1627
|
+
if (messages.length === 0 && this.cachedRunMessages != null) {
|
|
1508
1628
|
return this.cachedRunMessages;
|
|
1509
1629
|
}
|
|
1510
|
-
return
|
|
1630
|
+
return messages.slice(this.startIndex);
|
|
1511
1631
|
}
|
|
1512
1632
|
|
|
1513
1633
|
override getDiscoveredTools(agentId?: string): string[] {
|
|
@@ -1559,10 +1679,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1559
1679
|
// `messages` can be null/undefined on a graph that has been disposed
|
|
1560
1680
|
// (clearHeavyState) but is still reachable via a cache (e.g. RedisJobStore's
|
|
1561
1681
|
// WeakRef) during a HITL resume/reconnect. Guard instead of dereferencing null.
|
|
1562
|
-
|
|
1682
|
+
const messages = this.messages as BaseMessage[] | undefined;
|
|
1683
|
+
if (messages == null) {
|
|
1563
1684
|
return undefined;
|
|
1564
1685
|
}
|
|
1565
|
-
return convertMessagesToContent(
|
|
1686
|
+
return convertMessagesToContent(messages.slice(this.startIndex));
|
|
1566
1687
|
}
|
|
1567
1688
|
|
|
1568
1689
|
getCalibrationRatio(): number {
|
|
@@ -1597,13 +1718,14 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1597
1718
|
// `contentData` can be null/undefined on a disposed-but-cached graph during a
|
|
1598
1719
|
// HITL resume/reconnect; without this guard `[...this.contentData]` throws
|
|
1599
1720
|
// "this.contentData is not iterable".
|
|
1600
|
-
|
|
1721
|
+
const contentData = this.contentData as t.RunStep[] | undefined;
|
|
1722
|
+
if (contentData == null) {
|
|
1601
1723
|
return [];
|
|
1602
1724
|
}
|
|
1603
1725
|
if (agentId == null || agentId === '') {
|
|
1604
|
-
return [...
|
|
1726
|
+
return [...contentData];
|
|
1605
1727
|
}
|
|
1606
|
-
return
|
|
1728
|
+
return contentData.filter((step) => step.agentId === agentId);
|
|
1607
1729
|
}
|
|
1608
1730
|
|
|
1609
1731
|
/**
|
|
@@ -1734,6 +1856,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1734
1856
|
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
1735
1857
|
toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
|
|
1736
1858
|
fileCheckpointer: this.getOrCreateFileCheckpointer(),
|
|
1859
|
+
getBreakerSignal: (): AbortSignal => this.breakerAbort.signal,
|
|
1860
|
+
getRunScope: (): RunBreakerScope => this.runScope,
|
|
1737
1861
|
errorHandler: (data, metadata): Promise<boolean> =>
|
|
1738
1862
|
StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
1739
1863
|
});
|
|
@@ -1801,6 +1925,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1801
1925
|
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
1802
1926
|
toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
|
|
1803
1927
|
fileCheckpointer: this.getOrCreateFileCheckpointer(),
|
|
1928
|
+
getBreakerSignal: (): AbortSignal => this.breakerAbort.signal,
|
|
1929
|
+
getRunScope: (): RunBreakerScope => this.runScope,
|
|
1804
1930
|
});
|
|
1805
1931
|
this.registerCompiledToolNode(node);
|
|
1806
1932
|
return node;
|
|
@@ -1927,6 +2053,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1927
2053
|
state: t.AgentSubgraphState,
|
|
1928
2054
|
config?: RunnableConfig
|
|
1929
2055
|
): Promise<Partial<t.AgentSubgraphState>> => {
|
|
2056
|
+
/** Captured at node ENTRY, before any host-facing await (context-usage
|
|
2057
|
+
* dispatch, hooks): a sibling's trip can fail the run and a prompt
|
|
2058
|
+
* next run can reset the controller while this node is paused in one
|
|
2059
|
+
* of those awaits, and a later capture would bind this attempt to the
|
|
2060
|
+
* fresh controller. Trips and reason reads below stay on this
|
|
2061
|
+
* capture. */
|
|
2062
|
+
const attemptBreaker = this.breakerAbort;
|
|
2063
|
+
const attemptBreakerEpoch = this.breakerEpoch;
|
|
2064
|
+
/** Already-tripped-at-entry: a parallel sibling's breach has failed
|
|
2065
|
+
* the run before this node was scheduled. Rethrow before hooks or the
|
|
2066
|
+
* provider call — a custom provider that doesn't synchronously reject
|
|
2067
|
+
* an aborted signal would otherwise start another model request on a
|
|
2068
|
+
* failed run. */
|
|
2069
|
+
const entryTripReason = this.resolveTrippedBreakerReason(
|
|
2070
|
+
attemptBreaker.signal
|
|
2071
|
+
);
|
|
2072
|
+
if (entryTripReason != null) {
|
|
2073
|
+
throw entryTripReason;
|
|
2074
|
+
}
|
|
1930
2075
|
const agentContext = this.agentContexts.get(agentId);
|
|
1931
2076
|
if (!agentContext) {
|
|
1932
2077
|
throw new Error(`Agent context not found for agentId: ${agentId}`);
|
|
@@ -3120,6 +3265,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3120
3265
|
let langfuseHandler: CallbackEntry | undefined;
|
|
3121
3266
|
let invokeConfig = {
|
|
3122
3267
|
...config,
|
|
3268
|
+
/** The run-scoped breaker composed in, so a stream-limit trip in one
|
|
3269
|
+
* parallel agent node also cancels sibling nodes' in-flight model
|
|
3270
|
+
* calls, not only their subagents. */
|
|
3271
|
+
signal: composeAbortSignals(config.signal, attemptBreaker.signal),
|
|
3123
3272
|
metadata: {
|
|
3124
3273
|
...(config.metadata ?? {}),
|
|
3125
3274
|
...traceMetadata,
|
|
@@ -3128,6 +3277,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3128
3277
|
* length cap, but scope trust (`isForeignScope`) needs the id
|
|
3129
3278
|
* verbatim regardless of length. */
|
|
3130
3279
|
agentId,
|
|
3280
|
+
[STREAM_LIMIT_EPOCH_KEY]: attemptBreakerEpoch,
|
|
3131
3281
|
},
|
|
3132
3282
|
};
|
|
3133
3283
|
initializeLangfuseTracing(langfuse);
|
|
@@ -3166,6 +3316,18 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3166
3316
|
if (preInvokeContextOverflowError != null) {
|
|
3167
3317
|
throw preInvokeContextOverflowError;
|
|
3168
3318
|
}
|
|
3319
|
+
/** Rechecked after the pre-invoke awaits (context-usage dispatch,
|
|
3320
|
+
* hooks): a sibling can trip the breaker while this node is paused
|
|
3321
|
+
* in one of them, and a provider that doesn't synchronously reject
|
|
3322
|
+
* an aborted signal would still start the request. */
|
|
3323
|
+
{
|
|
3324
|
+
const preInvokeTrip = this.resolveTrippedBreakerReason(
|
|
3325
|
+
attemptBreaker.signal
|
|
3326
|
+
);
|
|
3327
|
+
if (preInvokeTrip != null) {
|
|
3328
|
+
throw preInvokeTrip;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3169
3331
|
result = await withLangfuseRuntimeScope(
|
|
3170
3332
|
resolveLangfuseRuntimeScope({
|
|
3171
3333
|
runLangfuse: this.langfuse,
|
|
@@ -3189,6 +3351,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3189
3351
|
graph: this,
|
|
3190
3352
|
metadata,
|
|
3191
3353
|
});
|
|
3354
|
+
/**
|
|
3355
|
+
* A tripped stream circuit breaker is a deliberate abort, not a
|
|
3356
|
+
* provider failure: entering overflow recovery or the fallback chain
|
|
3357
|
+
* would spend more provider work after the safety limit fired, and a
|
|
3358
|
+
* succeeding fallback would resolve a run the public contract says
|
|
3359
|
+
* must reject. Rethrow before any recovery path.
|
|
3360
|
+
*/
|
|
3361
|
+
if (primaryError instanceof StreamLimitExceededError) {
|
|
3362
|
+
/** Tripped before rethrowing so parallel agent nodes' in-flight
|
|
3363
|
+
* model calls and subagents stop while the rejection propagates. */
|
|
3364
|
+
attemptBreaker.abort(primaryError);
|
|
3365
|
+
throw primaryError;
|
|
3366
|
+
}
|
|
3367
|
+
/** A sibling that tripped the shared breaker aborts this branch's
|
|
3368
|
+
* composed signal, and some providers surface that as a generic
|
|
3369
|
+
* abort error; entering overflow planning or the fallback chain
|
|
3370
|
+
* would start new provider work after the run-wide breaker fired.
|
|
3371
|
+
* Rethrow the breaker's own stream-limit reason instead. */
|
|
3372
|
+
{
|
|
3373
|
+
const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
|
|
3374
|
+
if (trippedReason != null) {
|
|
3375
|
+
throw trippedReason;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3192
3378
|
/**
|
|
3193
3379
|
* A context overflow is a deterministic consequence of the payload,
|
|
3194
3380
|
* not a provider being unavailable — so it is answered by compacting
|
|
@@ -3422,6 +3608,20 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3422
3608
|
})
|
|
3423
3609
|
);
|
|
3424
3610
|
} catch (fallbackError) {
|
|
3611
|
+
if (fallbackError instanceof StreamLimitExceededError) {
|
|
3612
|
+
/** Same treatment as the primary path: a fallback stream that
|
|
3613
|
+
* trips the breaker must stop parallel agent nodes' model calls
|
|
3614
|
+
* and subagents before the rejection propagates. */
|
|
3615
|
+
attemptBreaker.abort(fallbackError);
|
|
3616
|
+
throw fallbackError;
|
|
3617
|
+
}
|
|
3618
|
+
{
|
|
3619
|
+
/** Same sibling-abort translation guard as the primary catch. */
|
|
3620
|
+
const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
|
|
3621
|
+
if (trippedReason != null) {
|
|
3622
|
+
throw trippedReason;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3425
3625
|
const overflowCandidates =
|
|
3426
3626
|
getFallbackOverflowCandidates(fallbackError);
|
|
3427
3627
|
let fallbackRecovery: OverflowRecoveryPlan | null = null;
|
|
@@ -3840,6 +4040,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3840
4040
|
const executor = new SubagentExecutor({
|
|
3841
4041
|
configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
|
|
3842
4042
|
parentSignal: this.signal,
|
|
4043
|
+
breakerScope: {
|
|
4044
|
+
controller: (): AbortController => this.breakerAbort,
|
|
4045
|
+
},
|
|
3843
4046
|
hookRegistry: this.hookRegistry,
|
|
3844
4047
|
/** Lazy — Run wires the registry onto the graph AFTER
|
|
3845
4048
|
* `createWorkflow()` runs, so a direct capture here would be
|
|
@@ -3850,6 +4053,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3850
4053
|
langfuse: this.langfuse,
|
|
3851
4054
|
tokenCounter: agentContext.tokenCounter,
|
|
3852
4055
|
usageSink: this.subagentUsageSink,
|
|
4056
|
+
streamLimits: this.streamLimits,
|
|
3853
4057
|
maxDepth: effectiveSubagentDepth,
|
|
3854
4058
|
createChildGraph: (input): StandardGraph => {
|
|
3855
4059
|
const childGraph = new StandardGraph(input);
|
|
@@ -3912,11 +4116,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3912
4116
|
const toolCall = (config as { toolCall?: { id?: string } }).toolCall;
|
|
3913
4117
|
const parentToolCallId =
|
|
3914
4118
|
typeof toolCall?.id === 'string' ? toolCall.id : undefined;
|
|
4119
|
+
/** The parent tool batch's entry-captured scope (stamped by
|
|
4120
|
+
* ToolNode before PreToolUse hooks) — binds this child to the
|
|
4121
|
+
* run that dispatched it, not to whatever controller a reset
|
|
4122
|
+
* installed while the hooks were awaited. */
|
|
4123
|
+
const batchScope = config.configurable?.[
|
|
4124
|
+
RUN_BREAKER_SCOPE_CONFIG_KEY
|
|
4125
|
+
] as RunBreakerScope | undefined;
|
|
3915
4126
|
const result = await executor.execute({
|
|
3916
4127
|
description,
|
|
3917
4128
|
subagentType,
|
|
3918
4129
|
threadId,
|
|
3919
4130
|
parentToolCallId,
|
|
4131
|
+
breaker: batchScope?.controller,
|
|
3920
4132
|
/**
|
|
3921
4133
|
* Forward the parent's `configurable` so host-set fields
|
|
3922
4134
|
* (`requestBody`, `user`, etc.) propagate into the child
|
|
@@ -4002,6 +4214,21 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4002
4214
|
}),
|
|
4003
4215
|
});
|
|
4004
4216
|
|
|
4217
|
+
const readChargeCredits = ():
|
|
4218
|
+
| WeakMap<object, Map<string, number>>
|
|
4219
|
+
| undefined => this.streamLimitChargeCredits;
|
|
4220
|
+
const readBreakerEpoch = (): number => this.breakerEpoch;
|
|
4221
|
+
const readActiveGenerations = (): Set<string> | undefined =>
|
|
4222
|
+
this.activeStreamLimitGenerations;
|
|
4223
|
+
const writeActiveGenerations = (value: Set<string> | undefined): void => {
|
|
4224
|
+
this.activeStreamLimitGenerations = value;
|
|
4225
|
+
};
|
|
4226
|
+
const writeChargeCredits = (
|
|
4227
|
+
value: WeakMap<object, Map<string, number>> | undefined
|
|
4228
|
+
): void => {
|
|
4229
|
+
this.streamLimitChargeCredits = value;
|
|
4230
|
+
};
|
|
4231
|
+
|
|
4005
4232
|
const workflow = new StateGraph(StateAnnotation)
|
|
4006
4233
|
.addNode(agentNode, this.createCallModel(agentId))
|
|
4007
4234
|
.addNode(
|
|
@@ -4025,6 +4252,54 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4025
4252
|
runId: this.runId,
|
|
4026
4253
|
isMultiAgent: this.isMultiAgentGraph(),
|
|
4027
4254
|
hookRegistry: this.hookRegistry,
|
|
4255
|
+
/**
|
|
4256
|
+
* Live references (both maps are cleared in place, never
|
|
4257
|
+
* replaced), so summarization streams share the run's event
|
|
4258
|
+
* budget accounting under their own generation key.
|
|
4259
|
+
*/
|
|
4260
|
+
streamLimits: this.streamLimits,
|
|
4261
|
+
streamDeltaEventCounts: this.streamDeltaEventCounts,
|
|
4262
|
+
streamedToolCallArgTallies: this.streamedToolCallArgTallies,
|
|
4263
|
+
/** Accessor pair, not a value copy: unlike the two maps above,
|
|
4264
|
+
* the credit map is REPLACED by graph resets rather than cleared
|
|
4265
|
+
* in place, and the guards' lazy `??=` must install onto the
|
|
4266
|
+
* graph — a copy held here would survive resets and grow one
|
|
4267
|
+
* attempt-stamped entry per compaction for a retained reused
|
|
4268
|
+
* chunk object. */
|
|
4269
|
+
get streamLimitChargeCredits() {
|
|
4270
|
+
return readChargeCredits();
|
|
4271
|
+
},
|
|
4272
|
+
set streamLimitChargeCredits(
|
|
4273
|
+
value: WeakMap<object, Map<string, number>> | undefined
|
|
4274
|
+
) {
|
|
4275
|
+
writeChargeCredits(value);
|
|
4276
|
+
},
|
|
4277
|
+
/** Live epoch, so summary tallies are creation-tagged and the
|
|
4278
|
+
* resetValues grace sweep treats them like model-attempt
|
|
4279
|
+
* entries. */
|
|
4280
|
+
get breakerEpoch(): number {
|
|
4281
|
+
return readBreakerEpoch();
|
|
4282
|
+
},
|
|
4283
|
+
/** Accessor pair like the charge credits: summary attempts
|
|
4284
|
+
* lease their generations on the GRAPH's active set, and the
|
|
4285
|
+
* lazy `??=` in the lease helper must install there. */
|
|
4286
|
+
get activeStreamLimitGenerations(): Set<string> | undefined {
|
|
4287
|
+
return readActiveGenerations();
|
|
4288
|
+
},
|
|
4289
|
+
set activeStreamLimitGenerations(value: Set<string> | undefined) {
|
|
4290
|
+
writeActiveGenerations(value);
|
|
4291
|
+
},
|
|
4292
|
+
/** Read per attempt: a sibling branch tripping the run breaker
|
|
4293
|
+
* must also cancel in-flight summarization model calls. */
|
|
4294
|
+
getBreakerSignal: (): AbortSignal => this.breakerAbort.signal,
|
|
4295
|
+
/** The node captures this at entry so its own chunk handler's
|
|
4296
|
+
* breach trips the run that STARTED the summarization, not a
|
|
4297
|
+
* controller installed by a later reset. */
|
|
4298
|
+
getBreakerController: (): AbortController => this.breakerAbort,
|
|
4299
|
+
/** Captured at node entry and stamped into the summary attempt
|
|
4300
|
+
* metadata, so the wire consumer epoch-gates old-run summary
|
|
4301
|
+
* chunks exactly like model-attempt chunks. */
|
|
4302
|
+
getBreakerEpoch: (): number => this.breakerEpoch,
|
|
4028
4303
|
dispatchRunStep: async (runStep, nodeConfig) => {
|
|
4029
4304
|
const resolvedConfig = nodeConfig ?? this.config;
|
|
4030
4305
|
if (runStep.agentId != null) {
|