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/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/index.d.ts
4
+ /** Standalone Bundle behavior; the settings/UI owner remains safe when false. */
5
+ interface Config {
6
+ /** Add the canonical compression stack to every non-Minimal preset. */
7
+ presetOverlay?: boolean;
8
+ /**
9
+ * Own the estimator catalog HTTP route. Set on the Loader row that declares
10
+ * `inject: [webServer]`.
11
+ *
12
+ * **Measured, and it contradicts the note this field was introduced with.**
13
+ * The original justification — "registering a route authorizes against the
14
+ * calling fiber, and a fiber that has not declared `webServer` cannot reach
15
+ * it, not even through `ctx.inject` or `ctx.get`" — is wrong on both halves:
16
+ * the host's `register` performs no authorization at all (it reads
17
+ * `this.exact` / `this.prefixes` and throws only on a duplicate
18
+ * `(kind, path)`), and `ctx.get(name, strict)` checks only that the providing
19
+ * fiber is active (`state === 2`), never the caller's `inject` list. The one
20
+ * inject-gated path is the `ctx.webServer` **property** access, which this
21
+ * plugin never uses: `registerEstimatorCatalogRoute` uses `ctx.get` plus its
22
+ * own `ctx.inject(['webServer'], …)`.
23
+ *
24
+ * So the row-level `inject` is **not load-bearing**; it is kept as
25
+ * belt-and-braces so the route row stays inactive until `webServer` exists,
26
+ * and the flag keeps the route off standalone Bundle rows on profiles that
27
+ * have no web server. The internal two-channel registration is what actually
28
+ * covers both arrival orders. Do not cite this comment as a rule to the
29
+ * 0.1.5 replay — cite the host source.
30
+ */
31
+ estimatorCatalogRoute?: boolean;
32
+ }
33
+ /** Loader validation for the standalone Bundle opt-in. */
34
+ declare const Config: z<Config>;
35
+ /** Register the persisted default read by the currently mounted root pruner. */
36
+ declare function apply(ctx: Context, config?: Config): void;
37
+ //#endregion
38
+ export { Config, apply };
package/lib/index.js ADDED
@@ -0,0 +1,645 @@
1
+ import { n as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, r as ContextCompressionSettingsSchema } from "./config.js";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import "@deepseek-ai/dsh-settings";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { createHash } from "node:crypto";
6
+ import { chmod, mkdtemp, readFile, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { isAbsolute, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { applyEntryPatches, entryListSchema } from "@deepseek-ai/cordis-plugin-include";
11
+ import { dump, load } from "js-yaml";
12
+ //#region src/estimator-catalog.ts
13
+ function str(value) {
14
+ return typeof value === "string" ? value : "";
15
+ }
16
+ /** Resolve the effective host model route (override → session default). */
17
+ function resolveHostRoute(deps) {
18
+ const overrideProvider = str(deps.overrideProvider);
19
+ const overrideModel = str(deps.overrideModel);
20
+ const selected = deps.currentSelection?.();
21
+ const selectedProvider = str(selected?.provider);
22
+ const selectedModel = str(selected?.model);
23
+ const provider = overrideProvider !== "" ? overrideProvider : selectedProvider;
24
+ const model = overrideModel !== "" ? overrideModel : selectedModel;
25
+ if (provider === "" || model === "") return void 0;
26
+ return {
27
+ provider,
28
+ model
29
+ };
30
+ }
31
+ /** Build the catalog projection. Never throws. */
32
+ async function buildEstimatorCatalog(deps) {
33
+ if (deps.llm?.listProviders === void 0) {
34
+ const selection = resolveHostRoute(deps);
35
+ return selection === void 0 ? { providers: [] } : {
36
+ providers: [],
37
+ selection
38
+ };
39
+ }
40
+ const selection = resolveHostRoute(deps);
41
+ const raw = deps.llm.listProviders();
42
+ return {
43
+ providers: await Promise.all(raw.map(async (p) => {
44
+ let models = [];
45
+ let error;
46
+ try {
47
+ models = (await deps.llm?.listModels?.(p.id) ?? []).map((m) => ({
48
+ id: m.id,
49
+ name: m.name
50
+ }));
51
+ } catch (e) {
52
+ error = String(e?.message ?? e);
53
+ }
54
+ if (models.length === 0) try {
55
+ const entry = deps.llm?.listConfigurableProviders?.().find((c) => c.provider === p.id);
56
+ if (entry !== void 0 && deps.llm?.discoverModels !== void 0) {
57
+ const discovered = await deps.llm.discoverModels(entry.settingsNs, { provider: p.id });
58
+ if (discovered.length > 0) {
59
+ models = discovered.map((m) => ({
60
+ id: m.id,
61
+ name: m.name ?? m.id
62
+ }));
63
+ error = void 0;
64
+ }
65
+ }
66
+ } catch {}
67
+ if (models.length === 0 && error === void 0) error = "no models advertised";
68
+ return {
69
+ id: p.id,
70
+ name: p.name,
71
+ models,
72
+ ...error === void 0 ? {} : { error }
73
+ };
74
+ })),
75
+ ...selection === void 0 ? {} : { selection }
76
+ };
77
+ }
78
+ //#endregion
79
+ //#region src/preset-overlay.ts
80
+ /** Plugin-owned, reversible compression overlays for native agent presets. */
81
+ /**
82
+ * Fixed base for deterministic standing mtimes. The stamp is derived from the
83
+ * FULL content identity (source + modules + threshold), not from the write
84
+ * clock: identical identities always republish to the same stamp (no
85
+ * generation flapping).
86
+ *
87
+ * The seconds component carries an 8-hex identity window directly. Most
88
+ * identities therefore separate even when a filesystem truncates mtimes to
89
+ * whole seconds; identities that share that 32-bit window deliberately collide
90
+ * first, are detected from the staging file's observed {mtimeMs, size}, and
91
+ * escalate to later hash windows before publication. The prefix keeps the
92
+ * latest possible stamp around year 2162, inside the nanosecond range every
93
+ * supported filesystem can store; the next 3 hex digits set sub-second
94
+ * milliseconds on filesystems that preserve them.
95
+ */
96
+ const STANDING_MTIME_EPOCH_SECONDS = Math.floor(Date.UTC(2026, 0, 1) / 1e3);
97
+ const STANDING_MTIME_WINDOW_HEX = 11;
98
+ const DEFAULT_METADATA_IO = Object.freeze({
99
+ async setTimes(path, stamp) {
100
+ await utimes(path, stamp, stamp);
101
+ },
102
+ async read(path) {
103
+ const observed = await stat(path);
104
+ return {
105
+ mtimeMs: observed.mtimeMs,
106
+ size: observed.size
107
+ };
108
+ }
109
+ });
110
+ /**
111
+ * Deterministic standing stamp for one full generation identity, read from
112
+ * hash window `windowIndex` (0 = identity prefix). Exported for the
113
+ * collision-fixture tests; production code uses {@link standingStampMs}.
114
+ */
115
+ function standingStampMsAtWindow(identity, windowIndex) {
116
+ const start = windowIndex * STANDING_MTIME_WINDOW_HEX;
117
+ const seconds = STANDING_MTIME_EPOCH_SECONDS + parseInt(identity.slice(start, start + 8).padEnd(8, "0"), 16);
118
+ const subSecond = parseInt(identity.slice(start + 8, start + STANDING_MTIME_WINDOW_HEX).padEnd(3, "0"), 16) % 1e3;
119
+ return seconds * 1e3 + subSecond;
120
+ }
121
+ const COMPRESSION_IDS = /* @__PURE__ */ new Set([
122
+ "compaction",
123
+ "compaction-basic",
124
+ "command-compact",
125
+ "tool-result-pruner"
126
+ ]);
127
+ const COMPRESSION_PACKAGES = /* @__PURE__ */ new Set([
128
+ "@deepseek-ai/dsh-compaction-basic",
129
+ "@deepseek-ai/dsh-command-compact",
130
+ "@deepseek-ai/dsh-compaction-tool-result-pruner",
131
+ "dsh-context-compression-improved-runtime",
132
+ "dsh-context-compression-improved",
133
+ "dsh-context-compression-improved/pruner"
134
+ ]);
135
+ /**
136
+ * Resolve the three compression package entries once from this package.
137
+ * @returns Absolute entry paths for the canonical compression layer.
138
+ */
139
+ function resolveCompressionModulePaths() {
140
+ return {
141
+ compactionBasic: modulePath("@deepseek-ai/dsh-compaction-basic", import.meta.resolve("@deepseek-ai/dsh-compaction-basic")),
142
+ commandCompact: modulePath("@deepseek-ai/dsh-command-compact", import.meta.resolve("@deepseek-ai/dsh-command-compact")),
143
+ toolResultPruner: modulePath("dsh-context-compression-improved/pruner", import.meta.resolve("dsh-context-compression-improved/pruner"))
144
+ };
145
+ }
146
+ /** Convert one package resolution into the absolute path preset mounting accepts. */
147
+ function modulePath(specifier, resolved) {
148
+ if (!resolved.startsWith("file:")) throw new Error(`context-compression selector: ${specifier} resolved outside the filesystem (${resolved})`);
149
+ return fileURLToPath(resolved);
150
+ }
151
+ /** Generated composition storage owned by one decorator installation. */
152
+ var PresetOverlayStore = class {
153
+ options;
154
+ rootTask;
155
+ disposed = false;
156
+ metadataIo;
157
+ constructor(options) {
158
+ this.options = options;
159
+ this.metadataIo = options.metadataIo ?? DEFAULT_METADATA_IO;
160
+ const paths = Object.entries(options.modules);
161
+ for (const [name, path] of paths) if (!isAbsolute(path)) throw new TypeError(`context-compression selector: module path ${name} is not absolute: ${path}`);
162
+ }
163
+ /** Return a detached preset record whose path names the canonical overlay. */
164
+ async overlay(preset) {
165
+ if (this.disposed) throw new Error("context-compression selector: preset overlay is disposed");
166
+ if (preset.broken !== void 0) return preset;
167
+ const source = await readFile(preset.path, "utf8");
168
+ const rows = parseRows(source, preset.path);
169
+ const thresholdPercent = this.options.autoCompactThresholdPercent?.();
170
+ if (thresholdPercent !== void 0 && !Number.isFinite(thresholdPercent)) throw new Error(`context-compression selector: Auto Compact threshold percent must be finite, got ${String(thresholdPercent)}`);
171
+ const patched = applyEntryPatches(stripCompressionRows(rows), [{ insert: canonicalCompressionRows(this.options.modules, thresholdPercent) }], (message, ...args) => {
172
+ throw new Error(renderPatchWarning(message, args));
173
+ });
174
+ const rendered = dump(patched, {
175
+ schema: entryListSchema,
176
+ noRefs: true,
177
+ lineWidth: -1,
178
+ sortKeys: false
179
+ });
180
+ const identity = createHash("sha256").update(preset.id).update("\0").update(source).update("\0").update(JSON.stringify({
181
+ modules: this.options.modules,
182
+ autoCompactThresholdPercent: thresholdPercent ?? null
183
+ })).digest("hex").slice(0, 24);
184
+ const root = await this.root();
185
+ const path = join(root, `${preset.id}-${identity}.agent.cordis.yml`);
186
+ const staging = join(root, `${preset.id}-${identity}.${String(process.pid)}-${String(Math.random()).slice(2)}.tmp`);
187
+ try {
188
+ await writeFile(staging, rendered, {
189
+ encoding: "utf8",
190
+ mode: 384
191
+ });
192
+ await chmod(staging, 384);
193
+ await this.disambiguateStamp(staging, identity);
194
+ await rename(staging, path);
195
+ } catch (error) {
196
+ try {
197
+ await rm(staging, { force: true });
198
+ } catch {}
199
+ throw error;
200
+ }
201
+ return {
202
+ ...preset,
203
+ path
204
+ };
205
+ }
206
+ /** Observed {mtimeMs,size} keys published by this store, per identity. */
207
+ standingKeys = /* @__PURE__ */ new Map();
208
+ /**
209
+ * Ensure an unpublished staging file's observed {mtimeMs, size} is not
210
+ * shared with a different identity. Escalation rewrites the mtime from later
211
+ * hash windows before atomic publication, and fails loudly if no window
212
+ * separates them (better a loud error than a silently reused generation).
213
+ */
214
+ async disambiguateStamp(path, identity) {
215
+ for (let window = 0; window < 3; window += 1) {
216
+ const stamp = new Date(standingStampMsAtWindow(identity, window));
217
+ await this.metadataIo.setTimes(path, stamp);
218
+ const observed = await this.metadataIo.read(path);
219
+ const key = `${String(observed.mtimeMs)}:${String(observed.size)}`;
220
+ const owner = this.standingKeys.get(key);
221
+ if (owner === void 0 || owner === identity) {
222
+ this.standingKeys.set(key, identity);
223
+ return;
224
+ }
225
+ }
226
+ throw new Error(`context-compression selector: standing stamp collision for identity ${identity} across all hash windows`);
227
+ }
228
+ /** Remove all generated files without touching any source preset. */
229
+ async dispose() {
230
+ if (this.disposed) return;
231
+ this.disposed = true;
232
+ if (this.rootTask === void 0) return;
233
+ const root = await this.rootTask;
234
+ await rm(root, {
235
+ recursive: true,
236
+ force: true
237
+ });
238
+ }
239
+ /** Lazily create the one owner-only directory for this installation. */
240
+ async root() {
241
+ if (this.rootTask === void 0) {
242
+ const parent = this.options.tempParent ?? tmpdir();
243
+ this.rootTask = mkdtemp(join(parent, "dsh-context-compression-presets-")).then(async (root) => {
244
+ await chmod(root, 448);
245
+ return root;
246
+ });
247
+ }
248
+ return await this.rootTask;
249
+ }
250
+ };
251
+ /** Parse one native preset with exactly the Loader's YAML dialect. */
252
+ function parseRows(source, path) {
253
+ const parsed = load(source, { schema: entryListSchema });
254
+ if (!Array.isArray(parsed)) throw new TypeError(`context-compression selector: preset ${path} is not a top-level entry list`);
255
+ return parsed;
256
+ }
257
+ /** Remove any prior compression implementation before adding the canonical one. */
258
+ function stripCompressionRows(rows) {
259
+ const kept = [];
260
+ for (const row of rows) {
261
+ if (COMPRESSION_IDS.has(row.id) || COMPRESSION_PACKAGES.has(row.name)) continue;
262
+ if (row.group === true && Array.isArray(row.config)) {
263
+ const nested = row.config;
264
+ kept.push({
265
+ ...row,
266
+ config: stripCompressionRows(nested)
267
+ });
268
+ } else kept.push(row);
269
+ }
270
+ return kept;
271
+ }
272
+ /**
273
+ * Complete, same-realm compression stack added to every applicable preset.
274
+ * When the Host settings expose an Auto Compact threshold, one read feeds both
275
+ * the compaction-basic `thresholdRatio` (beside the pinned first-release
276
+ * `retainRatio`) and the runtime deployment config, so plugin History and
277
+ * native Auto Compact share one watermark for this whole generation.
278
+ */
279
+ function canonicalCompressionRows(modules, thresholdPercent) {
280
+ return [{
281
+ id: "compaction",
282
+ name: "cordis:group",
283
+ group: true,
284
+ isolate: {
285
+ compaction: true,
286
+ toolResultPruner: true
287
+ },
288
+ config: [
289
+ {
290
+ id: "compaction-basic",
291
+ name: modules.compactionBasic,
292
+ ...thresholdPercent === void 0 ? {} : { config: {
293
+ thresholdRatio: thresholdPercent / 100,
294
+ retainRatio: .16
295
+ } }
296
+ },
297
+ {
298
+ id: "command-compact",
299
+ name: modules.commandCompact
300
+ },
301
+ {
302
+ id: "tool-result-pruner",
303
+ name: modules.toolResultPruner,
304
+ config: {
305
+ headChars: 4096,
306
+ tailChars: 1024,
307
+ ...thresholdPercent === void 0 ? {} : { autoCompactThresholdPercent: thresholdPercent }
308
+ }
309
+ }
310
+ ]
311
+ }];
312
+ }
313
+ /** Render include's printf-style warning without silently losing its target. */
314
+ function renderPatchWarning(message, args) {
315
+ let index = 0;
316
+ return `context-compression selector: ${message.replace(/%C/g, () => JSON.stringify(args[index++]))}`;
317
+ }
318
+ /**
319
+ * Cordis can hand two callers different traceable proxies for one service.
320
+ * Symbol properties forward to the shared target, unlike proxy identity.
321
+ */
322
+ const SHARED_DECORATION = Symbol.for("dsh-context-compression-improved/preset-overlay");
323
+ /** Object-identity keys keep test metadata policies from sharing one store. */
324
+ const METADATA_IO_KEYS = /* @__PURE__ */ new WeakMap();
325
+ let nextMetadataIoKey = 1;
326
+ function metadataIoKey(metadataIo) {
327
+ const existing = METADATA_IO_KEYS.get(metadataIo);
328
+ if (existing !== void 0) return existing;
329
+ const key = nextMetadataIoKey;
330
+ nextMetadataIoKey += 1;
331
+ METADATA_IO_KEYS.set(metadataIo, key);
332
+ return key;
333
+ }
334
+ /**
335
+ * Reversibly decorate native AgentPresets composition calls.
336
+ *
337
+ * Duplicate Host rows share one physical decoration. This matters while an
338
+ * installation migrates from a Harness-bundled selector row to the standalone
339
+ * Bundle: either row can unload first without double-compressing or disposing
340
+ * the generated files still used by the other.
341
+ * @param presets Native AgentPresets service to decorate during composition.
342
+ * @param options Canonical module paths, exclusions, and optional test directory.
343
+ * @returns A reference-counted handle that restores the native service on final disposal.
344
+ */
345
+ function decorateAgentPresets(presets, options) {
346
+ const optionsKey = overlayOptionsKey(options);
347
+ const carrier = presets;
348
+ let shared = carrier[SHARED_DECORATION];
349
+ if (shared === void 0) {
350
+ shared = {
351
+ optionsKey,
352
+ references: 0,
353
+ installation: installAgentPresetsDecoration(presets, options)
354
+ };
355
+ Object.defineProperty(carrier, SHARED_DECORATION, {
356
+ configurable: true,
357
+ enumerable: false,
358
+ writable: false,
359
+ value: shared
360
+ });
361
+ } else if (shared.optionsKey !== optionsKey) throw new Error("context-compression selector: AgentPresets already has a different compression overlay");
362
+ const lease = shared;
363
+ lease.references += 1;
364
+ let disposed = false;
365
+ return { async dispose() {
366
+ if (disposed) return;
367
+ disposed = true;
368
+ lease.references -= 1;
369
+ if (lease.references !== 0) return;
370
+ if (carrier[SHARED_DECORATION] === lease) Reflect.deleteProperty(carrier, SHARED_DECORATION);
371
+ await lease.installation.dispose();
372
+ } };
373
+ }
374
+ /** Stable equality for two rows asking to share one physical overlay. */
375
+ function overlayOptionsKey(options) {
376
+ return JSON.stringify({
377
+ modules: options.modules,
378
+ excludedPresetIds: [...options.excludedPresetIds ?? ["minimal"]].sort(),
379
+ tempParent: options.tempParent,
380
+ metadataIo: metadataIoKey(options.metadataIo ?? DEFAULT_METADATA_IO)
381
+ });
382
+ }
383
+ /**
384
+ * Install the one physical method decoration leased by public callers.
385
+ *
386
+ * Direct resolution and authoring stay source-preserving. AsyncLocalStorage
387
+ * scopes the overlay to the async call tree of mount/recompose/standingKeyFor,
388
+ * so an unrelated resolve racing the mount cannot inherit its generated path.
389
+ */
390
+ function installAgentPresetsDecoration(presets, options) {
391
+ const excluded = new Set(options.excludedPresetIds ?? ["minimal"]);
392
+ const operations = new AsyncLocalStorage();
393
+ const store = new PresetOverlayStore(options);
394
+ const snapshots = snapshotMethods(presets);
395
+ const resolveSnapshot = snapshotFor(snapshots, "resolve");
396
+ const resolveWrapped = async (id) => {
397
+ const preset = await Reflect.apply(resolveSnapshot.original, presets, [id]);
398
+ if (operations.getStore()?.composing !== true || excluded.has(preset.id)) return preset;
399
+ return await store.overlay(preset);
400
+ };
401
+ installMethod(presets, resolveSnapshot, resolveWrapped);
402
+ for (const method of [
403
+ "mount",
404
+ "recompose",
405
+ "standingKeyFor"
406
+ ]) {
407
+ const snapshot = snapshotFor(snapshots, method);
408
+ const wrapped = (...args) => operations.run({ composing: true }, () => Reflect.apply(snapshot.original, presets, args));
409
+ installMethod(presets, snapshot, wrapped);
410
+ }
411
+ let disposed = false;
412
+ return { async dispose() {
413
+ if (disposed) return;
414
+ disposed = true;
415
+ for (const snapshot of [...snapshots].reverse()) restoreMethod(presets, snapshot);
416
+ await store.dispose();
417
+ } };
418
+ }
419
+ /** Capture callable methods and whether each was inherited or owned. */
420
+ function snapshotMethods(presets) {
421
+ return [
422
+ "resolve",
423
+ "mount",
424
+ "recompose",
425
+ "standingKeyFor"
426
+ ].map((name) => {
427
+ const original = presets[name];
428
+ if (typeof original !== "function") throw new TypeError(`context-compression selector: AgentPresets.${name} is unavailable`);
429
+ return {
430
+ name,
431
+ own: Object.getOwnPropertyDescriptor(presets, name),
432
+ original
433
+ };
434
+ });
435
+ }
436
+ /** Return the captured method or fail loudly if the snapshot set is corrupt. */
437
+ function snapshotFor(snapshots, name) {
438
+ const snapshot = snapshots.find((candidate) => candidate.name === name);
439
+ if (snapshot === void 0) throw new Error(`context-compression selector: missing method snapshot for ${name}`);
440
+ return snapshot;
441
+ }
442
+ /** Install one own method while retaining its identity for safe disposal. */
443
+ function installMethod(presets, snapshot, wrapped) {
444
+ snapshot.wrapped = wrapped;
445
+ Object.defineProperty(presets, snapshot.name, {
446
+ configurable: true,
447
+ writable: true,
448
+ value: wrapped
449
+ });
450
+ }
451
+ /** Restore the captured own/prototype state after the final shared lease. */
452
+ function restoreMethod(presets, snapshot) {
453
+ if (snapshot.own === void 0) Reflect.deleteProperty(presets, snapshot.name);
454
+ else Object.defineProperty(presets, snapshot.name, snapshot.own);
455
+ }
456
+ //#endregion
457
+ //#region src/index.ts
458
+ const ESTIMATOR_CATALOG_ROUTES = ["/endpoint/dsh-context-compression-improved/estimator-catalog", "/api/dsh-context-compression-improved/estimator-catalog"];
459
+ /**
460
+ * The one service the catalog route actually needs. `llm` and
461
+ * `agentDefaultModel` are payload enrichment the handler resolves per request,
462
+ * never reasons to withhold the route.
463
+ */
464
+ const ESTIMATOR_CATALOG_ROUTE_DEPS = ["webServer"];
465
+ function asWebServer(value) {
466
+ if (typeof value?.register !== "function") return void 0;
467
+ return value;
468
+ }
469
+ /**
470
+ * Serve `GET /api/dsh-context-compression-improved/estimator-catalog` — the
471
+ * settings card's host-route dropdowns (live provider/model groups from the DSH
472
+ * `llm` service plus the effective selection). This lives on the top-level
473
+ * plugin context, NOT inside the isolated toolResultPruner service.
474
+ *
475
+ * (The isolation reason this placement was originally justified with — "a route
476
+ * registered there can never reach `webServer` across the isolation boundary" —
477
+ * is **unverified**: no `@deepseek-ai` package calls `.isolate(`, so there is no
478
+ * boundary to cross here. Top-level placement is still the right choice, for a
479
+ * reason that needs no framework rule: the route is host-wide, not
480
+ * per-pruner-instance. Don't promote the isolation wording into a rule.)
481
+ *
482
+ * The route gates on `webServer` **alone**. `llm` and `agentDefaultModel` only
483
+ * enrich the response and are resolved per request, so listing them here would
484
+ * let an estimator-side service the handler never needs keep the route
485
+ * unregistered. That failure is silent by construction — an unsatisfied
486
+ * `ctx.inject` callback never runs, so the plugin simply has no HTTP API and
487
+ * every request falls through to the host 404.
488
+ *
489
+ * Two channels cover the two arrival orders: a direct lookup catches a
490
+ * `webServer` that is already active when the plugin loads, and `ctx.inject`
491
+ * catches one that activates later. Both funnel into a single guarded
492
+ * registration, because a late-arriving service must not re-register a
493
+ * `(kind, path)` the host treats as a composition-contract violation.
494
+ */
495
+ function registerEstimatorCatalogRoute(ctx) {
496
+ const readService = (name) => {
497
+ try {
498
+ return ctx.get(name);
499
+ } catch {
500
+ return;
501
+ }
502
+ };
503
+ const log = (level, message, ...args) => {
504
+ console[level](message, ...args);
505
+ };
506
+ let registered = false;
507
+ const register = (webServer, channel) => {
508
+ if (registered) return;
509
+ const handler = (_req, res) => {
510
+ const resTyped = res;
511
+ if (typeof resTyped?.writeHead !== "function" || typeof resTyped?.end !== "function") return;
512
+ const llm = readService("llm");
513
+ const defaults = readService("agentDefaultModel");
514
+ buildEstimatorCatalog({
515
+ ...llm === void 0 ? {} : { llm },
516
+ ...typeof defaults?.currentSelection === "function" ? { currentSelection: () => defaults.currentSelection?.() } : {}
517
+ }).then((catalog) => {
518
+ resTyped.writeHead(200, {
519
+ "content-type": "application/json; charset=utf-8",
520
+ "cache-control": "no-cache"
521
+ });
522
+ resTyped.end(JSON.stringify({
523
+ ok: true,
524
+ ...catalog
525
+ }));
526
+ }, (error) => {
527
+ resTyped.writeHead(500, { "content-type": "application/json; charset=utf-8" });
528
+ resTyped.end(JSON.stringify({
529
+ ok: false,
530
+ error: String(error?.message ?? error)
531
+ }));
532
+ });
533
+ };
534
+ try {
535
+ const disposers = ESTIMATOR_CATALOG_ROUTES.map((path) => webServer.register({
536
+ kind: "exact",
537
+ path,
538
+ handler
539
+ })).filter((off) => typeof off === "function");
540
+ registered = true;
541
+ ctx.effect(() => () => {
542
+ for (const off of disposers) off();
543
+ }, "contextCompressionSelector.estimator-catalog route");
544
+ log("info", "context-compression estimator catalog route registered (%s): %s", channel, ESTIMATOR_CATALOG_ROUTES.join(", "));
545
+ } catch (error) {
546
+ log("warn", "context-compression estimator catalog route registration failed (%s): %o", channel, error);
547
+ }
548
+ };
549
+ const active = asWebServer(readService("webServer"));
550
+ if (active !== void 0) {
551
+ register(active, "direct");
552
+ if (registered) return;
553
+ }
554
+ ctx.inject([...ESTIMATOR_CATALOG_ROUTE_DEPS], (injected) => {
555
+ const webServer = asWebServer(injected.webServer);
556
+ if (webServer === void 0) {
557
+ log("warn", "context-compression webServer exposes no register() — estimator catalog route not registered");
558
+ return;
559
+ }
560
+ register(webServer, "inject");
561
+ });
562
+ log("warn", "context-compression webServer not active yet — estimator catalog route pending: %s", ESTIMATOR_CATALOG_ROUTES.join(", "));
563
+ }
564
+ const CONTEXT_COMPRESSION_NAMESPACE = CONTEXT_COMPRESSION_SETTINGS_NAMESPACE;
565
+ /** Symbol properties reach the shared service target through Cordis proxies. */
566
+ const SHARED_SETTINGS = Symbol.for("dsh-context-compression-improved/settings-registration");
567
+ /** Loader validation for the standalone Bundle opt-in. */
568
+ const Config = z.object({
569
+ presetOverlay: z.boolean().default(false),
570
+ estimatorCatalogRoute: z.boolean().default(false)
571
+ });
572
+ /** Register the persisted default read by the currently mounted root pruner. */
573
+ function apply(ctx, config = {}) {
574
+ try {
575
+ ctx.inject(["settings"], (settingsCtx) => {
576
+ acquireSettingsRegistration(settingsCtx);
577
+ });
578
+ if (config.estimatorCatalogRoute === true) registerEstimatorCatalogRoute(ctx);
579
+ if (config.presetOverlay !== true) return;
580
+ ctx.inject(["agentPresets"], (presetsCtx) => {
581
+ const installation = decorateAgentPresets(presetsCtx.agentPresets, {
582
+ modules: resolveCompressionModulePaths(),
583
+ excludedPresetIds: ["minimal"],
584
+ autoCompactThresholdPercent: () => resolveAutoCompactThresholdPercent(presetsCtx)
585
+ });
586
+ presetsCtx.effect(() => () => installation.dispose(), "contextCompressionSelector.agentPresets()");
587
+ });
588
+ } catch (error) {
589
+ console.error("context-compression apply() failed:", error);
590
+ throw error;
591
+ }
592
+ }
593
+ /**
594
+ * Read the current Auto Compact threshold ratio at composition time. Settings
595
+ * values are revalidated here, and any unreadable value falls back to the 80%
596
+ * default rather than blocking preset composition.
597
+ */
598
+ function resolveAutoCompactThresholdPercent(presetsCtx) {
599
+ const raw = presetsCtx.get("settings")?.get(CONTEXT_COMPRESSION_NAMESPACE);
600
+ try {
601
+ return ContextCompressionSettingsSchema(structuredClone(raw)).autoCompact.thresholdPercent;
602
+ } catch {
603
+ return 80;
604
+ }
605
+ }
606
+ /**
607
+ * Lease one native settings registration across duplicate Host rows.
608
+ *
609
+ * The lease effect is intentionally registered before settings.register().
610
+ * Cordis disposes effects in reverse order, so the native registration first
611
+ * releases the namespace; this disposer can then transfer it to another live
612
+ * owner without a duplicate-registration window.
613
+ */
614
+ function acquireSettingsRegistration(ctx) {
615
+ const settings = ctx.settings;
616
+ const owner = { settings };
617
+ let shared = settings[SHARED_SETTINGS];
618
+ if (shared === void 0) {
619
+ shared = {
620
+ owners: /* @__PURE__ */ new Set(),
621
+ registrationOwner: owner,
622
+ scope: void 0
623
+ };
624
+ Object.defineProperty(settings, SHARED_SETTINGS, {
625
+ configurable: true,
626
+ enumerable: false,
627
+ writable: false,
628
+ value: shared
629
+ });
630
+ }
631
+ shared.owners.add(owner);
632
+ const state = shared;
633
+ ctx.effect(() => () => {
634
+ state.owners.delete(owner);
635
+ if (state.registrationOwner === owner && state.owners.size > 0) {
636
+ const next = state.owners.values().next().value;
637
+ state.registrationOwner = next;
638
+ state.scope = next.settings.register(CONTEXT_COMPRESSION_NAMESPACE, ContextCompressionSettingsSchema);
639
+ }
640
+ if (state.owners.size === 0 && settings[SHARED_SETTINGS] === state) Reflect.deleteProperty(settings, SHARED_SETTINGS);
641
+ }, "contextCompressionSelector.settingsLease()");
642
+ if (state.owners.size === 1) state.scope = settings.register(CONTEXT_COMPRESSION_NAMESPACE, ContextCompressionSettingsSchema);
643
+ }
644
+ //#endregion
645
+ export { Config, apply };
@@ -0,0 +1,10 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+ //#region src/invariant.d.ts
3
+ /** Cordis companion plugin name. */
4
+ declare const name = "context-compression-selector-runtime-invariant";
5
+ /** Services required before the companion can register. */
6
+ declare const inject: string[];
7
+ /** Register this package's invariant companion. */
8
+ declare const apply: (ctx: Context) => Promise<() => void>;
9
+ //#endregion
10
+ export { apply, inject, name };