dsh-compaction-threshold
Verifieddsh-compaction-threshold · v1.0.3 · MIT · Web UI
在上下文监控卡片内直接调节自动压缩的触发阈值。host 半在运行时替换 @deepseek-ai/dsh-compaction-basic 已挂载引擎的 thresholdRatio 并持久化,client 半把滑块画进卡片面板。
Install
dsh plugin add dsh-compaction-threshold Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-compaction-threshold
在 DSH 的上下文监控卡片里直接调节自动压缩的触发阈值,并查看本会话的压缩记录。
点开输入框下方的上下文占用卡片,面板底部会多出:
- 一条「压缩触发阈值」滑块,拖动即生效,无需重启;
- 一个「压缩记录」入口,展开后按时间倒序列出本会话每次压缩前后的上下文占用。
它改的是什么
自动压缩由 @deepseek-ai/dsh-compaction-basic 驱动,触发条件是
thresholdTokens = floor(min(contextWindow * thresholdRatio,
contextWindow - reservedCompletionTokens - headroomTokens))
measurement.totalTokens >= thresholdTokens → 压缩
滑块写的就是 thresholdRatio。默认 0.8,即上下文用到窗口的 80% 时压缩;
调低更早压缩、更省窗口,调高则更晚压缩、单轮上下文更完整。
范围 20% – 95%,下限还会自动抬到各引擎 retainRatio 之上(官方
resolveCompactSpec 要求保留比例严格小于触发比例,两者相等会抛错)。
口径:以卡片为准
只看 thresholdRatio 会有一个坑:官方存在两套不同的上下文口径。
| 算法 | 特点 | |
|---|---|---|
| 卡片 | contextPressure 投影的 projectedTokens ?? pressureTokens |
主项是 provider 回报的真实 prompt usage(不含输出) |
| 引擎 | TokenMeter.measure().totalTokens |
真实 usage 与 4 字符/令牌的启发式估值取高者 |
真实中文/代码内容的密度约 5.4–6.9 字符/令牌,而启发式硬编码 4,于是它几乎 总是高估(实测 33%–73%),并且总是赢得那个「取高者」判断。结果就是引擎读数 长期高于卡片:卡片才 50% 出头,引擎已经越过 80% 阈值 —— 表现为「明明设了 80%,却在 60% 左右就压缩了」。
本插件把 TokenMeter.measure() 的 totalTokens 对齐到卡片那个数,于是
滑块比例与卡片显示的百分比成为同一件事:卡片到 80% 才压。
对齐只做三件最小的事:
- 只影子 meter 实例自身的
measure,不碰原型; - 只替换
totalTokens,nodes原样透传 —— 压缩选区 (selectCompactableRange)与 surface 稳定性校验只读nodes; - 传
requestHeader的推测调用一律不介入(卡片只为已落盘的事实作答)。
contextPressure 是纯事件折叠、不回调 measure,所以不存在递归。投影或
meter 不可用时原样退回引擎读数,绝不影响压缩本身能否发生。
headroomTokens 被强制为一个负值(-1e6)。官方默认是 65536,在 200K 窗口上
会把压力上限压到约 64%;但置 0 并不够——resolveCompactSpec 的第二支是
contextWindow - reservedCompletionTokens - headroomTokens
而 reservedCompletionTokens 取路由模型自己声明的 maxTokens。本机 wk 路由的
buddy:deepseek-v4.1-flash 声明了 maxTokens: 128000,于是这一支变成
200000 - 128000 - 0 = 72000 // 仅 36%
min() 在上面那一支里永远选它,滑块高于 36% 的部分全部失效。日志里的真实触发点
是 72196 和 88293 令牌(36% / 44%),而不是 160000。
负的 headroom 直接把这份预留抵消掉:contextWindow - reserved - (-1e6) > contextWindow,
第二支再也无法成为较小者,比例重新成为唯一决定项。它不是一个预算,只是一次抵消,
所以量级只需超过任何真实 contextWindow 即可;摘要请求自己的输出上限由 maxTokens
单独重述,不受影响。
输出预算兜底
DSH 在请求发出前会调 clampMaxTokensToContext 收缩输出上限:
available = contextWindow - estimateContextTokens(...).tokens - CONTEXT_SAFETY_TOKENS(4096)
max_completion_tokens = min(maxTokens, max(1, available))
estimateContextTokens 在拿不到 provider usage 时退回 4 字符/令牌的启发式,
而真实密度约 5.4–6.9,于是长期高估约 1.3 倍。上下文一涨,available 就被算没,
输出上限被压到几十个令牌,表现为模型刚开口就停、并弹出「已达到输出 token 上限」。
这不是一个点而是一段区间:只要 available < maxTokens 就开始压,在 200K 窗口 +
128000 上限下,真实上下文约 51K 起上限就一路下滑。实测(session 524bf8d2):
| 真实上下文 | estimate |
输出上限产物 | 实际输出 |
|---|---|---|---|
| 148408 | 195857 | 47 | 47 |
| 148408 | 195886 | 18 | 18 |
修在 globalThis.fetch 层——它是唯一位于 clamp 之后的位置,改 model、改配置
或修估算都要跟那个 Math.min 相斗,而这里改的是最终离开进程的字节。判定线是
clamp 的产物本身(<= 16384,等价于估算口径超过窗口 89%),命中的字段抬回
outputFloorTokens(跟随压缩器实际的 maxTokens,默认 65536)。正常配置值
(65536 / 128000 / 256000)远高于判定线,不受影响。
压缩记录
compaction-basic 每压缩一次都会往会话里写三个事件:
compaction/start → compaction/summary → compaction/end
host 半监听 host 平面的 session/event,在 start 时量一次「压缩前」,压缩
结束后量一次「压缩后」,连同 summary 里的 shadowedSeqs(折叠了几条消息)
与 shadowedTokenCount 一起落盘。因为读数走的是上面已对齐卡片的 measure(),
日志里的百分比与卡片当时显示的百分比一致。卡片里每条记录显示:
10/04 21:05 · 第 7 轮
~160K (80%) → ~42K (21%)
折叠 12 条消息 · 省下 ~118K
「压缩后」不是取 compaction/end 那一刻,而是等下一次 provider 回报 usage 之后
再定稿:surface 替换发生在 usage 刷新之前,此时投影仍持有压缩前的 surface 印记,
projectedTokens 会被夹到接近 0(实测 end 时 1%,一个 usage 采样之后才是真实
的 17%)。end 时的读数只作为「会话到此为止」的兜底。
压缩失败(compaction/end 带 error)也会记录,并把失败原因标红显示。日志
按会话过滤:面板是 portal 到 document.body 的,与会话根节点分离,所以靠
「当前展开的那个 trigger」回溯它所在的 [data-conversation-session] 拿会话 id。
tokenMeter 取不到时(例如引擎尚未挂载),记录仍会写入,只是前后读数显示为
「未知」——量不出来不该影响压缩本身。
安装前的历史压缩
插件的日志只从装载那一刻开始记。但三个压缩事件是持久化在会话日志里的,所以 面板首次打开某个会话时会回填该会话此前的每一次压缩,行首标一个「历史」小标签 以示区别。
回填不是近似:measure() 与 contextPressure 投影都是对事件列表的纯折叠,
把会话事件截断到某个历史 seq 重放,得到的正是卡片当时显示的那个数。因此历史行
的百分比与当时的卡片读数一致。
回填按 compactionId 与已有记录去重,可重复执行;已由实时路径记录过的压缩保持
其「实时」身份,不会被历史行覆盖。
为什么不是改写官方文件
桌面版 runtime 封在只读的 app.asar 里,条目带 SHA256 integrity,
改写官方库文件无效也不安全。本插件是纯 cordis 插件:
- host 半(
lib/index.js)在运行时替换已挂载压缩引擎实例上的config引用。引擎每次判定都重新读this.config,所以下一次agent/pre-step就按新阈值决策。 - 官方把
compaction挂在isolate组里,组外ctx.get("compaction")解析 不到实例;但isolate只拦截上下文代理取值,fiber 自身的store是普通对象, 因此 host 半遍历ctx.registry的 fiber store 拿到全部存活引擎(已用真实 cordis 验证)。 - client 半(
lib/client.js)把控件追加进卡片面板。面板内容写死在dsh-client-ui-conversation的 bundle 里、内部没有 slot,所以用 DOM 增强, 并以MutationObserver兜底重挂。
安装
dsh plugin --profile desktop add dsh-compaction-threshold
或手动:把包放进 $DSH_HOME/profiles/<profile>/node_modules/,并在该
profile 的 cordis.patch.yml 里加一行
- insert:
- id: compaction-threshold
name: 'dsh-compaction-threshold'
- insert: 行不按 id 去重,不要重复挂载,否则插件会注册两次。
状态文件
- 阈值:
$DSH_HOME/compaction-threshold.json,跨重启保留。 - 压缩记录:
$DSH_HOME/compaction-log.json,最多保留最近 200 条。
HTTP 接口
/compaction-threshold/api/state
GET→{ ok, value: { thresholdRatio, minRatio, maxRatio, defaultRatio, engines } }POST{ thresholdRatio }→ 持久化并立即应用到所有存活引擎
/compaction-threshold/api/log
GET→{ ok, value: { entries, total, limit } },最新在前GET ?session=<id>→ 只返回该会话的记录
仅接受 loopback(或 webRuntime.trustedHosts 中配置的 authority)且带同源
标记的请求。