@librechat/agents 3.3.3 → 3.3.4
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/graphs/Graph.cjs +342 -109
- 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/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 +37 -10
- 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 +64 -3
- 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/main.cjs +19 -2
- 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 +218 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +7 -3
- 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 +54 -41
- package/dist/cjs/tools/ToolNode.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/graphs/Graph.mjs +349 -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/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 +37 -10
- 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 +65 -5
- 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/main.mjs +7 -7
- 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 +218 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +7 -3
- 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 +54 -41
- package/dist/esm/tools/ToolNode.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/graphs/Graph.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +27 -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/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/prune.d.ts +16 -12
- package/dist/types/types/stream.d.ts +1 -1
- 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/graphs/Graph.ts +698 -200
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1128 -4
- 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 +49 -1
- package/src/llm/bedrock/utils/message_inputs.ts +86 -13
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +196 -2
- 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/messages/__tests__/observationMasking.test.ts +93 -2
- 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 +363 -91
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/prune.ts +996 -183
- 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.ts +23 -15
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +147 -68
- 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/toolOutputReferences.ts +6 -0
- package/src/types/stream.ts +1 -1
- 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/src/stream.ts
CHANGED
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
coerceRecordArgs,
|
|
27
27
|
normalizeError,
|
|
28
28
|
} from '@/tools/eagerEventExecution';
|
|
29
|
+
import {
|
|
30
|
+
serializeStructuredValueBounded,
|
|
31
|
+
serializeToolContentBounded,
|
|
32
|
+
} from '@/utils/toolContent';
|
|
29
33
|
import {
|
|
30
34
|
handleServerToolResult,
|
|
31
35
|
handleToolCallChunks,
|
|
@@ -841,15 +845,20 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
841
845
|
if (stepId === '') {
|
|
842
846
|
continue;
|
|
843
847
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
:
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
848
|
+
let output: string;
|
|
849
|
+
if (result.status === 'error') {
|
|
850
|
+
output = truncateToolResultContent(
|
|
851
|
+
`Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`,
|
|
852
|
+
maxToolResultChars
|
|
853
|
+
);
|
|
854
|
+
} else if (typeof result.content === 'string') {
|
|
855
|
+
output = truncateToolResultContent(result.content, maxToolResultChars);
|
|
856
|
+
} else {
|
|
857
|
+
output = serializeStructuredValueBounded(
|
|
858
|
+
result.content,
|
|
859
|
+
maxToolResultChars
|
|
860
|
+
).content;
|
|
861
|
+
}
|
|
853
862
|
|
|
854
863
|
try {
|
|
855
864
|
const dispatched = await safeDispatchCustomEvent(
|
|
@@ -861,7 +870,10 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
861
870
|
type: 'tool_call' as const,
|
|
862
871
|
eager: true,
|
|
863
872
|
tool_call: {
|
|
864
|
-
args:
|
|
873
|
+
args: serializeToolContentBounded(
|
|
874
|
+
record.request.args,
|
|
875
|
+
maxToolResultChars
|
|
876
|
+
),
|
|
865
877
|
name: record.toolName,
|
|
866
878
|
id: result.toolCallId,
|
|
867
879
|
output,
|
|
@@ -2248,11 +2260,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2248
2260
|
const contentIndex =
|
|
2249
2261
|
toolCallIndices[toolCallIndex] ?? runStep.index;
|
|
2250
2262
|
const toolCallId = toolCall.id ?? '';
|
|
2251
|
-
registerToolContentIndex(
|
|
2252
|
-
toolStepContent,
|
|
2253
|
-
contentIndex,
|
|
2254
|
-
toolCallId
|
|
2255
|
-
);
|
|
2263
|
+
registerToolContentIndex(toolStepContent, contentIndex, toolCallId);
|
|
2256
2264
|
const contentPart: t.MessageContentComplex = {
|
|
2257
2265
|
type: ContentTypes.TOOL_CALL,
|
|
2258
2266
|
tool_call: {
|
|
@@ -1045,6 +1045,83 @@ describe('recency window — first-turn protection', () => {
|
|
|
1045
1045
|
expect(tailToolMsg!.content).not.toContain('FULL_ORIGINAL_OUTPUT');
|
|
1046
1046
|
});
|
|
1047
1047
|
|
|
1048
|
+
it('bounds restored tool originals and preserves ToolMessage metadata', async () => {
|
|
1049
|
+
captureEvents();
|
|
1050
|
+
|
|
1051
|
+
let restoredToolMessage: ToolMessage | undefined;
|
|
1052
|
+
const invokeMock = jest.fn().mockImplementation((messages: unknown) => {
|
|
1053
|
+
restoredToolMessage = (messages as Array<unknown>).find(
|
|
1054
|
+
(message) => message instanceof ToolMessage
|
|
1055
|
+
) as ToolMessage | undefined;
|
|
1056
|
+
return Promise.resolve({ content: 'summary' });
|
|
1057
|
+
});
|
|
1058
|
+
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1059
|
+
class {
|
|
1060
|
+
constructor() {
|
|
1061
|
+
return { invoke: invokeMock };
|
|
1062
|
+
}
|
|
1063
|
+
} as never
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
const artifact = { source: 'clickhouse', rows: 1_000 };
|
|
1067
|
+
const originalToolMessage = new ToolMessage({
|
|
1068
|
+
content: 'masked-stub',
|
|
1069
|
+
tool_call_id: 'bounded',
|
|
1070
|
+
name: 'run_select_query',
|
|
1071
|
+
status: 'success',
|
|
1072
|
+
artifact,
|
|
1073
|
+
metadata: { traceId: 'trace-1' },
|
|
1074
|
+
additional_kwargs: { retained: true },
|
|
1075
|
+
response_metadata: { requestId: 'request-1' },
|
|
1076
|
+
});
|
|
1077
|
+
const agentContext = createAgentContext({
|
|
1078
|
+
maxContextTokens: 2_000,
|
|
1079
|
+
summarizationConfig: { retainRecent: { turns: 0 } },
|
|
1080
|
+
setSummary: jest.fn(),
|
|
1081
|
+
});
|
|
1082
|
+
agentContext.pendingOriginalToolContent = new Map([
|
|
1083
|
+
[2, `[{"rows":"${'x'.repeat(20_000)}"}]`],
|
|
1084
|
+
]);
|
|
1085
|
+
|
|
1086
|
+
const summarizeNode = createSummarizeNode({
|
|
1087
|
+
agentContext,
|
|
1088
|
+
graph: mockGraph() as never,
|
|
1089
|
+
generateStepId,
|
|
1090
|
+
});
|
|
1091
|
+
await summarizeNode(
|
|
1092
|
+
{
|
|
1093
|
+
messages: [
|
|
1094
|
+
new HumanMessage('query the table'),
|
|
1095
|
+
new AIMessage({
|
|
1096
|
+
content: '',
|
|
1097
|
+
tool_calls: [{ id: 'bounded', name: 'run_select_query', args: {} }],
|
|
1098
|
+
}),
|
|
1099
|
+
originalToolMessage,
|
|
1100
|
+
],
|
|
1101
|
+
summarizationRequest: {
|
|
1102
|
+
remainingContextTokens: 0,
|
|
1103
|
+
agentId: 'agent_0',
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
{} as RunnableConfig
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
expect(restoredToolMessage).toBeDefined();
|
|
1110
|
+
expect(typeof restoredToolMessage!.content).toBe('string');
|
|
1111
|
+
expect(String(restoredToolMessage!.content).length).toBeLessThanOrEqual(
|
|
1112
|
+
2_400
|
|
1113
|
+
);
|
|
1114
|
+
expect(restoredToolMessage!.content).toContain('[truncated:');
|
|
1115
|
+
expect(restoredToolMessage!.status).toBe('success');
|
|
1116
|
+
expect(restoredToolMessage!.artifact).toBe(artifact);
|
|
1117
|
+
expect(restoredToolMessage!.metadata).toEqual({ traceId: 'trace-1' });
|
|
1118
|
+
expect(restoredToolMessage!.additional_kwargs).toEqual({ retained: true });
|
|
1119
|
+
expect(restoredToolMessage!.response_metadata).toEqual({
|
|
1120
|
+
requestId: 'request-1',
|
|
1121
|
+
});
|
|
1122
|
+
expect(originalToolMessage.content).toBe('masked-stub');
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1048
1125
|
it('preserves tail-relevant pendingOriginalToolContent entries (reindexed) for future summaries', async () => {
|
|
1049
1126
|
captureEvents();
|
|
1050
1127
|
|
|
@@ -10,6 +10,12 @@ import type { AgentContext } from '@/agents/AgentContext';
|
|
|
10
10
|
import type { HookRegistry } from '@/hooks';
|
|
11
11
|
import type { OnChunk } from '@/llm/invoke';
|
|
12
12
|
import type * as t from '@/types';
|
|
13
|
+
import {
|
|
14
|
+
cloneToolMessageWithContent,
|
|
15
|
+
compactToolContent,
|
|
16
|
+
isComputerCallOutputMessage,
|
|
17
|
+
serializeToolContentBounded,
|
|
18
|
+
} from '@/utils/toolContent';
|
|
13
19
|
import {
|
|
14
20
|
addTailCacheControl,
|
|
15
21
|
resolvePromptCacheTtl,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
} from '@/common';
|
|
29
35
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
30
36
|
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
37
|
+
import { calculateMaxToolResultChars } from '@/utils/truncation';
|
|
31
38
|
import { createRemoveAllMessage } from '@/messages/reducer';
|
|
32
39
|
import { getMaxOutputTokensKey } from '@/llm/request';
|
|
33
40
|
import { initializeModel } from '@/llm/init';
|
|
@@ -206,10 +213,10 @@ function extractToolFailuresSection(messages: BaseMessage[]): string {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
const toolName = toolMsg.name ?? 'tool';
|
|
209
|
-
const content =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
const content = serializeToolContentBounded(
|
|
217
|
+
toolMsg.content,
|
|
218
|
+
MAX_TOOL_FAILURE_CHARS * 4
|
|
219
|
+
);
|
|
213
220
|
const normalized = content.replace(/\s+/g, ' ').trim();
|
|
214
221
|
const summary =
|
|
215
222
|
normalized.length > MAX_TOOL_FAILURE_CHARS
|
|
@@ -250,25 +257,45 @@ function enrichSummary(summaryText: string, messages: BaseMessage[]): string {
|
|
|
250
257
|
*/
|
|
251
258
|
function restoreOriginalToolContent(
|
|
252
259
|
messages: BaseMessage[],
|
|
253
|
-
originalToolContent: Map<number, string> | undefined
|
|
260
|
+
originalToolContent: Map<number, string> | undefined,
|
|
261
|
+
maxContextTokens?: number
|
|
254
262
|
): BaseMessage[] {
|
|
255
263
|
if (originalToolContent == null || originalToolContent.size === 0) {
|
|
256
264
|
return messages;
|
|
257
265
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
|
|
267
|
+
const restorable: Array<{
|
|
268
|
+
index: number;
|
|
269
|
+
message: ToolMessage;
|
|
270
|
+
content: string;
|
|
271
|
+
}> = [];
|
|
272
|
+
for (const [index, content] of originalToolContent) {
|
|
273
|
+
const message = messages[index];
|
|
274
|
+
if (
|
|
275
|
+
message instanceof ToolMessage &&
|
|
276
|
+
!isComputerCallOutputMessage(message)
|
|
277
|
+
) {
|
|
278
|
+
restorable.push({ index, message, content });
|
|
270
279
|
}
|
|
271
280
|
}
|
|
281
|
+
if (restorable.length === 0) {
|
|
282
|
+
return messages;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Restored originals improve checkpoint quality, but they still feed a
|
|
287
|
+
* provider call. Share one tool-result budget across every restoration so
|
|
288
|
+
* several previously masked results cannot overflow the summarizer.
|
|
289
|
+
*/
|
|
290
|
+
let remainingChars = calculateMaxToolResultChars(maxContextTokens);
|
|
291
|
+
const restored = [...messages];
|
|
292
|
+
for (let i = 0; i < restorable.length; i++) {
|
|
293
|
+
const { index, message, content } = restorable[i];
|
|
294
|
+
const maxChars = Math.floor(remainingChars / (restorable.length - i));
|
|
295
|
+
const compacted = compactToolContent(content, maxChars).content;
|
|
296
|
+
restored[index] = cloneToolMessageWithContent(message, compacted);
|
|
297
|
+
remainingChars -= serializeToolContentBounded(compacted, maxChars).length;
|
|
298
|
+
}
|
|
272
299
|
return restored;
|
|
273
300
|
}
|
|
274
301
|
|
|
@@ -845,7 +872,8 @@ export function createSummarizeNode({
|
|
|
845
872
|
|
|
846
873
|
const restoredMessages = restoreOriginalToolContent(
|
|
847
874
|
state.messages,
|
|
848
|
-
originalPending
|
|
875
|
+
originalPending,
|
|
876
|
+
agentContext.maxContextTokens
|
|
849
877
|
);
|
|
850
878
|
|
|
851
879
|
const runnableConfig = config ?? graph.config;
|
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,
|
|
@@ -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,
|
|
@@ -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),
|
|
@@ -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,
|