dsh-context-compression-improved 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/pruner.js ADDED
@@ -0,0 +1,3807 @@
1
+ import { _ as COMPRESSION_PROFILES, a as PRUNE_MARKER, c as isValidAutoCompactThresholdPercent, d as resolvePolicy, f as CustomCompressionPolicySchema, g as deepFreeze, h as assertNever, i as DEFAULTS, l as parseContextCompressionSettings, m as resolveCustomPolicy, n as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, o as codePointLength, p as DEFAULT_CUSTOM_COMPRESSION_POLICY, r as ContextCompressionSettingsSchema, s as isCompressionProfile, t as AUTO_COMPACT_THRESHOLD_LIMITS, u as resolveConfig } from "./config.js";
2
+ import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as sessionEvents, r as tailTrimRef, t as parseTailTrimRef } from "./tail-trim.js";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { createHash } from "node:crypto";
5
+ import { Service } from "@deepseek-ai/cordis";
6
+ import { createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
7
+ import { deriveEventMessage } from "@deepseek-ai/dsh-session";
8
+ import { readFileSync } from "node:fs";
9
+ import { Tokenizer } from "@huggingface/tokenizers";
10
+ import { defineTool } from "@deepseek-ai/dsh-tools";
11
+ //#region src/deepseek-v4-tokenizer.ts
12
+ /** Offline DeepSeek tokenizers backed by pinned official Hugging Face assets. */
13
+ const TOKENIZER_ID = "deepseek-ai/DeepSeek-V4-Pro";
14
+ const TOKENIZER_REVISION = "0e1a0e5e52aea73055f50fef6f2423db370265b6";
15
+ const TOKENIZER_SHA256 = "8f9f37ca37fdc4f5fd36d5cf4d3b0e8392edb4e894fd10cc0d70b4957c8633cf";
16
+ const CONFIG_SHA256 = "6ac8c8dc065ed118161d02dd532749ae3f52c243deac27872134fae2f50d8547";
17
+ const VISION_TOKENIZER_ID = "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp";
18
+ const VISION_TOKENIZER_REVISION = "6821d6ad3681a4b137b066b76094fa82ebd0a380";
19
+ const VISION_TOKENIZER_SHA256 = "c90dfa01249db1be4245780a052ede752e1361c612ac6d08e2bdada7d599476b";
20
+ const VISION_CONFIG_SHA256 = "6ac8c8dc065ed118161d02dd532749ae3f52c243deac27872134fae2f50d8547";
21
+ /** Auditable origin and compatibility mapping for the bundled V4 Pro tokenizer. */
22
+ const DEEPSEEK_V4_TOKENIZER_ARTIFACT = Object.freeze({
23
+ repository: TOKENIZER_ID,
24
+ revision: TOKENIZER_REVISION,
25
+ license: "MIT",
26
+ tokenizerSha256: TOKENIZER_SHA256,
27
+ tokenizerConfigSha256: CONFIG_SHA256,
28
+ modelIds: Object.freeze(["deepseek-v4-flash", "deepseek-v4-pro"])
29
+ });
30
+ /**
31
+ * Auditable origin and compatibility mapping for the bundled V4 Flash Vision
32
+ * tokenizer. The vision repository ships a distinct `tokenizer.json` (it adds
33
+ * the `<|deepseek_image|>` special token), so the vision model must never be
34
+ * mapped onto the V4 Pro tokenizer as an alias.
35
+ */
36
+ const DEEPSEEK_VISION_TOKENIZER_ARTIFACT = Object.freeze({
37
+ repository: VISION_TOKENIZER_ID,
38
+ revision: VISION_TOKENIZER_REVISION,
39
+ license: "MIT",
40
+ tokenizerSha256: VISION_TOKENIZER_SHA256,
41
+ tokenizerConfigSha256: VISION_CONFIG_SHA256,
42
+ modelIds: Object.freeze(["deepseek-v4-flash-vision-exp"])
43
+ });
44
+ /**
45
+ * Every bundled artifact is registered with independent asset roots, integrity
46
+ * manifests, and cache entries: one corrupted artifact must never disable the
47
+ * tokenizer serving the other model family.
48
+ *
49
+ * This module deliberately sits at the package's `src/` root rather than in
50
+ * `src/runtime/`: the build flattens the runtime into `lib/*.js`, so only a
51
+ * module one level below the package root resolves `../assets/` identically
52
+ * from source and from the published artifact.
53
+ */
54
+ const ARTIFACTS = Object.freeze([Object.freeze({
55
+ origin: DEEPSEEK_V4_TOKENIZER_ARTIFACT,
56
+ assetRoot: new URL("../assets/deepseek-v4/", import.meta.url),
57
+ integrity: Object.freeze({
58
+ tokenizer: Object.freeze({
59
+ bytes: 6367146,
60
+ sha256: TOKENIZER_SHA256
61
+ }),
62
+ config: Object.freeze({
63
+ bytes: 801,
64
+ sha256: CONFIG_SHA256
65
+ })
66
+ })
67
+ }), Object.freeze({
68
+ origin: DEEPSEEK_VISION_TOKENIZER_ARTIFACT,
69
+ assetRoot: new URL("../assets/deepseek-v4-vision-exp/", import.meta.url),
70
+ integrity: Object.freeze({
71
+ tokenizer: Object.freeze({
72
+ bytes: 6367257,
73
+ sha256: VISION_TOKENIZER_SHA256
74
+ }),
75
+ config: Object.freeze({
76
+ bytes: 801,
77
+ sha256: VISION_CONFIG_SHA256
78
+ })
79
+ })
80
+ })]);
81
+ const registryCache = /* @__PURE__ */ new Map();
82
+ function artifactForModel(modelId) {
83
+ return ARTIFACTS.find((artifact) => artifact.origin.modelIds.includes(modelId));
84
+ }
85
+ /**
86
+ * Resolve the shared offline tokenizer for one compatible API model.
87
+ * Unknown models and a cached asset/runtime failure return `undefined`; callers
88
+ * must report unavailable instead of manufacturing a character estimate.
89
+ * @param modelId - exact DeepSeek API wire model id.
90
+ * @returns the shared verified tokenizer, or undefined when unsupported/unavailable.
91
+ */
92
+ function deepSeekV4TokenizerForModel(modelId) {
93
+ const artifact = artifactForModel(modelId);
94
+ if (artifact === void 0) return void 0;
95
+ const cached = registryCache.get(artifact.origin);
96
+ if (cached !== void 0) return cached.tokenizer;
97
+ let entry;
98
+ try {
99
+ entry = { tokenizer: createDeepSeekV4TokenizerFromAssets(artifact.assetRoot, artifact.integrity, artifact.origin) };
100
+ } catch (error) {
101
+ entry = { failure: error instanceof Error ? error.message : String(error) };
102
+ }
103
+ registryCache.set(artifact.origin, entry);
104
+ return entry.tokenizer;
105
+ }
106
+ /**
107
+ * Build a tokenizer from one local asset directory after byte/hash validation.
108
+ * This provider-private seam exists so tests can prove every failure branch
109
+ * without mutating the committed artifact.
110
+ * @param assetRoot - local URL containing tokenizer.json and tokenizer_config.json.
111
+ * @param integrity - expected byte length and SHA-256 for both files.
112
+ * @param origin - auditable identity recorded on every returned count.
113
+ * @returns a synchronous exact text counter.
114
+ * @internal
115
+ */
116
+ function createDeepSeekV4TokenizerFromAssets(assetRoot, integrity, origin = DEEPSEEK_V4_TOKENIZER_ARTIFACT) {
117
+ const tokenizerJson = readVerifiedJson(assetRoot, "tokenizer.json", integrity.tokenizer);
118
+ const tokenizerConfig = readVerifiedJson(assetRoot, "tokenizer_config.json", integrity.config);
119
+ const runtime = new Tokenizer(tokenizerJson, tokenizerConfig);
120
+ return Object.freeze({ countText(text) {
121
+ const tokens = runtime.encode(text, { add_special_tokens: false }).ids.length;
122
+ return Object.freeze({
123
+ kind: "exact-tokenizer",
124
+ tokens,
125
+ tokenizerId: origin.repository,
126
+ tokenizerRevision: origin.revision
127
+ });
128
+ } });
129
+ }
130
+ function readVerifiedJson(assetRoot, name, descriptor) {
131
+ const bytes = readFileSync(new URL(name, assetRoot));
132
+ if (bytes.byteLength !== descriptor.bytes) throw new Error(`DeepSeek tokenizer asset ${name} has ${String(bytes.byteLength)} bytes; expected ${String(descriptor.bytes)}`);
133
+ if (createHash("sha256").update(bytes).digest("hex") !== descriptor.sha256) throw new Error(`DeepSeek tokenizer asset ${name} failed SHA-256 verification`);
134
+ const parsed = JSON.parse(bytes.toString("utf8"));
135
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`DeepSeek tokenizer asset ${name} must contain a JSON object`);
136
+ return parsed;
137
+ }
138
+ //#endregion
139
+ //#region src/runtime/deepseek-v4-vision-tokens.ts
140
+ /**
141
+ * Exact DeepSeek V4 Flash Vision image-token arithmetic.
142
+ *
143
+ * Every rule in this module is a line-by-line port of the official
144
+ * `inference/image_processor.py` published by
145
+ * `deepseek-ai/DeepSeek-V4-Flash-Vision-Exp` at the pinned immutable revision
146
+ * recorded below. The golden fixtures in `tests/fixtures/vision-golden.json`
147
+ * are generated by executing that official implementation, so any change here
148
+ * must keep the Node counts byte-identical to the reference output.
149
+ *
150
+ * These arithmetic results back intrinsic-grid estimates, never exact counts
151
+ * of the final request. The official expansion
152
+ * depends on the absolute serialized prompt position (system prompt,
153
+ * chat-template framing, adapter image handles) and on the adapter's final
154
+ * request-image projection — including per-route pixel-budget or image-detail
155
+ * overrides and byte-cap reprojection — none of which is published through a
156
+ * public API, and a projected image can count FEWER tokens than its intrinsic
157
+ * grid suggests. The measurement layer therefore labels image-bearing nodes
158
+ * as estimates and keeps them outside exact rewrite proofs; the 640,000-pixel
159
+ * budget below documents the adapter default rather than establishing
160
+ * exactness.
161
+ */
162
+ /** Official projection parameters pinned from the model repository config. */
163
+ const DEEPSEEK_VISION_PROJECTION = Object.freeze({
164
+ sourceRepository: "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp",
165
+ sourceRevision: "6821d6ad3681a4b137b066b76094fa82ebd0a380",
166
+ /** `vision_patch_size` from the official config. */
167
+ visionPatchSize: 14,
168
+ /** `vision_downsample_ratio` from the official config. */
169
+ visionDownsampleRatio: 3,
170
+ /** `vision_max_n_token`: post-preprocessing cap per image, not a fixed value. */
171
+ visionMaxNTokens: 384,
172
+ /** `vision_min_pixels`: tiny images are upscaled before patching. */
173
+ visionMinPixels: 147456,
174
+ /** `vision_max_wh_ratio`: wider-than-ratio images are width-clamped. */
175
+ visionMaxWhRatio: 8,
176
+ /**
177
+ * Default per-image pixel budget used by the DeepSeek adapter's normal
178
+ * attachment projection (`DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET` in
179
+ * `@deepseek-ai/dsh-llm-deepseek`). Route overrides remain unobservable to
180
+ * this estimator.
181
+ */
182
+ requestImagePixelBudget: 64e4
183
+ });
184
+ /** Stable identity for the deliberately approximate image-token counter. */
185
+ const DEEPSEEK_VISION_IMAGE_ESTIMATOR = Object.freeze({
186
+ id: `${DEEPSEEK_VISION_PROJECTION.sourceRepository}/image-token-estimate`,
187
+ revision: `${DEEPSEEK_VISION_PROJECTION.sourceRevision}:v1`
188
+ });
189
+ /** Official `COMPRESS_PAD_TO` alignment constant from image_processor.py. */
190
+ const COMPRESS_PAD_TO = 4;
191
+ /**
192
+ * Estimate one image without claiming an exact serialized position or final
193
+ * adapter projection. Valid intrinsic dimensions use the midpoint of the four
194
+ * possible alignment residues. Invalid or unsafe metadata uses the documented
195
+ * fixed fallback. In both cases the official per-image budget is retained as
196
+ * a conservative upper bound.
197
+ */
198
+ function estimateDeepSeekVisionImageTokens(width, height) {
199
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0 || !Number.isSafeInteger(width * height)) return Object.freeze({
200
+ tokens: 256,
201
+ upperBoundTokens: DEEPSEEK_VISION_PROJECTION.visionMaxNTokens,
202
+ source: "default"
203
+ });
204
+ try {
205
+ const grid = deepSeekVisionImageGrid(width, height);
206
+ const counts = [
207
+ 0,
208
+ 1,
209
+ 2,
210
+ 3
211
+ ].map((position) => deepSeekVisionImageBlockTokens(grid.nLlmH, grid.nLlmW, position));
212
+ const paddingMinimumTokens = Math.min(...counts);
213
+ const paddingMaximumTokens = Math.max(...counts);
214
+ return Object.freeze({
215
+ tokens: Math.round((paddingMinimumTokens + paddingMaximumTokens) / 2),
216
+ upperBoundTokens: DEEPSEEK_VISION_PROJECTION.visionMaxNTokens,
217
+ source: "intrinsic-grid",
218
+ paddingMinimumTokens,
219
+ paddingMaximumTokens
220
+ });
221
+ } catch {
222
+ return Object.freeze({
223
+ tokens: 256,
224
+ upperBoundTokens: DEEPSEEK_VISION_PROJECTION.visionMaxNTokens,
225
+ source: "default"
226
+ });
227
+ }
228
+ }
229
+ /**
230
+ * Resolve the aligner grid for one image's intrinsic dimensions.
231
+ *
232
+ * Port of the arithmetic path of the official `load_image`: aspect-ratio
233
+ * clamp, minimum-pixel upscale, patch-grid ceiling, and the `safe_resize`
234
+ * budget loop.
235
+ */
236
+ function deepSeekVisionImageGrid(width, height) {
237
+ const { visionPatchSize: patch, visionMaxWhRatio, visionMinPixels } = DEEPSEEK_VISION_PROJECTION;
238
+ let effectiveWidth = width;
239
+ let effectiveHeight = height;
240
+ if (visionMaxWhRatio !== void 0 && effectiveWidth > effectiveHeight * visionMaxWhRatio) effectiveWidth = effectiveHeight * visionMaxWhRatio;
241
+ if (effectiveWidth * effectiveHeight > 0 && effectiveWidth * effectiveHeight < visionMinPixels) {
242
+ const ratio = (visionMinPixels / (effectiveWidth * effectiveHeight)) ** .5;
243
+ effectiveWidth = Math.trunc(effectiveWidth * ratio);
244
+ effectiveHeight = Math.trunc(effectiveHeight * ratio);
245
+ }
246
+ const bestWidth = Math.ceil(effectiveWidth / patch) * patch;
247
+ const bestHeight = Math.ceil(effectiveHeight / patch) * patch;
248
+ const resolved = safeResize(effectiveHeight, effectiveWidth, bestHeight, bestWidth);
249
+ return Object.freeze({ ...resolved });
250
+ }
251
+ /**
252
+ * Token count of one expanded image block, port of `build_image_block` length.
253
+ * @internal exported for direct golden-fixture comparison.
254
+ */
255
+ function deepSeekVisionImageBlockTokens(nLlmH, nLlmW, startTokenPos) {
256
+ const compressPad = 3 - startTokenPos % COMPRESS_PAD_TO;
257
+ const rows = nLlmH + nLlmH % 2;
258
+ const rowLen = nLlmW + 1;
259
+ const padLast = Math.floor(rows / 2) * rowLen % 2 * 2;
260
+ return compressPad + 1 + rows * rowLen + padLast + 1;
261
+ }
262
+ /** Port of the official `grid_tokens` N-layout occupancy check. */
263
+ function gridTokens(bestHeight, bestWidth) {
264
+ const { visionPatchSize: patch, visionDownsampleRatio: downsample } = DEEPSEEK_VISION_PROJECTION;
265
+ const nLlmH = Math.ceil(Math.floor(bestHeight / patch) / downsample);
266
+ const nLlmW = Math.ceil(Math.floor(bestWidth / patch) / downsample);
267
+ let numTokens = nLlmH * (nLlmW + 1) + 2;
268
+ if (nLlmH % 2 === 1) numTokens += nLlmW + 1;
269
+ numTokens += Math.floor((nLlmH + 1) / 2) * (nLlmW + 1) % 2 * 2;
270
+ return {
271
+ nLlmH,
272
+ nLlmW,
273
+ numTokens
274
+ };
275
+ }
276
+ /** Port of the official `solve_resize_ratio` budget solver. */
277
+ function solveResizeRatio(height, width, maxNTokens) {
278
+ const { visionPatchSize: patch, visionDownsampleRatio: downsample } = DEEPSEEK_VISION_PROJECTION;
279
+ const ratio = height / width;
280
+ const maxWFloat = Math.sqrt((maxNTokens - 2) / ratio + .25) - .5;
281
+ const maxHFloat = maxWFloat * ratio;
282
+ let bestWidth;
283
+ let bestHeight;
284
+ if (maxWFloat < 1) {
285
+ const maxW = 1;
286
+ let maxH = Math.floor((maxNTokens - 2) / 2);
287
+ if (maxH % 2 === 1) maxH -= 1;
288
+ bestWidth = maxW * patch * downsample;
289
+ bestHeight = maxH * patch * downsample;
290
+ } else if (maxHFloat < 2) {
291
+ const maxH = 2;
292
+ const maxW = Math.floor((maxNTokens - 2) / maxH) - 1;
293
+ if (maxW <= 1) throw new Error("DeepSeek vision resize solver produced an invalid width");
294
+ bestWidth = maxW * patch * downsample;
295
+ bestHeight = maxH * patch * downsample;
296
+ } else {
297
+ const maxW = Math.floor(maxWFloat);
298
+ let maxH = Math.floor(maxHFloat);
299
+ if (maxH % 2 === 1) maxH -= 1;
300
+ const beta = Math.min(maxW * patch * downsample / width, maxH * patch * downsample / height);
301
+ bestWidth = Math.floor(width * beta / patch) * patch;
302
+ bestHeight = Math.floor(height * beta / patch) * patch;
303
+ }
304
+ const grid = gridTokens(bestHeight, bestWidth);
305
+ return {
306
+ nLlmH: grid.nLlmH,
307
+ nLlmW: grid.nLlmW,
308
+ bestHeight,
309
+ bestWidth
310
+ };
311
+ }
312
+ /** Port of the official `safe_resize` loop with the compress-pad budget. */
313
+ function safeResize(height, width, initialBestHeight, initialBestWidth) {
314
+ const { visionMaxNTokens } = DEEPSEEK_VISION_PROJECTION;
315
+ let budget = visionMaxNTokens - 3;
316
+ let grid = gridTokens(initialBestHeight, initialBestWidth);
317
+ let bestHeight = initialBestHeight;
318
+ let bestWidth = initialBestWidth;
319
+ while (grid.numTokens > budget) {
320
+ const solved = solveResizeRatio(height, width, budget);
321
+ grid = gridTokens(solved.bestHeight, solved.bestWidth);
322
+ bestHeight = solved.bestHeight;
323
+ bestWidth = solved.bestWidth;
324
+ budget -= 1;
325
+ }
326
+ return {
327
+ nLlmH: grid.nLlmH,
328
+ nLlmW: grid.nLlmW,
329
+ bestHeight,
330
+ bestWidth
331
+ };
332
+ }
333
+ //#endregion
334
+ //#region src/runtime/token-count.ts
335
+ /** Build an explicit unavailable result without inventing an estimate. */
336
+ function unavailableTokenCount(reason) {
337
+ if (reason.length === 0) throw new TypeError("unavailable token count requires a reason");
338
+ return Object.freeze({
339
+ kind: "unavailable",
340
+ reason
341
+ });
342
+ }
343
+ /** Sum independent canonical fields only when every count has one identity. */
344
+ function countExactCanonicalTextFields(fields, counter, subject) {
345
+ if (subject.length === 0) throw new TypeError("canonical text field count requires a subject");
346
+ const values = fields.length === 0 ? [""] : fields;
347
+ let identity;
348
+ let tokens = 0;
349
+ for (const value of values) {
350
+ const count = counter(value);
351
+ if (count.kind !== "exact-tokenizer") return unavailableTokenCount(`${subject}: ${count.kind === "unavailable" ? count.reason : "canonical content requires an exact tokenizer count"}`);
352
+ if (identity !== void 0 && (identity.tokenizerId !== count.tokenizerId || identity.tokenizerRevision !== count.tokenizerRevision)) return unavailableTokenCount(`${subject}: tokenizer identity changed within one measurement`);
353
+ identity ??= count;
354
+ tokens += count.tokens;
355
+ if (!Number.isSafeInteger(tokens) || tokens < 0) return unavailableTokenCount(`${subject}: token sum is outside the safe integer range`);
356
+ }
357
+ if (identity === void 0) return unavailableTokenCount(`${subject}: no tokenizer identity`);
358
+ return Object.freeze({
359
+ ...identity,
360
+ tokens
361
+ });
362
+ }
363
+ //#endregion
364
+ //#region src/runtime/measurement.ts
365
+ const VISION_MODEL_ID = DEEPSEEK_VISION_TOKENIZER_ARTIFACT.modelIds[0];
366
+ /**
367
+ * Capture one route-bound view without calling patched Harness methods.
368
+ * Official `measure()` remains authoritative for request pressure; the bundled
369
+ * tokenizer supplies exact canonical content counts used by safe rewrites.
370
+ */
371
+ function measureForCompaction(ctx, session) {
372
+ const header = session.requestHeader();
373
+ const measurement = ctx.tokenMeter.measure(session, header);
374
+ const target = header?.config;
375
+ const counter = bindCounter(target?.provider, target?.model);
376
+ const events = sessionEvents(session);
377
+ const measuredNodes = measurement.nodes.map((node) => {
378
+ const event = events[node.seq];
379
+ if (event === void 0) return {
380
+ seq: node.seq,
381
+ count: unavailableTokenCount(`surface node ${String(node.seq)} is missing`)
382
+ };
383
+ const message = deriveEventMessage(event);
384
+ if (message === null) return {
385
+ seq: node.seq,
386
+ count: unavailableTokenCount(`surface node ${String(node.seq)} is not model-visible`)
387
+ };
388
+ const count = countCanonicalContent(message.content, counter, `surface node ${String(node.seq)}`);
389
+ const intrinsicImageBlockEstimate = count.kind === "tokenizer-estimate" ? intrinsicImageDiagnostic(message.content, target) : void 0;
390
+ return {
391
+ seq: node.seq,
392
+ count,
393
+ ...intrinsicImageBlockEstimate === void 0 ? {} : { intrinsicImageBlockEstimate }
394
+ };
395
+ });
396
+ const currentSurface = countSurfaceCounts(measuredNodes.map((node) => node.count), "current surface");
397
+ const intrinsicImageBlockEstimateTokens = measuredNodes.reduce((sum, node) => sum + (node.intrinsicImageBlockEstimate?.paddingMinimumTokens ?? 0), 0);
398
+ return Object.freeze({
399
+ ...measurement,
400
+ ...target === void 0 ? {} : {
401
+ providerRoute: target.provider,
402
+ modelId: target.model
403
+ },
404
+ measuredNodes: Object.freeze(measuredNodes),
405
+ currentSurface,
406
+ intrinsicImageBlockEstimateTokens,
407
+ countCanonicalText: counter.countText
408
+ });
409
+ }
410
+ /**
411
+ * Count one canonical content walk in canonical field order.
412
+ *
413
+ * Text, reasoning, tool-call names/arguments, and nested text tool results are
414
+ * counted exactly with one tokenizer identity. Image blocks produce a bounded
415
+ * estimate because the absolute prompt position and the adapter's final
416
+ * projection are not publicly observable. A mixed text/image node is therefore
417
+ * an estimate and never qualifies for an exact rewrite proof.
418
+ */
419
+ function countCanonicalContent(blocks, counter, subject) {
420
+ let identity;
421
+ let estimateIdentity;
422
+ let tokens = 0;
423
+ let upperBoundTokens = 0;
424
+ let firstRefusal;
425
+ const absorb = (count) => {
426
+ if (count.kind === "unavailable") {
427
+ firstRefusal ??= count;
428
+ return false;
429
+ }
430
+ if (count.kind === "exact-tokenizer") {
431
+ if (identity !== void 0 && (identity.tokenizerId !== count.tokenizerId || identity.tokenizerRevision !== count.tokenizerRevision)) {
432
+ firstRefusal ??= unavailableTokenCount(`${subject}: tokenizer identity changed within one measurement`);
433
+ return false;
434
+ }
435
+ identity ??= count;
436
+ tokens += count.tokens;
437
+ upperBoundTokens += count.tokens;
438
+ } else {
439
+ if (estimateIdentity !== void 0 && (estimateIdentity.estimatorId !== count.estimatorId || estimateIdentity.estimatorRevision !== count.estimatorRevision)) {
440
+ firstRefusal ??= unavailableTokenCount(`${subject}: image estimator identity changed within one measurement`);
441
+ return false;
442
+ }
443
+ estimateIdentity ??= {
444
+ estimatorId: count.estimatorId,
445
+ estimatorRevision: count.estimatorRevision
446
+ };
447
+ tokens += count.tokens;
448
+ upperBoundTokens += count.upperBoundTokens;
449
+ }
450
+ return Number.isSafeInteger(tokens) && tokens >= 0 && Number.isSafeInteger(upperBoundTokens) && upperBoundTokens >= tokens;
451
+ };
452
+ const walk = (content) => {
453
+ for (const block of content) switch (block.type) {
454
+ case "text":
455
+ case "reasoning":
456
+ if (!absorb(counter.countText(block.text))) return false;
457
+ break;
458
+ case "tool-call":
459
+ if (!absorb(counter.countText(block.name))) return false;
460
+ if (!absorb(counter.countText(block.arguments))) return false;
461
+ break;
462
+ case "tool-result":
463
+ if (!walk(block.content)) return false;
464
+ break;
465
+ case "image":
466
+ if (!absorb(counter.countImage(block.attachment))) return false;
467
+ break;
468
+ default:
469
+ firstRefusal ??= unavailableTokenCount(`${subject}: contains an unsupported content block`);
470
+ return false;
471
+ }
472
+ return true;
473
+ };
474
+ if (!walk(blocks)) {
475
+ if (firstRefusal?.kind === "unavailable") return unavailableTokenCount(`${subject}: ${firstRefusal.reason}`);
476
+ return unavailableTokenCount(`${subject}: contains content the canonical counter cannot count exactly`);
477
+ }
478
+ if (identity === void 0 && estimateIdentity === void 0) {
479
+ const empty = counter.countText("");
480
+ if (empty.kind !== "exact-tokenizer") return empty;
481
+ return empty;
482
+ }
483
+ if (!Number.isSafeInteger(tokens) || tokens < 0) return unavailableTokenCount(`${subject}: invalid token sum`);
484
+ if (estimateIdentity !== void 0) return Object.freeze({
485
+ kind: "tokenizer-estimate",
486
+ ...estimateIdentity,
487
+ tokens,
488
+ upperBoundTokens
489
+ });
490
+ if (identity === void 0) return unavailableTokenCount(`${subject}: no tokenizer identity`);
491
+ return Object.freeze({
492
+ ...identity,
493
+ tokens
494
+ });
495
+ }
496
+ function bindCounter(provider, model) {
497
+ if (provider === void 0 || model === void 0) {
498
+ const unavailable = () => unavailableTokenCount("canonical text: no durable provider/model request header");
499
+ return {
500
+ countText: unavailable,
501
+ countImage: () => unavailableTokenCount("canonical image: no durable provider/model request header")
502
+ };
503
+ }
504
+ if (provider !== "deepseek" && provider !== "deepseek-official") {
505
+ const reason = `canonical text: provider "${provider}" is not the supported DeepSeek route`;
506
+ return {
507
+ countText: () => unavailableTokenCount(reason),
508
+ countImage: () => unavailableTokenCount(`canonical image: provider "${provider}" is not the supported DeepSeek route`)
509
+ };
510
+ }
511
+ const tokenizer = deepSeekV4TokenizerForModel(model);
512
+ if (tokenizer === void 0) {
513
+ const reason = `canonical text: no verified bundled tokenizer for model "${model}"`;
514
+ return {
515
+ countText: () => unavailableTokenCount(reason),
516
+ countImage: (attachment) => countCanonicalImage(model, attachment)
517
+ };
518
+ }
519
+ return {
520
+ countText: (text) => tokenizer.countText(text),
521
+ countImage: (attachment) => countCanonicalImage(model, attachment)
522
+ };
523
+ }
524
+ /**
525
+ * Images never claim an exact count. The official expansion depends on the
526
+ * absolute prompt position (system prompt, chat-template framing, adapter
527
+ * image handles) and on the adapter's final request-image projection, neither
528
+ * of which is exposed through a public API; a route may even override the
529
+ * pixel budget or re-project under the byte cap. Valid dimensions therefore
530
+ * use the midpoint of the four alignment residues as a bounded estimate;
531
+ * malformed dimensions use a fixed default. Estimate-bearing nodes remain
532
+ * ineligible for exact rewrite proofs.
533
+ */
534
+ function countCanonicalImage(model, attachment) {
535
+ if (model !== VISION_MODEL_ID) return unavailableTokenCount(`canonical image: model "${model}" has no vision image counter`);
536
+ const estimate = estimateDeepSeekVisionImageTokens(attachment.width, attachment.height);
537
+ return Object.freeze({
538
+ kind: "tokenizer-estimate",
539
+ tokens: estimate.tokens,
540
+ upperBoundTokens: estimate.upperBoundTokens,
541
+ estimatorId: DEEPSEEK_VISION_IMAGE_ESTIMATOR.id,
542
+ estimatorRevision: DEEPSEEK_VISION_IMAGE_ESTIMATOR.revision
543
+ });
544
+ }
545
+ /**
546
+ * Intrinsic-grid diagnostic for one content walk: the official block
547
+ * arithmetic on intrinsic dimensions at both alignment extremes. Only images
548
+ * on the pinned DeepSeek vision route with usable metadata contribute.
549
+ */
550
+ function intrinsicImageDiagnostic(blocks, target) {
551
+ if (target === void 0 || target.provider !== "deepseek" && target.provider !== "deepseek-official" || target.model !== VISION_MODEL_ID) return void 0;
552
+ let paddingMinimumTokens = 0;
553
+ let paddingMaximumTokens = 0;
554
+ let seen = false;
555
+ const walk = (content) => {
556
+ for (const block of content) if (block.type === "image") {
557
+ const { width, height } = block.attachment;
558
+ const estimate = estimateDeepSeekVisionImageTokens(width, height);
559
+ if (estimate.source !== "intrinsic-grid" || estimate.paddingMinimumTokens === void 0 || estimate.paddingMaximumTokens === void 0) continue;
560
+ paddingMinimumTokens += estimate.paddingMinimumTokens;
561
+ paddingMaximumTokens += estimate.paddingMaximumTokens;
562
+ seen = true;
563
+ } else if (block.type === "tool-result") walk(block.content);
564
+ };
565
+ walk(blocks);
566
+ return seen ? Object.freeze({
567
+ paddingMinimumTokens,
568
+ paddingMaximumTokens
569
+ }) : void 0;
570
+ }
571
+ function countSurfaceCounts(counts, subject) {
572
+ if (counts.length === 0) return unavailableTokenCount(`${subject}: no surface nodes`);
573
+ let identity;
574
+ let estimateIdentity;
575
+ let tokens = 0;
576
+ let upperBoundTokens = 0;
577
+ for (const count of counts) {
578
+ if (count.kind === "unavailable") return unavailableTokenCount(`${subject}: ${count.reason}`);
579
+ if (count.kind === "exact-tokenizer") {
580
+ if (identity !== void 0 && (identity.tokenizerId !== count.tokenizerId || identity.tokenizerRevision !== count.tokenizerRevision)) return unavailableTokenCount(`${subject}: tokenizer identity changed within one measurement`);
581
+ identity ??= count;
582
+ tokens += count.tokens;
583
+ upperBoundTokens += count.tokens;
584
+ } else {
585
+ if (estimateIdentity !== void 0 && (estimateIdentity.estimatorId !== count.estimatorId || estimateIdentity.estimatorRevision !== count.estimatorRevision)) return unavailableTokenCount(`${subject}: image estimator identity changed within one measurement`);
586
+ estimateIdentity ??= {
587
+ estimatorId: count.estimatorId,
588
+ estimatorRevision: count.estimatorRevision
589
+ };
590
+ tokens += count.tokens;
591
+ upperBoundTokens += count.upperBoundTokens;
592
+ }
593
+ }
594
+ if (!Number.isSafeInteger(tokens) || tokens < 0 || !Number.isSafeInteger(upperBoundTokens) || upperBoundTokens < tokens) return unavailableTokenCount(`${subject}: invalid token sum`);
595
+ if (estimateIdentity !== void 0) return Object.freeze({
596
+ kind: "tokenizer-estimate",
597
+ ...estimateIdentity,
598
+ tokens,
599
+ upperBoundTokens
600
+ });
601
+ if (identity === void 0) return unavailableTokenCount(`${subject}: no tokenizer identity`);
602
+ return Object.freeze({
603
+ ...identity,
604
+ tokens
605
+ });
606
+ }
607
+ z.object({
608
+ maxChars: z.number().step(1).min(1).default(5e4),
609
+ maxScanChars: z.number().step(1).min(1).default(25e4),
610
+ maxQueryChars: z.number().step(1).min(1).default(256)
611
+ });
612
+ const REF_PATTERN = /^session:\/\/([^/]+)\/event\/(\d+)$/;
613
+ const MAX_LINES = 1e3;
614
+ const DEFAULT_MAX_CHARS = 5e4;
615
+ const DEFAULT_MAX_SCAN_CHARS = 25e4;
616
+ const DEFAULT_MAX_QUERY_CHARS = 256;
617
+ const TRUNCATION_MARKER = "\n[context_compression_retrieve output truncated; reported lines describe the selected source range]\n";
618
+ const PROMPT = "When a compacted tool result contains a session://<session-id>/event/<seq> reference, or TailTrim contains a session://<session-id>/tailtrim/<seq> reference, use context_compression_retrieve with that exact ref and a narrow line range or query if the omitted evidence is necessary. The returned event content comes from the append-only session log, which is the source of truth.";
619
+ const OUTPUT = {
620
+ schema: { type: "string" },
621
+ render: (_args, value) => [{
622
+ type: "text",
623
+ text: value
624
+ }]
625
+ };
626
+ /**
627
+ * Register the current-session recovery tool and its stable guidance.
628
+ *
629
+ * @param ctx Plugin context providing the tool registry and system prompt.
630
+ * @param config Optional response, scan, and query bounds.
631
+ */
632
+ function installContextCompressionRetrieve(ctx, config = {}) {
633
+ const maxChars = resolvePositiveInteger("maxChars", config.maxChars, DEFAULT_MAX_CHARS);
634
+ const maxScanChars = resolvePositiveInteger("maxScanChars", config.maxScanChars, DEFAULT_MAX_SCAN_CHARS);
635
+ const maxQueryChars = resolvePositiveInteger("maxQueryChars", config.maxQueryChars, DEFAULT_MAX_QUERY_CHARS);
636
+ ctx.systemPrompt.section({
637
+ name: "tool:context-compression-retrieve",
638
+ order: 114,
639
+ text: PROMPT
640
+ });
641
+ ctx.tools.register(defineTool({
642
+ name: "context_compression_retrieve",
643
+ description: "Recover exact content from one compacted tool result or TailTrim group using its current-session session:// reference.",
644
+ parameters: {
645
+ ref: {
646
+ type: "string",
647
+ required: true,
648
+ description: "Exact session://<current-session-id>/event/<seq> or /tailtrim/<seq> reference from a placeholder."
649
+ },
650
+ query: {
651
+ type: "string",
652
+ description: "Optional case-insensitive text to search for inside the original result."
653
+ },
654
+ start_line: {
655
+ type: "integer",
656
+ description: "Optional 1-based first line for a direct slice. Defaults to 1."
657
+ },
658
+ max_lines: {
659
+ type: "integer",
660
+ description: "Maximum lines to return. Defaults to 200; maximum 1000."
661
+ }
662
+ },
663
+ output: OUTPUT,
664
+ isConcurrencySafe: () => true,
665
+ execute(args, exec) {
666
+ if (exec.agent === void 0) throw new Error("context_compression_retrieve requires an agent session");
667
+ const match = REF_PATTERN.exec(args.ref);
668
+ const tailTrimRef = parseTailTrimRef(args.ref);
669
+ if (match === null && tailTrimRef === null) throw new Error("context_compression_retrieve: ref must be session://<session-id>/(event|tailtrim)/<seq>");
670
+ if ((match?.[1] ?? tailTrimRef?.sessionId) !== String(exec.agent.id)) throw new Error("context_compression_retrieve: a compression reference may only read the caller's current session");
671
+ if (tailTrimRef !== null) return Promise.resolve(recoverTailTrim(exec.agent.session, args.ref, tailTrimRef.manifestSeq, args.query, args.start_line, args.max_lines, {
672
+ maxChars,
673
+ maxScanChars,
674
+ maxQueryChars
675
+ }));
676
+ const seq = Number(match?.[2]);
677
+ const event = sessionEvents(exec.agent.session)[seq];
678
+ if (event?.type !== "tool/result") throw new Error(`context_compression_retrieve: event ${String(seq)} is not a tool/result in the current session`);
679
+ const maxLines = resolveMaxLines(args.max_lines);
680
+ const scan = scanBlocks(event.data.message.content[0].content, maxScanChars);
681
+ const scannedLines = splitScannedLines(scan);
682
+ const lines = scannedLines.lines;
683
+ const query = args.query;
684
+ if (query !== void 0 && exceedsCodePointLimit(query, maxQueryChars)) throw new Error(`context_compression_retrieve: query must be at most ${String(maxQueryChars)} Unicode code points`);
685
+ const selected = query === void 0 || query === "" ? directSlice(lines, args.start_line ?? 1, maxLines, scan.complete, scannedLines.partialTail) : querySlice(lines, query, maxLines, scan.complete, scannedLines.partialTail);
686
+ const total = scan.complete ? String(lines.length) : `at least ${String(lines.length)}`;
687
+ const output = `${[
688
+ `source: ${args.ref}`,
689
+ `tool_call_id: ${event.data.message.source.callId}`,
690
+ `status: ${event.data.message.content[0].isError === true ? "error" : "completed"}`,
691
+ `lines: ${String(selected.start)}-${String(selected.end)} of ${total}`,
692
+ scan.complete ? "" : "note: source scan limit reached; later lines were not inspected",
693
+ selected.partialLine === void 0 ? "" : `note: line ${String(selected.partialLine)} is a partial prefix ending at the source scan limit`,
694
+ selected.omitted ? query === void 0 || query === "" ? "note: additional source lines were omitted" : "note: additional matching or neighboring lines were omitted" : "",
695
+ "--- original tool result ---"
696
+ ].filter(Boolean).join("\n")}\n${selected.text}`;
697
+ return Promise.resolve(boundCodePoints(output, maxChars));
698
+ }
699
+ }));
700
+ }
701
+ function recoverTailTrim(session, ref, manifestSeq, query, startLine, requestedMaxLines, bounds) {
702
+ const published = validatePublishedTailTrim(session, manifestSeq);
703
+ if (published === null || published.ref !== ref) throw new Error("context_compression_retrieve: ref is not a valid published TailTrim group");
704
+ if (query !== void 0 && exceedsCodePointLimit(query, bounds.maxQueryChars)) throw new Error(`context_compression_retrieve: query must be at most ${String(bounds.maxQueryChars)} Unicode code points`);
705
+ const fixedHeader = [
706
+ `source: ${ref}`,
707
+ "kind: tailtrim-group",
708
+ `records: ${String(published.roots.length)}`,
709
+ "--- original tool group (jsonl) ---"
710
+ ].join("\n");
711
+ const scanBudget = Math.max(0, bounds.maxScanChars - codePointLength$1(`${fixedHeader}\n`));
712
+ const scan = consumeChunks(renderGroupRecordChunks(published.roots), scanBudget);
713
+ const scannedLines = splitScannedLines(scan);
714
+ const maxLines = resolveMaxLines(requestedMaxLines);
715
+ const selected = query === void 0 || query === "" ? directSlice(scannedLines.lines, startLine ?? 1, maxLines, scan.complete, scannedLines.partialTail) : querySlice(scannedLines.lines, query, maxLines, scan.complete, scannedLines.partialTail);
716
+ return boundCodePoints(`${[
717
+ fixedHeader.split("\n").slice(0, 3).join("\n"),
718
+ scan.complete ? "" : "note: source scan limit reached; later records were not inspected",
719
+ selected.omitted ? "note: additional group records were omitted" : "",
720
+ "--- original tool group (jsonl) ---"
721
+ ].filter(Boolean).join("\n")}\n${selected.text}`, bounds.maxChars);
722
+ }
723
+ function resolvePositiveInteger(name, value, fallback) {
724
+ const resolved = value ?? fallback;
725
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) throw new TypeError(`tool-context-retrieve: ${name} must be a positive safe integer`);
726
+ return resolved;
727
+ }
728
+ function resolveMaxLines(value) {
729
+ const resolved = value ?? 200;
730
+ if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > MAX_LINES) throw new Error(`context_compression_retrieve: max_lines must be an integer from 1 to ${String(MAX_LINES)}`);
731
+ return resolved;
732
+ }
733
+ function scanBlocks(blocks, maxChars) {
734
+ return consumeChunks(renderBlockChunks(blocks), maxChars);
735
+ }
736
+ function* renderBlockChunks(blocks) {
737
+ let first = true;
738
+ for (const block of blocks) {
739
+ if (!first) yield "\n";
740
+ first = false;
741
+ if (block.type === "text") yield block.text;
742
+ else yield* jsonTokens(block);
743
+ }
744
+ }
745
+ function* renderGroupRecordChunks(roots) {
746
+ let first = true;
747
+ for (const root of roots) {
748
+ if (!first) yield "\n";
749
+ first = false;
750
+ const message = root.data.message;
751
+ yield* jsonTokens({
752
+ seq: root.seq,
753
+ type: root.type,
754
+ message: {
755
+ id: message.id,
756
+ role: message.role,
757
+ content: message.content,
758
+ source: message.source
759
+ }
760
+ });
761
+ }
762
+ }
763
+ function* jsonTokens(value) {
764
+ if (value === null) {
765
+ yield "null";
766
+ return;
767
+ }
768
+ switch (typeof value) {
769
+ case "string":
770
+ yield "\"";
771
+ for (const point of value) yield jsonScalar(point).slice(1, -1);
772
+ yield "\"";
773
+ return;
774
+ case "number":
775
+ case "boolean":
776
+ yield jsonScalar(value);
777
+ return;
778
+ case "object": {
779
+ if (Array.isArray(value)) {
780
+ yield "[";
781
+ for (let index = 0; index < value.length; index++) {
782
+ if (index > 0) yield ",";
783
+ yield* jsonTokens(value[index]);
784
+ }
785
+ yield "]";
786
+ return;
787
+ }
788
+ yield "{";
789
+ let first = true;
790
+ for (const key in value) {
791
+ if (!Object.hasOwn(value, key)) continue;
792
+ if (!first) yield ",";
793
+ first = false;
794
+ yield* jsonTokens(key);
795
+ yield ":";
796
+ yield* jsonTokens(value[key]);
797
+ }
798
+ yield "}";
799
+ return;
800
+ }
801
+ default: throw new TypeError("context_compression_retrieve: source content is not JSON-serializable");
802
+ }
803
+ }
804
+ function jsonScalar(value) {
805
+ return JSON.stringify(value);
806
+ }
807
+ function consumeChunks(chunks, maxChars) {
808
+ const output = [];
809
+ let remaining = maxChars;
810
+ for (const chunk of chunks) {
811
+ const prefix = codePointPrefix(chunk, remaining);
812
+ output.push(prefix.text);
813
+ remaining -= prefix.count;
814
+ if (!prefix.complete) return {
815
+ text: output.join(""),
816
+ complete: false
817
+ };
818
+ }
819
+ return {
820
+ text: output.join(""),
821
+ complete: true
822
+ };
823
+ }
824
+ function splitScannedLines(scan) {
825
+ const lines = scan.text.split("\n");
826
+ const partialTail = !scan.complete && !scan.text.endsWith("\n");
827
+ if (scan.text.endsWith("\n")) lines.pop();
828
+ return {
829
+ lines,
830
+ partialTail
831
+ };
832
+ }
833
+ function directSlice(lines, startLine, maxLines, scanComplete, partialTail) {
834
+ if (!Number.isSafeInteger(startLine) || startLine < 1) throw new Error("context_compression_retrieve: start_line must be a positive safe integer");
835
+ if (startLine > lines.length) throw new Error(`context_compression_retrieve: start_line ${String(startLine)} is ${scanComplete ? "outside the source line range" : "beyond the source scan limit"}`);
836
+ const startIndex = startLine - 1;
837
+ const selected = lines.slice(startIndex, startIndex + maxLines);
838
+ return {
839
+ text: selected.join("\n"),
840
+ start: startIndex + 1,
841
+ end: startIndex + selected.length,
842
+ omitted: startIndex > 0 || startIndex + selected.length < lines.length || !scanComplete,
843
+ ...partialTail && startIndex + selected.length === lines.length ? { partialLine: lines.length } : {}
844
+ };
845
+ }
846
+ function querySlice(lines, query, maxLines, scanComplete, partialTail) {
847
+ const needle = query.toLowerCase();
848
+ const chosen = /* @__PURE__ */ new Set();
849
+ let matched = false;
850
+ let omitted = !scanComplete;
851
+ for (let index = 0; index < lines.length; index++) {
852
+ const line = lines[index];
853
+ if (line === void 0 || !line.toLowerCase().includes(needle)) continue;
854
+ matched = true;
855
+ for (let row = Math.max(0, index - 2); row <= Math.min(lines.length - 1, index + 2); row++) {
856
+ if (chosen.has(row)) continue;
857
+ if (chosen.size >= maxLines) {
858
+ omitted = true;
859
+ continue;
860
+ }
861
+ chosen.add(row);
862
+ }
863
+ }
864
+ if (!matched) return {
865
+ text: scanComplete ? "[no matches]" : "[no matches within source scan limit]",
866
+ start: 0,
867
+ end: 0,
868
+ omitted
869
+ };
870
+ const ordered = [...chosen].sort((a, b) => a - b);
871
+ const rendered = [];
872
+ let previous = -2;
873
+ let start = 0;
874
+ let end = 0;
875
+ for (const index of ordered) {
876
+ const line = lines[index];
877
+ if (line === void 0) continue;
878
+ if (index > previous + 1) rendered.push("...");
879
+ rendered.push(`${String(index + 1)}: ${line}`);
880
+ if (start === 0) start = index + 1;
881
+ end = index + 1;
882
+ previous = index;
883
+ }
884
+ return {
885
+ text: rendered.join("\n"),
886
+ start,
887
+ end,
888
+ omitted,
889
+ ...partialTail && ordered.includes(lines.length - 1) ? { partialLine: lines.length } : {}
890
+ };
891
+ }
892
+ function boundCodePoints(text, maxChars) {
893
+ if (codePointPrefix(text, maxChars).complete) return text;
894
+ const marker = codePointPrefix(TRUNCATION_MARKER, maxChars);
895
+ if (!marker.complete) return marker.text;
896
+ return codePointPrefix(text, maxChars - marker.count).text + marker.text;
897
+ }
898
+ function codePointPrefix(text, maxChars) {
899
+ const output = [];
900
+ let count = 0;
901
+ for (const point of text) {
902
+ if (count >= maxChars) return {
903
+ text: output.join(""),
904
+ count,
905
+ complete: false
906
+ };
907
+ output.push(point);
908
+ count++;
909
+ }
910
+ return {
911
+ text: output.join(""),
912
+ count,
913
+ complete: true
914
+ };
915
+ }
916
+ function exceedsCodePointLimit(text, limit) {
917
+ let count = 0;
918
+ for (const _point of text) {
919
+ count++;
920
+ if (count > limit) return true;
921
+ }
922
+ return false;
923
+ }
924
+ function codePointLength$1(text) {
925
+ let count = 0;
926
+ for (const _point of text) count++;
927
+ return count;
928
+ }
929
+ /** Count the lines present in the original but absent from the replacement. */
930
+ function countOmittedLines(original, replacement) {
931
+ const omitted = original.split("\n").length - replacement.split("\n").length;
932
+ return omitted > 0 ? omitted : void 0;
933
+ }
934
+ /** Routed-context utilization required before capacity-pressure History may age sent history. */
935
+ const CAPACITY_PRESSURE_RATIO = .7;
936
+ //#endregion
937
+ //#region src/pruner/content.ts
938
+ function onlyTextBlock(blocks) {
939
+ return blocks.length === 1 && blocks[0]?.type === "text" ? blocks[0] : null;
940
+ }
941
+ function onlyTextBlocks(blocks) {
942
+ return blocks.every((block) => block.type === "text") ? blocks : null;
943
+ }
944
+ function countToolContent(blocks, view) {
945
+ const text = onlyTextBlocks(blocks);
946
+ if (text === null) return unavailableCount("tool result contains unsupported rich content");
947
+ return countExactCanonicalTextFields(text.map((block) => block.text), (candidate) => view.countCanonicalText(candidate), "tool result replacement");
948
+ }
949
+ function exactTokens(count) {
950
+ return count.kind === "exact-tokenizer" ? count.tokens : void 0;
951
+ }
952
+ function sameProviderMeasurementKey(left, right) {
953
+ return left.provider === right.provider && left.baseUrlClass === right.baseUrlClass && left.apiRoute === right.apiRoute && left.modelId === right.modelId && left.requestTemplateRevision === right.requestTemplateRevision && left.tokenizerRevision === right.tokenizerRevision && left.modality === right.modality;
954
+ }
955
+ function unavailableCount(reason) {
956
+ return Object.freeze({
957
+ kind: "unavailable",
958
+ reason
959
+ });
960
+ }
961
+ function recoveryMarker(sourceRef, label) {
962
+ return `\n\n[... ${label}; source=${sourceRef}; use context_compression_retrieve if needed ...]\n\n`;
963
+ }
964
+ /**
965
+ * Measure text content in Unicode code points; non-text blocks cost zero.
966
+ * @param blocks - tool-result content to measure.
967
+ * @returns total Unicode code points across text blocks.
968
+ */
969
+ function measureContent(blocks) {
970
+ let chars = 0;
971
+ for (const block of blocks) if (block.type === "text") chars += codePointLength(block.text);
972
+ return chars;
973
+ }
974
+ function pressureCost(blocks) {
975
+ let cost = 0;
976
+ for (const block of blocks) switch (block.type) {
977
+ case "text":
978
+ case "reasoning":
979
+ cost += codePointLength(block.text);
980
+ break;
981
+ case "tool-call":
982
+ cost += 256 + codePointLength(block.name) + codePointLength(block.arguments);
983
+ break;
984
+ case "tool-result":
985
+ cost += 256 + pressureCost(block.content);
986
+ break;
987
+ default: {
988
+ const serialized = JSON.stringify(block);
989
+ cost += Math.max(256, codePointLength(serialized));
990
+ }
991
+ }
992
+ return cost;
993
+ }
994
+ function nativePruneContent(blocks, thresholdChars, headChars, tailChars, marker = PRUNE_MARKER) {
995
+ const totalChars = measureContent(blocks);
996
+ if (totalChars <= thresholdChars) return null;
997
+ const markerChars = codePointLength(marker);
998
+ const safeHead = Math.max(0, Math.min(headChars, thresholdChars - markerChars));
999
+ const safeTail = Math.max(0, Math.min(tailChars, thresholdChars - markerChars - safeHead));
1000
+ const removedStart = safeHead;
1001
+ const removedEnd = totalChars - safeTail;
1002
+ const pruned = [];
1003
+ let consumed = 0;
1004
+ let markerInserted = false;
1005
+ for (const block of blocks) {
1006
+ if (block.type !== "text") {
1007
+ pruned.push(block);
1008
+ continue;
1009
+ }
1010
+ const points = Array.from(block.text);
1011
+ const blockStart = consumed;
1012
+ const blockEnd = blockStart + points.length;
1013
+ const headEnd = Math.min(points.length, Math.max(0, removedStart - blockStart));
1014
+ const tailStart = Math.min(points.length, Math.max(0, removedEnd - blockStart));
1015
+ const insertion = blockStart < removedEnd && blockEnd > removedStart && !markerInserted ? marker : "";
1016
+ if (insertion !== "") markerInserted = true;
1017
+ const text = points.slice(0, headEnd).join("") + insertion + points.slice(tailStart).join("");
1018
+ if (text !== "") pruned.push({
1019
+ ...block,
1020
+ text
1021
+ });
1022
+ consumed = blockEnd;
1023
+ }
1024
+ if (!markerInserted) return null;
1025
+ const charsAfter = measureContent(pruned);
1026
+ return charsAfter <= thresholdChars && charsAfter < totalChars ? pruned : null;
1027
+ }
1028
+ function summarize(entries) {
1029
+ return {
1030
+ pruned: entries,
1031
+ charsRemoved: entries.reduce((sum, entry) => sum + entry.charsBefore - entry.charsAfter, 0),
1032
+ tokensRemoved: entries.reduce((sum, entry) => sum + entry.tokensBefore - entry.tokensAfter, 0)
1033
+ };
1034
+ }
1035
+ function emptyResult() {
1036
+ return {
1037
+ pruned: [],
1038
+ charsRemoved: 0,
1039
+ tokensRemoved: 0
1040
+ };
1041
+ }
1042
+ //#endregion
1043
+ //#region src/pruner/session.ts
1044
+ /** Check whether the session currently has an open (unterminated) turn. */
1045
+ function hasOpenTurn(session) {
1046
+ let open = false;
1047
+ for (const event of sessionEvents(session)) if (event.type === "turn/start") open = true;
1048
+ else if (event.type === "turn/end") open = false;
1049
+ return open;
1050
+ }
1051
+ /** Walk the tool-result source chain to find the root result seq. */
1052
+ function rootToolResultSeq(session, seq) {
1053
+ const events = sessionEvents(session);
1054
+ let current = seq;
1055
+ const seen = /* @__PURE__ */ new Set();
1056
+ while (!seen.has(current)) {
1057
+ seen.add(current);
1058
+ const event = events[current];
1059
+ if (event?.type !== "tool/result" || typeof event.surfaceOp !== "object") return current;
1060
+ const previous = event.sourceEventSeqs?.[0];
1061
+ if (previous === void 0) return current;
1062
+ current = previous;
1063
+ }
1064
+ return seq;
1065
+ }
1066
+ /** Build a session:// event reference string for a given seq. */
1067
+ function sourceRef(session, seq) {
1068
+ return `session://${session.id}/event/${String(seq)}`;
1069
+ }
1070
+ /** Find the latest completed step number for a given turn. */
1071
+ function latestCompletedToolStep(session, turn) {
1072
+ let latest;
1073
+ for (const event of sessionEvents(session)) if (event.type === "step/end" && event.data.turn === turn) latest = event.data.step;
1074
+ return latest;
1075
+ }
1076
+ /** Routed provider/model when the durable request header names one route. */
1077
+ function routeAuditFact(session) {
1078
+ const header = session.requestHeader()?.config;
1079
+ if (header === void 0 || header.provider.length === 0 || header.model.length === 0) return void 0;
1080
+ return {
1081
+ provider: header.provider,
1082
+ model: header.model
1083
+ };
1084
+ }
1085
+ /** Bundled tokenizer identity for one route, when the route is eligible. */
1086
+ function tokenizerAuditFact(route) {
1087
+ const identity = route.provider === "deepseek" || route.provider === "deepseek-official" ? deepSeekV4TokenizerForModel(route.model)?.countText("") : void 0;
1088
+ if (identity?.kind === "exact-tokenizer") return { tokenizer: {
1089
+ repository: identity.tokenizerId,
1090
+ revision: identity.tokenizerRevision
1091
+ } };
1092
+ return { tokenizer: {
1093
+ repository: "unavailable",
1094
+ revision: "unavailable"
1095
+ } };
1096
+ }
1097
+ /** Check whether a snapshot candidate represents an error result. */
1098
+ function isError(candidate) {
1099
+ return candidate.event.data.message.content[0].isError === true || candidate.event.data.error !== void 0;
1100
+ }
1101
+ /** Wrap a plan list into a HistoryPlanOutcome. */
1102
+ function historyOutcome(plans) {
1103
+ return {
1104
+ kind: "planned",
1105
+ plans: [...plans]
1106
+ };
1107
+ }
1108
+ //#endregion
1109
+ //#region src/runtime/tokenpilot/locator.ts
1110
+ /** Files touched by read/grep-style tool calls inside the range. */
1111
+ const TOUCHED_FILE_TOOL = /(?:^|[-_])?(?:read|write|edit|glob|grep|view|str_replace_editor)(?:$|[-_])/i;
1112
+ /** Spill notice paths emitted by the Harness output-retention policy. */
1113
+ const SPILL_PATH = /stored at:\s*([^\s)\]]+)/g;
1114
+ /** Tool call arguments keys that commonly carry a file path. */
1115
+ const PATH_KEYS$1 = ["path", "file_path"];
1116
+ /**
1117
+ * Find the latest compaction/summary event matching the compaction id of a
1118
+ * compaction/end event. Returns undefined when the transaction cannot be
1119
+ * identified — the caller must skip rather than guess.
1120
+ */
1121
+ function findCompactionTrace(events, compactionId) {
1122
+ let trace;
1123
+ for (const event of events) if (event.type === "compaction/summary" && event.data.compactionId === compactionId) trace = {
1124
+ compactionId,
1125
+ summarySeq: event.seq,
1126
+ summaryShadowedRange: event.data.shadowedRange
1127
+ };
1128
+ return trace;
1129
+ }
1130
+ /** Extract spill file paths from one text chunk. */
1131
+ function extractSpillPaths(text) {
1132
+ const paths = [];
1133
+ for (const match of text.matchAll(SPILL_PATH)) {
1134
+ const path = match[1]?.replace(/[.,;]+$/, "");
1135
+ if (path !== void 0 && path.length > 0) paths.push(path);
1136
+ }
1137
+ return paths;
1138
+ }
1139
+ /** Extract touched file paths from one tool/call event's arguments. */
1140
+ function extractTouchedPath(name, argumentsText) {
1141
+ if (!TOUCHED_FILE_TOOL.test(name)) return void 0;
1142
+ let parsed;
1143
+ try {
1144
+ parsed = JSON.parse(argumentsText);
1145
+ } catch {
1146
+ return;
1147
+ }
1148
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1149
+ const record = parsed;
1150
+ for (const key of PATH_KEYS$1) {
1151
+ const value = record[key];
1152
+ if (typeof value === "string" && value.length > 0) return value;
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Build the Exact Sources block for one shadowed range, or null when the
1157
+ * range locates nothing concrete (no spill files and no touched files).
1158
+ */
1159
+ function buildLocatorBlock(events, shadowedRange) {
1160
+ const spillFiles = /* @__PURE__ */ new Set();
1161
+ const touchedFiles = /* @__PURE__ */ new Set();
1162
+ for (let seq = shadowedRange.start; seq <= shadowedRange.end && seq < events.length; seq += 1) {
1163
+ const event = events[seq];
1164
+ if (event === void 0) continue;
1165
+ if (event.type === "tool/call") {
1166
+ const path = extractTouchedPath(event.data.name, event.data.arguments);
1167
+ if (path !== void 0) touchedFiles.add(path);
1168
+ continue;
1169
+ }
1170
+ if (event.type === "tool/result" || event.type === "user/message") {
1171
+ const data = event.data;
1172
+ const content = Array.isArray(data.content) ? data.content : data.message?.content;
1173
+ if (!Array.isArray(content)) continue;
1174
+ for (const block of content) if (block.type === "text") for (const path of extractSpillPaths(block.text)) spillFiles.add(path);
1175
+ }
1176
+ }
1177
+ if (spillFiles.size === 0 && touchedFiles.size === 0) return null;
1178
+ return {
1179
+ text: [
1180
+ "## Exact Sources (locators)",
1181
+ `- seq range: ${String(shadowedRange.start)}-${String(shadowedRange.end)}`,
1182
+ ...[...spillFiles].map((path) => `- spill file: ${path}`),
1183
+ ...[...touchedFiles].map((path) => `- file touched: ${path}`),
1184
+ "(Use `read <spill file>` or `context_compression_retrieve` with a `session://` source to restore exact text.)"
1185
+ ].join("\n"),
1186
+ spillFiles: spillFiles.size,
1187
+ touchedFiles: touchedFiles.size
1188
+ };
1189
+ }
1190
+ //#endregion
1191
+ //#region src/runtime/tokenpilot/read-state.ts
1192
+ /** Write-style tool names whose success supersedes earlier reads. */
1193
+ const WRITE_TOOLS = /(?:^|[-_])?(?:write|edit|apply_patch|file_write|file_edit|str_replace|replace|multiedit)(?:$|[-_])/i;
1194
+ const PATH_KEYS = ["path", "file_path"];
1195
+ /** Parse one path out of a tool-call arguments JSON blob. */
1196
+ function toolCallPath(argumentsText) {
1197
+ let parsed;
1198
+ try {
1199
+ parsed = JSON.parse(argumentsText);
1200
+ } catch {
1201
+ return;
1202
+ }
1203
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1204
+ const record = parsed;
1205
+ for (const key of PATH_KEYS) {
1206
+ const value = record[key];
1207
+ if (typeof value === "string" && value.length > 0) return value;
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Decide whether an oversized read result was superseded by a later mutation
1212
+ * of the same file. `readPath` is the read call's target path; events after
1213
+ * `readSeq` are scanned for a write-style call on it.
1214
+ */
1215
+ function isSupersededRead(events, readSeq, readPath) {
1216
+ if (readPath === void 0) return false;
1217
+ for (let seq = readSeq + 1; seq < events.length; seq += 1) {
1218
+ const event = events[seq];
1219
+ if (event?.type !== "tool/call") continue;
1220
+ if (!WRITE_TOOLS.test(event.data.name)) continue;
1221
+ if (toolCallPath(event.data.arguments) === readPath) return true;
1222
+ }
1223
+ return false;
1224
+ }
1225
+ /** Error/warning/info line classifiers used by the omission summary. */
1226
+ const ERROR_LINE = /\b(error|failed|failure|fatal|exception|traceback|cannot|unable|denied)\b/i;
1227
+ const WARN_LINE = /\b(warn|warning|deprecated)\b/i;
1228
+ /**
1229
+ * Cluster one omitted line-count into an error/warn/info census appended to a
1230
+ * placeholder marker, giving the model meta-knowledge about what was dropped.
1231
+ */
1232
+ function clusterOmittedLines(text, omittedLines) {
1233
+ if (omittedLines <= 0) return void 0;
1234
+ let errors = 0;
1235
+ let warns = 0;
1236
+ let infos = 0;
1237
+ for (const line of text.split("\n")) if (ERROR_LINE.test(line)) errors += 1;
1238
+ else if (WARN_LINE.test(line)) warns += 1;
1239
+ else infos += 1;
1240
+ const parts = [];
1241
+ if (errors > 0) parts.push(`${String(errors)} error`);
1242
+ if (warns > 0) parts.push(`${String(warns)} warn`);
1243
+ if (infos > 0) parts.push(`${String(infos)} info`);
1244
+ if (parts.length === 0) return void 0;
1245
+ return `${String(omittedLines)} lines omitted (${parts.join(", ")})`;
1246
+ }
1247
+ //#endregion
1248
+ //#region src/runtime/tokenpilot/estimator.ts
1249
+ /** Exponential backoff with a 5-minute cap: 1s, 2s, 4s, … */
1250
+ function backoffCooldownMs(failures) {
1251
+ return Math.min(3e5, 1e3 * 2 ** Math.max(0, failures - 1));
1252
+ }
1253
+ function isCoolingDown(state, now) {
1254
+ return state !== void 0 && state.cooldownUntil > now;
1255
+ }
1256
+ function buildEstimatorSystemPrompt() {
1257
+ return [
1258
+ "You are a session residual-utility estimator.",
1259
+ "For each numbered historical file read, decide whether the live agent is likely to",
1260
+ "reference that exact file state again later in the session. Reads whose file was",
1261
+ "already rewritten, or whose task has visibly moved on, are expired.",
1262
+ "Answer with ONLY a JSON array: [{\"seq\":<number>,\"expired\":<boolean>}]."
1263
+ ].join(" ");
1264
+ }
1265
+ function buildEstimatorUserPrompt(samples) {
1266
+ return samples.map((sample) => `{"seq":${String(sample.seq)},"path":${JSON.stringify(sample.path)},"turn":${String(sample.turn)}}`).join("\n");
1267
+ }
1268
+ /** Parse the estimator answer; anything malformed yields no verdicts. */
1269
+ function parseEstimatorAnswer(text) {
1270
+ const start = text.indexOf("[");
1271
+ const end = text.lastIndexOf("]");
1272
+ if (start < 0 || end <= start) return [];
1273
+ try {
1274
+ const parsed = JSON.parse(text.slice(start, end + 1));
1275
+ if (!Array.isArray(parsed)) return [];
1276
+ const verdicts = [];
1277
+ for (const entry of parsed) {
1278
+ if (typeof entry !== "object" || entry === null) continue;
1279
+ const record = entry;
1280
+ if (typeof record.seq !== "number" || typeof record.expired !== "boolean") continue;
1281
+ verdicts.push({
1282
+ seq: record.seq,
1283
+ expired: record.expired
1284
+ });
1285
+ }
1286
+ return verdicts;
1287
+ } catch {
1288
+ return [];
1289
+ }
1290
+ }
1291
+ /** One channel-bound estimator. `ask` resolves undefined on any failure. */
1292
+ var Estimator = class {
1293
+ ctx;
1294
+ options;
1295
+ constructor(ctx, options) {
1296
+ this.ctx = ctx;
1297
+ this.options = options;
1298
+ }
1299
+ get enabled() {
1300
+ return this.options.estimatorMode === "host" || this.options.estimatorMode === "direct";
1301
+ }
1302
+ async ask(system, user, signal) {
1303
+ const timeoutMs = this.options.estimatorTimeoutMs ?? 3e3;
1304
+ const timeout = AbortSignal.timeout(timeoutMs);
1305
+ const signal2 = typeof AbortSignal.any === "function" ? AbortSignal.any([signal, timeout]) : timeout;
1306
+ try {
1307
+ if (this.options.estimatorMode === "host") return await this.askHost(system, user, signal2);
1308
+ if (this.options.estimatorMode === "direct") return await this.askDirect(system, user, signal2);
1309
+ return;
1310
+ } catch {
1311
+ return;
1312
+ }
1313
+ }
1314
+ async askHost(system, user, signal) {
1315
+ let llm;
1316
+ try {
1317
+ llm = this.ctx.get("llm");
1318
+ } catch {
1319
+ return;
1320
+ }
1321
+ if (llm?.stream === void 0) return void 0;
1322
+ const provider = this.options.estimatorProvider ?? "";
1323
+ const model = this.options.estimatorModel ?? "";
1324
+ if (provider.length === 0 || model.length === 0) return void 0;
1325
+ let text = "";
1326
+ const stream = llm.stream({
1327
+ provider,
1328
+ model,
1329
+ messages: [{
1330
+ role: "user",
1331
+ content: [{
1332
+ type: "text",
1333
+ text: user
1334
+ }]
1335
+ }],
1336
+ system,
1337
+ temperature: 0,
1338
+ reasoningEffort: "off",
1339
+ maxTokens: 256,
1340
+ signal
1341
+ });
1342
+ for await (const chunk of stream) if ((chunk.type === "text-delta" || chunk.type === "reasoning-delta") && typeof chunk.text === "string") text += chunk.text;
1343
+ else if (chunk.type === "finish" && chunk.text === void 0) break;
1344
+ return text.trim().length > 0 ? text : void 0;
1345
+ }
1346
+ async askDirect(system, user, signal) {
1347
+ const baseUrl = this.options.estimatorBaseUrl;
1348
+ if (baseUrl === void 0 || baseUrl.length === 0) return void 0;
1349
+ const headers = { "content-type": "application/json" };
1350
+ if (this.options.estimatorApiKey !== void 0 && this.options.estimatorApiKey.length > 0) headers.authorization = `Bearer ${this.options.estimatorApiKey}`;
1351
+ const model = this.options.estimatorModel ?? "";
1352
+ if (model.length === 0) return void 0;
1353
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/chat/completions`, {
1354
+ method: "POST",
1355
+ headers,
1356
+ body: JSON.stringify({
1357
+ model,
1358
+ messages: [{
1359
+ role: "system",
1360
+ content: system
1361
+ }, {
1362
+ role: "user",
1363
+ content: user
1364
+ }],
1365
+ temperature: 0,
1366
+ max_tokens: 256
1367
+ }),
1368
+ signal
1369
+ });
1370
+ if (!response.ok) return void 0;
1371
+ const text = (await response.json()).choices?.[0]?.message?.content;
1372
+ return typeof text === "string" && text.trim().length > 0 ? text : void 0;
1373
+ }
1374
+ };
1375
+ //#endregion
1376
+ //#region src/runtime/tokenpilot/dedup.ts
1377
+ /**
1378
+ * TokenPilot-inspired A1: byte-identical repeated tool-result dedup.
1379
+ *
1380
+ * Pure helpers behind the ToolResultPruner fresh pass. The per-session table
1381
+ * maps a canonical-content SHA-256 to the first surface seq that produced it;
1382
+ * later identical results may be replaced with a pointer placeholder that the
1383
+ * recovery tool can resolve back to the original full text via the append-only
1384
+ * session log. Only hash+seq metadata is stored — never content.
1385
+ */
1386
+ /** Per-session dedup index with insertion-order eviction. */
1387
+ var DedupeTable = class {
1388
+ maxEntries;
1389
+ entries = /* @__PURE__ */ new Map();
1390
+ constructor(maxEntries = 2048) {
1391
+ this.maxEntries = maxEntries;
1392
+ }
1393
+ /** Look up the first occurrence for one canonical hash, if any. */
1394
+ get(hash) {
1395
+ return this.entries.get(hash);
1396
+ }
1397
+ /** Record a first occurrence; existing hashes only refresh insertion order. */
1398
+ record(hash, entry) {
1399
+ if (this.entries.has(hash)) return;
1400
+ while (this.entries.size >= this.maxEntries) {
1401
+ const oldest = this.entries.keys().next().value;
1402
+ if (oldest === void 0) break;
1403
+ this.entries.delete(oldest);
1404
+ }
1405
+ this.entries.set(hash, entry);
1406
+ }
1407
+ };
1408
+ /** Canonicalize tool-result text for hashing. */
1409
+ function canonicalizeForDedupe(text, mode) {
1410
+ if (mode === "exact") return text;
1411
+ return text.replace(/[ \t]+\r?\n/g, "\n").replace(/(^\s+)|(\s+$)/g, "");
1412
+ }
1413
+ /** SHA-256 hex of the canonicalized text. */
1414
+ function dedupeHash(text, mode) {
1415
+ return createHash("sha256").update(canonicalizeForDedupe(text, mode), "utf8").digest("hex");
1416
+ }
1417
+ /** Concatenated text of an all-text content block list; null when rich. */
1418
+ function flattenPlainText(content) {
1419
+ let text = "";
1420
+ for (const block of content) {
1421
+ if (block.type !== "text") return void 0;
1422
+ text += block.text;
1423
+ }
1424
+ return text;
1425
+ }
1426
+ /** Pointer placeholder pointing at the first occurrence's original event. */
1427
+ function dedupePlaceholder(entry, originalChars) {
1428
+ return [
1429
+ `[... identical to the earlier ${entry.toolName} result; first seen at ${entry.sourceRef};`,
1430
+ `original_chars=${String(originalChars)};`,
1431
+ "use context_compression_retrieve with this source if the omitted evidence is necessary.]"
1432
+ ].join(" ");
1433
+ }
1434
+ //#endregion
1435
+ //#region src/runtime/reducers.ts
1436
+ /** Deterministic, evidence-backed reducers for fresh tool results. */
1437
+ const ANSI_PATTERN = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001B\\))/gu;
1438
+ const IMPORTANT_PATTERN = new RegExp([
1439
+ String.raw`\b(?:error|failed|failure|fatal|panic|exception|warning|warn|conflict|denied|forbidden|`,
1440
+ String.raw`timeout|timed out|not found|cannot|unable|invalid|exit(?:ed)?\s+(?:code|status)|traceback|`,
1441
+ String.raw`assert(?:ion)?|segmentation fault|oom|out of memory)\b`
1442
+ ].join(""), "i");
1443
+ const STATUS_PATTERN = new RegExp([String.raw`\b(?:success|succeeded|passed|installed|added|removed|updated|built|compiled|`, String.raw`tests?\s+(?:passed|failed)|exit(?:ed)?\s+(?:code|status))\b`].join(""), "i");
1444
+ const PATH_LINE_PATTERN = /^(.*?):(\d+)(?::\d+)?(?::|\s+-\s+)(.*)$/;
1445
+ const GIT_STATUS_PATTERN = new RegExp([String.raw`^(?:On branch|Your branch|HEAD detached|Changes |Untracked |Unmerged |\s*(?:modified|deleted|`, String.raw`new file|renamed|both modified):)`].join(""), "i");
1446
+ const CODE_IMPORT_PATTERN = new RegExp([String.raw`^\s*(?:import\b|from\s+[\w.]+\s+import\b|use\s+\w|package\s+|#include\b|`, String.raw`using\s+[\w.]+;|require\s*\(|extern\s+crate\b)`].join(""));
1447
+ const CODE_STRUCTURE_PATTERN = new RegExp([
1448
+ String.raw`^\s*(?:@[\w.]+|export\s+|default\s+|declare\s+|abstract\s+|public\s+|private\s+|protected\s+|`,
1449
+ String.raw`internal\s+|static\s+|final\s+|sealed\s+|override\s+|pub(?:\([^)]*\))?\s+|async\s+|unsafe\s+)*`,
1450
+ String.raw`(?:function\b|class\b|interface\b|enum\b|struct\b|impl\b|trait\b|type\s+\w|fn\s|func\b|`,
1451
+ String.raw`def\s|module\b|namespace\b|sub\s)`
1452
+ ].join(""));
1453
+ const PYTHON_STRUCTURE_PATTERN = /^\s*(?:async\s+)?def\s|^\s*class\s/;
1454
+ const CODE_DECORATOR_PATTERN = /^\s*@[\w.]+/;
1455
+ const CODE_COMMENT_PATTERN = /^\s*(?:\/\/|#|\/\*|\*)/;
1456
+ /**
1457
+ * Select a reducer from verified tool, command, and content evidence.
1458
+ * @param input - original result text, recovery source, and output budget.
1459
+ * @returns a verified candidate, or `null` when every reducer fails open.
1460
+ */
1461
+ function reduceFreshToolResult(input) {
1462
+ const normalized = normalizeTerminalText(input.text);
1463
+ const prepared = {
1464
+ ...input,
1465
+ text: normalized
1466
+ };
1467
+ const command = extractCommand(input.argumentsText);
1468
+ const name = input.toolName.toLowerCase();
1469
+ const candidates = [];
1470
+ if (looksLikeJson(normalized)) candidates.push(() => reduceJson(prepared));
1471
+ if (isSearchTool(name, command)) candidates.push(() => reduceSearch(prepared));
1472
+ if (isGitCommand(name, command)) candidates.push(() => reduceGit(prepared, command));
1473
+ if (isPackageCommand(command)) candidates.push(() => reducePatternLog(prepared, "hypa-package", packagePattern()));
1474
+ if (isBuildOrTestCommand(command)) candidates.push(() => reducePatternLog(prepared, "hypa-build-test", buildPattern()));
1475
+ if (input.codeSkeleton === true && looksLikeSourceCode(normalized)) candidates.push(() => reduceCodeSkeleton(prepared));
1476
+ if (isReadTool(name)) candidates.push(() => reduceHead(prepared, "pi-head"));
1477
+ if (isShellTool(name) || command !== "") candidates.push(() => reduceShell(prepared));
1478
+ candidates.push(() => reduceSalient(prepared, "generic-salience"));
1479
+ for (const make of candidates) {
1480
+ const candidate = make();
1481
+ if (candidate !== null && verifyReduction(input, candidate)) return candidate;
1482
+ }
1483
+ return null;
1484
+ }
1485
+ /**
1486
+ * Build a recoverable placeholder for an old tool result.
1487
+ * @param input - tool identity, source reference, size, status, and retained evidence.
1488
+ * @returns a lossy placeholder that cites the immutable source event.
1489
+ */
1490
+ function historicalPlaceholder(input) {
1491
+ const anchor = input.compact ? "" : importantAnchor(input.text, 360);
1492
+ const lines = [
1493
+ "[Old tool result content cleared from active context]",
1494
+ `tool: ${input.toolName || "unknown"}`,
1495
+ `status: ${input.isError ? "error" : "completed"}`,
1496
+ `original_chars: ${String(input.charsBefore)}`,
1497
+ `source: ${input.sourceRef}`,
1498
+ "retrieve: context_compression_retrieve({\"ref\":\"" + input.sourceRef + "\"})"
1499
+ ];
1500
+ if (anchor !== "") lines.push(`retained_anchor: ${anchor}`);
1501
+ return {
1502
+ text: lines.join("\n"),
1503
+ reducer: input.compact ? "pair-preserving-tail-aging" : "historical-tool-result-aging",
1504
+ lossy: true
1505
+ };
1506
+ }
1507
+ /**
1508
+ * Validate shrinkage, budget, recovery, and error retention.
1509
+ * @param input - original reducer input and its safety requirements.
1510
+ * @param output - candidate reduced text and reducer metadata.
1511
+ * @returns whether the candidate is safe to land.
1512
+ */
1513
+ function verifyReduction(input, output) {
1514
+ const before = codePointLength(input.text);
1515
+ const after = codePointLength(output.text);
1516
+ if (after <= 0 || after >= before || after > input.budgetChars) return false;
1517
+ if (output.lossy && !output.text.includes(input.sourceRef)) return false;
1518
+ if ((input.isError || IMPORTANT_PATTERN.test(input.text)) && !IMPORTANT_PATTERN.test(output.text) && !output.text.includes("status: error")) return false;
1519
+ return true;
1520
+ }
1521
+ /**
1522
+ * Strip ANSI, collapse carriage-return progress redraws, and fold exact repeats.
1523
+ * @param text - raw terminal output.
1524
+ * @returns normalized terminal text.
1525
+ */
1526
+ function normalizeTerminalText(text) {
1527
+ const logical = text.replace(ANSI_PATTERN, "").split("\n").map((line) => {
1528
+ return line.split("\r").filter((part) => part !== "").at(-1) ?? "";
1529
+ });
1530
+ const folded = [];
1531
+ let previous;
1532
+ let count = 0;
1533
+ const flush = () => {
1534
+ if (previous === void 0) return;
1535
+ folded.push(previous);
1536
+ if (count > 1) folded.push(`[previous line repeated ${String(count - 1)} more times]`);
1537
+ };
1538
+ for (const line of logical) {
1539
+ if (line === previous) {
1540
+ count++;
1541
+ continue;
1542
+ }
1543
+ flush();
1544
+ previous = line;
1545
+ count = 1;
1546
+ }
1547
+ flush();
1548
+ return folded.join("\n");
1549
+ }
1550
+ function reduceHead(input, reducer) {
1551
+ const marker = omissionMarker(input, reducer);
1552
+ const available = input.budgetChars - codePointLength(marker) - 1;
1553
+ if (available <= 0) return null;
1554
+ const head = takeWholeLinesFromHead(input.text, available);
1555
+ if (head === input.text || head === "") return null;
1556
+ return {
1557
+ text: `${head}\n${marker}`,
1558
+ reducer,
1559
+ lossy: true
1560
+ };
1561
+ }
1562
+ function reduceTail(input, reducer) {
1563
+ const marker = omissionMarker(input, reducer);
1564
+ const available = input.budgetChars - codePointLength(marker) - 1;
1565
+ if (available <= 0) return null;
1566
+ const tail = takeWholeLinesFromTail(input.text, available);
1567
+ if (tail === input.text || tail === "") return null;
1568
+ return {
1569
+ text: `${marker}\n${tail}`,
1570
+ reducer,
1571
+ lossy: true
1572
+ };
1573
+ }
1574
+ function reduceJson(input) {
1575
+ let value;
1576
+ try {
1577
+ value = JSON.parse(input.text);
1578
+ } catch {
1579
+ return null;
1580
+ }
1581
+ const minified = JSON.stringify(value);
1582
+ if (codePointLength(minified) < codePointLength(input.text) && codePointLength(minified) <= input.budgetChars) return {
1583
+ text: minified,
1584
+ reducer: "json-minify",
1585
+ lossy: false
1586
+ };
1587
+ const envelope = {
1588
+ $dsh_compression: {
1589
+ kind: "json-preview",
1590
+ source: input.sourceRef,
1591
+ original_chars: codePointLength(input.text)
1592
+ },
1593
+ value: shrinkJson(value, 0)
1594
+ };
1595
+ const text = JSON.stringify(envelope, null, 2);
1596
+ if (codePointLength(text) <= input.budgetChars) return {
1597
+ text,
1598
+ reducer: "json-structure-preview",
1599
+ lossy: true
1600
+ };
1601
+ return null;
1602
+ }
1603
+ function shrinkJson(value, depth) {
1604
+ if (depth >= 5) {
1605
+ if (Array.isArray(value)) return `[array length=${String(value.length)} omitted]`;
1606
+ if (typeof value === "object" && value !== null) return "[object omitted]";
1607
+ return value;
1608
+ }
1609
+ if (Array.isArray(value)) {
1610
+ if (value.length <= 8) return value.map((entry) => shrinkJson(entry, depth + 1));
1611
+ return [
1612
+ ...value.slice(0, 3).map((entry) => shrinkJson(entry, depth + 1)),
1613
+ { $dsh_omitted_items: value.length - 5 },
1614
+ ...value.slice(-2).map((entry) => shrinkJson(entry, depth + 1))
1615
+ ];
1616
+ }
1617
+ if (typeof value !== "object" || value === null) {
1618
+ if (typeof value === "string" && codePointLength(value) > 800) return `${Array.from(value).slice(0, 500).join("")}…[${String(codePointLength(value) - 700)} chars omitted]…${Array.from(value).slice(-200).join("")}`;
1619
+ return value;
1620
+ }
1621
+ const entries = Object.entries(value);
1622
+ const important = entries.filter(([key]) => /error|warn|status|code|message|path|file|line|summary/i.test(key));
1623
+ const selected = entries.length <= 18 ? entries : [
1624
+ ...entries.slice(0, 10),
1625
+ ...important.filter((entry) => !entries.slice(0, 10).includes(entry)).slice(0, 6),
1626
+ ...entries.slice(-2)
1627
+ ];
1628
+ const result = {};
1629
+ for (const [key, entry] of selected) result[key] = shrinkJson(entry, depth + 1);
1630
+ if (selected.length < entries.length) result.$dsh_omitted_keys = entries.length - selected.length;
1631
+ return result;
1632
+ }
1633
+ function reduceSearch(input) {
1634
+ const lines = splitLines(input.text);
1635
+ const groups = /* @__PURE__ */ new Map();
1636
+ const ungrouped = [];
1637
+ for (const line of lines) {
1638
+ const match = PATH_LINE_PATTERN.exec(line);
1639
+ const row = {
1640
+ line,
1641
+ important: IMPORTANT_PATTERN.test(line)
1642
+ };
1643
+ if (match === null) {
1644
+ ungrouped.push(row);
1645
+ continue;
1646
+ }
1647
+ const path = match[1] ?? "<unknown>";
1648
+ const bucket = groups.get(path) ?? [];
1649
+ bucket.push(row);
1650
+ groups.set(path, bucket);
1651
+ }
1652
+ if (groups.size === 0) return reduceSalient(input, "search-salience");
1653
+ const selected = [];
1654
+ let omitted = 0;
1655
+ for (const [path, rows] of groups) {
1656
+ const keep = /* @__PURE__ */ new Set([0, rows.length - 1]);
1657
+ rows.forEach((row, index) => {
1658
+ if (row.important) keep.add(index);
1659
+ });
1660
+ for (let index = 0; index < rows.length && keep.size < 5; index++) keep.add(index);
1661
+ const indexes = [...keep].filter((index) => index >= 0).sort((a, b) => a - b);
1662
+ selected.push(`## ${path} (${String(rows.length)} matches)`);
1663
+ for (const index of indexes) {
1664
+ const row = rows[index];
1665
+ if (row !== void 0) selected.push(row.line);
1666
+ }
1667
+ omitted += rows.length - indexes.length;
1668
+ }
1669
+ for (const row of ungrouped.filter((row) => row.important).slice(0, 12)) selected.push(row.line);
1670
+ const text = fitLines([`[search results compressed; ${String(omitted)} matches omitted; source: ${input.sourceRef}]`, ...selected], input.budgetChars, input.sourceRef);
1671
+ return text === null ? null : {
1672
+ text,
1673
+ reducer: "search-by-file",
1674
+ lossy: true
1675
+ };
1676
+ }
1677
+ function reduceGit(input, command) {
1678
+ const lines = splitLines(input.text);
1679
+ const lower = command.toLowerCase();
1680
+ let keep;
1681
+ let reducer;
1682
+ if (/\bgit\s+(?:diff|show)\b/.test(lower)) {
1683
+ reducer = "hypa-git-diff";
1684
+ keep = lines.filter((line) => /^(?:diff --git|index |--- |\+\+\+ |@@ |[+-](?![+-]))/.test(line) || IMPORTANT_PATTERN.test(line));
1685
+ } else if (/\bgit\s+(?:status|switch|checkout|merge|rebase|cherry-pick)\b/.test(lower)) {
1686
+ reducer = "hypa-git-status";
1687
+ keep = lines.filter((line) => GIT_STATUS_PATTERN.test(line) || IMPORTANT_PATTERN.test(line));
1688
+ } else {
1689
+ reducer = "hypa-git-log";
1690
+ keep = lines.filter((line) => /^(?:commit\s+[0-9a-f]+|Author:|Date:|[0-9a-f]{7,}\s)/i.test(line) || IMPORTANT_PATTERN.test(line));
1691
+ }
1692
+ if (keep.length === 0) return reduceSalient(input, reducer);
1693
+ const text = fitLines([
1694
+ `[git output compressed; source: ${input.sourceRef}]`,
1695
+ ...keep,
1696
+ ...lines.slice(-8)
1697
+ ], input.budgetChars, input.sourceRef);
1698
+ return text === null ? null : {
1699
+ text,
1700
+ reducer,
1701
+ lossy: true
1702
+ };
1703
+ }
1704
+ function reducePatternLog(input, reducer, pattern) {
1705
+ const lines = splitLines(input.text);
1706
+ const important = lines.filter((line) => pattern.test(line) || IMPORTANT_PATTERN.test(line) || STATUS_PATTERN.test(line));
1707
+ const text = fitLines([
1708
+ `[command output compressed by ${reducer}; source: ${input.sourceRef}]`,
1709
+ ...important,
1710
+ ...lines.slice(-20)
1711
+ ], input.budgetChars, input.sourceRef);
1712
+ return text === null ? null : {
1713
+ text,
1714
+ reducer,
1715
+ lossy: true
1716
+ };
1717
+ }
1718
+ function reduceShell(input) {
1719
+ const lines = splitLines(input.text);
1720
+ const important = lines.filter((line) => IMPORTANT_PATTERN.test(line));
1721
+ if (important.length === 0) return reduceTail(input, "pi-tail");
1722
+ const text = fitLines([
1723
+ `[shell/log output compressed; source: ${input.sourceRef}]`,
1724
+ ...important,
1725
+ "--- final output ---",
1726
+ ...lines.slice(-40)
1727
+ ], input.budgetChars, input.sourceRef);
1728
+ return text === null ? null : {
1729
+ text,
1730
+ reducer: "shell-salience-tail",
1731
+ lossy: true
1732
+ };
1733
+ }
1734
+ function reduceSalient(input, reducer) {
1735
+ const lines = splitLines(input.text);
1736
+ if (lines.length < 3) return reduceHead(input, reducer);
1737
+ const marker = omissionMarker(input, reducer);
1738
+ const headBudget = Math.max(1, Math.floor((input.budgetChars - codePointLength(marker)) * .34));
1739
+ const tailBudget = headBudget;
1740
+ const head = takeWholeLinesFromHead(input.text, headBudget);
1741
+ const tail = takeWholeLinesFromTail(input.text, tailBudget);
1742
+ const text = fitLines([
1743
+ head,
1744
+ ...lines.filter((line) => IMPORTANT_PATTERN.test(line) || STATUS_PATTERN.test(line)).slice(0, 24),
1745
+ marker,
1746
+ tail
1747
+ ], input.budgetChars, input.sourceRef);
1748
+ return text === null ? null : {
1749
+ text,
1750
+ reducer,
1751
+ lossy: true
1752
+ };
1753
+ }
1754
+ /**
1755
+ * Keep a source-file skeleton: imports, decorators, declaration signatures,
1756
+ * comments at brace depth zero, and every error-signalling line, eliding the
1757
+ * remaining bodies with counted markers. Covers brace languages (TS/JS, Rust,
1758
+ * Go, Java, C family) and indent blocks (Python); unknown syntax fails open to
1759
+ * the next candidate. Output is compressed evidence, not required to parse.
1760
+ * @param input - original result text, recovery source, and output budget.
1761
+ * @returns a verified candidate, or `null` when the text is not code-like.
1762
+ */
1763
+ function reduceCodeSkeleton(input) {
1764
+ const lines = splitLines(input.text);
1765
+ const kept = [];
1766
+ let elided = 0;
1767
+ const flushElided = () => {
1768
+ if (elided > 0) kept.push(`[... ${String(elided)} lines elided ...]`);
1769
+ elided = 0;
1770
+ };
1771
+ let depth = 0;
1772
+ let index = 0;
1773
+ const elideBraceBody = () => {
1774
+ const startDepth = depth;
1775
+ index += 1;
1776
+ while (index < lines.length && depth > startDepth) {
1777
+ const body = lines[index];
1778
+ if (body === void 0) break;
1779
+ if (IMPORTANT_PATTERN.test(body)) {
1780
+ flushElided();
1781
+ kept.push(body);
1782
+ } else elided += 1;
1783
+ depth += braceDelta(body);
1784
+ index += 1;
1785
+ }
1786
+ flushElided();
1787
+ };
1788
+ const keepPythonSignature = (signatureLine) => {
1789
+ index += 1;
1790
+ if (/:\s*$/.test(signatureLine)) {
1791
+ elideIndentedBody(leadingIndent(signatureLine));
1792
+ return;
1793
+ }
1794
+ for (let guard = 0; guard < 6 && index < lines.length; guard += 1) {
1795
+ const next = lines[index];
1796
+ if (next === void 0) break;
1797
+ if (next.trim() !== "" && leadingIndent(next) <= leadingIndent(signatureLine)) break;
1798
+ flushElided();
1799
+ kept.push(next);
1800
+ index += 1;
1801
+ if (/:\s*$/.test(next)) {
1802
+ elideIndentedBody(leadingIndent(next));
1803
+ return;
1804
+ }
1805
+ if (next.trim() !== "" && !/[:,(]\s*$/.test(next)) break;
1806
+ }
1807
+ };
1808
+ const elideIndentedBody = (indent) => {
1809
+ while (index < lines.length) {
1810
+ const body = lines[index];
1811
+ if (body === void 0) break;
1812
+ if (body.trim() !== "" && leadingIndent(body) <= indent) break;
1813
+ if (IMPORTANT_PATTERN.test(body)) {
1814
+ flushElided();
1815
+ kept.push(body);
1816
+ index += 1;
1817
+ continue;
1818
+ }
1819
+ if (isCodeStructureLine(body) || CODE_DECORATOR_PATTERN.test(body)) {
1820
+ flushElided();
1821
+ kept.push(body);
1822
+ keepPythonSignature(body);
1823
+ continue;
1824
+ }
1825
+ elided += 1;
1826
+ index += 1;
1827
+ }
1828
+ flushElided();
1829
+ };
1830
+ while (index < lines.length) {
1831
+ const line = lines[index];
1832
+ if (line === void 0) break;
1833
+ const delta = braceDelta(line);
1834
+ if (IMPORTANT_PATTERN.test(line)) {
1835
+ flushElided();
1836
+ kept.push(line);
1837
+ depth += delta;
1838
+ index += 1;
1839
+ continue;
1840
+ }
1841
+ if (isCodeStructureLine(line) || CODE_IMPORT_PATTERN.test(line) || CODE_DECORATOR_PATTERN.test(line)) {
1842
+ flushElided();
1843
+ kept.push(line);
1844
+ depth += delta;
1845
+ if (delta > 0) {
1846
+ elideBraceBody();
1847
+ continue;
1848
+ }
1849
+ if (PYTHON_STRUCTURE_PATTERN.test(line)) {
1850
+ keepPythonSignature(line);
1851
+ continue;
1852
+ }
1853
+ let opened = false;
1854
+ for (let guard = 0; guard < 6 && index + 1 < lines.length; guard += 1) {
1855
+ const next = lines[index + 1];
1856
+ if (next === void 0) break;
1857
+ const nextDelta = braceDelta(next);
1858
+ if (nextDelta === 0 && next.trim() !== "" && !/[:,(]\s*$/.test(next)) break;
1859
+ flushElided();
1860
+ kept.push(next);
1861
+ depth += nextDelta;
1862
+ index += 1;
1863
+ if (nextDelta > 0) {
1864
+ opened = true;
1865
+ break;
1866
+ }
1867
+ }
1868
+ if (opened) elideBraceBody();
1869
+ else index += 1;
1870
+ continue;
1871
+ }
1872
+ if (depth === 0 && CODE_COMMENT_PATTERN.test(line)) {
1873
+ flushElided();
1874
+ kept.push(line);
1875
+ } else elided += 1;
1876
+ depth += delta;
1877
+ index += 1;
1878
+ }
1879
+ flushElided();
1880
+ return finishSkeleton(kept, lines, input);
1881
+ }
1882
+ function finishSkeleton(kept, lines, input) {
1883
+ const text = fitLines([
1884
+ `[code output compressed by hypa-code-skeleton; source: ${input.sourceRef}]`,
1885
+ ...kept,
1886
+ ...lines.slice(-4)
1887
+ ], input.budgetChars, input.sourceRef);
1888
+ return text === null ? null : {
1889
+ text,
1890
+ reducer: "hypa-code-skeleton",
1891
+ lossy: true
1892
+ };
1893
+ }
1894
+ /** Net brace delta of one line, ignoring braces inside string literals. */
1895
+ function braceDelta(line) {
1896
+ let delta = 0;
1897
+ let quote = null;
1898
+ for (let position = 0; position < line.length; position += 1) {
1899
+ const char = line[position];
1900
+ if (quote !== null) {
1901
+ if (char === "\\") position += 1;
1902
+ else if (char === quote) quote = null;
1903
+ continue;
1904
+ }
1905
+ if (char === "\"" || char === "'" || char === "`") {
1906
+ quote = char;
1907
+ continue;
1908
+ }
1909
+ if (char === "{") delta += 1;
1910
+ else if (char === "}") delta -= 1;
1911
+ }
1912
+ return delta;
1913
+ }
1914
+ function leadingIndent(line) {
1915
+ return codePointLength(line) - codePointLength(line.trimStart());
1916
+ }
1917
+ function isCodeStructureLine(line) {
1918
+ return CODE_STRUCTURE_PATTERN.test(line) || PYTHON_STRUCTURE_PATTERN.test(line);
1919
+ }
1920
+ /**
1921
+ * Require content evidence of source code: enough declaration, import, or
1922
+ * decorator lines among a bounded prefix. Failing this keeps prose, logs, and
1923
+ * data on their existing reducers.
1924
+ * @param text - normalized result text.
1925
+ * @returns whether the text qualifies as source code.
1926
+ */
1927
+ function looksLikeSourceCode(text) {
1928
+ const lines = splitLines(text);
1929
+ if (lines.length < 12) return false;
1930
+ let evidence = 0;
1931
+ for (const line of lines.slice(0, 400)) if (isCodeStructureLine(line) || CODE_IMPORT_PATTERN.test(line) || CODE_DECORATOR_PATTERN.test(line)) {
1932
+ evidence += 1;
1933
+ if (evidence >= 3) return true;
1934
+ }
1935
+ return false;
1936
+ }
1937
+ function omissionMarker(input, reducer) {
1938
+ return `[... ${reducer} omitted content; original_chars=${String(codePointLength(input.text))}; source=${input.sourceRef}; retrieve with context_compression_retrieve ...]`;
1939
+ }
1940
+ function importantAnchor(text, maxChars) {
1941
+ const lines = splitLines(normalizeTerminalText(text));
1942
+ const chosen = lines.find((line) => IMPORTANT_PATTERN.test(line)) ?? lines.at(-1) ?? "";
1943
+ return Array.from(chosen.trim()).slice(0, maxChars).join("");
1944
+ }
1945
+ function fitLines(lines, budgetChars, requiredRef) {
1946
+ const unique = [];
1947
+ const seen = /* @__PURE__ */ new Set();
1948
+ for (const line of lines) {
1949
+ if (line === "" || seen.has(line)) continue;
1950
+ seen.add(line);
1951
+ unique.push(line);
1952
+ }
1953
+ const output = [];
1954
+ let used = 0;
1955
+ for (const line of unique) {
1956
+ const cost = codePointLength(line) + (output.length === 0 ? 0 : 1);
1957
+ if (used + cost > budgetChars) continue;
1958
+ output.push(line);
1959
+ used += cost;
1960
+ }
1961
+ const text = output.join("\n");
1962
+ return text.includes(requiredRef) ? text : null;
1963
+ }
1964
+ function takeWholeLinesFromHead(text, budgetChars) {
1965
+ const output = [];
1966
+ let used = 0;
1967
+ for (const line of splitLines(text)) {
1968
+ const cost = codePointLength(line) + (output.length === 0 ? 0 : 1);
1969
+ if (used + cost > budgetChars) break;
1970
+ output.push(line);
1971
+ used += cost;
1972
+ }
1973
+ if (output.length === 0) return Array.from(text).slice(0, budgetChars).join("");
1974
+ return output.join("\n");
1975
+ }
1976
+ function takeWholeLinesFromTail(text, budgetChars) {
1977
+ const lines = splitLines(text);
1978
+ const output = [];
1979
+ let used = 0;
1980
+ for (let index = lines.length - 1; index >= 0; index--) {
1981
+ const line = lines[index];
1982
+ if (line === void 0) continue;
1983
+ const cost = codePointLength(line) + (output.length === 0 ? 0 : 1);
1984
+ if (used + cost > budgetChars) break;
1985
+ output.unshift(line);
1986
+ used += cost;
1987
+ }
1988
+ if (output.length === 0) return Array.from(text).slice(-budgetChars).join("");
1989
+ return output.join("\n");
1990
+ }
1991
+ function splitLines(text) {
1992
+ const lines = text.split("\n");
1993
+ if (text.endsWith("\n")) lines.pop();
1994
+ return lines;
1995
+ }
1996
+ function extractCommand(argumentsText) {
1997
+ try {
1998
+ const parsed = JSON.parse(argumentsText);
1999
+ if (typeof parsed !== "object" || parsed === null) return "";
2000
+ const record = parsed;
2001
+ for (const key of [
2002
+ "command",
2003
+ "cmd",
2004
+ "script",
2005
+ "input"
2006
+ ]) {
2007
+ const value = record[key];
2008
+ if (typeof value === "string") return value;
2009
+ }
2010
+ } catch {
2011
+ return "";
2012
+ }
2013
+ return "";
2014
+ }
2015
+ function looksLikeJson(text) {
2016
+ const trimmed = text.trim();
2017
+ return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
2018
+ }
2019
+ function isReadTool(name) {
2020
+ return /(?:^|[-_/])(?:read|cat|view|open_file)(?:$|[-_/])/.test(name);
2021
+ }
2022
+ function isSearchTool(name, command) {
2023
+ return /(?:grep|search|glob|find|ripgrep|rg)/.test(name) || /(?:^|\s)(?:rg|grep|find|fd)\s/.test(command);
2024
+ }
2025
+ function isShellTool(name) {
2026
+ return /(?:bash|shell|terminal|powershell|pwsh|exec|command)/.test(name);
2027
+ }
2028
+ function isGitCommand(name, command) {
2029
+ return name.includes("git") || /(?:^|\s)git\s/.test(command);
2030
+ }
2031
+ function isPackageCommand(command) {
2032
+ return /(?:^|\s)(?:npm|pnpm|yarn|bun|pip|pip3|uv|poetry)\s/.test(command);
2033
+ }
2034
+ function isBuildOrTestCommand(command) {
2035
+ return new RegExp([String.raw`(?:^|\s)(?:tsc|dotnet\s+(?:build|test)|pytest|cargo\s+(?:build|test|check)|go\s+test|mvn\s+test|`, String.raw`gradle|npm\s+(?:test|run\s+build)|pnpm\s+(?:test|build|lint)|yarn\s+(?:test|build|lint))\b`].join("")).test(command);
2036
+ }
2037
+ function packagePattern() {
2038
+ return new RegExp([String.raw`(?:ERR!|WARN|warning|error|failed|conflict|peer dep|added\s+\d+|removed\s+\d+|installed|success|`, String.raw`up to date|packages?\s+(?:added|removed|changed)|resolution|No matching distribution|Could not find a version)`].join(""), "i");
2039
+ }
2040
+ function buildPattern() {
2041
+ return new RegExp([
2042
+ String.raw`(?:error\s+TS\d+|warning\s+TS\d+|FAILED|FAIL\b|AssertionError|expected|actual|`,
2043
+ String.raw`tests?\s+(?:run|passed|failed|skipped)|Build\s+(?:succeeded|FAILED)|\d+\s+Error\(s\)|`,
2044
+ String.raw`\d+\s+Warning\(s\)|Finished\s+test|compilation failed)`
2045
+ ].join(""), "i");
2046
+ }
2047
+ //#endregion
2048
+ //#region src/runtime/deepseek-official-pricing.ts
2049
+ /** Checked-in DeepSeek official prices and fixed-point provider-usage accounting. */
2050
+ const DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION = "deepseek-official-2026-08-25";
2051
+ /** Wall-clock time at which the checked-in official price pages were verified. */
2052
+ const DEEPSEEK_OFFICIAL_PRICE_CHECKED_AT = "2026-08-25T00:10:20+08:00";
2053
+ const PRICES = Object.freeze({
2054
+ "deepseek-v4-flash": modelPrices("DeepSeek-V4-Flash-0731", [
2055
+ "0.007",
2056
+ "0.22",
2057
+ "0.66"
2058
+ ], [
2059
+ "0.014",
2060
+ "0.44",
2061
+ "1.32"
2062
+ ], [
2063
+ "0.05",
2064
+ "1.5",
2065
+ "4.5"
2066
+ ], [
2067
+ "0.10",
2068
+ "3.0",
2069
+ "9.0"
2070
+ ]),
2071
+ "deepseek-v4-pro": modelPrices("DeepSeek-V4-Pro-0813", [
2072
+ "0.022",
2073
+ "0.66",
2074
+ "1.98"
2075
+ ], [
2076
+ "0.044",
2077
+ "1.32",
2078
+ "3.96"
2079
+ ], [
2080
+ "0.15",
2081
+ "4.5",
2082
+ "13.5"
2083
+ ], [
2084
+ "0.30",
2085
+ "9.0",
2086
+ "27.0"
2087
+ ]),
2088
+ "deepseek-v4-flash-vision-exp": modelPrices("DeepSeek-V4-Flash-Vision-Exp", [
2089
+ "0.007",
2090
+ "0.22",
2091
+ "0.66"
2092
+ ], [
2093
+ "0.014",
2094
+ "0.44",
2095
+ "1.32"
2096
+ ], [
2097
+ "0.05",
2098
+ "1.5",
2099
+ "4.5"
2100
+ ], [
2101
+ "0.10",
2102
+ "3.0",
2103
+ "9.0"
2104
+ ])
2105
+ });
2106
+ const PEAK_RULE = "Asia/Shanghai Mon-Fri 09:00-12:00,14:00-18:00";
2107
+ const USD_SOURCE = "https://api-docs.deepseek.com/quick_start/pricing/";
2108
+ const CNY_SOURCE = "https://api-docs.deepseek.com/zh-cn/quick_start/pricing/";
2109
+ /**
2110
+ * Resolve one immutable official price record; aliases and compatible gateways fail closed.
2111
+ * @param input - exact provider, endpoint, route, model, currency, and timestamp applicability.
2112
+ * @returns An immutable price record or an explicit unpriced reason.
2113
+ */
2114
+ function resolveOfficialDeepSeekPrice(input) {
2115
+ if (input.provider !== "deepseek-official") return unpriced("unknown provider route");
2116
+ if (input.baseUrlClass !== "official-public") return unpriced("unknown base-url applicability");
2117
+ if (input.apiRoute !== "chat-completions" && input.apiRoute !== "responses") return unpriced("unknown API route");
2118
+ if (!isOfficialModel(input.modelId)) return unpriced("unknown model id");
2119
+ if (input.currency !== "USD" && input.currency !== "CNY") return unpriced("unknown currency");
2120
+ const band = priceBandAt(input.at);
2121
+ if (band === void 0) return unpriced("invalid price timestamp");
2122
+ const model = PRICES[input.modelId];
2123
+ const [inputCacheHit, inputCacheMiss, output] = model[input.currency][band];
2124
+ return {
2125
+ kind: "priced",
2126
+ record: Object.freeze({
2127
+ catalogVersion: DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION,
2128
+ checkedAt: DEEPSEEK_OFFICIAL_PRICE_CHECKED_AT,
2129
+ provider: "deepseek-official",
2130
+ baseUrlClass: "official-public",
2131
+ apiRoute: input.apiRoute,
2132
+ modelId: input.modelId,
2133
+ modelVersion: model.version,
2134
+ currency: input.currency,
2135
+ unitTokens: 1e6,
2136
+ band,
2137
+ inputCacheHit,
2138
+ inputCacheMiss,
2139
+ output,
2140
+ sourceUrl: input.currency === "USD" ? USD_SOURCE : CNY_SOURCE,
2141
+ sourceLocale: input.currency === "USD" ? "en" : "zh-CN",
2142
+ peakRule: PEAK_RULE
2143
+ })
2144
+ };
2145
+ }
2146
+ /**
2147
+ * Classify a timestamp under the published Beijing peak schedule.
2148
+ * @param at - absolute request time to interpret in Asia/Shanghai.
2149
+ * @returns Peak/off-peak, or undefined for an invalid timestamp.
2150
+ */
2151
+ function priceBandAt(at) {
2152
+ if (!Number.isFinite(at.getTime())) return void 0;
2153
+ const parts = new Intl.DateTimeFormat("en-US", {
2154
+ timeZone: "Asia/Shanghai",
2155
+ weekday: "short",
2156
+ hour: "2-digit",
2157
+ minute: "2-digit",
2158
+ second: "2-digit",
2159
+ hourCycle: "h23"
2160
+ }).formatToParts(at);
2161
+ const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
2162
+ const weekday = values.weekday;
2163
+ const hour = Number(values.hour);
2164
+ const minute = Number(values.minute);
2165
+ const second = Number(values.second);
2166
+ if (weekday === void 0 || !Number.isInteger(hour) || !Number.isInteger(minute) || !Number.isInteger(second)) return void 0;
2167
+ const workday = weekday !== "Sat" && weekday !== "Sun";
2168
+ const seconds = hour * 3600 + minute * 60 + second;
2169
+ return workday && (seconds >= 32400 && seconds < 43200 || seconds >= 50400 && seconds < 64800) ? "peak" : "off-peak";
2170
+ }
2171
+ /**
2172
+ * Price one completed request, returning a range when it spans a published band boundary.
2173
+ * @param input - exact applicability, request interval, and complete disjoint usage buckets.
2174
+ * @returns Fixed-point exact/range cost or an explicit unpriced reason.
2175
+ */
2176
+ function priceOfficialDeepSeekUsage(input) {
2177
+ for (const [name, value] of Object.entries(input.usage)) if (!Number.isSafeInteger(value) || value < 0) return unpriced(`invalid ${name}`);
2178
+ if (input.completedAt.getTime() < input.startedAt.getTime()) return unpriced("completion timestamp precedes request start");
2179
+ const start = resolveOfficialDeepSeekPrice({
2180
+ ...input,
2181
+ at: input.startedAt
2182
+ });
2183
+ if (start.kind === "unpriced") return start;
2184
+ const end = resolveOfficialDeepSeekPrice({
2185
+ ...input,
2186
+ at: input.completedAt
2187
+ });
2188
+ if (end.kind === "unpriced") return end;
2189
+ const startAmount = amountFor(start.record, input.usage);
2190
+ if (startAmount === void 0) return unpriced("invalid decimal price record");
2191
+ const crossesBoundary = spansPublishedPriceBoundary(input.startedAt, input.completedAt);
2192
+ if (start.record.band === end.record.band && !crossesBoundary) return {
2193
+ kind: "exact",
2194
+ currency: start.record.currency,
2195
+ band: start.record.band,
2196
+ ...startAmount
2197
+ };
2198
+ const comparisonRecord = start.record.band === end.record.band ? priceRecordInBand(start.record, start.record.band === "peak" ? "off-peak" : "peak") : end.record;
2199
+ const endAmount = amountFor(comparisonRecord, input.usage);
2200
+ if (endAmount === void 0) return unpriced("invalid decimal price record");
2201
+ const startFemto = BigInt(startAmount.femtoUnits);
2202
+ const endFemto = BigInt(endAmount.femtoUnits);
2203
+ return {
2204
+ kind: "range",
2205
+ currency: start.record.currency,
2206
+ bands: [start.record.band, comparisonRecord.band],
2207
+ minimum: startFemto <= endFemto ? startAmount : endAmount,
2208
+ maximum: startFemto <= endFemto ? endAmount : startAmount
2209
+ };
2210
+ }
2211
+ /** Detect any published UTC band boundary, even when both endpoints share a band. */
2212
+ function spansPublishedPriceBoundary(startedAt, completedAt) {
2213
+ const start = startedAt.getTime();
2214
+ const end = completedAt.getTime();
2215
+ if (end <= start) return false;
2216
+ const dayMs = 864e5;
2217
+ if (end - start >= 7 * dayMs) return true;
2218
+ const firstDay = Math.floor(start / dayMs) * dayMs;
2219
+ for (let day = firstDay; day <= end; day += dayMs) {
2220
+ const weekday = new Date(day).getUTCDay();
2221
+ if (weekday === 0 || weekday === 6) continue;
2222
+ for (const hour of [
2223
+ 1,
2224
+ 4,
2225
+ 6,
2226
+ 10
2227
+ ]) {
2228
+ const boundary = day + hour * 60 * 60 * 1e3;
2229
+ if (boundary > start && boundary <= end) return true;
2230
+ }
2231
+ }
2232
+ return false;
2233
+ }
2234
+ function priceRecordInBand(record, band) {
2235
+ const [inputCacheHit, inputCacheMiss, output] = PRICES[record.modelId][record.currency][band];
2236
+ return Object.freeze({
2237
+ ...record,
2238
+ band,
2239
+ inputCacheHit,
2240
+ inputCacheMiss,
2241
+ output
2242
+ });
2243
+ }
2244
+ /**
2245
+ * Parse a non-negative decimal rate into nano-currency units, without Number arithmetic.
2246
+ * @param value - canonical non-negative decimal with at most nine fractional digits.
2247
+ * @returns Integer nano-units, or undefined when the decimal is invalid.
2248
+ */
2249
+ function decimalRateNanoUnits(value) {
2250
+ const match = /^(0|[1-9]\d*)(?:\.(\d{1,9}))?$/u.exec(value);
2251
+ if (match === null) return void 0;
2252
+ const whole = match[1] ?? "0";
2253
+ const fraction = (match[2] ?? "").padEnd(9, "0");
2254
+ return BigInt(whole) * 1000000000n + BigInt(fraction || "0");
2255
+ }
2256
+ function amountFor(record, usage) {
2257
+ const hit = decimalRateNanoUnits(record.inputCacheHit);
2258
+ const miss = decimalRateNanoUnits(record.inputCacheMiss);
2259
+ const output = decimalRateNanoUnits(record.output);
2260
+ if (hit === void 0 || miss === void 0 || output === void 0) return void 0;
2261
+ const femtoUnits = BigInt(usage.cacheReadTokens) * hit + BigInt(usage.cacheMissTokens) * miss + BigInt(usage.outputTokens) * output;
2262
+ return {
2263
+ femtoUnits: femtoUnits.toString(),
2264
+ decimal: formatFemto(femtoUnits)
2265
+ };
2266
+ }
2267
+ function formatFemto(value) {
2268
+ const digits = value.toString().padStart(16, "0");
2269
+ const whole = digits.slice(0, -15);
2270
+ const fraction = digits.slice(-15).replace(/0+$/u, "");
2271
+ return fraction.length === 0 ? whole : `${whole}.${fraction}`;
2272
+ }
2273
+ function modelPrices(version, usdOffPeak, usdPeak, cnyOffPeak, cnyPeak) {
2274
+ return Object.freeze({
2275
+ version,
2276
+ USD: Object.freeze({
2277
+ "off-peak": usdOffPeak,
2278
+ peak: usdPeak
2279
+ }),
2280
+ CNY: Object.freeze({
2281
+ "off-peak": cnyOffPeak,
2282
+ peak: cnyPeak
2283
+ })
2284
+ });
2285
+ }
2286
+ function isOfficialModel(value) {
2287
+ return Object.prototype.hasOwnProperty.call(PRICES, value);
2288
+ }
2289
+ function unpriced(reason) {
2290
+ return {
2291
+ kind: "unpriced",
2292
+ reason
2293
+ };
2294
+ }
2295
+ //#endregion
2296
+ //#region src/runtime/adaptive-cost.ts
2297
+ /**
2298
+ * Bound Adaptive's benefit and cache-loss exposure without attributing the
2299
+ * request-level cache split to individual messages. Removed tokens are not
2300
+ * charged again as part of the retained suffix.
2301
+ * @param input - exact planned reclaim, adjacent request measurement, and same-revision nodes.
2302
+ * @returns Conservative removal/cache-risk bounds or an explicit unknown reason.
2303
+ */
2304
+ function deriveAdaptiveTokenBounds(input) {
2305
+ if (!isCount(input.exactReclaimedTokens) || input.exactReclaimedTokens === 0) return unknown("invalid-reclaimed-token-count");
2306
+ if (!isCount(input.earliestChangedSeq)) return unknown("invalid-earliest-changed-seq");
2307
+ if (!isCount(input.previousPromptTokens)) return unknown("invalid-previous-prompt-tokens");
2308
+ if (input.expectedTokenizerRevision.length === 0) return unknown("expected-tokenizer-revision-unavailable");
2309
+ const request = input.previousRequestMeasurement;
2310
+ let margin = 0;
2311
+ let measurementKind;
2312
+ if (request.kind === "unavailable") return unknown("request-measurement-unavailable");
2313
+ if (request.kind === "exact-tokenizer") {
2314
+ if (!isCount(request.tokens) || request.tokens !== input.previousPromptTokens) return unknown("exact-request-usage-mismatch");
2315
+ if (request.tokenizerRevision !== input.expectedTokenizerRevision) return unknown("request-tokenizer-revision-mismatch");
2316
+ measurementKind = request.kind;
2317
+ } else {
2318
+ const calibration = request.calibration;
2319
+ if (calibration === void 0) return unknown("estimate-calibration-unavailable");
2320
+ if (!isCount(request.tokens) || !isCount(request.upperBoundTokens) || request.upperBoundTokens < request.tokens || input.previousPromptTokens > request.upperBoundTokens || !isCount(calibration.sampleCount) || !isCount(calibration.conservativeMarginTokens)) return unknown("invalid-estimate-calibration");
2321
+ margin = calibration.conservativeMarginTokens;
2322
+ measurementKind = request.kind;
2323
+ }
2324
+ const reclaimedLowerBoundTokens = Math.max(0, input.exactReclaimedTokens - margin);
2325
+ if (reclaimedLowerBoundTokens === 0) return unknown("reclaim-not-positive-after-margin");
2326
+ let identity;
2327
+ let exactPrefixLowerBoundTokens = 0;
2328
+ const seen = /* @__PURE__ */ new Set();
2329
+ for (const node of input.measuredNodes) {
2330
+ if (!isCount(node.seq) || seen.has(node.seq)) return unknown("invalid-measured-node-sequence");
2331
+ seen.add(node.seq);
2332
+ if (node.seq >= input.earliestChangedSeq || node.count.kind !== "exact-tokenizer") continue;
2333
+ if (!isCount(node.count.tokens)) return unknown("invalid-exact-prefix-count");
2334
+ if (node.count.tokenizerRevision !== input.expectedTokenizerRevision) return unknown("exact-prefix-tokenizer-revision-mismatch");
2335
+ if (identity !== void 0 && (identity.tokenizerId !== node.count.tokenizerId || identity.tokenizerRevision !== node.count.tokenizerRevision)) return unknown("exact-prefix-tokenizer-identity-mismatch");
2336
+ identity ??= node.count;
2337
+ exactPrefixLowerBoundTokens += node.count.tokens;
2338
+ if (!isCount(exactPrefixLowerBoundTokens)) return unknown("exact-prefix-overflow");
2339
+ }
2340
+ const accounted = exactPrefixLowerBoundTokens + reclaimedLowerBoundTokens;
2341
+ if (!isCount(accounted) || accounted > input.previousPromptTokens) return unknown("adaptive-bounds-exceed-previous-prompt");
2342
+ return {
2343
+ kind: "available",
2344
+ measurementKind,
2345
+ reclaimedLowerBoundTokens,
2346
+ affectedRetainedSuffixUpperBoundTokens: input.previousPromptTokens - accounted,
2347
+ exactPrefixLowerBoundTokens
2348
+ };
2349
+ }
2350
+ /**
2351
+ * Allow routine History only when D*P_hit is strictly greater than A*(P_miss-P_hit).
2352
+ * @param input - capacity state, token bounds, request hit cap, and official input rates.
2353
+ * @returns Capacity override or a strict fixed-point conservative-cost decision.
2354
+ */
2355
+ function decideConservativeAdaptive(input) {
2356
+ if (input.capacityPressure) return {
2357
+ allowHistory: true,
2358
+ reason: "capacity-override"
2359
+ };
2360
+ if (input.bounds.kind === "unknown") return {
2361
+ allowHistory: false,
2362
+ reason: input.bounds.reason
2363
+ };
2364
+ if (input.inputCacheHitRate === void 0 || input.inputCacheMissRate === void 0) return {
2365
+ allowHistory: false,
2366
+ reason: "adaptive-unknown-price"
2367
+ };
2368
+ const hit = decimalRateNanoUnits(input.inputCacheHitRate);
2369
+ const miss = decimalRateNanoUnits(input.inputCacheMissRate);
2370
+ if (hit === void 0 || miss === void 0 || miss < hit) return {
2371
+ allowHistory: false,
2372
+ reason: "adaptive-unknown-price"
2373
+ };
2374
+ if (input.observedCacheReadTokens !== void 0 && !isCount(input.observedCacheReadTokens)) return {
2375
+ allowHistory: false,
2376
+ reason: "adaptive-invalid-cache-telemetry"
2377
+ };
2378
+ const affectedHitUpperBound = input.observedCacheReadTokens === void 0 ? input.bounds.affectedRetainedSuffixUpperBoundTokens : Math.min(input.bounds.affectedRetainedSuffixUpperBoundTokens, input.observedCacheReadTokens);
2379
+ const minimumRemovalValue = BigInt(input.bounds.reclaimedLowerBoundTokens) * hit;
2380
+ const maximumCacheLossPenalty = BigInt(affectedHitUpperBound) * (miss - hit);
2381
+ return {
2382
+ allowHistory: minimumRemovalValue > maximumCacheLossPenalty,
2383
+ reason: minimumRemovalValue > maximumCacheLossPenalty ? "cost-interval-clearly-favourable" : "cache-risk-not-clearly-paid-back",
2384
+ minimumRemovalValue: minimumRemovalValue.toString(),
2385
+ maximumCacheLossPenalty: maximumCacheLossPenalty.toString()
2386
+ };
2387
+ }
2388
+ function unknown(reason) {
2389
+ return {
2390
+ kind: "unknown",
2391
+ reason
2392
+ };
2393
+ }
2394
+ function isCount(value) {
2395
+ return Number.isSafeInteger(value) && value >= 0;
2396
+ }
2397
+ //#endregion
2398
+ //#region src/runtime/audit.ts
2399
+ /** Stable prefix used to locate one JSON audit record in Harness runtime logs. */
2400
+ const COMPRESSION_AUDIT_PREFIX = "context-compression audit ";
2401
+ /**
2402
+ * Encode one stable single-line audit message.
2403
+ * @param record - content-free structured audit record.
2404
+ * @returns the fixed prefix followed by one JSON object.
2405
+ */
2406
+ function formatCompressionAudit(record) {
2407
+ return `${COMPRESSION_AUDIT_PREFIX}${JSON.stringify(record)}`;
2408
+ }
2409
+ /**
2410
+ * Publish one audit message through the Harness logger.
2411
+ * @param logger - current plugin logger.
2412
+ * @param record - structured record committed by the caller.
2413
+ */
2414
+ function emitCompressionAudit(logger, record) {
2415
+ try {
2416
+ logger.info(formatCompressionAudit(record));
2417
+ } catch {}
2418
+ }
2419
+ //#endregion
2420
+ //#region src/pruner.ts
2421
+ /**
2422
+ * Replay-safe, model-free context-compression selector for tool results.
2423
+ *
2424
+ * Standard profiles never rewrite ordinary Assistant prose. The only durable
2425
+ * replacements emitted here are content-only `tool/result` rewrites whose
2426
+ * full source remains in the append-only Session log.
2427
+ *
2428
+ * @module dsh-context-compression-improved-runtime
2429
+ */
2430
+ /** Mixed deterministic selector behind the existing `ctx.toolResultPruner` seam. */
2431
+ var ToolResultPruner = class extends Service {
2432
+ static inject = ["tokenMeter"];
2433
+ static Config = z.object({
2434
+ profile: z.union([...COMPRESSION_PROFILES]).default(DEFAULTS.profile),
2435
+ headChars: z.number().step(1).min(0).default(DEFAULTS.headChars),
2436
+ tailChars: z.number().step(1).min(0).default(DEFAULTS.tailChars),
2437
+ nativeTriggerTokens: z.number().step(1).min(1).required(false),
2438
+ nativeTargetTokens: z.number().step(1).min(1).required(false),
2439
+ freshTriggerTokens: z.number().step(1).min(1).required(false),
2440
+ freshTargetTokens: z.number().step(1).min(1).required(false),
2441
+ aggregateTriggerTokens: z.number().step(1).min(1).required(false),
2442
+ aggregateTargetTokens: z.number().step(1).min(1).required(false),
2443
+ historyTriggerTokens: z.number().step(1).min(1).required(false),
2444
+ historyKeepRecentToolCalls: z.number().step(1).min(0).required(false),
2445
+ historyKeepRecentTokens: z.number().step(1).min(0).required(false),
2446
+ historyMinReclaimTokens: z.number().step(1).min(1).required(false),
2447
+ autoCompactThresholdPercent: z.number().step(1).min(50).max(90).required(false)
2448
+ });
2449
+ /** Consolidated per-session mutable state. */
2450
+ state;
2451
+ constructor(ctx, config = {}) {
2452
+ super(ctx, "toolResultPruner");
2453
+ ctx.inject(["tools", "systemPrompt"], (recoveryCtx) => {
2454
+ installContextCompressionRetrieve(recoveryCtx);
2455
+ });
2456
+ this.state = {
2457
+ config: resolveConfig(config),
2458
+ sessionSettings: /* @__PURE__ */ new WeakMap(),
2459
+ firstExposure: /* @__PURE__ */ new WeakMap(),
2460
+ recoveryExemptions: /* @__PURE__ */ new WeakMap(),
2461
+ dedupeTables: /* @__PURE__ */ new WeakMap(),
2462
+ estimatorVerdicts: /* @__PURE__ */ new WeakMap(),
2463
+ estimatorFailures: /* @__PURE__ */ new WeakMap(),
2464
+ warnedFailures: /* @__PURE__ */ new WeakMap(),
2465
+ postflightDiagnostics: /* @__PURE__ */ new WeakMap(),
2466
+ activeRequestBoundaries: /* @__PURE__ */ new WeakMap(),
2467
+ tailTrimBoundaryAttempts: /* @__PURE__ */ new WeakMap(),
2468
+ policyResolutionAudits: /* @__PURE__ */ new WeakMap()
2469
+ };
2470
+ ctx.on("session/event", (session, event) => {
2471
+ if (event.type === "compaction/summary") {
2472
+ emitCompressionAudit(ctx.logger, {
2473
+ schemaVersion: 1,
2474
+ kind: "native-auto-compact",
2475
+ sessionId: String(session.id),
2476
+ manifestEventType: "compaction/summary",
2477
+ manifestSeq: event.seq,
2478
+ reducer: "llm-summary",
2479
+ provider: event.data.provider,
2480
+ model: event.data.model,
2481
+ tokensBefore: event.data.shadowedTokenCount,
2482
+ tokensAfter: null
2483
+ });
2484
+ return;
2485
+ }
2486
+ if (event.type === "compaction/end") try {
2487
+ this.attachSummaryLocator(session, event.data.compactionId);
2488
+ } catch (error) {
2489
+ this.auditFailure(session, "pressure", "summary-locator", error);
2490
+ ctx.logger.warn("context-compression summary locator failed open: %o", error);
2491
+ }
2492
+ });
2493
+ ctx.on("agent/pre-step", async ({ agent, signal, turn, step }, next) => {
2494
+ const boundary = {};
2495
+ this.state.activeRequestBoundaries.set(agent.session, boundary);
2496
+ try {
2497
+ if (!signal.aborted) try {
2498
+ this.runRequestBoundary(agent.session, turn, step - 1, signal);
2499
+ } catch (error) {
2500
+ this.auditFailure(agent.session, "fresh", "request-boundary", error);
2501
+ ctx.logger.warn("context-compression fresh pass failed open: %o", error);
2502
+ }
2503
+ return await next();
2504
+ } finally {
2505
+ if (this.state.activeRequestBoundaries.get(agent.session) === boundary) this.state.activeRequestBoundaries.delete(agent.session);
2506
+ }
2507
+ }, { prepend: true });
2508
+ ctx.on("agent/turn-stopping", ({ agent, turn, signal }) => {
2509
+ if (signal.aborted) return;
2510
+ try {
2511
+ const step = latestCompletedToolStep(agent.session, turn);
2512
+ if (step !== void 0) this.runRequestBoundary(agent.session, turn, step, signal);
2513
+ } catch (error) {
2514
+ this.auditFailure(agent.session, "fresh", "terminal-pass", error);
2515
+ ctx.logger.warn("context-compression terminal pass failed open: %o", error);
2516
+ }
2517
+ this.postflightEstimatorPass(agent.session, signal).catch(() => void 0);
2518
+ });
2519
+ }
2520
+ /**
2521
+ * Measure text content in Unicode code points; non-text blocks cost zero.
2522
+ * @param blocks - tool-result content to measure.
2523
+ * @returns total Unicode code points across text blocks.
2524
+ */
2525
+ measureContent(blocks) {
2526
+ return measureContent(blocks);
2527
+ }
2528
+ /**
2529
+ * Apply the configured native head/middle/tail transform.
2530
+ * @param blocks - original tool-result content.
2531
+ * @returns reduced content, or `null` when no reduction is required.
2532
+ */
2533
+ pruneContent(blocks) {
2534
+ return nativePruneContent(blocks, this.state.config.headChars + codePointLength(PRUNE_MARKER) + this.state.config.tailChars, this.state.config.headChars, this.state.config.tailChars);
2535
+ }
2536
+ /**
2537
+ * Run one stable-surface pass. `fresh` is invoked before every request and
2538
+ * only reduces original oversized results. `pressure` is called by
2539
+ * compaction-basic and may additionally age old results at one high-water.
2540
+ * @param session - session whose current tool-result surface may be rewritten.
2541
+ * @param options - pass stage and optional completed-step coordinates.
2542
+ * @returns landed replacements and aggregate Unicode-code-point savings.
2543
+ */
2544
+ pruneSession(session, options = {}) {
2545
+ const stage = options.stage ?? "pressure";
2546
+ const contextWindowTokens = options.contextWindowTokens ?? this.contextWindowForRequest(session);
2547
+ const policy = this.activePolicy(session, contextWindowTokens, stage);
2548
+ if (policy === void 0) return emptyResult();
2549
+ const profile = policy.profile;
2550
+ const view = measureForCompaction(this.ctx, session);
2551
+ if (stage === "fresh") return this.decideFreshStep(session, options, policy, view);
2552
+ if (profile === "off") return emptyResult();
2553
+ const landed = [];
2554
+ if (policy.nativeToolResultEnabled) {
2555
+ const eligible = this.snapshot(session, view).filter((candidate) => !this.isRecoveryExempt(session, candidate));
2556
+ const exactUnavailable = eligible.some((candidate) => candidate.count.kind !== "exact-tokenizer");
2557
+ if (exactUnavailable) this.warnExactUnavailable(session, view, "native");
2558
+ const planned = eligible.map((candidate) => this.planNative(candidate, session, stage, policy, view)).filter((entry) => entry !== null);
2559
+ landed.push(...this.landAll(session, planned));
2560
+ if (landed.length === 0) {
2561
+ const exact = eligible.flatMap((candidate) => candidate.count.kind === "exact-tokenizer" ? [candidate.count.tokens] : []);
2562
+ this.auditComponent(session, policy, "native-tool-result", "pressure", "skipped", exactUnavailable ? "exact-tokenizer-unavailable" : exact.length === 0 ? "no-tool-result-candidates" : Math.max(...exact) <= policy.nativeTriggerTokens ? "at-or-below-trigger" : planned.length === 0 ? "no-valid-reduction" : "recovery-tool-unavailable", {
2563
+ measurementKind: exactUnavailable ? "unavailable" : "exact-tokenizer",
2564
+ ...exact.length === 0 ? {} : { currentTokens: Math.max(...exact) },
2565
+ triggerTokens: policy.nativeTriggerTokens,
2566
+ targetTokens: policy.nativeTargetTokens
2567
+ });
2568
+ }
2569
+ return summarize(landed);
2570
+ }
2571
+ let historyOutcome = {
2572
+ kind: "planned",
2573
+ plans: []
2574
+ };
2575
+ let historyAllowed = false;
2576
+ if (policy.historyMode === "adaptive") {
2577
+ historyOutcome = this.planHistoricalAging(session, policy, view);
2578
+ if (historyOutcome.kind === "planned") {
2579
+ const capacityPressure = this.capacityPressureActive(session, view, policy);
2580
+ historyAllowed = this.adaptiveHistoryAllowed(session, view, historyOutcome.plans, capacityPressure);
2581
+ if (historyAllowed) landed.push(...this.landAll(session, historyOutcome.plans));
2582
+ }
2583
+ } else {
2584
+ historyAllowed = this.historyAllowed(session, policy, view);
2585
+ if (historyAllowed) {
2586
+ historyOutcome = this.planHistoricalAging(session, policy, view);
2587
+ if (historyOutcome.kind === "planned") landed.push(...this.landAll(session, historyOutcome.plans));
2588
+ }
2589
+ }
2590
+ if (!landed.some((entry) => entry.stage === "pressure")) this.auditHistoryEvaluation(session, policy, view, historyAllowed, historyOutcome);
2591
+ if (policy.tailTrim?.enabled === true) {
2592
+ const tailView = measureForCompaction(this.ctx, session);
2593
+ this.landOldestTailTrimGroup(session, policy, tailView);
2594
+ } else this.auditComponent(session, policy, "tail-trim", "pressure", "disabled", "profile-policy");
2595
+ return summarize(landed);
2596
+ }
2597
+ activeSettings(session) {
2598
+ const frozen = this.state.sessionSettings.get(session);
2599
+ if (frozen !== void 0) return frozen;
2600
+ const settings = this.ctx.get("settings")?.get(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE);
2601
+ let resolved;
2602
+ let settingsSource = settings === void 0 ? "plugin-config-fallback" : "host-settings";
2603
+ let autoCompactThresholdSource = settings === void 0 ? "schema-default" : "host-settings";
2604
+ let settingsInvalidFallback;
2605
+ try {
2606
+ resolved = settings === void 0 ? ContextCompressionSettingsSchema({ profile: this.state.config.profile }) : parseContextCompressionSettings(settings);
2607
+ } catch (error) {
2608
+ const reason = error instanceof Error ? error.message : String(error);
2609
+ this.auditFailure(session, "pressure", "policy-resolution", error);
2610
+ this.warnOnce(session, `settings-invalid:${reason}`, "context-compression froze this session effectively off because the stored settings document is invalid: %s", reason);
2611
+ resolved = ContextCompressionSettingsSchema({ profile: "off" });
2612
+ settingsSource = "plugin-config-fallback";
2613
+ autoCompactThresholdSource = "schema-default";
2614
+ settingsInvalidFallback = "lossless-off";
2615
+ }
2616
+ if (this.state.config.autoCompactThresholdPercent !== void 0) {
2617
+ resolved = {
2618
+ ...resolved,
2619
+ autoCompact: { thresholdPercent: this.state.config.autoCompactThresholdPercent }
2620
+ };
2621
+ autoCompactThresholdSource = "generation-config";
2622
+ }
2623
+ const snapshot = deepFreeze(structuredClone(resolved));
2624
+ this.state.sessionSettings.set(session, snapshot);
2625
+ emitCompressionAudit(this.ctx.logger, {
2626
+ schemaVersion: 1,
2627
+ kind: "policy-frozen",
2628
+ sessionId: String(session.id),
2629
+ settingsSource,
2630
+ autoCompactThresholdSource,
2631
+ ...settingsInvalidFallback === void 0 ? {} : { settingsInvalidFallback },
2632
+ settings: snapshot,
2633
+ deploymentConfig: this.state.config
2634
+ });
2635
+ return snapshot;
2636
+ }
2637
+ /**
2638
+ * TokenPilot-inspired A2: replace the compaction summary checkpoint node
2639
+ * with the same summary plus an Exact Sources locator block. Fails open:
2640
+ * any unresolved shape (no trace, no checkpoint node, already annotated)
2641
+ * leaves the summary untouched.
2642
+ */
2643
+ attachSummaryLocator(session, compactionId) {
2644
+ const policy = this.activePolicy(session);
2645
+ if (policy?.presetOptions?.summaryLocator !== true) return;
2646
+ const events = sessionEvents(session);
2647
+ const trace = findCompactionTrace(events, compactionId);
2648
+ if (trace === void 0) return;
2649
+ const located = buildLocatorBlock(events, trace.summaryShadowedRange);
2650
+ if (located === null) return;
2651
+ const block = located.text;
2652
+ let checkpointSeq;
2653
+ for (const seq of [...session.surface.nodes].reverse()) {
2654
+ const event = events[seq];
2655
+ if (event === void 0 || event.type !== "user/message") continue;
2656
+ const source = event.data.source;
2657
+ if (source === void 0 || source === null) continue;
2658
+ if (source.compactionId !== compactionId) continue;
2659
+ checkpointSeq = seq;
2660
+ break;
2661
+ }
2662
+ if (checkpointSeq === void 0) return;
2663
+ const original = events[checkpointSeq];
2664
+ if (original?.type !== "user/message") return;
2665
+ const content = original.data.content.map((block) => ({ ...block }));
2666
+ const lastText = content.filter((block) => block.type === "text").at(-1);
2667
+ const marker = "## Exact Sources (locators)";
2668
+ if (lastText === void 0) return;
2669
+ if (lastText.text.includes(marker)) return;
2670
+ lastText.text = `${lastText.text}\n\n${block}`;
2671
+ const replacement = createUserMessage({
2672
+ content,
2673
+ source: {
2674
+ kind: "plugin",
2675
+ plugin: "dsh-context-compression-improved-runtime"
2676
+ }
2677
+ });
2678
+ session.append("user/message", replacement, {
2679
+ surfaceOp: {
2680
+ op: "replace",
2681
+ start: checkpointSeq,
2682
+ end: checkpointSeq
2683
+ },
2684
+ sourceEventSeqs: [checkpointSeq]
2685
+ });
2686
+ emitCompressionAudit(this.ctx.logger, {
2687
+ schemaVersion: 1,
2688
+ kind: "summary-locator",
2689
+ sessionId: String(session.id),
2690
+ profile: policy.profile,
2691
+ checkpointSeq,
2692
+ summarySeq: trace.summarySeq,
2693
+ locatorChars: codePointLength(located.text),
2694
+ spillFiles: located.spillFiles,
2695
+ touchedFiles: located.touchedFiles
2696
+ });
2697
+ }
2698
+ /**
2699
+ * TokenPilot-inspired E1: sample oversized historical reads and ask the
2700
+ * auxiliary estimator whether their file state is still likely to be
2701
+ * referenced. Fire-and-forget: never awaited on the pruning chain, failures
2702
+ * back off exponentially per Session, verdicts only extend the rule-only
2703
+ * superseded classification.
2704
+ */
2705
+ async postflightEstimatorPass(session, signal) {
2706
+ const policy = this.activePolicy(session);
2707
+ const presetOptions = policy?.presetOptions;
2708
+ if (policy === void 0 || presetOptions?.readState !== true) return;
2709
+ const estimatorMode = presetOptions.estimator?.mode ?? "";
2710
+ if (estimatorMode === "") return;
2711
+ const failures = this.state.estimatorFailures.get(session);
2712
+ if (isCoolingDown(failures, Date.now())) return;
2713
+ const events = sessionEvents(session);
2714
+ const samples = [];
2715
+ const now = Date.now();
2716
+ for (const candidate of this.snapshot(session, measureForCompaction(this.ctx, session))) {
2717
+ if (samples.length >= 3) break;
2718
+ if (candidate.event.data.turn === void 0) continue;
2719
+ const tokens = exactTokens(candidate.count);
2720
+ if (tokens === void 0 || tokens <= policy.freshTriggerTokens) continue;
2721
+ const path = toolCallPath(candidate.call.arguments);
2722
+ if (path === void 0) continue;
2723
+ if (isSupersededRead(events, candidate.seq, path)) continue;
2724
+ if (this.state.estimatorVerdicts.get(session)?.has(candidate.seq) === true) continue;
2725
+ samples.push({
2726
+ seq: candidate.seq,
2727
+ path,
2728
+ turn: candidate.event.data.turn
2729
+ });
2730
+ }
2731
+ if (samples.length === 0) return;
2732
+ const answer = await new Estimator(this.ctx, this.activeSettings(session).presetOptions ?? {}).ask(buildEstimatorSystemPrompt(), buildEstimatorUserPrompt(samples), signal);
2733
+ const latencyMs = Date.now() - now;
2734
+ const ok = answer !== void 0 && signal.aborted === false;
2735
+ let expired = 0;
2736
+ if (ok && answer !== void 0) {
2737
+ let verdicts = this.state.estimatorVerdicts.get(session);
2738
+ if (verdicts === void 0) {
2739
+ verdicts = /* @__PURE__ */ new Map();
2740
+ this.state.estimatorVerdicts.set(session, verdicts);
2741
+ }
2742
+ for (const verdict of parseEstimatorAnswer(answer)) {
2743
+ if (verdicts.has(verdict.seq)) continue;
2744
+ verdicts.set(verdict.seq, verdict.expired);
2745
+ if (verdict.expired) expired += 1;
2746
+ }
2747
+ } else {
2748
+ const next = {
2749
+ failures: (failures?.failures ?? 0) + 1,
2750
+ cooldownUntil: Date.now() + backoffCooldownMs((failures?.failures ?? 0) + 1)
2751
+ };
2752
+ this.state.estimatorFailures.set(session, next);
2753
+ }
2754
+ emitCompressionAudit(this.ctx.logger, {
2755
+ schemaVersion: 1,
2756
+ kind: "estimator-outcome",
2757
+ sessionId: String(session.id),
2758
+ profile: policy.profile,
2759
+ channel: estimatorMode === "host" ? "host" : "direct",
2760
+ sampled: samples.length,
2761
+ expired,
2762
+ latencyMs,
2763
+ ok
2764
+ });
2765
+ }
2766
+ activePolicy(session, contextWindowTokens, stage = "pressure") {
2767
+ const settings = this.activeSettings(session);
2768
+ try {
2769
+ const policy = resolvePolicy(this.state.config, settings.profile, settings.custom, {
2770
+ ...contextWindowTokens === void 0 ? {} : { contextWindowTokens },
2771
+ autoCompactThresholdPercent: settings.autoCompact.thresholdPercent
2772
+ });
2773
+ const route = routeAuditFact(session);
2774
+ const auditKey = JSON.stringify({
2775
+ policy,
2776
+ contextWindowTokens: contextWindowTokens ?? null,
2777
+ route: route ?? null
2778
+ });
2779
+ if (this.state.policyResolutionAudits.get(session) !== auditKey) {
2780
+ this.state.policyResolutionAudits.set(session, auditKey);
2781
+ const overriddenLinkedFields = [
2782
+ "historyTriggerTokens",
2783
+ "historyKeepRecentTokens",
2784
+ "historyMinReclaimTokens"
2785
+ ].filter((key) => this.state.config[key] !== void 0).length;
2786
+ emitCompressionAudit(this.ctx.logger, {
2787
+ schemaVersion: 1,
2788
+ kind: "policy-resolved",
2789
+ sessionId: String(session.id),
2790
+ policy,
2791
+ ...contextWindowTokens === void 0 ? {} : { contextWindowTokens },
2792
+ coordination: {
2793
+ thresholdPercent: settings.autoCompact.thresholdPercent,
2794
+ ...policy.autoCompactTokens === void 0 ? {} : { autoCompactTokens: policy.autoCompactTokens },
2795
+ ...policy.microDeadlineTokens === void 0 ? {} : { microDeadlineTokens: policy.microDeadlineTokens },
2796
+ paramSource: settings.profile === "custom" ? "custom-manual" : overriddenLinkedFields === 3 ? "deployment-override" : overriddenLinkedFields > 0 ? "mixed" : policy.microDeadlineTokens === void 0 ? "fixed-preset" : "auto-compact-linked"
2797
+ },
2798
+ ...route === void 0 ? {} : { route },
2799
+ ...route === void 0 ? {} : tokenizerAuditFact(route)
2800
+ });
2801
+ }
2802
+ return policy;
2803
+ } catch (error) {
2804
+ const reason = error instanceof Error ? error.message : String(error);
2805
+ this.auditFailure(session, stage, "policy-resolution", error);
2806
+ this.warnOnce(session, `custom-policy:${settings.profile}:${reason}`, "context-compression kept original tool results because the Custom policy is not effective: %s", reason);
2807
+ return;
2808
+ }
2809
+ }
2810
+ contextWindowForRequest(session) {
2811
+ const settings = this.activeSettings(session);
2812
+ if (settings.profile === "off" || settings.profile === "native") return void 0;
2813
+ if (settings.profile === "custom" && settings.custom.unit !== "context-percent") return void 0;
2814
+ const config = session.requestHeader()?.config;
2815
+ const routed = session.requestContext();
2816
+ if (config === void 0 || config.provider.length === 0 || config.model.length === 0 || routed === void 0) return;
2817
+ if (routed.provider !== config.provider || routed.model !== config.model) {
2818
+ this.warnOnce(session, `custom-context-window-route:${config.provider}\0${config.model}`, "context-compression kept the context-linked policy inactive because durable route capacity belongs to %s/%s, not %s/%s", routed.provider, routed.model, config.provider, config.model);
2819
+ return;
2820
+ }
2821
+ if (!Number.isSafeInteger(routed.contextWindow) || routed.contextWindow === void 0 || routed.contextWindow <= 0) {
2822
+ this.warnOnce(session, `custom-context-window-capacity:${config.provider}\0${config.model}`, "context-compression kept the context-linked policy inactive because %s/%s has no positive durable context capacity", config.provider, config.model);
2823
+ return;
2824
+ }
2825
+ return routed.contextWindow;
2826
+ }
2827
+ runRequestBoundary(session, turn, step, signal) {
2828
+ const contextWindowTokens = this.contextWindowForRequest(session);
2829
+ if (signal.aborted) return;
2830
+ const policy = this.activePolicy(session, contextWindowTokens, "fresh");
2831
+ if (policy === void 0) return;
2832
+ const capacity = contextWindowTokens === void 0 ? {} : { contextWindowTokens };
2833
+ this.pruneSession(session, {
2834
+ stage: "fresh",
2835
+ freshTurn: turn,
2836
+ freshStep: step,
2837
+ ...capacity
2838
+ });
2839
+ if (policy.historyMode !== "disabled" || policy.tailTrim?.enabled === true) this.pruneSession(session, {
2840
+ stage: "pressure",
2841
+ ...capacity
2842
+ });
2843
+ }
2844
+ /** Resolve historical-aging authority without accepting caller-supplied elevation. */
2845
+ historyAllowed(session, policy, view) {
2846
+ switch (policy.historyMode) {
2847
+ case "disabled": return false;
2848
+ case "routine": return true;
2849
+ case "capacity-pressure": return this.capacityPressureActive(session, view, policy);
2850
+ case "adaptive": return false;
2851
+ /* v8 ignore next -- closed-union exhaustiveness guard */
2852
+ default: return assertNever(policy.historyMode, "history mode");
2853
+ }
2854
+ }
2855
+ /**
2856
+ * Match the compaction-basic pressure gate using public durable data. The
2857
+ * frozen Auto Compact deadline `D = floor(A x 0.875)` replaces the legacy
2858
+ * fixed 0.7 ratio once the standard-profile linkage resolved; without
2859
+ * linkage the 0.7 ratio is the documented fallback and reproduces the
2860
+ * previous behavior.
2861
+ */
2862
+ capacityPressureActive(session, view, policy) {
2863
+ const deadline = policy.microDeadlineTokens;
2864
+ if (deadline !== void 0) return view.totalTokens >= deadline;
2865
+ const header = session.requestHeader()?.config;
2866
+ const routed = session.requestContext();
2867
+ const contextWindow = routed?.contextWindow;
2868
+ if (header === void 0 || routed === void 0 || routed.provider !== header.provider || routed.model !== header.model || contextWindow === void 0 || !Number.isSafeInteger(contextWindow) || contextWindow <= 0) return false;
2869
+ return view.totalTokens >= Math.floor(contextWindow * CAPACITY_PRESSURE_RATIO);
2870
+ }
2871
+ /** Emit one bounded, independently correlatable postflight cost diagnostic per completed attempt. */
2872
+ logAdaptivePostflight(session, usage) {
2873
+ const attemptId = String(usage.attemptId);
2874
+ if (this.state.postflightDiagnostics.get(session) === attemptId) return;
2875
+ this.state.postflightDiagnostics.set(session, attemptId);
2876
+ const key = usage.key;
2877
+ let priceRecord;
2878
+ let cost;
2879
+ if (key === void 0) cost = {
2880
+ kind: "unpriced",
2881
+ reason: "measurement key unavailable"
2882
+ };
2883
+ else if (usage.responseModelId !== key.modelId) cost = {
2884
+ kind: "unpriced",
2885
+ reason: "response model mismatch or unavailable"
2886
+ };
2887
+ else if (usage.observedOutputTokens === void 0) cost = {
2888
+ kind: "unpriced",
2889
+ reason: "output token count unavailable"
2890
+ };
2891
+ else if (usage.cacheStatus !== "complete" || usage.cacheReadTokens === void 0 || usage.cacheMissTokens === void 0) cost = {
2892
+ kind: "unpriced",
2893
+ reason: "complete cache split unavailable"
2894
+ };
2895
+ else {
2896
+ const startedAt = new Date(usage.startedAtMs);
2897
+ const completedAt = new Date(usage.completedAtMs);
2898
+ const resolution = resolveOfficialDeepSeekPrice({
2899
+ provider: key.provider,
2900
+ baseUrlClass: key.baseUrlClass,
2901
+ apiRoute: key.apiRoute,
2902
+ modelId: key.modelId,
2903
+ currency: "USD",
2904
+ at: startedAt
2905
+ });
2906
+ if (resolution.kind === "priced") priceRecord = {
2907
+ catalogVersion: resolution.record.catalogVersion,
2908
+ checkedAt: resolution.record.checkedAt,
2909
+ sourceUrl: resolution.record.sourceUrl,
2910
+ currency: resolution.record.currency,
2911
+ modelId: resolution.record.modelId,
2912
+ apiRoute: resolution.record.apiRoute,
2913
+ startBand: resolution.record.band
2914
+ };
2915
+ cost = priceOfficialDeepSeekUsage({
2916
+ provider: key.provider,
2917
+ baseUrlClass: key.baseUrlClass,
2918
+ apiRoute: key.apiRoute,
2919
+ modelId: key.modelId,
2920
+ currency: "USD",
2921
+ startedAt,
2922
+ completedAt,
2923
+ usage: {
2924
+ cacheReadTokens: usage.cacheReadTokens,
2925
+ cacheMissTokens: usage.cacheMissTokens,
2926
+ outputTokens: usage.observedOutputTokens
2927
+ }
2928
+ });
2929
+ }
2930
+ this.ctx.logger.debug(`context-compression adaptive postflight ${JSON.stringify({
2931
+ sessionId: String(session.id),
2932
+ providerRequestOrdinal: Number(usage.providerRequestOrdinal),
2933
+ attemptId,
2934
+ startedAtMs: usage.startedAtMs,
2935
+ completedAtMs: usage.completedAtMs,
2936
+ measurementKind: usage.measurement.kind,
2937
+ catalogVersion: DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION,
2938
+ ...priceRecord === void 0 ? {} : { priceRecord },
2939
+ usage: {
2940
+ promptTokens: usage.observedPromptTokens,
2941
+ ...usage.observedOutputTokens === void 0 ? {} : { outputTokens: usage.observedOutputTokens },
2942
+ cacheStatus: usage.cacheStatus ?? "unknown",
2943
+ ...usage.cacheReadTokens === void 0 ? {} : { cacheReadTokens: usage.cacheReadTokens },
2944
+ ...usage.cacheMissTokens === void 0 ? {} : { cacheMissTokens: usage.cacheMissTokens }
2945
+ },
2946
+ cost
2947
+ })}`);
2948
+ }
2949
+ /** Decide one already-planned History batch from adjacent request-level facts only. */
2950
+ adaptiveHistoryAllowed(session, view, plans, capacityPressure) {
2951
+ const log = (allowHistory, reason, detail = {}) => {
2952
+ this.ctx.logger.debug(`context-compression adaptive ${JSON.stringify({
2953
+ sessionId: String(session.id),
2954
+ allowHistory,
2955
+ reason,
2956
+ catalogVersion: DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION,
2957
+ ...detail
2958
+ })}`);
2959
+ return allowHistory;
2960
+ };
2961
+ const usage = view.lastCompletedUsage;
2962
+ if (usage !== void 0) this.logAdaptivePostflight(session, usage);
2963
+ if (plans.length === 0) return false;
2964
+ if (capacityPressure) return log(true, "capacity-override");
2965
+ const currentKey = view.latestEnvelopeKey;
2966
+ if (usage === void 0) return log(false, "usage-unavailable");
2967
+ if (usage.key === void 0 || currentKey === void 0) return log(false, "measurement-key-unavailable");
2968
+ if (!sameProviderMeasurementKey(usage.key, currentKey)) return log(false, "measurement-key-mismatch");
2969
+ if (usage.responseModelId !== usage.key.modelId) return log(false, "response-model-mismatch-or-unavailable");
2970
+ if (usage.cacheStatus !== "complete" || usage.cacheReadTokens === void 0 || usage.cacheMissTokens === void 0) return log(false, "cache-split-incomplete");
2971
+ const price = resolveOfficialDeepSeekPrice({
2972
+ provider: usage.key.provider,
2973
+ baseUrlClass: usage.key.baseUrlClass,
2974
+ apiRoute: usage.key.apiRoute,
2975
+ modelId: usage.key.modelId,
2976
+ currency: "USD",
2977
+ at: /* @__PURE__ */ new Date()
2978
+ });
2979
+ if (price.kind === "unpriced") return log(false, `adaptive-unknown-price:${price.reason}`);
2980
+ const bounds = deriveAdaptiveTokenBounds({
2981
+ exactReclaimedTokens: plans.reduce((sum, plan) => sum + plan.tokensBefore - plan.tokensAfter, 0),
2982
+ earliestChangedSeq: Math.min(...plans.map((plan) => plan.candidate.seq)),
2983
+ previousPromptTokens: usage.observedPromptTokens,
2984
+ expectedTokenizerRevision: usage.key.tokenizerRevision,
2985
+ previousRequestMeasurement: usage.measurement,
2986
+ measuredNodes: view.measuredNodes
2987
+ });
2988
+ const decision = decideConservativeAdaptive({
2989
+ capacityPressure: false,
2990
+ bounds,
2991
+ inputCacheHitRate: price.record.inputCacheHit,
2992
+ inputCacheMissRate: price.record.inputCacheMiss,
2993
+ observedCacheReadTokens: usage.cacheReadTokens
2994
+ });
2995
+ return log(decision.allowHistory, decision.reason, {
2996
+ priceBand: price.record.band,
2997
+ observedPromptTokens: usage.observedPromptTokens,
2998
+ observedCacheReadTokens: usage.cacheReadTokens,
2999
+ bounds,
3000
+ ..."minimumRemovalValue" in decision ? { minimumRemovalValue: decision.minimumRemovalValue } : {},
3001
+ ..."maximumCacheLossPenalty" in decision ? { maximumCacheLossPenalty: decision.maximumCacheLossPenalty } : {}
3002
+ });
3003
+ }
3004
+ decisions(session) {
3005
+ let decisions = this.state.firstExposure.get(session);
3006
+ if (decisions === void 0) {
3007
+ decisions = /* @__PURE__ */ new Set();
3008
+ this.state.firstExposure.set(session, decisions);
3009
+ }
3010
+ return decisions;
3011
+ }
3012
+ /**
3013
+ * TokenPilot-style skipReduction: recovery tool output is permanently exempt
3014
+ * from every reduction pass so retrieved content can never enter a
3015
+ * compress-restore-oscillation loop. A call-name match covers the built-in
3016
+ * recovery tool; the per-session set admits future recovery paths.
3017
+ */
3018
+ isRecoveryExempt(session, candidate) {
3019
+ if (candidate.call.name === "context_compression_retrieve") return true;
3020
+ return this.state.recoveryExemptions.get(session)?.has(candidate.seq) ?? false;
3021
+ }
3022
+ /** Register a result seq as permanently exempt from further reduction. */
3023
+ grantRecoveryExemption(session, seq) {
3024
+ let exemptions = this.state.recoveryExemptions.get(session);
3025
+ if (exemptions === void 0) {
3026
+ exemptions = /* @__PURE__ */ new Set();
3027
+ this.state.recoveryExemptions.set(session, exemptions);
3028
+ }
3029
+ exemptions.add(seq);
3030
+ }
3031
+ decideFreshStep(session, options, policy, view) {
3032
+ if (options.freshTurn === void 0 || options.freshStep === void 0) {
3033
+ this.auditComponent(session, policy, "fresh", "fresh", policy.freshEnabled ? "skipped" : "disabled", policy.freshEnabled ? "missing-completed-step-coordinates" : "profile-policy");
3034
+ this.auditComponent(session, policy, "aggregate", "fresh", policy.aggregateEnabled ? "skipped" : "disabled", policy.aggregateEnabled ? "missing-completed-step-coordinates" : "profile-policy");
3035
+ return emptyResult();
3036
+ }
3037
+ const decisions = this.decisions(session);
3038
+ const candidates = this.snapshot(session, view).filter((candidate) => typeof candidate.event.surfaceOp !== "object" && candidate.event.data.turn === options.freshTurn && candidate.event.data.step === options.freshStep && !decisions.has(candidate.seq));
3039
+ if (candidates.length === 0) {
3040
+ this.auditComponent(session, policy, "fresh", "fresh", policy.freshEnabled ? "skipped" : "disabled", policy.freshEnabled ? "no-new-tool-result-candidates" : "profile-policy");
3041
+ this.auditComponent(session, policy, "aggregate", "fresh", policy.aggregateEnabled ? "skipped" : "disabled", policy.aggregateEnabled ? "no-new-tool-result-candidates" : "profile-policy");
3042
+ return emptyResult();
3043
+ }
3044
+ const plans = /* @__PURE__ */ new Map();
3045
+ let freshPlanned = 0;
3046
+ const dedupeEnabled = policy.presetOptions?.dedupeToolResults === true;
3047
+ const exactCandidateTokens = candidates.map((candidate) => exactTokens(candidate.count));
3048
+ const exactAvailable = exactCandidateTokens.every((tokens) => tokens !== void 0);
3049
+ const maxCandidateTokens = exactAvailable ? Math.max(...exactCandidateTokens) : void 0;
3050
+ if (policy.freshEnabled) {
3051
+ if (candidates.some((candidate) => candidate.call.name !== "context_compression_retrieve" && candidate.count.kind !== "exact-tokenizer")) this.warnExactUnavailable(session, view, "fresh");
3052
+ for (const candidate of candidates) {
3053
+ if (this.isRecoveryExempt(session, candidate)) continue;
3054
+ if (dedupeEnabled) {
3055
+ const dedupePlan = this.planDedupe(candidate, session, policy, view);
3056
+ if (dedupePlan !== null) {
3057
+ plans.set(candidate.seq, dedupePlan);
3058
+ continue;
3059
+ }
3060
+ }
3061
+ const plan = this.planFresh(candidate, session, policy, view);
3062
+ if (plan !== null) {
3063
+ plans.set(candidate.seq, plan);
3064
+ freshPlanned += 1;
3065
+ }
3066
+ }
3067
+ }
3068
+ let aggregateInputTokens;
3069
+ let aggregatePlanned = 0;
3070
+ if (policy.aggregateEnabled) {
3071
+ const aggregateAvailable = exactAvailable;
3072
+ if (!aggregateAvailable) this.warnExactUnavailable(session, view, "aggregate");
3073
+ let total = aggregateAvailable ? candidates.reduce((sum, candidate) => sum + (plans.get(candidate.seq)?.tokensAfter ?? exactTokens(candidate.count) ?? 0), 0) : 0;
3074
+ if (aggregateAvailable) aggregateInputTokens = total;
3075
+ if (aggregateAvailable && total > policy.aggregateTriggerTokens) {
3076
+ const remaining = candidates.filter((candidate) => !this.isRecoveryExempt(session, candidate)).sort((a, b) => Number(isError(a)) - Number(isError(b)) || (plans.get(b.seq)?.tokensAfter ?? exactTokens(b.count) ?? 0) - (plans.get(a.seq)?.tokensAfter ?? exactTokens(a.count) ?? 0));
3077
+ for (const candidate of remaining) {
3078
+ const previous = plans.get(candidate.seq);
3079
+ const plan = this.planAggregate(candidate, session, view);
3080
+ const previousTokens = previous?.tokensAfter ?? exactTokens(candidate.count) ?? 0;
3081
+ if (plan === null || plan.tokensAfter >= previousTokens) continue;
3082
+ plans.set(candidate.seq, plan);
3083
+ aggregatePlanned += 1;
3084
+ total -= previousTokens - plan.tokensAfter;
3085
+ if (total <= policy.aggregateTargetTokens) break;
3086
+ }
3087
+ if (total > policy.aggregateTargetTokens) this.ctx.logger.warn("context-compression fresh aggregate residual: %d tokens exceed target %d", total, policy.aggregateTargetTokens);
3088
+ }
3089
+ }
3090
+ const landed = this.landAll(session, candidates.map((candidate) => plans.get(candidate.seq)).filter((plan) => plan !== void 0));
3091
+ const freshLanded = landed.some((entry) => entry.stage === "fresh" && plans.get(entry.originalSeq)?.component === "fresh");
3092
+ const aggregateLanded = landed.some((entry) => entry.stage === "fresh" && plans.get(entry.originalSeq)?.component === "aggregate");
3093
+ if (!freshLanded) this.auditComponent(session, policy, "fresh", "fresh", policy.freshEnabled ? "skipped" : "disabled", !policy.freshEnabled ? "profile-policy" : !exactAvailable ? "exact-tokenizer-unavailable" : (maxCandidateTokens ?? 0) <= policy.freshTriggerTokens ? "at-or-below-trigger" : freshPlanned > 0 && aggregatePlanned > 0 ? "superseded-by-aggregate" : freshPlanned === 0 ? "no-valid-reduction" : "recovery-tool-unavailable", {
3094
+ measurementKind: exactAvailable ? "exact-tokenizer" : "unavailable",
3095
+ ...maxCandidateTokens === void 0 ? {} : { currentTokens: maxCandidateTokens },
3096
+ triggerTokens: policy.freshTriggerTokens,
3097
+ targetTokens: policy.freshTargetTokens
3098
+ });
3099
+ if (!aggregateLanded) this.auditComponent(session, policy, "aggregate", "fresh", policy.aggregateEnabled ? "skipped" : "disabled", !policy.aggregateEnabled ? "profile-policy" : !exactAvailable ? "exact-tokenizer-unavailable" : (aggregateInputTokens ?? 0) <= policy.aggregateTriggerTokens ? "at-or-below-trigger" : aggregatePlanned === 0 ? "no-valid-reduction" : "recovery-tool-unavailable", {
3100
+ measurementKind: exactAvailable ? "exact-tokenizer" : "unavailable",
3101
+ ...aggregateInputTokens === void 0 ? {} : { currentTokens: aggregateInputTokens },
3102
+ triggerTokens: policy.aggregateTriggerTokens,
3103
+ targetTokens: policy.aggregateTargetTokens
3104
+ });
3105
+ for (const candidate of candidates) decisions.add(candidate.seq);
3106
+ return summarize(landed);
3107
+ }
3108
+ snapshot(session, view) {
3109
+ const events = sessionEvents(session);
3110
+ const calls = /* @__PURE__ */ new Map();
3111
+ for (const event of events) if (event.type === "tool/call") calls.set(event.data.callId, {
3112
+ name: event.data.name,
3113
+ arguments: event.data.arguments
3114
+ });
3115
+ const candidates = [];
3116
+ const measured = new Map(view.measuredNodes.map((node) => [node.seq, node.count]));
3117
+ const projectionPrices = new Map(view.nodes.map((node) => [node.seq, node.tokens]));
3118
+ for (const seq of [...session.surface.nodes]) {
3119
+ const event = events[seq];
3120
+ if (event?.type !== "tool/result") continue;
3121
+ const shadowedHeuristicTokenCount = projectionPrices.get(seq);
3122
+ if (shadowedHeuristicTokenCount === void 0) throw new Error(`surface node ${String(seq)} is absent from the atomic legacy projection`);
3123
+ const content = event.data.message.content[0].content;
3124
+ candidates.push({
3125
+ seq,
3126
+ event,
3127
+ call: calls.get(event.data.message.source.callId) ?? {
3128
+ name: "unknown",
3129
+ arguments: "{}"
3130
+ },
3131
+ count: onlyTextBlocks(content) === null ? unavailableCount(`surface node ${String(seq)} contains unsupported rich tool-result content`) : measured.get(seq) ?? unavailableCount(`surface node ${String(seq)} is absent from the atomic token view`),
3132
+ shadowedHeuristicTokenCount,
3133
+ characterPressure: pressureCost(content)
3134
+ });
3135
+ }
3136
+ return candidates;
3137
+ }
3138
+ planNative(candidate, session, stage, policy, view) {
3139
+ if (this.isRecoveryExempt(session, candidate)) return null;
3140
+ const tokensBefore = exactTokens(candidate.count);
3141
+ if (tokensBefore === void 0 || tokensBefore <= policy.nativeTriggerTokens) return null;
3142
+ const result = candidate.event.data.message.content[0];
3143
+ if (onlyTextBlocks(result.content) === null) return null;
3144
+ const sourceSeq = rootToolResultSeq(session, candidate.seq);
3145
+ const marker = recoveryMarker(sourceRef(session, sourceSeq), "tool result middle pruned");
3146
+ let head = this.state.config.headChars;
3147
+ let tail = this.state.config.tailChars;
3148
+ for (let attempt = 0; attempt < 10; attempt += 1) {
3149
+ const threshold = head + codePointLength(marker) + tail;
3150
+ const content = nativePruneContent(result.content, threshold, head, tail, marker);
3151
+ if (content !== null) {
3152
+ const plan = this.plan(candidate, content, sourceSeq, "native-head-tail", stage, "native-tool-result", void 0, view);
3153
+ if (plan !== null && plan.tokensAfter <= policy.nativeTargetTokens) return plan;
3154
+ }
3155
+ if (head === 0 && tail === 0) break;
3156
+ head = Math.floor(head / 2);
3157
+ tail = Math.floor(tail / 2);
3158
+ }
3159
+ return this.planAggregate(candidate, session, view, "native-whole-result", stage, policy.nativeTargetTokens, "native-tool-result");
3160
+ }
3161
+ /**
3162
+ * TokenPilot-inspired A1: replace a byte-identical repeat of an earlier
3163
+ * oversized tool result with a pointer to its first occurrence. The first
3164
+ * occurrence's hash is always recorded so later repeats can point at the
3165
+ * append-only original event even after the surface copy is reduced.
3166
+ */
3167
+ planDedupe(candidate, session, policy, view) {
3168
+ if (typeof candidate.event.surfaceOp === "object") return null;
3169
+ const result = candidate.event.data.message.content[0];
3170
+ const text = flattenPlainText(result.content);
3171
+ if (text === void 0) return null;
3172
+ const tokensBefore = exactTokens(candidate.count);
3173
+ if (tokensBefore === void 0 || tokensBefore <= policy.freshTriggerTokens) return null;
3174
+ let table = this.state.dedupeTables.get(session);
3175
+ if (table === void 0) {
3176
+ table = new DedupeTable();
3177
+ this.state.dedupeTables.set(session, table);
3178
+ }
3179
+ const hash = dedupeHash(text, "trim-eol");
3180
+ const entry = table.get(hash);
3181
+ if (entry !== void 0 && entry.seq !== candidate.seq) {
3182
+ const placeholder = dedupePlaceholder(entry, codePointLength(text));
3183
+ const plan = this.plan(candidate, [{
3184
+ type: "text",
3185
+ text: placeholder
3186
+ }], entry.seq, "dedupe-pointer", "fresh", "fresh", void 0, view, { noNetSavingsGuard: true });
3187
+ if (plan !== null) return plan;
3188
+ return null;
3189
+ }
3190
+ if (entry === void 0) table.record(hash, {
3191
+ seq: candidate.seq,
3192
+ sourceRef: sourceRef(session, candidate.seq),
3193
+ toolName: candidate.call.name,
3194
+ originalChars: codePointLength(text)
3195
+ });
3196
+ return null;
3197
+ }
3198
+ planFresh(candidate, session, policy, view) {
3199
+ if (typeof candidate.event.surfaceOp === "object") return null;
3200
+ const result = candidate.event.data.message.content[0];
3201
+ const tokensBefore = exactTokens(candidate.count);
3202
+ if (tokensBefore === void 0 || tokensBefore <= policy.freshTriggerTokens) return null;
3203
+ const sourceSeq = candidate.seq;
3204
+ const sourceRef$1 = sourceRef(session, sourceSeq);
3205
+ const textBlock = onlyTextBlock(result.content);
3206
+ if (textBlock !== null) {
3207
+ let budgetChars = Math.max(1, Math.floor(codePointLength(textBlock.text) * .75));
3208
+ const codeSkeleton = this.activeSettings(session).codeSkeleton.enabled;
3209
+ for (let attempt = 0; attempt < 10; attempt += 1) {
3210
+ const output = reduceFreshToolResult({
3211
+ toolName: candidate.call.name,
3212
+ argumentsText: candidate.call.arguments,
3213
+ text: textBlock.text,
3214
+ budgetChars,
3215
+ sourceRef: sourceRef$1,
3216
+ isError: result.isError === true || candidate.event.data.error !== void 0,
3217
+ codeSkeleton
3218
+ });
3219
+ if (output !== null) {
3220
+ const plan = this.plan(candidate, [{
3221
+ ...textBlock,
3222
+ text: output.text
3223
+ }], sourceSeq, output.reducer, "fresh", "fresh", void 0, view, { noNetSavingsGuard: policy.presetOptions?.noNetSavingsGuard === true });
3224
+ if (plan !== null && plan.tokensAfter <= policy.freshTargetTokens) return plan;
3225
+ }
3226
+ if (budgetChars === 1) break;
3227
+ budgetChars = Math.max(1, Math.floor(budgetChars / 2));
3228
+ }
3229
+ }
3230
+ return this.planAggregate(candidate, session, view, "fresh-whole-result", "fresh", policy.freshTargetTokens, "fresh");
3231
+ }
3232
+ planAggregate(candidate, session, view, reducer = "fresh-step-aggregate", stage = "fresh", targetTokens, component = "aggregate", historyMode) {
3233
+ if (isError(candidate)) return this.planErrorEvidence(candidate, session, view, stage, targetTokens, component, historyMode);
3234
+ const sourceSeq = rootToolResultSeq(session, candidate.seq);
3235
+ const sourceRef$2 = sourceRef(session, sourceSeq);
3236
+ const text = [
3237
+ "[Tool result reduced to satisfy the completed-step aggregate budget]",
3238
+ `tool: ${candidate.call.name}`,
3239
+ `source: ${sourceRef$2}`,
3240
+ "Use context_compression_retrieve with this source if the omitted evidence is necessary."
3241
+ ].join("\n");
3242
+ const plan = this.plan(candidate, [{
3243
+ type: "text",
3244
+ text
3245
+ }], sourceSeq, reducer, stage, component, historyMode, view);
3246
+ return plan !== null && (targetTokens === void 0 || plan.tokensAfter <= targetTokens) ? plan : null;
3247
+ }
3248
+ /** Preserve bounded diagnostic evidence whenever an all-text error is reduced. */
3249
+ planErrorEvidence(candidate, session, view, stage, targetTokens, component = "aggregate", historyMode) {
3250
+ if (!isError(candidate)) return null;
3251
+ const result = candidate.event.data.message.content[0];
3252
+ const blocks = onlyTextBlocks(result.content);
3253
+ if (blocks === null) return null;
3254
+ const text = blocks.map((block) => block.text).join("\n");
3255
+ const sourceSeq = rootToolResultSeq(session, candidate.seq);
3256
+ const sourceRef$3 = sourceRef(session, sourceSeq);
3257
+ const output = historicalPlaceholder({
3258
+ toolName: candidate.call.name,
3259
+ sourceRef: sourceRef$3,
3260
+ charsBefore: codePointLength(text),
3261
+ isError: true,
3262
+ text,
3263
+ compact: false
3264
+ });
3265
+ if (!verifyReduction({
3266
+ toolName: candidate.call.name,
3267
+ argumentsText: candidate.call.arguments,
3268
+ text,
3269
+ budgetChars: 1200,
3270
+ sourceRef: sourceRef$3,
3271
+ isError: true
3272
+ }, output)) return null;
3273
+ const plan = this.plan(candidate, [{
3274
+ type: "text",
3275
+ text: output.text
3276
+ }], sourceSeq, "error-evidence-placeholder", stage, component, historyMode, view);
3277
+ return plan !== null && (targetTokens === void 0 || plan.tokensAfter <= targetTokens) ? plan : null;
3278
+ }
3279
+ planHistoricalAging(session, policy, view) {
3280
+ const candidates = this.snapshot(session, view);
3281
+ const events = sessionEvents(session);
3282
+ const exact = [];
3283
+ for (const candidate of candidates) {
3284
+ const tokens = exactTokens(candidate.count);
3285
+ if (tokens === void 0) {
3286
+ this.warnExactUnavailable(session, view, "history");
3287
+ return { kind: "exact-tokenizer-unavailable" };
3288
+ }
3289
+ exact.push(tokens);
3290
+ }
3291
+ const total = exact.reduce((sum, tokens) => sum + tokens, 0);
3292
+ const trigger = policy.historyTriggerTokens;
3293
+ const deadline = policy.microDeadlineTokens;
3294
+ const lastChance = deadline !== void 0 && view.totalTokens >= deadline;
3295
+ if (total <= trigger && !lastChance) return { kind: "below-profile-trigger" };
3296
+ const protectedSeqs = this.protectedHistoryCandidateSeqs(candidates, policy);
3297
+ const isUnsafe = (candidate) => {
3298
+ if (this.isRecoveryExempt(session, candidate)) return true;
3299
+ const result = candidate.event.data.message.content[0];
3300
+ return onlyTextBlock(result.content)?.text.includes("[Old tool result content cleared from active context]") === true;
3301
+ };
3302
+ const safe = candidates.filter((candidate) => !isUnsafe(candidate));
3303
+ const eligible = safe.filter((candidate) => !protectedSeqs.has(candidate.seq));
3304
+ if (eligible.length === 0) return safe.length === 0 ? { kind: "no-safe-candidates" } : { kind: "protected-working-set" };
3305
+ const planned = [];
3306
+ let reclaim = 0;
3307
+ const microTarget = deadline === void 0 ? void 0 : Math.max(0, deadline - policy.historyMinReclaimTokens);
3308
+ const required = Math.max(policy.historyMinReclaimTokens, total - trigger, ...microTarget === void 0 ? [] : [view.totalTokens - microTarget]);
3309
+ const batchTarget = microTarget === void 0 ? policy.historyMinReclaimTokens : required;
3310
+ for (const candidate of eligible) {
3311
+ const result = candidate.event.data.message.content[0];
3312
+ const block = onlyTextBlock(result.content);
3313
+ if (policy.presetOptions?.readState === true && block !== null) {
3314
+ const readPath = toolCallPath(candidate.call.arguments);
3315
+ const estimatorExpired = this.state.estimatorVerdicts.get(session)?.get(candidate.seq) === true;
3316
+ if (readPath !== void 0 && (isSupersededRead(events, candidate.seq, readPath) || estimatorExpired)) {
3317
+ const plan = this.planAggregate(candidate, session, view, "superseded-read-whole-result", "pressure", void 0, "history", policy.historyMode);
3318
+ if (plan === null) continue;
3319
+ planned.push(plan);
3320
+ reclaim += plan.tokensBefore - plan.tokensAfter;
3321
+ if (reclaim >= required) break;
3322
+ continue;
3323
+ }
3324
+ }
3325
+ const sourceSeq = rootToolResultSeq(session, candidate.seq);
3326
+ if (block === null) {
3327
+ const plan = this.planAggregate(candidate, session, view, "historical-rich-whole-result", "pressure", void 0, "history", policy.historyMode);
3328
+ if (plan === null) continue;
3329
+ planned.push(plan);
3330
+ reclaim += plan.tokensBefore - plan.tokensAfter;
3331
+ if (reclaim >= required) break;
3332
+ continue;
3333
+ }
3334
+ const output = historicalPlaceholder({
3335
+ toolName: candidate.call.name,
3336
+ sourceRef: sourceRef(session, sourceSeq),
3337
+ charsBefore: codePointLength(block.text),
3338
+ isError: result.isError === true || candidate.event.data.error !== void 0,
3339
+ text: block.text,
3340
+ compact: false
3341
+ });
3342
+ if (!verifyReduction({
3343
+ toolName: candidate.call.name,
3344
+ argumentsText: candidate.call.arguments,
3345
+ text: block.text,
3346
+ budgetChars: 1200,
3347
+ sourceRef: sourceRef(session, sourceSeq),
3348
+ isError: result.isError === true || candidate.event.data.error !== void 0
3349
+ }, output)) continue;
3350
+ let replacementText = output.text;
3351
+ if (policy.presetOptions?.readState === true) {
3352
+ const omitted = countOmittedLines(block.text, output.text);
3353
+ const census = omitted === void 0 ? void 0 : clusterOmittedLines(block.text, omitted);
3354
+ if (census !== void 0) replacementText = `${output.text}
3355
+ [... ${census} ...]`;
3356
+ }
3357
+ const plan = this.plan(candidate, [{
3358
+ ...block,
3359
+ text: replacementText
3360
+ }], sourceSeq, output.reducer, "pressure", "history", policy.historyMode, view);
3361
+ if (plan === null) continue;
3362
+ planned.push(plan);
3363
+ reclaim += plan.tokensBefore - plan.tokensAfter;
3364
+ if (reclaim >= required) break;
3365
+ }
3366
+ if (reclaim >= batchTarget && planned.length > 0) return historyOutcome(planned);
3367
+ return lastChance ? {
3368
+ kind: "cannot-reach-deadline-target",
3369
+ reclaim,
3370
+ required
3371
+ } : {
3372
+ kind: "insufficient-reclaim",
3373
+ reclaim,
3374
+ required
3375
+ };
3376
+ }
3377
+ protectedHistoryResultSeqs(session, policy, view) {
3378
+ const candidates = this.snapshot(session, view);
3379
+ if (candidates.some((candidate) => exactTokens(candidate.count) === void 0)) return null;
3380
+ return this.protectedHistoryCandidateSeqs(candidates, policy);
3381
+ }
3382
+ /** Select the newest completed tool calls and token tail for History-derived stages. */
3383
+ protectedHistoryCandidateSeqs(candidates, policy) {
3384
+ const protectedSeqs = /* @__PURE__ */ new Set();
3385
+ for (let index = candidates.length - 1; index >= 0 && candidates.length - index <= policy.historyKeepRecentToolCalls; index--) {
3386
+ const candidate = candidates[index];
3387
+ if (candidate !== void 0) protectedSeqs.add(candidate.seq);
3388
+ }
3389
+ let recentTokens = 0;
3390
+ for (let index = candidates.length - 1; index >= 0 && recentTokens < policy.historyKeepRecentTokens; index--) {
3391
+ const candidate = candidates[index];
3392
+ if (candidate === void 0) continue;
3393
+ protectedSeqs.add(candidate.seq);
3394
+ recentTokens += exactTokens(candidate.count) ?? 0;
3395
+ }
3396
+ return protectedSeqs;
3397
+ }
3398
+ /** Atomically replace at most one oldest safe completed tool-call group. */
3399
+ landOldestTailTrimGroup(session, policy, view) {
3400
+ const tailTrim = policy.tailTrim;
3401
+ if (tailTrim?.enabled !== true) return;
3402
+ const events = sessionEvents(session);
3403
+ if (view.currentSurface.kind !== "exact-tokenizer" || view.currentSurface.tokens <= tailTrim.triggerTokens) {
3404
+ if (view.currentSurface.kind !== "exact-tokenizer") this.warnExactUnavailable(session, view, "tailtrim");
3405
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", view.currentSurface.kind !== "exact-tokenizer" ? "exact-tokenizer-unavailable" : "at-or-below-trigger", {
3406
+ measurementKind: view.currentSurface.kind,
3407
+ ...view.currentSurface.kind === "exact-tokenizer" ? { currentTokens: view.currentSurface.tokens } : {},
3408
+ triggerTokens: tailTrim.triggerTokens
3409
+ });
3410
+ return;
3411
+ }
3412
+ const surfaceCount = view.currentSurface;
3413
+ if (!this.hasRecoveryTool(session)) {
3414
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "recovery-tool-unavailable", {
3415
+ measurementKind: "exact-tokenizer",
3416
+ currentTokens: surfaceCount.tokens,
3417
+ triggerTokens: tailTrim.triggerTokens
3418
+ });
3419
+ return;
3420
+ }
3421
+ if (!hasOpenTurn(session)) {
3422
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "no-open-turn", {
3423
+ measurementKind: "exact-tokenizer",
3424
+ currentTokens: surfaceCount.tokens,
3425
+ triggerTokens: tailTrim.triggerTokens
3426
+ });
3427
+ return;
3428
+ }
3429
+ const protectedResults = this.protectedHistoryResultSeqs(session, policy, view);
3430
+ if (protectedResults === null) {
3431
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "exact-tokenizer-unavailable-in-protected-set", {
3432
+ measurementKind: "unavailable",
3433
+ currentTokens: surfaceCount.tokens,
3434
+ triggerTokens: tailTrim.triggerTokens
3435
+ });
3436
+ return;
3437
+ }
3438
+ const measured = new Map(view.measuredNodes.map((node) => [node.seq, node.count]));
3439
+ const heuristic = new Map(view.nodes.map((node) => [node.seq, node.tokens]));
3440
+ const completedTurns = /* @__PURE__ */ new Set();
3441
+ const completedSteps = /* @__PURE__ */ new Set();
3442
+ for (const event of events) if (event.type === "turn/end") completedTurns.add(event.data.turn);
3443
+ else if (event.type === "step/end") completedSteps.add(`${String(event.data.turn)}:${String(event.data.step)}`);
3444
+ const firstCompletedSurfaceTurn = session.surface.nodes.map((seq) => events[seq]).filter((event) => (event?.type === "assistant/message" || event?.type === "tool/result") && completedTurns.has(event.data.turn)).reduce((first, event) => first === void 0 ? event.data.turn : Math.min(first, event.data.turn), void 0);
3445
+ const nodes = [...session.surface.nodes];
3446
+ for (let index = 0; index < nodes.length; index++) {
3447
+ const assistantSeq = nodes[index];
3448
+ if (assistantSeq === void 0) continue;
3449
+ const assistant = events[assistantSeq];
3450
+ if (assistant?.type !== "assistant/message" || assistant.data.interrupted === true || assistant.data.message.content.length === 0 || assistant.data.message.content.some((block) => block.type !== "tool-call") || assistant.data.turn === firstCompletedSurfaceTurn || !completedTurns.has(assistant.data.turn) || !completedSteps.has(`${String(assistant.data.turn)}:${String(assistant.data.step)}`)) continue;
3451
+ const calls = assistant.data.message.content;
3452
+ if (calls.some((call) => call.name === "context_compression_retrieve")) continue;
3453
+ const callIds = calls.map((call) => String(call.id));
3454
+ if (new Set(callIds).size !== callIds.length) continue;
3455
+ const resultSeqs = nodes.slice(index + 1, index + 1 + calls.length);
3456
+ if (resultSeqs.length !== calls.length || resultSeqs.some((seq) => protectedResults.has(seq))) continue;
3457
+ const results = resultSeqs.map((seq) => events[seq]);
3458
+ if (results.some((event) => {
3459
+ if (event?.type !== "tool/result" || event.data.turn !== assistant.data.turn || event.data.step !== assistant.data.step || event.data.error !== void 0) return true;
3460
+ const block = event.data.message.content[0];
3461
+ if (block.isError === true) return true;
3462
+ return block.content.some((contentBlock) => contentBlock.type !== "text");
3463
+ })) continue;
3464
+ const next = events[nodes[index + 1 + calls.length] ?? -1];
3465
+ if (next?.type === "tool/result" && next.data.turn === assistant.data.turn && next.data.step === assistant.data.step) continue;
3466
+ const resultIds = results.map((event) => event?.type === "tool/result" ? String(event.data.message.source.callId) : "");
3467
+ if (new Set(resultIds).size !== resultIds.length || resultIds.some((id, resultIndex) => id !== callIds[resultIndex])) continue;
3468
+ const shadowedSeqs = [assistantSeq, ...resultSeqs];
3469
+ const roots = shadowedSeqs.map((seq) => this.uniqueAppendRoot(session, seq));
3470
+ if (roots.some((root) => root === null)) continue;
3471
+ const sourceEventSeqs = roots;
3472
+ if (new Set(sourceEventSeqs).size !== sourceEventSeqs.length) continue;
3473
+ const counts = shadowedSeqs.map((seq) => measured.get(seq));
3474
+ if (counts.some((count) => count?.kind !== "exact-tokenizer")) continue;
3475
+ const exactCounts = counts;
3476
+ if (exactCounts.some((count) => count.tokenizerId !== surfaceCount.tokenizerId || count.tokenizerRevision !== surfaceCount.tokenizerRevision)) continue;
3477
+ const tokensBefore = exactCounts.reduce((sum, count) => sum + count.tokens, 0);
3478
+ const manifestSeq = events.length;
3479
+ const ref = tailTrimRef(String(session.id), manifestSeq);
3480
+ const stub = tailTrimStub(ref, calls.map((call) => call.name), sourceEventSeqs);
3481
+ if (stub === null) continue;
3482
+ const stubCount = countExactCanonicalTextFields([stub], (candidate) => view.countCanonicalText(candidate), "TailTrim group stub");
3483
+ if (stubCount.kind !== "exact-tokenizer" || stubCount.tokenizerId !== surfaceCount.tokenizerId || stubCount.tokenizerRevision !== surfaceCount.tokenizerRevision || stubCount.tokens <= 0 || tokensBefore - stubCount.tokens < policy.historyMinReclaimTokens) continue;
3484
+ const heuristicTokens = shadowedSeqs.reduce((sum, seq) => sum + (heuristic.get(seq) ?? 0), 0);
3485
+ const range = {
3486
+ start: assistantSeq,
3487
+ end: resultSeqs.at(-1) ?? assistantSeq
3488
+ };
3489
+ if (!this.reserveTailTrimBoundaryAttempt(session)) {
3490
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "already-attempted-at-request-boundary", {
3491
+ measurementKind: "exact-tokenizer",
3492
+ currentTokens: surfaceCount.tokens,
3493
+ triggerTokens: tailTrim.triggerTokens
3494
+ });
3495
+ return;
3496
+ }
3497
+ const manifest = session.append("compaction/prune", {
3498
+ shadowedRange: range,
3499
+ shadowedSeqs,
3500
+ shadowedTokenCount: heuristicTokens
3501
+ });
3502
+ let replacement;
3503
+ try {
3504
+ replacement = session.append("user/message", tailTrimMessage(stub), {
3505
+ surfaceOp: {
3506
+ op: "replace",
3507
+ ...range
3508
+ },
3509
+ sourceEventSeqs: [manifest.seq, ...shadowedSeqs]
3510
+ });
3511
+ } catch (error) {
3512
+ this.auditPublicationFailure(session, "pressure", "tail-trim", manifest.seq, error);
3513
+ return;
3514
+ }
3515
+ emitCompressionAudit(this.ctx.logger, {
3516
+ schemaVersion: 1,
3517
+ kind: "rewrite",
3518
+ sessionId: String(session.id),
3519
+ profile: policy.profile,
3520
+ component: "tail-trim",
3521
+ stage: "pressure",
3522
+ reducer: "pair-preserving-tail-trim",
3523
+ manifestEventType: "compaction/prune",
3524
+ manifestSeq: manifest.seq,
3525
+ replacementSeq: replacement.seq,
3526
+ sourceSeqs: sourceEventSeqs,
3527
+ tokensBefore,
3528
+ tokensAfter: stubCount.tokens,
3529
+ tokensRemoved: tokensBefore - stubCount.tokens,
3530
+ tokenizerId: stubCount.tokenizerId,
3531
+ tokenizerRevision: stubCount.tokenizerRevision
3532
+ });
3533
+ return;
3534
+ }
3535
+ this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "no-safe-eligible-tool-group", {
3536
+ measurementKind: "exact-tokenizer",
3537
+ currentTokens: surfaceCount.tokens,
3538
+ triggerTokens: tailTrim.triggerTokens
3539
+ });
3540
+ }
3541
+ reserveTailTrimBoundaryAttempt(session) {
3542
+ const boundary = this.state.activeRequestBoundaries.get(session);
3543
+ if (boundary === void 0) return true;
3544
+ if (this.state.tailTrimBoundaryAttempts.get(session) === boundary) return false;
3545
+ this.state.tailTrimBoundaryAttempts.set(session, boundary);
3546
+ return true;
3547
+ }
3548
+ uniqueAppendRoot(session, seq) {
3549
+ const events = sessionEvents(session);
3550
+ const pending = [{
3551
+ seq,
3552
+ depth: 0
3553
+ }];
3554
+ const visited = /* @__PURE__ */ new Set();
3555
+ const roots = /* @__PURE__ */ new Set();
3556
+ while (pending.length > 0) {
3557
+ const next = pending.pop();
3558
+ if (next === void 0 || next.depth > 64 || visited.has(next.seq)) continue;
3559
+ visited.add(next.seq);
3560
+ if (visited.size > 64) return null;
3561
+ const event = events[next.seq];
3562
+ if (event === void 0 || event.type !== "assistant/message" && event.type !== "tool/result") return null;
3563
+ if (event.surfaceOp === "append") roots.add(event.seq);
3564
+ else if (typeof event.surfaceOp === "object") {
3565
+ const sources = event.sourceEventSeqs;
3566
+ if (sources === void 0 || sources.length === 0) return null;
3567
+ for (const source of sources) pending.push({
3568
+ seq: source,
3569
+ depth: next.depth + 1
3570
+ });
3571
+ } else return null;
3572
+ if (roots.size > 1) return null;
3573
+ }
3574
+ return roots.size === 1 ? [...roots][0] ?? null : null;
3575
+ }
3576
+ plan(candidate, content, sourceSeq, reducer, stage, component, historyMode, view, options = {}) {
3577
+ const countBefore = candidate.count;
3578
+ if (countBefore.kind !== "exact-tokenizer") return null;
3579
+ const countAfter = countToolContent(content, view);
3580
+ if (countAfter.kind !== "exact-tokenizer" || countAfter.tokenizerId !== countBefore.tokenizerId || countAfter.tokenizerRevision !== countBefore.tokenizerRevision) return null;
3581
+ const tokensBefore = countBefore.tokens;
3582
+ const tokensAfter = countAfter.tokens;
3583
+ if (tokensAfter <= 0 || tokensAfter >= tokensBefore) return null;
3584
+ if (options.noNetSavingsGuard === true) {
3585
+ const originalBlocks = onlyTextBlocks(candidate.event.data.message.content[0].content);
3586
+ const replacementBlocks = onlyTextBlocks(content);
3587
+ if (originalBlocks !== null && replacementBlocks !== null) {
3588
+ const originalChars = originalBlocks.reduce((sum, block) => sum + codePointLength(block.text), 0);
3589
+ if (replacementBlocks.reduce((sum, block) => sum + codePointLength(block.text), 0) >= originalChars) return null;
3590
+ }
3591
+ }
3592
+ const charsBefore = candidate.characterPressure;
3593
+ const charsAfter = pressureCost(content);
3594
+ return {
3595
+ candidate,
3596
+ content,
3597
+ sourceSeq,
3598
+ reducer,
3599
+ stage,
3600
+ component,
3601
+ ...historyMode === void 0 ? {} : { historyMode },
3602
+ charsBefore,
3603
+ charsAfter,
3604
+ tokensBefore,
3605
+ tokensAfter,
3606
+ tokenizerId: countBefore.tokenizerId,
3607
+ tokenizerRevision: countBefore.tokenizerRevision
3608
+ };
3609
+ }
3610
+ land(session, plan) {
3611
+ const { candidate } = plan;
3612
+ const result = candidate.event.data.message.content[0];
3613
+ const message = freezeMessage({
3614
+ ...candidate.event.data.message,
3615
+ content: [{
3616
+ ...result,
3617
+ content: plan.content
3618
+ }]
3619
+ });
3620
+ const manifest = session.append("compaction/prune", {
3621
+ shadowedRange: {
3622
+ start: candidate.seq,
3623
+ end: candidate.seq
3624
+ },
3625
+ shadowedSeqs: [candidate.seq],
3626
+ shadowedTokenCount: candidate.shadowedHeuristicTokenCount
3627
+ });
3628
+ let replacement;
3629
+ try {
3630
+ replacement = session.append("tool/result", {
3631
+ ...candidate.event.data,
3632
+ message
3633
+ }, {
3634
+ surfaceOp: {
3635
+ op: "replace",
3636
+ start: candidate.seq,
3637
+ end: candidate.seq
3638
+ },
3639
+ sourceEventSeqs: [candidate.seq]
3640
+ });
3641
+ } catch (error) {
3642
+ this.auditPublicationFailure(session, plan.stage, plan.component, manifest.seq, error);
3643
+ return null;
3644
+ }
3645
+ emitCompressionAudit(this.ctx.logger, {
3646
+ schemaVersion: 1,
3647
+ kind: "rewrite",
3648
+ sessionId: String(session.id),
3649
+ profile: this.activeSettings(session).profile,
3650
+ component: plan.component,
3651
+ stage: plan.stage,
3652
+ reducer: plan.reducer,
3653
+ ...plan.historyMode === void 0 ? {} : { historyMode: plan.historyMode },
3654
+ manifestEventType: "compaction/prune",
3655
+ manifestSeq: manifest.seq,
3656
+ replacementSeq: replacement.seq,
3657
+ sourceSeqs: [plan.sourceSeq],
3658
+ tokensBefore: plan.tokensBefore,
3659
+ tokensAfter: plan.tokensAfter,
3660
+ tokensRemoved: plan.tokensBefore - plan.tokensAfter,
3661
+ tokenizerId: plan.tokenizerId,
3662
+ tokenizerRevision: plan.tokenizerRevision
3663
+ });
3664
+ return {
3665
+ originalSeq: candidate.seq,
3666
+ sourceSeq: plan.sourceSeq,
3667
+ replacementSeq: replacement.seq,
3668
+ callId: candidate.event.data.message.source.callId,
3669
+ reducer: plan.reducer,
3670
+ stage: plan.stage,
3671
+ charsBefore: plan.charsBefore,
3672
+ charsAfter: plan.charsAfter,
3673
+ tokensBefore: plan.tokensBefore,
3674
+ tokensAfter: plan.tokensAfter
3675
+ };
3676
+ }
3677
+ landAll(session, plans) {
3678
+ if (plans.length === 0) return [];
3679
+ if (!this.hasRecoveryTool(session)) {
3680
+ this.warnOnce(session, "missing-context-retrieve", "context-compression kept original tool results because context_compression_retrieve is unavailable");
3681
+ return [];
3682
+ }
3683
+ if (!hasOpenTurn(session)) throw new Error("tool-result pruning cannot append a surface replacement outside any open turn");
3684
+ const landed = [];
3685
+ for (const plan of plans) {
3686
+ const entry = this.land(session, plan);
3687
+ if (entry === null) break;
3688
+ landed.push(entry);
3689
+ }
3690
+ return landed;
3691
+ }
3692
+ hasRecoveryTool(session) {
3693
+ const tools = this.ctx.get("tools");
3694
+ if (tools === void 0) return false;
3695
+ const agent = this.ctx.get("agents")?.get(session.id);
3696
+ return tools.get("context_compression_retrieve", agent) !== void 0;
3697
+ }
3698
+ auditHistoryEvaluation(session, policy, view, allowed, outcome) {
3699
+ if (policy.historyMode === "disabled") {
3700
+ this.auditComponent(session, policy, "history", "pressure", "disabled", "profile-policy", { historyMode: policy.historyMode });
3701
+ return;
3702
+ }
3703
+ if (!allowed && outcome.kind === "planned") {
3704
+ const deadlineTrigger = policy.microDeadlineTokens;
3705
+ const capacity = deadlineTrigger === void 0 ? session.requestContext()?.contextWindow : void 0;
3706
+ const capacityTrigger = deadlineTrigger !== void 0 ? deadlineTrigger : Number.isSafeInteger(capacity) && capacity !== void 0 && capacity > 0 ? Math.floor(capacity * CAPACITY_PRESSURE_RATIO) : void 0;
3707
+ this.auditComponent(session, policy, "history", "pressure", "skipped", policy.historyMode === "capacity-pressure" ? "below-micro-deadline" : "adaptive-cost-rejected", {
3708
+ historyMode: policy.historyMode,
3709
+ measurementKind: view.currentSurface.kind,
3710
+ currentTokens: view.totalTokens,
3711
+ ...capacityTrigger === void 0 ? {} : { triggerTokens: capacityTrigger }
3712
+ });
3713
+ return;
3714
+ }
3715
+ const deadline = policy.microDeadlineTokens;
3716
+ const lastChance = deadline !== void 0 && view.totalTokens >= deadline;
3717
+ const detail = (extra = {}) => ({
3718
+ historyMode: policy.historyMode,
3719
+ measurementKind: outcome.kind === "exact-tokenizer-unavailable" ? "unavailable" : "exact-tokenizer",
3720
+ currentTokens: view.totalTokens,
3721
+ ...outcome.kind === "insufficient-reclaim" || outcome.kind === "cannot-reach-deadline-target" ? {
3722
+ reclaimTokens: outcome.reclaim,
3723
+ requiredTokens: outcome.required
3724
+ } : {},
3725
+ ...extra
3726
+ });
3727
+ switch (outcome.kind) {
3728
+ case "exact-tokenizer-unavailable":
3729
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "exact-tokenizer-unavailable", detail({ triggerTokens: policy.historyTriggerTokens }));
3730
+ return;
3731
+ case "below-profile-trigger":
3732
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "below-profile-trigger", detail({ triggerTokens: policy.historyTriggerTokens }));
3733
+ return;
3734
+ case "no-safe-candidates":
3735
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "no-safe-candidates", detail({ triggerTokens: policy.historyTriggerTokens }));
3736
+ return;
3737
+ case "protected-working-set":
3738
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "protected-working-set", detail({ triggerTokens: policy.historyTriggerTokens }));
3739
+ return;
3740
+ case "insufficient-reclaim":
3741
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "insufficient-reclaim", detail({ triggerTokens: policy.historyTriggerTokens }));
3742
+ return;
3743
+ case "cannot-reach-deadline-target":
3744
+ this.auditComponent(session, policy, "history", "pressure", "skipped", "cannot-reach-deadline-target", detail(lastChance ? { triggerTokens: deadline } : {}));
3745
+ return;
3746
+ case "planned":
3747
+ this.auditComponent(session, policy, "history", "pressure", "skipped", outcome.plans.length > 0 ? "recovery-tool-unavailable" : "insufficient-reclaim", detail({ triggerTokens: lastChance ? deadline : policy.historyTriggerTokens }));
3748
+ return;
3749
+ /* v8 ignore next -- closed-union exhaustiveness guard */
3750
+ default: return assertNever(outcome, "history plan outcome");
3751
+ }
3752
+ }
3753
+ auditComponent(session, policy, component, stage, status, reason, detail = {}) {
3754
+ emitCompressionAudit(this.ctx.logger, {
3755
+ schemaVersion: 1,
3756
+ kind: "component-evaluation",
3757
+ sessionId: String(session.id),
3758
+ profile: policy.profile,
3759
+ component,
3760
+ stage,
3761
+ status,
3762
+ reason,
3763
+ ...detail
3764
+ });
3765
+ }
3766
+ auditFailure(session, stage, operation, error) {
3767
+ emitCompressionAudit(this.ctx.logger, {
3768
+ schemaVersion: 1,
3769
+ kind: "failure",
3770
+ sessionId: String(session.id),
3771
+ stage,
3772
+ operation,
3773
+ errorName: error instanceof Error ? error.name : "UnknownError",
3774
+ errorMessage: error instanceof Error ? error.message : String(error)
3775
+ });
3776
+ }
3777
+ auditPublicationFailure(session, stage, component, manifestSeq, error) {
3778
+ emitCompressionAudit(this.ctx.logger, {
3779
+ schemaVersion: 1,
3780
+ kind: "failure",
3781
+ sessionId: String(session.id),
3782
+ stage,
3783
+ operation: "publication",
3784
+ component,
3785
+ manifestSeq,
3786
+ errorName: error instanceof Error ? error.name : "UnknownError",
3787
+ errorMessage: "surface replacement append failed after compaction/prune committed"
3788
+ });
3789
+ }
3790
+ warnExactUnavailable(session, view, gate) {
3791
+ const provider = view.providerRoute ?? "unbound-provider";
3792
+ const model = view.modelId ?? "unbound-model";
3793
+ this.warnOnce(session, `exact-tokenizer:${gate}:${provider}\0${model}`, "context-compression %s kept original tool results because exact tokenizer counts are unavailable for %s/%s", gate, provider, model);
3794
+ }
3795
+ warnOnce(session, key, message, ...args) {
3796
+ let warned = this.state.warnedFailures.get(session);
3797
+ if (warned === void 0) {
3798
+ warned = /* @__PURE__ */ new Set();
3799
+ this.state.warnedFailures.set(session, warned);
3800
+ }
3801
+ if (warned.has(key)) return;
3802
+ warned.add(key);
3803
+ this.ctx.logger.warn(message, ...args);
3804
+ }
3805
+ };
3806
+ //#endregion
3807
+ export { AUTO_COMPACT_THRESHOLD_LIMITS, COMPRESSION_PROFILES, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, ContextCompressionSettingsSchema, CustomCompressionPolicySchema, DEFAULTS, DEFAULT_CUSTOM_COMPRESSION_POLICY, PRUNE_MARKER, ToolResultPruner, ToolResultPruner as default, codePointLength, historicalPlaceholder, isCompressionProfile, isValidAutoCompactThresholdPercent, measureForCompaction, normalizeTerminalText, parseContextCompressionSettings, reduceFreshToolResult, resolveConfig, resolveCustomPolicy, resolvePolicy, verifyReduction };