npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-command-context-trim

v0.6.6

Published

Model-free /trim for DeepSeek Harness — drop the oldest, least valuable span of context on demand (the `/trim` command) or automatically when a request hits the model's context wall, without any model call.

Downloads

5,514

Readme

dsh-command-context-trim

English →

给 DeepSeek Harness 增加一个不调用任何模型的 /trim: 在真正溢出之前,把对话里最旧、最不重要的一段上下文裁掉,让会话能切到窗口更小的模型上继续跑;同时可以按路由 调优 DSH 自己的 compaction 触发点与工具结果裁剪阈值。

本文是 README.md 的中文同步版:同结构、同命令、同默认值、同公式、同验证事实(长段落做了压缩翻译)。 版本号见上方 synced-with-readme 标记,test/docs-sync.test.js 会检查它与 package.json 一致、且关键内容不漂移。

为什么需要它

把长会话从大窗口的云端模型切到小窗口的本地模型时,下一个请求就会超过新模型的窗口。既有做法是切换前先 /compact——而这正是痛点所在:compaction 是摘要,它的摘要请求必须既能塞进(已经变小的)窗口,又要容纳那段 正被摘要的内容;窗口越小,摘要越容易失败,或摘要质量越差(要压缩的信息越多)。/trim 换了一条路:先把最旧的 上下文裁掉,不调用模型、不产生摘要,于是后续 /compact 更便宜、也更容易成功。

它给本地模型带来什么

DSH 的压缩触发点是 min(thresholdRatio × contextWindow, contextWindow − R − headroom),其中 R 是该路由自己的输出预留,而 headroom 固定为 65536,与窗口大小无关。在百万 token 的云路由上这几乎无所谓;在本地卡上它决定一切:

  • 131072 窗口:固定 headroom 让触发点落在 37.5 %,而不是比例想要的 80 %⇒压缩来得又早又频繁;
  • 本插件把该路由的 headroom 设为 0(比例重新说了算)⇒触发点回到 104857(80 %);
  • 效果就是更少、更晚的压缩:同样一段工作,压缩次数下降、每次压缩要处理的量更小。

preset 路线是上锁的,而且必须保持可解析

会话一旦开始,它的 preset 就固定了(agent-preset/locked),fork 也继承父会话的 preset 并被同样上锁: buildForkSeed 会把父会话的 turn/start 事件复制进子会话(dsh-session/lib/index.js:884-893),而 fork 本身要求 父会话已有一个完成的 turn,于是子会话的 turnBoundary.lastTurn > 0⇒同样换不了 preset。因此:

  • 想要新参数:让进程重启即可——会话的 preset id 是粘住的(它来自会话的 agentPreset 投影,而 assertPresetUnchanged 会拒绝另一个 id),但该 id 背后的定义在每次 adopt 时都会被重新解析: createOrAdopt 与 resumeObserved 都会调用 composeAgent(presetForObservation(observation)),再把新组合交给 agents.resume,而 retain(id) 返回的是当前定义所激活的那一代。所以"改 preset 时没有在运行"的会话, 只要 resume 就会吃到调优——不需要 fork。

只有一种情形需要 fork:改定义时会话仍在运行。活着的 agent 绑在它当初组合的那一代上,那份绑定会让旧 realm 保持 存活,所以改动到不了它⇒这时才 fork(或等下一次重启)。fork 是新 session id、且自身也被锁(继承父会话的 turn/start)——但这对它无害,因为它已经在跑调优后的值。

该用哪条路线

两条路线都能设置全部压缩参数——触发点、pruner、以及摘要模型(summarizationProvider / summarizationModel 就是 compaction-basic 上的普通键,可用 modelPolicies 按路由设置)。优先 profile 平面:它每次启动都能重新调优、 且不涉及 preset 的持久性负担。

| 路线 | 覆盖 | |---|---| | profile 平面(headless / tui:autoTuneCompaction、/context-tune tune)| 自动路径确实会触发:host 平面拥有 agent| | preset 平面(web:/context-tune preset inplace + check + fork)| 手动:会话 agent 在各自 preset 域里创建,其生命周期事件到不了 host 平面(已实测)|

安装

# 从 npm
dsh plugin --profile web add dsh-command-context-trim

# 从 checkout
dsh plugin --profile web add file:/path/to/dsh-command-context-trim

用法

  /trim                                 # 按当前模型窗口裁剪
  /trim check                           # 只报告计划,不改动
  /trim 32k                             # 指定 32768 token 预算
  /trim 40000                           # 指定 40000 token 预算
  /trim provider:model                  # 按另一条路由的窗口计算
  /context-tune tune [check]            # 用当前活跃路由重新调优本进程的 compaction 行(profile 平面)
  /context-tune preset [check|list|default|inplace|p:m]
  /context-tune rescue <id> [--from <donor>] [--untuned]
  /context-tune reset [check] [preset-id] # 去掉本插件写进 preset 的调优行

想撤销调优用 /context-tune reset:它删掉本插件写进 profile 补丁的那些标记块,于是每个 preset 回到它 自己的定义。不带参数就是列出并删除全部;带 preset id 只删那一个;加 check 只报告不写。它靠标记块精确认出 哪些行是自己写的(不靠匹配描述文字),所以别的工具写的块会被列出来但不动。preset 锁依然有效——删除同样只对 新会话、或重启之后的会话生效。

/context-tune preset 的完整形式:

/context-tune preset              # 依据 profile 里配置的路由生成并落地一个调优 preset
/context-tune preset inplace      # 覆盖**基础 preset 自己的 id**,而不是新增一个 id(见下)
/context-tune preset check        # 打印将生成的行 + 路由覆盖率,不写任何东西
/context-tune preset list         # 会覆盖哪些路由、各自拿到什么触发点
/context-tune preset default      # 同时把它设为**新会话**的默认 preset
/context-tune preset p:m          # 本次用 p:m 作为摘要路由
/context-tune rescue <id>         # 用同 id 重建丢失的 preset(--from <donor>、--untuned)

/context-tune preset check 是过期信号:它统计当前 preset 覆盖了多少条路由,并对消息预算 ≤ 64K 却未被覆盖的路由 给出明确告警——那是唯一一种"继承顶层调优值反而打开压力触发"的情况。新增模型后跑一次 check,报缺口就重跑 /context-tune preset inplace:

coverage: 4 route(s) configured, 3 covered by this preset, 1 not covered.
⚠ uncovered SMALL route bonsai-8gb//models/mtp-lean.gguf (40960 − 8192 ≤ 65536): without a policy it inherits the
  tuned top level, which enables the pressure trigger on a route where that measured slower. Re-run /context-tune preset inplace…

工作原理

一次 trim 是两次同步相邻的追加:先写入一条替换消息(带 plugin:dsh-command-context-trim 来源标记), 再写入 token meter 的测量事件;两者之间没有 await,所以模型不会看到中间态。裁剪按从最旧开始的优先级进行, 最后一条消息降级为"尽量不裁",用户指令始终受保护。

上下文墙上的自动裁剪

autoTrim(默认开启)让同样的免模型裁剪在无人敲命令时发生:一个 prepend 的 agent/request-error 监听器 响应 CONTEXT_WINDOW_EXCEEDED,裁剪,然后请循环重试。它不在普通压缩时触发。

请求失败(上下文墙)
  → 裁剪并重试(最多 maxAutoTrimRetries 次)
  → 仍失败⇒交给 DSH 自己的压缩路径

受保护的内容

用户指令(最后一条消息)尽量不裁;protectHeadNodes(默认 1)保护开头的节点;保留尾部(retainRatio / retainTokens / minTailTokens)逐字保留最近上下文;allowTailTrim 控制最后手段的层级。

兼容性

该装哪个插件版本

这里没有 peer 依赖来强制这条规则,所以只能写在这里、需要人去读:dsh 0.1.5 只能配本插件的 0.1.x。 0.2.0 起直接装最新版。0.1.7 和 0.2.0 差别不大,同一个构建两边都能用。

| dsh | 装哪个 | 原因 | |---|---|---| | 0.1.2-rc.1 | 0.1.x | devDependency 里钉死的下限 | | 0.1.5-rc.2 / 0.1.5-rc.3 | 只能用 0.1.x | 设置卡片需要 configForms 和 schemastery 的 .volatile(),0.1.5 两样都没有 | | 0.1.7-rc.2 | 最新版(0.6.x) | 到 0.2.0 为止,本插件做的事都没变 | | 0.2.0-rc.2 | 最新版(0.6.x) | 第一条能在这条线上正确保存卡片写入的版本 |

0.1.5 被单独划出来而不是"降级可用":它早于卡片契约的两半。它的 web 宿主不提供 configForms 服务,没有表单可渲染、 也就无处可存;它的 schemastery 早于 .volatile(),而那是把字段放上卡片的唯一条件。插件在那里仍然能加载、/trim 仍然能用——客户端半边会检测到缺失的服务然后什么都不注册,而不是在页面里抛错——所以失败形态是"卡片悄悄不出现", 而不是启动报错。这是最不该让人去猜的一种失败,所以写在这里。

0.2.0 线上真正变化的是保存路径。0.1.7 时代的构建会把布尔值按字符串 "true" 暂存,.volatile() 过去会放行; 0.2.0 按 schema 校验这次操作并带内拒绝——没有 HTTP 错误,console 也没有输出。0.5.0 修好了,它写的是正确类型的值。 所以在拆分两行和改名之前,0.2.0 就已经需要 0.5.0 以上了。

session format v4 下的标记来源。 替换消息携带来源 kind plugin:dsh-command-context-trim——v4 要求的 plugin:<plugin id> 形状——因为 0.1.7 的准入路径会拒绝已退休的 {kind: 'plugin'} 包装,报 format v4 message requires a producer-owned source kind。

设置卡片

Plugins 页面上的卡片显示的是生效值,而不是"是否覆盖":部署没设过的字段会显示插件真正会用到的默认值——因为一片空白 的控件正是"我的新模型为什么还没调优"变成无解问题的原因。卡片分两个分区:压缩(触发阈值、摘要路由、两个自动调优 开关)与裁剪(工具结果裁剪阈值),而裁剪阈值是"选值下拉 + 数值框":只有选 Custom 时数值框才出现。

| 选值 | 落到设置面的值 | |---|---| | Disabled | 0(不动 pruner)| | Auto | auto(按路由窗口推导)| | Custom | 你填进数值框的字符数 |

运行时自动调优压缩阈值 上以粗体写着唯一要紧的前提:仅限 headless profile——web profile 需使用 /context-tune preset 命令来调优 (web 里会话 agent 在各自 preset 域内创建,其生命周期事件到不了 host 平面)。开关用的是壳子自带的 Switch 原语;选值框是 原生 <select>(原语没有 Select)。

配置

在 profile 补丁的 context-trim 行上覆盖(随包 cordis.patch.yml 列出了完整默认值):

| 键 | 默认值 | 含义 | |---|---|---| | targetRatio | 0.9 | budget = floor((contextWindow - reserveOutputTokens) * targetRatio) | | reserveOutputTokens | 8192 | 留给模型自己回复的输出空间。它与服务端自己的 maxTokens 之和要落在真实窗口内:一个 32k 的服务若 maxTokens: 16384,即使提示词都装得下,提示词 + 输出也会耗尽窗口 | | retainRatio / retainTokens | 0.16 /—| 逐字保留的最近尾部(两种形式互斥)| | minTailTokens | 2048 | 该尾部的绝对下限 | | protectHeadNodes | 1 | 永不裁剪的开头节点数 | | allowTailTrim | true | 启用层级 2–3(伸进保留尾部;最后手段可含末条消息)。false 表示只做层级 1,保留尾部成为硬边界 | | markerSlackTokens | 64 | 加在计价标记上的余量,保证裁剪后的请求仍在预算内 | | autoTrim | true | 在 CONTEXT_WINDOW_EXCEEDED 时自动裁剪;绝不在普通压缩时触发 | | maxAutoTrimRetries | 3 | 每次溢出允许的自动裁剪次数,之后交给压缩 | | autoTrimShrink | 0.5 | 重复溢出后,按被拒请求的这个比例重新取预算目标(声明窗口有误时的几何下降)| | preferInPlacePrune | true | 规划任何区间之前,先就地瘦身过大的工具结果;能用到官方 pruner 时就用它 | | compactionTargetRatio | 0.8 | /context-tune preset 写进 preset 的触发比例(只塑造 preset,从不影响本插件自己的裁剪)| | compactionRoute | 未设置 | 生成 preset 的摘要调用所用的可选 {provider, model} | | prunerThresholdChars | auto(DSH_TRIM_PRUNER)| 工具结果裁剪阈值。auto 按路由派生为 max(8192, min(32768, 2 × (contextWindow − maxTokens)))(各路由取最小),小窗口下一次整文件读取因此不会被裁掉(DSH 的 stock 值是 8192)。整数覆盖它;0 表示退出、保持不动。与 compaction 行同一平面,因此同样的可达性规则适用——web profile 把 pruner 放在每个会话的 preset 里,插件会报告这一点而不是写入。小窗口上这是最关键的杠杆;压力触发已启用时它会放大提示词,那里要三思 | | autoTuneCompaction | false | 重新调优本进程的 compaction 行(仅在 compaction 位于 profile 平面时)。web 或生产 profile 应该设置这一项(通过设置卡片或补丁层);覆盖它的 DSH_TRIM_AUTO_TUNE 环境变量只用于自动化/CI | | pruneThresholdChars / pruneHeadChars / pruneTailChars | 8192 / 4096 / 1024 | 就地瘦身的预算,镜像 DSH 自己的 pruner 默认值 |

环境变量(供自动化/CI 覆盖对应配置项):DSH_TRIM_AUTO_TUNE、DSH_TRIM_PRUNER。

权限与失败边界

插件触碰了什么,写在这里以免审阅者靠猜:

  • 只写两条会话事件(替换消息 + 测量),不调用模型;
  • 读取 profile 补丁、会话记录与 token meter;
  • /context-tune preset 与 /context-tune rescue 会写 profile 补丁(写前备份为 <patch>.bak-trim-preset);
  • 自动路径只在 profile 平面可达时写入;不可达时只报告,不静默失败。

边界

  • 它是丢弃内容,不是摘要。 被裁掉的细节从模型视野里消失(仍在日志里)。想要摘要就用 /compact;两者互补 (先 /trim 会让之后的 /compact 更便宜、更可能成功)。
  • 最后一条消息尽量不裁(见 allowTailTrim):保护用户指令优先于塞进预算。
  • /trim 不改变 preset,preset 也不能在会话中途更换——要新参数就 fork 或新开会话。

调优压缩触发点(/context-tune preset)

DSH 依据 thresholdTokens = min(contextWindow × thresholdRatio, messageBudget − headroomTokens) 决定何时压缩, headroomTokens 默认 65536。在约 370k 以下的任何窗口上,决定触发点的是这个默认值而不是比例:131072 的路由在 37.5 % 就压缩,而不是 80 %。单靠比例改不动它,而 compaction 的策略是在 preset isolate realm 里组合时读取的, 插件无法在运行时改动它。因此 /context-tune preset 写的是一个 preset:

# >>> dsh-command-context-trim: preset-standard (generated; delete this block to drop the preset) >>>
- id: preset-standard
  name: '@deepseek-ai/dsh-agent-preset'
  config:
    id: standard
    plugins:
      - id: compaction-basic
        name: '@deepseek-ai/dsh-compaction-basic'
        config:
          thresholdRatio: 0.8
          headroomTokens: 0
          modelPolicies:
            - provider: b70-sycl
              model: /models/q.gguf
              thresholdRatio: 0.8
              headroomTokens: 0
# <<< dsh-command-context-trim: preset-standard <<<

inplace 写的是裸覆盖行(- id: preset-<base> + 完整 config:):补丁层按 id 整块替换该行的 config, 所以它顶掉随包 preset 的插件清单,新会话继续用同一个 preset id、不需要任何人去挑。代价:随包那份每次升级都会 被覆盖,而这一行会遮蔽它,直到你删掉这段标记块。

运行时重新调优(/context-tune tune)

在 compaction 位于 profile 平面的地方——任何基于 dsh-base 的组合,即 headless 与 tui——它的阈值就是某一行 的普通 config,cordis 通过重启 fiber 来应用 config 变更(Fiber.update() 解析新配置并调用 restart(),即 dispose 加一次全新 apply)。所以 /context-tune tune 是有效的:它改的是那一行。web profile 里 compaction 在会话的 preset 域内, host 平面碰不到它——插件会报告这一点(/context-tune preset 才是那里的答案)。

自动化调优阈值(headless 运行)

headless profile 自己组合 compaction——它的树在 profile 平面上带 compaction-basic、command-compact 与 tool-result-pruner,而且它从不解析会话 preset(只有 session API、web 客户端与 agent-preset 行会)。 实测(干净 DSH_HOME):在路由上声明 contextWindow 并设 DSH_TRIM_AUTO_TUNE=1 后,该行的 thresholdRatio/headroomTokens 与 pruner 的 thresholdChars 都会被写成派生值,且带 context-trim/tuned 记录 可审计。web 侧对应的是手动流程(见该用哪条路线)。

读会话日志

compaction/prune 有两个生产者,只有一个是本插件:本插件在就地瘦身时写入自己的记录,DSH 官方的 tool-result-pruner 也写同名事件。区分办法是看事件的来源与伴随字段;本插件还会写 context-trim/tuned 记录, 里面带有它算出的 thresholdChars(这也是我们验证 Bonsai 2 那次"17 → 2"的依据)。

开发

它放在 fixtures/ 而不是 test/ 下是有意的:node --test 会执行 test/ 下的每个 JavaScript 文件,把服务器放在那里 会让测试挂住。

端到端溢出检查(不需要模型)

fixtures/mock-overflow-server.mjs 是一个有状态的 OpenAI 兼容端点:它强制一个比告诉 harness 的 contextWindow 更低的真实上限,并且对前 TOOL_STEPS 个请求回以工具调用,于是一个 turn 会不断循环、越过真实上限——这就是 上下文墙,且不需要切换模型。

前端卡片开发工作流(src/client/ → lib/client.js)

设置页面的前端卡片源码按模块维护在 src/client/ 目录下(包含常量、i18n 多语言、辅助函数、表单控制器、卡片组件及生命周期注入)。

切勿直接修改 lib/client.js。修改前端代码后,需运行:

npm run build:client

测试套件(test/client-bundle.test.js)会在 npm test 中对 lib/client.js 与 src/client/ 组装产物进行逐字节一致性校验,任何未构建的改动或直接修改打包产物的行为都会导致测试失败。

验证状态

见英文 README.md 的 Verification status 表(149 个测试、隔离实例验证、0.3.9–0.4.4 的各项证据,以及 Bonsai 2 headless 的前后对比:thresholdChars: 32768、compaction/prune 17 → 2)。中文版不重复该表以免两处漂移; test/docs-sync.test.js 会检查本节仍然指向它。

许可证

MIT


计算细节

本插件算出的每个数字,以及它从哪个默认值出发。token↔字符换算用 DSH 自己的 CHARS_PER_TOKEN = 4 (@deepseek-ai/dsh-token-meter)。

1. 裁剪预算(/trim 把请求面塞进多少)

usable  = contextWindow − reserveOutputTokens          # reserveOutputTokens: 8192
budget  = max(1, floor(usable × targetRatio))          # targetRatio: 0.9
retain  = max(minTailTokens,                           # minTailTokens: 2048
              floor(contextWindow × retainRatio))      # retainRatio: 0.16

2. 规划器在比较什么

surfaceTokens  = Σ node.heuristicTokens                       # token meter 给每个请求面节点的定价
envelopeTokens = measurement.totalTokens − measurement.surfaceTokens   # 工具 schema 等固定请求数据
totalTokens    = envelopeTokens + surfaceTokens

3. 压缩触发点(自动调优与 /context-tune preset 写入的值)

镜像 @deepseek-ai/dsh-compaction-basic 的 resolveCompactSpec(按 dsh 0.1.7-rc.2 读取):

reservedCompletion = 该路由请求的 maxTokens      # 输出预留,从 request/header 读
messageBudget      = contextWindow − reservedCompletion
headroom           = headroomTokens             # stock 65536;我们写 0
pressureBudget     = messageBudget − headroom
thresholdTokens    = floor(min(contextWindow × thresholdRatio, pressureBudget))

4. 工具结果裁剪阈值(0.3.5 起由自动调优派生)

prunerThresholdChars = max(8192, min(32768, 2 × (contextWindow − maxTokens)))

即一条工具结果最多可占该路由消息预算的一半,上限 32 KB、下限为 DSH 的 stock 8192。各路由取最小,因此 最严格的那条说了算。

5. 样例

| 路由 | stock 触发点 | 调优后 | |---|---|---| | Bonsai 40960 / 8192 | 无(32384 ≤ 65536,无主动触发)| 32768(80 %)| | Bonsai 40960 / 16384 | 无(24576 ≤ 65536,无主动触发)| 24576(60 %)| | 131072 / 16384 | 49152(37.5 %)| 104857(80 %)| | 1000000 / 256000 | 678464(67.8 %)| 744000(74.4 %)|

前两行以前会被跳过、保持 stock,现在是照常调优:没有主动触发点意味着只有真的撞墙才压缩,而那不是罕见情况而是 系统性缺口——没有任何预定时刻,任务能不能恢复取决于它自己的上下文恰好落在哪里。「少压缩」因此不是省事:一次 209–372 秒的等待是有界的代价,任务卡住不是。

6. 自动裁剪

autoTrim: true 通过缩小预算来重试失败的请求: nextCeiling = max(1, floor(failingTotal × (1 − autoTrimShrink))),autoTrimShrink: 0.5,最多 maxAutoTrimRetries: 3 次,之后放弃并报告它测到的东西。