@cuylabs/agent-core 4.10.0 → 5.0.0
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/agent/chat-loop/commit-batch.d.ts +1 -1
- package/dist/agent/chat-loop/commit-batch.d.ts.map +1 -1
- package/dist/agent/chat-loop/context-recovery.d.ts +7 -3
- package/dist/agent/chat-loop/context-recovery.d.ts.map +1 -1
- package/dist/agent/chat-loop/finalize-turn.d.ts.map +1 -1
- package/dist/agent/chat-loop/loop.d.ts.map +1 -1
- package/dist/agent/chat-loop/model-step-snapshot.d.ts +1 -1
- package/dist/agent/chat-loop/model-step-snapshot.d.ts.map +1 -1
- package/dist/agent/chat-loop/types.d.ts +1 -1
- package/dist/agent/chat-loop/types.d.ts.map +1 -1
- package/dist/agent/event-printer.d.ts.map +1 -1
- package/dist/agent/fork.d.ts +1 -1
- package/dist/agent/fork.d.ts.map +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/instance/context-management.d.ts +14 -1
- package/dist/agent/instance/context-management.d.ts.map +1 -1
- package/dist/agent/instance/forking.d.ts +1 -1
- package/dist/agent/instance/forking.d.ts.map +1 -1
- package/dist/agent/instance/index.d.ts +17 -5
- package/dist/agent/instance/index.d.ts.map +1 -1
- package/dist/agent/instance/sessions.d.ts +1 -1
- package/dist/agent/instance/sessions.d.ts.map +1 -1
- package/dist/agent/instance/turn-lifecycle.d.ts +1 -1
- package/dist/agent/instance/turn-lifecycle.d.ts.map +1 -1
- package/dist/agent/session.d.ts +1 -1
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/setup/config.d.ts +1 -1
- package/dist/agent/setup/config.d.ts.map +1 -1
- package/dist/agent/setup/context-window.d.ts.map +1 -1
- package/dist/agent/setup.d.ts +1 -1
- package/dist/agent/setup.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/agent-context.d.ts +26 -3
- package/dist/agent/turn-context/compaction/agent-context.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/budget.d.ts +2 -2
- package/dist/agent/turn-context/compaction/budget.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/check.d.ts +9 -3
- package/dist/agent/turn-context/compaction/check.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/index.d.ts +1 -1
- package/dist/agent/turn-context/compaction/index.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/memory.d.ts +5 -4
- package/dist/agent/turn-context/compaction/memory.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/results.d.ts +7 -1
- package/dist/agent/turn-context/compaction/results.d.ts.map +1 -1
- package/dist/agent/turn-context/compaction/types.d.ts +7 -1
- package/dist/agent/turn-context/compaction/types.d.ts.map +1 -1
- package/dist/agent/turn-context/fit-model-context.d.ts +1 -1
- package/dist/agent/turn-context/fit-model-context.d.ts.map +1 -1
- package/dist/agent/turn-context/index.d.ts +1 -1
- package/dist/agent/turn-context/index.d.ts.map +1 -1
- package/dist/agent/types/config.d.ts +7 -0
- package/dist/agent/types/config.d.ts.map +1 -1
- package/dist/{chunk-LX4AHGI3.js → chunk-346FIYKT.js} +1 -1
- package/dist/{chunk-EBVSPHXA.js → chunk-556CPZ3J.js} +1 -1
- package/dist/{chunk-V4YQ6MBK.js → chunk-BKHWKKSG.js} +1 -1
- package/dist/{chunk-AAGKWUXR.js → chunk-CGP6UNCQ.js} +33 -18
- package/dist/{chunk-EEAGM5MS.js → chunk-DD7S7ZG4.js} +32 -15
- package/dist/{chunk-NMJNN6LS.js → chunk-DYZGHHDB.js} +424 -121
- package/dist/{chunk-VMGZKIFT.js → chunk-EDKZOPUV.js} +34 -298
- package/dist/{chunk-TU5KDFWI.js → chunk-GHVW7L4P.js} +41 -0
- package/dist/{chunk-IQA64CAO.js → chunk-TYQWH6XH.js} +6 -2
- package/dist/context/assembly/prepare.d.ts.map +1 -1
- package/dist/context/assembly/types.d.ts +6 -2
- package/dist/context/assembly/types.d.ts.map +1 -1
- package/dist/context/config.d.ts +10 -1
- package/dist/context/config.d.ts.map +1 -1
- package/dist/context/fragments/messages.d.ts +2 -0
- package/dist/context/fragments/messages.d.ts.map +1 -1
- package/dist/context/index.js +9 -3
- package/dist/context/window/budget.d.ts +28 -1
- package/dist/context/window/budget.d.ts.map +1 -1
- package/dist/context/window/compactor.d.ts +17 -2
- package/dist/context/window/compactor.d.ts.map +1 -1
- package/dist/context/window/cut-planner.d.ts +3 -0
- package/dist/context/window/cut-planner.d.ts.map +1 -1
- package/dist/context/window/decision.d.ts +11 -2
- package/dist/context/window/decision.d.ts.map +1 -1
- package/dist/context/window/estimation.d.ts +19 -4
- package/dist/context/window/estimation.d.ts.map +1 -1
- package/dist/context/window/index.d.ts +5 -3
- package/dist/context/window/index.d.ts.map +1 -1
- package/dist/context/window/manager.d.ts +33 -0
- package/dist/context/window/manager.d.ts.map +1 -1
- package/dist/context/window/summary.d.ts +9 -0
- package/dist/context/window/summary.d.ts.map +1 -1
- package/dist/context/window/tool-pruning.d.ts +11 -0
- package/dist/context/window/tool-pruning.d.ts.map +1 -1
- package/dist/dispatch/index.js +3 -3
- package/dist/execution/index.js +3 -3
- package/dist/execution/turn/index.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +311 -125
- package/dist/memory/config.d.ts +2 -2
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +5 -5
- package/dist/memory/middleware.d.ts +2 -2
- package/dist/memory/middleware.d.ts.map +1 -1
- package/dist/memory/types.d.ts +34 -15
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/middleware/index.d.ts +2 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/runner.d.ts +16 -1
- package/dist/middleware/runner.d.ts.map +1 -1
- package/dist/middleware/types.d.ts +57 -2
- package/dist/middleware/types.d.ts.map +1 -1
- package/dist/{storage → sessions}/index.d.ts +7 -8
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/{storage → sessions}/index.js +6 -18
- package/dist/{storage → sessions}/manager/default.d.ts +3 -3
- package/dist/sessions/manager/default.d.ts.map +1 -0
- package/dist/sessions/manager/index.d.ts.map +1 -0
- package/dist/{storage → sessions}/manager/session-manager.d.ts +14 -4
- package/dist/sessions/manager/session-manager.d.ts.map +1 -0
- package/dist/sessions/manager/types.d.ts.map +1 -0
- package/dist/sessions/store/lock.d.ts.map +1 -0
- package/dist/{storage → sessions/store}/memory.d.ts +5 -5
- package/dist/sessions/store/memory.d.ts.map +1 -0
- package/dist/{storage → sessions}/types.d.ts +16 -6
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/{storage → sessions}/utils.d.ts +3 -3
- package/dist/sessions/utils.d.ts.map +1 -0
- package/dist/subagents/index.js +4 -4
- package/dist/types/compaction.d.ts +49 -0
- package/dist/types/compaction.d.ts.map +1 -1
- package/dist/types/events.d.ts +4 -2
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/storage/file/helpers.d.ts +0 -16
- package/dist/storage/file/helpers.d.ts.map +0 -1
- package/dist/storage/file/index.d.ts +0 -6
- package/dist/storage/file/index.d.ts.map +0 -1
- package/dist/storage/file/storage.d.ts +0 -29
- package/dist/storage/file/storage.d.ts.map +0 -1
- package/dist/storage/file/types.d.ts +0 -6
- package/dist/storage/file/types.d.ts.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/lock.d.ts.map +0 -1
- package/dist/storage/manager/default.d.ts.map +0 -1
- package/dist/storage/manager/index.d.ts.map +0 -1
- package/dist/storage/manager/session-manager.d.ts.map +0 -1
- package/dist/storage/manager/types.d.ts.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/paths.d.ts +0 -37
- package/dist/storage/paths.d.ts.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/utils.d.ts.map +0 -1
- /package/dist/{storage → sessions}/manager/index.d.ts +0 -0
- /package/dist/{storage → sessions}/manager/types.d.ts +0 -0
- /package/dist/{storage → sessions/store}/lock.d.ts +0 -0
|
@@ -5,27 +5,56 @@ import {
|
|
|
5
5
|
isAgentContextFragmentMessage,
|
|
6
6
|
truncateTextMiddle,
|
|
7
7
|
truncateTextPrefix
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CGP6UNCQ.js";
|
|
9
9
|
|
|
10
10
|
// src/context/window/budget.ts
|
|
11
11
|
var DEFAULT_CONTEXT_LIMITS = {
|
|
12
12
|
contextWindow: 128e3,
|
|
13
|
+
effectiveContextWindowPercent: 95,
|
|
13
14
|
reserveTokens: 16e3,
|
|
14
15
|
protectedTokens: 4e4,
|
|
15
16
|
pruneMinimum: 2e4
|
|
16
17
|
};
|
|
18
|
+
var MIN_EFFECTIVE_CONTEXT_WINDOW_PERCENT = 1;
|
|
19
|
+
var MAX_EFFECTIVE_CONTEXT_WINDOW_PERCENT = 100;
|
|
20
|
+
function finitePositiveInteger(value) {
|
|
21
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
22
|
+
const normalized = Math.floor(value);
|
|
23
|
+
return normalized > 0 ? normalized : void 0;
|
|
24
|
+
}
|
|
25
|
+
function normalizePercent(value, fallback) {
|
|
26
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
27
|
+
return Math.min(
|
|
28
|
+
MAX_EFFECTIVE_CONTEXT_WINDOW_PERCENT,
|
|
29
|
+
Math.max(MIN_EFFECTIVE_CONTEXT_WINDOW_PERCENT, Math.floor(value))
|
|
30
|
+
);
|
|
31
|
+
}
|
|
17
32
|
var DEFAULT_SUMMARY_MAX_OUTPUT_TOKENS = 2e3;
|
|
18
33
|
var DEFAULT_SUMMARY_MAX_INPUT_TOKENS = 24e3;
|
|
19
34
|
var DEFAULT_COMPACTION_EFFECTIVE_MIN_SAVINGS_RATIO = 0.02;
|
|
20
35
|
function getUsableTokenLimit(limits) {
|
|
21
|
-
|
|
36
|
+
const effectiveWindow = getEffectiveContextWindow(limits);
|
|
37
|
+
const reserveLimitedWindow = limits.contextWindow - limits.reserveTokens;
|
|
38
|
+
return Math.max(1, Math.min(effectiveWindow, reserveLimitedWindow));
|
|
39
|
+
}
|
|
40
|
+
function getEffectiveContextWindow(limits) {
|
|
41
|
+
const percent = normalizePercent(
|
|
42
|
+
limits.effectiveContextWindowPercent,
|
|
43
|
+
DEFAULT_CONTEXT_LIMITS.effectiveContextWindowPercent ?? 95
|
|
44
|
+
);
|
|
45
|
+
return Math.max(1, Math.floor(limits.contextWindow * percent / 100));
|
|
46
|
+
}
|
|
47
|
+
function getAutoCompactTokenLimit(limits) {
|
|
48
|
+
const usableLimit = getUsableTokenLimit(limits);
|
|
49
|
+
const configured = finitePositiveInteger(limits.autoCompactTokenLimit);
|
|
50
|
+
return Math.min(configured ?? usableLimit, usableLimit);
|
|
22
51
|
}
|
|
23
52
|
function isContextOverflowing(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
|
|
24
53
|
return tokens > getUsableTokenLimit(limits);
|
|
25
54
|
}
|
|
26
55
|
function shouldPruneContext(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
|
|
27
56
|
if (tokens < limits.pruneMinimum) return false;
|
|
28
|
-
return
|
|
57
|
+
return tokens > getAutoCompactTokenLimit(limits);
|
|
29
58
|
}
|
|
30
59
|
function finiteNonNegativeInteger(value) {
|
|
31
60
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -45,11 +74,6 @@ function createCompactionEffectiveness(tokensBefore, tokensAfter, minSavingsRati
|
|
|
45
74
|
effective
|
|
46
75
|
};
|
|
47
76
|
}
|
|
48
|
-
function finitePositiveInteger(value) {
|
|
49
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
50
|
-
const normalized = Math.floor(value);
|
|
51
|
-
return normalized > 0 ? normalized : void 0;
|
|
52
|
-
}
|
|
53
77
|
function clampRatio(value) {
|
|
54
78
|
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
55
79
|
if (value <= 0) return void 0;
|
|
@@ -133,7 +157,7 @@ function getAdjustedCutReason(options) {
|
|
|
133
157
|
}
|
|
134
158
|
return options.targetBoundaryWasSafe ? "turn-boundary" : "tool-boundary";
|
|
135
159
|
}
|
|
136
|
-
function buildCutPlan(messages, cutIndex, protectedTokens, reason) {
|
|
160
|
+
function buildCutPlan(messages, cutIndex, protectedTokens, reason, tokenEstimator) {
|
|
137
161
|
if (cutIndex === 0) {
|
|
138
162
|
return {
|
|
139
163
|
kind: "conversation",
|
|
@@ -141,7 +165,7 @@ function buildCutPlan(messages, cutIndex, protectedTokens, reason) {
|
|
|
141
165
|
removedMessages: [],
|
|
142
166
|
keptMessages: [...messages],
|
|
143
167
|
tokensRemoved: 0,
|
|
144
|
-
tokensKept: estimateConversationTokens(messages),
|
|
168
|
+
tokensKept: estimateConversationTokens(messages, tokenEstimator),
|
|
145
169
|
protectedTokens,
|
|
146
170
|
reason,
|
|
147
171
|
historyMessages: [],
|
|
@@ -155,15 +179,15 @@ function buildCutPlan(messages, cutIndex, protectedTokens, reason) {
|
|
|
155
179
|
cutIndex,
|
|
156
180
|
removedMessages,
|
|
157
181
|
keptMessages,
|
|
158
|
-
tokensRemoved: estimateConversationTokens(removedMessages),
|
|
159
|
-
tokensKept: estimateConversationTokens(keptMessages),
|
|
182
|
+
tokensRemoved: estimateConversationTokens(removedMessages, tokenEstimator),
|
|
183
|
+
tokensKept: estimateConversationTokens(keptMessages, tokenEstimator),
|
|
160
184
|
protectedTokens,
|
|
161
185
|
reason,
|
|
162
186
|
historyMessages: removedMessages,
|
|
163
187
|
currentTurnPrefixMessages: []
|
|
164
188
|
};
|
|
165
189
|
}
|
|
166
|
-
function buildSplitTurnPrefixPlan(messages, cutIndex, protectedTokens, currentTurnStartIndex) {
|
|
190
|
+
function buildSplitTurnPrefixPlan(messages, cutIndex, protectedTokens, currentTurnStartIndex, tokenEstimator) {
|
|
167
191
|
const removedMessages = messages.slice(0, cutIndex);
|
|
168
192
|
const keptMessages = messages.slice(cutIndex);
|
|
169
193
|
const historyMessages = messages.slice(0, currentTurnStartIndex);
|
|
@@ -176,8 +200,8 @@ function buildSplitTurnPrefixPlan(messages, cutIndex, protectedTokens, currentTu
|
|
|
176
200
|
cutIndex,
|
|
177
201
|
removedMessages,
|
|
178
202
|
keptMessages,
|
|
179
|
-
tokensRemoved: estimateConversationTokens(removedMessages),
|
|
180
|
-
tokensKept: estimateConversationTokens(keptMessages),
|
|
203
|
+
tokensRemoved: estimateConversationTokens(removedMessages, tokenEstimator),
|
|
204
|
+
tokensKept: estimateConversationTokens(keptMessages, tokenEstimator),
|
|
181
205
|
protectedTokens,
|
|
182
206
|
reason: "split-turn-prefix",
|
|
183
207
|
historyMessages,
|
|
@@ -185,23 +209,23 @@ function buildSplitTurnPrefixPlan(messages, cutIndex, protectedTokens, currentTu
|
|
|
185
209
|
currentTurnStartIndex
|
|
186
210
|
};
|
|
187
211
|
}
|
|
188
|
-
function getProtectedWindowStart(messages, protectedTokens) {
|
|
212
|
+
function getProtectedWindowStart(messages, protectedTokens, tokenEstimator) {
|
|
189
213
|
let tokensFromEnd = 0;
|
|
190
214
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
191
|
-
tokensFromEnd += estimateMessageTokens(messages[i]);
|
|
215
|
+
tokensFromEnd += estimateMessageTokens(messages[i], tokenEstimator);
|
|
192
216
|
if (tokensFromEnd >= protectedTokens) {
|
|
193
217
|
return i;
|
|
194
218
|
}
|
|
195
219
|
}
|
|
196
220
|
return messages.length;
|
|
197
221
|
}
|
|
198
|
-
function findSplitTurnPrefixPlan(messages, protectedTokens, maxKeptTokens, normalPlan) {
|
|
222
|
+
function findSplitTurnPrefixPlan(messages, protectedTokens, maxKeptTokens, normalPlan, tokenEstimator) {
|
|
199
223
|
const currentTurnStartIndex = findLatestUserMessageIndex(messages);
|
|
200
224
|
if (currentTurnStartIndex === void 0 || currentTurnStartIndex >= messages.length - 1) {
|
|
201
225
|
return void 0;
|
|
202
226
|
}
|
|
203
227
|
const targetStart = Math.max(
|
|
204
|
-
getProtectedWindowStart(messages, protectedTokens),
|
|
228
|
+
getProtectedWindowStart(messages, protectedTokens, tokenEstimator),
|
|
205
229
|
currentTurnStartIndex + 1
|
|
206
230
|
);
|
|
207
231
|
let bestPlan;
|
|
@@ -211,7 +235,8 @@ function findSplitTurnPrefixPlan(messages, protectedTokens, maxKeptTokens, norma
|
|
|
211
235
|
messages,
|
|
212
236
|
index,
|
|
213
237
|
protectedTokens,
|
|
214
|
-
currentTurnStartIndex
|
|
238
|
+
currentTurnStartIndex,
|
|
239
|
+
tokenEstimator
|
|
215
240
|
);
|
|
216
241
|
if (plan.tokensKept >= normalPlan.tokensKept) return false;
|
|
217
242
|
if (maxKeptTokens === void 0 || plan.tokensKept <= maxKeptTokens) {
|
|
@@ -232,23 +257,36 @@ function findSplitTurnPrefixPlan(messages, protectedTokens, maxKeptTokens, norma
|
|
|
232
257
|
return bestPlan;
|
|
233
258
|
}
|
|
234
259
|
function planCompactionCut(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens, options = {}) {
|
|
260
|
+
const tokenEstimator = options.tokenEstimator;
|
|
235
261
|
if (messages.length === 0) {
|
|
236
|
-
return buildCutPlan(
|
|
262
|
+
return buildCutPlan(
|
|
263
|
+
messages,
|
|
264
|
+
0,
|
|
265
|
+
protectedTokens,
|
|
266
|
+
"no-safe-cut",
|
|
267
|
+
tokenEstimator
|
|
268
|
+
);
|
|
237
269
|
}
|
|
238
|
-
const targetCutIndex = getProtectedWindowStart(
|
|
270
|
+
const targetCutIndex = getProtectedWindowStart(
|
|
271
|
+
messages,
|
|
272
|
+
protectedTokens,
|
|
273
|
+
tokenEstimator
|
|
274
|
+
);
|
|
239
275
|
if (targetCutIndex <= 1) {
|
|
240
276
|
const noSafePlan2 = buildCutPlan(
|
|
241
277
|
messages,
|
|
242
278
|
0,
|
|
243
279
|
protectedTokens,
|
|
244
|
-
"no-safe-cut"
|
|
280
|
+
"no-safe-cut",
|
|
281
|
+
tokenEstimator
|
|
245
282
|
);
|
|
246
283
|
if (options.allowSplitTurn) {
|
|
247
284
|
return findSplitTurnPrefixPlan(
|
|
248
285
|
messages,
|
|
249
286
|
protectedTokens,
|
|
250
287
|
options.maxKeptTokens,
|
|
251
|
-
noSafePlan2
|
|
288
|
+
noSafePlan2,
|
|
289
|
+
tokenEstimator
|
|
252
290
|
) ?? noSafePlan2;
|
|
253
291
|
}
|
|
254
292
|
return noSafePlan2;
|
|
@@ -262,14 +300,16 @@ function planCompactionCut(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.pr
|
|
|
262
300
|
messages,
|
|
263
301
|
0,
|
|
264
302
|
protectedTokens,
|
|
265
|
-
"no-safe-cut"
|
|
303
|
+
"no-safe-cut",
|
|
304
|
+
tokenEstimator
|
|
266
305
|
);
|
|
267
306
|
if (options.allowSplitTurn) {
|
|
268
307
|
return findSplitTurnPrefixPlan(
|
|
269
308
|
messages,
|
|
270
309
|
protectedTokens,
|
|
271
310
|
options.maxKeptTokens,
|
|
272
|
-
noSafePlan2
|
|
311
|
+
noSafePlan2,
|
|
312
|
+
tokenEstimator
|
|
273
313
|
) ?? noSafePlan2;
|
|
274
314
|
}
|
|
275
315
|
return noSafePlan2;
|
|
@@ -285,14 +325,16 @@ function planCompactionCut(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.pr
|
|
|
285
325
|
targetCutIndex,
|
|
286
326
|
latestUserIndex,
|
|
287
327
|
targetBoundaryWasSafe
|
|
288
|
-
})
|
|
328
|
+
}),
|
|
329
|
+
tokenEstimator
|
|
289
330
|
);
|
|
290
331
|
if (options.allowSplitTurn && options.maxKeptTokens !== void 0 && plan.tokensKept > options.maxKeptTokens) {
|
|
291
332
|
return findSplitTurnPrefixPlan(
|
|
292
333
|
messages,
|
|
293
334
|
protectedTokens,
|
|
294
335
|
options.maxKeptTokens,
|
|
295
|
-
plan
|
|
336
|
+
plan,
|
|
337
|
+
tokenEstimator
|
|
296
338
|
) ?? plan;
|
|
297
339
|
}
|
|
298
340
|
return plan;
|
|
@@ -309,93 +351,138 @@ function planCompactionCut(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.pr
|
|
|
309
351
|
targetCutIndex,
|
|
310
352
|
latestUserIndex,
|
|
311
353
|
targetBoundaryWasSafe
|
|
312
|
-
})
|
|
354
|
+
}),
|
|
355
|
+
tokenEstimator
|
|
313
356
|
);
|
|
314
357
|
if (options.allowSplitTurn && options.maxKeptTokens !== void 0 && plan.tokensKept > options.maxKeptTokens) {
|
|
315
358
|
return findSplitTurnPrefixPlan(
|
|
316
359
|
messages,
|
|
317
360
|
protectedTokens,
|
|
318
361
|
options.maxKeptTokens,
|
|
319
|
-
plan
|
|
362
|
+
plan,
|
|
363
|
+
tokenEstimator
|
|
320
364
|
) ?? plan;
|
|
321
365
|
}
|
|
322
366
|
return plan;
|
|
323
367
|
}
|
|
324
368
|
}
|
|
325
|
-
const noSafePlan = buildCutPlan(
|
|
369
|
+
const noSafePlan = buildCutPlan(
|
|
370
|
+
messages,
|
|
371
|
+
0,
|
|
372
|
+
protectedTokens,
|
|
373
|
+
"no-safe-cut",
|
|
374
|
+
tokenEstimator
|
|
375
|
+
);
|
|
326
376
|
if (options.allowSplitTurn) {
|
|
327
377
|
return findSplitTurnPrefixPlan(
|
|
328
378
|
messages,
|
|
329
379
|
protectedTokens,
|
|
330
380
|
options.maxKeptTokens,
|
|
331
|
-
noSafePlan
|
|
381
|
+
noSafePlan,
|
|
382
|
+
tokenEstimator
|
|
332
383
|
) ?? noSafePlan;
|
|
333
384
|
}
|
|
334
385
|
return noSafePlan;
|
|
335
386
|
}
|
|
336
387
|
|
|
337
388
|
// src/context/window/decision.ts
|
|
389
|
+
function defaultCompactionReason(options) {
|
|
390
|
+
if (options.reason) return options.reason;
|
|
391
|
+
if (options.trigger === "manual") return "manual-request";
|
|
392
|
+
if (options.forced) return "provider-overflow";
|
|
393
|
+
return "context-limit";
|
|
394
|
+
}
|
|
395
|
+
function createDecision(params) {
|
|
396
|
+
return {
|
|
397
|
+
needed: params.needed,
|
|
398
|
+
reason: params.reason,
|
|
399
|
+
strategy: params.strategy,
|
|
400
|
+
forced: params.forced,
|
|
401
|
+
trigger: params.trigger,
|
|
402
|
+
compactionReason: params.compactionReason,
|
|
403
|
+
canSummarize: params.canSummarize,
|
|
404
|
+
inputTokens: params.inputTokens,
|
|
405
|
+
limit: params.limit,
|
|
406
|
+
overflowTokens: params.overflowTokens,
|
|
407
|
+
protectedTokens: params.protectedTokens,
|
|
408
|
+
pruneMinimum: params.pruneMinimum
|
|
409
|
+
};
|
|
410
|
+
}
|
|
338
411
|
function decideContextCompaction(messages, options = {}) {
|
|
339
412
|
const limits = options.limits ?? DEFAULT_CONTEXT_LIMITS;
|
|
340
|
-
const inputTokens = options.inputTokens ?? estimateConversationTokens(messages);
|
|
341
|
-
const limit =
|
|
413
|
+
const inputTokens = options.inputTokens ?? estimateConversationTokens(messages, options.tokenEstimator);
|
|
414
|
+
const limit = getAutoCompactTokenLimit(limits);
|
|
342
415
|
const overflowTokens = Math.max(0, inputTokens - limit);
|
|
343
416
|
const forced = options.force === true;
|
|
417
|
+
const trigger = options.trigger ?? (forced ? "recovery" : "auto");
|
|
418
|
+
const compactionReason = defaultCompactionReason({
|
|
419
|
+
forced,
|
|
420
|
+
trigger,
|
|
421
|
+
reason: options.reason
|
|
422
|
+
});
|
|
344
423
|
const canSummarize = options.canSummarize === true;
|
|
345
424
|
if (forced) {
|
|
346
|
-
return {
|
|
425
|
+
return createDecision({
|
|
347
426
|
needed: true,
|
|
348
427
|
reason: "forced-recovery",
|
|
349
428
|
strategy: "tool-context-first",
|
|
350
429
|
forced,
|
|
430
|
+
trigger,
|
|
431
|
+
compactionReason,
|
|
351
432
|
canSummarize,
|
|
352
433
|
inputTokens,
|
|
353
434
|
limit,
|
|
354
435
|
overflowTokens,
|
|
355
436
|
protectedTokens: limits.protectedTokens,
|
|
356
437
|
pruneMinimum: limits.pruneMinimum
|
|
357
|
-
};
|
|
438
|
+
});
|
|
358
439
|
}
|
|
359
440
|
if (inputTokens < limits.pruneMinimum) {
|
|
360
|
-
return {
|
|
441
|
+
return createDecision({
|
|
361
442
|
needed: false,
|
|
362
443
|
reason: "below-prune-minimum",
|
|
363
444
|
strategy: "none",
|
|
364
445
|
forced,
|
|
446
|
+
trigger,
|
|
447
|
+
compactionReason,
|
|
365
448
|
canSummarize,
|
|
366
449
|
inputTokens,
|
|
367
450
|
limit,
|
|
368
451
|
overflowTokens,
|
|
369
452
|
protectedTokens: limits.protectedTokens,
|
|
370
453
|
pruneMinimum: limits.pruneMinimum
|
|
371
|
-
};
|
|
454
|
+
});
|
|
372
455
|
}
|
|
373
456
|
if (!shouldPruneContext(inputTokens, limits)) {
|
|
374
|
-
return {
|
|
457
|
+
return createDecision({
|
|
375
458
|
needed: false,
|
|
376
459
|
reason: "under-limit",
|
|
377
460
|
strategy: "none",
|
|
378
461
|
forced,
|
|
462
|
+
trigger,
|
|
463
|
+
compactionReason,
|
|
379
464
|
canSummarize,
|
|
380
465
|
inputTokens,
|
|
381
466
|
limit,
|
|
382
467
|
overflowTokens,
|
|
383
468
|
protectedTokens: limits.protectedTokens,
|
|
384
469
|
pruneMinimum: limits.pruneMinimum
|
|
385
|
-
};
|
|
470
|
+
});
|
|
386
471
|
}
|
|
387
|
-
return {
|
|
472
|
+
return createDecision({
|
|
388
473
|
needed: true,
|
|
389
474
|
reason: "overflow",
|
|
390
475
|
strategy: "tool-context-first",
|
|
391
476
|
forced,
|
|
477
|
+
trigger,
|
|
478
|
+
compactionReason,
|
|
392
479
|
canSummarize,
|
|
393
480
|
inputTokens,
|
|
394
481
|
limit,
|
|
395
482
|
overflowTokens,
|
|
396
483
|
protectedTokens: limits.protectedTokens,
|
|
397
484
|
pruneMinimum: limits.pruneMinimum
|
|
398
|
-
};
|
|
485
|
+
});
|
|
399
486
|
}
|
|
400
487
|
|
|
401
488
|
// src/types/compaction.ts
|
|
@@ -435,6 +522,7 @@ Preserve:
|
|
|
435
522
|
4. Open issues or blockers that the kept suffix depends on
|
|
436
523
|
|
|
437
524
|
Do not invent next steps. Do not ask or answer questions from the removed material. Return only the handoff summary body.`;
|
|
525
|
+
var DEFAULT_RECENT_MESSAGE_RATIO = 0.65;
|
|
438
526
|
function createCompactionSummaryContent(summary) {
|
|
439
527
|
return `${SUMMARY_PREFIX}${summary.trim()}`;
|
|
440
528
|
}
|
|
@@ -465,6 +553,7 @@ function redactSummaryText(text) {
|
|
|
465
553
|
);
|
|
466
554
|
}
|
|
467
555
|
function compactText(text, maxChars) {
|
|
556
|
+
if (maxChars <= 0) return "";
|
|
468
557
|
const redacted = redactSummaryText(text);
|
|
469
558
|
return truncateTextMiddle(redacted, maxChars, {
|
|
470
559
|
headRatio: 0.7,
|
|
@@ -501,6 +590,95 @@ ${content}`];
|
|
|
501
590
|
if (toolCalls) parts.push(toolCalls);
|
|
502
591
|
return parts.join("\n\n");
|
|
503
592
|
}
|
|
593
|
+
function separatorLength(parts) {
|
|
594
|
+
return parts.length > 0 ? 2 : 0;
|
|
595
|
+
}
|
|
596
|
+
function joinedLength(parts) {
|
|
597
|
+
if (parts.length === 0) return 0;
|
|
598
|
+
return parts.reduce((total, part) => total + part.length, 0) + (parts.length - 1) * 2;
|
|
599
|
+
}
|
|
600
|
+
function clampRecentMessageRatio(value) {
|
|
601
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
602
|
+
return DEFAULT_RECENT_MESSAGE_RATIO;
|
|
603
|
+
}
|
|
604
|
+
return Math.min(0.9, Math.max(0.1, value));
|
|
605
|
+
}
|
|
606
|
+
function appendWithinBudget(params) {
|
|
607
|
+
const remaining = params.budget - joinedLength(params.parts) - separatorLength(params.parts);
|
|
608
|
+
if (remaining <= 0) return false;
|
|
609
|
+
if (params.text.length <= remaining) {
|
|
610
|
+
params.parts.push(params.text);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
if (!params.allowPartial) return false;
|
|
614
|
+
const compacted = compactText(params.text, remaining);
|
|
615
|
+
if (!compacted) return false;
|
|
616
|
+
params.parts.push(compacted);
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
function prependWithinBudget(params) {
|
|
620
|
+
const remaining = params.budget - joinedLength(params.parts) - separatorLength(params.parts);
|
|
621
|
+
if (remaining <= 0) return false;
|
|
622
|
+
if (params.text.length <= remaining) {
|
|
623
|
+
params.parts.unshift(params.text);
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
if (!params.allowPartial) return false;
|
|
627
|
+
const compacted = compactText(params.text, remaining);
|
|
628
|
+
if (!compacted) return false;
|
|
629
|
+
params.parts.unshift(compacted);
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
function serializeBoundedMessages(serializedMessages, maxChars, recentMessageRatio) {
|
|
633
|
+
if (serializedMessages.length === 0) {
|
|
634
|
+
return "[No message content was available for summarization.]";
|
|
635
|
+
}
|
|
636
|
+
const fullTranscript = serializedMessages.join("\n\n");
|
|
637
|
+
if (fullTranscript.length <= maxChars) {
|
|
638
|
+
return fullTranscript;
|
|
639
|
+
}
|
|
640
|
+
if (serializedMessages.length === 1) {
|
|
641
|
+
return compactText(serializedMessages[0], maxChars);
|
|
642
|
+
}
|
|
643
|
+
const markerFor = (omittedCount2) => omittedCount2 > 0 ? `[...${omittedCount2} messages omitted for compaction; preserved beginning and most recent removed context...]` : "[...message content omitted for compaction; preserved beginning and most recent removed context...]";
|
|
644
|
+
const markerReserve = markerFor(serializedMessages.length).length + 4;
|
|
645
|
+
const contentBudget = Math.max(1, maxChars - markerReserve);
|
|
646
|
+
const tailBudget = Math.max(
|
|
647
|
+
1,
|
|
648
|
+
Math.floor(contentBudget * recentMessageRatio)
|
|
649
|
+
);
|
|
650
|
+
const headBudget = Math.max(1, contentBudget - tailBudget);
|
|
651
|
+
const headParts = [];
|
|
652
|
+
let headEnd = 0;
|
|
653
|
+
for (let i = 0; i < serializedMessages.length; i++) {
|
|
654
|
+
const added = appendWithinBudget({
|
|
655
|
+
parts: headParts,
|
|
656
|
+
text: serializedMessages[i],
|
|
657
|
+
budget: headBudget,
|
|
658
|
+
allowPartial: headParts.length === 0
|
|
659
|
+
});
|
|
660
|
+
if (!added) break;
|
|
661
|
+
headEnd = i + 1;
|
|
662
|
+
if (joinedLength(headParts) >= headBudget) break;
|
|
663
|
+
}
|
|
664
|
+
const tailParts = [];
|
|
665
|
+
let tailStart = serializedMessages.length;
|
|
666
|
+
for (let i = serializedMessages.length - 1; i >= headEnd; i--) {
|
|
667
|
+
const added = prependWithinBudget({
|
|
668
|
+
parts: tailParts,
|
|
669
|
+
text: serializedMessages[i],
|
|
670
|
+
budget: tailBudget,
|
|
671
|
+
allowPartial: tailParts.length === 0
|
|
672
|
+
});
|
|
673
|
+
if (!added) break;
|
|
674
|
+
tailStart = i;
|
|
675
|
+
if (joinedLength(tailParts) >= tailBudget) break;
|
|
676
|
+
}
|
|
677
|
+
const omittedCount = Math.max(0, tailStart - headEnd);
|
|
678
|
+
const marker = markerFor(omittedCount);
|
|
679
|
+
const transcript = [...headParts, marker, ...tailParts].filter((part) => part.length > 0).join("\n\n");
|
|
680
|
+
return transcript.length <= maxChars ? transcript : compactText(transcript, maxChars);
|
|
681
|
+
}
|
|
504
682
|
function serializeMessagesForSummary(messages, options = {}) {
|
|
505
683
|
const maxChars = estimateCharsFromTokens(
|
|
506
684
|
options.maxInputTokens,
|
|
@@ -510,28 +688,25 @@ function serializeMessagesForSummary(messages, options = {}) {
|
|
|
510
688
|
options.maxMessageTokens,
|
|
511
689
|
DEFAULT_MESSAGE_INPUT_TOKENS
|
|
512
690
|
);
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
parts.push(`${separator}${compactText(serialized, remaining)}`);
|
|
522
|
-
usedChars = maxChars;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
parts.push(`${separator}${serialized}`);
|
|
526
|
-
usedChars += separator.length + serialized.length;
|
|
527
|
-
}
|
|
528
|
-
const transcript = parts.join("");
|
|
691
|
+
const serializedMessages = messages.map(
|
|
692
|
+
(message) => serializeMessageForSummary(message, maxMessageChars)
|
|
693
|
+
);
|
|
694
|
+
const transcript = serializeBoundedMessages(
|
|
695
|
+
serializedMessages,
|
|
696
|
+
maxChars,
|
|
697
|
+
clampRecentMessageRatio(options.recentMessageRatio)
|
|
698
|
+
);
|
|
529
699
|
if (messages.length > 0 && transcript.length >= maxChars) {
|
|
530
700
|
return `${transcript}
|
|
531
701
|
|
|
532
702
|
[Summary input reached its configured compaction cap.]`;
|
|
533
703
|
}
|
|
534
|
-
|
|
704
|
+
if (messages.length > 0 && serializedMessages.join("\n\n").length > transcript.length) {
|
|
705
|
+
return `${transcript}
|
|
706
|
+
|
|
707
|
+
[Summary input reached its configured compaction cap.]`;
|
|
708
|
+
}
|
|
709
|
+
return transcript;
|
|
535
710
|
}
|
|
536
711
|
async function generateSummary(messages, options) {
|
|
537
712
|
const conversationText = serializeMessagesForSummary(messages, {
|
|
@@ -594,16 +769,67 @@ ${currentTurnPrefixText}`,
|
|
|
594
769
|
}
|
|
595
770
|
|
|
596
771
|
// src/context/window/tool-pruning.ts
|
|
597
|
-
var
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
772
|
+
var DEFAULT_TOOL_CONTEXT_PRUNE_POLICY = {
|
|
773
|
+
outputThresholdTokens: 500,
|
|
774
|
+
argumentThresholdTokens: 500,
|
|
775
|
+
argumentPreviewChars: 240,
|
|
776
|
+
argumentMaxStringChars: 1800,
|
|
777
|
+
argumentStringHeadChars: 1200,
|
|
778
|
+
argumentStringTailChars: 400,
|
|
779
|
+
argumentArrayHeadItems: 24,
|
|
780
|
+
argumentArrayTailItems: 8,
|
|
781
|
+
argumentObjectMaxKeys: 80,
|
|
782
|
+
argumentMaxDepth: 8
|
|
783
|
+
};
|
|
784
|
+
function positiveIntegerOrDefault(value, fallback) {
|
|
785
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
786
|
+
const normalized = Math.floor(value);
|
|
787
|
+
return normalized > 0 ? normalized : fallback;
|
|
788
|
+
}
|
|
789
|
+
function resolveToolContextPrunePolicy(policy) {
|
|
790
|
+
return {
|
|
791
|
+
outputThresholdTokens: positiveIntegerOrDefault(
|
|
792
|
+
policy?.outputThresholdTokens,
|
|
793
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.outputThresholdTokens
|
|
794
|
+
),
|
|
795
|
+
argumentThresholdTokens: positiveIntegerOrDefault(
|
|
796
|
+
policy?.argumentThresholdTokens,
|
|
797
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentThresholdTokens
|
|
798
|
+
),
|
|
799
|
+
argumentPreviewChars: positiveIntegerOrDefault(
|
|
800
|
+
policy?.argumentPreviewChars,
|
|
801
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentPreviewChars
|
|
802
|
+
),
|
|
803
|
+
argumentMaxStringChars: positiveIntegerOrDefault(
|
|
804
|
+
policy?.argumentMaxStringChars,
|
|
805
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentMaxStringChars
|
|
806
|
+
),
|
|
807
|
+
argumentStringHeadChars: positiveIntegerOrDefault(
|
|
808
|
+
policy?.argumentStringHeadChars,
|
|
809
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentStringHeadChars
|
|
810
|
+
),
|
|
811
|
+
argumentStringTailChars: positiveIntegerOrDefault(
|
|
812
|
+
policy?.argumentStringTailChars,
|
|
813
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentStringTailChars
|
|
814
|
+
),
|
|
815
|
+
argumentArrayHeadItems: positiveIntegerOrDefault(
|
|
816
|
+
policy?.argumentArrayHeadItems,
|
|
817
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentArrayHeadItems
|
|
818
|
+
),
|
|
819
|
+
argumentArrayTailItems: positiveIntegerOrDefault(
|
|
820
|
+
policy?.argumentArrayTailItems,
|
|
821
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentArrayTailItems
|
|
822
|
+
),
|
|
823
|
+
argumentObjectMaxKeys: positiveIntegerOrDefault(
|
|
824
|
+
policy?.argumentObjectMaxKeys,
|
|
825
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentObjectMaxKeys
|
|
826
|
+
),
|
|
827
|
+
argumentMaxDepth: positiveIntegerOrDefault(
|
|
828
|
+
policy?.argumentMaxDepth,
|
|
829
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY.argumentMaxDepth
|
|
830
|
+
)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
607
833
|
function stringifyToolValue(value) {
|
|
608
834
|
if (typeof value === "string") return value;
|
|
609
835
|
if (value === void 0) return "";
|
|
@@ -623,24 +849,25 @@ function compactPreview(value, maxChars) {
|
|
|
623
849
|
separator: ""
|
|
624
850
|
});
|
|
625
851
|
}
|
|
626
|
-
function compactLongArgumentString(value) {
|
|
627
|
-
if (value.length <=
|
|
628
|
-
const totalKeptArgumentChars =
|
|
629
|
-
return truncateTextMiddle(value,
|
|
630
|
-
headRatio:
|
|
852
|
+
function compactLongArgumentString(value, policy) {
|
|
853
|
+
if (value.length <= policy.argumentMaxStringChars) return value;
|
|
854
|
+
const totalKeptArgumentChars = policy.argumentStringHeadChars + policy.argumentStringTailChars;
|
|
855
|
+
return truncateTextMiddle(value, policy.argumentMaxStringChars, {
|
|
856
|
+
headRatio: policy.argumentStringHeadChars / totalKeptArgumentChars,
|
|
631
857
|
marker: (omitted) => `
|
|
632
858
|
[${omitted} characters omitted by context compaction]
|
|
633
859
|
`
|
|
634
860
|
});
|
|
635
861
|
}
|
|
636
|
-
function compactToolArgumentValue(value, depth, seen) {
|
|
637
|
-
if (typeof value === "string")
|
|
862
|
+
function compactToolArgumentValue(value, depth, seen, policy) {
|
|
863
|
+
if (typeof value === "string")
|
|
864
|
+
return compactLongArgumentString(value, policy);
|
|
638
865
|
if (value === null || typeof value !== "object") return value;
|
|
639
|
-
if (depth >=
|
|
866
|
+
if (depth >= policy.argumentMaxDepth) {
|
|
640
867
|
return {
|
|
641
868
|
__contextWindowCompaction: {
|
|
642
869
|
reason: "max-depth",
|
|
643
|
-
preview: compactPreview(value,
|
|
870
|
+
preview: compactPreview(value, policy.argumentPreviewChars)
|
|
644
871
|
}
|
|
645
872
|
};
|
|
646
873
|
}
|
|
@@ -653,21 +880,23 @@ function compactToolArgumentValue(value, depth, seen) {
|
|
|
653
880
|
}
|
|
654
881
|
seen.add(value);
|
|
655
882
|
if (Array.isArray(value)) {
|
|
656
|
-
const shouldSlice = value.length >
|
|
657
|
-
const head = shouldSlice ? value.slice(0,
|
|
658
|
-
const tail = shouldSlice ? value.slice(-
|
|
883
|
+
const shouldSlice = value.length > policy.argumentArrayHeadItems + policy.argumentArrayTailItems;
|
|
884
|
+
const head = shouldSlice ? value.slice(0, policy.argumentArrayHeadItems) : value;
|
|
885
|
+
const tail = shouldSlice ? value.slice(-policy.argumentArrayTailItems) : [];
|
|
659
886
|
const compacted2 = head.map(
|
|
660
|
-
(item) => compactToolArgumentValue(item, depth + 1, seen)
|
|
887
|
+
(item) => compactToolArgumentValue(item, depth + 1, seen, policy)
|
|
661
888
|
);
|
|
662
889
|
if (shouldSlice) {
|
|
663
890
|
compacted2.push({
|
|
664
891
|
__contextWindowCompaction: {
|
|
665
892
|
reason: "array-length",
|
|
666
|
-
omittedItems: value.length -
|
|
893
|
+
omittedItems: value.length - policy.argumentArrayHeadItems - policy.argumentArrayTailItems
|
|
667
894
|
}
|
|
668
895
|
});
|
|
669
896
|
compacted2.push(
|
|
670
|
-
...tail.map(
|
|
897
|
+
...tail.map(
|
|
898
|
+
(item) => compactToolArgumentValue(item, depth + 1, seen, policy)
|
|
899
|
+
)
|
|
671
900
|
);
|
|
672
901
|
}
|
|
673
902
|
seen.delete(value);
|
|
@@ -675,9 +904,14 @@ function compactToolArgumentValue(value, depth, seen) {
|
|
|
675
904
|
}
|
|
676
905
|
const entries = Object.entries(value);
|
|
677
906
|
const compacted = {};
|
|
678
|
-
const keptEntries = entries.slice(0,
|
|
907
|
+
const keptEntries = entries.slice(0, policy.argumentObjectMaxKeys);
|
|
679
908
|
for (const [key, entryValue] of keptEntries) {
|
|
680
|
-
compacted[key] = compactToolArgumentValue(
|
|
909
|
+
compacted[key] = compactToolArgumentValue(
|
|
910
|
+
entryValue,
|
|
911
|
+
depth + 1,
|
|
912
|
+
seen,
|
|
913
|
+
policy
|
|
914
|
+
);
|
|
681
915
|
}
|
|
682
916
|
if (entries.length > keptEntries.length) {
|
|
683
917
|
compacted.__contextWindowCompaction = {
|
|
@@ -688,12 +922,17 @@ function compactToolArgumentValue(value, depth, seen) {
|
|
|
688
922
|
seen.delete(value);
|
|
689
923
|
return compacted;
|
|
690
924
|
}
|
|
691
|
-
function compactToolCallArgs(args) {
|
|
925
|
+
function compactToolCallArgs(args, policy, tokenEstimator) {
|
|
692
926
|
const beforeText = stringifyToolValue(args);
|
|
693
|
-
if (estimateTokens(beforeText) <
|
|
927
|
+
if (estimateTokens(beforeText, tokenEstimator) < policy.argumentThresholdTokens) {
|
|
694
928
|
return { args, compacted: false };
|
|
695
929
|
}
|
|
696
|
-
const compactedArgs = compactToolArgumentValue(
|
|
930
|
+
const compactedArgs = compactToolArgumentValue(
|
|
931
|
+
args,
|
|
932
|
+
0,
|
|
933
|
+
/* @__PURE__ */ new WeakSet(),
|
|
934
|
+
policy
|
|
935
|
+
);
|
|
697
936
|
const afterText = stringifyToolValue(compactedArgs);
|
|
698
937
|
if (afterText.length < beforeText.length) {
|
|
699
938
|
return { args: compactedArgs, compacted: true };
|
|
@@ -702,8 +941,8 @@ function compactToolCallArgs(args) {
|
|
|
702
941
|
args: {
|
|
703
942
|
__contextWindowCompaction: {
|
|
704
943
|
reason: "argument-size",
|
|
705
|
-
originalEstimatedTokens: estimateTokens(beforeText),
|
|
706
|
-
preview: compactPreview(args,
|
|
944
|
+
originalEstimatedTokens: estimateTokens(beforeText, tokenEstimator),
|
|
945
|
+
preview: compactPreview(args, policy.argumentMaxStringChars)
|
|
707
946
|
}
|
|
708
947
|
},
|
|
709
948
|
compacted: true
|
|
@@ -720,13 +959,13 @@ function collectToolCallArgs(messages) {
|
|
|
720
959
|
return argsByCallId;
|
|
721
960
|
}
|
|
722
961
|
function summarizeToolOutput(options) {
|
|
723
|
-
const { tool, args, currentTokens, duplicateOf } = options;
|
|
962
|
+
const { tool, args, currentTokens, policy, duplicateOf } = options;
|
|
724
963
|
if (duplicateOf) {
|
|
725
964
|
return `[Tool output compacted] ${tool.toolName} produced duplicate output already retained in call ${duplicateOf}. Original output was ${currentTokens} estimated tokens.`;
|
|
726
965
|
}
|
|
727
966
|
const lines = tool.content.split(/\r?\n/).filter((line) => line.trim());
|
|
728
967
|
const lineCount = lines.length;
|
|
729
|
-
const argsPreview = compactPreview(args,
|
|
968
|
+
const argsPreview = compactPreview(args, policy.argumentPreviewChars);
|
|
730
969
|
const rawFirstLine = lines[0] ?? "";
|
|
731
970
|
const firstLine = rawFirstLine.length > 0 && rawFirstLine.length <= 160 ? compactPreview(rawFirstLine, 160) : "";
|
|
732
971
|
const details = [
|
|
@@ -743,14 +982,14 @@ function summarizeToolOutput(options) {
|
|
|
743
982
|
}
|
|
744
983
|
return `[Tool output compacted] ${details.join("; ")}`;
|
|
745
984
|
}
|
|
746
|
-
function maybeCompactAssistantToolCalls(message, protectedToolSet) {
|
|
985
|
+
function maybeCompactAssistantToolCalls(message, protectedToolSet, policy, tokenEstimator) {
|
|
747
986
|
if (!message.toolCalls || message.toolCalls.length === 0) {
|
|
748
987
|
return { message, compactedCount: 0 };
|
|
749
988
|
}
|
|
750
989
|
let compactedCount = 0;
|
|
751
990
|
const toolCalls = message.toolCalls.map((toolCall) => {
|
|
752
991
|
if (protectedToolSet.has(toolCall.toolName)) return toolCall;
|
|
753
|
-
const result = compactToolCallArgs(toolCall.args);
|
|
992
|
+
const result = compactToolCallArgs(toolCall.args, policy, tokenEstimator);
|
|
754
993
|
if (!result.compacted) return toolCall;
|
|
755
994
|
compactedCount++;
|
|
756
995
|
return {
|
|
@@ -768,6 +1007,8 @@ function maybeCompactAssistantToolCalls(message, protectedToolSet) {
|
|
|
768
1007
|
};
|
|
769
1008
|
}
|
|
770
1009
|
function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens, options) {
|
|
1010
|
+
const policy = resolveToolContextPrunePolicy(options?.policy);
|
|
1011
|
+
const tokenEstimator = options?.tokenEstimator;
|
|
771
1012
|
const protectedToolSet = /* @__PURE__ */ new Set([
|
|
772
1013
|
...PRUNE_PROTECTED_TOOLS,
|
|
773
1014
|
...options?.protectedTools ?? []
|
|
@@ -775,13 +1016,13 @@ function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_
|
|
|
775
1016
|
const argsByCallId = collectToolCallArgs(messages);
|
|
776
1017
|
const newestFullOutputByContent = /* @__PURE__ */ new Map();
|
|
777
1018
|
const tokensBefore = messages.reduce(
|
|
778
|
-
(total, message) => total + estimateMessageTokens(message),
|
|
1019
|
+
(total, message) => total + estimateMessageTokens(message, tokenEstimator),
|
|
779
1020
|
0
|
|
780
1021
|
);
|
|
781
1022
|
let tokensFromEnd = 0;
|
|
782
1023
|
const tokenPositions = [];
|
|
783
1024
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
784
|
-
tokensFromEnd += estimateMessageTokens(messages[i]);
|
|
1025
|
+
tokensFromEnd += estimateMessageTokens(messages[i], tokenEstimator);
|
|
785
1026
|
tokenPositions[i] = tokensFromEnd;
|
|
786
1027
|
}
|
|
787
1028
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -790,7 +1031,7 @@ function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_
|
|
|
790
1031
|
if (typeof message.content !== "string" || message.content.length < 200) {
|
|
791
1032
|
continue;
|
|
792
1033
|
}
|
|
793
|
-
const isRetained = tokenPositions[i] < protectedTokens || Boolean(message.compactedAt) || message.toolName !== void 0 && protectedToolSet.has(message.toolName) || estimateTokens(message.content) <
|
|
1034
|
+
const isRetained = tokenPositions[i] < protectedTokens || Boolean(message.compactedAt) || message.toolName !== void 0 && protectedToolSet.has(message.toolName) || estimateTokens(message.content, tokenEstimator) < policy.outputThresholdTokens;
|
|
794
1035
|
if (!isRetained) continue;
|
|
795
1036
|
if (!newestFullOutputByContent.has(message.content)) {
|
|
796
1037
|
newestFullOutputByContent.set(message.content, message.toolCallId);
|
|
@@ -802,7 +1043,12 @@ function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_
|
|
|
802
1043
|
if (tokenPositions[i] < protectedTokens) return msg;
|
|
803
1044
|
if (!("role" in msg)) return msg;
|
|
804
1045
|
if (msg.role === "assistant") {
|
|
805
|
-
const result = maybeCompactAssistantToolCalls(
|
|
1046
|
+
const result = maybeCompactAssistantToolCalls(
|
|
1047
|
+
msg,
|
|
1048
|
+
protectedToolSet,
|
|
1049
|
+
policy,
|
|
1050
|
+
tokenEstimator
|
|
1051
|
+
);
|
|
806
1052
|
argumentCompactedCount += result.compactedCount;
|
|
807
1053
|
return result.message;
|
|
808
1054
|
}
|
|
@@ -810,13 +1056,14 @@ function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_
|
|
|
810
1056
|
const toolMsg = msg;
|
|
811
1057
|
if ("compactedAt" in toolMsg && toolMsg.compactedAt) return msg;
|
|
812
1058
|
if (toolMsg.toolName && protectedToolSet.has(toolMsg.toolName)) return msg;
|
|
813
|
-
const currentTokens = estimateTokens(toolMsg.content);
|
|
814
|
-
if (currentTokens <
|
|
1059
|
+
const currentTokens = estimateTokens(toolMsg.content, tokenEstimator);
|
|
1060
|
+
if (currentTokens < policy.outputThresholdTokens) return msg;
|
|
815
1061
|
const duplicateOf = newestFullOutputByContent.get(toolMsg.content);
|
|
816
1062
|
const compactedContent = summarizeToolOutput({
|
|
817
1063
|
tool: toolMsg,
|
|
818
1064
|
args: argsByCallId.get(toolMsg.toolCallId),
|
|
819
1065
|
currentTokens,
|
|
1066
|
+
policy,
|
|
820
1067
|
duplicateOf: duplicateOf && duplicateOf !== toolMsg.toolCallId ? duplicateOf : void 0
|
|
821
1068
|
});
|
|
822
1069
|
outputCompactedCount++;
|
|
@@ -828,7 +1075,7 @@ function pruneToolContextWithReport(messages, protectedTokens = DEFAULT_CONTEXT_
|
|
|
828
1075
|
};
|
|
829
1076
|
});
|
|
830
1077
|
const tokensAfter = nextMessages.reduce(
|
|
831
|
-
(total, message) => total + estimateMessageTokens(message),
|
|
1078
|
+
(total, message) => total + estimateMessageTokens(message, tokenEstimator),
|
|
832
1079
|
0
|
|
833
1080
|
);
|
|
834
1081
|
return {
|
|
@@ -897,12 +1144,19 @@ async function pruneContext(messages, options = {}) {
|
|
|
897
1144
|
let summary;
|
|
898
1145
|
let summaryOutputTokens;
|
|
899
1146
|
const summaryPolicy = resolveContextSummaryPolicy(options.summary);
|
|
900
|
-
const
|
|
1147
|
+
const tokenEstimator = options.tokenEstimator;
|
|
1148
|
+
const initialTokens = estimateConversationTokens(
|
|
1149
|
+
currentMessages,
|
|
1150
|
+
tokenEstimator
|
|
1151
|
+
);
|
|
901
1152
|
const decision = decideContextCompaction(currentMessages, {
|
|
902
1153
|
limits,
|
|
903
1154
|
force: options.force,
|
|
904
1155
|
canSummarize: canGenerateCompactionSummary(summaryPolicy),
|
|
905
|
-
inputTokens: initialTokens
|
|
1156
|
+
inputTokens: initialTokens,
|
|
1157
|
+
trigger: options.trigger,
|
|
1158
|
+
reason: options.reason,
|
|
1159
|
+
tokenEstimator
|
|
906
1160
|
});
|
|
907
1161
|
if (!decision.needed) {
|
|
908
1162
|
return {
|
|
@@ -920,15 +1174,26 @@ async function pruneContext(messages, options = {}) {
|
|
|
920
1174
|
cutReason: "no-safe-cut"
|
|
921
1175
|
};
|
|
922
1176
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1177
|
+
let toolContext;
|
|
1178
|
+
let afterPruneTokens = initialTokens;
|
|
1179
|
+
if (options.toolPruning?.enabled !== false) {
|
|
1180
|
+
const toolContextResult = pruneToolContextWithReport(
|
|
1181
|
+
currentMessages,
|
|
1182
|
+
limits.protectedTokens,
|
|
1183
|
+
{
|
|
1184
|
+
policy: options.toolPruning?.policy,
|
|
1185
|
+
tokenEstimator
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
toolContext = toolContextResult.report;
|
|
1189
|
+
const prunedMessages = toolContextResult.messages;
|
|
1190
|
+
afterPruneTokens = estimateConversationTokens(
|
|
1191
|
+
prunedMessages,
|
|
1192
|
+
tokenEstimator
|
|
1193
|
+
);
|
|
1194
|
+
tokensRemoved = Math.max(0, initialTokens - afterPruneTokens);
|
|
1195
|
+
currentMessages = prunedMessages;
|
|
1196
|
+
}
|
|
932
1197
|
if (!isContextOverflowing(afterPruneTokens, limits)) {
|
|
933
1198
|
return {
|
|
934
1199
|
messages: currentMessages,
|
|
@@ -948,7 +1213,8 @@ async function pruneContext(messages, options = {}) {
|
|
|
948
1213
|
}
|
|
949
1214
|
const cutPlan = planCompactionCut(currentMessages, limits.protectedTokens, {
|
|
950
1215
|
allowSplitTurn: summaryPolicy.mode !== "disabled",
|
|
951
|
-
maxKeptTokens: getUsableTokenLimit(limits)
|
|
1216
|
+
maxKeptTokens: getUsableTokenLimit(limits),
|
|
1217
|
+
tokenEstimator
|
|
952
1218
|
});
|
|
953
1219
|
if (cutPlan.cutIndex === 0) {
|
|
954
1220
|
return {
|
|
@@ -985,7 +1251,7 @@ async function pruneContext(messages, options = {}) {
|
|
|
985
1251
|
...messagesToSummarize,
|
|
986
1252
|
...currentTurnPrefixMessages
|
|
987
1253
|
];
|
|
988
|
-
const summaryInputTokens = summaryInputMessages.length > 0 ? estimateConversationTokens(summaryInputMessages) : cutPlan.tokensRemoved;
|
|
1254
|
+
const summaryInputTokens = summaryInputMessages.length > 0 ? estimateConversationTokens(summaryInputMessages, tokenEstimator) : cutPlan.tokensRemoved;
|
|
989
1255
|
summaryOutputTokens = resolveSummaryOutputTokens({
|
|
990
1256
|
inputTokens: summaryInputTokens,
|
|
991
1257
|
limits,
|
|
@@ -1028,7 +1294,7 @@ async function pruneContext(messages, options = {}) {
|
|
|
1028
1294
|
decision,
|
|
1029
1295
|
effectiveness: createCompactionEffectiveness(
|
|
1030
1296
|
initialTokens,
|
|
1031
|
-
estimateConversationTokens(currentMessages)
|
|
1297
|
+
estimateConversationTokens(currentMessages, tokenEstimator)
|
|
1032
1298
|
),
|
|
1033
1299
|
removedCount,
|
|
1034
1300
|
tokensRemoved,
|
|
@@ -1038,6 +1304,7 @@ async function pruneContext(messages, options = {}) {
|
|
|
1038
1304
|
removedMessages,
|
|
1039
1305
|
keptMessages: toKeep,
|
|
1040
1306
|
summaryOutputTokens,
|
|
1307
|
+
...previousSummary !== void 0 ? { previousSummary } : {},
|
|
1041
1308
|
cutIndex: cutPlan.cutIndex,
|
|
1042
1309
|
cutReason: cutPlan.reason,
|
|
1043
1310
|
toolContext
|
|
@@ -1049,10 +1316,14 @@ var ContextWindowManager = class {
|
|
|
1049
1316
|
limits;
|
|
1050
1317
|
activeModel;
|
|
1051
1318
|
summaryPolicy;
|
|
1319
|
+
toolPruning;
|
|
1320
|
+
tokenEstimator;
|
|
1052
1321
|
constructor(options) {
|
|
1053
1322
|
this.limits = { ...DEFAULT_CONTEXT_LIMITS, ...options?.limits };
|
|
1054
1323
|
this.activeModel = options?.model;
|
|
1055
1324
|
this.summaryPolicy = options?.summary;
|
|
1325
|
+
this.toolPruning = options?.toolPruning;
|
|
1326
|
+
this.tokenEstimator = options?.tokenEstimator;
|
|
1056
1327
|
}
|
|
1057
1328
|
/** Get a copy of the current context limits. */
|
|
1058
1329
|
getLimits() {
|
|
@@ -1070,13 +1341,29 @@ var ContextWindowManager = class {
|
|
|
1070
1341
|
setSummaryPolicy(policy) {
|
|
1071
1342
|
this.summaryPolicy = policy;
|
|
1072
1343
|
}
|
|
1344
|
+
/** Set deterministic tool-context pruning behavior. */
|
|
1345
|
+
setToolPruning(options) {
|
|
1346
|
+
this.toolPruning = options;
|
|
1347
|
+
}
|
|
1348
|
+
/** Get deterministic tool-context pruning behavior. */
|
|
1349
|
+
getToolPruning() {
|
|
1350
|
+
return this.toolPruning ? { ...this.toolPruning } : void 0;
|
|
1351
|
+
}
|
|
1352
|
+
/** Set the token estimator used for context-window planning. */
|
|
1353
|
+
setTokenEstimator(estimator) {
|
|
1354
|
+
this.tokenEstimator = estimator;
|
|
1355
|
+
}
|
|
1356
|
+
/** Get the token estimator used for context-window planning. */
|
|
1357
|
+
getTokenEstimator() {
|
|
1358
|
+
return this.tokenEstimator;
|
|
1359
|
+
}
|
|
1073
1360
|
/** Resolve summary policy against the active agent model. */
|
|
1074
1361
|
getSummaryPolicy() {
|
|
1075
1362
|
return resolveContextSummaryPolicy(this.summaryPolicy, this.activeModel);
|
|
1076
1363
|
}
|
|
1077
1364
|
/** Estimate total tokens for a message array. */
|
|
1078
1365
|
estimateTokens(messages) {
|
|
1079
|
-
return estimateConversationTokens(messages);
|
|
1366
|
+
return estimateConversationTokens(messages, this.tokenEstimator);
|
|
1080
1367
|
}
|
|
1081
1368
|
/** Check whether the context is overflowing. */
|
|
1082
1369
|
isOverflowing(messages) {
|
|
@@ -1095,7 +1382,10 @@ var ContextWindowManager = class {
|
|
|
1095
1382
|
limits: this.limits,
|
|
1096
1383
|
force: options?.force,
|
|
1097
1384
|
inputTokens: options?.inputTokens,
|
|
1098
|
-
|
|
1385
|
+
trigger: options?.trigger,
|
|
1386
|
+
reason: options?.reason,
|
|
1387
|
+
canSummarize: canGenerateCompactionSummary(summaryPolicy),
|
|
1388
|
+
tokenEstimator: this.tokenEstimator
|
|
1099
1389
|
});
|
|
1100
1390
|
}
|
|
1101
1391
|
/** Prune context to fit within limits. */
|
|
@@ -1103,7 +1393,11 @@ var ContextWindowManager = class {
|
|
|
1103
1393
|
return pruneContext(messages, {
|
|
1104
1394
|
limits: this.limits,
|
|
1105
1395
|
summary: this.getSummaryPolicy(),
|
|
1106
|
-
force: options?.force
|
|
1396
|
+
force: options?.force,
|
|
1397
|
+
trigger: options?.trigger,
|
|
1398
|
+
reason: options?.reason,
|
|
1399
|
+
toolPruning: options?.toolPruning ?? this.toolPruning,
|
|
1400
|
+
tokenEstimator: this.tokenEstimator
|
|
1107
1401
|
});
|
|
1108
1402
|
}
|
|
1109
1403
|
/**
|
|
@@ -1113,11 +1407,17 @@ var ContextWindowManager = class {
|
|
|
1113
1407
|
*/
|
|
1114
1408
|
getStats(messages) {
|
|
1115
1409
|
const tokens = this.estimateTokens(messages);
|
|
1116
|
-
const
|
|
1410
|
+
const effectiveContextWindow = getEffectiveContextWindow(this.limits);
|
|
1411
|
+
const usableLimit = getUsableTokenLimit(this.limits);
|
|
1412
|
+
const autoCompactLimit = getAutoCompactTokenLimit(this.limits);
|
|
1413
|
+
const limit = autoCompactLimit;
|
|
1117
1414
|
return {
|
|
1118
1415
|
tokens,
|
|
1119
1416
|
limit,
|
|
1120
|
-
|
|
1417
|
+
effectiveContextWindow,
|
|
1418
|
+
usableLimit,
|
|
1419
|
+
autoCompactLimit,
|
|
1420
|
+
available: Math.max(0, usableLimit - tokens),
|
|
1121
1421
|
utilizationPercent: Math.round(tokens / limit * 100),
|
|
1122
1422
|
isOverflowing: isContextOverflowing(tokens, this.limits),
|
|
1123
1423
|
shouldPrune: shouldPruneContext(tokens, this.limits)
|
|
@@ -1131,6 +1431,8 @@ export {
|
|
|
1131
1431
|
DEFAULT_SUMMARY_MAX_INPUT_TOKENS,
|
|
1132
1432
|
DEFAULT_COMPACTION_EFFECTIVE_MIN_SAVINGS_RATIO,
|
|
1133
1433
|
getUsableTokenLimit,
|
|
1434
|
+
getEffectiveContextWindow,
|
|
1435
|
+
getAutoCompactTokenLimit,
|
|
1134
1436
|
isContextOverflowing,
|
|
1135
1437
|
shouldPruneContext,
|
|
1136
1438
|
createCompactionEffectiveness,
|
|
@@ -1144,6 +1446,7 @@ export {
|
|
|
1144
1446
|
serializeMessagesForSummary,
|
|
1145
1447
|
generateSummary,
|
|
1146
1448
|
generateSplitTurnSummary,
|
|
1449
|
+
DEFAULT_TOOL_CONTEXT_PRUNE_POLICY,
|
|
1147
1450
|
pruneToolContextWithReport,
|
|
1148
1451
|
pruneToolContext,
|
|
1149
1452
|
ContextSummaryModelRequiredError,
|