dsh-context-compression-improved 0.5.0 → 0.5.2
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/CHANGELOG.ja.md +51 -0
- package/CHANGELOG.ko.md +51 -0
- package/CHANGELOG.md +55 -0
- package/CHANGELOG.zh.md +45 -0
- package/package.json +1 -1
- package/packages/selector/lib/advisor-state.js +4 -231
- package/packages/selector/lib/client.d.ts +0 -24
- package/packages/selector/lib/client.js +6 -501
- package/packages/selector/lib/index.d.ts +4 -10
- package/packages/selector/lib/index.js +65 -235
- package/packages/selector/lib/pruner.d.ts +13 -248
- package/packages/selector/lib/pruner.js +148 -552
- package/packages/selector/src/client/EstimatorControls.tsx +277 -378
- package/packages/selector/src/client/index.ts +0 -17
- package/packages/selector/src/client/locales.ts +196 -234
- package/packages/selector/src/client/preset-options.ts +3 -2
- package/packages/selector/src/client/settings-section.tsx +8 -17
- package/packages/selector/src/index.ts +463 -710
- package/packages/selector/src/preset-overlay.ts +60 -1
- package/packages/selector/src/profiles.ts +4 -27
- package/packages/selector/src/pruner/state.ts +50 -73
- package/packages/selector/src/pruner.ts +2402 -2730
- package/packages/selector/src/runtime/audit.ts +27 -21
- package/packages/selector/src/runtime/config.ts +6 -32
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +16 -0
- package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
- package/packages/selector/src/runtime/types.ts +0 -17
- package/packages/selector/tests/built/client-artifact.spec.ts +9 -5
- package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
- package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
- package/packages/selector/tests/runtime/audit.spec.ts +35 -21
- package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
- package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
- package/packages/selector/tests/settings-seat.client.spec.ts +16 -10
- package/packages/selector/tests/standing-generation.host.spec.ts +54 -5
- package/scripts/packed-components-smoke.mjs +30 -8
- package/scripts/packed-install-e2e.mjs +69 -15
- package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
- package/packages/selector/src/client/review-scope.ts +0 -16
- package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
- package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
- package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
- package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
- package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
- package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
- package/packages/selector/tests/review-routes.host.spec.ts +0 -290
- package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
- package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
- package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Structured, content-free runtime audit records for context compression. */
|
|
2
2
|
|
|
3
|
+
import type { AdviceBand } from './tokenpilot/benefit.ts'
|
|
3
4
|
import type {
|
|
4
5
|
CompressionPolicy,
|
|
5
6
|
CompressionProfile,
|
|
@@ -204,29 +205,34 @@ export interface AdvisorOutcomeAuditRecord extends CompressionAuditBase {
|
|
|
204
205
|
readonly latencyMs: number
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
readonly
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
readonly decision?: 'approved' | 'rejected' | 'ignored'
|
|
222
|
-
/** Execution receipt state (apply-receipt only). */
|
|
223
|
-
readonly receiptStatus?: 'applied' | 'deferred'
|
|
224
|
-
/** Aligned reason code (deferred receipts and void applications only). */
|
|
225
|
-
readonly reasonCode?: string
|
|
208
|
+
/**
|
|
209
|
+
* One advisory benefit-model label for a batch that LANDED. The retired review
|
|
210
|
+
* gate used these bands to withhold a batch; a reduction must never block
|
|
211
|
+
* automatic processing, so the band is published as advice instead. Numeric and
|
|
212
|
+
* enum fields only — never content.
|
|
213
|
+
*/
|
|
214
|
+
export interface ReductionAdviceAuditRecord extends CompressionAuditBase {
|
|
215
|
+
readonly kind: 'reduction-advice'
|
|
216
|
+
/** Profile the advised pass ran under. */
|
|
217
|
+
readonly profile: CompressionProfile
|
|
218
|
+
/** Band the benefit model labelled the landed batch with. */
|
|
219
|
+
readonly band: AdviceBand
|
|
220
|
+
/** Landing stage the batch was priced at. */
|
|
221
|
+
readonly stage: 'fresh' | 'history'
|
|
226
222
|
readonly itemSeqs: readonly number[]
|
|
223
|
+
/** Candidates that carried a positive recovery and were priced. */
|
|
224
|
+
readonly pricedCandidates: number
|
|
225
|
+
/** Largest single-candidate token mass in the batch. */
|
|
226
|
+
readonly maxTokensBefore: number
|
|
227
227
|
readonly tokensBefore: number
|
|
228
228
|
readonly tokensAfter: number
|
|
229
|
-
|
|
229
|
+
readonly recoveredTokens: number
|
|
230
|
+
readonly penaltyTokens: number
|
|
231
|
+
/** Turns of discounted recovery needed to recoup the refill penalty. */
|
|
232
|
+
readonly paybackTurns?: number
|
|
233
|
+
/** Discounted net benefit over the remaining session; omitted when Ŝ is unknown. */
|
|
234
|
+
readonly expectedSaving?: number
|
|
235
|
+
/** Turn index the batch landed at. */
|
|
230
236
|
readonly turnIndex?: number
|
|
231
237
|
}
|
|
232
238
|
|
|
@@ -241,7 +247,7 @@ export type CompressionAuditRecord =
|
|
|
241
247
|
| SummaryLocatorAuditRecord
|
|
242
248
|
| EstimatorOutcomeAuditRecord
|
|
243
249
|
| AdvisorOutcomeAuditRecord
|
|
244
|
-
|
|
|
250
|
+
| ReductionAdviceAuditRecord
|
|
245
251
|
|
|
246
252
|
/** Minimal logger method consumed by the audit publisher. */
|
|
247
253
|
export interface CompressionAuditLogger {
|
|
@@ -127,6 +127,11 @@ export function parsePresetOptionsSettings(value: unknown): PresetOptionsSetting
|
|
|
127
127
|
const allowed = new Set([
|
|
128
128
|
'dedupeToolResults', 'summaryLocator', 'prefixStabilizer', 'readState', 'estimatorMode',
|
|
129
129
|
'estimatorProvider', 'estimatorModel', 'estimatorBaseUrl', 'estimatorApiKey', 'estimatorTimeoutMs',
|
|
130
|
+
// Legacy keys of the retired human-gated review pipeline. They stay in the
|
|
131
|
+
// ACCEPTED set on purpose: the keys are persisted in existing settings
|
|
132
|
+
// documents, and rejecting them would make this plugin fail to load on an
|
|
133
|
+
// upgrade. They are read by nothing — the gate they configured no longer
|
|
134
|
+
// exists, and a reduction never blocks automatic processing.
|
|
130
135
|
'reviewMode', 'reviewTimeoutTurns', 'cacheHitDiscountAlpha', 'reviewHighImpactTokens',
|
|
131
136
|
'advisorMode', 'advisorTimeoutMs', 'advisorRefreshTurns', 'advisorScoreThreshold', 'advisorSampleLimit',
|
|
132
137
|
'advisorMinTokens',
|
|
@@ -135,7 +140,7 @@ export function parsePresetOptionsSettings(value: unknown): PresetOptionsSetting
|
|
|
135
140
|
if (unknown !== undefined) {
|
|
136
141
|
throw new TypeError(`Context-compression presetOptions: unknown key "${unknown}"`)
|
|
137
142
|
}
|
|
138
|
-
const booleans = ['dedupeToolResults', 'summaryLocator', 'prefixStabilizer', 'readState'
|
|
143
|
+
const booleans = ['dedupeToolResults', 'summaryLocator', 'prefixStabilizer', 'readState'] as const
|
|
139
144
|
for (const key of booleans) {
|
|
140
145
|
const entry = value[key]
|
|
141
146
|
if (entry !== undefined && typeof entry !== 'boolean') {
|
|
@@ -185,23 +190,6 @@ export function parsePresetOptionsSettings(value: unknown): PresetOptionsSetting
|
|
|
185
190
|
|| estimatorTimeoutMs < 100 || estimatorTimeoutMs > 60_000)) {
|
|
186
191
|
throw new TypeError('Context-compression presetOptions.estimatorTimeoutMs must be an integer between 100 and 60000')
|
|
187
192
|
}
|
|
188
|
-
const reviewTimeoutTurns = value.reviewTimeoutTurns
|
|
189
|
-
if (reviewTimeoutTurns !== undefined
|
|
190
|
-
&& (typeof reviewTimeoutTurns !== 'number' || !Number.isSafeInteger(reviewTimeoutTurns) || reviewTimeoutTurns < 1)) {
|
|
191
|
-
throw new TypeError('Context-compression presetOptions.reviewTimeoutTurns must be an integer of at least 1')
|
|
192
|
-
}
|
|
193
|
-
const cacheHitDiscountAlpha = value.cacheHitDiscountAlpha
|
|
194
|
-
if (cacheHitDiscountAlpha !== undefined
|
|
195
|
-
&& (typeof cacheHitDiscountAlpha !== 'number' || !Number.isFinite(cacheHitDiscountAlpha)
|
|
196
|
-
|| cacheHitDiscountAlpha <= 0 || cacheHitDiscountAlpha >= 1)) {
|
|
197
|
-
throw new TypeError('Context-compression presetOptions.cacheHitDiscountAlpha must be a number strictly between 0 and 1')
|
|
198
|
-
}
|
|
199
|
-
const reviewHighImpactTokens = value.reviewHighImpactTokens
|
|
200
|
-
if (reviewHighImpactTokens !== undefined
|
|
201
|
-
&& (typeof reviewHighImpactTokens !== 'number' || !Number.isSafeInteger(reviewHighImpactTokens)
|
|
202
|
-
|| reviewHighImpactTokens < 0)) {
|
|
203
|
-
throw new TypeError('Context-compression presetOptions.reviewHighImpactTokens must be a non-negative integer')
|
|
204
|
-
}
|
|
205
193
|
for (const key of ['estimatorProvider', 'estimatorModel', 'estimatorBaseUrl', 'estimatorApiKey'] as const) {
|
|
206
194
|
const entry = value[key]
|
|
207
195
|
if (entry !== undefined && typeof entry !== 'string') {
|
|
@@ -221,10 +209,6 @@ export function parsePresetOptionsSettings(value: unknown): PresetOptionsSetting
|
|
|
221
209
|
if (value.estimatorBaseUrl !== undefined) result.estimatorBaseUrl = value.estimatorBaseUrl as string
|
|
222
210
|
if (value.estimatorApiKey !== undefined) result.estimatorApiKey = value.estimatorApiKey as string
|
|
223
211
|
if (estimatorTimeoutMs !== undefined) result.estimatorTimeoutMs = estimatorTimeoutMs as number
|
|
224
|
-
if (value.reviewMode !== undefined) result.reviewMode = value.reviewMode as boolean
|
|
225
|
-
if (reviewTimeoutTurns !== undefined) result.reviewTimeoutTurns = reviewTimeoutTurns as number
|
|
226
|
-
if (cacheHitDiscountAlpha !== undefined) result.cacheHitDiscountAlpha = cacheHitDiscountAlpha as number
|
|
227
|
-
if (reviewHighImpactTokens !== undefined) result.reviewHighImpactTokens = reviewHighImpactTokens as number
|
|
228
212
|
if (advisorMode !== undefined) result.advisorMode = advisorMode as '' | 'host' | 'direct'
|
|
229
213
|
if (advisorTimeoutMs !== undefined) result.advisorTimeoutMs = advisorTimeoutMs as number
|
|
230
214
|
if (advisorRefreshTurns !== undefined) result.advisorRefreshTurns = advisorRefreshTurns as number
|
|
@@ -498,12 +482,6 @@ const PRESET_OPTION_DEFAULTS: PresetOptions = deepFreeze({
|
|
|
498
482
|
prefixStabilizer: true,
|
|
499
483
|
readState: true,
|
|
500
484
|
estimator: { mode: '' },
|
|
501
|
-
// Review pipeline (beta) ships off: pending proposals never block the
|
|
502
|
-
// automatic path until the user opts in.
|
|
503
|
-
reviewMode: false,
|
|
504
|
-
reviewTimeoutTurns: 6,
|
|
505
|
-
cacheHitDiscountAlpha: 0.1,
|
|
506
|
-
reviewHighImpactTokens: 4000,
|
|
507
485
|
// Advisory advisor ships off: statistics and suggestions only, never a gate.
|
|
508
486
|
advisor: {
|
|
509
487
|
mode: '',
|
|
@@ -530,10 +508,6 @@ function mergePresetOptions(overrides: PresetOptionsSettings | undefined): Prese
|
|
|
530
508
|
prefixStabilizer: overrides.prefixStabilizer ?? PRESET_OPTION_DEFAULTS.prefixStabilizer,
|
|
531
509
|
readState: overrides.readState ?? PRESET_OPTION_DEFAULTS.readState,
|
|
532
510
|
estimator: { mode: overrides.estimatorMode ?? PRESET_OPTION_DEFAULTS.estimator.mode },
|
|
533
|
-
reviewMode: overrides.reviewMode ?? PRESET_OPTION_DEFAULTS.reviewMode,
|
|
534
|
-
reviewTimeoutTurns: overrides.reviewTimeoutTurns ?? PRESET_OPTION_DEFAULTS.reviewTimeoutTurns,
|
|
535
|
-
cacheHitDiscountAlpha: overrides.cacheHitDiscountAlpha ?? PRESET_OPTION_DEFAULTS.cacheHitDiscountAlpha,
|
|
536
|
-
reviewHighImpactTokens: overrides.reviewHighImpactTokens ?? PRESET_OPTION_DEFAULTS.reviewHighImpactTokens,
|
|
537
511
|
advisor: {
|
|
538
512
|
mode: overrides.advisorMode ?? PRESET_OPTION_DEFAULTS.advisor.mode,
|
|
539
513
|
timeoutMs: overrides.advisorTimeoutMs ?? PRESET_OPTION_DEFAULTS.advisor.timeoutMs,
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
* plain import rather than constructor injection.
|
|
14
14
|
*/
|
|
15
15
|
import type { Session } from '@deepseek-ai/dsh-session'
|
|
16
|
+
import type { AdviceBand } from './benefit.ts'
|
|
17
|
+
|
|
18
|
+
/** The most recent benefit-model label of a landed batch. Observational only:
|
|
19
|
+
* no decision path reads it — the report route serves it verbatim. */
|
|
20
|
+
export interface AdvisorAdviceSnapshot {
|
|
21
|
+
readonly band: AdviceBand
|
|
22
|
+
/** Turn index the advised batch landed at. */
|
|
23
|
+
readonly turn: number
|
|
24
|
+
readonly itemSeqs: readonly number[]
|
|
25
|
+
readonly recoveredTokens: number
|
|
26
|
+
readonly penaltyTokens: number
|
|
27
|
+
readonly paybackTurns?: number
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
/** One relevance score for one surface seq, with the turn it was scored at. */
|
|
18
31
|
export interface AdvisorScoreEntry {
|
|
@@ -59,6 +72,8 @@ export interface AdvisorState {
|
|
|
59
72
|
inFlight: boolean
|
|
60
73
|
/** Most recent decay computation, shared verbatim by audits and the report route. */
|
|
61
74
|
lastDecay: { readonly decay: number, readonly weightedChars: number, readonly turn: number } | undefined
|
|
75
|
+
/** Most recent benefit-model advice; replaced by every advised batch. */
|
|
76
|
+
lastAdvice: AdvisorAdviceSnapshot | undefined
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
/** Upper bound of the per-session scores LRU. */
|
|
@@ -85,6 +100,7 @@ export function getAdvisorState(session: Session): AdvisorState {
|
|
|
85
100
|
failures: undefined,
|
|
86
101
|
inFlight: false,
|
|
87
102
|
lastDecay: undefined,
|
|
103
|
+
lastAdvice: undefined,
|
|
88
104
|
}
|
|
89
105
|
advisorStates.set(session, state)
|
|
90
106
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenPilot-inspired benefit model — **advisory only**.
|
|
3
|
+
*
|
|
4
|
+
* This module prices one pass's planned replacements as ONE merged mutation and
|
|
5
|
+
* labels the batch with the band the model would have chosen. The label is
|
|
6
|
+
* ADVICE: nothing here suppresses, delays, or rewrites a reduction. The former
|
|
7
|
+
* human-gated review pipeline consumed these bands to withhold the batch from
|
|
8
|
+
* landing; that gate was retired because a reduction must never block automatic
|
|
9
|
+
* processing. The bands now feed the `reduction-advice` audit and the advisor
|
|
10
|
+
* report, and every planned replacement that the rule engine produced lands.
|
|
11
|
+
*
|
|
12
|
+
* Cache accounting (TokenPilot paper): one merged mutation pays a one-time tail
|
|
13
|
+
* KV-cache refill penalty of `(1−α)·tailTokens`, and every later turn recovers
|
|
14
|
+
* the reclaimed tokens at the cache-hit discount `α`:
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* R = Σ(tokensBefore − tokensAfter) // net reclaimed tokens
|
|
18
|
+
* paybackTurns = (1−α)·tailTokens / (α·R) // one-time refill / per-turn saving
|
|
19
|
+
* expectedSaving = α·R·max(0, Ŝ − paybackTurns) // Ŝ = estimated remaining turns
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* The refill penalty only models mutations of already-cached context. A
|
|
23
|
+
* fresh-stage batch (shaped before its first request) is exempt via
|
|
24
|
+
* `refillPenaltyExempt`: payback is 0 and every reclaimed token saves from
|
|
25
|
+
* the very first turn. Without that exemption every realistic fresh batch
|
|
26
|
+
* prices into the `not-worth-it` band, which makes the advice useless exactly
|
|
27
|
+
* where it is most often consulted.
|
|
28
|
+
*
|
|
29
|
+
* `expectedSaving` is only produced when Ŝ is known (the estimator answered
|
|
30
|
+
* with `expectedRemainingTurns`); it is never fabricated from a guess.
|
|
31
|
+
*
|
|
32
|
+
* Pure functions only: the classifier needs no I/O, no session state, and no
|
|
33
|
+
* host services, so every label is unit-testable and audit-replayable.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** The minimal per-candidate face the benefit model consumes. */
|
|
37
|
+
export interface BenefitCandidate {
|
|
38
|
+
readonly sourceSeq: number
|
|
39
|
+
readonly tokensBefore: number
|
|
40
|
+
readonly tokensAfter: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BenefitInput {
|
|
44
|
+
/** Cache-hit discount rate α ∈ (0,1); validated upstream by config parsing. */
|
|
45
|
+
readonly alpha: number
|
|
46
|
+
/** Token mass of the protected tail that must be refilled after a mutation. */
|
|
47
|
+
readonly tailTokens: number
|
|
48
|
+
/** Estimated remaining turns Ŝ; `undefined` keeps expectedSaving out of the result. */
|
|
49
|
+
readonly remainingTurns?: number | undefined
|
|
50
|
+
/** True for fresh-stage batches: their content was never served, so it is
|
|
51
|
+
* not in the KV cache and shaping it causes no cache break — no refill
|
|
52
|
+
* penalty applies and the whole discounted recovery is pure gain. */
|
|
53
|
+
readonly refillPenaltyExempt?: boolean | undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BenefitEstimate {
|
|
57
|
+
/** Net reclaimed tokens across the batch; may be ≤ 0 when a batch is not worth it. */
|
|
58
|
+
readonly recoveredTokens: number
|
|
59
|
+
/** The one-time cache-refill penalty the merged mutation pays: (1−α)·tailTokens. */
|
|
60
|
+
readonly penaltyTokens: number
|
|
61
|
+
/** Turns of discounted recovery needed to recoup the penalty; `undefined` when α·R ≤ 0. */
|
|
62
|
+
readonly paybackTurns?: number
|
|
63
|
+
/** Discounted net benefit over the remaining session; omitted when Ŝ is unknown. */
|
|
64
|
+
readonly expectedSaving?: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The band one priced batch falls into. Advisory vocabulary only — every band
|
|
69
|
+
* lands; the label says what the model thought of the landing, not whether it
|
|
70
|
+
* was allowed to happen.
|
|
71
|
+
*/
|
|
72
|
+
export type AdviceBand =
|
|
73
|
+
/** Payback ≤ 1 turn, or ≤ a quarter of the estimated remaining turns. */
|
|
74
|
+
| 'profitable'
|
|
75
|
+
/** At least one candidate reaches the high-impact threshold: the batch is
|
|
76
|
+
* large enough that a human would want to know it moved. */
|
|
77
|
+
| 'high-impact'
|
|
78
|
+
/** Ŝ known and payback ∈ (1, 3]: it does pay back, but slowly. */
|
|
79
|
+
| 'slow-payback'
|
|
80
|
+
/** α·R ≤ 0: the accounting has no discounted recovery to argue from. */
|
|
81
|
+
| 'unpriceable'
|
|
82
|
+
/** Ŝ known and payback > 3 turns: the model would not have spent the cache break. */
|
|
83
|
+
| 'not-worth-it'
|
|
84
|
+
|
|
85
|
+
export interface AdviceInput {
|
|
86
|
+
/** Cache-hit discount rate α ∈ (0,1); validated upstream by config parsing. */
|
|
87
|
+
readonly alpha: number
|
|
88
|
+
/** Token mass of the protected tail that must be refilled after a mutation. */
|
|
89
|
+
readonly tailTokens: number
|
|
90
|
+
/** Candidates at or above this token impact are labelled `high-impact`. */
|
|
91
|
+
readonly highImpactTokens: number
|
|
92
|
+
/** Estimated remaining turns Ŝ; `undefined` closes the slow-payback band. */
|
|
93
|
+
readonly remainingTurns?: number | undefined
|
|
94
|
+
/** Landing stage of the batch: `'fresh'` batches are priced without the
|
|
95
|
+
* tail-refill penalty (first-exposure shaping causes no cache break);
|
|
96
|
+
* `'history'` batches — already-served content — pay it in full. */
|
|
97
|
+
readonly stage?: 'fresh' | 'history' | undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface AdviceResult {
|
|
101
|
+
readonly band: AdviceBand
|
|
102
|
+
/** The full benefit estimate the band was derived from. */
|
|
103
|
+
readonly benefit: BenefitEstimate
|
|
104
|
+
/** Number of candidates that carried a positive recovery and were priced. */
|
|
105
|
+
readonly priced: number
|
|
106
|
+
/** Largest single-candidate token mass in the batch (the high-impact evidence). */
|
|
107
|
+
readonly maxTokensBefore: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Shipped defaults of the advisory model. They are constants rather than
|
|
112
|
+
* settings keys because the machine no longer acts on them: after the review
|
|
113
|
+
* gate was retired these numbers only shape a label, so exposing them as
|
|
114
|
+
* tunable configuration would advertise a control that changes no behavior.
|
|
115
|
+
*/
|
|
116
|
+
export const DEFAULT_ADVICE_ALPHA = 0.1
|
|
117
|
+
export const DEFAULT_ADVICE_HIGH_IMPACT_TOKENS = 4_000
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Aggregate the batch-level benefit of a set of reduction candidates.
|
|
121
|
+
*
|
|
122
|
+
* Individual candidates whose replacement would grow the context contribute
|
|
123
|
+
* zero recovery (they never make a batch look better than dropping them).
|
|
124
|
+
*/
|
|
125
|
+
export function computeBenefit(candidates: readonly BenefitCandidate[], input: BenefitInput): BenefitEstimate {
|
|
126
|
+
const { alpha, tailTokens, remainingTurns } = input
|
|
127
|
+
let recoveredTokens = 0
|
|
128
|
+
for (const candidate of candidates) {
|
|
129
|
+
recoveredTokens += Math.max(0, candidate.tokensBefore - candidate.tokensAfter)
|
|
130
|
+
}
|
|
131
|
+
const penaltyTokens = input.refillPenaltyExempt === true ? 0 : (1 - alpha) * tailTokens
|
|
132
|
+
const perTurnSaving = alpha * recoveredTokens
|
|
133
|
+
if (perTurnSaving <= 0) {
|
|
134
|
+
return remainingTurns === undefined
|
|
135
|
+
? { recoveredTokens, penaltyTokens }
|
|
136
|
+
: { recoveredTokens, penaltyTokens, expectedSaving: -penaltyTokens }
|
|
137
|
+
}
|
|
138
|
+
const paybackTurns = penaltyTokens / perTurnSaving
|
|
139
|
+
if (remainingTurns === undefined) {
|
|
140
|
+
return { recoveredTokens, penaltyTokens, paybackTurns }
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
recoveredTokens,
|
|
144
|
+
penaltyTokens,
|
|
145
|
+
paybackTurns,
|
|
146
|
+
expectedSaving: perTurnSaving * Math.max(0, remainingTurns - paybackTurns),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Label one batch of planned replacements.
|
|
152
|
+
*
|
|
153
|
+
* Pipeline: zero/negative-recovery candidates are priced out first (they never
|
|
154
|
+
* make a batch look better), the surviving batch is priced once through
|
|
155
|
+
* `computeBenefit`, and the band is a batch decision — the refill penalty is a
|
|
156
|
+
* property of the landing event, not of any single candidate, so pricing per
|
|
157
|
+
* candidate would overstate payback N-fold.
|
|
158
|
+
*
|
|
159
|
+
* Band precedence (identical thresholds to the retired triage model):
|
|
160
|
+
* - α too small to price a payback → `unpriceable`;
|
|
161
|
+
* - any candidate at `highImpactTokens` → `high-impact`;
|
|
162
|
+
* - `paybackTurns ≤ 1`, or Ŝ known and `paybackTurns ≤ 0.25·Ŝ` → `profitable`;
|
|
163
|
+
* - Ŝ known and `paybackTurns ∈ (1, 3]` → `slow-payback`;
|
|
164
|
+
* - everything else → `not-worth-it`.
|
|
165
|
+
*
|
|
166
|
+
* @param candidates - planned replacements of one pass, in any order.
|
|
167
|
+
* @param input - pricing inputs and the stage of the batch.
|
|
168
|
+
* @returns the advice, or `undefined` when nothing carried a positive recovery.
|
|
169
|
+
*/
|
|
170
|
+
export function adviseCandidates(
|
|
171
|
+
candidates: readonly BenefitCandidate[],
|
|
172
|
+
input: AdviceInput,
|
|
173
|
+
): AdviceResult | undefined {
|
|
174
|
+
const usable: BenefitCandidate[] = []
|
|
175
|
+
for (const candidate of candidates) {
|
|
176
|
+
if (Math.max(0, candidate.tokensBefore - candidate.tokensAfter) <= 0) continue
|
|
177
|
+
usable.push(candidate)
|
|
178
|
+
}
|
|
179
|
+
if (usable.length === 0) return undefined
|
|
180
|
+
|
|
181
|
+
const benefit = computeBenefit(usable, {
|
|
182
|
+
alpha: input.alpha,
|
|
183
|
+
tailTokens: input.tailTokens,
|
|
184
|
+
...input.remainingTurns !== undefined ? { remainingTurns: input.remainingTurns } : {},
|
|
185
|
+
refillPenaltyExempt: input.stage === 'fresh',
|
|
186
|
+
})
|
|
187
|
+
const payback = benefit.paybackTurns
|
|
188
|
+
const maxTokensBefore = usable.reduce((max, candidate) => Math.max(max, candidate.tokensBefore), 0)
|
|
189
|
+
const band: AdviceBand = payback === undefined
|
|
190
|
+
? 'unpriceable'
|
|
191
|
+
: maxTokensBefore >= input.highImpactTokens
|
|
192
|
+
? 'high-impact'
|
|
193
|
+
: payback <= 1
|
|
194
|
+
|| (input.remainingTurns !== undefined && payback <= 0.25 * input.remainingTurns)
|
|
195
|
+
? 'profitable'
|
|
196
|
+
: input.remainingTurns !== undefined && payback <= 3
|
|
197
|
+
? 'slow-payback'
|
|
198
|
+
: 'not-worth-it'
|
|
199
|
+
return { band, benefit, priced: usable.length, maxTokensBefore }
|
|
200
|
+
}
|
|
@@ -85,18 +85,6 @@ export interface PresetOptions {
|
|
|
85
85
|
readonly sampleLimit: number
|
|
86
86
|
readonly minTokens: number
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Human-gated review pipeline (beta): edge/high-impact candidates queue for
|
|
90
|
-
* manual approval and execute in one merged batch at the next turn boundary
|
|
91
|
-
* instead of the automatic path (R4).
|
|
92
|
-
*/
|
|
93
|
-
readonly reviewMode: boolean
|
|
94
|
-
/** Turn-boundary patience: pending review proposals older than this many turns auto-expire (R4). */
|
|
95
|
-
readonly reviewTimeoutTurns: number
|
|
96
|
-
/** Cache-hit discount rate α in the benefit model; expectedSaving = α·R·Ŝ − (1−α)·tail. */
|
|
97
|
-
readonly cacheHitDiscountAlpha: number
|
|
98
|
-
/** Candidates whose tokenBefore reaches this threshold bypass payback triage and always enter review (R4). */
|
|
99
|
-
readonly reviewHighImpactTokens: number
|
|
100
88
|
}
|
|
101
89
|
|
|
102
90
|
/** Common user-authored Custom stages shared by persisted policy versions. */
|
|
@@ -154,11 +142,6 @@ export interface PresetOptionsSettings {
|
|
|
154
142
|
readonly prefixStabilizer?: boolean
|
|
155
143
|
readonly readState?: boolean
|
|
156
144
|
readonly estimatorMode?: '' | 'host' | 'direct'
|
|
157
|
-
/** Review-mode overrides (beta); see PresetOptions.reviewMode. */
|
|
158
|
-
readonly reviewMode?: boolean
|
|
159
|
-
readonly reviewTimeoutTurns?: number
|
|
160
|
-
readonly cacheHitDiscountAlpha?: number
|
|
161
|
-
readonly reviewHighImpactTokens?: number
|
|
162
145
|
/**
|
|
163
146
|
* Estimator endpoint fields. Persisted-settings only: they never enter the
|
|
164
147
|
* frozen CompressionPolicy, which is emitted verbatim by policy-resolved
|
|
@@ -34,11 +34,15 @@ describe('built Harness client artifact', () => {
|
|
|
34
34
|
}) as { apply?: unknown, inject?: unknown }
|
|
35
35
|
|
|
36
36
|
expect(exported.apply).toBeTypeOf('function')
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
37
|
+
// Declare every consumed service: cordis holds apply until each one is
|
|
38
|
+
// provided, which is the official client-plugin pattern on this 0.1.5 host
|
|
39
|
+
// line (dsh-thinking-levels does the same). The earlier lazy alternative —
|
|
40
|
+
// resolving `locale` / `settingsScope` through ctx.get() inside apply —
|
|
41
|
+
// raced the settings client's activation; on a loss the apply
|
|
42
|
+
// early-returned and EVERY settings entry (the standalone section, the
|
|
43
|
+
// plugins-tab card, the item card) silently vanished. See the rationale
|
|
44
|
+
// recorded in src/client/index.ts.
|
|
45
|
+
expect(exported.inject).toEqual(['slots', 'locale', 'settingsScope'])
|
|
42
46
|
const style = document.querySelector<HTMLStyleElement>(
|
|
43
47
|
'style[data-plugin-css="dsh-context-compression-improved/CompressionProfileSelector.module.css"]',
|
|
44
48
|
)
|
|
@@ -148,35 +148,19 @@ describe('presetOptions writes are path-addressed', () => {
|
|
|
148
148
|
expect(writes).toEqual([[{ op: 'set', path: ['presetOptions', 'estimatorProvider'], value: 'local-35b' }]])
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
it('
|
|
151
|
+
it('treats a retired review-gate patch as a no-op instead of a write', async () => {
|
|
152
152
|
const { injected, writes, snapshot } = bindInjected({ estimatorMode: 'host' })
|
|
153
|
+
// The patch surface no longer carries these keys, and the decoders accept
|
|
154
|
+
// them only so a legacy document keeps loading. Cast past the removed type
|
|
155
|
+
// so the test pins the RUNTIME behaviour: nothing is written.
|
|
153
156
|
await injected.savePresetOptions({
|
|
154
157
|
reviewMode: true,
|
|
155
158
|
reviewTimeoutTurns: 8,
|
|
156
159
|
cacheHitDiscountAlpha: 0.2,
|
|
157
160
|
reviewHighImpactTokens: 6000,
|
|
158
|
-
})
|
|
159
|
-
expect(
|
|
160
|
-
|
|
161
|
-
reviewMode: true,
|
|
162
|
-
reviewTimeoutTurns: 8,
|
|
163
|
-
cacheHitDiscountAlpha: 0.2,
|
|
164
|
-
reviewHighImpactTokens: 6000,
|
|
165
|
-
})
|
|
166
|
-
await injected.savePresetOptions({ reviewMode: undefined })
|
|
167
|
-
expect(snapshot()['presetOptions']).toEqual({
|
|
168
|
-
estimatorMode: 'host',
|
|
169
|
-
reviewTimeoutTurns: 8,
|
|
170
|
-
cacheHitDiscountAlpha: 0.2,
|
|
171
|
-
reviewHighImpactTokens: 6000,
|
|
172
|
-
})
|
|
173
|
-
for (const batch of writes) {
|
|
174
|
-
for (const op of batch) {
|
|
175
|
-
expect(op.path).toEqual(['presetOptions', op.path[1] ?? ''])
|
|
176
|
-
expect(['reviewMode', 'reviewTimeoutTurns', 'cacheHitDiscountAlpha', 'reviewHighImpactTokens'])
|
|
177
|
-
.toContain(op.path[1])
|
|
178
|
-
}
|
|
179
|
-
}
|
|
161
|
+
} as unknown as Parameters<typeof injected.savePresetOptions>[0])
|
|
162
|
+
expect(writes).toEqual([])
|
|
163
|
+
expect(snapshot()['presetOptions']).toEqual({ estimatorMode: 'host' })
|
|
180
164
|
})
|
|
181
165
|
|
|
182
166
|
it('never replaces the whole section, so sibling overrides survive', async () => {
|