@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
package/src/tools/ToolNode.ts
CHANGED
|
@@ -38,6 +38,15 @@ import type {
|
|
|
38
38
|
PostToolBatchEntry,
|
|
39
39
|
} from '@/hooks';
|
|
40
40
|
import type * as t from '@/types';
|
|
41
|
+
import {
|
|
42
|
+
cloneToolMessageWithContent,
|
|
43
|
+
compactToolContent,
|
|
44
|
+
hasComputerCallOutputMarker,
|
|
45
|
+
isComputerCallOutputContent,
|
|
46
|
+
isComputerCallOutputMessage,
|
|
47
|
+
serializeStructuredValueBounded,
|
|
48
|
+
serializeToolContentBounded,
|
|
49
|
+
} from '@/utils/toolContent';
|
|
41
50
|
import {
|
|
42
51
|
buildToolExecutionRequestPlan,
|
|
43
52
|
resolveRuntimeSessionHint,
|
|
@@ -61,7 +70,7 @@ import {
|
|
|
61
70
|
} from '@/tools/local';
|
|
62
71
|
import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
63
72
|
import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
|
|
64
|
-
import {
|
|
73
|
+
import { convertInjectedMessages } from '@/messages/injected';
|
|
65
74
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
66
75
|
import { RunnableCallable } from '@/utils';
|
|
67
76
|
import { executeHooks } from '@/hooks';
|
|
@@ -119,6 +128,40 @@ type RunToolBatchContext<T = unknown> = {
|
|
|
119
128
|
runInput?: T;
|
|
120
129
|
};
|
|
121
130
|
|
|
131
|
+
type BoundedToolOutput = {
|
|
132
|
+
content: string;
|
|
133
|
+
registryContent: string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Produces the provider preview and, when requested, the exact registry prefix
|
|
138
|
+
* in one bounded traversal. String outputs already exist in memory when a tool
|
|
139
|
+
* returns them; structured outputs must never be fully JSON-materialized before
|
|
140
|
+
* either limit applies.
|
|
141
|
+
*/
|
|
142
|
+
function serializeToolOutputWithinLimits(
|
|
143
|
+
output: unknown,
|
|
144
|
+
maxToolResultChars: number,
|
|
145
|
+
registryPrefixChars = 0
|
|
146
|
+
): BoundedToolOutput {
|
|
147
|
+
if (typeof output === 'string') {
|
|
148
|
+
return {
|
|
149
|
+
content: truncateToolResultContent(output, maxToolResultChars),
|
|
150
|
+
registryContent: registryPrefixChars > 0 ? output : '',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const serialized = serializeStructuredValueBounded(
|
|
155
|
+
output,
|
|
156
|
+
maxToolResultChars,
|
|
157
|
+
registryPrefixChars
|
|
158
|
+
);
|
|
159
|
+
return {
|
|
160
|
+
content: serialized.content,
|
|
161
|
+
registryContent: serialized.prefix,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
122
165
|
const TOOL_NODE_RUN_NAME = 'tool_batch';
|
|
123
166
|
const NANOID_URL_ALPHABET =
|
|
124
167
|
'_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
@@ -1107,6 +1150,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1107
1150
|
}
|
|
1108
1151
|
if (isBaseMessage(output) && output._getType() === 'tool') {
|
|
1109
1152
|
const toolMsg = output as ToolMessage;
|
|
1153
|
+
if (isComputerCallOutputMessage(toolMsg)) {
|
|
1154
|
+
return toolMsg;
|
|
1155
|
+
}
|
|
1156
|
+
const originalContent = toolMsg.content;
|
|
1157
|
+
const compacted = compactToolContent(
|
|
1158
|
+
originalContent,
|
|
1159
|
+
this.maxToolResultChars
|
|
1160
|
+
);
|
|
1161
|
+
if (compacted.changed) {
|
|
1162
|
+
toolMsg.content = compacted.content;
|
|
1163
|
+
}
|
|
1110
1164
|
const isError = toolMsg.status === 'error';
|
|
1111
1165
|
if (isError) {
|
|
1112
1166
|
/**
|
|
@@ -1125,14 +1179,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1125
1179
|
return toolMsg;
|
|
1126
1180
|
}
|
|
1127
1181
|
if (this.toolOutputRegistry != null || unresolvedRefs.length > 0) {
|
|
1128
|
-
if (typeof
|
|
1129
|
-
const rawContent =
|
|
1182
|
+
if (typeof originalContent === 'string') {
|
|
1183
|
+
const rawContent = originalContent;
|
|
1130
1184
|
const registryContent = stripCodeSessionFileSummary(rawContent);
|
|
1131
|
-
const llmContent = truncateToolResultContent(
|
|
1132
|
-
rawContent,
|
|
1133
|
-
this.maxToolResultChars
|
|
1134
|
-
);
|
|
1135
|
-
toolMsg.content = llmContent;
|
|
1136
1185
|
const refMeta = this.recordOutputReference(
|
|
1137
1186
|
runId,
|
|
1138
1187
|
registryContent,
|
|
@@ -1148,7 +1197,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1148
1197
|
} else {
|
|
1149
1198
|
/**
|
|
1150
1199
|
* Non-string content (multi-part content blocks — text +
|
|
1151
|
-
* image).
|
|
1200
|
+
* image). It is now bounded for the LLM, but cannot register under a
|
|
1152
1201
|
* reference key because there's no canonical serialized
|
|
1153
1202
|
* form. Warn once per tool per run when the caller
|
|
1154
1203
|
* intended to register. The unresolved-refs hint is still
|
|
@@ -1176,22 +1225,23 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1176
1225
|
}
|
|
1177
1226
|
return toolMsg;
|
|
1178
1227
|
}
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1228
|
+
const serialized = serializeToolOutputWithinLimits(
|
|
1229
|
+
output,
|
|
1230
|
+
this.maxToolResultChars,
|
|
1231
|
+
this.toolOutputRegistry != null && refKey != null
|
|
1232
|
+
? this.toolOutputRegistry.perOutputLimit
|
|
1233
|
+
: 0
|
|
1184
1234
|
);
|
|
1185
1235
|
const refMeta = this.recordOutputReference(
|
|
1186
1236
|
runId,
|
|
1187
|
-
stripCodeSessionFileSummary(
|
|
1237
|
+
stripCodeSessionFileSummary(serialized.registryContent),
|
|
1188
1238
|
refKey,
|
|
1189
1239
|
unresolvedRefs
|
|
1190
1240
|
);
|
|
1191
1241
|
return new ToolMessage({
|
|
1192
1242
|
status: 'success',
|
|
1193
1243
|
name: tool.name,
|
|
1194
|
-
content:
|
|
1244
|
+
content: serialized.content,
|
|
1195
1245
|
tool_call_id: call.id!,
|
|
1196
1246
|
...(refMeta != null && {
|
|
1197
1247
|
additional_kwargs: refMeta as Record<string, unknown>,
|
|
@@ -1258,7 +1308,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1258
1308
|
});
|
|
1259
1309
|
}
|
|
1260
1310
|
}
|
|
1261
|
-
const errorContent =
|
|
1311
|
+
const errorContent = truncateToolResultContent(
|
|
1312
|
+
`Error: ${e.message}\n Please fix your mistakes.`,
|
|
1313
|
+
this.maxToolResultChars
|
|
1314
|
+
);
|
|
1262
1315
|
const refMeta =
|
|
1263
1316
|
unresolvedRefs.length > 0
|
|
1264
1317
|
? this.recordOutputReference(
|
|
@@ -1630,7 +1683,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1630
1683
|
error:
|
|
1631
1684
|
typeof output.content === 'string'
|
|
1632
1685
|
? output.content
|
|
1633
|
-
:
|
|
1686
|
+
: serializeStructuredValueBounded(
|
|
1687
|
+
output.content,
|
|
1688
|
+
this.maxToolResultChars
|
|
1689
|
+
).content,
|
|
1634
1690
|
stepId,
|
|
1635
1691
|
turn,
|
|
1636
1692
|
},
|
|
@@ -1682,10 +1738,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1682
1738
|
}
|
|
1683
1739
|
|
|
1684
1740
|
if (postResult?.updatedOutput != null) {
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1741
|
+
if (hasComputerCallOutputMarker(output)) {
|
|
1742
|
+
if (!isComputerCallOutputContent(postResult.updatedOutput)) {
|
|
1743
|
+
throw new Error(
|
|
1744
|
+
'PostToolUse updatedOutput for a computer call must be a valid screenshot URL or screenshot content block.'
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
return cloneToolMessageWithContent(output, postResult.updatedOutput);
|
|
1748
|
+
}
|
|
1689
1749
|
// Keep the tool-output registry in sync with what the model
|
|
1690
1750
|
// actually sees. Without this, `runTool` already registered
|
|
1691
1751
|
// the PRE-hook content under `_refKey`, and a later
|
|
@@ -1700,17 +1760,21 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1700
1760
|
| undefined;
|
|
1701
1761
|
const refKey = refMeta?._refKey;
|
|
1702
1762
|
const refScope = refMeta?._refScope;
|
|
1763
|
+
const replaced = serializeToolOutputWithinLimits(
|
|
1764
|
+
postResult.updatedOutput,
|
|
1765
|
+
this.maxToolResultChars,
|
|
1766
|
+
this.toolOutputRegistry != null && refKey != null
|
|
1767
|
+
? this.toolOutputRegistry.perOutputLimit
|
|
1768
|
+
: 0
|
|
1769
|
+
);
|
|
1703
1770
|
if (this.toolOutputRegistry != null && refKey != null) {
|
|
1704
|
-
this.toolOutputRegistry.set(
|
|
1771
|
+
this.toolOutputRegistry.set(
|
|
1772
|
+
refScope,
|
|
1773
|
+
refKey,
|
|
1774
|
+
replaced.registryContent
|
|
1775
|
+
);
|
|
1705
1776
|
}
|
|
1706
|
-
return
|
|
1707
|
-
status: output.status,
|
|
1708
|
-
name: output.name,
|
|
1709
|
-
content: replaced,
|
|
1710
|
-
artifact: output.artifact,
|
|
1711
|
-
tool_call_id: output.tool_call_id,
|
|
1712
|
-
additional_kwargs: output.additional_kwargs,
|
|
1713
|
-
});
|
|
1777
|
+
return cloneToolMessageWithContent(output, replaced.content);
|
|
1714
1778
|
}
|
|
1715
1779
|
}
|
|
1716
1780
|
|
|
@@ -1993,10 +2057,20 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1993
2057
|
continue;
|
|
1994
2058
|
}
|
|
1995
2059
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2060
|
+
let contentString: string;
|
|
2061
|
+
if (isComputerCallOutputMessage(toolMessage)) {
|
|
2062
|
+
contentString = truncateToolResultContent(
|
|
2063
|
+
'[Computer screenshot omitted from completion event]',
|
|
2064
|
+
this.maxToolResultChars
|
|
2065
|
+
);
|
|
2066
|
+
} else if (typeof toolMessage.content === 'string') {
|
|
2067
|
+
contentString = toolMessage.content;
|
|
2068
|
+
} else {
|
|
2069
|
+
contentString = serializeStructuredValueBounded(
|
|
2070
|
+
toolMessage.content,
|
|
2071
|
+
this.maxToolResultChars
|
|
2072
|
+
).content;
|
|
2073
|
+
}
|
|
2000
2074
|
|
|
2001
2075
|
/**
|
|
2002
2076
|
* Prefer the post-substitution args when a `{{…}}` placeholder
|
|
@@ -2006,10 +2080,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2006
2080
|
*/
|
|
2007
2081
|
const effectiveArgs = resolvedArgsByCallId?.get(toolCallId) ?? call.args;
|
|
2008
2082
|
const tool_call: t.ProcessedToolCall = {
|
|
2009
|
-
args:
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2083
|
+
args: serializeToolContentBounded(
|
|
2084
|
+
(effectiveArgs as unknown) ?? {},
|
|
2085
|
+
this.maxToolResultChars
|
|
2086
|
+
),
|
|
2013
2087
|
name: call.name,
|
|
2014
2088
|
id: toolCallId,
|
|
2015
2089
|
output: contentString,
|
|
@@ -2860,7 +2934,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2860
2934
|
if (result.injectedMessages && result.injectedMessages.length > 0) {
|
|
2861
2935
|
try {
|
|
2862
2936
|
injected.push(
|
|
2863
|
-
...
|
|
2937
|
+
...convertInjectedMessages(result.injectedMessages)
|
|
2864
2938
|
);
|
|
2865
2939
|
} catch (e) {
|
|
2866
2940
|
// eslint-disable-next-line no-console
|
|
@@ -2886,7 +2960,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2886
2960
|
let finalToolOutput: unknown = result.content;
|
|
2887
2961
|
|
|
2888
2962
|
if (result.status === 'error') {
|
|
2889
|
-
contentString =
|
|
2963
|
+
contentString = truncateToolResultContent(
|
|
2964
|
+
`Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`,
|
|
2965
|
+
this.maxToolResultChars
|
|
2966
|
+
);
|
|
2890
2967
|
/**
|
|
2891
2968
|
* Error results bypass registration but stamp the
|
|
2892
2969
|
* unresolved-refs hint into `additional_kwargs` so the lazy
|
|
@@ -2949,14 +3026,22 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2949
3026
|
}
|
|
2950
3027
|
}
|
|
2951
3028
|
} else {
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3029
|
+
const batchIndex = batchIndexByCallId.get(result.toolCallId);
|
|
3030
|
+
const refKey =
|
|
3031
|
+
this.toolOutputRegistry != null &&
|
|
3032
|
+
batchIndex != null &&
|
|
3033
|
+
turn != null
|
|
3034
|
+
? buildReferenceKey(batchIndex, turn)
|
|
3035
|
+
: undefined;
|
|
3036
|
+
let serialized = serializeToolOutputWithinLimits(
|
|
3037
|
+
result.content,
|
|
3038
|
+
this.maxToolResultChars,
|
|
3039
|
+
this.toolOutputRegistry != null && refKey != null
|
|
3040
|
+
? this.toolOutputRegistry.perOutputLimit
|
|
3041
|
+
: 0
|
|
2959
3042
|
);
|
|
3043
|
+
let registryRaw = serialized.registryContent;
|
|
3044
|
+
contentString = serialized.content;
|
|
2960
3045
|
|
|
2961
3046
|
if (hasPostHook) {
|
|
2962
3047
|
const hookResult = await executeHooks({
|
|
@@ -2983,15 +3068,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2983
3068
|
}
|
|
2984
3069
|
}
|
|
2985
3070
|
if (hookResult?.updatedOutput != null) {
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
replaced,
|
|
2993
|
-
this.maxToolResultChars
|
|
3071
|
+
serialized = serializeToolOutputWithinLimits(
|
|
3072
|
+
hookResult.updatedOutput,
|
|
3073
|
+
this.maxToolResultChars,
|
|
3074
|
+
this.toolOutputRegistry != null && refKey != null
|
|
3075
|
+
? this.toolOutputRegistry.perOutputLimit
|
|
3076
|
+
: 0
|
|
2994
3077
|
);
|
|
3078
|
+
registryRaw = serialized.registryContent;
|
|
3079
|
+
contentString = serialized.content;
|
|
2995
3080
|
finalToolOutput = hookResult.updatedOutput;
|
|
2996
3081
|
/**
|
|
2997
3082
|
* The hook ACTUALLY rewrote this output: any completion the
|
|
@@ -3005,14 +3090,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3005
3090
|
}
|
|
3006
3091
|
}
|
|
3007
3092
|
|
|
3008
|
-
const batchIndex = batchIndexByCallId.get(result.toolCallId);
|
|
3009
3093
|
const unresolved = unresolvedByCallId.get(result.toolCallId) ?? [];
|
|
3010
|
-
const refKey =
|
|
3011
|
-
this.toolOutputRegistry != null &&
|
|
3012
|
-
batchIndex != null &&
|
|
3013
|
-
turn != null
|
|
3014
|
-
? buildReferenceKey(batchIndex, turn)
|
|
3015
|
-
: undefined;
|
|
3016
3094
|
const successRefMeta = this.recordOutputReference(
|
|
3017
3095
|
registryRunId,
|
|
3018
3096
|
stripCodeSessionFileSummary(registryRaw),
|
|
@@ -3266,7 +3344,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3266
3344
|
*/
|
|
3267
3345
|
if (hookInjectedMessages.length > 0) {
|
|
3268
3346
|
try {
|
|
3269
|
-
injected.push(...
|
|
3347
|
+
injected.push(...convertInjectedMessages(hookInjectedMessages));
|
|
3270
3348
|
} catch (e) {
|
|
3271
3349
|
// eslint-disable-next-line no-console
|
|
3272
3350
|
console.warn(
|
|
@@ -3303,7 +3381,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3303
3381
|
index: turn ?? this.toolUsageCount.get(toolName) ?? 0,
|
|
3304
3382
|
type: 'tool_call' as const,
|
|
3305
3383
|
tool_call: {
|
|
3306
|
-
args:
|
|
3384
|
+
args: serializeToolContentBounded(args, this.maxToolResultChars),
|
|
3307
3385
|
name: toolName,
|
|
3308
3386
|
id: toolCallId,
|
|
3309
3387
|
output,
|
|
@@ -3330,13 +3408,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3330
3408
|
): Promise<boolean> {
|
|
3331
3409
|
const output =
|
|
3332
3410
|
result.status === 'error'
|
|
3333
|
-
?
|
|
3334
|
-
|
|
3335
|
-
typeof result.content === 'string'
|
|
3336
|
-
? result.content
|
|
3337
|
-
: JSON.stringify(result.content),
|
|
3411
|
+
? truncateToolResultContent(
|
|
3412
|
+
`Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`,
|
|
3338
3413
|
this.maxToolResultChars
|
|
3339
|
-
)
|
|
3414
|
+
)
|
|
3415
|
+
: serializeToolOutputWithinLimits(
|
|
3416
|
+
result.content,
|
|
3417
|
+
this.maxToolResultChars
|
|
3418
|
+
).content;
|
|
3340
3419
|
return this.dispatchStepCompleted(
|
|
3341
3420
|
result.toolCallId,
|
|
3342
3421
|
request.name,
|
|
@@ -3347,34 +3426,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3347
3426
|
);
|
|
3348
3427
|
}
|
|
3349
3428
|
|
|
3350
|
-
/**
|
|
3351
|
-
* Converts InjectedMessage instances to LangChain HumanMessage objects.
|
|
3352
|
-
* Both 'user' and 'system' roles become HumanMessage to avoid provider
|
|
3353
|
-
* rejections (Anthropic/Google reject non-leading SystemMessages).
|
|
3354
|
-
* The original role is preserved in additional_kwargs for downstream consumers.
|
|
3355
|
-
*/
|
|
3356
|
-
private convertInjectedMessages(
|
|
3357
|
-
messages: t.InjectedMessage[]
|
|
3358
|
-
): BaseMessage[] {
|
|
3359
|
-
const converted: BaseMessage[] = [];
|
|
3360
|
-
for (const msg of messages) {
|
|
3361
|
-
const additional_kwargs: Record<string, unknown> = {
|
|
3362
|
-
role: msg.role,
|
|
3363
|
-
};
|
|
3364
|
-
if (msg.isMeta != null) additional_kwargs.isMeta = msg.isMeta;
|
|
3365
|
-
if (msg.source != null) additional_kwargs.source = msg.source;
|
|
3366
|
-
if (msg.skillName != null) additional_kwargs.skillName = msg.skillName;
|
|
3367
|
-
|
|
3368
|
-
converted.push(
|
|
3369
|
-
new HumanMessage({
|
|
3370
|
-
content: toLangChainContent(msg.content),
|
|
3371
|
-
additional_kwargs,
|
|
3372
|
-
})
|
|
3373
|
-
);
|
|
3374
|
-
}
|
|
3375
|
-
return converted;
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
3429
|
/**
|
|
3379
3430
|
* Execute a group of direct (in-process) tool calls with interrupt-safe
|
|
3380
3431
|
* ordering, returning outputs aligned 1:1 with `directCalls`.
|
|
@@ -29,7 +29,7 @@ function createAIMessageWithToolCalls(
|
|
|
29
29
|
type CompletionEvent = {
|
|
30
30
|
result: {
|
|
31
31
|
id: string;
|
|
32
|
-
tool_call: { id: string; output: string };
|
|
32
|
+
tool_call: { id: string; output: string; args?: string };
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -124,6 +124,153 @@ describe('ToolNode per-call onResult completion emission', () => {
|
|
|
124
124
|
expect(result.messages.map((m) => m.content)).toEqual(['sunny', '42']);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it('serializes bigint output before early and batch completion paths', async () => {
|
|
128
|
+
const completions: CompletionEvent[] = [];
|
|
129
|
+
const structuredOutput = [{ rowsRead: BigInt(42), status: 'complete' }];
|
|
130
|
+
|
|
131
|
+
jest
|
|
132
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
133
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
134
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
135
|
+
completions.push(data as CompletionEvent);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
142
|
+
batch.onResult?.({
|
|
143
|
+
toolCallId: 'call_query',
|
|
144
|
+
status: 'success',
|
|
145
|
+
content: structuredOutput,
|
|
146
|
+
});
|
|
147
|
+
await flushAsync();
|
|
148
|
+
batch.resolve([
|
|
149
|
+
{
|
|
150
|
+
toolCallId: 'call_query',
|
|
151
|
+
status: 'success',
|
|
152
|
+
content: structuredOutput,
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const toolNode = new ToolNode({
|
|
158
|
+
tools: [createDummyTool('query')],
|
|
159
|
+
eventDrivenMode: true,
|
|
160
|
+
toolCallStepIds: new Map([['call_query', 'step_query']]),
|
|
161
|
+
});
|
|
162
|
+
const result = (await toolNode.invoke({
|
|
163
|
+
messages: [
|
|
164
|
+
createAIMessageWithToolCalls([
|
|
165
|
+
{ id: 'call_query', name: 'query', args: {} },
|
|
166
|
+
]),
|
|
167
|
+
],
|
|
168
|
+
})) as { messages: ToolMessage[] };
|
|
169
|
+
|
|
170
|
+
const serialized = '[{"rowsRead":"42","status":"complete"}]';
|
|
171
|
+
expect(completions).toHaveLength(1);
|
|
172
|
+
expect(completions[0].result.tool_call.output).toBe(serialized);
|
|
173
|
+
expect(result.messages[0].content).toBe(serialized);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('omits native computer screenshots from completion events', async () => {
|
|
177
|
+
const completions: CompletionEvent[] = [];
|
|
178
|
+
const screenshot = `data:image/png;base64,${'A'.repeat(2_000)}`;
|
|
179
|
+
const computerOutput = new ToolMessage({
|
|
180
|
+
content: screenshot,
|
|
181
|
+
tool_call_id: 'call_computer',
|
|
182
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
183
|
+
});
|
|
184
|
+
const computer = createDummyTool('computer_use');
|
|
185
|
+
(
|
|
186
|
+
computer as unknown as {
|
|
187
|
+
invoke: () => Promise<ToolMessage>;
|
|
188
|
+
}
|
|
189
|
+
).invoke = async () => computerOutput;
|
|
190
|
+
jest
|
|
191
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
192
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
193
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
194
|
+
completions.push(data as CompletionEvent);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const toolNode = new ToolNode({
|
|
198
|
+
tools: [computer],
|
|
199
|
+
eventDrivenMode: true,
|
|
200
|
+
directToolNames: new Set(['computer_use']),
|
|
201
|
+
toolCallStepIds: new Map([['call_computer', 'step_computer']]),
|
|
202
|
+
maxToolResultChars: 80,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const result = (await toolNode.invoke({
|
|
206
|
+
messages: [
|
|
207
|
+
createAIMessageWithToolCalls([
|
|
208
|
+
{ id: 'call_computer', name: 'computer_use', args: {} },
|
|
209
|
+
]),
|
|
210
|
+
],
|
|
211
|
+
})) as { messages: ToolMessage[] };
|
|
212
|
+
|
|
213
|
+
expect(result.messages[0]).toBe(computerOutput);
|
|
214
|
+
expect(result.messages[0].content).toBe(screenshot);
|
|
215
|
+
expect(completions).toHaveLength(1);
|
|
216
|
+
expect(completions[0].result.tool_call.output).toContain(
|
|
217
|
+
'Computer screenshot omitted'
|
|
218
|
+
);
|
|
219
|
+
expect(completions[0].result.tool_call.output.length).toBeLessThanOrEqual(
|
|
220
|
+
80
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('bounds cyclic tool args without losing the completion event', async () => {
|
|
225
|
+
const completions: CompletionEvent[] = [];
|
|
226
|
+
const cyclicArgs: Record<string, unknown> = { city: 'NYC' };
|
|
227
|
+
cyclicArgs.self = cyclicArgs;
|
|
228
|
+
|
|
229
|
+
jest
|
|
230
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
231
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
232
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
233
|
+
completions.push(data as CompletionEvent);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
240
|
+
batch.onResult?.({
|
|
241
|
+
toolCallId: 'call_weather',
|
|
242
|
+
status: 'success',
|
|
243
|
+
content: 'sunny',
|
|
244
|
+
});
|
|
245
|
+
await flushAsync();
|
|
246
|
+
batch.resolve([
|
|
247
|
+
{
|
|
248
|
+
toolCallId: 'call_weather',
|
|
249
|
+
status: 'success',
|
|
250
|
+
content: 'sunny',
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const toolNode = new ToolNode({
|
|
256
|
+
tools: [createDummyTool('weather')],
|
|
257
|
+
eventDrivenMode: true,
|
|
258
|
+
toolCallStepIds: new Map([['call_weather', 'step_weather']]),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await toolNode.invoke({
|
|
262
|
+
messages: [
|
|
263
|
+
createAIMessageWithToolCalls([
|
|
264
|
+
{ id: 'call_weather', name: 'weather', args: cyclicArgs },
|
|
265
|
+
]),
|
|
266
|
+
],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(completions).toHaveLength(1);
|
|
270
|
+
expect(completions[0].result.tool_call.args).toContain('[Circular]');
|
|
271
|
+
expect(completions[0].result.tool_call.output).toBe('sunny');
|
|
272
|
+
});
|
|
273
|
+
|
|
127
274
|
it('ignores duplicate and unknown onResult reports', async () => {
|
|
128
275
|
const completions: CompletionEvent[] = [];
|
|
129
276
|
|