@librechat/agents 3.6.16 → 3.7.1
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/_virtual/_rolldown/runtime.cjs +10 -0
- package/dist/cjs/agents/AgentContext.cjs +11 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +12 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/lazyRequire.cjs +33 -0
- package/dist/cjs/lazyRequire.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +13 -0
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +10 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +3 -2
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +10 -1
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +6 -4
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +56 -9
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/mistral/index.cjs +9 -0
- package/dist/cjs/llm/mistral/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +42 -2
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +12 -3
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +3 -2
- package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/providerRegistry.cjs +15 -3
- package/dist/cjs/llm/providerRegistry.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +18 -22
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/providers.eager.cjs +21 -0
- package/dist/cjs/llm/providers.eager.cjs.map +1 -0
- package/dist/cjs/llm/vertexai/index.cjs +17 -0
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -11
- package/dist/cjs/messages/content.cjs +8 -5
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +9 -4
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +15 -5
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +2 -1
- package/dist/cjs/messages/projectionInvariant.cjs +74 -0
- package/dist/cjs/messages/projectionInvariant.cjs.map +1 -0
- package/dist/cjs/messages/provenance.cjs +30 -8
- package/dist/cjs/messages/provenance.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +42 -8
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +202 -7
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/toolResultTypes.cjs +1 -0
- package/dist/cjs/run.cjs +7 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -23
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/deriveMessages.cjs +25 -0
- package/dist/cjs/session/deriveMessages.cjs.map +1 -0
- package/dist/cjs/session/index.cjs +1 -0
- package/dist/cjs/summarization/node.cjs +13 -6
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/Calculator.cjs +7 -4
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +65 -8
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +16 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/esm/_virtual/_rolldown/runtime.mjs +14 -0
- package/dist/esm/agents/AgentContext.mjs +11 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +12 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/lazyRequire.mjs +30 -0
- package/dist/esm/lazyRequire.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +6 -1
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +4 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +3 -2
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +4 -2
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +7 -5
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +56 -9
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/mistral/index.mjs +3 -1
- package/dist/esm/llm/mistral/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +32 -10
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +3 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +3 -2
- package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
- package/dist/esm/llm/providerRegistry.mjs +15 -3
- package/dist/esm/llm/providerRegistry.mjs.map +1 -1
- package/dist/esm/llm/providers.eager.mjs +22 -0
- package/dist/esm/llm/providers.eager.mjs.map +1 -0
- package/dist/esm/llm/providers.mjs +19 -23
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +8 -1
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +10 -11
- package/dist/esm/messages/content.mjs +8 -6
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +9 -5
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +16 -6
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +2 -1
- package/dist/esm/messages/projectionInvariant.mjs +72 -0
- package/dist/esm/messages/projectionInvariant.mjs.map +1 -0
- package/dist/esm/messages/provenance.mjs +30 -9
- package/dist/esm/messages/provenance.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +42 -9
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +201 -8
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/toolResultTypes.mjs +1 -1
- package/dist/esm/run.mjs +8 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -23
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/deriveMessages.mjs +25 -0
- package/dist/esm/session/deriveMessages.mjs.map +1 -0
- package/dist/esm/session/index.mjs +1 -0
- package/dist/esm/summarization/node.mjs +14 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/Calculator.mjs +7 -2
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +65 -8
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +15 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +6 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/lazyRequire.d.ts +8 -0
- package/dist/types/llm/providerRegistry.d.ts +11 -0
- package/dist/types/llm/providers.eager.d.ts +1 -0
- package/dist/types/messages/content.d.ts +4 -1
- package/dist/types/messages/core.d.ts +2 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/messages/projectionInvariant.d.ts +25 -0
- package/dist/types/messages/provenance.d.ts +10 -0
- package/dist/types/messages/prune.d.ts +5 -4
- package/dist/types/messages/recency.d.ts +30 -18
- package/dist/types/session/deriveMessages.d.ts +11 -0
- package/dist/types/session/index.d.ts +2 -0
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +13 -1
- package/dist/types/types/graph.d.ts +1 -1
- package/dist/types/types/subagentTasks.d.ts +22 -0
- package/dist/types/types/summarize.d.ts +15 -13
- package/dist/types/utils/llm.d.ts +10 -0
- package/package.json +60 -1
- package/src/agents/AgentContext.ts +19 -5
- package/src/graphs/Graph.ts +11 -9
- package/src/index.ts +3 -3
- package/src/lazyRequire.ts +67 -0
- package/src/llm/bedrock/toolCache.ts +6 -2
- package/src/llm/init.ts +15 -8
- package/src/llm/invoke.ts +102 -23
- package/src/llm/openrouter/toolCache.ts +8 -2
- package/src/llm/providerRegistry.ts +41 -1
- package/src/llm/providers.eager.ts +24 -0
- package/src/llm/providers.ts +63 -49
- package/src/messages/content.ts +20 -10
- package/src/messages/core.ts +16 -11
- package/src/messages/format.ts +29 -5
- package/src/messages/index.ts +1 -0
- package/src/messages/projectionInvariant.ts +134 -0
- package/src/messages/provenance.ts +60 -18
- package/src/messages/prune.ts +86 -11
- package/src/messages/recency.ts +429 -27
- package/src/run.ts +14 -6
- package/src/session/AgentSession.ts +4 -30
- package/src/session/deriveMessages.ts +37 -0
- package/src/session/index.ts +2 -0
- package/src/summarization/node.ts +37 -15
- package/src/tools/Calculator.ts +9 -2
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +139 -6
- package/src/types/graph.ts +1 -0
- package/src/types/subagentTasks.ts +28 -0
- package/src/types/summarize.ts +15 -13
- package/src/utils/llm.ts +39 -0
package/src/messages/prune.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { resolveContextPruningSettings } from './contextPruningSettings';
|
|
|
31
31
|
import { hasUnsafeStructuredSerialization } from '@/utils/tokens';
|
|
32
32
|
import { ContentTypes, Providers, Constants } from '@/common';
|
|
33
33
|
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
34
|
+
import { dropIncompleteToolStreamContent } from './core';
|
|
34
35
|
import { applyContextPruning } from './contextPruning';
|
|
35
36
|
import { toLangChainContent } from './langchain';
|
|
36
37
|
|
|
@@ -1412,6 +1413,47 @@ function cloneWithProjectedProperties<T extends object>(
|
|
|
1412
1413
|
) as T;
|
|
1413
1414
|
}
|
|
1414
1415
|
|
|
1416
|
+
function cloneAIMessageWithProjectedStreamContent(
|
|
1417
|
+
message: AIMessage | AIMessageChunk,
|
|
1418
|
+
changes: Readonly<Record<string, unknown>>,
|
|
1419
|
+
streamContent: AIMessage['content']
|
|
1420
|
+
): AIMessage | AIMessageChunk {
|
|
1421
|
+
const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
|
|
1422
|
+
string,
|
|
1423
|
+
PropertyDescriptor | undefined
|
|
1424
|
+
>;
|
|
1425
|
+
for (const [key, projectedValue] of Object.entries(changes)) {
|
|
1426
|
+
const descriptor = descriptors[key];
|
|
1427
|
+
descriptors[key] = {
|
|
1428
|
+
configurable: descriptor?.configurable ?? true,
|
|
1429
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
1430
|
+
value: projectedValue,
|
|
1431
|
+
writable: descriptor?.writable ?? true,
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
const lcKwargs = descriptors.lc_kwargs;
|
|
1435
|
+
if (
|
|
1436
|
+
lcKwargs != null &&
|
|
1437
|
+
'value' in lcKwargs &&
|
|
1438
|
+
typeof lcKwargs.value === 'object' &&
|
|
1439
|
+
lcKwargs.value != null
|
|
1440
|
+
) {
|
|
1441
|
+
descriptors.lc_kwargs = {
|
|
1442
|
+
...lcKwargs,
|
|
1443
|
+
value: {
|
|
1444
|
+
...(lcKwargs.value as Record<string, unknown>),
|
|
1445
|
+
...(message.response_metadata.output_version === 'v1'
|
|
1446
|
+
? { content: undefined, contentBlocks: streamContent }
|
|
1447
|
+
: { content: streamContent }),
|
|
1448
|
+
},
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return Object.create(
|
|
1452
|
+
Object.getPrototypeOf(message),
|
|
1453
|
+
descriptors as PropertyDescriptorMap
|
|
1454
|
+
) as AIMessage | AIMessageChunk;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1415
1457
|
function createBoundedTruncationValue(
|
|
1416
1458
|
preview: string,
|
|
1417
1459
|
originalChars: number,
|
|
@@ -1875,9 +1917,10 @@ export function calculateMaxToolCallInputChars(
|
|
|
1875
1917
|
* Returns the original array when no message changes and otherwise clones only
|
|
1876
1918
|
* the array and AI messages whose inline input or `tool_calls` args changed.
|
|
1877
1919
|
*/
|
|
1878
|
-
|
|
1920
|
+
function projectToolCallInputsInternal(
|
|
1879
1921
|
messages: BaseMessage[],
|
|
1880
|
-
maxInputChars: number
|
|
1922
|
+
maxInputChars: number,
|
|
1923
|
+
dropIncompleteStreamContent: boolean
|
|
1881
1924
|
): BaseMessage[] {
|
|
1882
1925
|
const normalizedMaxInputChars = normalizeToolInputLimit(maxInputChars);
|
|
1883
1926
|
let projectedMessages: BaseMessage[] | undefined;
|
|
@@ -1889,16 +1932,21 @@ export function projectToolCallInputs(
|
|
|
1889
1932
|
}
|
|
1890
1933
|
|
|
1891
1934
|
const aiMessage = message as AIMessage | AIMessageChunk;
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1935
|
+
const streamContent = dropIncompleteStreamContent
|
|
1936
|
+
? dropIncompleteToolStreamContent(aiMessage.content)
|
|
1937
|
+
: aiMessage.content;
|
|
1938
|
+
let projectedContent = streamContent;
|
|
1939
|
+
let contentChanged = streamContent !== aiMessage.content;
|
|
1940
|
+
if (Array.isArray(streamContent)) {
|
|
1941
|
+
const originalContent = streamContent as MessageContentComplex[];
|
|
1896
1942
|
const mappedContent = originalContent.map((block) =>
|
|
1897
1943
|
projectInlineToolInput(block, normalizedMaxInputChars)
|
|
1898
1944
|
);
|
|
1899
|
-
contentChanged =
|
|
1900
|
-
|
|
1901
|
-
|
|
1945
|
+
contentChanged =
|
|
1946
|
+
contentChanged ||
|
|
1947
|
+
mappedContent.some(
|
|
1948
|
+
(block, blockIndex) => block !== originalContent[blockIndex]
|
|
1949
|
+
);
|
|
1902
1950
|
projectedContent = toLangChainContent(mappedContent);
|
|
1903
1951
|
}
|
|
1904
1952
|
|
|
@@ -2020,18 +2068,45 @@ export function projectToolCallInputs(
|
|
|
2020
2068
|
continue;
|
|
2021
2069
|
}
|
|
2022
2070
|
|
|
2023
|
-
const
|
|
2071
|
+
const changes = {
|
|
2024
2072
|
content: projectedContent,
|
|
2025
2073
|
tool_calls: projectedToolCalls.length > 0 ? projectedToolCalls : [],
|
|
2026
2074
|
additional_kwargs: projectedAdditionalKwargs,
|
|
2027
2075
|
response_metadata: projectedResponseMetadata,
|
|
2028
|
-
}
|
|
2076
|
+
};
|
|
2077
|
+
const projectedMessage =
|
|
2078
|
+
streamContent === aiMessage.content
|
|
2079
|
+
? cloneWithProjectedProperties(aiMessage, changes)
|
|
2080
|
+
: cloneAIMessageWithProjectedStreamContent(
|
|
2081
|
+
aiMessage,
|
|
2082
|
+
changes,
|
|
2083
|
+
streamContent
|
|
2084
|
+
);
|
|
2029
2085
|
projectedMessages ??= [...messages];
|
|
2030
2086
|
projectedMessages[i] = projectedMessage;
|
|
2031
2087
|
}
|
|
2032
2088
|
return projectedMessages ?? messages;
|
|
2033
2089
|
}
|
|
2034
2090
|
|
|
2091
|
+
/** Projects all historical tool-call input representations to bounded values. */
|
|
2092
|
+
export function projectToolCallInputs(
|
|
2093
|
+
messages: BaseMessage[],
|
|
2094
|
+
maxInputChars: number
|
|
2095
|
+
): BaseMessage[] {
|
|
2096
|
+
return projectToolCallInputsInternal(messages, maxInputChars, false);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/**
|
|
2100
|
+
* Derives provider-safe tool history in one pass by dropping incomplete stream
|
|
2101
|
+
* content and bounding every provider-consumed tool-call input representation.
|
|
2102
|
+
*/
|
|
2103
|
+
export function projectToolMessagesForProvider(
|
|
2104
|
+
messages: BaseMessage[],
|
|
2105
|
+
maxInputChars: number
|
|
2106
|
+
): BaseMessage[] {
|
|
2107
|
+
return projectToolCallInputsInternal(messages, maxInputChars, true);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2035
2110
|
export function preFlightTruncateToolCallInputs(params: {
|
|
2036
2111
|
messages: BaseMessage[];
|
|
2037
2112
|
maxContextTokens: number;
|
package/src/messages/recency.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AIMessage,
|
|
3
|
+
BaseMessage,
|
|
4
|
+
ToolMessage,
|
|
5
|
+
} from '@langchain/core/messages';
|
|
6
|
+
import type {
|
|
7
|
+
ProviderToolCallIndex,
|
|
8
|
+
ProviderToolCallPartDescriptor,
|
|
9
|
+
ProviderToolResultPartDescriptor,
|
|
10
|
+
} from './toolResultTypes';
|
|
11
|
+
import { getProviderSourceMessageIds } from './provenance';
|
|
12
|
+
import {
|
|
13
|
+
appendProviderToolCallDescriptor,
|
|
14
|
+
consumeProviderToolResultPair,
|
|
15
|
+
getBoundedProviderPairingArray,
|
|
16
|
+
getBoundedProviderPairingArrayProperty,
|
|
17
|
+
getProviderAIMessageToolCallDescriptor,
|
|
18
|
+
getProviderToolCallPartDescriptor,
|
|
19
|
+
getProviderToolResultPartDescriptor,
|
|
20
|
+
PROVIDER_TOOL_PAIRING_MAX_IDENTIFIER_CHARS,
|
|
21
|
+
} from './toolResultTypes';
|
|
2
22
|
|
|
3
23
|
export const DEFAULT_RETAIN_RECENT_TURNS = 2;
|
|
24
|
+
export const DEFAULT_INTRA_TURN_RETAIN_RATIO = 0.16;
|
|
4
25
|
|
|
5
26
|
/**
|
|
6
27
|
* Configuration for splitting a message list into a head (to be summarized)
|
|
@@ -8,15 +29,15 @@ export const DEFAULT_RETAIN_RECENT_TURNS = 2;
|
|
|
8
29
|
*/
|
|
9
30
|
export interface RecencyWindowOptions {
|
|
10
31
|
/**
|
|
11
|
-
* Maximum number of recent user-led turns to keep in the tail.
|
|
12
|
-
* begins at a HumanMessage and includes every following
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
32
|
+
* Maximum number of recent user-led turns to keep in the tail. A "turn"
|
|
33
|
+
* begins at a user-authored HumanMessage and includes every following
|
|
34
|
+
* AIMessage and tool result up to the next user-authored HumanMessage.
|
|
35
|
+
* Provider-native HumanMessages containing only tool results remain in the
|
|
36
|
+
* current turn, so the boundary cannot split them from their calls.
|
|
16
37
|
*
|
|
17
|
-
* The most recent turn is
|
|
18
|
-
*
|
|
19
|
-
*
|
|
38
|
+
* The most recent turn is preserved unless `intraTurnTokens` enables the
|
|
39
|
+
* pairing-balanced fallback for a tool-heavy history. A lone oversized user
|
|
40
|
+
* message is never eligible for that fallback.
|
|
20
41
|
*
|
|
21
42
|
* Defaults to `2`. A value of `0` disables the recency window (head =
|
|
22
43
|
* everything, tail = empty), restoring the pre-recency-window behavior.
|
|
@@ -34,31 +55,378 @@ export interface RecencyWindowOptions {
|
|
|
34
55
|
tokens?: number;
|
|
35
56
|
/** Token-counter used to evaluate the optional `tokens` cap. */
|
|
36
57
|
tokenCounter?: (m: BaseMessage) => number;
|
|
58
|
+
/**
|
|
59
|
+
* Minimum token budget to retain when the turn window would otherwise make
|
|
60
|
+
* the whole history indivisible. When set with `tokenCounter`, older closed
|
|
61
|
+
* tool-call/result units may be summarized from within the earliest retained
|
|
62
|
+
* turn. A lone user payload and open tool units remain indivisible.
|
|
63
|
+
*/
|
|
64
|
+
intraTurnTokens?: number;
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
export interface RecencySplit {
|
|
40
68
|
/** Older messages eligible for summarization. Empty when nothing to summarize. */
|
|
41
69
|
head: BaseMessage[];
|
|
42
|
-
/** Recent messages preserved verbatim
|
|
70
|
+
/** Recent messages preserved verbatim, beginning at a pairing-balanced boundary. */
|
|
43
71
|
tail: BaseMessage[];
|
|
44
72
|
/** Number of user-led turns retained in the tail (0 if no HumanMessage exists). */
|
|
45
73
|
tailTurnCount: number;
|
|
46
74
|
/** Index in the original `messages` array where the tail begins. */
|
|
47
75
|
tailStartIndex: number;
|
|
76
|
+
/** True when the pairing-balanced fallback selected the boundary. */
|
|
77
|
+
usedIntraTurnFallback: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveIntraTurnRetainTokens({
|
|
81
|
+
tokens,
|
|
82
|
+
maxContextTokens,
|
|
83
|
+
}: {
|
|
84
|
+
tokens?: number;
|
|
85
|
+
maxContextTokens?: number;
|
|
86
|
+
}): number | undefined {
|
|
87
|
+
if (tokens != null) {
|
|
88
|
+
return Number.isFinite(tokens) && tokens > 0
|
|
89
|
+
? Math.floor(tokens)
|
|
90
|
+
: undefined;
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
maxContextTokens == null ||
|
|
94
|
+
!Number.isFinite(maxContextTokens) ||
|
|
95
|
+
maxContextTokens <= 0
|
|
96
|
+
) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return Math.max(
|
|
100
|
+
1,
|
|
101
|
+
Math.floor(maxContextTokens * DEFAULT_INTRA_TURN_RETAIN_RATIO)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readOwnString(value: unknown, key: string): string | undefined {
|
|
106
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
111
|
+
return descriptor != null &&
|
|
112
|
+
'value' in descriptor &&
|
|
113
|
+
typeof descriptor.value === 'string' &&
|
|
114
|
+
descriptor.value !== '' &&
|
|
115
|
+
descriptor.value.length <= PROVIDER_TOOL_PAIRING_MAX_IDENTIFIER_CHARS
|
|
116
|
+
? descriptor.value
|
|
117
|
+
: undefined;
|
|
118
|
+
} catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readOwnValue(value: unknown, key: string): unknown {
|
|
124
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
129
|
+
return descriptor != null && 'value' in descriptor
|
|
130
|
+
? descriptor.value
|
|
131
|
+
: undefined;
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const LEGACY_FUNCTION_CALL_PREFIX = 'legacy_function:';
|
|
138
|
+
|
|
139
|
+
function getLegacyFunctionCallId(name: string): string | undefined {
|
|
140
|
+
const callId = `${LEGACY_FUNCTION_CALL_PREFIX}${name}`;
|
|
141
|
+
return callId.length <= PROVIDER_TOOL_PAIRING_MAX_IDENTIFIER_CHARS
|
|
142
|
+
? callId
|
|
143
|
+
: undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getRawToolCallDescriptor(
|
|
147
|
+
toolCall: unknown
|
|
148
|
+
): ProviderToolCallPartDescriptor | undefined {
|
|
149
|
+
const callId = readOwnString(toolCall, 'id');
|
|
150
|
+
if (callId == null) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
let name: string | undefined;
|
|
154
|
+
if (toolCall != null && typeof toolCall === 'object') {
|
|
155
|
+
try {
|
|
156
|
+
const property = Object.getOwnPropertyDescriptor(toolCall, 'function');
|
|
157
|
+
name =
|
|
158
|
+
property != null && 'value' in property
|
|
159
|
+
? readOwnString(property.value, 'name')
|
|
160
|
+
: undefined;
|
|
161
|
+
} catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
callId,
|
|
167
|
+
kind: 'tool',
|
|
168
|
+
sourceType: 'raw_tool_calls',
|
|
169
|
+
...(name == null ? {} : { name }),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function appendMessageToolCalls(
|
|
174
|
+
message: BaseMessage,
|
|
175
|
+
calls: ProviderToolCallIndex
|
|
176
|
+
): void {
|
|
177
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
178
|
+
if (message.getType() !== 'ai' && messageRole !== 'assistant') {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const toolCall of (message as AIMessage).tool_calls ?? []) {
|
|
183
|
+
const descriptor = getProviderAIMessageToolCallDescriptor(toolCall);
|
|
184
|
+
if (descriptor != null) {
|
|
185
|
+
appendProviderToolCallDescriptor(calls, descriptor);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const rawToolCalls = getBoundedProviderPairingArrayProperty(
|
|
190
|
+
message.additional_kwargs,
|
|
191
|
+
'tool_calls'
|
|
192
|
+
);
|
|
193
|
+
if (rawToolCalls != null) {
|
|
194
|
+
for (const toolCall of rawToolCalls) {
|
|
195
|
+
const descriptor = getRawToolCallDescriptor(toolCall);
|
|
196
|
+
if (descriptor != null) {
|
|
197
|
+
appendProviderToolCallDescriptor(calls, descriptor);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const legacyFunctionCall = readOwnValue(
|
|
203
|
+
message.additional_kwargs,
|
|
204
|
+
'function_call'
|
|
205
|
+
);
|
|
206
|
+
const legacyFunctionName = readOwnString(legacyFunctionCall, 'name');
|
|
207
|
+
const legacyFunctionCallId =
|
|
208
|
+
legacyFunctionName == null
|
|
209
|
+
? undefined
|
|
210
|
+
: getLegacyFunctionCallId(legacyFunctionName);
|
|
211
|
+
if (legacyFunctionCallId != null) {
|
|
212
|
+
appendProviderToolCallDescriptor(calls, {
|
|
213
|
+
callId: legacyFunctionCallId,
|
|
214
|
+
kind: 'tool',
|
|
215
|
+
name: legacyFunctionName,
|
|
216
|
+
sourceType: 'legacy_function_call',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getToolMessageResultDescriptor(
|
|
222
|
+
message: BaseMessage
|
|
223
|
+
): ProviderToolResultPartDescriptor | undefined {
|
|
224
|
+
if (message.getType() === 'function' && message.name != null) {
|
|
225
|
+
const toolCallId = getLegacyFunctionCallId(message.name);
|
|
226
|
+
return toolCallId == null
|
|
227
|
+
? undefined
|
|
228
|
+
: {
|
|
229
|
+
type: 'function_message',
|
|
230
|
+
toolCallId,
|
|
231
|
+
compatibleCallKinds: ['tool'],
|
|
232
|
+
expectedToolNames: [message.name],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (message.getType() !== 'tool') {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const toolMessage = message as ToolMessage & { toolCallId?: unknown };
|
|
239
|
+
const toolCallId =
|
|
240
|
+
typeof toolMessage.tool_call_id === 'string'
|
|
241
|
+
? toolMessage.tool_call_id
|
|
242
|
+
: toolMessage.toolCallId;
|
|
243
|
+
return typeof toolCallId === 'string' &&
|
|
244
|
+
toolCallId !== '' &&
|
|
245
|
+
toolCallId.length <= PROVIDER_TOOL_PAIRING_MAX_IDENTIFIER_CHARS
|
|
246
|
+
? {
|
|
247
|
+
type: 'tool_message',
|
|
248
|
+
toolCallId,
|
|
249
|
+
compatibleCallKinds: ['tool'],
|
|
250
|
+
}
|
|
251
|
+
: undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface MessagePairingResult {
|
|
255
|
+
completedToolCalls: number;
|
|
256
|
+
trustedHumanToolResult: boolean;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function inspectMessagePairing(
|
|
260
|
+
message: BaseMessage,
|
|
261
|
+
calls: ProviderToolCallIndex
|
|
262
|
+
): MessagePairingResult {
|
|
263
|
+
const isHuman = message.getType() === 'human';
|
|
264
|
+
const candidateCalls = isHuman ? new Map(calls) : calls;
|
|
265
|
+
if (!isHuman) {
|
|
266
|
+
appendMessageToolCalls(message, candidateCalls);
|
|
267
|
+
}
|
|
268
|
+
const content = getBoundedProviderPairingArray(message.content);
|
|
269
|
+
let completedToolCalls = 0;
|
|
270
|
+
let resultPartCount = 0;
|
|
271
|
+
let everyPartIsTrustedHumanResult = isHuman && content != null;
|
|
272
|
+
if (content != null) {
|
|
273
|
+
let previousPart: unknown;
|
|
274
|
+
for (const part of content) {
|
|
275
|
+
const callDescriptor = getProviderToolCallPartDescriptor(part);
|
|
276
|
+
if (callDescriptor != null && !isHuman) {
|
|
277
|
+
appendProviderToolCallDescriptor(candidateCalls, callDescriptor);
|
|
278
|
+
}
|
|
279
|
+
const resultDescriptor = getProviderToolResultPartDescriptor(part);
|
|
280
|
+
if (resultDescriptor == null) {
|
|
281
|
+
everyPartIsTrustedHumanResult = false;
|
|
282
|
+
} else {
|
|
283
|
+
resultPartCount += 1;
|
|
284
|
+
const canPairHumanResult =
|
|
285
|
+
!isHuman || resultDescriptor.allowHumanMessagePairing === true;
|
|
286
|
+
if (
|
|
287
|
+
canPairHumanResult &&
|
|
288
|
+
consumeProviderToolResultPair(
|
|
289
|
+
resultDescriptor,
|
|
290
|
+
candidateCalls,
|
|
291
|
+
previousPart
|
|
292
|
+
)
|
|
293
|
+
) {
|
|
294
|
+
completedToolCalls += 1;
|
|
295
|
+
} else {
|
|
296
|
+
everyPartIsTrustedHumanResult = false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
previousPart = part;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const toolMessageResult = getToolMessageResultDescriptor(message);
|
|
304
|
+
if (
|
|
305
|
+
toolMessageResult != null &&
|
|
306
|
+
consumeProviderToolResultPair(toolMessageResult, candidateCalls)
|
|
307
|
+
) {
|
|
308
|
+
completedToolCalls += 1;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const trustedHumanToolResult =
|
|
312
|
+
everyPartIsTrustedHumanResult &&
|
|
313
|
+
resultPartCount > 0 &&
|
|
314
|
+
resultPartCount === content?.length;
|
|
315
|
+
if (trustedHumanToolResult) {
|
|
316
|
+
calls.clear();
|
|
317
|
+
for (const [callId, entry] of candidateCalls) {
|
|
318
|
+
calls.set(callId, entry);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
completedToolCalls: isHuman && !trustedHumanToolResult
|
|
323
|
+
? 0
|
|
324
|
+
: completedToolCalls,
|
|
325
|
+
trustedHumanToolResult,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findTurnStarts(messages: BaseMessage[]): number[] {
|
|
330
|
+
const turnStarts: number[] = [];
|
|
331
|
+
const calls: ProviderToolCallIndex = new Map();
|
|
332
|
+
for (let i = 0; i < messages.length; i++) {
|
|
333
|
+
const message = messages[i] as BaseMessage;
|
|
334
|
+
const pairing = inspectMessagePairing(message, calls);
|
|
335
|
+
if (message.getType() !== 'human' || pairing.trustedHumanToolResult) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
turnStarts.push(i);
|
|
339
|
+
calls.clear();
|
|
340
|
+
}
|
|
341
|
+
return turnStarts;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getCoverageSourceIds(message: BaseMessage): string[] {
|
|
345
|
+
const sourceIds = getProviderSourceMessageIds(message);
|
|
346
|
+
if (sourceIds.length > 0) {
|
|
347
|
+
return sourceIds;
|
|
348
|
+
}
|
|
349
|
+
const messageId = message.id?.trim();
|
|
350
|
+
return messageId != null && messageId !== '' ? [messageId] : [];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function findIntraTurnBoundary(
|
|
354
|
+
messages: BaseMessage[],
|
|
355
|
+
countMessageAt: (index: number) => number,
|
|
356
|
+
retainTokens: number,
|
|
357
|
+
earliestRetainedTurnEndIndex: number
|
|
358
|
+
): number | undefined {
|
|
359
|
+
let remainingTokens = 0;
|
|
360
|
+
for (let i = 0; i < messages.length; i++) {
|
|
361
|
+
remainingTokens += countMessageAt(i);
|
|
362
|
+
}
|
|
363
|
+
if (remainingTokens <= retainTokens) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const sourceIdsByIndex = new Array<string[]>(messages.length);
|
|
368
|
+
const lastIndexBySourceId = new Map<string, number>();
|
|
369
|
+
for (let i = 0; i < messages.length; i++) {
|
|
370
|
+
const sourceIds = getCoverageSourceIds(messages[i] as BaseMessage);
|
|
371
|
+
sourceIdsByIndex[i] = sourceIds;
|
|
372
|
+
for (const sourceId of sourceIds) {
|
|
373
|
+
lastIndexBySourceId.set(sourceId, i);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const pendingToolCalls: ProviderToolCallIndex = new Map();
|
|
378
|
+
const straddlingSourceIds = new Set<string>();
|
|
379
|
+
let completedToolCalls = 0;
|
|
380
|
+
let boundary: number | undefined;
|
|
381
|
+
|
|
382
|
+
const boundarySearchEnd = Math.min(
|
|
383
|
+
messages.length - 1,
|
|
384
|
+
earliestRetainedTurnEndIndex - 1
|
|
385
|
+
);
|
|
386
|
+
for (let i = 0; i <= boundarySearchEnd; i++) {
|
|
387
|
+
const message = messages[i] as BaseMessage;
|
|
388
|
+
remainingTokens -= countMessageAt(i);
|
|
389
|
+
|
|
390
|
+
const pairing = inspectMessagePairing(message, pendingToolCalls);
|
|
391
|
+
completedToolCalls += pairing.completedToolCalls;
|
|
392
|
+
if (message.getType() === 'human' && !pairing.trustedHumanToolResult) {
|
|
393
|
+
pendingToolCalls.clear();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
for (const sourceId of sourceIdsByIndex[i] as string[]) {
|
|
397
|
+
if (lastIndexBySourceId.get(sourceId) === i) {
|
|
398
|
+
straddlingSourceIds.delete(sourceId);
|
|
399
|
+
} else {
|
|
400
|
+
straddlingSourceIds.add(sourceId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (remainingTokens < retainTokens) {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
completedToolCalls > 0 &&
|
|
409
|
+
pendingToolCalls.size === 0 &&
|
|
410
|
+
straddlingSourceIds.size === 0 &&
|
|
411
|
+
(message.getType() !== 'human' || pairing.trustedHumanToolResult)
|
|
412
|
+
) {
|
|
413
|
+
boundary = i + 1;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return boundary;
|
|
48
418
|
}
|
|
49
419
|
|
|
50
420
|
/**
|
|
51
421
|
* Splits `messages` into a head (older, to summarize) and a tail (recent,
|
|
52
|
-
* to preserve verbatim)
|
|
53
|
-
* user-led turn is
|
|
54
|
-
*
|
|
422
|
+
* to preserve verbatim), preferring user-message boundaries. The most recent
|
|
423
|
+
* user-led turn is normally included in the tail; additional older turns are
|
|
424
|
+
* added subject to `turns` and `tokens` caps.
|
|
55
425
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* the "first turn destruction" failure mode where a single large
|
|
61
|
-
* user-pasted payload would otherwise be replaced by a generic summary.
|
|
426
|
+
* When that policy exposes no compactable head, `intraTurnTokens` may select
|
|
427
|
+
* a boundary after older closed tool-call/result units. This keeps runaway
|
|
428
|
+
* first-turn tool loops compactable without splitting parallel calls from
|
|
429
|
+
* their results. A user payload without a completed tool unit stays intact.
|
|
62
430
|
*
|
|
63
431
|
* When `messages` contains no HumanMessage (degenerate state — e.g. system
|
|
64
432
|
* + assistant messages from a programmatic preamble), everything is
|
|
@@ -78,15 +446,11 @@ export function splitAtRecencyBoundary(
|
|
|
78
446
|
tail: [],
|
|
79
447
|
tailTurnCount: 0,
|
|
80
448
|
tailStartIndex: messages.length,
|
|
449
|
+
usedIntraTurnFallback: false,
|
|
81
450
|
};
|
|
82
451
|
}
|
|
83
452
|
|
|
84
|
-
const turnStarts
|
|
85
|
-
for (let i = 0; i < messages.length; i++) {
|
|
86
|
-
if (messages[i].getType() === 'human') {
|
|
87
|
-
turnStarts.push(i);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
453
|
+
const turnStarts = findTurnStarts(messages);
|
|
90
454
|
|
|
91
455
|
if (turnStarts.length === 0) {
|
|
92
456
|
return {
|
|
@@ -94,6 +458,7 @@ export function splitAtRecencyBoundary(
|
|
|
94
458
|
tail: [],
|
|
95
459
|
tailTurnCount: 0,
|
|
96
460
|
tailStartIndex: messages.length,
|
|
461
|
+
usedIntraTurnFallback: false,
|
|
97
462
|
};
|
|
98
463
|
}
|
|
99
464
|
|
|
@@ -105,6 +470,16 @@ export function splitAtRecencyBoundary(
|
|
|
105
470
|
const tokenCounter = options.tokenCounter;
|
|
106
471
|
const trackTokens =
|
|
107
472
|
tokensCap != null && Number.isFinite(tokensCap) && tokenCounter != null;
|
|
473
|
+
const messageTokens: Array<number | undefined> = new Array(messages.length);
|
|
474
|
+
const countMessageAt = (index: number): number => {
|
|
475
|
+
const cached = messageTokens[index];
|
|
476
|
+
if (cached != null) {
|
|
477
|
+
return cached;
|
|
478
|
+
}
|
|
479
|
+
const count = tokenCounter?.(messages[index] as BaseMessage) ?? 0;
|
|
480
|
+
messageTokens[index] = count;
|
|
481
|
+
return count;
|
|
482
|
+
};
|
|
108
483
|
|
|
109
484
|
/**
|
|
110
485
|
* Token-counting strategy: each candidate turn `t` spans the half-open
|
|
@@ -122,7 +497,7 @@ export function splitAtRecencyBoundary(
|
|
|
122
497
|
let tailTokens = 0;
|
|
123
498
|
if (trackTokens) {
|
|
124
499
|
for (let i = lastTurnStart; i < messages.length; i++) {
|
|
125
|
-
tailTokens +=
|
|
500
|
+
tailTokens += countMessageAt(i);
|
|
126
501
|
}
|
|
127
502
|
}
|
|
128
503
|
|
|
@@ -136,7 +511,7 @@ export function splitAtRecencyBoundary(
|
|
|
136
511
|
if (trackTokens) {
|
|
137
512
|
let turnTokens = 0;
|
|
138
513
|
for (let i = turnStart; i < turnEnd; i++) {
|
|
139
|
-
turnTokens +=
|
|
514
|
+
turnTokens += countMessageAt(i);
|
|
140
515
|
}
|
|
141
516
|
if (tailTokens + turnTokens > (tokensCap as number)) {
|
|
142
517
|
break;
|
|
@@ -148,10 +523,37 @@ export function splitAtRecencyBoundary(
|
|
|
148
523
|
tailTurnCount += 1;
|
|
149
524
|
}
|
|
150
525
|
|
|
526
|
+
if (tailStartIndex === turnStarts[0]) {
|
|
527
|
+
const intraTurnTokens = options.intraTurnTokens;
|
|
528
|
+
if (
|
|
529
|
+
intraTurnTokens != null &&
|
|
530
|
+
Number.isFinite(intraTurnTokens) &&
|
|
531
|
+
intraTurnTokens > 0 &&
|
|
532
|
+
tokenCounter != null
|
|
533
|
+
) {
|
|
534
|
+
const intraTurnBoundary = findIntraTurnBoundary(
|
|
535
|
+
messages,
|
|
536
|
+
countMessageAt,
|
|
537
|
+
intraTurnTokens,
|
|
538
|
+
turnStarts[1] ?? messages.length
|
|
539
|
+
);
|
|
540
|
+
if (intraTurnBoundary != null) {
|
|
541
|
+
return {
|
|
542
|
+
head: messages.slice(0, intraTurnBoundary),
|
|
543
|
+
tail: messages.slice(intraTurnBoundary),
|
|
544
|
+
tailTurnCount,
|
|
545
|
+
tailStartIndex: intraTurnBoundary,
|
|
546
|
+
usedIntraTurnFallback: true,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
151
552
|
return {
|
|
152
553
|
head: messages.slice(0, tailStartIndex),
|
|
153
554
|
tail: messages.slice(tailStartIndex),
|
|
154
555
|
tailTurnCount,
|
|
155
556
|
tailStartIndex,
|
|
557
|
+
usedIntraTurnFallback: false,
|
|
156
558
|
};
|
|
157
559
|
}
|