@librechat/agents 3.3.2 → 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/langfuse.cjs +6 -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 +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/langfuse.mjs +6 -0
- 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 +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 +13 -9
- 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/langfuse.ts +15 -0
- 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/langfuse-callbacks.test.ts +203 -1
- 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/messages/format.ts
CHANGED
|
@@ -27,8 +27,15 @@ import type {
|
|
|
27
27
|
TPayload,
|
|
28
28
|
TMessage,
|
|
29
29
|
} from '@/types';
|
|
30
|
+
import {
|
|
31
|
+
compactToolContent,
|
|
32
|
+
getToolContentCharLength,
|
|
33
|
+
isAtomicToolContentBlock,
|
|
34
|
+
serializeStructuredValueBounded,
|
|
35
|
+
} from '@/utils/toolContent';
|
|
30
36
|
import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
|
|
31
37
|
import { toLangChainContent, toLangChainMessageFields } from './langchain';
|
|
38
|
+
import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
|
|
32
39
|
import { Providers, ContentTypes, Constants } from '@/common';
|
|
33
40
|
import { emitAgentLog } from '@/utils/events';
|
|
34
41
|
|
|
@@ -459,6 +466,15 @@ function hasToolCallOutput(part: MessageContentComplex): boolean {
|
|
|
459
466
|
return output != null && output !== '';
|
|
460
467
|
}
|
|
461
468
|
|
|
469
|
+
function formatToolCallOutput(
|
|
470
|
+
output: ToolCallPart['output'] | undefined
|
|
471
|
+
): MessageContent {
|
|
472
|
+
if (output == null) {
|
|
473
|
+
return '';
|
|
474
|
+
}
|
|
475
|
+
return compactToolContent(output, HARD_MAX_TOOL_RESULT_CHARS).content;
|
|
476
|
+
}
|
|
477
|
+
|
|
462
478
|
/**
|
|
463
479
|
* Helper function to format an assistant message
|
|
464
480
|
* @param message The message to format
|
|
@@ -718,7 +734,7 @@ function formatAssistantMessage(
|
|
|
718
734
|
new ToolMessage({
|
|
719
735
|
tool_call_id: tool_call.id ?? '',
|
|
720
736
|
name: tool_call.name,
|
|
721
|
-
content: output
|
|
737
|
+
content: formatToolCallOutput(output),
|
|
722
738
|
}),
|
|
723
739
|
'tool'
|
|
724
740
|
)
|
|
@@ -1250,7 +1266,11 @@ function contentPartCharLength(part: MessageContentComplex): number {
|
|
|
1250
1266
|
if (typeof input === 'string') {
|
|
1251
1267
|
len += input.length;
|
|
1252
1268
|
} else if (input != null && typeof input === 'object') {
|
|
1253
|
-
|
|
1269
|
+
const measured = serializeStructuredValueBounded(input, 0).originalChars;
|
|
1270
|
+
len +=
|
|
1271
|
+
measured === Number.MAX_SAFE_INTEGER
|
|
1272
|
+
? HARD_MAX_TOOL_RESULT_CHARS
|
|
1273
|
+
: Math.min(measured, HARD_MAX_TOOL_RESULT_CHARS);
|
|
1254
1274
|
}
|
|
1255
1275
|
return len;
|
|
1256
1276
|
}
|
|
@@ -1689,7 +1709,14 @@ export const formatAgentMessages = (
|
|
|
1689
1709
|
if (typeof args === 'string') {
|
|
1690
1710
|
len += args.length;
|
|
1691
1711
|
} else if (args != null) {
|
|
1692
|
-
|
|
1712
|
+
const measured = serializeStructuredValueBounded(
|
|
1713
|
+
args,
|
|
1714
|
+
0
|
|
1715
|
+
).originalChars;
|
|
1716
|
+
len +=
|
|
1717
|
+
measured === Number.MAX_SAFE_INTEGER
|
|
1718
|
+
? HARD_MAX_TOOL_RESULT_CHARS
|
|
1719
|
+
: Math.min(measured, HARD_MAX_TOOL_RESULT_CHARS);
|
|
1693
1720
|
}
|
|
1694
1721
|
}
|
|
1695
1722
|
}
|
|
@@ -1754,13 +1781,156 @@ export function shiftIndexTokenCountMap(
|
|
|
1754
1781
|
return shiftedMap;
|
|
1755
1782
|
}
|
|
1756
1783
|
|
|
1757
|
-
/** Block types that contain binary image data and must be preserved structurally. */
|
|
1758
|
-
const IMAGE_BLOCK_TYPES = new Set(['image_url', 'image']);
|
|
1759
|
-
|
|
1760
1784
|
/** Checks whether a BaseMessage is a tool-role message. */
|
|
1761
1785
|
const isToolMessage = (m: BaseMessage): boolean =>
|
|
1762
1786
|
m instanceof ToolMessage || ('role' in m && (m as any).role === 'tool');
|
|
1763
1787
|
|
|
1788
|
+
const PORTABLE_FOLDED_MEDIA_TYPES = new Set(['image', 'image_url']);
|
|
1789
|
+
const MAX_FOLDED_BLOCK_CHARS = 8_000;
|
|
1790
|
+
const MAX_FOLDED_CONTEXT_CHARS = HARD_MAX_TOOL_RESULT_CHARS;
|
|
1791
|
+
const MAX_FOLDED_CONTEXT_WORK = 100_000;
|
|
1792
|
+
const FOLDED_CONTEXT_TRUNCATION_NOTICE =
|
|
1793
|
+
'… [additional folded context omitted]';
|
|
1794
|
+
const syntheticProviderContextMessages = new WeakSet<BaseMessage>();
|
|
1795
|
+
|
|
1796
|
+
type FoldContextBudget = {
|
|
1797
|
+
remainingChars: number;
|
|
1798
|
+
remainingWork: number;
|
|
1799
|
+
truncated: boolean;
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
function createFoldContextBudget(): FoldContextBudget {
|
|
1803
|
+
return {
|
|
1804
|
+
remainingChars: MAX_FOLDED_CONTEXT_CHARS,
|
|
1805
|
+
remainingWork: MAX_FOLDED_CONTEXT_WORK,
|
|
1806
|
+
truncated: false,
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function readFoldedDataProperty(value: unknown, key: string): unknown {
|
|
1811
|
+
if (value == null || typeof value !== 'object') {
|
|
1812
|
+
return undefined;
|
|
1813
|
+
}
|
|
1814
|
+
try {
|
|
1815
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1816
|
+
return descriptor != null && 'value' in descriptor
|
|
1817
|
+
? descriptor.value
|
|
1818
|
+
: undefined;
|
|
1819
|
+
} catch {
|
|
1820
|
+
return undefined;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Adds one logical line without first concatenating caller-controlled strings.
|
|
1826
|
+
* The shared budget covers every synthetic message emitted by one transform,
|
|
1827
|
+
* so many individually bounded blocks cannot build an unbounded aggregate.
|
|
1828
|
+
*/
|
|
1829
|
+
function appendFoldedLine(
|
|
1830
|
+
textChunks: string[],
|
|
1831
|
+
budget: FoldContextBudget,
|
|
1832
|
+
pieces: readonly string[]
|
|
1833
|
+
): boolean {
|
|
1834
|
+
if (budget.remainingChars <= 0) {
|
|
1835
|
+
budget.truncated = true;
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
const separatorChars = textChunks.length > 0 ? 1 : 0;
|
|
1840
|
+
const available = budget.remainingChars - separatorChars;
|
|
1841
|
+
if (available <= 0) {
|
|
1842
|
+
budget.remainingChars = 0;
|
|
1843
|
+
budget.truncated = true;
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
let totalChars = 0;
|
|
1848
|
+
for (const piece of pieces) {
|
|
1849
|
+
totalChars = Math.min(Number.MAX_SAFE_INTEGER, totalChars + piece.length);
|
|
1850
|
+
}
|
|
1851
|
+
if (totalChars <= available) {
|
|
1852
|
+
const line = pieces.join('');
|
|
1853
|
+
textChunks.push(line);
|
|
1854
|
+
budget.remainingChars -= separatorChars + line.length;
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
const notice = FOLDED_CONTEXT_TRUNCATION_NOTICE.slice(0, available);
|
|
1859
|
+
let remainingHeadChars = Math.max(0, available - notice.length);
|
|
1860
|
+
const boundedPieces: string[] = [];
|
|
1861
|
+
for (const piece of pieces) {
|
|
1862
|
+
if (remainingHeadChars <= 0) {
|
|
1863
|
+
break;
|
|
1864
|
+
}
|
|
1865
|
+
const retained = piece.slice(0, remainingHeadChars);
|
|
1866
|
+
boundedPieces.push(retained);
|
|
1867
|
+
remainingHeadChars -= retained.length;
|
|
1868
|
+
}
|
|
1869
|
+
boundedPieces.push(notice);
|
|
1870
|
+
textChunks.push(boundedPieces.join(''));
|
|
1871
|
+
budget.remainingChars = 0;
|
|
1872
|
+
budget.truncated = true;
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function consumeFoldedWork(
|
|
1877
|
+
textChunks: string[],
|
|
1878
|
+
budget: FoldContextBudget
|
|
1879
|
+
): boolean {
|
|
1880
|
+
if (budget.remainingChars <= 0) {
|
|
1881
|
+
return false;
|
|
1882
|
+
}
|
|
1883
|
+
if (budget.remainingWork-- > 0) {
|
|
1884
|
+
return true;
|
|
1885
|
+
}
|
|
1886
|
+
appendFoldedLine(textChunks, budget, [FOLDED_CONTEXT_TRUNCATION_NOTICE]);
|
|
1887
|
+
budget.remainingChars = 0;
|
|
1888
|
+
budget.truncated = true;
|
|
1889
|
+
return false;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function serializeFoldedValue(value: unknown): string {
|
|
1893
|
+
const compacted = compactToolContent(value, MAX_FOLDED_BLOCK_CHARS).content;
|
|
1894
|
+
return typeof compacted === 'string'
|
|
1895
|
+
? compacted
|
|
1896
|
+
: serializeStructuredValueBounded(compacted, MAX_FOLDED_BLOCK_CHARS)
|
|
1897
|
+
.content;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
function markSyntheticProviderContext<T extends BaseMessage>(message: T): T {
|
|
1901
|
+
syntheticProviderContextMessages.add(message);
|
|
1902
|
+
return message;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function appendSyntheticProviderContextMessage(
|
|
1906
|
+
result: BaseMessage[],
|
|
1907
|
+
parts: MessageContentComplex[]
|
|
1908
|
+
): boolean {
|
|
1909
|
+
if (parts.length === 0) {
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
result.push(
|
|
1913
|
+
markSyntheticProviderContext(
|
|
1914
|
+
withMessageRole(
|
|
1915
|
+
new HumanMessage({ content: toLangChainContent(parts) }),
|
|
1916
|
+
'user'
|
|
1917
|
+
)
|
|
1918
|
+
)
|
|
1919
|
+
);
|
|
1920
|
+
return true;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/**
|
|
1924
|
+
* Identifies provider-context placeholders created by this module without
|
|
1925
|
+
* trusting user-controlled content prefixes or leaking marker metadata onto
|
|
1926
|
+
* the provider wire.
|
|
1927
|
+
*/
|
|
1928
|
+
export function isSyntheticProviderContextMessage(
|
|
1929
|
+
message: BaseMessage
|
|
1930
|
+
): boolean {
|
|
1931
|
+
return syntheticProviderContextMessages.has(message);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1764
1934
|
/** Flushes accumulated text chunks into `parts` as a single text block. */
|
|
1765
1935
|
function flushTextChunks(
|
|
1766
1936
|
textChunks: string[],
|
|
@@ -1778,10 +1948,10 @@ function flushTextChunks(
|
|
|
1778
1948
|
|
|
1779
1949
|
/**
|
|
1780
1950
|
* Appends a single message's content to the running `textChunks` / `parts`
|
|
1781
|
-
* accumulators.
|
|
1782
|
-
* binary data
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1951
|
+
* accumulators. Portable image blocks are shallow-copied into `parts` so
|
|
1952
|
+
* binary data never becomes text tokens. Provider-specific media/resource
|
|
1953
|
+
* blocks are retained as bounded text so a folded cross-provider history
|
|
1954
|
+
* cannot contain an unsupported native block or JSON-expand without limit.
|
|
1785
1955
|
*
|
|
1786
1956
|
* When `content` is an array containing tool_use blocks, `tool_calls` is NOT
|
|
1787
1957
|
* additionally serialized (avoiding double output). `tool_calls` is used as
|
|
@@ -1791,37 +1961,69 @@ function appendMessageContent(
|
|
|
1791
1961
|
msg: BaseMessage,
|
|
1792
1962
|
role: string,
|
|
1793
1963
|
textChunks: string[],
|
|
1794
|
-
parts: MessageContentComplex[]
|
|
1964
|
+
parts: MessageContentComplex[],
|
|
1965
|
+
budget: FoldContextBudget
|
|
1795
1966
|
): void {
|
|
1796
1967
|
const { content } = msg;
|
|
1797
1968
|
|
|
1798
1969
|
if (typeof content === 'string') {
|
|
1799
|
-
if (content) {
|
|
1800
|
-
textChunks
|
|
1970
|
+
if (content && consumeFoldedWork(textChunks, budget)) {
|
|
1971
|
+
appendFoldedLine(textChunks, budget, [`${role}: `, content]);
|
|
1801
1972
|
}
|
|
1802
|
-
appendToolCalls(msg, role, textChunks);
|
|
1973
|
+
appendToolCalls(msg, role, textChunks, budget);
|
|
1803
1974
|
return;
|
|
1804
1975
|
}
|
|
1805
1976
|
|
|
1806
1977
|
if (!Array.isArray(content)) {
|
|
1807
|
-
appendToolCalls(msg, role, textChunks);
|
|
1978
|
+
appendToolCalls(msg, role, textChunks, budget);
|
|
1808
1979
|
return;
|
|
1809
1980
|
}
|
|
1810
1981
|
|
|
1811
1982
|
let hasToolUseBlock = false;
|
|
1812
1983
|
|
|
1813
1984
|
for (const block of content as ExtendedMessageContent[]) {
|
|
1814
|
-
if (
|
|
1815
|
-
|
|
1816
|
-
|
|
1985
|
+
if (!consumeFoldedWork(textChunks, budget)) {
|
|
1986
|
+
hasToolUseBlock = true;
|
|
1987
|
+
break;
|
|
1988
|
+
}
|
|
1989
|
+
const blockTypeValue = readFoldedDataProperty(block, 'type');
|
|
1990
|
+
const blockType =
|
|
1991
|
+
typeof blockTypeValue === 'string' ? blockTypeValue : undefined;
|
|
1992
|
+
|
|
1993
|
+
if (
|
|
1994
|
+
blockType !== 'tool_use' &&
|
|
1995
|
+
blockType !== 'tool_call' &&
|
|
1996
|
+
blockType !== 'tool_result' &&
|
|
1997
|
+
isAtomicToolContentBlock(block)
|
|
1998
|
+
) {
|
|
1999
|
+
if (PORTABLE_FOLDED_MEDIA_TYPES.has(blockType ?? '')) {
|
|
2000
|
+
const blockChars = getToolContentCharLength([block]);
|
|
2001
|
+
if (blockChars > budget.remainingChars) {
|
|
2002
|
+
appendFoldedLine(textChunks, budget, [
|
|
2003
|
+
`${role}: [${blockType ?? 'media'} omitted: folded context limit]`,
|
|
2004
|
+
]);
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
flushTextChunks(textChunks, parts);
|
|
2008
|
+
parts.push({ ...block } as MessageContentComplex);
|
|
2009
|
+
budget.remainingChars -= blockChars;
|
|
2010
|
+
} else {
|
|
2011
|
+
appendFoldedLine(textChunks, budget, [
|
|
2012
|
+
`${role}: [${blockType ?? 'media'}] `,
|
|
2013
|
+
serializeFoldedValue(block),
|
|
2014
|
+
]);
|
|
2015
|
+
}
|
|
1817
2016
|
continue;
|
|
1818
2017
|
}
|
|
1819
2018
|
|
|
1820
|
-
if (
|
|
2019
|
+
if (blockType === 'tool_use') {
|
|
1821
2020
|
hasToolUseBlock = true;
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2021
|
+
const name = readFoldedDataProperty(block, 'name');
|
|
2022
|
+
const input = readFoldedDataProperty(block, 'input');
|
|
2023
|
+
appendFoldedLine(textChunks, budget, [
|
|
2024
|
+
`${role}: [tool_use] ${typeof name === 'string' ? name : ''} `,
|
|
2025
|
+
serializeFoldedValue(input ?? {}),
|
|
2026
|
+
]);
|
|
1825
2027
|
continue;
|
|
1826
2028
|
}
|
|
1827
2029
|
|
|
@@ -1830,17 +2032,32 @@ function appendMessageContent(
|
|
|
1830
2032
|
// toolUse) or this repo's `ToolCallContent` (`{ tool_call: { name, args,
|
|
1831
2033
|
// output } }`, from `convertMessagesToContent` / persisted history). Handle
|
|
1832
2034
|
// both, and emit any embedded output, so the name/args/result survive.
|
|
1833
|
-
if (
|
|
2035
|
+
if (blockType === 'tool_call') {
|
|
1834
2036
|
hasToolUseBlock = true;
|
|
1835
|
-
const nested = (block
|
|
1836
|
-
const
|
|
1837
|
-
const
|
|
2037
|
+
const nested = readFoldedDataProperty(block, 'tool_call');
|
|
2038
|
+
const nestedName = readFoldedDataProperty(nested, 'name');
|
|
2039
|
+
const topLevelName = readFoldedDataProperty(block, 'name');
|
|
2040
|
+
let name = '';
|
|
2041
|
+
if (typeof nestedName === 'string') {
|
|
2042
|
+
name = nestedName;
|
|
2043
|
+
} else if (typeof topLevelName === 'string') {
|
|
2044
|
+
name = topLevelName;
|
|
2045
|
+
}
|
|
2046
|
+
const nestedArgs = readFoldedDataProperty(nested, 'args');
|
|
2047
|
+
const topLevelArgs = readFoldedDataProperty(block, 'args');
|
|
2048
|
+
const rawArgs = nestedArgs ?? topLevelArgs ?? {};
|
|
1838
2049
|
const argsText =
|
|
1839
|
-
typeof rawArgs === 'string' ? rawArgs :
|
|
1840
|
-
textChunks
|
|
1841
|
-
|
|
2050
|
+
typeof rawArgs === 'string' ? rawArgs : serializeFoldedValue(rawArgs);
|
|
2051
|
+
appendFoldedLine(textChunks, budget, [
|
|
2052
|
+
`${role}: [tool_use] ${name}${argsText ? ' ' : ''}`,
|
|
2053
|
+
argsText,
|
|
2054
|
+
]);
|
|
2055
|
+
const output = readFoldedDataProperty(nested, 'output');
|
|
1842
2056
|
if (output != null && output !== '') {
|
|
1843
|
-
textChunks
|
|
2057
|
+
appendFoldedLine(textChunks, budget, [
|
|
2058
|
+
'Tool: ',
|
|
2059
|
+
typeof output === 'string' ? output : serializeFoldedValue(output),
|
|
2060
|
+
]);
|
|
1844
2061
|
}
|
|
1845
2062
|
continue;
|
|
1846
2063
|
}
|
|
@@ -1848,65 +2065,101 @@ function appendMessageContent(
|
|
|
1848
2065
|
// A `tool_result` content block (e.g. an AIMessage(tool_call) followed by a
|
|
1849
2066
|
// user message carrying the result). Preserve nested image blocks as-is
|
|
1850
2067
|
// instead of JSON-stringifying them through the generic fallback.
|
|
1851
|
-
if (
|
|
2068
|
+
if (blockType === 'tool_result') {
|
|
1852
2069
|
hasToolUseBlock = true;
|
|
1853
|
-
const inner = (block
|
|
1854
|
-
|
|
2070
|
+
const inner = readFoldedDataProperty(block, 'content') as
|
|
2071
|
+
| ToolResultContent['content']
|
|
2072
|
+
| undefined;
|
|
1855
2073
|
if (typeof inner === 'string') {
|
|
1856
2074
|
if (inner) {
|
|
1857
|
-
textChunks
|
|
2075
|
+
appendFoldedLine(textChunks, budget, [
|
|
2076
|
+
`${role}: [tool_result] `,
|
|
2077
|
+
inner,
|
|
2078
|
+
]);
|
|
1858
2079
|
}
|
|
1859
2080
|
} else if (Array.isArray(inner)) {
|
|
1860
2081
|
for (const innerBlock of inner as Array<
|
|
1861
2082
|
string | ExtendedMessageContent
|
|
1862
2083
|
>) {
|
|
2084
|
+
if (!consumeFoldedWork(textChunks, budget)) {
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
1863
2087
|
if (typeof innerBlock === 'string') {
|
|
1864
2088
|
if (innerBlock) {
|
|
1865
|
-
textChunks
|
|
2089
|
+
appendFoldedLine(textChunks, budget, [
|
|
2090
|
+
`${role}: [tool_result] `,
|
|
2091
|
+
innerBlock,
|
|
2092
|
+
]);
|
|
1866
2093
|
}
|
|
1867
|
-
} else if (IMAGE_BLOCK_TYPES.has(innerBlock.type ?? '')) {
|
|
1868
|
-
flushTextChunks(textChunks, parts);
|
|
1869
|
-
parts.push({ ...innerBlock } as MessageContentComplex);
|
|
1870
2094
|
} else {
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
2095
|
+
const innerTypeValue = readFoldedDataProperty(innerBlock, 'type');
|
|
2096
|
+
const innerType =
|
|
2097
|
+
typeof innerTypeValue === 'string' ? innerTypeValue : undefined;
|
|
2098
|
+
if (
|
|
2099
|
+
isAtomicToolContentBlock(innerBlock) &&
|
|
2100
|
+
PORTABLE_FOLDED_MEDIA_TYPES.has(innerType ?? '')
|
|
2101
|
+
) {
|
|
2102
|
+
const blockChars = getToolContentCharLength([innerBlock]);
|
|
2103
|
+
if (blockChars <= budget.remainingChars) {
|
|
2104
|
+
flushTextChunks(textChunks, parts);
|
|
2105
|
+
parts.push({ ...innerBlock } as MessageContentComplex);
|
|
2106
|
+
budget.remainingChars -= blockChars;
|
|
2107
|
+
} else {
|
|
2108
|
+
appendFoldedLine(textChunks, budget, [
|
|
2109
|
+
`${role}: [${innerType ?? 'media'} omitted: folded context limit]`,
|
|
2110
|
+
]);
|
|
2111
|
+
}
|
|
2112
|
+
} else {
|
|
2113
|
+
const textValue = readFoldedDataProperty(innerBlock, 'text');
|
|
2114
|
+
const inputValue = readFoldedDataProperty(innerBlock, 'input');
|
|
2115
|
+
const innerText = textValue ?? inputValue;
|
|
2116
|
+
appendFoldedLine(textChunks, budget, [
|
|
2117
|
+
`${role}: [tool_result] `,
|
|
1874
2118
|
typeof innerText === 'string' && innerText
|
|
1875
2119
|
? innerText
|
|
1876
|
-
:
|
|
1877
|
-
|
|
1878
|
-
|
|
2120
|
+
: serializeFoldedValue(innerBlock),
|
|
2121
|
+
]);
|
|
2122
|
+
}
|
|
1879
2123
|
}
|
|
1880
2124
|
}
|
|
1881
2125
|
} else if (inner != null) {
|
|
1882
|
-
textChunks
|
|
2126
|
+
appendFoldedLine(textChunks, budget, [
|
|
2127
|
+
`${role}: [tool_result] `,
|
|
2128
|
+
serializeFoldedValue(inner),
|
|
2129
|
+
]);
|
|
1883
2130
|
}
|
|
1884
2131
|
continue;
|
|
1885
2132
|
}
|
|
1886
2133
|
|
|
1887
|
-
const text =
|
|
2134
|
+
const text =
|
|
2135
|
+
readFoldedDataProperty(block, 'text') ??
|
|
2136
|
+
readFoldedDataProperty(block, 'input');
|
|
1888
2137
|
if (typeof text === 'string' && text) {
|
|
1889
|
-
textChunks
|
|
2138
|
+
appendFoldedLine(textChunks, budget, [`${role}: `, text]);
|
|
1890
2139
|
continue;
|
|
1891
2140
|
}
|
|
1892
2141
|
|
|
1893
2142
|
// Fallback: serialize unrecognized block types to preserve context
|
|
1894
|
-
if (
|
|
1895
|
-
textChunks
|
|
2143
|
+
if (blockType != null && blockType !== '') {
|
|
2144
|
+
appendFoldedLine(textChunks, budget, [
|
|
2145
|
+
`${role}: [${blockType}] `,
|
|
2146
|
+
serializeFoldedValue(block),
|
|
2147
|
+
]);
|
|
1896
2148
|
}
|
|
1897
2149
|
}
|
|
1898
2150
|
|
|
1899
2151
|
// If content array had no tool_use blocks, fall back to tool_calls metadata
|
|
1900
2152
|
// (handles edge case: empty content array with tool_calls populated)
|
|
1901
2153
|
if (!hasToolUseBlock) {
|
|
1902
|
-
appendToolCalls(msg, role, textChunks);
|
|
2154
|
+
appendToolCalls(msg, role, textChunks, budget);
|
|
1903
2155
|
}
|
|
1904
2156
|
}
|
|
1905
2157
|
|
|
1906
2158
|
function appendToolCalls(
|
|
1907
2159
|
msg: BaseMessage,
|
|
1908
2160
|
role: string,
|
|
1909
|
-
textChunks: string[]
|
|
2161
|
+
textChunks: string[],
|
|
2162
|
+
budget: FoldContextBudget
|
|
1910
2163
|
): void {
|
|
1911
2164
|
if (role !== 'AI') {
|
|
1912
2165
|
return;
|
|
@@ -1914,7 +2167,16 @@ function appendToolCalls(
|
|
|
1914
2167
|
const aiMsg = msg as AIMessage;
|
|
1915
2168
|
if (aiMsg.tool_calls && aiMsg.tool_calls.length > 0) {
|
|
1916
2169
|
for (const tc of aiMsg.tool_calls) {
|
|
1917
|
-
|
|
2170
|
+
if (!consumeFoldedWork(textChunks, budget)) {
|
|
2171
|
+
break;
|
|
2172
|
+
}
|
|
2173
|
+
const name = readFoldedDataProperty(tc, 'name');
|
|
2174
|
+
const args = readFoldedDataProperty(tc, 'args');
|
|
2175
|
+
appendFoldedLine(textChunks, budget, [
|
|
2176
|
+
`AI: [tool_call] ${typeof name === 'string' ? name : ''}(`,
|
|
2177
|
+
serializeFoldedValue(args),
|
|
2178
|
+
')',
|
|
2179
|
+
]);
|
|
1918
2180
|
}
|
|
1919
2181
|
return;
|
|
1920
2182
|
}
|
|
@@ -1924,14 +2186,20 @@ function appendToolCalls(
|
|
|
1924
2186
|
return;
|
|
1925
2187
|
}
|
|
1926
2188
|
for (const tc of rawToolCalls) {
|
|
1927
|
-
|
|
1928
|
-
|
|
2189
|
+
if (!consumeFoldedWork(textChunks, budget)) {
|
|
2190
|
+
break;
|
|
2191
|
+
}
|
|
2192
|
+
const fn = readFoldedDataProperty(tc, 'function');
|
|
1929
2193
|
if (fn == null) {
|
|
1930
2194
|
continue;
|
|
1931
2195
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2196
|
+
const name = readFoldedDataProperty(fn, 'name');
|
|
2197
|
+
const args = readFoldedDataProperty(fn, 'arguments');
|
|
2198
|
+
appendFoldedLine(textChunks, budget, [
|
|
2199
|
+
`AI: [tool_call] ${typeof name === 'string' ? name : ''}(`,
|
|
2200
|
+
typeof args === 'string' ? args : '',
|
|
2201
|
+
')',
|
|
2202
|
+
]);
|
|
1935
2203
|
}
|
|
1936
2204
|
}
|
|
1937
2205
|
|
|
@@ -1991,6 +2259,7 @@ export function ensureThinkingBlockInMessages(
|
|
|
1991
2259
|
|
|
1992
2260
|
const result: BaseMessage[] =
|
|
1993
2261
|
lastHumanIndex >= 0 ? messages.slice(0, lastHumanIndex + 1) : [];
|
|
2262
|
+
const foldBudget = createFoldContextBudget();
|
|
1994
2263
|
let i = lastHumanIndex + 1;
|
|
1995
2264
|
|
|
1996
2265
|
while (i < messages.length) {
|
|
@@ -2021,13 +2290,14 @@ export function ensureThinkingBlockInMessages(
|
|
|
2021
2290
|
if (typeof c !== 'object') {
|
|
2022
2291
|
continue;
|
|
2023
2292
|
}
|
|
2024
|
-
|
|
2293
|
+
const type = readFoldedDataProperty(c, 'type');
|
|
2294
|
+
if (type === 'tool_use') {
|
|
2025
2295
|
hasToolUse = true;
|
|
2026
2296
|
} else if (
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2297
|
+
type === ContentTypes.THINKING ||
|
|
2298
|
+
type === ContentTypes.REASONING_CONTENT ||
|
|
2299
|
+
type === ContentTypes.REASONING ||
|
|
2300
|
+
type === 'redacted_thinking'
|
|
2031
2301
|
) {
|
|
2032
2302
|
hasThinkingBlock = true;
|
|
2033
2303
|
}
|
|
@@ -2082,30 +2352,33 @@ export function ensureThinkingBlockInMessages(
|
|
|
2082
2352
|
// ToolMessages — preserves image blocks as-is to avoid serializing
|
|
2083
2353
|
// binary data as text (which caused 174× token amplification).
|
|
2084
2354
|
const parts: MessageContentComplex[] = [];
|
|
2085
|
-
const textChunks: string[] = [
|
|
2355
|
+
const textChunks: string[] = [];
|
|
2356
|
+
appendFoldedLine(textChunks, foldBudget, ['[Previous agent context]']);
|
|
2086
2357
|
|
|
2087
|
-
appendMessageContent(msg, 'AI', textChunks, parts);
|
|
2358
|
+
appendMessageContent(msg, 'AI', textChunks, parts, foldBudget);
|
|
2088
2359
|
|
|
2089
2360
|
let j = i + 1;
|
|
2090
2361
|
while (j < messages.length && isToolMessage(messages[j])) {
|
|
2091
|
-
appendMessageContent(
|
|
2362
|
+
appendMessageContent(
|
|
2363
|
+
messages[j],
|
|
2364
|
+
'Tool',
|
|
2365
|
+
textChunks,
|
|
2366
|
+
parts,
|
|
2367
|
+
foldBudget
|
|
2368
|
+
);
|
|
2092
2369
|
j++;
|
|
2093
2370
|
}
|
|
2094
2371
|
|
|
2095
2372
|
flushTextChunks(textChunks, parts);
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
new HumanMessage({ content: toLangChainContent(parts) }),
|
|
2106
|
-
'user'
|
|
2107
|
-
)
|
|
2108
|
-
);
|
|
2373
|
+
if (appendSyntheticProviderContextMessage(result, parts)) {
|
|
2374
|
+
emitAgentLog(
|
|
2375
|
+
config,
|
|
2376
|
+
'warn',
|
|
2377
|
+
'format',
|
|
2378
|
+
'ensureThinkingBlockInMessages: injecting [Previous agent context] HumanMessage' +
|
|
2379
|
+
` (${parts.length} msgs at index ${i}, no thinking block in chain)`
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2109
2382
|
i = j;
|
|
2110
2383
|
} else {
|
|
2111
2384
|
// Keep the message as is
|
|
@@ -2140,11 +2413,10 @@ function messageHasToolContent(msg: BaseMessage): boolean {
|
|
|
2140
2413
|
}
|
|
2141
2414
|
if (Array.isArray(msg.content)) {
|
|
2142
2415
|
for (const block of msg.content as ExtendedMessageContent[]) {
|
|
2416
|
+
const type = readFoldedDataProperty(block, 'type');
|
|
2143
2417
|
if (
|
|
2144
2418
|
typeof block === 'object' &&
|
|
2145
|
-
(
|
|
2146
|
-
block.type === 'tool_call' ||
|
|
2147
|
-
block.type === 'tool_result')
|
|
2419
|
+
(type === 'tool_use' || type === 'tool_call' || type === 'tool_result')
|
|
2148
2420
|
) {
|
|
2149
2421
|
return true;
|
|
2150
2422
|
}
|
|
@@ -2164,7 +2436,9 @@ function isToolResultMessage(msg: BaseMessage): boolean {
|
|
|
2164
2436
|
}
|
|
2165
2437
|
if (Array.isArray(msg.content)) {
|
|
2166
2438
|
return (msg.content as ExtendedMessageContent[]).some(
|
|
2167
|
-
(block) =>
|
|
2439
|
+
(block) =>
|
|
2440
|
+
typeof block === 'object' &&
|
|
2441
|
+
readFoldedDataProperty(block, 'type') === 'tool_result'
|
|
2168
2442
|
);
|
|
2169
2443
|
}
|
|
2170
2444
|
return false;
|
|
@@ -2197,6 +2471,7 @@ export function foldToolBlocksForToollessAgent(
|
|
|
2197
2471
|
): BaseMessage[] {
|
|
2198
2472
|
let result: BaseMessage[] | null = null;
|
|
2199
2473
|
let foldedCount = 0;
|
|
2474
|
+
const foldBudget = createFoldContextBudget();
|
|
2200
2475
|
let i = 0;
|
|
2201
2476
|
while (i < messages.length) {
|
|
2202
2477
|
const msg = messages[i];
|
|
@@ -2212,29 +2487,26 @@ export function foldToolBlocksForToollessAgent(
|
|
|
2212
2487
|
}
|
|
2213
2488
|
|
|
2214
2489
|
const parts: MessageContentComplex[] = [];
|
|
2215
|
-
const textChunks: string[] = [
|
|
2490
|
+
const textChunks: string[] = [];
|
|
2491
|
+
appendFoldedLine(textChunks, foldBudget, ['[Previous tool interaction]']);
|
|
2216
2492
|
appendMessageContent(
|
|
2217
2493
|
msg,
|
|
2218
2494
|
isToolResultMessage(msg) ? 'Tool' : 'AI',
|
|
2219
2495
|
textChunks,
|
|
2220
|
-
parts
|
|
2496
|
+
parts,
|
|
2497
|
+
foldBudget
|
|
2221
2498
|
);
|
|
2222
2499
|
foldedCount++;
|
|
2223
2500
|
|
|
2224
2501
|
let j = i + 1;
|
|
2225
2502
|
while (j < messages.length && isToolResultMessage(messages[j])) {
|
|
2226
|
-
appendMessageContent(messages[j], 'Tool', textChunks, parts);
|
|
2503
|
+
appendMessageContent(messages[j], 'Tool', textChunks, parts, foldBudget);
|
|
2227
2504
|
foldedCount++;
|
|
2228
2505
|
j++;
|
|
2229
2506
|
}
|
|
2230
2507
|
|
|
2231
2508
|
flushTextChunks(textChunks, parts);
|
|
2232
|
-
result
|
|
2233
|
-
withMessageRole(
|
|
2234
|
-
new HumanMessage({ content: toLangChainContent(parts) }),
|
|
2235
|
-
'user'
|
|
2236
|
-
)
|
|
2237
|
-
);
|
|
2509
|
+
appendSyntheticProviderContextMessage(result, parts);
|
|
2238
2510
|
i = j;
|
|
2239
2511
|
}
|
|
2240
2512
|
|