Skip to content
dsh.fish
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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-0.2.0-blue)](https://www.npmjs.com/package/@jxame/dsh-condense)
[![Verified: DSH 0.1.0-rc.8](https://img.shields.io/badge/dsh-0.1.0--rc.8-green)]()
[![npm](https://img.shields.io/npm/v/@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

  • 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.
Source