@local/dsh-compaction-policy
这个 bundle 在 Web 端 设置 → 模型 的每个提供方卡片里,为每个模型提供自动压缩参数。它们与原生模型参数(上下文窗口 / 最大输出 / 输入类型)同处一张卡片、内联展示,以一行可折叠标题呈现,默认收起,点开才显示四个输入框。
| 设置项 | 作用 | 写入压缩引擎的字段 |
|---|
| 压缩触发余量 tokens | 输出预留之外的额外压力余量;触发点 = min(W × 80%, W − O − B) | modelPolicies[].headroomTokens(并同时写一个显式 maxTokens,见下) |
| 压缩保留比例 % | 压缩时逐字保留多少近期内容,按 W − O 的比例计 | modelPolicies[].retainRatio |
| 压缩保留 tokens | 同上,按绝对 token 数计 | modelPolicies[].retainTokens |
| 压缩引擎最大输出 tokens | 限制“生成压缩摘要”那次模型调用的输出上限 | modelPolicies[].maxTokens |
四项都可留空:留空 = 跟随 DSH 默认。UI 上的“已覆盖 / 恢复默认”标记直接来自用户层配置,每个模型下面还会显示一行预估:→ 触发点约 71%(窗口 128000),保留近期约 16480 tokens。
两个保留项是引擎自己的两种互斥形式(比例 / 绝对量),所以只能填一个:写入其中一个会在同一次设置变更里 unset 另一个。
推理等级(reasoningEfforts / 路由默认 reasoning)曾由本插件提供,DSH 现已内置,因此本插件只保留压缩策略。
为什么是余量而不是百分比
引擎的触发点是 threshold = min(W × thresholdRatio, W − O − headroomTokens):两者谁小谁说了算。在容量紧张的路由上,百分比是被遮蔽的那个,调它没有任何效果。所以页面只暴露真正起作用的容量侧(B)与摘要上限;百分比交回引擎默认的 80%。B 越小 → 触发越晚(越接近窗口上限)。
为什么需要保留量
引擎每压一次,都会把最近的 retain = floor((W − O) × retainRatio) 个 token 原文留下,只压更早的部分;默认 retainRatio 是 0.16。保留量调小 → 单次能回收更多 → 触发次数自然下降。
只能往小调:引擎在模型首次使用时校验 retainTokens < thresholdTokens,保留量顶到触发点会逐步报错(客户端写入前就会拦住)。
值存在哪里
自动压缩存在本插件自己的 volatile 配置 里,命名空间就是这个 profile 条目 id compaction-policy:
- id: compaction-policy
name: '@local/dsh-compaction-policy'
config:
enabled: true
models:
arkcli-agent-plan/deepseek-v4-1-flash-260910:
headroomTokens: 16384
maxOutputTokens: 16384
写路径:ctx.configForms.get('compaction-policy') → remote.settings.mutate → 宿主 SettingsForms.mutate → configEditor.edit → 原子改写 profile 的 cordis.patch.yml。因为字段是 volatile,Loader 会把变更提交到运行中的插件 fiber 并触发 loader/volatile-update,插件随即重新应用策略。
两个护栏(客户端写入前就拦住)
| 条件 | 后果 | 处理 |
|---|
O + B ≥ W | 引擎抛 leaving no pressure budget | 拒绝写入并提示 |
B > (W − O) − 当前保留量 | 触发点被压到保留预算之下,引擎抛 retainTokens … must be less than threshold tokens | 拒绝写入并提示 |
| 保留量 ≥ 触发点 | 同上 | 拒绝写入并提示 |
其中 W/O 直接取自模型卡片里的「上下文窗口 / 最大输出」。模型没填这两项时无法预估,界面显示「无法预估触发点」而不是猜一个值。
宿主侧如何生效
@deepseek-ai/dsh-compaction-basic 在构造时 resolveConfig() 并 deepFreeze() 结果,且挂在当前 agent preset 的隔离域里。本插件不接管/不替换压缩服务,而是:
- 定位现存引擎实例(
ctx → root → ctx.loader.entries() → ctx.registry 各 fiber),整体替换它的 config 对象;
- 保留原有全部字段,只重建
modelPolicies(保留别人的条目,覆盖我们负责的 provider/model);
- 引擎晚于本插件激活时,在
internal/plugin(150ms 节流)与 agent/pre-step 边界持续重试,接管后只在引擎自身插件生命周期事件上重新确认。
每次应用都会写一行 [compaction-policy] ... 到宿主日志。
界面位置
settings.models.provider-card 插槽——即 模型设置页每个提供方卡片内部(就是该提供方的模型行所在的同一张卡片)。一个卡片里内联渲染「按模型压缩策略」:一行标题 + 状态徽标(已覆盖 / 跟随默认)+ 右侧 ▾ 箭头,默认折叠;点开后才是每个模型的四个数字输入框。留空提交时写 unset,填写数字则写 set,保留量的两种形式在同一个 mutation 里“设一个、清另一个”。
折叠用的是原生 <details> / <summary>:没有独立边框和背景,视觉上就是卡片里多一行文字;箭头靠 CSS ::after 切换(▾ / ▴),不依赖脚本状态,刷新后仍是收起的。
因为同一个 settings 命名空间下可以挂多个 provider,目录按 settingsNs + settingsPath 索引,每个卡片只显示自己的 models[]。
安装 / 卸载
标准第三方 bundle:包内 package.json 声明 dsh.bundle.patch,由 cordis.patch.yml 往 Loader 里插一条 compaction-policy 行。
Web 界面:设置 → 插件 → 安装 bundle,目标填源码目录或导出的 .tgz。等价于 Host 侧的 pluginManager.installBundle(spec)。
安装后 profile 的 cordis.patch.yml 里只剩这个插件自己的设置覆盖行(- id: compaction-policy + config.models.*),插入行由 bundle 提供。
卸载:设置 → 插件 → 卸载,或把 dsh.profile.bundles 里的这一项去掉。
Node 会按模块 URL 缓存模块,包括“导入失败”的记录:替换/升级已安装的包要重启宿主才会加载新的 JavaScript。只改客户端半边(lib/client.js)时还要重启宿主并刷新页面。
打包 / 分发
npm pack --pack-destination <输出目录> # → local-dsh-compaction-policy-1.2.1.tgz
tgz 里只有 package.json / cordis.patch.yml / lib/** / locale/*.json / icon.svg / README.md,无依赖、无构建脚本,install_bundle 可直接吃。
开发
node --check lib/index.js # 宿主半边语法
node --check lib/client.js # 客户端半边语法
node test/client-harness.mjs # 客户端半边离线 harness
node --import ./test/register-resolver.mjs test/host-harness.mjs # 宿主半边离线 harness
node --import ./test/register-resolver.mjs test/volatile-shape.mjs # volatile 引用链路
node test/verify-live.mjs # 对着运行中的宿主做一次只读核验
test/schemastery-resolver.mjs 把 @deepseek-ai/schemastery / @deepseek-ai/cosmokit 指到 profile 实际随附的那份;这两个包在 DSH 的 app.asar 里,可用 DSH_DEPS 环境变量指定解包目录。
兼容性
- 目标运行时是 DSH 0.2.1-rc.3(
DSH Desktop 自带那份)。用到的客户端 API(slots / locale / configForms / remote.settings / createSnapshotStore)与 settings.models.provider-card 槽在 rc.3 中均存在。
- 从 profile 自己的依赖树解析
@deepseek-ai/schemastery,宿主校验走 ~standard 接口(跨副本安全);缺 .volatile() 修饰符时按同样语义(extra('volatile', true))就地补上。
行为细节
- 余量一定带显式摘要上限:引擎在“策略设了
headroomTokens、而两级都没设 maxTokens”时会把这个余量当作该模型的摘要上限。所以插件写入余量时总会带上一个 maxTokens(优先用你填的值,没填就继承引擎当前的摘要上限)。
- 越界值不钳制:
headroomTokens 0-4194304、maxOutputTokens ≥ 256、保留比例 1-95、保留 tokens 1-4194304,否则该项被忽略并在宿主日志里报告(UI 侧由三条护栏先行拦截)。
- 插件记住它写过的所有路由(
owned):某个模型的设置被清空、或整个开关被关掉时,引擎里对应的 modelPolicies 条目会被一并移除,不影响别人的条目。
- 同值重复事件不会重复改写引擎 config,避免噪声日志。
- 只做按模型(路由
provider/model)粒度;不写入会话级状态。