DeepSeek Harness Hub
← 返回列表

unclecode/toolshrink

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
⚠ 装前注意

按含义而非截断位置来削减大型 agent 工具输出。

基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/8/19 · 已提供中文文档

按含义而非截断位置来削减大型 agent 工具输出。13 个内容感知缩减器 + DeepSeek Harness 插件。

综合分
34.1
GitHub 分
34.1
用户评分
★ Stars
11
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add unclecode/toolshrink
未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
数据截至 2026/9/19(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意

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

npm 包toolshrink(未发布到 npm,仅可源码安装)
Node 引擎未声明 engines.node
dsh CLI 依赖未声明 dsh 版本约束
入口文件main/exports/bin 已声明

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

验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/19 20:59:46

用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

toolshrink

按含义而非截断位置来削减大型 agent 工具输出。

我每天都用 Claude Code,一直希望能干预它管理上下文的方式。早期你可以直接编辑会话 JSONL。后来这扇门关上了。

当 DeepSeek 开源 Harness 时,那里一切都是插件,我看了看内部。那里的工具输出按大小截断:保留头部,保留尾部,丢弃中间。我读了 Codex 和 pi,它们也一样。它们都不看文本包含什么内容。

这会以可预测的方式失败。你的测试套件打印 5,000 行通过和中间 3 个失败。按大小截断会保留通过项,丢掉失败项。模型读了它,相信这次运行,然后给出错误答案。

所以我构建了我一直希望 Claude Code 拥有的 shrinker。它先读取输出,识别其形态,并保留承载信息的部分:

input: a vitest run, 31,958 chars, 805 lines, budget 2,000 chars

head+tail cut:   1,904 chars   the model learns: the summary
toolshrink:        255 chars   the model learns: which test failed,
why, at which line, and the summary

所有被移除的内容都会计入一个模型可读的标记中,完整原文会带定位符保存到磁盘。没有任何东西被静默丢失。

各种削减

每种削减识别一种文本形态。第一个识别输入的削减会运行。当没有削减识别时,大小回退会运行,因此结果总能适应预算。

| 削减 | 识别 | 保留 | 丢弃 |
| --- | --- | --- | --- |
| diff | git diff、补丁 | 变更行、文件和 hunk 头、每侧 1 行上下文 | 未变更的上下文 |
| json | 一个 JSON 值 | 结构、长数组每 3 个样本、宽对象每 5 个键、计数 | 重复记录 |
| tests | vitest、jest、pytest、cargo test、go test | 失败及其解释、摘要 | 通过的测试 |
| build | tsc、cargo、gcc、webpack、esbuild | 错误和警告及其代码帧、摘要 | 构建进度 |
| stacktrace | node、Python、Java、Ruby 堆栈跟踪 | 消息和 YOUR 代码中的帧 | 依赖帧,已计数 |
| log | 带时间戳的日志 | 错误和警告及其前面的行、结尾 | 常规行 |
| tree | find、ls -R、文件列表 | 结构、每个目录 8 个条目、计数 | 拥挤的目录 |
| repeat | 重试风暴、进度刷屏 | 每次运行 2 个样本,加上“2,998 similar lines omitted” | 连续近乎相同的行 |
| lint | eslint、ruff、clippy | 每条规则及其计数和示例位置、最差文件 | 同一规则的重复出现 |
| install | npm、pip、pnpm、cargo | 摘要、版本、弃用、漏洞、错误 | 获取和下载进度 |
| csv | CSV、TSV、管道表格 | 表头、开头 5 行、结尾 2 行、行数和列数 | 中间的行 |
| gitlog | git log,两种格式 | 最新的 15 条提交、总数、作者及计数 | 更早的提交 |
| size | 全部内容(兜底) | bash:末尾 · grep/read:开头 · 未知:两端 | 其余部分,已计数 |

今天发布了十三个裁剪器。每一个都是带有共享接口的普通文件,因此添加你自己的裁剪器只需一个文件,而不是一个分支。

每个裁剪器都遵循四条规则,取自我所阅读的三个 agent:

- 绝不返回不完整的行(来自 pi)
- 绝不拆分 UTF-16 代理对(来自 DeepSeek Harness)
- 准确说明移除了多少内容:... 15,903 characters, 401 lines omitted ...(来自 Codex)
- 第二次处理不会改变任何东西

与 DeepSeek Harness 一起使用

一条命令:

dsh plugin --profile web add github:unclecode/toolshrink

这就是全部安装步骤。该包带有一个 dsh.bundle 清单,因此插件会在下次启动时以 50,000 字符的默认预算挂载。从你自己的层 ~/.dsh/cordis.patch.yml 更改预算:

- id: toolshrink
config:
maxChars: 20000
log: /tmp/toolshrink.log

想直接改它?克隆、npm install && npm run build,然后通过 insert 行按路径挂载适配器文件(见下方的适配器配置)。

适配器配置

- insert:
- id: toolshrink
name: /path/to/toolshrink/adapters/harness/toolshrink.mjs
config:
maxChars: 50000        # 超过这么多字符就裁剪(默认 50000)
maxLines: 2000         # 或超过这么多行就裁剪(默认 2000)
maxLineChars: 0        # 限制单个长行,0 = 关闭(默认 0)
disable: [json]        # 跳过指定的裁剪器(默认无)
spillDir: ~/.dsh-toolshrink   # 完整原始内容存放位置
log: /tmp/toolshrink.log      # 每次裁剪一行,省略则静默

日志行格式:bash  64151 -> 2942 via tree+size。

作为库使用

import { shrink, FileSpillStore } from 'toolshrink'

const out = shrink(bigText, { tool: 'bash', command: 'npm test' }, {
budget: { maxChars: 20_000 },
spill: new FileSpillStore({ dir: '/tmp/spills' }),  // 可选
})

out.content   // 交给模型的文本
out.reduced   // 当输入本就适配时为 false
out.strategy  // "tests"、"diff+size"、"size:tail"、"none"、……
out.note      // 一行人类可读的说明,描述发生了什么
out.stats     // inputChars、outputChars、keptLines、droppedLines、……

hint(第二个参数)是可选的,可改善路由:tool 决定裁剪方向,command 帮助检测测试运行和 diff,path 帮助检测 JSON 和日志。

编写你自己的裁剪器

一个裁剪器就是一个文件,默认导出三个成员。文件名即裁剪器名称。

// mycut.mjs
export default {
name: 'mycut',
// 廉价且确定。不确定时返回 false:错误匹配比 size 兜底更糟。
detect(text, hint) {
return hint.command?.startsWith('kubectl') ?? false
},
// 进一步查看后返回 null 表示拒绝;然后下一个裁剪器会尝试。
reduce(text, hint, budget) {
const content = text.slice(0, budget.maxChars) // 你的真实逻辑写在这里
return {
content,
reduced: true,
strategy: 'mycut',
note: '保留了我确定重要的部分',
stats: {
inputChars: text.length, inputLines: 0,
outputChars: content.length, outputLines: 0,
},
}
},
}

使用它:

import { shrink, loadReducers } from 'toolshrink'

const mine = await loadReducers('/path/to/my-cuts')   // 读取该目录
shrink(text, hint, { extra: mine })                    // 在内置规则之前尝试

或者控制内置规则:{ only: ['tests', 'diff'] } 限定并排序,
{ disable: ['json'] } 跳过。

溢出:什么都不会丢失

有了溢出存储,完整的原始内容会在任何裁剪之前被保存,裁剪后的
文本末尾会加上:

[full output saved as spill:bash-d63d2aebb643: directories sampled to 8 entries each]

store.load('spill:bash-d63d2aebb643') 会逐字节返回原始内容。
文件会在 24 小时后清理。该存储是一个接口;默认实现写入
文件,宿主可以接入自己的存储。

我在实际使用中看到的

在 3,000 字符的预算下,agent 拿到一个 60,000 字符的 find 结果,
被裁剪到只剩开头。它的回复开头是:“输出被截断了。让我按目录
统计一下数量”——它看到了省略标记,用聚合方式重新查询,
并仅凭总共 4,000 字符而不是 60,000 字符正确作答。

这就是设计在起作用:一个诚实的标记把裁剪从静默的数据丢失
变成了模型会据此行动的信号。这一直是我想要的干预,而
现在它只是一行 YAML。

TODO:我接下来想要的裁剪

下面每一个都是具有相同接口的单个文件。挑一个并发起 pull
request。

| 裁剪 | 识别 | 会保留 |
| --- | --- | --- |
| semantic | 任何内容,给定 agent 当前的目标 | 与目标最相关的块。两个阶段:词法评分(BM25,无需模型),然后可选的嵌入评分,以捕捉超越共享词语的含义 |
| sql | 查询结果、EXPLAIN 计划 | 计划中开销大的节点、抽样的结果行 |
| docker | 构建和 compose 输出 | 失败的层、最终镜像、丢弃的构建杂音 |

semantic 裁剪是有趣的那个:上面每个裁剪都按形状决定,
而这个会按相关性决定。它需要一个额外输入,即 agent 当前正在
处理什么的查询,宿主适配器可以通过 hint 传入。

面向其他 agent 的适配器

这个库对任何 agent 一无所知。Harness 适配器只有 70 行:
捕获结果事件,调用 shrink,返回替换内容。

- pi(earendil-works/pi)有一个可访问工具结果的扩展 API。
- Codex(openai/codex)在 codex-rs/core-plugins 中有一个插件系统。

这两个适配器都是待完成的工作。如果你写了一个,欢迎发起 pull request。

许可证

MIT。使用它,修改它,无需询问。

由 @unclecode 构建,他是
Crawl4AI
Crawl4AI 星标数。
在 X 上关注我,了解我接下来构建的内容:x.com/unclecode。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群