🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

令牌节流器 xuzihao66/dsh-token-frugal

DeepSeek Harness
xuzihao6617 天前有提交已提供中文文档扫描:含敏感能力

削减长时间运行的 DeepSeek Harness 会话的输入 token…

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add xuzihao66/dsh-token-frugal
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · model
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-token-frugal(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 04:19:56

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 34 处,展示前 34 条)
  • 读取本地文件
    index.js:318
    if (path === undefined || !existsSync(path)) return '';
  • 读取本地文件
    index.js:319
    if (statSync(path).size > MEMO_READ_LIMIT) return readFileSync(path, 'utf8').slice(-MEMO_READ_LIMIT);
  • 读取本地文件
    index.js:319
    if (statSync(path).size > MEMO_READ_LIMIT) return readFileSync(path, 'utf8').slice(-MEMO_READ_LIMIT);
  • 读取本地文件
    index.js:320
    return readFileSync(path, 'utf8');
  • 写入本地文件
    index.js:337
    appendFileSync(path, `${block.trim()}\n\n`);
  • 读取本地文件
    index.js:645
    if (!existsSync(memoPath)) {
  • 写入本地文件
    index.js:646
    mkdirSync(dirname(memoPath), { recursive: true });
  • 写入本地文件
    index.js:649
    writeFileSync(memoPath, `${appendBlock('', sessionHeadingBlock(sessionId, cwd))}\n`);
  • 读取本地文件
    index.js:650
    } else if (!readFileSync(memoPath, 'utf8').includes(`## ${sessionId} `)) {
  • 写入本地文件
    index.js:651
    appendFileSync(memoPath, `\n${sessionHeadingBlock(sessionId, cwd)}\n\n`);
  • 发起网络请求
    client.js:430
    const response = await fetch(ENDPOINT, { headers: { accept: 'application/json' } });
  • 发起网络请求
    client.js:453
    const response = await fetch(ENDPOINT, {
  • 发起网络请求
    client.js:705
    const response = await fetch(STATS_ENDPOINT, { headers: { accept: 'application/json' } });
  • 发起网络请求
    client.js:725
    const response = await fetch(STATS_ENDPOINT, {
  • 读取本地文件
    tools/measure-tokens.mjs:99
    return decodeLogBuffer(readFileSync(path));
  • 读取本地文件
    tools/measure-tokens.mjs:127
    try { info = statSync(path); } catch { return; }
  • 读取本地文件
    tools/measure-tokens.mjs:133
    for (const entry of readdirSync(path)) walk(join(path, entry), depth + 1);
  • 读取本地文件
    tools/measure-tokens.mjs:153
    try { text = readFileSync(path, 'utf8'); } catch { return undefined; }
  • 读取本地文件
    tools/measure-tokens.mjs:246
    apply(JSON.parse(readFileSync(options.config, 'utf8')), options.config);
  • 写入本地文件
    tools/measure-tokens.mjs:780
    writeFileSync(options.json, `${JSON.stringify({
  • 读取本地文件
    lib/modes.js:129
    return readdirSync(profiles, { withFileTypes: true })
  • 读取本地文件
    lib/modes.js:132
    .filter((dir) => existsSync(join(dir, 'node_modules', PACKAGE_NAME))
  • 读取本地文件
    lib/modes.js:133
    || existsSync(join(dir, STATE_FILE))
  • 读取本地文件
    lib/modes.js:134
    || existsSync(join(dir, STATS_FILE)));
  • 读取本地文件
    lib/modes.js:178
    if (!existsSync(path)) return undefined;
  • 读取本地文件
    lib/modes.js:179
    const parsed = JSON.parse(readFileSync(path, 'utf8'));
  • 写入本地文件
    lib/modes.js:209
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    lib/modes.js:210
    writeFileSync(temporary, payload);
  • 写入本地文件
    lib/modes.js:211
    renameSync(temporary, path);
  • 读取本地文件
    lib/stats.js:213
    if (!existsSync(path)) return emptyStats();
  • 读取本地文件
    lib/stats.js:214
    const parsed = JSON.parse(readFileSync(path, 'utf8'));
  • 写入本地文件
    lib/stats.js:236
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    lib/stats.js:239
    writeFileSync(temporary, payload);
  • 写入本地文件
    lib/stats.js:240
    renameSync(temporary, path);
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 压缩工具输出
  • 控制工具目录可见性
  • 附带测量脚本

给 DeepSeek Harness 长会话省下输入 token:在 tools/post-execute 上做源头侧工具输出压缩,再按智能体控制工具目录可见性,附带测量脚本。

README

由 DeepSeek 最新模型翻译生成
dsh-token-frugal 削减长时间运行的 DeepSeek Harness 会话的输入 token 成本,同时不牺牲任何能力。三个扩展点全部位于 Harness 事件流内部,再加一个面板,把每一项节省都交到你手里: | 扩展点 | 作用 | | tools/post-execute | 在每条被接受的工具结果写入日志前压缩它:先做无损的结构感知变换,再做有预算的首尾省略,省略标记会指明恢复路径。 | | agent/created | 通过 ctx.tools.restrict() 在该智能体的作用域上下文里,对单个智能体隐藏配置的工具;循环会把它记录为 tool-removal / tool-addition 块,并在工作区记忆文档中打开本次会话的章节。 | | agent/inbox/claimed | 把新的用户消息与该文档匹配,用 agent.inject() 注入最相关的摘录;这会追加一条 agent/inbox/spliced 事件,而不是改写历史。 | | conversation.composer.dock | 面板:把节省项分成三档——初等(无损)、中等(折叠)、高等(截断与改写)——每项节省一个开关,立即作用于当前会话,并持久化到配置档旁边。
展开完整文档(README 剩余 1 节)
dsh-token-frugal

削减长时间运行的 DeepSeek Harness 会话的输入 token 成本,同时不牺牲任何能力。三个扩展点全部位于 Harness 事件流内部,再加一个面板,把每一项节省都交到你手里:

| 扩展点 | 作用 |
| tools/post-execute | 在每条被接受的工具结果写入日志前压缩它:先做无损的结构感知变换,再做有预算的首尾省略,省略标记会指明恢复路径。 |
| agent/created | 通过 ctx.tools.restrict() 在该智能体的作用域上下文里,对单个智能体隐藏配置的工具;循环会把它记录为 tool-removal / tool-addition 块,并在工作区记忆文档中打开本次会话的章节。 |
| agent/inbox/claimed | 把新的用户消息与该文档匹配,用 agent.inject() 注入最相关的摘录;这会追加一条 agent/inbox/spliced 事件,而不是改写历史。 |
| conversation.composer.dock | 面板:把节省项分成三档——初等(无损)、中等(折叠)、高等(截断与改写)——每项节省一个开关,立即作用于当前会话,并持久化到配置档旁边。 |
| settings.section | 统计页面:记录节省项实际移除了什么,包括本次运行与历史累计,按 Harness 自带的「4 字符折合 1 token」启发式计价。 |

2.0.0 新增 —— 节省模式面板与带召回的工作区记忆; 2.0.4 新增 —— 分档面板与统计页面;见 CHANGELOG.md。1.0.0 的行为与配置键保持不变。

在本机对四个已完成的录制会话实测(149 次模型请求,基线提示词 8.6 M token,主要由 web-fetch、终端和文件读取输出占据):

prompt tokens 8,624,238 -> 5,022,830 41.76% target 40% -> MET
tool-result chars 684,300 -> 149,970 78.1%
cache-eligible 8,309,095 -> 4,841,277 41.74%
cache hit rate 96.35% -> 96.39%

这只是压缩那一半;记忆与召回功能会带来一点有上限的开销(每条匹配消息最多 recall.maxChars,没有匹配时为零),换来的是不必重读对话记录。

节省模式面板

输入框下方有一个胶囊按钮。它显示已开启多少项节省,点开是三档节省项,每项带一个开关:

▾ 省 Token · 已开 6/9 改动立即生效,无需重启。
初等 只清理空白与控制符,不改变任何语义内容 3/3 ▸ 中等 把重复行与递增计数折叠成区间,内容可无损还原 2/2 ▾ [全部关闭] ● 重复行折叠 把连续重复的同一行合并为一行并标注出现次数 [switch] ● 递增计数折叠 把连续递增的计数折叠为「起始-结束」区间 [switch] 高等 截断超长结果并改写模型可见内容,存在信息取舍 1/4 ▸ 完整节省统计:设置 → 省 Token 统计设置文件 C:\Users\you\.dsh\profiles\desktop

各档按选错的代价排序。初等(terminal, columns、json) 在构造上就是无损的:它只删掉空白、颜色和没有含义的排版。中等(repeatedLines、numericRuns)折叠文本自身编码的结构——一串相同的行、一个递增的计数器——所以原文可以从剩下的内容重建。高等(elide、catalogue、memo、recall)要么丢弃内容,要么改变模型看到的东西,每一种都有实实在在的取舍。每个模式都写明它删掉或折叠的到底是什么,并说明是否可逆,因为开关自己的标签就是拨动它之前唯一会读的文档。

卡片是手风琴式:打开一个等级会关掉其他等级,所以面板永远不会高过一个等级。每个等级都带一个一键全选控件。

拨动开关会立刻作用于正在运行的会话——不用重启,也不用改你的配置档:选择会写到配置档旁边的 dsh-token-frugal.state.json。配置档取自 DSH_PROFILE_DIR(或 DSH_HOME 加 DSH_PROFILE),前提是 harness 声明了其中一个;否则就靠发现:那个唯一持有本包或更早的 dsh-token-frugal 状态文件的配置档。应用进程本身不导出这些变量中的任何一个,所以正是这套发现机制让这个文件在正常安装时出现。catalogue 会重新应用到已经存在的智能体,而不只是之后创建的。前提条件缺失的模式(catalogue 配上空的 hiddenTools)会显示为灰色并给出说明,而不是悄悄什么都不做。

它是怎么接线的,因为这部分才需要研究:

- Client 那一半是懒加载模块,通过 window.__ModuleLoader__.load 注册,其 id 等于包名。它用 ctx.slots.inject + ctx.slots.register 挂载到 conversation.composer.dock(面板)和 settings.section(统计页),并在 dsh.client.inject 里声明 @deepseek-ai/dsh-client-runtime 和 @deepseek-ai/dsh-client-ui-slots,因为 slots 包拥有那些目标。
- 两半通过精确的 HTTP 路由通信(bridge.path 及其派生的 /stats 兄弟路由)。这不是偏好:host.call 只对沙箱化的动态包存在,而 Client 服务目录(layout、locale、sessions、slots、theme、timer、uiWorkspace、workspaces)没有面向 Host 的调用面,所以配置档安装的 bundle 需要一个页面能触达的载体。随附的市场插件也是这么解决的。
- 可见文本由 client.js 里的 PANEL_LOCALE 固定为中文:Client 的 locale 服务无法从配置档安装的 bundle 可靠读取,所以跟随它就意味着悄悄回退到英文。英文字典留在同一张表里,所以切回去只改一行。每种颜色都是 --dsw-alias- 主题 token,所以浅色和深色都能正确显示,不需要样式表。

统计页

设置 → 省 Token 统计 会说明节省到底起了什么作用:

累计节省 Token(估算) 5,120 来自被移除的 20,480 个字符(仅统计压缩)

本次进程 历史累计检查过的工具结果 42 检查过的工具结果 310 被重写的结果 35 被重写的结果 288 被截断的结果 12 被截断的结果 97 移除的字符数 12.0k / 40.0k 移除的字符数 88.0k / 240.0k 节省比例 30.0% 节省比例 36.7%

按工具明细(本次进程)工具 Token 被重写 调用次数 节省比例 read 3,000 3 3 100.0% pwsh 1,120 8 12 40.2%

工具目录精简 2 tool(s) hidden · 每次请求约省 900 token 记忆注入次数 1 记忆注入字符数 300 原文留存失败 0

- 计价方式。 Host 那一半保留计数器,并用 Harness 展示上下文用量时的同一套启发式来换算(4 个字符 ≈ 1 token),所以这个数字能和上下文计量表对得上,而不是另一套互不兼容的估算。它只统计压缩:算的是变换移除了多少,不是模型本来就会发多少。
- 两个范围。 当前进程和历史累计并排展示,因为它们回答的是不同问题——「现在有没有生效」和「装它到底值不值」。一个两步操作只清空历史累计那一半;正在运行的进程数据保持真实。
- 工具目录的数字是每次请求的量,不是总量,展示时也这么写:限制生效期间,智能体发出的每次请求都缺那些 schema,所以没法归到某一次请求上。
- 记录会持久化在模式状态旁边(dsh-token-frugal.stats.json),最多每五秒写一次,关闭时再写一次,所以一串工具调用只会写一次。找不到配置档时,历史累计那一半只留在内存里,页面会如实说明,而不是假装总量完整。spillFailures 统计的是全文存不下来的结果:这些情况下裁剪没法给出恢复路径。
- 页面打开期间会跟随记录(每五秒刷新一次),并通过派生的 /stats 路由读取,该路由只接受 {"reset": true} 这一种请求体。

工作区记忆文档

每个工作区一个 markdown 文件,每段对话一个 ## session 小节,每条记录一个 ### 块:

dsh-token-frugal session memory

session-815c4306818a — 2026-10-08 06:11 UTC · C:\work\project

user · 2026-10-08 06:12 UTC

请记住:重试策略实现在 dispatcher 里,超时参数在 timeoutPolicy。

summary turn 1 · 2026-10-08 06:20 UTC

- 已完成重试策略迁移
- 已用 29 个测试验证

- 你的消息按原样记录,因为未来的查询更像过去的输入,而不像对它的摘要。
- 摘要是抽取式的,不是生成的。 它保留项目符号和结论式的句子,从不引用围栏代码,实在没有就退回开头那句。所以记录不消耗模型 token。
- 路径相对于会话的工作区,所以记录会跟着工作。工作区是仓库时,把它加进 .gitignore。

召回

每条新的用户消息都会先与那份文档匹配,之后模型才读取任何内容。token 由拉丁词加上 CJK 双字组构成,所以中文无需附带分词器就能匹配;打分用词频,按文档自身各块的逆文档频率加权,再给你自己原话的措辞加权,并做长度衰减,这样长块无法靠体量取胜。

三条规则让它不至于花费超过节省:

1. 跨会话笔记优先。 同一工作区里其他会话的块,携带的是本会话从未见过的信息;本会话自己的块只是兜底,用于压缩把它们剪掉之后。
2. 必须有佐证。 至少要匹配 recall.minMatchedTerms 个不同词项,这样一个共享词就不会把 token 花在与问题无关的摘录上。
3. 只追加。 摘录通过 agent.inject() 到达,落成 agent/inbox/spliced 事件——是对请求的追加,不是重写,所以服务商的前缀缓存得以保留。当前轮记录的块绝不会被回引,同样的摘录也绝不会连续注入两次。

每次注入都是一条完整的用户消息:有自己的 id、role: 'user',以及由生产者拥有的 source.kind,值为 token-frugal-recall。最后这个字段不是装饰——Harness 在三处不加防护地读取 message.source.kind(agent/inbox/spliced 上的 webhook 不变量、智能体循环里的 isOwned(),以及 Chat 收件箱过滤器),而 dsh-session 会拒绝没有它的消息。没有 source 的注入不会安静地失败:它会让页面空白。这个 kind 还告诉插件哪些消息是自己的——载体像其他待处理输入一样被认领,所以召回遇到它会提前返回,而不是把自己的文本记进记忆文档。

注入的文本会说明来源,并说明拿它做什么:

<recalled-context source="C:\work\project\.dsh-token-frugal\memory.md">
Matches from this workspace's session memory, recorded from earlier turns and
earlier conversations here. Prefer these over re-reading the transcript or
re-deriving what was already established. They are extracts, not the full record;
read C:\work\project\.dsh-token-frugal\memory.md when you need more.
...
</recalled-context>

在本机对四个已完成的记录会话测量(149 次模型请求,基线提示词 token 8.6 M,主要由 web-fetch、终端和文件读取输出占大头):

prompt tokens 8,624,238 -> 5,022,830 41.76% target 40% -> MET
tool-result chars 684,300 -> 149,970 78.1%
cache-eligible 8,309,095 -> 4,841,277 41.74%
cache hit rate 96.35% -> 96.39%

用 node tools/measure-tokens.mjs --compare-placement 复现。测量运行所在的会话默认被排除,因为它仍在会被追加,结果取决于截取时间; --include-live 会把它包含进来。

为什么这不会与压缩或溢出机制重复

Harness 已经自带两套限制工具输出的机制,本插件刻意不碰它们:

- dsh-compaction-tool-result-pruner 在压缩触发条件满足后,重写历史表层节点。它自己的 README 记录了代价:「替换早先的结果会让从第一个改动 token 起的复用失效。」本插件从不触碰表层——它改变的是最初写入表层的字节,因此历史保持只追加、可前缀缓存。
- dsh-spill-policy 决定把某个超大结果变成头尾预览,外加一份存储副本和一个定位符,并受一个全局 token 预算约束。本插件自己不存储任何东西:当自身的省略需要地方存放完整文本时,它调用共享的 ctx.spillStore 服务;该服务不存在时,它完全跳过省略。它还能识别已有的溢出通知,复用其定位符,而不是把同一个结果溢出两次。

两者互补而非重叠,因为本插件的监听器注册在自带的溢出监听器之后(后者把自己前置),所以流水线是:结构感知压缩 → 本插件的按工具省略 → 溢出策略的 token 预算。溢出仍是最终 token 上限和保留策略的权威;本插件在那个决定做出之前就让内容更便宜,并保留按工具的预算和带错误的中间行——这些是全局上限无法表达的。

模型实际看到什么

两类变化,都在文本里自行说明。

无损编码。 终端控制序列和回车重绘被解析掉;行内的对齐填充被折叠,缩进保留;行尾空白和连续空行去掉;连续相同的行变成一行加 ... (x40 identical);等差数列,比如构建计数器或时间戳序列,变成两个端点加 ... (1 -> 20 step 1; 18 line(s) folded),能精确重建每一折叠行;美化打印的 JSON 被压缩,同构的扁平对象数组变成带标签的表格([30 rows, 3 columns: id, name, ok] 后跟 TSV 行)——这是无损重编码,不是摘要。

按预算省略,仅当无损结果仍超出该工具的预算时:

... [357 line(s), 19538 char(s) elided by dsh-token-frugal. Re-read C:\x\big.ts with offset/limit for the elided lines.] ...

头部和尾部保留,任何匹配错误/警告模式的中间行保留,标记准确说明丢了多少、怎么取回。富块(图片、文件)从不省略,从不重排,也从不相对文本移动:预算在文本块之间拆分,而不是把它们拼接起来。

可恢复性是前提,不是尽力而为

| 情况 | 行为 |
| 结果里已经带了溢出提示 | 省略照常进行,已有的定位信息留在文本中。 |
| read 结果,readSourceRecovery: true | 天生可恢复——磁盘上的文件就是事实来源,标记里写明路径,并提示用 offset/limit 重新读取。 |
| ctx.spillStore 可用 | 全文通过这个共享服务保存下来,定位信息写进标记。 |
| 完全没有恢复路径 | 跳过省略。 只跑无损变换。插件绝不丢弃无法归还的文本。 |
| 调用失败(isError) | 预算乘以 errorBudgetFactor(默认 2):同样大小的错误消息比成功消息更值钱。 |
| 变换过程中抛出任何异常 | 返回未改动的决策,并记录一条警告。压缩出错绝不能把成功的工具调用变成错误。 |
| 同级的 tools/post-execute 监听器什么都没返回 | next() 解析出 undefined,这对该钩子来说是合法返回值。钩子会补回流水线要求的 kind——{ kind: 'accept' },也就是流水线自己的终结返回值——保留同级监听器确实返回的每个字段,再把修好的决策往外传,这样一个行为异常的监听器既弄不坏本插件,也弄不坏在它之前注册的监听器。 |

设置 recovery: none 会直接关掉省略——无损底线实测能减少 1.87% 的提示词 token,且一个字节都不丢。

配置

所有可调项都放在 cordis.patch.yml 里那一行的 config 中,这样生效的策略一眼能看全,插件升级后也不会丢。lib/defaults.js 用纯 JSON 保存同样的值,另有一个单元测试把补丁、字段表和这些默认值互相钉死。

- insert:
- id: token-frugal
name: dsh-token-frugal
config:
defaultMaxChars: 1500
toolBudgets: { read: 3000, pwsh: 1200, web_fetch: 4000 }
skipTools: []
recovery: spill
hiddenTools: []

插件没有声明 schemastery 的 Config。配置档安装的 bundle 从配置档的 node_modules 解析导入,而 Harness 自己的包在那里并不存在,所以导入 @deepseek-ai/schemastery 会让这一行导入失败——第一次安装时出的正是这个问题。因此插件改为用 index.js 里的 FIELD_KINDS 表校验自己这一行的配置:未知字段和类型不对的值会让激活失败,并给出指明字段的报错。代价是插件管理器里少了一个自动生成的 schema 表单;这些字段改为在本文档里说明。

最要紧的几个字段:

| 字段 | 含义 |
| defaultMaxChars、toolBudgets | 每个工具的字符预算。默认值实测是最宽松的一套配置,能有余量地越过 40%。 |
| errorBudgetFactor | 失败调用的额外预算(默认 2)。 |
| skipTools | 原样保留、一个字节都不动的工具,无论其大小。 |
| transforms | 逐个开关无损转换。如果某个工作流依赖逐字读取对齐填充的表格,就设 columns: false。 |
| minGainRatio | 一次重写至少要省下这个比例,否则丢弃,这样琐碎内容永远不会反复改动请求前缀。 |
| structureAware | 省略内容时,保留匹配错误/警告模式的中间行。 |
| recovery | spill(默认)或 none,后者表示仅做无损处理。 |
| hiddenTools | 对所有智能体都隐藏的工具名。默认为空:隐藏工具等于移除一项能力,所以要主动开启。 |

实测预算配置档

同一语料、同一方法,与 v2.0.0 说明中引用的 149 次请求运行一致; --budgets 和 --default-max-chars 可复现每一行:

| 配置档 | 提示词 token 降幅 |
| recovery: none(仅无损,不丢弃任何内容) | 1.87% |
| 宽松(read: 4000、pwsh: 2000,默认 2500) | 38.74% |
| 中等(read: 3200、pwsh: 1300,默认 1600) | 41.09% |
| 出厂默认(read: 3000、pwsh: 1200,默认 1500) | 41.76% |

降幅取决于工作负载,所以汇总数据要连同其分布一起看。
同一语料中,各会话分别为:66.1%、56.9%、37.4%、32.9%。主要由小型工具结果构成的会话可省略的内容不多,收益最小;以抓取页面、终端输出和重复读取的文件为主的会话收益最大。
出厂默认刚好高于仍能超过 40% 的最宽松配置档(read: 3200、pwsh: 1300),所以它们看起来比实际更紧。

省略深度

高等档最大的杠杆是一个数字:工具结果被削减到的字符预算。一个结果在它所属会话的后续每次请求中都会重发,所以源头少一个字符,大约相当于提示词里少 250 个字符。
这就是为什么第三档还带一个选择器,也是为什么出厂行为是它三档中最浅的:预算表是谨慎的用户唯一应该刻意调整的策略。

| 档位 | 移除的工具结果字符 | 移除的计费提示词 token |
| 标准 standard | 47.2% | 21.8% |
| 深度 deep | 63.8% | 29.5% |
| 极限 extreme | 78.8% | 32.1% |

这些占比是相对计费* token 而言:一次缓存未命中的价格是缓存读取的十倍,这是服务商公布的比率。按原始 token 计算的占比会高估每一档,因为落在旧历史中的重写会让从该位置起的缓存前缀失效,并为此再付一次钱。
三行是同一语料的一次重放——脚本默认测量的那份冻结语料(13 个重放会话共 1946 次请求,各模式均为出厂默认)——所以档位是唯一变量;--depth 可复现任意一行:

node tools/measure-tokens.mjs --depth deep

更深的档位会覆盖 defaultMaxChars 以及那些工具的预算,产生大部分上下文(read、web_fetch、web_search、pwsh、bash、grep、glob),做法是在配置表里覆盖这些条目,而不是替换整张表——某个层级没有点名的工具,保留配置档给它的设置,而 --budgets / --default-max-chars 仍然优先于层级。层级只调整预算:它从不改变哪些转换会运行,也从不允许 elide 丢掉它无法交还的内容,所以无法恢复的结果在任何层级都保持完整。这个选择会作为 elisionDepth 持久化,与各模式并列存放在 dsh-token-frugal.state.json 中;v2.0.5 写出的状态文件按 standard 读取,未知层级会以 400 拒绝,而不是悄悄回退到面板没有要求的某个层级。

让不再新鲜的结果变旧

更深的层级在结果到达后继续削减。一旦某个结果掉出最新的两个(deep)或最新的一个(extreme),模型就不再收到该结果本身,而是收到它的摘要——头部、尾部,以及到达时省略所带的同一个 ... [N line(s), M char(s) elided by dsh-token-frugal. Re-run read for the full output.] ... 标记。新鲜的结果才是推理模型仍能据以工作的结果,所以预算要花在价值最低的地方。

| 层级 | 保持完整 | 较旧的结果 | 移除的计费提示词 token |
| 标准 standard | 每个结果 | 不动 | 21.8% |
| 深度 deep | 最新 2 个 | 300 字符摘要 | 29.5% |
| 极限 extreme | 最新 1 个 | 200 字符摘要 | 32.1% |

没有任何东西被销毁。这次改写是仅对模型的表面替换(surfaceOp),所以持久日志、转录记录和统计都保留原始内容;替换只在模型读取的请求中遮蔽它。它在 agent/pre-step 上提交,早于将要读取它的请求,而且每个结果最多变旧一次。

这些常量是实测出来的,重新预填充的成本正是它们如此之小的原因。每个摘要都落在旧历史里,所以它会从那个位置起让缓存的 prefix 失效;摘要只有在落在接近尾部时才划算。不做变旧处理时,同样的层级计费为 21.8%、27.15% 和 31.55%——所以 deep 才是这条规则体现价值的地方,而 keepFull 设为三或更多是净亏损。--aging-keep 和 --aging-limit 会重新运行搜索;--no-aging 按此功能之前的方式为层级计价。

工具目录可见性

agent/created 那一半在 hiddenTools 点名之前是不起作用的,因为隐藏工具要付出能力代价。测量会先量化它能换来什么,再让你决定——在这个语料上,已记录的工具 schema(每次请求 6,770 token)中,有 14 个从未被调用过一次,每次请求耗费 2,846 token,占整个基线的 4.92%:

workflow(852) subagent_fork(275) update_goal(250) present(199) job_output(158)
create_goal(149) send_message(136) exit_plan_mode(122) ...

取消注释 cordis.patch.yml 末尾的示例块,就能去掉你接受损失。隐藏功能是在智能体自己的作用域上下文上通过 ctx.tools.restrict() 实现的,因此它与 schema 展示、查找和执行保持一致,未知名称会被报告并忽略,而不是抛出异常——抛出异常的 agent/created 监听器会回滚智能体创建。

测量脚本

node tools/measure-tokens.mjs [options] [session-log-or-directory ...]

不带路径时,它会读取 $DSH_HOME/sessions 下的每个会话日志。它会从每个会话的 surfaceOp 记录重建该会话的 surface(因此替换、恢复和压缩都按循环组装请求的方式计价),把每个 step/start 重放两次——一次按记录原样,一次应用配置的策略——然后用 Harness 为其 contextBreakdown 投影所用的同一套固定密度启发式对两者计价。

| 选项 | 用途 |
| --json <path> | 写出完整报告;stdout 仍只输出摘要。 |
| --max-lines <n> | stdout 行数硬上限(默认 100)。 |
| --target <percent> | 判定目标(默认 40)。达标时退出状态为 0,未达标为 2。 |
| --compare-placement | 同时模拟另一种做法:在后期一次性重写历史,交付同样的缩减量。 |
| --include-live | 纳入仍在写入的会话(默认排除)。 |
| --budgets、--default-max-chars、--no-elide | 在不改动任何东西的前提下,为另一套策略重新计价。 |
| --depth <tier> | 为面板的 standard / deep / extreme 预算表计价;--budgets 和 --default-max-chars 仍然优先于它。 |
| --aging-keep <n>、--aging-limit <chars> | 重新运行老化搜索:多少条结果保持完整,以及更旧的一条被裁剪到什么程度。 |
| --no-aging | 按老化功能存在之前的样貌为某个层级计价。 |
| --only <tool>、--patch、--no-patch、--config、--quiet | 收窄范围与选择策略。 |

构造上就有界。 脚本最多打印 --max-lines 行,每行截断到 240 个字符,并且一次只流式处理一个会话,只保留聚合结果——所以从智能体里运行它不会淹没该智能体的上下文。在这个语料库上,默认摘要约 55 个短行(大约 2 k token),耗时远低于半秒。带 --json 的完整运行会在机器之外生成完整报告。

解读缓存数字

缓存数字需要一句提醒,因为它们很容易被误读。Harness 已经实现了近乎完全的前缀复用(这里是 96.35%,服务商自己记录的用量也吻合,为 96.90%),所以几乎没有 命中率 可再争取。收益在体量上:同样的约 96% 现在是更小 42% 的提示词中的 96% 占比,因此缓存部分和未缓存部分一起缩小。

放置策略真正换来的是没有代价。--compare-placement 模拟了另一种做法——在后期一次性重写历史,交付同样的缩减量——结果显示,在落地重写的那次请求上,命中率跌到 56.30%,每个会话一次就损失 76,652 个提示词 token 的复用。
在源头改字节,这次数为零,因为前缀从未以更大的形式发送过。

验证状态

| 检查 | 结果 |
| node --test test/.test.mjs | 29 个测试通过:变换不变量(绝不增长、幂等、富块保留)、补丁 ⇄ 字段表 ⇄ 默认值一致、配置校验、策略决策、多帧日志解码。 |
| node test/load-check.mjs | 通过,现在还驱动统计路由(GET 报告本次运行已产生的节省,POST {"reset": true} 将累计部分清零,运行中进程的数字保持不变,请求体错误返回 400,不支持的方法返回 405),并用 stub React 真正渲染两个 Client 界面:折叠面板列出三个等级且没有开关,依次展开每个等级恰好呈现全部九种模式各一次,模式状态与路由报告的一致,设置页渲染出按工具划分的表格。该检查还断言 client.js 中不含 U+FFFD 替换字符,因为对该文件一次有损重编码曾悄悄损坏面板。 |
| 实时统计 | 等待 Harness 重启。 Host 部分在进程启动时读取,所以仍在运行 2.0.3 的进程根本不提供统计路由——GET /dsh-token-frugal/stats 返回 404,这正是实时探测得到的结果。路由、其载荷、面板和设置页都已由 load-check 在进程内验证;重启应用才会启用它们。 |
| 实时挂载 | 已验证。 install_bundle 报告 application: applied,无警告;该行已生效(Config.listConfigs → patchId: token-frugal,status: absent,因为插件未声明 schema)。 |
| 实时省略与恢复 | 端到端已验证。 一条 400 行的 pwsh 结果被记录为保留 15 行头部、11 行尾部,以及一个标明截断的标记;完整的 400 行原文可在标记所指的定位符处找到,由 dsh-spill-local 通过共享的 ctx.spillStore 服务写入。 |

安装后一次工具调用得到的实时结果,原文如下:

line 15 value 416967 payload 17856
... [374 line(s), 13283 char(s) elided by dsh-token-frugal. Full text saved at
<temp>/dsh-spill-/session-/…-pwsh.txt: Use read with offset/limit, or grep this path to search within it.] ... line 400 value 119087 payload 17513

它所指的产物包含全部 400 行(14,590 字节),首行完整,末行完整。

对于 2.0.0,同一个 load-check 还在一个进程中驱动 v2 界面:创建记忆文档,填入一条原文消息和一份轮次摘要,并在下一条消息上匹配,通过 agent.inject 注入 643 个字符;重复匹配会被抑制;模式路由对 GET 返回全部九种模式,持久化 POST,回报变更,对未知模式返回 400,对不支持的方法返回 405;而 Client 部分用假模块加载器导入,以断言它的模块 id、它的 slots 依赖,以及精确的 conversation.composer.dock 注册。

到了 2.0.4,同样的检查更进一步,因为否则这两个新界面在真正重启之前都无法验证:统计路由是端到端驱动的

(and its GET must report the saving the check itself already produced, which
proves the counters are wired to the transform pipeline rather than merely
present), and both Client surfaces are really rendered with a stub React,
including the assertion that opening each grade in turn surfaces all nine modes
exactly once. The check reports 23843 chars saved across 2 tool(s) for its own
run.

A running Harness must be restarted once to load v2, and again for 2.0.4. A
profile-installed bundle is an ES module cached per process, so a disable/enable
cycle keeps executing the previous code generation — set_bundle reports unknown config field "modes" from the cached copy until the process restarts. That is a property of the Harness, not of this plugin. The Host half is what serves the statistics route, so until the restart GET /dsh-token-frugal/stats answers 404; the Client half is re-read per page load, so the panel and the settings page appear after a browser refresh.

npm run verify # syntax, unit tests, load check
node tools/measure-tokens.mjs --compare-placement --json report.json

load-check resolves @deepseek-ai/schemastery the way the Harness does. Inside
a Harness process that is just the bare specifier; from a plain shell it reads
the two packages it needs straight out of the installed app.asar, which it
locates automatically. Point it elsewhere with --from-asar <path> or
--core-dir <dir>, or set DSH_APP_ASAR.

Known limitations

- Character budgets approximate tokens. They are code points, not
tokenizer output; the Harness's own ctx.tokenMeter remains the authority for
whether pressure was relieved.
- Trimming is syntactic. Middle-line selection keeps the head, the tail, and
anything matching an error/warning pattern. It does not interpret which middle
lines matter semantically — that would need a model call, which this plugin deliberately never makes.
- columns: true is a judgement call. Collapsing runs of ≥3 spaces inside a
line removes table padding, which is most of a PowerShell Format-Table, and
also removes intra-line alignment inside string literals. read, grep, and
similar source-text tools get a tighter budget for the same reason; turn
columns off, or add a tool to skipTools, if a workflow depends on exact
spacing.
- The catalogue lever is evidence, not certainty. "Never called in these
logs" is not "never needed" — exit_plan_mode, for instance, is only used in plan mode. Read the list before uncommenting it.
- A resume reads the compressed log. Because compression happens before the
tool/result event is appended, a resumed or forked session sees the
compressed text, not the original. Recovery is through the spill locator or,
for read, the file itself.
- dsh-spill-policy takes precedence on ordering. The pipeline described
above assumes the shipped spill listener keeps prepending itself. If another
plugin replaces that ordering, this plugin's elision runs after spill's
bounding instead — still correct, since an existing notice is detected and respected, but the recovery locator in the marker is then spill's.

Appendix: the original v2 request, verbatim

This is the request 2.0.0 was built from, reproduced unchanged (it was committed
to this file through GitHub's web editor while the version was being scoped).
Its names and commands were the request*, not the shipped interface, so they are
mapped to the real ones underneath rather than published as instructions.

dsh-your-plugin

一个用于减少 DSH 调用 token 浪费的插件。

功能

· 在工具输出写入对话前进行头尾裁剪,中间用元数据替换
· 对 JSON/CSV 等结构化输出做压缩,降低上下文占用
· 管理工具 schema 可见性,避免不用的工具描述进入请求
· 附带测量脚本,报告优化前后的 token 对比

安装 bash
dsh plugin --profile web add dsh-your-plugin

配置

在 cordis.patch.yml 中调整参数: yaml config: thresholdRatio: 0.3 keepHeadRatio: 0.4 keepTailRatio: 0.4
exemptTools: []

开发 bash
git clone https://github.com/<你的账号>/dsh-your-plugin.git
cd dsh-your-plugin
npm install

插件导出 { name, inject, apply },所有改写走 DSH 事件流,保持 model-visible 与 logged 一致。

验证 bash
node measure-token-savings.mjs

输出优化前后的 token 数对比与缓存命中率变化。

(后续会创建自选优化项目的窗口,以供个人自己调配)(这次忘记添加在工作文件夹后自动创建md脚本,以防止反复调去上下文消耗大量token)

| In the request | Shipped |
| dsh-your-plugin | dsh-token-frugal |
| dsh plugin --profile web add dsh-your-plugin | No such CLI exists; a bundle is installed with the Harness plugin manager's install_bundle action against the package directory (see "Install" in the release notes). |
| thresholdRatio, keepHeadRatio, keepTailRatio, exemptTools | defaultMaxChars + per-tool toolBudgets, headRatio, tailRatio, skipTools, hiddenTools |
| measure-token-savings.mjs | tools/measure-tokens.mjs |
| (后续会创建自选优化项目的窗口) | Delivered: the saving-mode panel in conversation.composer.dock, one switch per saving. |
| (这次忘记添加在工作文件夹后自动创建 md 脚本) | Delivered: the per-workspace memory document plus recall, which is what stops context being re-read. |
预览分享卡片长什么样 ↓xuzihao66/dsh-token-frugal
分享 · DeepSeek Harness Hub
xuzihao66/dsh-token-frugal

削减长时间运行的 DeepSeek Harness 会话的输入 token…

回到顶部