Bundle
@jxame/dsh-condense
Token optimizer for DeepSeek Harness — condense your context, keep the essence
- Source
- JxaMe
- License
- MIT
- Updated
- Updated 14 days ago
Readme
<div align="center">
<img src="assets/mascot.png" alt="dsh-condense mascot" width="160">
# ✂️ dsh-condense
**浓缩上下文,留住精华。**
**Condense your context. Keep the essence.**
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 token 优化插件 — 自动削减 agent 循环中的低价值内容,同时 **保持 prompt 缓存命中**。
[](LICENSE)
[](https://www.npmjs.com/package/@jxame/dsh-condense)
[]()
[](https://www.npmjs.com/package/@jxame/dsh-condense)
**中文** · [English](README.en.md)
</div>
---
## 🧐 为什么需要它
LLM 编程 agent 在很少有帮助的内容上烧 token:
| 浪费场景 | 说明 |
|---|---|
| 📚 **整文件读取** | `node_modules`、lock 文件、vendor 代码灌满上下文 |
| 🐘 **超大输出** | 工具输出在整个会话期间留在历史里 |
| 🔁 **重复读取** | 同一文件被连续读两遍 |
| 🎯 **盲目检索** | 候选 20 个文件时只读 5 个就够 |
| 📝 **啰嗦回复** | 一句话能说清时模型写一大段 |
**dsh-condense 在每一个点上动手。**
---
## 💡 核心考量:省 token,同时不破坏缓存命中
> 💰 API 侧的 prompt 缓存按 **前缀匹配** 计费:命中部分大幅折扣(约全价的 1/10),未命中的新增内容才是全价。
两种"省 token"的思路,结果截然不同:
| 思路 | 缓存影响 | 结论 |
|---|---|---|
| ❌ **事后削减**(摘掉/重写已进历史的内容) | 历史前缀一变,后续请求从变化点起全部 **cache miss** | 省下的 token 被全价重算抵消,**可能比不省更贵** |
| ✅ **入历史前削减**(本插件的做法) | 历史从创建起就是浓缩形态,前缀 **字节级稳定** | 缓存命中率不受影响 |
```
模型请求 ──► 内容还没进历史 ──► dsh-condense 削减 ──► 浓缩版进历史 ──► 前缀稳定
```
🎯 省的是 **未命中的全价 token**(每次新增的大块输出),保留的是 **已命中的折扣 token** —— 这正是经济收益最大的位置。
---
## ⚙️ 功能一览
| 能力 | 机制 | 时机 |
|---|---|---|
| 🚫 **屏蔽低信号读取** | `tools/pre-execute` 拒绝读取 `node_modules/`、`.git/`、`dist/`、lock 文件、`.min.js` | 自动 |
| 🧭 **`read` → `smart_read` 引导** | 模型全量 `read` 源码文件时拒绝并提示改用骨架读取;局部读不受影响(Config `smartReadRedirect`,默认开) | 自动 |
| 🗜️ **压缩超大输出** | `tools/post-execute` 对 >8 KB 输出做头尾截断 | 自动 |
| ♻️ **重复调用去重** | 相同调用且 **输出哈希一致** 即返回占位符(无时间窗口,输出一变就返回新内容,绝不误导)。覆盖只读工具 + `bash` | 自动 |
| 🤫 **conciseness 指令** | 系统提示注入"直接回答、不说废话" | 自动 |
| 🧠 **`smart_read`** | tree-sitter 语法级骨架提取 — **TS/JS/TSX/JSX/Python/Rust/Go** | 按需 |
| 🔍 **`trim_context`** | BM25 候选文件排序(支持 **中英文**) | 按需 |
| 📊 **`condense_stats`** | 统一节省量统计 + **真实用量计量** + 持久化 | 按需 |
---
## 📈 实测效果
> 所有数字均来自真实运行(详见 [EXPERIMENTS.md](EXPERIMENTS.md));token 估算口径 ≈4 字符/token,诚实标注。
| 实验 | 结果 |
|---|---|
| 🧠 `smart_read` 单文件节省 | 实现文件 **45–74%**;纯类型声明(.d.ts)≈0%(骨架≈原文) |
| 🔗 `trim_context` → `smart_read` 链路 | 4 组查询 **整体 91%**,相关文件全部命中 |
| 🗜️ 输出压缩 | 超大输出保留头尾 8 KB,削减率 **77–82%** |
| 🚫 屏蔽低信号读取 | 目标文件完全不进上下文(**100%**) |
| 💾 prompt 缓存影响 | **无** — 所有削减发生在内容进历史之前,前缀保持稳定 |
---
## 🏗️ 工作原理
```
┌────────────────────────────────────────────────┐
│ dsh-condense │
│ │
model ────────►│ tools/pre-execute ──► 屏蔽低信号读取 │
│ │ │
│ tool 执行 (或按需 smart_read / │
│ │ trim_context) │
│ ▼ │
│ tools/post-execute ──► 压缩 + 去重 │
│ │ │
│ 会话历史(已浓缩)── 前缀稳定,缓存命中不损 │
│ │ │
│ 系统提示 section ──► conciseness 指令 │
└────────────────────────────────────────────────┘
```
两个组成部分,都是纯插件(**零 DSH 源码改动**):
- 🧩 **主插件**(标准 Cordis 插件,`dsh plugin add` 一键安装):事件钩子(`tools/pre-execute`、`tools/post-execute`、`session/event` 真实计量)、按需工具、统计持久化。
- 🚀 **宿主插件**(`host-plugin/`):运行在完整 Node 环境,因此能加载 **tree-sitter WASM 语法** — 这是语法级符号提取的基础。
宿主插件通过 `ctx.loader.create()` 引导加载,随主插件自动启动,无需额外配置。
---
## 🚀 安装
> ✅ 已验证 DSH **0.1.0-rc.8**(peer 范围:`>=0.1.0-rc.7`)。
```bash
# 从 npm(推荐,可版本化更新)
dsh plugin add @jxame/dsh-condense
# 或从 GitHub
dsh plugin add github:JxaMe/dsh-condense
```
安装即完成:包自带 bundle patch(`dsh.bundle` 声明),`dsh plugin add` 自动注册为 profile 层,**无需手动编辑任何配置**。重启 DSH 后宿主插件随启动加载,钩子和工具立即生效。
> 🔄 更新到新版本:`dsh plugin remove @jxame/dsh-condense && dsh plugin add @jxame/dsh-condense`。
> 🛠️ 开发期改代码可用本地路径 `dsh plugin add /path/to/dsh-condense`(symlink 跟随本地目录,`npm run build` 后重启即生效)。
### 📍 放置位置
`dsh plugin add` 默认装入 **host 组成**(全会话生效)。如需限定单个会话,把插件行放进对应 **agent preset** 的 `plugins:` 列表:
| 位置 | 生效范围 |
|---|---|
| 🏠 **host 组成**(默认) | 所有会话,全 harness 共享一份统计 |
| 🎯 **agent preset** | 单个会话,每会话独立统计 |
统计(`condense_stats`)与削减记录持久化在 `~/.dsh/plugins/dsh-condense/stats.json`(Windows: `%USERPROFILE%\.dsh\plugins\dsh-condense\stats.json`),重启不丢。
---
## 🛠️ 工具
| 工具 | 说明 |
|---|---|
| 🧠 `smart_read` | 源码文件的符号骨架(tree-sitter,TS/JS/Python/Rust/Go)。探索结构时优先于 `read`。 |
| 🔍 `trim_context` | 对候选文件路径按 BM25 与查询排序,返回 top-N 及分数。支持中英文(含单字中文)。 |
| 📖 `diff_read` | 读取 git diff(支持 path/base/head/staged),迭代场景下替代整文件阅读。 |
| 📊 `condense_stats` | 查询会话的 token 节省量(按机制细分)+ 真实用量(provider 实测,`session/event` 持久化事件)+ 持久化位置。 |
---
## ⚠️ 已知限制
- 🧠 `smart_read` 对 Python/Rust 的类/结构体成员:方法签名化、字段保留(与 TS/JS 一致);Go 结构体保留字段。
- 🇨🇳 单个汉字查询已支持(bigram 分词器也保留单字中文 token)。
- 🔄 宿主插件代码更新需重启 DSH 生效(模块缓存)。
- 🚫 DSH 的 `pre-execute` 按设计禁止改写工具参数,因此无法做 quiet-flag 式命令改写;屏蔽读取是等价的替代防护。
- 🖥️ **实时 UI 统计面板未提供**:统计的 **数据层已完全固化**(`condense_stats` 工具 + `stats.json` 持久化 + `condenseStats` 服务,重启不丢);浏览器端展示层需要 DSH 官方 client 插件构建工具(`__ModuleLoader__` bundle 格式),当前版本未随发行提供,故未提供面板。统计经 `condense_stats` 工具查询。
---
## 🏛️ 架构说明
- 🔌 **用到的钩子**:`tools/pre-execute`(deny)、`tools/post-execute`(替换 content)、`session/event`(真实用量计量,官方推荐事件溯源方式)、`systemPrompt.section()`(conciseness + smart_read 引导)、`ctx.loader.create()`(宿主插件引导)。
- 🧩 **为什么是两个插件**:标准插件环境无法加载 WASM/import npm 包;宿主插件运行在完整 Node 运行时。这个拆分正是 tree-sitter 可行的原因。
- 🚫 **陈旧输出截断**有意不重复实现 — DSH 原生自带 `toolResultPruner`。
- 🚫 **不做请求前历史裁剪**:把已写进历史的内容摘掉会破坏缓存前缀(省下的 token 被全价重算抵消)。本插件只削减"将进入"的内容,不碰"已在"的历史 — 这是 DSH 缓存计费模型下的最优策略。
---
## 🧑💻 开发
```bash
# 边界测试:正则提取器在 dsh-src(87 个真实文件)上的翻车率
node bench-edge.js
# 组合链路测试:trim_context → smart_read 的 token 节省
node bench-combo.js
# 本地验证 tree-sitter 提取器(无需 DSH)
node --input-type=module -e "import { extractSymbols } from './host-plugin/dist/index.js'; console.log(await extractSymbols('def f(x): return x', '/tmp/a.py'))"
# 单元测试
npm test
```
实验数据、方法学与发布素材见 [EXPERIMENTS.md](EXPERIMENTS.md)。
---
## 📜 许可证
MIT — 详见 [LICENSE](LICENSE)。
## 🙏 致谢
受 [ctxlite](https://github.com/ctxlite/ctxlite)(MIT + Commons Clause)启发 — 同类问题,不同架构(DSH 原生插件 vs 外部工具)。BM25 参数与符号提取策略遵循其公开设计;实现为原创。署名义务见 LICENSE 声明。
Install
dsh plugin --profile web add github:JxaMe/dsh-condense
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install jxame-dsh-condense from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.