@sagmans/dsh-auto-compact 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sercan Sagman - ahmetsercansagman@gmail.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # dsh-auto-compact
2
+
3
+ A DeepSeek Harness compaction backend that adds an absolute token budget to the
4
+ shipped window-ratio trigger. It condenses at a fixed price on large-window
5
+ models, while every model whose window is already smaller than that budget keeps
6
+ its proportional trigger.
7
+
8
+ ```
9
+ trigger = min(thresholdTokens, floor(contextWindow x thresholdRatio))
10
+ ```
11
+
12
+ Releases publish to npm as [`@sagmans/dsh-auto-compact`](https://www.npmjs.com/package/@sagmans/dsh-auto-compact); every release carries a provenance attestation built by the tag workflow, and no npm token is stored. The code is [MIT licensed](LICENSE).
13
+
14
+ ## Requirements
15
+
16
+ - Node.js 24 LTS (verified with 24.20.0).
17
+ - pnpm 11.21.0 for this repository.
18
+ - A DeepSeek Harness install on the supported line: `>=0.1.5-rc.1 <0.1.6` (verified against `0.1.5-rc.2`). The plugin declares that range as a peer dependency, so a profile resolves the harness copy it already has rather than a second framework instance.
19
+
20
+ ## Configuration
21
+
22
+ Every shipped setting keeps its meaning; this backend adds two fields and one
23
+ per-route override.
24
+
25
+ | Field | Default | Meaning |
26
+ |---|---|---|
27
+ | `thresholdTokens` | — | Absolute trigger budget in tokens. Omitted means the ratio trigger alone. |
28
+ | `retainShare` | `0.2` | Retained tail as a fraction of the derived trigger, applied only when the budget is what triggers condensation. |
29
+ | `modelPolicies[].thresholdTokens` | — | Budget for one exact `provider`/`model` route. |
30
+ | `thresholdRatio`, `retainRatio`, `retainTokens`, `summarizationProvider`, `summarizationModel`, `maxTokens`, `compactionRetries`, `maxOverflowRetries`, `modelPolicies`, `auto` | shipped | Unchanged; see the shipped backend documentation. |
31
+
32
+ ```yaml
33
+ - id: auto-compact
34
+ name: '@sagmans/dsh-auto-compact'
35
+ config:
36
+ thresholdTokens: 200000
37
+ modelPolicies:
38
+ - provider: opencode-go-session
39
+ model: deepseek-flash
40
+ thresholdTokens: 120000
41
+ ```
42
+
43
+ ## Trigger examples
44
+
45
+ | Context window | `thresholdRatio` | `thresholdTokens` | Trigger |
46
+ |---|---|---|---|
47
+ | 1,000,000 | 0.8 | 200,000 | **200,000** (budget) |
48
+ | 262,144 | 0.8 | 200,000 | **200,000** (budget) |
49
+ | 131,072 | 0.8 | 200,000 | 104,857 (ratio) |
50
+ | 1,000,000 | 0.8 | — | 800,000 (ratio only) |
51
+
52
+ ## Install
53
+
54
+ The bundle patch disables the shipped `compaction-basic` row and inserts this
55
+ backend in its place, so the profile keeps exactly one compaction service.
56
+
57
+ ```sh
58
+ dsh plugin --profile tui add link:/path/to/dsh-auto-compact
59
+ dsh --profile tui --dump-config | grep -A3 'id: auto-compact'
60
+ ```
61
+
62
+ Profile-specific tuning belongs in `$DSH_HOME/profiles/<name>/cordis.patch.yml`,
63
+ which the profile watches:
64
+
65
+ ```yaml
66
+ - id: auto-compact
67
+ config:
68
+ thresholdTokens: 200000
69
+ ```
70
+
71
+ Restart the profile after a bundle change. Tune the patch file afterwards
72
+ without a restart.
73
+
74
+ ## Where the engine runs
75
+
76
+ The TUI and Web bundles disable the host-plane compaction rows so that each
77
+ agent preset owns an engine in its own realm. This bundle provides the service
78
+ on the host plane, which serves every agent because an unscoped listener
79
+ receives all agent events. A preset that still mounts the shipped backend keeps
80
+ it as a later listener: that engine can only condense after this one already
81
+ did, or at its own wider ratio trigger, so it stays inert.
82
+
83
+ Two consequences are worth knowing:
84
+
85
+ - The host plane mounts no tool-result pruner in those profiles, so
86
+ condensation here summarizes without the model-free prune pass. Insert a
87
+ `tool-result-pruner` row at the host plane if you want that pass.
88
+ - A deployment that wants exactly one engine can instead copy the agent preset
89
+ it uses, point that copy's `compaction-basic` row at this package, and select
90
+ the copy as the default preset. The copy is a snapshot and drifts from the
91
+ shipped preset.
92
+
93
+ ## How it works
94
+
95
+ The absolute budget cannot be expressed as a shipped setting, so the engine
96
+ subclasses the shipped backend and overrides `compactIfNeeded` for the pressure
97
+ trigger only:
98
+
99
+ 1. Read the durable route and the budget for it. Below the budget, delegate
100
+ unchanged and make no capacity lookup.
101
+ 2. Read the routed model's context window from the adapter.
102
+ 3. Derive the trigger and the retained tail, then run the shipped transaction
103
+ under that policy.
104
+
105
+ The shipped engine resolves its policy from a frozen instance field, so the
106
+ derived policy is placed on a per-call receiver that shares the engine's
107
+ context and state. A field swap would leak between agents condensing
108
+ concurrently on different routes.
109
+
110
+ Provider-confirmed context overflow, `/compact`, and `auto: false` keep their
111
+ shipped behaviour. Overflow recovery never consults a threshold.
112
+
113
+ ## Verification
114
+
115
+ Unit tests cover the decision surface, including the boundary where the budget
116
+ stops binding and the point where the derived trigger must carry the decision
117
+ past the shipped ratio check.
118
+
119
+ A controlled pair of real sessions proves the trigger end to end. Both runs used
120
+ the same task, the same route, and a reported window of 1,000,000 tokens, where
121
+ the shipped ratio trigger is 800,000:
122
+
123
+ | Run | `thresholdTokens` | Compaction events |
124
+ |---|---|---|
125
+ | A | 8,000 | 4 starts, 3 summaries, 1 prune |
126
+ | B | — | none |
127
+
128
+ Reproduce it with a scratch profile:
129
+
130
+ ```sh
131
+ dsh --profile autocompact-dogfood --from-default-profile headless --dump-config
132
+ dsh plugin --profile autocompact-dogfood add link:/path/to/dsh-auto-compact
133
+ # patch the profile with the route rows and a small thresholdTokens, then:
134
+ cd /tmp/dsh-auto-compact-dogfood
135
+ dsh --profile autocompact-dogfood "cat big-a.md big-b.md, then report the character count"
136
+ zstd -dc ~/.dsh/sessions/--private-tmp-dsh-auto-compact-dogfood--/*/session.v3.jsonl.zstd \
137
+ | grep -oE '"type":"compaction/[a-z]+"' | sort | uniq -c
138
+ ```
139
+
140
+ Remove `thresholdTokens` and the same run must report no compaction events.
141
+
142
+ ## Limits
143
+
144
+ - The adapter must report a context window. A route without one keeps the
145
+ shipped warn-once failure and no capacity is guessed.
146
+ - Token pressure comes from the shipped meter (four characters per token, with
147
+ provider usage when available), so a budget is as accurate as that meter.
148
+ - Per-route overrides match exactly; there is no wildcard matcher.
149
+ - A budget below the fixed request envelope (system prompt plus tool schemas)
150
+ cannot be satisfied. The shipped retry policy then warns and continues the
151
+ turn.
152
+
153
+ ## Development
154
+
155
+ ```sh
156
+ pnpm install
157
+ pnpm run check # typecheck, unit tests, build, built-artifact tests, release guards, pack smoke
158
+ ```
159
+
160
+ The profile loads `dist/index.js`, so rebuild and restart the profile after a
161
+ source change.
162
+
163
+ ## Releasing
164
+
165
+ Published artefacts carry a provenance attestation, which only a CI provider can issue, so releases ship from the tag workflow rather than a laptop.
166
+
167
+ 1. Bump `version` in `package.json`, land it on `main` through a reviewed PR, and wait for CI to pass on the merged SHA.
168
+ 2. Tag that SHA with a signed tag and push it. The tag ruleset admits repository admins only.
169
+ 3. [`.github/workflows/release.yml`](.github/workflows/release.yml) re-runs the checks and the package smoke; the publish job then waits for a maintainer's approval on the `npm-release` environment before it publishes with OIDC trusted publishing and automatic provenance.
170
+
171
+ The workflow stores no npm token: the registry trusts `release.yml` on the `npm-release` environment, and [`scripts/npm/release.py`](scripts/npm/release.py) creates both the environment and that trust. The full runbook is [RELEASE.md](RELEASE.md).
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,18 @@
1
+ # dsh-auto-compact — the shipped compaction engine, plus an absolute token cap.
2
+ #
3
+ # The base row is disabled rather than reconfigured: a patch replaces a row's
4
+ # whole config, and the engine swap is what adds the absolute trigger. This
5
+ # profile plane owns no `compaction` provider of its own (the TUI and Web
6
+ # bundles disable the base row so each agent's preset can own one), so this row
7
+ # provides the service without a realm conflict and serves preset-composed
8
+ # agents through the unscoped host listener.
9
+ - id: compaction-basic
10
+ disabled: true
11
+
12
+ - insert:
13
+ - id: auto-compact
14
+ name: '@sagmans/dsh-auto-compact'
15
+ config:
16
+ # Condense at 200K tokens even on a much larger window; the 80% ratio
17
+ # keeps governing every model whose window falls below 250K.
18
+ thresholdTokens: 200000
package/dist/config.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Configuration vocabulary and load-time resolution for the absolute compaction
3
+ * budget.
4
+ * @module @sagmans/dsh-auto-compact/config
5
+ */
6
+ import Schema from '@deepseek-ai/schemastery';
7
+ import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
8
+ /** Tail share of the derived threshold used when no explicit share is configured. */
9
+ export const DEFAULT_RETAIN_SHARE = 0.2;
10
+ const thresholdTokensSchema = Schema.number().step(1).min(1);
11
+ // Bounds only; the open lower bound stays with resolvers, which reject a zero
12
+ // or negative share with a message naming the field.
13
+ const retainShareSchema = Schema.number().min(0).max(1);
14
+ /** Shipped schema dicts; composing from them keeps a new shipped field usable here. */
15
+ const stockConfigDict = BasicCompactionEngine.Config.dict ?? {};
16
+ const stockRouteDict = stockConfigDict.modelPolicies?.inner?.dict ?? {};
17
+ /** One exact route override: every shipped field plus the absolute budget. */
18
+ const routePolicySchema = Schema.object({
19
+ ...stockRouteDict,
20
+ thresholdTokens: thresholdTokensSchema,
21
+ });
22
+ /** Loader-validated plugin schema. */
23
+ export const Config = Schema.object({
24
+ ...stockConfigDict,
25
+ thresholdTokens: thresholdTokensSchema,
26
+ retainShare: retainShareSchema,
27
+ modelPolicies: Schema.array(routePolicySchema),
28
+ });
29
+ /** Strip the plugin-only fields from one route override. */
30
+ function stockRoutePolicy(policy) {
31
+ const { thresholdTokens: _thresholdTokens, ...stock } = policy;
32
+ return stock;
33
+ }
34
+ /**
35
+ * Reduce this plugin's configuration to the shipped engine's own vocabulary.
36
+ *
37
+ * The shipped resolver rejects unknown keys at the top level and inside every
38
+ * route override, so an absolute budget left in place would fail the plugin at
39
+ * load instead of being ignored.
40
+ *
41
+ * @param config - validated plugin configuration.
42
+ * @returns the configuration the shipped engine can resolve.
43
+ */
44
+ export function toEngineConfig(config = {}) {
45
+ const { thresholdTokens: _thresholdTokens, retainShare: _retainShare, modelPolicies, ...stock } = config;
46
+ if (modelPolicies === undefined)
47
+ return stock;
48
+ return { ...stock, modelPolicies: modelPolicies.map(stockRoutePolicy) };
49
+ }
50
+ /** Key matching the shipped engine's exact provider/model route identity. */
51
+ export function routeKey(provider, model) {
52
+ return `${provider}\u0000${model}`;
53
+ }
54
+ function assertCap(name, value) {
55
+ if (value === undefined)
56
+ return undefined;
57
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
58
+ throw new Error(`${name} (${String(value)}) must be a positive integer`);
59
+ }
60
+ return value;
61
+ }
62
+ function assertRetainShare(value) {
63
+ if (value === undefined)
64
+ return DEFAULT_RETAIN_SHARE;
65
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) {
66
+ throw new Error(`retainShare (${String(value)}) must be a number in (0, 1]`);
67
+ }
68
+ return value;
69
+ }
70
+ /**
71
+ * Validate the deployment's absolute budgets.
72
+ *
73
+ * Duplicate route overrides are rejected by the shipped resolver, which sees
74
+ * every override through {@link toEngineConfig}.
75
+ *
76
+ * @param config - plugin configuration, before or after schema normalization.
77
+ * @returns detached budget lookups.
78
+ */
79
+ export function resolveCapPolicy(config = {}) {
80
+ const routeCaps = new Map();
81
+ for (const [index, policy] of (config.modelPolicies ?? []).entries()) {
82
+ const cap = assertCap(`modelPolicies[${index}].thresholdTokens`, policy.thresholdTokens);
83
+ if (cap !== undefined)
84
+ routeCaps.set(routeKey(policy.provider, policy.model), cap);
85
+ }
86
+ return {
87
+ defaultCap: assertCap('thresholdTokens', config.thresholdTokens),
88
+ routeCaps,
89
+ retainShare: assertRetainShare(config.retainShare),
90
+ };
91
+ }
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Absolute-budget compaction engine for the DeepSeek Harness.
3
+ *
4
+ * The shipped engine triggers on a fraction of the routed model's window. This
5
+ * backend keeps that policy and adds an absolute token budget, so a deployment
6
+ * can condense at a fixed price on large-window models while every smaller
7
+ * window keeps its ratio trigger.
8
+ * @module @sagmans/dsh-auto-compact
9
+ */
10
+ import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
11
+ import { Config, resolveCapPolicy, routeKey, toEngineConfig } from "./config.js";
12
+ import { decidePressurePolicy, routePolicyTerms } from "./policy.js";
13
+ import { derivedConfig, findOverride, resolveContextWindow, routedTarget, withDerivedPolicy, } from "./view.js";
14
+ export { Config, DEFAULT_RETAIN_SHARE, resolveCapPolicy, routeKey, toEngineConfig } from "./config.js";
15
+ export { decidePressurePolicy, routePolicyTerms } from "./policy.js";
16
+ /**
17
+ * Shipped compaction backend plus an absolute token budget.
18
+ *
19
+ * Every shipped setting keeps its meaning; `thresholdTokens` adds a cap on the
20
+ * trigger, and `retainShare` bounds the retained tail whenever that cap is what
21
+ * triggers compaction.
22
+ */
23
+ export class AutoCompactEngine extends BasicCompactionEngine {
24
+ static Config = Config;
25
+ static inject = [...BasicCompactionEngine.inject];
26
+ /** Absolute-budget policy resolved at load. */
27
+ capPolicy;
28
+ /**
29
+ * @param ctx - context owning the LLM, token meter, and session services.
30
+ * @param config - plugin configuration; the shipped subset is forwarded as-is.
31
+ */
32
+ constructor(ctx, config = {}) {
33
+ super(ctx, toEngineConfig(config));
34
+ this.capPolicy = resolveCapPolicy(config);
35
+ }
36
+ /**
37
+ * Compact for step-boundary pressure or provider-confirmed overflow.
38
+ *
39
+ * Pressure runs under the derived absolute budget when one applies; every
40
+ * other trigger, and every route the budget does not bind, delegates to the
41
+ * shipped implementation unchanged.
42
+ *
43
+ * @param agent - agent whose latest durable routed request is measured.
44
+ * @param trigger - pressure or context-overflow.
45
+ * @param signal - live turn cancellation signal.
46
+ * @returns the compaction result, or `null` when nothing was needed.
47
+ */
48
+ async compactIfNeeded(agent, trigger, signal) {
49
+ const target = trigger === 'pressure' ? routedTarget(agent.session) : undefined;
50
+ const cap = target === undefined
51
+ ? undefined
52
+ : this.capPolicy.routeCaps.get(routeKey(target.provider, target.model))
53
+ ?? this.capPolicy.defaultCap;
54
+ const measured = cap === undefined ? 0 : this.ctx.tokenMeter.measure(agent.session).totalTokens;
55
+ // The derived trigger never exceeds the budget, so below it there is nothing
56
+ // to decide and no capacity lookup is worth making on every step.
57
+ if (target === undefined || cap === undefined || measured < cap) {
58
+ return super.compactIfNeeded(agent, trigger, signal);
59
+ }
60
+ // The shipped transaction resolves the same capacity again when it prices
61
+ // the span. That second lookup is accepted: it runs only on steps that
62
+ // already exceeded the budget, and the shipped signature exposes no seam to
63
+ // hand a resolved window through.
64
+ const contextWindow = await resolveContextWindow(this.ctx, target, signal);
65
+ if (contextWindow === undefined)
66
+ return super.compactIfNeeded(agent, trigger, signal);
67
+ const override = findOverride(this.config.modelPolicies, target);
68
+ const terms = routePolicyTerms(this.config, override, contextWindow);
69
+ const derived = decidePressurePolicy({
70
+ totalTokens: measured,
71
+ contextWindow,
72
+ thresholdTokens: cap,
73
+ thresholdRatio: terms.thresholdRatio,
74
+ retainTokens: terms.retainTokens,
75
+ retainShare: this.capPolicy.retainShare,
76
+ });
77
+ if (derived === undefined)
78
+ return super.compactIfNeeded(agent, trigger, signal);
79
+ this.ctx.logger.debug(`auto-compact: ${target.provider}/${target.model} pressure ${measured}, `
80
+ + `ratio trigger ${Math.floor(contextWindow * terms.thresholdRatio)} on window ${contextWindow}, `
81
+ + `absolute budget ${cap} -> threshold ${derived.thresholdTokens}, tail ${derived.retainTokens}`);
82
+ const result = await withDerivedPolicy(this, derivedConfig(this.config, override, derived), view => BasicCompactionEngine.prototype.compactIfNeeded.call(view, agent, trigger, signal));
83
+ if (result !== null) {
84
+ this.ctx.logger.info(`auto-compact: condensed at the absolute budget of ${derived.thresholdTokens} tokens `
85
+ + `(window ${contextWindow}, tail ${derived.retainTokens})`);
86
+ }
87
+ return result;
88
+ }
89
+ }
90
+ export default AutoCompactEngine;
package/dist/policy.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure decision logic for the absolute compaction budget: no cordis, no I/O.
3
+ * @module @sagmans/dsh-auto-compact/policy
4
+ */
5
+ /**
6
+ * Half-token bias added to the derived budget before it is expressed as a
7
+ * fraction. The shipped engine re-derives its trigger with
8
+ * `Math.floor(window * ratio)`, and a budget that binary floating point cannot
9
+ * represent exactly scales back to one token less, which then rejects the
10
+ * retained tail as reaching the trigger. Half a token clears that rounding
11
+ * error while staying below the next whole token.
12
+ */
13
+ const SCALE_EPSILON = 0.5;
14
+ /**
15
+ * Resolve the shipped per-route ratio and retained tail in tokens.
16
+ *
17
+ * This transcribes the shipped merge — an exact override wins field by field,
18
+ * retention resolves to exactly one form, and a ratio retention scales with the
19
+ * routed model's capacity — so pricing stays identical when no absolute budget
20
+ * applies.
21
+ *
22
+ * @param base - the shipped engine's resolved deployment policy.
23
+ * @param override - the exact route override, when one matches.
24
+ * @param contextWindow - adapter-owned capacity for the routed model.
25
+ * @returns the shipped terms for this route.
26
+ */
27
+ export function routePolicyTerms(base, override, contextWindow) {
28
+ const thresholdRatio = override?.thresholdRatio ?? base.thresholdRatio;
29
+ let retainTokens;
30
+ if (override?.retainTokens !== undefined) {
31
+ retainTokens = override.retainTokens;
32
+ }
33
+ else if (override?.retainRatio !== undefined) {
34
+ retainTokens = Math.floor(contextWindow * override.retainRatio);
35
+ }
36
+ else if (base.retainTokens !== undefined) {
37
+ retainTokens = base.retainTokens;
38
+ }
39
+ else if (base.retainRatio !== undefined) {
40
+ retainTokens = Math.floor(contextWindow * base.retainRatio);
41
+ }
42
+ else {
43
+ throw new Error('auto-compact: resolved policy carries no retention form');
44
+ }
45
+ return { thresholdRatio, retainTokens };
46
+ }
47
+ /**
48
+ * Decide whether the absolute budget, rather than the shipped ratio, is what
49
+ * should trigger compaction for this request.
50
+ *
51
+ * @param input - measured pressure plus the routed model's resolved budgets.
52
+ * @returns the derived policy, or `undefined` when the shipped path must decide
53
+ * alone (no budget configured, no reported capacity, or a ratio trigger that is
54
+ * already at least as tight).
55
+ */
56
+ export function decidePressurePolicy(input) {
57
+ const cap = input.thresholdTokens;
58
+ // The derived trigger can never exceed the absolute budget, so below the
59
+ // budget there is nothing to decide and no capacity is needed.
60
+ if (cap === undefined || input.totalTokens < cap)
61
+ return undefined;
62
+ const window = input.contextWindow;
63
+ if (window === undefined)
64
+ return undefined;
65
+ const ratioThreshold = Math.floor(window * input.thresholdRatio);
66
+ if (cap >= ratioThreshold)
67
+ return undefined;
68
+ // A model-free prune can land before summarization; the tail is bounded by
69
+ // the derived trigger so a later step always has history left to condense.
70
+ const boundedTail = Math.min(input.retainTokens, Math.floor(cap * input.retainShare),
71
+ // The shipped capacity scaling rejects a tail that reaches the trigger.
72
+ cap - 1);
73
+ return {
74
+ thresholdTokens: cap,
75
+ thresholdRatio: (cap + SCALE_EPSILON) / window,
76
+ retainTokens: Math.max(0, boundedTail),
77
+ };
78
+ }
package/dist/view.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Route resolution and the derived-policy receiver used to re-enter the shipped
3
+ * compaction transaction.
4
+ * @module @sagmans/dsh-auto-compact/view
5
+ */
6
+ /**
7
+ * Read the route the shipped engine itself would price this session against.
8
+ * @param session - session whose latest durable request header is inspected.
9
+ * @returns the routed target, or `undefined` when no route is durable yet.
10
+ */
11
+ export function routedTarget(session) {
12
+ const config = session.requestHeader()?.config;
13
+ if (config === undefined || config.provider.length === 0 || config.model.length === 0)
14
+ return undefined;
15
+ return { provider: config.provider, model: config.model };
16
+ }
17
+ /**
18
+ * Find the exact provider/model override the shipped merge would apply.
19
+ * @param policies - the shipped engine's resolved override table.
20
+ * @param target - routed target to match exactly.
21
+ * @returns the matching override, or `undefined`.
22
+ */
23
+ export function findOverride(policies, target) {
24
+ return policies.find(policy => policy.provider === target.provider && policy.model === target.model);
25
+ }
26
+ /**
27
+ * Read the routed model's adapter-owned capacity.
28
+ * @param ctx - context owning the LLM runtime.
29
+ * @param target - routed provider/model pair.
30
+ * @param signal - cancellation signal forwarded to the adapter.
31
+ * @returns the reported window, or `undefined` when the adapter declares none.
32
+ */
33
+ export async function resolveContextWindow(ctx, target, signal) {
34
+ return (await ctx.llm.resolveModelInfo(target.provider, target.model, signal)).context?.contextWindow;
35
+ }
36
+ /**
37
+ * Build the complete policy the shipped transaction must run under.
38
+ *
39
+ * The route override is merged here and then cleared from the table: the
40
+ * shipped merge would otherwise re-apply the ratio this derivation replaced.
41
+ *
42
+ * @param base - the shipped engine's resolved deployment policy.
43
+ * @param override - the matching exact route override, when any.
44
+ * @param derived - the derived absolute-budget policy.
45
+ * @returns a complete, detached policy for one transaction.
46
+ */
47
+ export function derivedConfig(base, override, derived) {
48
+ const { provider: _provider, model: _model, ...overrideFields } = override ?? {};
49
+ return {
50
+ ...base,
51
+ ...overrideFields,
52
+ thresholdRatio: derived.thresholdRatio,
53
+ retainRatio: undefined,
54
+ retainTokens: derived.retainTokens,
55
+ modelPolicies: [],
56
+ };
57
+ }
58
+ /**
59
+ * Run one shipped compaction transaction under a derived policy.
60
+ *
61
+ * WHY a receiver instead of a field: the shipped engine resolves its policy
62
+ * from the instance field `config`, which is deep-frozen at load and therefore
63
+ * cannot be replaced. Swapping the field for the duration of the call would let
64
+ * two agents compacting concurrently on different routes observe each other's
65
+ * policy, so the derived value lives on a per-call receiver that shares the
66
+ * engine's context, overrides, and private state through the prototype chain.
67
+ *
68
+ * @param engine - the live engine instance.
69
+ * @param policy - the derived policy for this one transaction.
70
+ * @param run - the shipped method to invoke on the receiver.
71
+ * @returns the shipped method's result.
72
+ */
73
+ export function withDerivedPolicy(engine, policy, run) {
74
+ const view = Object.create(engine);
75
+ Object.defineProperty(view, 'config', {
76
+ value: policy,
77
+ enumerable: false,
78
+ writable: false,
79
+ configurable: false,
80
+ });
81
+ return run(view);
82
+ }
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@sagmans/dsh-auto-compact",
3
+ "version": "0.1.0",
4
+ "description": "Absolute token trigger for DeepSeek Harness automatic compaction: condense at a fixed token budget or at the model window ratio, whichever comes first.",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "plugin",
10
+ "compaction",
11
+ "cordis"
12
+ ],
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "exports": {
17
+ ".": "./dist/index.js",
18
+ "./cordis.patch.yml": "./cordis.patch.yml",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "cordis.patch.yml",
24
+ "README.md"
25
+ ],
26
+ "engines": {
27
+ "node": "^24.0.0"
28
+ },
29
+ "packageManager": "pnpm@11.21.0",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/sagmans/dsh-auto-compact.git"
37
+ },
38
+ "homepage": "https://github.com/sagmans/dsh-auto-compact#readme",
39
+ "bugs": "https://github.com/sagmans/dsh-auto-compact/issues",
40
+ "dsh": {
41
+ "bundle": {
42
+ "patch": "./cordis.patch.yml"
43
+ },
44
+ "compatibility": {
45
+ "dsh": ">=0.1.5-rc.1 <0.1.6"
46
+ }
47
+ },
48
+ "scripts": {
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "node --import tsx/esm --test \"tests/*.spec.ts\"",
51
+ "build": "tsc -p tsconfig.build.json",
52
+ "test:build": "node --test \"tests/*.test.mjs\"",
53
+ "test:release": "python3 -m unittest discover -s tests/release -p 'test_*.py'",
54
+ "pack-smoke": "node tools/pack-smoke.mjs",
55
+ "check": "pnpm run typecheck && pnpm test && pnpm run build && pnpm run test:build && pnpm test:release && pnpm run pack-smoke",
56
+ "prepack": "pnpm run build",
57
+ "prepublishOnly": "pnpm run typecheck && pnpm test && pnpm test:release"
58
+ },
59
+ "peerDependencies": {
60
+ "@deepseek-ai/cordis": "^4.0.2",
61
+ "@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 <0.1.6",
62
+ "@deepseek-ai/dsh-compaction": ">=0.1.5-rc.1 <0.1.6",
63
+ "@deepseek-ai/dsh-compaction-basic": ">=0.1.5-rc.1 <0.1.6",
64
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6",
65
+ "@deepseek-ai/dsh-session": ">=0.1.5-rc.1 <0.1.6",
66
+ "@deepseek-ai/dsh-token-meter": ">=0.1.5-rc.1 <0.1.6",
67
+ "@deepseek-ai/schemastery": "^3.18.2"
68
+ },
69
+ "devDependencies": {
70
+ "@deepseek-ai/cordis": "4.0.2",
71
+ "@deepseek-ai/dsh": "0.1.5-rc.2",
72
+ "@deepseek-ai/dsh-agent": "0.1.5-rc.2",
73
+ "@deepseek-ai/dsh-compaction": "0.1.5-rc.2",
74
+ "@deepseek-ai/dsh-compaction-basic": "0.1.5-rc.2",
75
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
76
+ "@deepseek-ai/dsh-session": "0.1.5-rc.2",
77
+ "@deepseek-ai/dsh-token-meter": "0.1.5-rc.2",
78
+ "@deepseek-ai/schemastery": "3.18.2",
79
+ "@types/node": "22.20.0",
80
+ "tsx": "4.22.4",
81
+ "typescript": "6.0.3"
82
+ }
83
+ }