@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.
Files changed (155) hide show
  1. package/dist/agent/chat-loop/commit-batch.d.ts +1 -1
  2. package/dist/agent/chat-loop/commit-batch.d.ts.map +1 -1
  3. package/dist/agent/chat-loop/context-recovery.d.ts +7 -3
  4. package/dist/agent/chat-loop/context-recovery.d.ts.map +1 -1
  5. package/dist/agent/chat-loop/finalize-turn.d.ts.map +1 -1
  6. package/dist/agent/chat-loop/loop.d.ts.map +1 -1
  7. package/dist/agent/chat-loop/model-step-snapshot.d.ts +1 -1
  8. package/dist/agent/chat-loop/model-step-snapshot.d.ts.map +1 -1
  9. package/dist/agent/chat-loop/types.d.ts +1 -1
  10. package/dist/agent/chat-loop/types.d.ts.map +1 -1
  11. package/dist/agent/event-printer.d.ts.map +1 -1
  12. package/dist/agent/fork.d.ts +1 -1
  13. package/dist/agent/fork.d.ts.map +1 -1
  14. package/dist/agent/index.d.ts +1 -1
  15. package/dist/agent/index.d.ts.map +1 -1
  16. package/dist/agent/instance/context-management.d.ts +14 -1
  17. package/dist/agent/instance/context-management.d.ts.map +1 -1
  18. package/dist/agent/instance/forking.d.ts +1 -1
  19. package/dist/agent/instance/forking.d.ts.map +1 -1
  20. package/dist/agent/instance/index.d.ts +17 -5
  21. package/dist/agent/instance/index.d.ts.map +1 -1
  22. package/dist/agent/instance/sessions.d.ts +1 -1
  23. package/dist/agent/instance/sessions.d.ts.map +1 -1
  24. package/dist/agent/instance/turn-lifecycle.d.ts +1 -1
  25. package/dist/agent/instance/turn-lifecycle.d.ts.map +1 -1
  26. package/dist/agent/session.d.ts +1 -1
  27. package/dist/agent/session.d.ts.map +1 -1
  28. package/dist/agent/setup/config.d.ts +1 -1
  29. package/dist/agent/setup/config.d.ts.map +1 -1
  30. package/dist/agent/setup/context-window.d.ts.map +1 -1
  31. package/dist/agent/setup.d.ts +1 -1
  32. package/dist/agent/setup.d.ts.map +1 -1
  33. package/dist/agent/turn-context/compaction/agent-context.d.ts +26 -3
  34. package/dist/agent/turn-context/compaction/agent-context.d.ts.map +1 -1
  35. package/dist/agent/turn-context/compaction/budget.d.ts +2 -2
  36. package/dist/agent/turn-context/compaction/budget.d.ts.map +1 -1
  37. package/dist/agent/turn-context/compaction/check.d.ts +9 -3
  38. package/dist/agent/turn-context/compaction/check.d.ts.map +1 -1
  39. package/dist/agent/turn-context/compaction/index.d.ts +1 -1
  40. package/dist/agent/turn-context/compaction/index.d.ts.map +1 -1
  41. package/dist/agent/turn-context/compaction/memory.d.ts +5 -4
  42. package/dist/agent/turn-context/compaction/memory.d.ts.map +1 -1
  43. package/dist/agent/turn-context/compaction/results.d.ts +7 -1
  44. package/dist/agent/turn-context/compaction/results.d.ts.map +1 -1
  45. package/dist/agent/turn-context/compaction/types.d.ts +7 -1
  46. package/dist/agent/turn-context/compaction/types.d.ts.map +1 -1
  47. package/dist/agent/turn-context/fit-model-context.d.ts +1 -1
  48. package/dist/agent/turn-context/fit-model-context.d.ts.map +1 -1
  49. package/dist/agent/turn-context/index.d.ts +1 -1
  50. package/dist/agent/turn-context/index.d.ts.map +1 -1
  51. package/dist/agent/types/config.d.ts +7 -0
  52. package/dist/agent/types/config.d.ts.map +1 -1
  53. package/dist/{chunk-LX4AHGI3.js → chunk-346FIYKT.js} +1 -1
  54. package/dist/{chunk-EBVSPHXA.js → chunk-556CPZ3J.js} +1 -1
  55. package/dist/{chunk-V4YQ6MBK.js → chunk-BKHWKKSG.js} +1 -1
  56. package/dist/{chunk-AAGKWUXR.js → chunk-CGP6UNCQ.js} +33 -18
  57. package/dist/{chunk-EEAGM5MS.js → chunk-DD7S7ZG4.js} +32 -15
  58. package/dist/{chunk-NMJNN6LS.js → chunk-DYZGHHDB.js} +424 -121
  59. package/dist/{chunk-VMGZKIFT.js → chunk-EDKZOPUV.js} +34 -298
  60. package/dist/{chunk-TU5KDFWI.js → chunk-GHVW7L4P.js} +41 -0
  61. package/dist/{chunk-IQA64CAO.js → chunk-TYQWH6XH.js} +6 -2
  62. package/dist/context/assembly/prepare.d.ts.map +1 -1
  63. package/dist/context/assembly/types.d.ts +6 -2
  64. package/dist/context/assembly/types.d.ts.map +1 -1
  65. package/dist/context/config.d.ts +10 -1
  66. package/dist/context/config.d.ts.map +1 -1
  67. package/dist/context/fragments/messages.d.ts +2 -0
  68. package/dist/context/fragments/messages.d.ts.map +1 -1
  69. package/dist/context/index.js +9 -3
  70. package/dist/context/window/budget.d.ts +28 -1
  71. package/dist/context/window/budget.d.ts.map +1 -1
  72. package/dist/context/window/compactor.d.ts +17 -2
  73. package/dist/context/window/compactor.d.ts.map +1 -1
  74. package/dist/context/window/cut-planner.d.ts +3 -0
  75. package/dist/context/window/cut-planner.d.ts.map +1 -1
  76. package/dist/context/window/decision.d.ts +11 -2
  77. package/dist/context/window/decision.d.ts.map +1 -1
  78. package/dist/context/window/estimation.d.ts +19 -4
  79. package/dist/context/window/estimation.d.ts.map +1 -1
  80. package/dist/context/window/index.d.ts +5 -3
  81. package/dist/context/window/index.d.ts.map +1 -1
  82. package/dist/context/window/manager.d.ts +33 -0
  83. package/dist/context/window/manager.d.ts.map +1 -1
  84. package/dist/context/window/summary.d.ts +9 -0
  85. package/dist/context/window/summary.d.ts.map +1 -1
  86. package/dist/context/window/tool-pruning.d.ts +11 -0
  87. package/dist/context/window/tool-pruning.d.ts.map +1 -1
  88. package/dist/dispatch/index.js +3 -3
  89. package/dist/execution/index.js +3 -3
  90. package/dist/execution/turn/index.js +3 -3
  91. package/dist/index.d.ts +2 -2
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +311 -125
  94. package/dist/memory/config.d.ts +2 -2
  95. package/dist/memory/config.d.ts.map +1 -1
  96. package/dist/memory/index.d.ts +2 -2
  97. package/dist/memory/index.d.ts.map +1 -1
  98. package/dist/memory/index.js +5 -5
  99. package/dist/memory/middleware.d.ts +2 -2
  100. package/dist/memory/middleware.d.ts.map +1 -1
  101. package/dist/memory/types.d.ts +34 -15
  102. package/dist/memory/types.d.ts.map +1 -1
  103. package/dist/middleware/index.d.ts +2 -1
  104. package/dist/middleware/index.d.ts.map +1 -1
  105. package/dist/middleware/index.js +1 -1
  106. package/dist/middleware/runner.d.ts +16 -1
  107. package/dist/middleware/runner.d.ts.map +1 -1
  108. package/dist/middleware/types.d.ts +57 -2
  109. package/dist/middleware/types.d.ts.map +1 -1
  110. package/dist/{storage → sessions}/index.d.ts +7 -8
  111. package/dist/sessions/index.d.ts.map +1 -0
  112. package/dist/{storage → sessions}/index.js +6 -18
  113. package/dist/{storage → sessions}/manager/default.d.ts +3 -3
  114. package/dist/sessions/manager/default.d.ts.map +1 -0
  115. package/dist/sessions/manager/index.d.ts.map +1 -0
  116. package/dist/{storage → sessions}/manager/session-manager.d.ts +14 -4
  117. package/dist/sessions/manager/session-manager.d.ts.map +1 -0
  118. package/dist/sessions/manager/types.d.ts.map +1 -0
  119. package/dist/sessions/store/lock.d.ts.map +1 -0
  120. package/dist/{storage → sessions/store}/memory.d.ts +5 -5
  121. package/dist/sessions/store/memory.d.ts.map +1 -0
  122. package/dist/{storage → sessions}/types.d.ts +16 -6
  123. package/dist/sessions/types.d.ts.map +1 -0
  124. package/dist/{storage → sessions}/utils.d.ts +3 -3
  125. package/dist/sessions/utils.d.ts.map +1 -0
  126. package/dist/subagents/index.js +4 -4
  127. package/dist/types/compaction.d.ts +49 -0
  128. package/dist/types/compaction.d.ts.map +1 -1
  129. package/dist/types/events.d.ts +4 -2
  130. package/dist/types/events.d.ts.map +1 -1
  131. package/dist/types/index.d.ts +1 -1
  132. package/dist/types/index.d.ts.map +1 -1
  133. package/package.json +5 -5
  134. package/dist/storage/file/helpers.d.ts +0 -16
  135. package/dist/storage/file/helpers.d.ts.map +0 -1
  136. package/dist/storage/file/index.d.ts +0 -6
  137. package/dist/storage/file/index.d.ts.map +0 -1
  138. package/dist/storage/file/storage.d.ts +0 -29
  139. package/dist/storage/file/storage.d.ts.map +0 -1
  140. package/dist/storage/file/types.d.ts +0 -6
  141. package/dist/storage/file/types.d.ts.map +0 -1
  142. package/dist/storage/index.d.ts.map +0 -1
  143. package/dist/storage/lock.d.ts.map +0 -1
  144. package/dist/storage/manager/default.d.ts.map +0 -1
  145. package/dist/storage/manager/index.d.ts.map +0 -1
  146. package/dist/storage/manager/session-manager.d.ts.map +0 -1
  147. package/dist/storage/manager/types.d.ts.map +0 -1
  148. package/dist/storage/memory.d.ts.map +0 -1
  149. package/dist/storage/paths.d.ts +0 -37
  150. package/dist/storage/paths.d.ts.map +0 -1
  151. package/dist/storage/types.d.ts.map +0 -1
  152. package/dist/storage/utils.d.ts.map +0 -1
  153. /package/dist/{storage → sessions}/manager/index.d.ts +0 -0
  154. /package/dist/{storage → sessions}/manager/types.d.ts +0 -0
  155. /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-AAGKWUXR.js";
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
- return limits.contextWindow - limits.reserveTokens;
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 isContextOverflowing(tokens, limits);
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(messages, 0, protectedTokens, "no-safe-cut");
262
+ return buildCutPlan(
263
+ messages,
264
+ 0,
265
+ protectedTokens,
266
+ "no-safe-cut",
267
+ tokenEstimator
268
+ );
237
269
  }
238
- const targetCutIndex = getProtectedWindowStart(messages, protectedTokens);
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(messages, 0, protectedTokens, "no-safe-cut");
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 = getUsableTokenLimit(limits);
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 parts = [];
514
- let usedChars = 0;
515
- for (const message of messages) {
516
- const serialized = serializeMessageForSummary(message, maxMessageChars);
517
- const separator = parts.length > 0 ? "\n\n" : "";
518
- const remaining = maxChars - usedChars - separator.length;
519
- if (remaining <= 0) break;
520
- if (serialized.length > remaining) {
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
- return transcript || "[No message content was available for summarization.]";
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 TOOL_OUTPUT_PRUNE_THRESHOLD = 500;
598
- var TOOL_ARGUMENT_PRUNE_THRESHOLD = 500;
599
- var TOOL_ARGUMENT_PREVIEW_CHARS = 240;
600
- var TOOL_ARGUMENT_MAX_STRING_CHARS = 1800;
601
- var TOOL_ARGUMENT_STRING_HEAD_CHARS = 1200;
602
- var TOOL_ARGUMENT_STRING_TAIL_CHARS = 400;
603
- var TOOL_ARGUMENT_ARRAY_HEAD_ITEMS = 24;
604
- var TOOL_ARGUMENT_ARRAY_TAIL_ITEMS = 8;
605
- var TOOL_ARGUMENT_OBJECT_MAX_KEYS = 80;
606
- var TOOL_ARGUMENT_MAX_DEPTH = 8;
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 <= TOOL_ARGUMENT_MAX_STRING_CHARS) return value;
628
- const totalKeptArgumentChars = TOOL_ARGUMENT_STRING_HEAD_CHARS + TOOL_ARGUMENT_STRING_TAIL_CHARS;
629
- return truncateTextMiddle(value, TOOL_ARGUMENT_MAX_STRING_CHARS, {
630
- headRatio: TOOL_ARGUMENT_STRING_HEAD_CHARS / totalKeptArgumentChars,
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") return compactLongArgumentString(value);
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 >= TOOL_ARGUMENT_MAX_DEPTH) {
866
+ if (depth >= policy.argumentMaxDepth) {
640
867
  return {
641
868
  __contextWindowCompaction: {
642
869
  reason: "max-depth",
643
- preview: compactPreview(value, TOOL_ARGUMENT_PREVIEW_CHARS)
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 > TOOL_ARGUMENT_ARRAY_HEAD_ITEMS + TOOL_ARGUMENT_ARRAY_TAIL_ITEMS;
657
- const head = shouldSlice ? value.slice(0, TOOL_ARGUMENT_ARRAY_HEAD_ITEMS) : value;
658
- const tail = shouldSlice ? value.slice(-TOOL_ARGUMENT_ARRAY_TAIL_ITEMS) : [];
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 - TOOL_ARGUMENT_ARRAY_HEAD_ITEMS - TOOL_ARGUMENT_ARRAY_TAIL_ITEMS
893
+ omittedItems: value.length - policy.argumentArrayHeadItems - policy.argumentArrayTailItems
667
894
  }
668
895
  });
669
896
  compacted2.push(
670
- ...tail.map((item) => compactToolArgumentValue(item, depth + 1, seen))
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, TOOL_ARGUMENT_OBJECT_MAX_KEYS);
907
+ const keptEntries = entries.slice(0, policy.argumentObjectMaxKeys);
679
908
  for (const [key, entryValue] of keptEntries) {
680
- compacted[key] = compactToolArgumentValue(entryValue, depth + 1, seen);
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) < TOOL_ARGUMENT_PRUNE_THRESHOLD) {
927
+ if (estimateTokens(beforeText, tokenEstimator) < policy.argumentThresholdTokens) {
694
928
  return { args, compacted: false };
695
929
  }
696
- const compactedArgs = compactToolArgumentValue(args, 0, /* @__PURE__ */ new WeakSet());
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, TOOL_ARGUMENT_MAX_STRING_CHARS)
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, TOOL_ARGUMENT_PREVIEW_CHARS);
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) < TOOL_OUTPUT_PRUNE_THRESHOLD;
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(msg, protectedToolSet);
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 < TOOL_OUTPUT_PRUNE_THRESHOLD) return msg;
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 initialTokens = estimateConversationTokens(currentMessages);
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
- const toolContextResult = pruneToolContextWithReport(
924
- currentMessages,
925
- limits.protectedTokens
926
- );
927
- const toolContext = toolContextResult.report;
928
- const prunedMessages = toolContextResult.messages;
929
- const afterPruneTokens = estimateConversationTokens(prunedMessages);
930
- tokensRemoved = Math.max(0, initialTokens - afterPruneTokens);
931
- currentMessages = prunedMessages;
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
- canSummarize: canGenerateCompactionSummary(summaryPolicy)
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 limit = getUsableTokenLimit(this.limits);
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
- available: Math.max(0, limit - tokens),
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,