@librechat/agents 3.3.3 → 3.3.5
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/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +21 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/events.cjs +10 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +793 -111
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +25 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +8 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +38 -13
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +331 -5
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/preempt.cjs +132 -0
- package/dist/cjs/llm/preempt.cjs.map +1 -0
- package/dist/cjs/main.cjs +36 -5
- package/dist/cjs/messages/alternation.cjs +92 -0
- package/dist/cjs/messages/alternation.cjs.map +1 -0
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +290 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +63 -0
- package/dist/cjs/messages/handoffCue.cjs.map +1 -0
- package/dist/cjs/messages/index.cjs +4 -1
- package/dist/cjs/messages/injected.cjs +60 -0
- package/dist/cjs/messages/injected.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +80 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +18 -9
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +35 -10
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +56 -63
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +9 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +19 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/events.mjs +10 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +798 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +25 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +9 -1
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +38 -13
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +332 -8
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/preempt.mjs +131 -0
- package/dist/esm/llm/preempt.mjs.map +1 -0
- package/dist/esm/main.mjs +14 -11
- package/dist/esm/messages/alternation.mjs +91 -0
- package/dist/esm/messages/alternation.mjs.map +1 -0
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +290 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +61 -0
- package/dist/esm/messages/handoffCue.mjs.map +1 -0
- package/dist/esm/messages/index.mjs +4 -1
- package/dist/esm/messages/injected.mjs +60 -0
- package/dist/esm/messages/injected.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +80 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +19 -10
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +34 -11
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +56 -63
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +9 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/common/constants.d.ts +18 -0
- package/dist/types/graphs/Graph.d.ts +139 -2
- package/dist/types/hooks/HookRegistry.d.ts +15 -0
- package/dist/types/hooks/index.d.ts +12 -1
- package/dist/types/hooks/types.d.ts +45 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +34 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/llm/preempt.d.ts +40 -0
- package/dist/types/messages/alternation.d.ts +23 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/handoffCue.d.ts +40 -0
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/injected.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/run.d.ts +7 -0
- package/dist/types/stream.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +0 -7
- package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
- package/dist/types/tools/search/types.d.ts +31 -2
- package/dist/types/types/graph.d.ts +8 -1
- package/dist/types/types/run.d.ts +65 -0
- package/dist/types/types/stream.d.ts +1 -26
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
- package/src/common/constants.ts +21 -0
- package/src/events.ts +15 -1
- package/src/graphs/Graph.ts +1261 -198
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
- package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
- package/src/hooks/HookRegistry.ts +40 -0
- package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
- package/src/hooks/index.ts +16 -2
- package/src/hooks/types.ts +47 -3
- package/src/index.ts +1 -1
- package/src/langfuse.ts +26 -1
- package/src/llm/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +131 -1
- package/src/llm/bedrock/utils/message_inputs.ts +100 -24
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.alternation.test.ts +87 -0
- package/src/llm/invoke.handoffCue.test.ts +112 -0
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +611 -8
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/llm/preempt.test.ts +323 -0
- package/src/llm/preempt.ts +178 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/alternation.test.ts +212 -0
- package/src/messages/alternation.ts +112 -0
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +454 -91
- package/src/messages/formatAgentMessages.steer.test.ts +267 -0
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/handoffCue.test.ts +96 -0
- package/src/messages/handoffCue.ts +78 -0
- package/src/messages/index.ts +3 -0
- package/src/messages/injected.test.ts +90 -0
- package/src/messages/injected.ts +74 -0
- package/src/messages/prune.ts +996 -183
- package/src/run.ts +91 -6
- package/src/scripts/preempt-probe.ts +330 -0
- package/src/scripts/preempt-scenarios.ts +388 -0
- package/src/session/handlers.ts +32 -12
- package/src/specs/handoffCue.test.ts +165 -0
- package/src/specs/langfuse-callbacks.test.ts +352 -2
- package/src/specs/preemptSeal.test.ts +309 -0
- package/src/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.dispatch.test.ts +63 -0
- package/src/stream.ts +59 -23
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +150 -99
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- package/src/tools/search/keenable-scraper.test.ts +153 -0
- package/src/tools/search/keenable-scraper.ts +137 -0
- package/src/tools/search/tool.ts +13 -2
- package/src/tools/search/types.ts +50 -3
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +1 -42
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
- package/dist/cjs/splitStream.cjs +0 -151
- package/dist/cjs/splitStream.cjs.map +0 -1
- package/dist/esm/splitStream.mjs +0 -150
- package/dist/esm/splitStream.mjs.map +0 -1
- package/dist/types/mockStream.d.ts +0 -32
- package/dist/types/splitStream.d.ts +0 -37
- package/src/mockStream.ts +0 -99
- package/src/splitStream.ts +0 -234
|
@@ -3,20 +3,27 @@ const require_langfuseRuntimeScope = require("../langfuseRuntimeScope.cjs");
|
|
|
3
3
|
const require_langfuse = require("../langfuse.cjs");
|
|
4
4
|
const require_langfuseToolOutputTracing = require("../langfuseToolOutputTracing.cjs");
|
|
5
5
|
const require_callbacks = require("../utils/callbacks.cjs");
|
|
6
|
+
const require_constants = require("../common/constants.cjs");
|
|
6
7
|
const require_enum = require("../common/enum.cjs");
|
|
7
8
|
require("../common/index.cjs");
|
|
9
|
+
const require_truncation = require("../utils/truncation.cjs");
|
|
10
|
+
const require_toolContent = require("../utils/toolContent.cjs");
|
|
8
11
|
const require_tokens = require("../utils/tokens.cjs");
|
|
12
|
+
const require_preempt = require("../llm/preempt.cjs");
|
|
9
13
|
const require_instrumentation = require("../instrumentation.cjs");
|
|
14
|
+
const require_cache = require("../messages/cache.cjs");
|
|
10
15
|
const require_core = require("../messages/core.cjs");
|
|
11
16
|
const require_ids = require("../messages/ids.cjs");
|
|
12
17
|
const require_prune = require("../messages/prune.cjs");
|
|
13
18
|
const require_budget = require("../messages/budget.cjs");
|
|
14
19
|
const require_events = require("../utils/events.cjs");
|
|
15
20
|
const require_format = require("../messages/format.cjs");
|
|
16
|
-
const require_cache = require("../messages/cache.cjs");
|
|
17
21
|
const require_anthropicToolCache = require("../messages/anthropicToolCache.cjs");
|
|
18
22
|
const require_content = require("../messages/content.cjs");
|
|
19
23
|
const require_tools = require("../messages/tools.cjs");
|
|
24
|
+
const require_injected = require("../messages/injected.cjs");
|
|
25
|
+
const require_alternation = require("../messages/alternation.cjs");
|
|
26
|
+
const require_handoffCue = require("../messages/handoffCue.cjs");
|
|
20
27
|
const require_reducer = require("../messages/reducer.cjs");
|
|
21
28
|
const require_recency = require("../messages/recency.cjs");
|
|
22
29
|
require("../messages/index.cjs");
|
|
@@ -27,11 +34,13 @@ const require_toolOutputReferences = require("../tools/toolOutputReferences.cjs"
|
|
|
27
34
|
const require_events$1 = require("../events.cjs");
|
|
28
35
|
const require_run = require("../utils/run.cjs");
|
|
29
36
|
require("../utils/index.cjs");
|
|
30
|
-
const require_contextOverflowRecovery = require("../llm/contextOverflowRecovery.cjs");
|
|
31
37
|
const require_toolCache = require("../llm/bedrock/toolCache.cjs");
|
|
32
38
|
const require_init = require("../llm/init.cjs");
|
|
33
39
|
const require_invoke = require("../llm/invoke.cjs");
|
|
40
|
+
const require_contextOverflowRecovery = require("../llm/contextOverflowRecovery.cjs");
|
|
34
41
|
const require_toolCache$1 = require("../llm/openrouter/toolCache.cjs");
|
|
42
|
+
const require_executeHooks = require("../hooks/executeHooks.cjs");
|
|
43
|
+
require("../hooks/index.cjs");
|
|
35
44
|
const require_LocalCodingTools = require("../tools/local/LocalCodingTools.cjs");
|
|
36
45
|
const require_CloudflareSandboxTools = require("../tools/cloudflare/CloudflareSandboxTools.cjs");
|
|
37
46
|
require("../tools/cloudflare/index.cjs");
|
|
@@ -41,7 +50,7 @@ const require_ToolNode = require("../tools/ToolNode.cjs");
|
|
|
41
50
|
const require_SubagentExecutor = require("../tools/subagent/SubagentExecutor.cjs");
|
|
42
51
|
require("../tools/subagent/index.cjs");
|
|
43
52
|
const require_SubagentTool = require("../tools/SubagentTool.cjs");
|
|
44
|
-
const require_index$
|
|
53
|
+
const require_index$7 = require("../summarization/index.cjs");
|
|
45
54
|
const require_request = require("../llm/request.cjs");
|
|
46
55
|
const require_node = require("../summarization/node.cjs");
|
|
47
56
|
const require_schema = require("../tools/schema.cjs");
|
|
@@ -51,8 +60,25 @@ let _langchain_core_messages = require("@langchain/core/messages");
|
|
|
51
60
|
let _langchain_langgraph = require("@langchain/langgraph");
|
|
52
61
|
let _langchain_core_tools = require("@langchain/core/tools");
|
|
53
62
|
let nanoid = require("nanoid");
|
|
63
|
+
let _langchain_core_errors = require("@langchain/core/errors");
|
|
64
|
+
let uuid = require("uuid");
|
|
54
65
|
//#region src/graphs/Graph.ts
|
|
55
66
|
const { AGENT, TOOLS, SUMMARIZE } = require_enum.GraphNodeKeys;
|
|
67
|
+
const EMPTY_PREEMPT_BOUNDARY = {
|
|
68
|
+
messages: [],
|
|
69
|
+
preventContinuation: false
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* One signal that fires when either input fires. `AbortSignal.any` is skipped
|
|
73
|
+
* when the inputs collapse to a single signal — the composite is a fresh
|
|
74
|
+
* object per call, and the common cases (one channel, or the host reusing the
|
|
75
|
+
* same controller for both) don't need one.
|
|
76
|
+
*/
|
|
77
|
+
function composeAbortSignals(a, b) {
|
|
78
|
+
if (a == null || a === b) return b;
|
|
79
|
+
if (b == null) return a;
|
|
80
|
+
return AbortSignal.any([a, b]);
|
|
81
|
+
}
|
|
56
82
|
/** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
|
|
57
83
|
const CALIBRATION_VARIANCE_THRESHOLD = .15;
|
|
58
84
|
function createToolHandlerRegistry(source) {
|
|
@@ -62,19 +88,6 @@ function createToolHandlerRegistry(source) {
|
|
|
62
88
|
registry.register("on_tool_execute", toolHandler);
|
|
63
89
|
return registry;
|
|
64
90
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Start index of the span post-prune formatters can mutate in place: the
|
|
67
|
-
* trailing tool batch plus its owning AI message (artifact formatting touches
|
|
68
|
-
* every tool result after the last AI tool call; Bedrock rewrites the AI
|
|
69
|
-
* message before a trailing tool result). Capped so the usage-snapshot
|
|
70
|
-
* recount stays constant-cost.
|
|
71
|
-
*/
|
|
72
|
-
function trailingMutationStart(messages) {
|
|
73
|
-
const MAX_SPAN = 16;
|
|
74
|
-
let index = messages.length - 1;
|
|
75
|
-
while (index >= 0 && messages[index]?.getType() === "tool" && messages.length - index < MAX_SPAN) index--;
|
|
76
|
-
return Math.max(0, Math.min(index, messages.length - 2));
|
|
77
|
-
}
|
|
78
91
|
function getHandlerDispatchedEventKey(eventName, stepId) {
|
|
79
92
|
return `${eventName}:${stepId}`;
|
|
80
93
|
}
|
|
@@ -276,6 +289,20 @@ var Graph = class {
|
|
|
276
289
|
reasoningStepHasDeltas = /* @__PURE__ */ new Set();
|
|
277
290
|
handlerDispatchedEventCounts = /* @__PURE__ */ new Map();
|
|
278
291
|
signal;
|
|
292
|
+
/**
|
|
293
|
+
* The abort signal the CALLER handed to the current `processStream` call,
|
|
294
|
+
* assigned unconditionally — including back to `undefined` — on every call.
|
|
295
|
+
*
|
|
296
|
+
* Kept separate from {@link signal} on purpose. That field is construction
|
|
297
|
+
* state with its own consumers (model-call config, subagent parentSignal),
|
|
298
|
+
* so adopting a per-call signal into it would leak one call's controller
|
|
299
|
+
* into the next — `clearHeavyState()` is skipped on HITL interrupts, so a
|
|
300
|
+
* host that aborts a finished request's controller would poison the resumed
|
|
301
|
+
* run's model calls and boundary drains with an already-aborted signal.
|
|
302
|
+
* Boundary dispatch composes the two instead; see
|
|
303
|
+
* `StandardGraph.dispatchPreemptBoundary`.
|
|
304
|
+
*/
|
|
305
|
+
callerSignal;
|
|
279
306
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
280
307
|
invokedToolIds;
|
|
281
308
|
handlerRegistry;
|
|
@@ -352,6 +379,7 @@ var Graph = class {
|
|
|
352
379
|
clearHeavyState() {
|
|
353
380
|
this.config = void 0;
|
|
354
381
|
this.signal = void 0;
|
|
382
|
+
this.callerSignal = void 0;
|
|
355
383
|
this.contentData = [];
|
|
356
384
|
this.contentIndexMap = /* @__PURE__ */ new Map();
|
|
357
385
|
this.stepKeyIds = /* @__PURE__ */ new Map();
|
|
@@ -491,6 +519,8 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
491
519
|
messages = [];
|
|
492
520
|
/** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
|
|
493
521
|
cachedRunMessages;
|
|
522
|
+
/** Ids of AI turns the agent node returned THIS run; see isRunProducedMessage. */
|
|
523
|
+
runProducedAiMessageIds = /* @__PURE__ */ new Set();
|
|
494
524
|
/** Checkpoint scope whose messages match index-keyed tool snapshots. */
|
|
495
525
|
originalToolContentCheckpointScope;
|
|
496
526
|
runId;
|
|
@@ -516,13 +546,61 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
516
546
|
subagentUsageSink;
|
|
517
547
|
/** See {@link t.StandardGraphInput.subagentScope}. */
|
|
518
548
|
subagentScope;
|
|
519
|
-
|
|
549
|
+
/** See {@link t.StandardGraphInput.preemption}. */
|
|
550
|
+
preemption;
|
|
551
|
+
/**
|
|
552
|
+
* Seals charged against `preemption.maxSeals`. Per-turn: cleared by both
|
|
553
|
+
* reset paths so a fresh turn gets a fresh budget, while a HITL resume —
|
|
554
|
+
* which skips `resetValues` — keeps what it had left.
|
|
555
|
+
*/
|
|
556
|
+
preemptSealBudgetUsed = 0;
|
|
557
|
+
/**
|
|
558
|
+
* Seals honored over the graph's lifetime. Reported by
|
|
559
|
+
* {@link getPreemptStats}, so it deliberately SURVIVES `clearHeavyState()`
|
|
560
|
+
* — a host reads it after `processStream` returns, which is strictly after
|
|
561
|
+
* cleanup runs.
|
|
562
|
+
*/
|
|
563
|
+
preemptSealCount = 0;
|
|
564
|
+
/** Boundaries that produced nothing to inject, so the turn stopped early. */
|
|
565
|
+
preemptEmptyBoundaries = 0;
|
|
566
|
+
/**
|
|
567
|
+
* Set between claiming a seal and resolving its boundary. `MultiAgentGraph`
|
|
568
|
+
* fans parallel agents through this one instance against a single host
|
|
569
|
+
* request, so without a one-at-a-time gate several streams would each seal
|
|
570
|
+
* for the same queued message and every loser would take the
|
|
571
|
+
* nothing-to-inject path and cut its answer short.
|
|
572
|
+
*/
|
|
573
|
+
preemptSealInFlight = false;
|
|
574
|
+
/**
|
|
575
|
+
* True when a seal ended the turn without a resume. The assistant turn is
|
|
576
|
+
* real and kept, but it is not the answer the model intended to finish —
|
|
577
|
+
* hosts persist it as unfinished rather than complete.
|
|
578
|
+
*/
|
|
579
|
+
preemptIncomplete = false;
|
|
580
|
+
/**
|
|
581
|
+
* `stopReason` from a `PreemptBoundary` hook that halted the turn.
|
|
582
|
+
*
|
|
583
|
+
* Clearing the registry halt is what keeps the sealed turn alive, but the
|
|
584
|
+
* registry held the only copy of the reason — so it is captured here first.
|
|
585
|
+
* Without it `getHaltReason()` returns undefined and a host records a
|
|
586
|
+
* hook-halted turn as an ordinary completion.
|
|
587
|
+
*/
|
|
588
|
+
preemptHaltReason;
|
|
589
|
+
/**
|
|
590
|
+
* Agent IDs whose next superstep must return to the agent node. Keyed by
|
|
591
|
+
* agent because `MultiAgentGraph` routes every parallel agent through this
|
|
592
|
+
* same instance, and a single field would let one agent's boundary resume
|
|
593
|
+
* another's turn.
|
|
594
|
+
*/
|
|
595
|
+
pendingPreemptReturn = /* @__PURE__ */ new Set();
|
|
596
|
+
constructor({ runId, signal, agents, langfuse, tokenCounter, indexTokenCountMap, calibrationRatio, subagentUsageSink, subagentScope, preemption }) {
|
|
520
597
|
super();
|
|
521
598
|
this.runId = runId;
|
|
522
599
|
this.signal = signal;
|
|
523
600
|
this.langfuse = langfuse;
|
|
524
601
|
this.subagentUsageSink = subagentUsageSink;
|
|
525
602
|
this.subagentScope = subagentScope === true;
|
|
603
|
+
this.preemption = preemption;
|
|
526
604
|
if (agents.length === 0) throw new Error("At least one agent configuration is required");
|
|
527
605
|
for (const agentConfig of agents) {
|
|
528
606
|
const agentContext = require_AgentContext.AgentContext.fromConfig(agentConfig, tokenCounter, indexTokenCountMap);
|
|
@@ -547,6 +625,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
547
625
|
* a stale reference on 2nd+ processStream calls.
|
|
548
626
|
*/
|
|
549
627
|
this.toolCallStepIds.clear();
|
|
628
|
+
this.runProducedAiMessageIds.clear();
|
|
550
629
|
this.eagerEventToolExecutions.clear();
|
|
551
630
|
this.clearEagerEventToolUsageCounts();
|
|
552
631
|
this.eagerEventToolCallChunks.clear();
|
|
@@ -558,6 +637,8 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
558
637
|
this.reasoningStepHasDeltas = require_graph.resetIfNotEmpty(this.reasoningStepHasDeltas, /* @__PURE__ */ new Set());
|
|
559
638
|
this.prelimMessageIdsByStepKey = require_graph.resetIfNotEmpty(this.prelimMessageIdsByStepKey, /* @__PURE__ */ new Map());
|
|
560
639
|
this.invokedToolIds = require_graph.resetIfNotEmpty(this.invokedToolIds, void 0);
|
|
640
|
+
this.resetPreemptTurnState();
|
|
641
|
+
this.resetPreemptTotals();
|
|
561
642
|
const hasScopedCheckpoint = this.hasCompiledCheckpointer && checkpointScope != null && checkpointScope !== "";
|
|
562
643
|
const preserveOriginalToolContent = hasScopedCheckpoint && this.originalToolContentCheckpointScope === checkpointScope;
|
|
563
644
|
for (const context of this.agentContexts.values()) context.reset({ preserveOriginalToolContent });
|
|
@@ -568,9 +649,98 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
568
649
|
super.clearHeavyState();
|
|
569
650
|
this.messages = [];
|
|
570
651
|
this.overrideModel = void 0;
|
|
652
|
+
/**
|
|
653
|
+
* Turn state only. The reported totals must outlive cleanup — this runs
|
|
654
|
+
* in `processStream`'s `finally`, and the host reads `getPreemptStats()`
|
|
655
|
+
* after that returns.
|
|
656
|
+
*/
|
|
657
|
+
this.resetPreemptTurnState();
|
|
571
658
|
const preserveOriginalToolContent = this.hasCompiledCheckpointer && this.originalToolContentCheckpointScope != null;
|
|
572
659
|
for (const context of this.agentContexts.values()) context.reset({ preserveOriginalToolContent });
|
|
573
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Per-turn seal budget and routing markers. Cleared by both reset paths so
|
|
663
|
+
* a new turn starts with a full budget and no stale resume marker.
|
|
664
|
+
*
|
|
665
|
+
* The REPORTED counters are deliberately not touched here — see
|
|
666
|
+
* {@link resetPreemptTotals}.
|
|
667
|
+
*/
|
|
668
|
+
resetPreemptTurnState() {
|
|
669
|
+
this.preemptSealBudgetUsed = 0;
|
|
670
|
+
this.preemptSealInFlight = false;
|
|
671
|
+
this.pendingPreemptReturn.clear();
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Lifetime seal totals, cleared only when a genuinely new run starts.
|
|
675
|
+
* `clearHeavyState()` must NOT call this: it runs in `processStream`'s
|
|
676
|
+
* `finally`, so zeroing here would make {@link getPreemptStats} and
|
|
677
|
+
* `preemptIncomplete` unreadable for every caller of the method that just
|
|
678
|
+
* produced them.
|
|
679
|
+
*/
|
|
680
|
+
resetPreemptTotals() {
|
|
681
|
+
this.preemptSealCount = 0;
|
|
682
|
+
this.preemptEmptyBoundaries = 0;
|
|
683
|
+
this.preemptIncomplete = false;
|
|
684
|
+
this.preemptHaltReason = void 0;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* True when the host has requested a cooperative seal AND this graph may
|
|
688
|
+
* honor it. Read once per streamed chunk, so it stays property reads plus
|
|
689
|
+
* one host callback — no I/O, no allocation.
|
|
690
|
+
*
|
|
691
|
+
* Non-mutating: a true result only means a seal is worth evaluating. The
|
|
692
|
+
* budget is taken by {@link claimPreemptSeal} once the accumulated chunk is
|
|
693
|
+
* known to be safe, so a chunk that cannot seal never spends budget.
|
|
694
|
+
*
|
|
695
|
+
* Subagent scopes never seal: a steer targets the top-level conversation,
|
|
696
|
+
* and a child run must finish so its parent sees a complete result.
|
|
697
|
+
*/
|
|
698
|
+
/** Internal seal preconditions only — no host callback, no side effects. */
|
|
699
|
+
canClaimPreemptSeal() {
|
|
700
|
+
/**
|
|
701
|
+
* Resolved and required here with the same rule `dispatchPreemptBoundary`
|
|
702
|
+
* uses. Without it a direct `StandardGraph` consumer that supplies no
|
|
703
|
+
* `runId` could claim a seal on the strength of a global matcher, then hit
|
|
704
|
+
* the boundary's own null-runId guard and get nothing back — truncating
|
|
705
|
+
* the answer for a drain that provably could not run.
|
|
706
|
+
*/
|
|
707
|
+
const runId = this.config?.configurable?.run_id ?? this.runId;
|
|
708
|
+
return !this.subagentScope && this.preemption != null && !this.preemptSealInFlight && this.preemptSealBudgetUsed < require_preempt.resolveMaxSeals(this.preemption.maxSeals) && runId != null && this.hookRegistry?.hasDispatchableHookFor("PreemptBoundary", runId) === true;
|
|
709
|
+
}
|
|
710
|
+
shouldPreemptStream() {
|
|
711
|
+
return this.canClaimPreemptSeal() && this.preemption?.shouldPreempt() === true;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Takes the seal slot, or returns false if another stream already holds it.
|
|
715
|
+
*
|
|
716
|
+
* Assumes the caller already polled `shouldPreemptStream()` for THIS chunk,
|
|
717
|
+
* and deliberately does not poll the host again — `StreamPreemption`
|
|
718
|
+
* documents `shouldPreempt` as once per chunk, and a host that consumes a
|
|
719
|
+
* pending flag on read would lose the request to a second call.
|
|
720
|
+
*
|
|
721
|
+
* The guard and both mutations remain one synchronous body, which is what
|
|
722
|
+
* makes this safe under a parallel `MultiAgentGraph`: several agents share
|
|
723
|
+
* one graph and can each see the poll as true, but no `await` can split the
|
|
724
|
+
* claim, so only one takes the slot. The loser keeps streaming normally
|
|
725
|
+
* rather than sealing for a message it would never receive.
|
|
726
|
+
*/
|
|
727
|
+
claimPreemptSeal() {
|
|
728
|
+
if (!this.canClaimPreemptSeal()) return false;
|
|
729
|
+
this.preemptSealInFlight = true;
|
|
730
|
+
this.preemptSealBudgetUsed += 1;
|
|
731
|
+
this.preemptSealCount += 1;
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
/** Releases the seal slot once its boundary has resolved, win or lose. */
|
|
735
|
+
releasePreemptSeal() {
|
|
736
|
+
this.preemptSealInFlight = false;
|
|
737
|
+
}
|
|
738
|
+
getPreemptStats() {
|
|
739
|
+
return {
|
|
740
|
+
seals: this.preemptSealCount,
|
|
741
|
+
emptyBoundaries: this.preemptEmptyBoundaries
|
|
742
|
+
};
|
|
743
|
+
}
|
|
574
744
|
getRunStep(stepId) {
|
|
575
745
|
const index = this.contentIndexMap.get(stepId);
|
|
576
746
|
if (index !== void 0) return this.contentData[index];
|
|
@@ -654,6 +824,20 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
654
824
|
if (this.messages.length === 0 && this.cachedRunMessages != null) return this.cachedRunMessages;
|
|
655
825
|
return this.messages.slice(this.startIndex);
|
|
656
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* True when THIS RUN produced `message` — the provenance the handoff cue
|
|
829
|
+
* gate needs. Tracked as an id set rather than inferred from `startIndex`
|
|
830
|
+
* arithmetic: summarization's remove-all compaction rewrites the live
|
|
831
|
+
* array and leaves `startIndex` stale, so index-based run/host
|
|
832
|
+
* discrimination silently breaks right after a mid-run summarize. Ids
|
|
833
|
+
* survive compaction (retained messages keep theirs), host-supplied
|
|
834
|
+
* prefill messages are never in the set, and membership is O(1) per
|
|
835
|
+
* model call.
|
|
836
|
+
*/
|
|
837
|
+
isRunProducedMessage(message) {
|
|
838
|
+
const id = message.id;
|
|
839
|
+
return typeof id === "string" && id !== "" && this.runProducedAiMessageIds.has(id);
|
|
840
|
+
}
|
|
657
841
|
getContentParts() {
|
|
658
842
|
if (this.messages == null) return;
|
|
659
843
|
return require_core.convertMessagesToContent(this.messages.slice(this.startIndex));
|
|
@@ -852,6 +1036,20 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
852
1036
|
const agentContext = this.agentContexts.get(agentId);
|
|
853
1037
|
if (!agentContext) throw new Error(`Agent context not found for agentId: ${agentId}`);
|
|
854
1038
|
if (!config) throw new Error("No config provided");
|
|
1039
|
+
/**
|
|
1040
|
+
* A `PreemptBoundary` hook halted this run and the sealed commit is
|
|
1041
|
+
* already in state. Enforced at every model node's ENTRY because that
|
|
1042
|
+
* is the only site that covers all of `MultiAgentGraph`'s onward
|
|
1043
|
+
* routing at once — static direct edges, Command fan-out, fan-in
|
|
1044
|
+
* wrappers, and parallel siblings' subsequent inner-loop turns — none
|
|
1045
|
+
* of which consult the halt (the registry signal was deliberately
|
|
1046
|
+
* cleared to keep the stream-cancel from destroying the sealed turn).
|
|
1047
|
+
* Declining the model call turns every routed-to successor into a
|
|
1048
|
+
* no-op, so the outer workflow drains to END without new turns or tool
|
|
1049
|
+
* side effects. Reset per turn in `resetPreemptTotals`, so the next
|
|
1050
|
+
* `processStream` call starts clean.
|
|
1051
|
+
*/
|
|
1052
|
+
if (this.preemptHaltReason != null) return { messages: [] };
|
|
855
1053
|
const { messages } = state;
|
|
856
1054
|
const discoveredNames = require_tools.extractToolDiscoveries(messages);
|
|
857
1055
|
if (discoveredNames.length > 0) agentContext.markToolsAsDiscovered(discoveredNames);
|
|
@@ -901,6 +1099,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
901
1099
|
provider: agentContext.provider,
|
|
902
1100
|
tokenCounter: agentContext.tokenCounter,
|
|
903
1101
|
maxTokens: agentContext.maxContextTokens,
|
|
1102
|
+
maxToolResultChars: agentContext.maxToolResultChars,
|
|
904
1103
|
thinkingEnabled: require_request.isThinkingEnabled(agentContext.provider, agentContext.clientOptions),
|
|
905
1104
|
indexTokenCountMap: agentContext.indexTokenCountMap,
|
|
906
1105
|
contextPruningConfig: agentContext.contextPruningConfig,
|
|
@@ -971,7 +1170,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
971
1170
|
require_budget.syncBudgetDerivedFields(contextUsage);
|
|
972
1171
|
if (agentContext.summarizationEnabled === true && Array.isArray(messagesToRefine) && messagesToRefine.length > 0) {
|
|
973
1172
|
const shouldSkip = agentContext.shouldSkipSummarization(messages.length);
|
|
974
|
-
if (!shouldSkip && require_index$
|
|
1173
|
+
if (!shouldSkip && require_index$7.shouldTriggerSummarization({
|
|
975
1174
|
trigger: agentContext.summarizationConfig?.trigger,
|
|
976
1175
|
maxContextTokens: agentContext.maxContextTokens,
|
|
977
1176
|
prePruneContextTokens: prePruneContextTokens != null ? prePruneContextTokens + agentContext.instructionTokens : void 0,
|
|
@@ -1010,28 +1209,73 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1010
1209
|
}
|
|
1011
1210
|
}
|
|
1012
1211
|
let finalMessages = messagesToUse;
|
|
1013
|
-
/**
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
|
|
1020
|
-
let
|
|
1021
|
-
|
|
1022
|
-
let preFormatLegacyTokens = 0;
|
|
1212
|
+
/**
|
|
1213
|
+
* Keep the pruner's provider-grounded aggregate as the authoritative
|
|
1214
|
+
* baseline, then attribute it across retained messages. Provider
|
|
1215
|
+
* transforms can shrink one message while expanding or adding another;
|
|
1216
|
+
* per-origin accounting prevents that unrelated shrink from canceling
|
|
1217
|
+
* the expansion. Raw counts are frozen before in-place formatters run.
|
|
1218
|
+
*/
|
|
1219
|
+
let providerMessageBaseline;
|
|
1220
|
+
const providerMessageOrigins = /* @__PURE__ */ new WeakMap();
|
|
1023
1221
|
if (contextUsage != null && agentContext.tokenCounter != null) {
|
|
1024
|
-
|
|
1025
|
-
for (
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1222
|
+
const sourceIndices = /* @__PURE__ */ new WeakMap();
|
|
1223
|
+
for (let i = 0; i < messages.length; i++) sourceIndices.set(messages[i], i);
|
|
1224
|
+
providerMessageBaseline = messagesToUse.map((message, index) => {
|
|
1225
|
+
const rawTokens = agentContext.tokenCounter(message);
|
|
1226
|
+
const sourceIndex = sourceIndices.get(message);
|
|
1227
|
+
const indexedTokens = sourceIndex != null ? agentContext.indexTokenCountMap[sourceIndex] : void 0;
|
|
1228
|
+
const accountingWeight = indexedTokens != null && Number.isFinite(indexedTokens) && indexedTokens >= 0 ? indexedTokens : rawTokens;
|
|
1229
|
+
if (!providerMessageOrigins.has(message)) providerMessageOrigins.set(message, index);
|
|
1230
|
+
return {
|
|
1231
|
+
rawTokens,
|
|
1232
|
+
accountingWeight
|
|
1233
|
+
};
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
const getProviderMessageOriginKey = (message) => {
|
|
1237
|
+
const type = message.getType();
|
|
1238
|
+
if (message instanceof _langchain_core_messages.ToolMessage && typeof message.tool_call_id === "string" && message.tool_call_id.length > 0) return `tool:call:${message.tool_call_id}`;
|
|
1239
|
+
if (typeof message.id === "string" && message.id.length > 0) return `${type}:id:${message.id}`;
|
|
1240
|
+
};
|
|
1241
|
+
/**
|
|
1242
|
+
* Provider projections clone messages. Preserve their baseline origin
|
|
1243
|
+
* without writing tracking metadata onto the wire. Synthetic fold
|
|
1244
|
+
* messages intentionally remain unattributed and are charged in full.
|
|
1245
|
+
*/
|
|
1246
|
+
const trackProviderMessageOrigins = (before, after) => {
|
|
1247
|
+
if (providerMessageBaseline == null || before === after) return after;
|
|
1248
|
+
if (before.length === after.length) {
|
|
1249
|
+
for (let i = 0; i < after.length; i++) {
|
|
1250
|
+
const origin = providerMessageOrigins.get(before[i]);
|
|
1251
|
+
if (origin != null && !providerMessageOrigins.has(after[i]) && before[i].getType() === after[i].getType() && !require_format.isSyntheticProviderContextMessage(after[i])) providerMessageOrigins.set(after[i], origin);
|
|
1031
1252
|
}
|
|
1253
|
+
return after;
|
|
1254
|
+
}
|
|
1255
|
+
const keyedOrigins = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const message of before) {
|
|
1257
|
+
const origin = providerMessageOrigins.get(message);
|
|
1258
|
+
const key = getProviderMessageOriginKey(message);
|
|
1259
|
+
if (origin == null || key == null) continue;
|
|
1260
|
+
keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
|
|
1032
1261
|
}
|
|
1262
|
+
for (const message of after) {
|
|
1263
|
+
if (providerMessageOrigins.has(message) || require_format.isSyntheticProviderContextMessage(message)) continue;
|
|
1264
|
+
const key = getProviderMessageOriginKey(message);
|
|
1265
|
+
const origin = key != null ? keyedOrigins.get(key) : void 0;
|
|
1266
|
+
if (origin != null) providerMessageOrigins.set(message, origin);
|
|
1267
|
+
}
|
|
1268
|
+
return after;
|
|
1269
|
+
};
|
|
1270
|
+
if (agentContext.useLegacyContent) {
|
|
1271
|
+
const before = finalMessages;
|
|
1272
|
+
finalMessages = trackProviderMessageOrigins(before, require_content.formatContentStrings(before));
|
|
1033
1273
|
}
|
|
1034
|
-
|
|
1274
|
+
const maxProviderToolResultChars = agentContext.maxToolResultChars ?? require_truncation.calculateMaxToolResultChars(agentContext.maxContextTokens);
|
|
1275
|
+
const beforeToolStreamProjection = finalMessages;
|
|
1276
|
+
finalMessages = trackProviderMessageOrigins(beforeToolStreamProjection, require_core.projectToolStreamContentForProvider(beforeToolStreamProjection));
|
|
1277
|
+
const beforeToolInputProjection = finalMessages;
|
|
1278
|
+
finalMessages = trackProviderMessageOrigins(beforeToolInputProjection, require_prune.projectToolCallInputs(beforeToolInputProjection, require_prune.calculateMaxToolCallInputChars(agentContext.maxContextTokens)));
|
|
1035
1279
|
const lastMessageX = finalMessages.length >= 2 ? finalMessages[finalMessages.length - 2] : null;
|
|
1036
1280
|
const lastMessageY = finalMessages.length >= 1 ? finalMessages[finalMessages.length - 1] : null;
|
|
1037
1281
|
const anthropicLike = require_llm.isAnthropicLike(agentContext.provider, agentContext.clientOptions);
|
|
@@ -1042,34 +1286,238 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1042
1286
|
text: trimmed
|
|
1043
1287
|
}] : "";
|
|
1044
1288
|
}
|
|
1289
|
+
const localProviderOverflowMeasurements = /* @__PURE__ */ new WeakMap();
|
|
1290
|
+
const measureProviderPayload = (candidate, contextBudgetOverride, forceRawRecount = false) => {
|
|
1291
|
+
const contextBudget = contextBudgetOverride ?? contextUsage?.contextBudget;
|
|
1292
|
+
const effectiveInstructionTokens = contextUsage?.effectiveInstructionTokens ?? (forceRawRecount ? agentContext.instructionTokens : void 0);
|
|
1293
|
+
if (agentContext.tokenCounter == null || contextBudget == null || effectiveInstructionTokens == null) return { fits: true };
|
|
1294
|
+
const availableMessageTokens = Math.max(0, contextBudget - effectiveInstructionTokens);
|
|
1295
|
+
let usageRatio = agentContext.calibrationRatio > 0 ? agentContext.calibrationRatio : 1;
|
|
1296
|
+
if (contextUsage?.calibrationRatio != null && contextUsage.calibrationRatio > 0) usageRatio = contextUsage.calibrationRatio;
|
|
1297
|
+
if (forceRawRecount) usageRatio = Math.max(1, usageRatio);
|
|
1298
|
+
const baselineRemaining = contextUsage?.remainingContextTokens;
|
|
1299
|
+
const accountedMessageTokens = !forceRawRecount && providerMessageBaseline != null && baselineRemaining != null && Number.isFinite(baselineRemaining) ? availableMessageTokens - Math.min(availableMessageTokens, Math.max(0, baselineRemaining)) : void 0;
|
|
1300
|
+
let projectedMessageTokens;
|
|
1301
|
+
if (accountedMessageTokens != null && providerMessageBaseline != null) {
|
|
1302
|
+
const replyPrimerTokens = Math.round(3 * usageRatio);
|
|
1303
|
+
const rawWeights = {};
|
|
1304
|
+
let totalWeight = 0;
|
|
1305
|
+
for (let i = 0; i < providerMessageBaseline.length; i++) {
|
|
1306
|
+
const weight = providerMessageBaseline[i].accountingWeight;
|
|
1307
|
+
rawWeights[i] = weight;
|
|
1308
|
+
totalWeight += weight;
|
|
1309
|
+
}
|
|
1310
|
+
const attributableTokens = totalWeight > 0 ? Math.min(Math.max(0, accountedMessageTokens - replyPrimerTokens), Math.round(totalWeight * usageRatio)) : 0;
|
|
1311
|
+
const apportionedTokens = totalWeight > 0 ? require_tokens.apportionTokenCounts(rawWeights, attributableTokens / totalWeight, attributableTokens) : {};
|
|
1312
|
+
const attributedByOrigin = providerMessageBaseline.map((_, origin) => apportionedTokens[origin] || 0);
|
|
1313
|
+
projectedMessageTokens = Math.max(replyPrimerTokens, accountedMessageTokens - attributableTokens);
|
|
1314
|
+
let newRawTokens = 0;
|
|
1315
|
+
const usedOrigins = /* @__PURE__ */ new Set();
|
|
1316
|
+
for (const message of candidate) {
|
|
1317
|
+
const rawTokens = agentContext.tokenCounter(message);
|
|
1318
|
+
const origin = providerMessageOrigins.get(message);
|
|
1319
|
+
if (origin == null || usedOrigins.has(origin)) {
|
|
1320
|
+
newRawTokens += rawTokens;
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
usedOrigins.add(origin);
|
|
1324
|
+
projectedMessageTokens += Math.max(0, attributedByOrigin[origin] + Math.round((rawTokens - providerMessageBaseline[origin].rawTokens) * usageRatio));
|
|
1325
|
+
}
|
|
1326
|
+
projectedMessageTokens += Math.round(newRawTokens * usageRatio);
|
|
1327
|
+
} else {
|
|
1328
|
+
let rawTokens = 3;
|
|
1329
|
+
for (const message of candidate) rawTokens += agentContext.tokenCounter(message);
|
|
1330
|
+
projectedMessageTokens = Math.round(rawTokens * usageRatio);
|
|
1331
|
+
}
|
|
1332
|
+
return {
|
|
1333
|
+
fits: projectedMessageTokens <= availableMessageTokens,
|
|
1334
|
+
projectedMessageTokens,
|
|
1335
|
+
availableMessageTokens,
|
|
1336
|
+
contextBudget,
|
|
1337
|
+
effectiveInstructionTokens
|
|
1338
|
+
};
|
|
1339
|
+
};
|
|
1340
|
+
const createProviderPayloadOverflowError = ({ projection, provider, info }) => {
|
|
1341
|
+
const error = new _langchain_core_errors.ContextOverflowError(JSON.stringify({
|
|
1342
|
+
type: "final_context_overflow",
|
|
1343
|
+
info,
|
|
1344
|
+
provider,
|
|
1345
|
+
projectedMessageTokens: projection.projectedMessageTokens,
|
|
1346
|
+
availableMessageTokens: projection.availableMessageTokens
|
|
1347
|
+
}));
|
|
1348
|
+
if (projection.projectedMessageTokens != null && projection.contextBudget != null && projection.effectiveInstructionTokens != null) localProviderOverflowMeasurements.set(error, {
|
|
1349
|
+
contextBudget: projection.contextBudget,
|
|
1350
|
+
estimatedPromptTokens: projection.projectedMessageTokens + projection.effectiveInstructionTokens
|
|
1351
|
+
});
|
|
1352
|
+
return error;
|
|
1353
|
+
};
|
|
1354
|
+
const applyProviderMessageTransforms = (candidate) => {
|
|
1355
|
+
let transformed = candidate;
|
|
1356
|
+
if (require_request.isThinkingEnabled(agentContext.provider, agentContext.clientOptions)) {
|
|
1357
|
+
/**
|
|
1358
|
+
* Current-run AI messages may validly omit a thinking block. The
|
|
1359
|
+
* boundary prevents them from being mistaken for foreign history.
|
|
1360
|
+
*/
|
|
1361
|
+
const before = transformed;
|
|
1362
|
+
transformed = trackProviderMessageOrigins(before, require_format.ensureThinkingBlockInMessages(before, agentContext.provider, config, this.startIndex));
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Tool-less destinations cannot send inherited tool blocks without a
|
|
1366
|
+
* tool schema, so fold those interactions into provider-valid content.
|
|
1367
|
+
*/
|
|
1368
|
+
if (toolsForBinding == null || toolsForBinding.length === 0) {
|
|
1369
|
+
const before = transformed;
|
|
1370
|
+
transformed = trackProviderMessageOrigins(before, require_format.foldToolBlocksForToollessAgent(before, config));
|
|
1371
|
+
if (agentContext.useLegacyContent) {
|
|
1372
|
+
const beforeLegacyFormat = transformed;
|
|
1373
|
+
transformed = trackProviderMessageOrigins(beforeLegacyFormat, require_content.formatContentStrings(beforeLegacyFormat));
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Applied HERE for the primary so the cue is part of the MEASURED
|
|
1378
|
+
* payload — the pre-invoke projection and overflow guard run on this
|
|
1379
|
+
* stage's output, and a post-measure append could push a just-fits
|
|
1380
|
+
* prompt over budget unreported (#346 round 2). The attemptInvoke
|
|
1381
|
+
* funnel re-keys per SERVING provider: it strips this cue for a
|
|
1382
|
+
* tolerant fallback and adds it for a Claude fallback behind a
|
|
1383
|
+
* tolerant primary.
|
|
1384
|
+
*/
|
|
1385
|
+
if (require_llm.isAnthropicLike(agentContext.provider, agentContext.clientOptions)) {
|
|
1386
|
+
const before = transformed;
|
|
1387
|
+
transformed = trackProviderMessageOrigins(before, require_handoffCue.appendPredecessorHandoffCue(before, (message) => this.isRunProducedMessage(message)));
|
|
1388
|
+
}
|
|
1389
|
+
return transformed;
|
|
1390
|
+
};
|
|
1391
|
+
const toolOutputRegistry = this.getOrCreateToolOutputRegistry();
|
|
1392
|
+
const providerRunId = config.configurable?.run_id;
|
|
1393
|
+
const projectProviderReferences = (candidate) => trackProviderMessageOrigins(candidate, require_toolOutputReferences.annotateMessagesForLLM(candidate, toolOutputRegistry, providerRunId));
|
|
1394
|
+
const compactSyntheticProviderContext = (candidate) => {
|
|
1395
|
+
const synthetic = [];
|
|
1396
|
+
for (let i = 0; i < candidate.length; i++) {
|
|
1397
|
+
const message = candidate[i];
|
|
1398
|
+
if (!(message instanceof _langchain_core_messages.HumanMessage) || !require_format.isSyntheticProviderContextMessage(message)) continue;
|
|
1399
|
+
const content = message.content;
|
|
1400
|
+
synthetic.push({
|
|
1401
|
+
index: i,
|
|
1402
|
+
message,
|
|
1403
|
+
chars: require_toolContent.getToolContentCharLength(content)
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
if (synthetic.length === 0) return candidate;
|
|
1407
|
+
const buildCandidate = (scale) => {
|
|
1408
|
+
const compacted = [...candidate];
|
|
1409
|
+
for (const { index, message, chars } of synthetic) {
|
|
1410
|
+
const content = require_toolContent.compactToolContent(message.content, Math.floor(chars * scale)).content;
|
|
1411
|
+
compacted[index] = new _langchain_core_messages.HumanMessage({
|
|
1412
|
+
content,
|
|
1413
|
+
id: message.id,
|
|
1414
|
+
name: message.name,
|
|
1415
|
+
additional_kwargs: message.additional_kwargs,
|
|
1416
|
+
response_metadata: message.response_metadata
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
return compacted;
|
|
1420
|
+
};
|
|
1421
|
+
let best = buildCandidate(0);
|
|
1422
|
+
if (!measureProviderPayload(best).fits) return candidate;
|
|
1423
|
+
let low = 0;
|
|
1424
|
+
let high = 1;
|
|
1425
|
+
for (let i = 0; i < 12; i++) {
|
|
1426
|
+
const scale = (low + high) / 2;
|
|
1427
|
+
const attempt = buildCandidate(scale);
|
|
1428
|
+
if (measureProviderPayload(attempt).fits) {
|
|
1429
|
+
best = attempt;
|
|
1430
|
+
low = scale;
|
|
1431
|
+
} else high = scale;
|
|
1432
|
+
}
|
|
1433
|
+
return best;
|
|
1434
|
+
};
|
|
1435
|
+
let artifactBaseMessages;
|
|
1045
1436
|
if (lastMessageY instanceof _langchain_core_messages.ToolMessage) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1437
|
+
let artifactCandidate = finalMessages;
|
|
1438
|
+
if (anthropicLike) artifactCandidate = trackProviderMessageOrigins(finalMessages, require_core.projectAnthropicArtifactContent(finalMessages, maxProviderToolResultChars));
|
|
1439
|
+
else if (require_llm.isOpenAILike(agentContext.provider) && agentContext.provider !== "deepseek" || require_llm.isGoogleLike(agentContext.provider)) artifactCandidate = trackProviderMessageOrigins(finalMessages, require_core.projectArtifactPayload(finalMessages, maxProviderToolResultChars));
|
|
1440
|
+
if (artifactCandidate !== finalMessages) {
|
|
1441
|
+
const projection = measureProviderPayload(artifactCandidate);
|
|
1442
|
+
if (projection.fits) {
|
|
1443
|
+
artifactBaseMessages = finalMessages;
|
|
1444
|
+
finalMessages = artifactCandidate;
|
|
1445
|
+
} else require_events.emitAgentLog(config, "warn", "graph", "Artifact payload omitted because it exceeds the remaining context budget", {
|
|
1446
|
+
projectedMessageTokens: projection.projectedMessageTokens,
|
|
1447
|
+
availableMessageTokens: projection.availableMessageTokens
|
|
1448
|
+
}, {
|
|
1449
|
+
runId: this.runId,
|
|
1450
|
+
agentId
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
finalMessages = projectProviderReferences(applyProviderMessageTransforms(finalMessages));
|
|
1455
|
+
let finalProjection = measureProviderPayload(finalMessages);
|
|
1456
|
+
if (artifactBaseMessages != null) {
|
|
1457
|
+
if (!finalProjection.fits) {
|
|
1458
|
+
finalMessages = projectProviderReferences(applyProviderMessageTransforms(artifactBaseMessages));
|
|
1459
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
1460
|
+
require_events.emitAgentLog(config, "warn", "graph", "Artifact payload omitted after final provider formatting exceeded the remaining context budget", {
|
|
1461
|
+
projectedMessageTokens: finalProjection.projectedMessageTokens,
|
|
1462
|
+
availableMessageTokens: finalProjection.availableMessageTokens
|
|
1463
|
+
}, {
|
|
1464
|
+
runId: this.runId,
|
|
1465
|
+
agentId
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
if (!finalProjection.fits) {
|
|
1470
|
+
const compacted = compactSyntheticProviderContext(finalMessages);
|
|
1471
|
+
if (compacted !== finalMessages) {
|
|
1472
|
+
finalMessages = compacted;
|
|
1473
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
1474
|
+
require_events.emitAgentLog(config, finalProjection.fits ? "warn" : "error", "graph", finalProjection.fits ? "Synthetic provider context compacted to fit the final payload budget" : "Final provider payload still exceeds budget after synthetic context compaction", {
|
|
1475
|
+
projectedMessageTokens: finalProjection.projectedMessageTokens,
|
|
1476
|
+
availableMessageTokens: finalProjection.availableMessageTokens
|
|
1477
|
+
}, {
|
|
1478
|
+
runId: this.runId,
|
|
1479
|
+
agentId
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1048
1482
|
}
|
|
1049
|
-
if (require_request.isThinkingEnabled(agentContext.provider, agentContext.clientOptions))
|
|
1050
|
-
/**
|
|
1051
|
-
* Pass `this.startIndex` so the function can distinguish CURRENT-run
|
|
1052
|
-
* AI messages (the agent's own iterations — possibly without a
|
|
1053
|
-
* leading thinking block, which Claude is allowed to skip) from
|
|
1054
|
-
* historical context that genuinely needs the
|
|
1055
|
-
* `[Previous agent context]` placeholder. Without this signal the
|
|
1056
|
-
* function would convert the agent's own in-run tool_use messages,
|
|
1057
|
-
* polluting the next iteration's prompt with a placeholder the
|
|
1058
|
-
* model treats as suspicious injected content.
|
|
1059
|
-
*/
|
|
1060
|
-
finalMessages = require_format.ensureThinkingBlockInMessages(finalMessages, agentContext.provider, config, this.startIndex);
|
|
1061
1483
|
/**
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1484
|
+
* Mistral rejects consecutive user turns outright; Bedrock's Converse
|
|
1485
|
+
* API documents strict user/assistant alternation across its model
|
|
1486
|
+
* families, with enforcement varying by family (Claude on Converse
|
|
1487
|
+
* currently tolerates the shape — verified live — but the payload is
|
|
1488
|
+
* normalized for all of them rather than betting on leniency). Four
|
|
1489
|
+
* sites can emit them — the `PostToolBatch` and `PreemptBoundary` hook
|
|
1490
|
+
* boundaries (a consolidated context message followed by one
|
|
1491
|
+
* `HumanMessage` per injected entry), a queue drain carrying more than
|
|
1492
|
+
* one steer, and `run.ts`'s pre-stream context push onto a payload that
|
|
1493
|
+
* already ends on a user turn.
|
|
1494
|
+
*
|
|
1495
|
+
* Normalized here, at the last provider-facing hop, rather than at any
|
|
1496
|
+
* one boundary: the boundaries must keep per-message identity, because
|
|
1497
|
+
* `additional_kwargs.source`/`skillName` drive steer rendering and the
|
|
1498
|
+
* trailing-steer anchor downstream. Graph state and the host's
|
|
1499
|
+
* persisted messages are untouched — this shapes only what goes on the
|
|
1500
|
+
* wire, for the providers that actually care.
|
|
1501
|
+
*
|
|
1502
|
+
* Runs AFTER synthetic-context compaction: that pass can rewrite or
|
|
1503
|
+
* drop messages, so coalescing has to see its output, and it is the
|
|
1504
|
+
* last shaping step before the cache breakpoint is chosen.
|
|
1069
1505
|
*/
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1072
|
-
|
|
1506
|
+
if (require_alternation.strictAlternationProviders.has(agentContext.provider)) {
|
|
1507
|
+
/**
|
|
1508
|
+
* Wrapped like every other provider transform: the merged message is
|
|
1509
|
+
* a NEW object, and without re-attachment the final pre-invoke
|
|
1510
|
+
* measurement would drop both source turns' calibrated shares and
|
|
1511
|
+
* recharge the merge at full raw estimate — enough to flip a
|
|
1512
|
+
* just-fits payload (the synthetic-context compaction above binary
|
|
1513
|
+
* searches to exactly that) into a spurious pre-invoke overflow. The
|
|
1514
|
+
* merge keeps the first source's id, so the keyed branch re-attaches
|
|
1515
|
+
* that origin; the absorbed turn's tokens are charged as new raw
|
|
1516
|
+
* growth, which only ever under-estimates by less than the old
|
|
1517
|
+
* behavior over-estimated.
|
|
1518
|
+
*/
|
|
1519
|
+
const beforeCoalesce = finalMessages;
|
|
1520
|
+
finalMessages = trackProviderMessageOrigins(beforeCoalesce, require_alternation.coalesceAdjacentUserTurns(beforeCoalesce));
|
|
1073
1521
|
}
|
|
1074
1522
|
const anthropicPromptCacheEnabled = agentContext.provider === "anthropic" && agentContext.clientOptions?.promptCache === true;
|
|
1075
1523
|
const openRouterPromptCacheEnabled = agentContext.provider === "openrouter" && agentContext.clientOptions?.promptCache === true;
|
|
@@ -1077,7 +1525,11 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1077
1525
|
const providerPromptCacheEnabled = anthropicPromptCacheEnabled || openRouterPromptCacheEnabled || bedrockPromptCacheEnabled;
|
|
1078
1526
|
if (anthropicLike && (!agentContext.pruneMessages || finalMessages !== messagesToUse || providerPromptCacheEnabled)) {
|
|
1079
1527
|
const beforeSanitize = finalMessages.length;
|
|
1080
|
-
|
|
1528
|
+
const beforeSanitizeMessages = finalMessages;
|
|
1529
|
+
finalMessages = trackProviderMessageOrigins(beforeSanitizeMessages, require_prune.sanitizeOrphanToolBlocks(beforeSanitizeMessages, (source, clone) => {
|
|
1530
|
+
const origin = providerMessageOrigins.get(source);
|
|
1531
|
+
if (origin != null) providerMessageOrigins.set(clone, origin);
|
|
1532
|
+
}));
|
|
1081
1533
|
if (finalMessages.length !== beforeSanitize) require_events.emitAgentLog(config, "warn", "sanitize", "Orphan tool blocks removed", {
|
|
1082
1534
|
before: beforeSanitize,
|
|
1083
1535
|
after: finalMessages.length,
|
|
@@ -1087,11 +1539,34 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1087
1539
|
agentId
|
|
1088
1540
|
});
|
|
1089
1541
|
}
|
|
1090
|
-
if ((anthropicPromptCacheEnabled || openRouterPromptCacheEnabled) && !agentContext.systemRunnable)
|
|
1091
|
-
|
|
1542
|
+
if ((anthropicPromptCacheEnabled || openRouterPromptCacheEnabled) && !agentContext.systemRunnable) {
|
|
1543
|
+
const beforeCacheControl = finalMessages;
|
|
1544
|
+
finalMessages = trackProviderMessageOrigins(beforeCacheControl, require_cache.addTailCacheControl(beforeCacheControl, require_cache.resolvePromptCacheTtl(anthropicPromptCacheEnabled ? agentContext.clientOptions?.promptCacheTtl : agentContext.clientOptions?.promptCacheTtl)));
|
|
1545
|
+
} else if (bedrockPromptCacheEnabled) {
|
|
1092
1546
|
const bedrockOptions = agentContext.clientOptions;
|
|
1093
|
-
|
|
1547
|
+
const beforeCacheControl = finalMessages;
|
|
1548
|
+
finalMessages = trackProviderMessageOrigins(beforeCacheControl, require_cache.addBedrockTailCacheControl(beforeCacheControl, require_cache.resolveBedrockPromptCacheTtl(bedrockOptions?.promptCacheTtl, bedrockOptions?.model)));
|
|
1094
1549
|
}
|
|
1550
|
+
const fallbackBaseMessages = finalMessages;
|
|
1551
|
+
const beforeFinalProviderProjection = fallbackBaseMessages;
|
|
1552
|
+
finalMessages = trackProviderMessageOrigins(beforeFinalProviderProjection, require_invoke.projectMessagesForProvider({
|
|
1553
|
+
model: this.overrideModel ?? model,
|
|
1554
|
+
messages: beforeFinalProviderProjection,
|
|
1555
|
+
provider: agentContext.provider,
|
|
1556
|
+
maxToolResultChars: maxProviderToolResultChars,
|
|
1557
|
+
callOptions: config
|
|
1558
|
+
}));
|
|
1559
|
+
/**
|
|
1560
|
+
* Prompt-cache placement and orphan sanitization are provider-wire
|
|
1561
|
+
* transforms too. Re-measure after both so no content added after the
|
|
1562
|
+
* earlier artifact/synthetic compaction decision can bypass the guard.
|
|
1563
|
+
*/
|
|
1564
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
1565
|
+
const preInvokeContextOverflowError = !finalProjection.fits ? createProviderPayloadOverflowError({
|
|
1566
|
+
projection: finalProjection,
|
|
1567
|
+
provider: agentContext.provider,
|
|
1568
|
+
info: "Provider message formatting exceeded the context budget and no safe synthetic-context compaction could make it fit."
|
|
1569
|
+
}) : void 0;
|
|
1095
1570
|
if (agentContext.lastStreamCall != null && agentContext.streamBuffer != null) {
|
|
1096
1571
|
const timeSinceLastCall = Date.now() - agentContext.lastStreamCall;
|
|
1097
1572
|
if (timeSinceLastCall < agentContext.streamBuffer) await require_run.sleep(Math.ceil((agentContext.streamBuffer - timeSinceLastCall) / 1e3) * 1e3);
|
|
@@ -1125,28 +1600,9 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1125
1600
|
}
|
|
1126
1601
|
/** Past the empty-prompt guard — a model call is now guaranteed */
|
|
1127
1602
|
if (contextUsage != null) {
|
|
1128
|
-
|
|
1129
|
-
if (agentContext.tokenCounter != null && finalMessages.length !== messagesToUse.length) {
|
|
1130
|
-
/** Post-prune formatting restructured the payload (e.g. thinking
|
|
1131
|
-
* placeholder collapse, orphan drops) — recount so the gauge
|
|
1132
|
-
* reflects what is actually sent */
|
|
1133
|
-
let rawTokens = 0;
|
|
1134
|
-
for (const message of finalMessages) rawTokens += agentContext.tokenCounter(message);
|
|
1603
|
+
if (finalProjection.projectedMessageTokens != null && finalProjection.availableMessageTokens != null) {
|
|
1135
1604
|
contextUsage.breakdown.messageCount = finalMessages.length;
|
|
1136
|
-
|
|
1137
|
-
} else if (preFormatTailTokens != null && agentContext.tokenCounter != null && contextUsage.remainingContextTokens != null) {
|
|
1138
|
-
/** Same-length formatting can still mutate in place — the trailing
|
|
1139
|
-
* tool batch (artifacts, Bedrock rewrites) and any legacy-converted
|
|
1140
|
-
* messages before it — adjust remaining by the calibrated delta */
|
|
1141
|
-
let postFormatTailTokens = 0;
|
|
1142
|
-
for (const message of finalMessages.slice(tailStart)) postFormatTailTokens += agentContext.tokenCounter(message);
|
|
1143
|
-
let formatDelta = postFormatTailTokens - preFormatTailTokens;
|
|
1144
|
-
if (legacyIndices != null && legacyIndices.length > 0) {
|
|
1145
|
-
let postFormatLegacyTokens = 0;
|
|
1146
|
-
for (const index of legacyIndices) postFormatLegacyTokens += agentContext.tokenCounter(finalMessages[index]);
|
|
1147
|
-
formatDelta += postFormatLegacyTokens - preFormatLegacyTokens;
|
|
1148
|
-
}
|
|
1149
|
-
if (formatDelta !== 0) contextUsage.remainingContextTokens = Math.max(0, Math.min(contextUsage.contextBudget ?? Number.MAX_SAFE_INTEGER, contextUsage.remainingContextTokens - Math.round(formatDelta * usageRatio)));
|
|
1605
|
+
contextUsage.remainingContextTokens = Math.max(0, finalProjection.availableMessageTokens - finalProjection.projectedMessageTokens);
|
|
1150
1606
|
}
|
|
1151
1607
|
require_budget.syncBudgetDerivedFields(contextUsage);
|
|
1152
1608
|
/** Awaited so async host handlers receive the pre-invoke snapshot
|
|
@@ -1194,6 +1650,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1194
1650
|
}
|
|
1195
1651
|
const metadata = config.metadata;
|
|
1196
1652
|
try {
|
|
1653
|
+
if (preInvokeContextOverflowError != null) throw preInvokeContextOverflowError;
|
|
1197
1654
|
result = await require_langfuseRuntimeScope.withLangfuseRuntimeScope(require_langfuseRuntimeScope.resolveLangfuseRuntimeScope({
|
|
1198
1655
|
runLangfuse: this.langfuse,
|
|
1199
1656
|
langfuseOverlay: agentContext.langfuse
|
|
@@ -1223,31 +1680,37 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1223
1680
|
* keeps the original error and one round trip instead of three.
|
|
1224
1681
|
*/
|
|
1225
1682
|
const estimatedPromptTokens = getEstimatedPromptTokens(contextUsage);
|
|
1226
|
-
/**
|
|
1227
|
-
* A previous correction that left the prompt no smaller proves this
|
|
1228
|
-
* state has nothing left to compact — an emptied message list whose
|
|
1229
|
-
* content rides along in an injected summary, for instance. Measuring
|
|
1230
|
-
* that beats trying to predict every such configuration.
|
|
1231
|
-
*/
|
|
1232
|
-
const recoveryStalled = agentContext.overflowRecoveryStalled(estimatedPromptTokens);
|
|
1233
1683
|
const canSummarizeOverflow = agentContext.summarizationEnabled === true && require_recency.splitAtRecencyBoundary(messages, {
|
|
1234
1684
|
turns: agentContext.summarizationConfig?.retainRecent?.turns ?? 2,
|
|
1235
1685
|
tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
|
|
1236
1686
|
tokenCounter: agentContext.tokenCounter
|
|
1237
1687
|
}).head.length > 0;
|
|
1688
|
+
const getLocalProviderOverflowMeasurement = (error) => typeof error === "object" && error !== null ? localProviderOverflowMeasurements.get(error) : void 0;
|
|
1689
|
+
const getRecoveryPromptEstimate = (error, fallbackContext) => {
|
|
1690
|
+
const resolvedFallbackContext = fallbackContext ?? require_invoke.getFallbackErrorContext(error);
|
|
1691
|
+
return getLocalProviderOverflowMeasurement(error)?.estimatedPromptTokens ?? (resolvedFallbackContext == null ? estimatedPromptTokens : void 0);
|
|
1692
|
+
};
|
|
1238
1693
|
const planRecovery = (error, attributedFallbackContext) => {
|
|
1239
|
-
if (recoveryStalled) return null;
|
|
1240
1694
|
/**
|
|
1241
1695
|
* When the rejection came from a fallback, plan against *that*
|
|
1242
1696
|
* client: its window and output allowance are why it was configured
|
|
1243
1697
|
* as an alternative in the first place.
|
|
1244
1698
|
*/
|
|
1245
1699
|
const fallbackContext = attributedFallbackContext ?? require_invoke.getFallbackErrorContext(error);
|
|
1700
|
+
const localMeasurement = getLocalProviderOverflowMeasurement(error);
|
|
1701
|
+
const recoveryPromptEstimate = getRecoveryPromptEstimate(error, fallbackContext);
|
|
1702
|
+
/**
|
|
1703
|
+
* A previous correction that left the rejected prompt no smaller
|
|
1704
|
+
* proves this state has nothing left to compact. Use the fallback
|
|
1705
|
+
* projection when one exists so unlike provider formats are never
|
|
1706
|
+
* compared through the primary's cheaper pre-projection estimate.
|
|
1707
|
+
*/
|
|
1708
|
+
if (agentContext.overflowRecoveryStalled(recoveryPromptEstimate)) return null;
|
|
1246
1709
|
const recovery = require_contextOverflowRecovery.planContextOverflowRecovery({
|
|
1247
1710
|
error,
|
|
1248
1711
|
provider: fallbackContext?.provider ?? agentContext.provider,
|
|
1249
|
-
maxContextTokens: fallbackContext?.maxContextTokens ?? agentContext.maxContextTokens,
|
|
1250
|
-
estimatedPromptTokens,
|
|
1712
|
+
maxContextTokens: localMeasurement?.contextBudget ?? fallbackContext?.maxContextTokens ?? agentContext.maxContextTokens,
|
|
1713
|
+
estimatedPromptTokens: recoveryPromptEstimate,
|
|
1251
1714
|
calibrationRatio: agentContext.calibrationRatio,
|
|
1252
1715
|
instructionTokens: agentContext.instructionTokens,
|
|
1253
1716
|
canSummarize: agentContext.summarizationEnabled === true,
|
|
@@ -1257,20 +1720,23 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1257
1720
|
if (recovery == null) return null;
|
|
1258
1721
|
const translatedRecovery = fallbackContext != null ? {
|
|
1259
1722
|
...recovery,
|
|
1260
|
-
budgetTokens: minDefined(require_contextOverflowRecovery.getBlindRecoveryBudget(agentContext.maxContextTokens), require_contextOverflowRecovery.translateRecoveryBudget(recovery.budgetTokens, recovery.observedCalibrationRatio ?? 5, agentContext.calibrationRatio)),
|
|
1723
|
+
budgetTokens: minDefined(require_contextOverflowRecovery.getBlindRecoveryBudget(agentContext.maxContextTokens), localMeasurement != null ? recovery.budgetTokens : require_contextOverflowRecovery.translateRecoveryBudget(recovery.budgetTokens, recovery.observedCalibrationRatio ?? 5, agentContext.calibrationRatio)),
|
|
1261
1724
|
observedCalibrationRatio: void 0
|
|
1262
1725
|
} : recovery;
|
|
1263
1726
|
return canSummarizeOverflow || agentContext.tokenCounter != null && translatedRecovery.budgetTokens != null ? translatedRecovery : null;
|
|
1264
1727
|
};
|
|
1265
1728
|
const recovery = planRecovery(primaryError);
|
|
1266
|
-
if (recovery != null)
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1729
|
+
if (recovery != null) {
|
|
1730
|
+
const recoveryPromptEstimate = getRecoveryPromptEstimate(primaryError);
|
|
1731
|
+
return this.beginOverflowRecovery({
|
|
1732
|
+
recovery,
|
|
1733
|
+
agentContext,
|
|
1734
|
+
agentId,
|
|
1735
|
+
config,
|
|
1736
|
+
originalToolContent: prunedOriginalToolContent,
|
|
1737
|
+
estimatedPromptTokens: recoveryPromptEstimate
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1274
1740
|
/**
|
|
1275
1741
|
* A fallback can reject the same prompt as too large even when the
|
|
1276
1742
|
* primary failed for an unrelated reason — a fallback with a smaller
|
|
@@ -1284,7 +1750,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1284
1750
|
}), () => require_invoke.tryFallbackProviders({
|
|
1285
1751
|
fallbacks,
|
|
1286
1752
|
tools: agentContext.tools,
|
|
1287
|
-
messages:
|
|
1753
|
+
messages: fallbackBaseMessages,
|
|
1288
1754
|
config: invokeConfig,
|
|
1289
1755
|
primaryError,
|
|
1290
1756
|
context: this,
|
|
@@ -1297,16 +1763,52 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1297
1763
|
provider: agentContext.provider,
|
|
1298
1764
|
estimatedPromptTokens: getEstimatedPromptTokens(contextUsage),
|
|
1299
1765
|
maxContextTokens: agentContext.maxContextTokens
|
|
1766
|
+
},
|
|
1767
|
+
prepareProviderMessages: ({ model: fallbackModel, messages: fallbackMessages, provider: fallbackProvider, maxContextTokens: fallbackMaxContextTokens, config: fallbackConfig }) => {
|
|
1768
|
+
const fallbackToolResultChars = agentContext.maxToolResultChars ?? require_truncation.calculateMaxToolResultChars(fallbackMaxContextTokens ?? agentContext.maxContextTokens);
|
|
1769
|
+
/**
|
|
1770
|
+
* Serving-provider cue shaping BEFORE the fallback payload
|
|
1771
|
+
* is measured: a Claude fallback behind a tolerant primary
|
|
1772
|
+
* gains the cue inside the guarded projection (a prompt
|
|
1773
|
+
* within the cue's cost of the fallback budget must take
|
|
1774
|
+
* the recovery path, not ship oversized), and a tolerant
|
|
1775
|
+
* fallback behind an Anthropic primary sheds the baked cue
|
|
1776
|
+
* before it is measured against the tighter budget. The
|
|
1777
|
+
* attemptInvoke funnel pass then finds nothing to change.
|
|
1778
|
+
*/
|
|
1779
|
+
const cueShapedFallbackMessages = trackProviderMessageOrigins(fallbackMessages, require_llm.isAnthropicLike(fallbackProvider, { model: require_invoke.resolveServingModelId(fallbackModel) }) ? require_handoffCue.appendPredecessorHandoffCue(fallbackMessages, (m) => this.isRunProducedMessage(m)) : require_handoffCue.removePredecessorHandoffCue(fallbackMessages));
|
|
1780
|
+
const projectedFallbackMessages = trackProviderMessageOrigins(cueShapedFallbackMessages, require_invoke.projectMessagesForProvider({
|
|
1781
|
+
model: fallbackModel,
|
|
1782
|
+
messages: cueShapedFallbackMessages,
|
|
1783
|
+
provider: fallbackProvider,
|
|
1784
|
+
maxToolResultChars: fallbackToolResultChars,
|
|
1785
|
+
callOptions: fallbackConfig
|
|
1786
|
+
}));
|
|
1787
|
+
const primaryContextBudget = contextUsage?.contextBudget;
|
|
1788
|
+
const projection = measureProviderPayload(projectedFallbackMessages, fallbackMaxContextTokens == null ? primaryContextBudget : Math.min(primaryContextBudget ?? fallbackMaxContextTokens, fallbackMaxContextTokens), true);
|
|
1789
|
+
if (!projection.fits) throw createProviderPayloadOverflowError({
|
|
1790
|
+
projection,
|
|
1791
|
+
provider: fallbackProvider,
|
|
1792
|
+
info: "Fallback provider message formatting exceeded the context budget before invocation."
|
|
1793
|
+
});
|
|
1794
|
+
return projectedFallbackMessages;
|
|
1300
1795
|
}
|
|
1301
1796
|
}));
|
|
1302
1797
|
} catch (fallbackError) {
|
|
1303
1798
|
const overflowCandidates = require_invoke.getFallbackOverflowCandidates(fallbackError);
|
|
1304
1799
|
let fallbackRecovery = null;
|
|
1800
|
+
let fallbackRecoveryPromptEstimate;
|
|
1305
1801
|
for (const candidate of overflowCandidates) {
|
|
1306
1802
|
fallbackRecovery = planRecovery(candidate.error, candidate.context);
|
|
1307
|
-
if (fallbackRecovery != null)
|
|
1803
|
+
if (fallbackRecovery != null) {
|
|
1804
|
+
fallbackRecoveryPromptEstimate = getRecoveryPromptEstimate(candidate.error, candidate.context);
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (overflowCandidates.length === 0) {
|
|
1809
|
+
fallbackRecovery = planRecovery(fallbackError);
|
|
1810
|
+
fallbackRecoveryPromptEstimate = getRecoveryPromptEstimate(fallbackError);
|
|
1308
1811
|
}
|
|
1309
|
-
if (overflowCandidates.length === 0) fallbackRecovery = planRecovery(fallbackError);
|
|
1310
1812
|
if (fallbackRecovery == null) throw fallbackError;
|
|
1311
1813
|
return this.beginOverflowRecovery({
|
|
1312
1814
|
recovery: fallbackRecovery,
|
|
@@ -1314,7 +1816,7 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1314
1816
|
agentId,
|
|
1315
1817
|
config,
|
|
1316
1818
|
originalToolContent: prunedOriginalToolContent,
|
|
1317
|
-
estimatedPromptTokens
|
|
1819
|
+
estimatedPromptTokens: fallbackRecoveryPromptEstimate
|
|
1318
1820
|
});
|
|
1319
1821
|
}
|
|
1320
1822
|
} finally {
|
|
@@ -1337,6 +1839,22 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1337
1839
|
* handled everything — both paths become no-ops.
|
|
1338
1840
|
*/
|
|
1339
1841
|
const responseMessage = result.messages?.[0];
|
|
1842
|
+
/**
|
|
1843
|
+
* Provenance for the handoff-cue gate: recorded at the node, where the
|
|
1844
|
+
* produced turn is unambiguous. The public ChatModel contract does not
|
|
1845
|
+
* require implementations to set message ids — the reducer would
|
|
1846
|
+
* assign one AFTER this node returns, which is too late for the set —
|
|
1847
|
+
* so an id is assigned here first, the same way the reducer does it
|
|
1848
|
+
* (`v4()`, mirrored into `lc_kwargs`), and the reducer's
|
|
1849
|
+
* keep-existing-id rule makes the state message match.
|
|
1850
|
+
*/
|
|
1851
|
+
if (responseMessage?.getType() === "ai") {
|
|
1852
|
+
if (typeof responseMessage.id !== "string" || responseMessage.id === "") {
|
|
1853
|
+
responseMessage.id = (0, uuid.v4)();
|
|
1854
|
+
responseMessage.lc_kwargs.id = responseMessage.id;
|
|
1855
|
+
}
|
|
1856
|
+
this.runProducedAiMessageIds.add(responseMessage.id);
|
|
1857
|
+
}
|
|
1340
1858
|
const toolCalls = responseMessage?.tool_calls;
|
|
1341
1859
|
const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
|
|
1342
1860
|
const responseReasoningContent = getResponseReasoningContent({
|
|
@@ -1401,8 +1919,16 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1401
1919
|
}
|
|
1402
1920
|
const invokeElapsed = ((Date.now() - invokeStart) / 1e3).toFixed(2);
|
|
1403
1921
|
agentContext.currentUsage = this.getUsageMetadata(result.messages?.[0]);
|
|
1922
|
+
/**
|
|
1923
|
+
* Synthetic usage from a sealed turn is an estimate derived from the
|
|
1924
|
+
* host's own counter, so feeding it to calibration would teach a ratio
|
|
1925
|
+
* of exactly 1.0 — self-consistent by construction, and wrong for any
|
|
1926
|
+
* provider whose real ratio differs. It still flows to `currentUsage`
|
|
1927
|
+
* for host billing; it just does not get to move the EMA.
|
|
1928
|
+
*/
|
|
1929
|
+
const estimatedUsage = (result.messages?.[0])?.response_metadata.estimated_usage === true;
|
|
1404
1930
|
if (agentContext.currentUsage) {
|
|
1405
|
-
agentContext.updateLastCallUsage(agentContext.currentUsage);
|
|
1931
|
+
if (!estimatedUsage) agentContext.updateLastCallUsage(agentContext.currentUsage);
|
|
1406
1932
|
require_events.emitAgentLog(config, "debug", "graph", `LLM call complete (${invokeElapsed}s)`, {
|
|
1407
1933
|
...agentContext.currentUsage,
|
|
1408
1934
|
elapsedSeconds: Number(invokeElapsed),
|
|
@@ -1414,10 +1940,157 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1414
1940
|
elapsedSeconds: Number(invokeElapsed),
|
|
1415
1941
|
messageCount: finalMessages.length
|
|
1416
1942
|
}, invokeMeta, { force: true });
|
|
1943
|
+
if (responseMessage?.response_metadata.preempted === true) {
|
|
1944
|
+
const { messages: injected, preventContinuation } = await this.dispatchPreemptBoundary(agentId, config);
|
|
1945
|
+
/**
|
|
1946
|
+
* Release before branching: the slot is held only for the duration of
|
|
1947
|
+
* the drain, and an early return below must not strand it.
|
|
1948
|
+
*/
|
|
1949
|
+
this.releasePreemptSeal();
|
|
1950
|
+
if (preventContinuation) {
|
|
1951
|
+
/**
|
|
1952
|
+
* A hook halted at the boundary. Commit the sealed turn and anything
|
|
1953
|
+
* it injected, but do NOT self-loop: `preventContinuation` promises
|
|
1954
|
+
* no further model turn, and the run-loop poll in `processStream`
|
|
1955
|
+
* only sees the halt AFTER the next call would already have started
|
|
1956
|
+
* — direct graph consumers never poll it at all. A trailing injected
|
|
1957
|
+
* HumanMessage carries no tool calls, so `toolsCondition` routes it
|
|
1958
|
+
* to END.
|
|
1959
|
+
*/
|
|
1960
|
+
this.preemptIncomplete = true;
|
|
1961
|
+
/**
|
|
1962
|
+
* A halting boundary that ALSO injected nothing is still an empty
|
|
1963
|
+
* boundary by the `getPreemptStats().emptyBoundaries` contract —
|
|
1964
|
+
* hosts use the counter for truncated-seal telemetry, and both
|
|
1965
|
+
* paths end the turn with nothing to resume from.
|
|
1966
|
+
*/
|
|
1967
|
+
if (injected.length === 0) this.preemptEmptyBoundaries += 1;
|
|
1968
|
+
this.cleanupSignalListener();
|
|
1969
|
+
return injected.length > 0 ? { messages: [...result.messages ?? [], ...injected] } : result;
|
|
1970
|
+
}
|
|
1971
|
+
if (injected.length > 0) {
|
|
1972
|
+
this.pendingPreemptReturn.add(agentId);
|
|
1973
|
+
this.cleanupSignalListener();
|
|
1974
|
+
return { messages: [...result.messages ?? [], ...injected] };
|
|
1975
|
+
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Nothing to inject — the host cancelled or already drained. Do NOT
|
|
1978
|
+
* self-loop: a trailing model turn with no new input is dropped by
|
|
1979
|
+
* some Gemini models and read as prefill by Anthropic. Do NOT pretend
|
|
1980
|
+
* the turn completed either; the answer really was cut short.
|
|
1981
|
+
*/
|
|
1982
|
+
this.preemptEmptyBoundaries += 1;
|
|
1983
|
+
this.preemptIncomplete = true;
|
|
1984
|
+
}
|
|
1417
1985
|
this.cleanupSignalListener();
|
|
1418
1986
|
return result;
|
|
1419
1987
|
};
|
|
1420
1988
|
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Fires `PreemptBoundary` after a sealed turn and returns whatever the
|
|
1991
|
+
* hooks asked to inject, converted through the same `convertInjectedMessages`
|
|
1992
|
+
* the tool boundary uses so the two sites cannot emit different shapes.
|
|
1993
|
+
*
|
|
1994
|
+
* Never throws: a drain that fails or times out costs the injection, not the
|
|
1995
|
+
* run. The caller treats an empty result as "nothing to resume with".
|
|
1996
|
+
*
|
|
1997
|
+
* `preventContinuation` is surfaced alongside the messages rather than left
|
|
1998
|
+
* to the registry halt signal, which `processStream` only polls between
|
|
1999
|
+
* stream events — by then the self-loop it was meant to prevent has already
|
|
2000
|
+
* issued another model call, and a direct graph consumer never polls it.
|
|
2001
|
+
*/
|
|
2002
|
+
async dispatchPreemptBoundary(agentId, config) {
|
|
2003
|
+
if (this.hookRegistry == null) return EMPTY_PREEMPT_BOUNDARY;
|
|
2004
|
+
const configurable = config?.configurable;
|
|
2005
|
+
const runId = configurable?.run_id ?? this.runId;
|
|
2006
|
+
if (runId == null) return EMPTY_PREEMPT_BOUNDARY;
|
|
2007
|
+
const result = await require_executeHooks.executeHooks({
|
|
2008
|
+
registry: this.hookRegistry,
|
|
2009
|
+
input: {
|
|
2010
|
+
hook_event_name: "PreemptBoundary",
|
|
2011
|
+
runId,
|
|
2012
|
+
threadId: configurable?.thread_id,
|
|
2013
|
+
agentId: this.subagentScope ? agentId : void 0,
|
|
2014
|
+
executingAgentId: agentId,
|
|
2015
|
+
sealCount: this.preemptSealCount
|
|
2016
|
+
},
|
|
2017
|
+
sessionId: runId,
|
|
2018
|
+
timeoutMs: require_constants.PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS,
|
|
2019
|
+
/**
|
|
2020
|
+
* The host's own abort signal(s), deliberately NOT `config.signal` —
|
|
2021
|
+
* inside a node the latter is LangGraph's composed signal, which also
|
|
2022
|
+
* fires when an unrelated sibling in the same superstep throws.
|
|
2023
|
+
* Cancellation already returns control in milliseconds without this;
|
|
2024
|
+
* what it buys is that a drain does not keep running after the run it
|
|
2025
|
+
* belongs to died.
|
|
2026
|
+
*
|
|
2027
|
+
* Composed because the host can cancel through either channel: the
|
|
2028
|
+
* construction signal, or the per-call `callerConfig.signal` — the only
|
|
2029
|
+
* one a multi-agent run has, since `MultiAgentGraphConfig` exposes no
|
|
2030
|
+
* construction signal. When both exist they may be different
|
|
2031
|
+
* controllers, and a drain must stop when EITHER fires.
|
|
2032
|
+
*/
|
|
2033
|
+
signal: composeAbortSignals(this.signal, this.callerSignal)
|
|
2034
|
+
}).catch(() => void 0);
|
|
2035
|
+
if (result == null) return EMPTY_PREEMPT_BOUNDARY;
|
|
2036
|
+
/**
|
|
2037
|
+
* `executeHooks` raises a registry halt whenever a hook returns
|
|
2038
|
+
* `preventContinuation`. That halt has exactly one consumer — the poll in
|
|
2039
|
+
* `Run.processStream` — and its `break` cancels the stream iterator, which
|
|
2040
|
+
* aborts Pregel. The abort lands BEFORE the outer reducer commits
|
|
2041
|
+
* `StandardGraph.messages`, so honoring the halt here would destroy the
|
|
2042
|
+
* sealed assistant turn: the run returns empty content and the host
|
|
2043
|
+
* persists nothing. Measured deterministically — the commit is several
|
|
2044
|
+
* stream events downstream of the point the halt becomes observable.
|
|
2045
|
+
*
|
|
2046
|
+
* The `preventContinuation` branch in `createCallModel` already enforces
|
|
2047
|
+
* the contract locally by declining to self-loop, and a sealed chunk
|
|
2048
|
+
* provably carries no tool calls, so the turn routes to END after exactly
|
|
2049
|
+
* one model call either way. Clearing the halt therefore costs nothing it
|
|
2050
|
+
* was buying and saves the content the seal exists to preserve.
|
|
2051
|
+
*
|
|
2052
|
+
* Scoped to a halt this event raised, so a halt from an earlier hook in
|
|
2053
|
+
* the same run — `haltRun` is first-write-wins — is left alone.
|
|
2054
|
+
*/
|
|
2055
|
+
const halt = this.hookRegistry.getHaltSignal(runId);
|
|
2056
|
+
if (result.preventContinuation === true && halt?.source === "PreemptBoundary") {
|
|
2057
|
+
this.preemptHaltReason = halt.reason;
|
|
2058
|
+
this.hookRegistry.clearHaltSignal(runId);
|
|
2059
|
+
}
|
|
2060
|
+
const injected = [];
|
|
2061
|
+
/**
|
|
2062
|
+
* `PreemptBoundaryHookOutput` is `BaseHookOutput`, so `additionalContext`
|
|
2063
|
+
* is part of the contract here just as it is at the tool boundary. It has
|
|
2064
|
+
* to be materialized BEFORE the emptiness test, or a hook that returns
|
|
2065
|
+
* context alone would read as "nothing to resume with" and cut the answer
|
|
2066
|
+
* short. Same system-flavored `HumanMessage` convention `ToolNode` uses —
|
|
2067
|
+
* Anthropic and Google reject a mid-conversation `SystemMessage`.
|
|
2068
|
+
*/
|
|
2069
|
+
/**
|
|
2070
|
+
* Whitespace-only entries are dropped for the same reason empty
|
|
2071
|
+
* `injectedMessages` are: `executeHooks` keeps them because their raw
|
|
2072
|
+
* length is nonzero, but a blank turn is not something to resume from —
|
|
2073
|
+
* it costs a model call and strict providers reject it outright.
|
|
2074
|
+
*/
|
|
2075
|
+
const contexts = result.additionalContexts.filter((context) => context.trim() !== "");
|
|
2076
|
+
if (contexts.length > 0) injected.push(new _langchain_core_messages.HumanMessage({
|
|
2077
|
+
content: contexts.join("\n\n"),
|
|
2078
|
+
additional_kwargs: {
|
|
2079
|
+
role: "system",
|
|
2080
|
+
isMeta: true,
|
|
2081
|
+
source: "hook"
|
|
2082
|
+
}
|
|
2083
|
+
}));
|
|
2084
|
+
if (result.injectedMessages.length > 0) try {
|
|
2085
|
+
injected.push(...require_injected.convertInjectedMessages(result.injectedMessages));
|
|
2086
|
+
} catch (e) {
|
|
2087
|
+
console.warn("[StandardGraph] Failed to convert PreemptBoundary injectedMessages:", e instanceof Error ? e.message : e);
|
|
2088
|
+
}
|
|
2089
|
+
return {
|
|
2090
|
+
messages: injected,
|
|
2091
|
+
preventContinuation: result.preventContinuation === true
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
1421
2094
|
createAgentNode(agentId) {
|
|
1422
2095
|
const getConfig = () => this.config;
|
|
1423
2096
|
const agentContext = this.agentContexts.get(agentId);
|
|
@@ -1527,6 +2200,12 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1527
2200
|
const summarizeNode = `${SUMMARIZE}${agentId}`;
|
|
1528
2201
|
const routeMessage = (state, config) => {
|
|
1529
2202
|
this.config = config;
|
|
2203
|
+
/**
|
|
2204
|
+
* A sealed turn that injected messages resumes in the SAME pregel run:
|
|
2205
|
+
* back to the agent node as a new superstep, so the model continues in
|
|
2206
|
+
* one assistant message instead of restarting the graph.
|
|
2207
|
+
*/
|
|
2208
|
+
if (this.pendingPreemptReturn.delete(agentId)) return agentNode;
|
|
1530
2209
|
if (state.summarizationRequest != null) return summarizeNode;
|
|
1531
2210
|
return require_ToolNode.toolsCondition(state, toolNode, this.invokedToolIds);
|
|
1532
2211
|
};
|
|
@@ -1690,13 +2369,16 @@ var StandardGraph = class StandardGraph extends Graph {
|
|
|
1690
2369
|
const stepId = graph.toolCallStepIds.get(data.id) ?? "";
|
|
1691
2370
|
if (!stepId) return false;
|
|
1692
2371
|
const { name, input: args, error } = data;
|
|
2372
|
+
const eventValueLimit = require_truncation.calculateMaxToolResultChars();
|
|
2373
|
+
const errorOutputPrefix = "Error processing tool";
|
|
2374
|
+
const errorDetail = error?.message != null ? `: ${require_toolContent.serializeToolContentBounded(error.message, Math.max(0, eventValueLimit - 21 - 2))}` : "";
|
|
1693
2375
|
const runStep = graph.getRunStep(stepId);
|
|
1694
2376
|
if (!runStep) return false;
|
|
1695
2377
|
const tool_call = {
|
|
1696
2378
|
id: data.id,
|
|
1697
2379
|
name: name || "",
|
|
1698
|
-
args:
|
|
1699
|
-
output:
|
|
2380
|
+
args: require_toolContent.serializeToolContentBounded(args, eventValueLimit),
|
|
2381
|
+
output: `${errorOutputPrefix}${errorDetail}`,
|
|
1700
2382
|
progress: 1
|
|
1701
2383
|
};
|
|
1702
2384
|
const handler = graph.handlerRegistry?.getHandler("on_run_step_completed");
|