# dsh-bottom-cost [English](README.md) | [中文](README.zh.md) 给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 加一颗**会话费用**胶囊:它坐在输入框正下方那条统计栏里,和内置的统计胶囊同一行,一眼回答「这段对话花了多少钱」与「最近一轮花了多少钱」,并把费用拆成**命中缓存**与**未命中**两笔。 ![输入框下方统计栏里的费用胶囊,以及展开后的面板](docs/session-cost-panel.png) ## 安装 ### 方式一:在 DSH 界面里装(最简单) 1. 侧栏打开**插件 → 添加插件**; 2. 二选一填进去: - 包名 **`dsh-bottom-cost`** —— 到处都能装,**包括官方桌面客户端**(它只支持 npm 源); - 仓库地址 `https://github.com/LanFeng97/dsh-bottom-cost` —— 在 `dsh web` 上可用;它要经 codeload 下载 tarball,网络抖动会失败; 3. 点**安装**,再按它的提示**重启 DeepSeek Harness**(关闭当前 `dsh` 进程后重新运行,例如 `dsh web`)。 宿主半区是 Node 模块,**只重载配置不会被导入**,所以这一步重启不能省。重启后打开界面就能看到胶囊,不需要再刷新页面。 **以后的更新是显式的。** 安装会钉住精确版本(profile 里会写成 `"dsh-bottom-cost": "0.5.1"`),所以新版本要你在插件管理里点**更新**才会生效——它不会在你不知情的时候自己变。 ### 方式二:让你的 AI 帮你装 把下面这一整段复制给任意一个能读写你电脑的 AI(DSH 里的 agent、Claude Code、Codex 都行),它会自己完成克隆与安装: ```text 帮我安装 https://github.com/LanFeng97/dsh-bottom-cost 这个插件到我的 DeepSeek Harness: 克隆仓库,运行 node tools/install.mjs,装好后告诉我需要重启 DSH 才能生效。 ``` ### 方式三:从插件市场搜(上架后) DSH 插件以包的形式分发;在 [dshmarket](https://github.com/dsh-market/dsh-market) 里搜索 **dsh-bottom-cost**。上架之前请用方式一或方式二。 ### 方式四:从源码安装(开发者) 要改代码、或要装到非默认 profile 时用这条: ```sh git clone https://github.com/LanFeng97/dsh-bottom-cost cd dsh-bottom-cost node tools/install.mjs # 复制进 profile 的 node_modules,并追加一行 loader 行 node tools/install.mjs --dry-run # 只打印要做什么,不写任何文件 node tools/install.mjs web # 指定别的 profile(默认 desktop) ``` 脚本是幂等的,`git pull` 之后再跑一次即可同步更新;它按 `$DSH_HOME`、`~/.dsh` 的顺序解析 DSH 家目录,与 harness 本身一致。装完同样**需要重启**。之后改 `lib/client.js` 只需刷新页面,改 `lib/index.js` 仍需重启。 本插件**零运行时依赖**(形状校验用自带的 `lib/shape.js`,约 90 行),所以这条路径不需要联网装任何包。这里用**复制**而不是目录联结也是因此:联结会被 Node realpath 回克隆目录,万一将来引入裸三方导入,装载阶段就会在那里找不到它。 ### 装不上 / 卸不掉?(先看这里) **桌面客户端只能装 npm 源。** 如果你用的是官方桌面端,往「添加插件」里贴 GitHub 地址可能装不上——这不是网络问题,而是客户端的安装边界(它自己的提示原话就是「这个桌面客户端只能安装已发布到 npm 的插件」)。改用 npm 包名安装。 **网络不好时 GitHub 源容易超时。** 「添加插件」背后是 pnpm:它先把 `owner/repo` 解析成具体 commit,再从 `codeload.github.com` 下载 tarball,而解析那一步有 **6 秒超时**。网络抖动就会失败,重试通常能过。 **两条安装路线不要混用(重要)。** 用「插件管理 / 插件市场」装过之后,**不要再跑 `tools/install.mjs`**(也别让 AI 跑它),反过来也一样。原因:脚本会往 profile 的 `cordis.patch.yml` 追加一行 loader 行,而插件管理自己也声明这个组件——同一个组件被**两层**声明后,插件管理会拒绝停用与卸载,报的就是: > 卸载失败:其他配置仍在使用这个组合包的组件,请先停用它们 这种情况下那个「停用」开关也压不住另一层,所以你会看到**停用后功能照旧、重启后依旧如此**。 **遇到上面的报错怎么办**(按顺序): 1. 打开 `/dsh/profiles//cordis.patch.yml`,找到这样一段(注释行 + `insert` 三行)并**整段删掉**: ```yaml # dsh-bottom-cost: ... - insert: - id: ui-session-cost name: 'dsh-bottom-cost' ``` 2. **完全退出 DSH 再启动**(不是刷新页面); 3. 回到「插件管理」,这时停用与卸载都能正常点; 4. 若改报「当前 profile 的 patch 无法唯一定位这一项」,说明还有第二处声明——把该 profile 的 `cordis.patch.yml` 内容发我,我按行给处置。 **一行自查**(在 `/dsh/profiles//` 下): ```sh grep -n "dsh-bottom-cost" cordis.patch.yml package.json ``` `package.json` 里有它 = 插件管理装的(正常);**`cordis.patch.yml` 里也有它 = 两层声明**,就是上面那个问题。 ## 怎么读这个界面 看截图:最下面那一行就是**输入框下方的统计栏**——`◎ ¥5.294 · ¥0.9230` 是本插件,它与内置的 `15 轮 388 步 · 228 tok/s`(会话统计)、`147M tok · 缓存命中 99%`(Token 用量)以及最右边的上下文占用环排在同一行;上面那块是**展开后的面板**。 点面板里的**第 N 轮**会跳到对话中的那一轮:它只读地驱动官方右侧那颗「轮次导航」轨道——已加载的轮次直接落位,**还没加载的轮次由官方先取回更早的分页再落位**。定位不到时,面板底部会给出说明,不会静默失败。 每一轮的**轮次号右侧**会显示该轮用的是哪个模型(`v4-pro`、`v4-flash`……,只削掉 `deepseek-` 前缀;一轮里混用了多个就并列显示,悬停可看完整 id)。 标题行最右边的**小太阳(或月亮)**是明暗开关:它**只切换本插件自己的内容**——面板与底部那颗费用胶囊都会立刻换成另一套配色,应用的全局主题不受影响。图标表示当前模式(太阳=浅色、月亮=深色),配色取自官方主题的两套定义,所以两种模式下都与 DSH 其他界面同一套颜色,只是与本插件之外的界面相反。胶囊切到与外部相反时,会自动带上一层主题底色——否则近白的字落在浅色的统计栏上会看不清。 **统计栏里的胶囊**: ``` ◎ ¥2.922 · ¥0.4877 ↑ 左 ↑ 右 当前会话的总花费 本轮(最近一轮)的花费 ``` - **左边**是这段对话累计花了多少;**右边**是最近一轮花了多少。两个数字都随对话推进实时更新。 - 花费为零、或这段对话还没有产生过计费轮次时,胶囊不显示——与内置统计胶囊「无内容即不渲染」的行为一致。 **点一下胶囊,面板贴着你向上展开**: - **顶部固定区**:整段对话的**总花费**按「命中缓存」与「未命中」拆成两笔(截图里的 `¥1.609` 与 `¥1.313`)。这两个数加起来永远等于胶囊左边那个总额——`总 = 命中 + 未命中` 是构造上成立的,不是四舍五入碰巧对上的。 - **下面的滚动列表**:**每一轮**的花费,每轮三行——**总费用 / 命中 / 未命中**(截图里的第 7、8 轮)。默认停在最新一轮,**向上滚**即可依次回看更早的轮次。 - **滚动区高度**恰好是两块轮次——你要看的就是「最近这一轮」和「上一轮」,不需要为此把面板拉得很长。 **右上角那颗「刷新」** 它管的是**计价用的价目表**,不是重新统计你的对话:点击后宿主去抓**官方中文价目页**,解析出 12 个单价并逐项校验,然后**把差异先列给你看**(哪一项从多少变成多少)——你点「采用」才真正生效。这么设计是因为 DeepSeek 没有机读价目 API,只有人读文档页,解析天生可能出错;一次静默的错误解析会污染你看到的每一个金额。 面板第二行那条「价目:官方页 · 10/13 02:17」就是在告诉你**当前用的是哪一份价目、什么时候抓的**;显示「价目:内置表」则表示还没抓过,用的是随插件发布的内置表。 **要不要经常点它?** 不必。官方调价时点一次即可;点完采用后,**这段对话的全部历史花费会立刻按新价重算**(因为金额是浏览器按当前价目表现算的,历史里只存 token 事实),不会出现新旧两代价格混在一本账里的情况。 ## 数字是怎么来的 只有 **DeepSeek 官方路由**有公开价目表,所以只有它们会产生金额: | | | | --- | --- | | **命中费用** | 缓存读 tokens × 命中价 | | **未命中费用** | (未命中输入 + 缓存写)× 未命中输入价 **+** 输出 tokens × 输出价 | | **峰谷** | 北京时间周一至周五 09:00-12:00、14:00-18:00 为高峰(双倍价);周末与中国法定节假日全天空闲 | | **按什么时刻计价** | 每次调用**发生那一刻**——取会话事件自带的时间戳,所以回放几个月前的历史也按**当时**的档位算,不会拿今天的价套旧账 | | **其他 provider** | 绝不猜成 ¥0:显示为**未计价**,面板底部给出未计价的调用次数 | 统计范围是**当前会话自身的轮次**。子 agent 是另一个会话,刻意不并入,所以胶囊上的数字与你看到的轮次严格一一对应。 ### 是估算,不是账单 金额由 token 数与公开价目推算。插件不读你的余额、不碰凭据,也无法得知 DeepSeek 实际扣了多少。 ### `deepseek-v4-pro` 按它自己的官方价目计价 2026-09-14 曾计划下线 Pro,把它的请求按 V4.1-Flash 承接并计费;[`@linxin666/dsh-usage`](https://github.com/zhu1090093659/dsh-web) 据此设了一条时间栅栏,本插件早期也照抄了它(当时 README 里那段「档位存在争议」就是这么来的)。 **官方随后决定继续提供 Pro、计费方式不变**(Pro 严格按它自己的官方价目计费,与 Flash 分开)。因此 0.5.1 起**删掉了那条栅栏**:`deepseek-v4-pro` 的请求恒计在它自己那一行(未命中 4.5/9.0、输出 13.5/27.0),`按Flash档估` 的标记与汇总提醒也一并移除——它们的前提已不成立。 > 如果你从别处又看到"Pro 已被 Flash 承接"的说法,请注意那是 2026-09-14 之前的旧计划;不要再把栅栏加回来。 ## 刷新价目表的完整流程 1. 宿主抓取**官方中文价目页**(`api-docs.deepseek.com/zh-cn/quick_start/pricing`,给人民币;英文页给美元,不用); 2. 解析那张表并校验全部 12 个单价——任何一个缺失或不合理,整份解析直接作废; 3. **把差异列给你看**,点 **采用** 才生效,也可以 **放弃**。 价目表只存在 `/dsh-bottom-cost/pricebook.json`(原子写),重启后仍然生效;抓取失败(断网、超时、官网改版)会明确报出来并**保留当前价目**;首次启动的兜底始终是内置表。 ### 能力边界 - **只有一次出网请求**:抓上面那个公开文档页,**且只在你点「刷新」时发生**。除此之外它不把任何东西发出去。 - **只写一个文件**:`/dsh-bottom-cost/pricebook.json`。 - 三条价目路由都是**仅回环 + 同源**校验(与本工作区既有 DSH 插件同一套围栏)。 - 宿主半区不读凭据、不写会话数据、不装定时器。 - 面板里的**第 N 轮**是**只读地**驱动官方的轮次导航:读取刻度按钮的 `aria-label`(含轮号)与 `aria-current`,并调用刻度自己的点击;轨道整个不可用时回落到 transcript 行上的 `data-chat-turn` 锚点。本插件**不注入节点、不改官方布局**,也不写死任何构建哈希类名——但官方若改掉这些语义属性,跳转会失效,届时面板会如实提示定位失败。 ### 为什么形状校验是手写的 DSH 的投影注册表向每个单元索取 `stateSchema` 与 `wire.viewSchema`,并且**只对它们调用 `.parse`**(`@deepseek-ai/dsh-session-projection` 里共 6 处;没有 `safeParse`、没有 `optional`、没有 `refine`)。这个契约是鸭子类型的,所以 `lib/shape.js` 只用约 90 行、零依赖地实现了本插件真正需要的七个谓词:object / string / 有限数字 / boolean / array / 枚举 / nullable。 另一条路是 `zod`(DSH 自己的 `dsh-token-meter` 就用它)。那条路不算错,但对本插件不划算:发布的包有 **840 个文件 / 5.86 MB**(哪怕只留 v4 ESM 运行时也有 111 个文件 / 886 KB),而本 README 首推的安装路径是**复制式安装、不带依赖**——要带上 zod 就只能往仓库里塞一大块第三方代码,或者让安装脚本联网跑 `pnpm` 去改写你 profile 的 `package.json`。手写让这条路径真正自足。 一处**刻意比 zod 更严**:`NaN` 与 `Infinity` 一律拒绝——它们一旦混进 token 或金额会静默污染累计值。另外,因为校验器是本仓库自己的代码,自测可以**直接测它**(zod 作为三方导入做不到这一点)。 ## 实现要点 ### 用官方扩展点,不做 DOM 注入 - 胶囊注册进内置 slot **`conversation.composer.dock`**(`order: 1`;随包发布的 `stats` 两颗胶囊是 `order: 0`)。该 slot *就是* 输入框下方那条统计栏,容器是 `display:flex; gap:12px` 的横向行,因此胶囊是内置胶囊的同级兄弟——不用 DOM 插桩,也不怕官方重渲染。 - 面板复用 `@deepseek-ai/dsh-client-ui-primitives` 的 `useAnchoredPosition` 与 `useDismissOnOutsidePointer`,与内置统计弹层同一套,所以定位、夹取、点击外部关闭、Esc 的行为完全一致。 - 样式只走主题 CSS 变量(`--dsw-*`),跟随你的主题而不是写死颜色。 ### token 事实通过会话投影传输 宿主注册一个**会话投影单元**(`sessionCost`),浏览器用 `useProjection('sessionCost')` 读。这白拿三件事: - **历史回填**:投影注册表会在首次触碰时把后注册的单元在内存日志上回放一遍,所以装上插件或重启 DSH 之后,当前对话里**已经发生**的轮次也被折进来——胶囊不会从 ¥0 开始。 - **持久化**:状态搭 harness 的投影检查点缓存,插件自己不写数据文件。 - **推送而非轮询**:值走会话控制流下发,没有定时器,也没有拉取循环。 ### 分层,便于脱离 DSH 自测 | 文件 | 职责 | | --- | --- | | `lib/fold.js` | 纯折叠:会话事件 → 按轮 token 事实。零依赖。 | | `lib/pricing.js` | 纯计价:峰谷时钟、模型→价目行判定、tokens→金额。零依赖。 | | `lib/pricebook.js` | 纯价目书:内置表、严格校验、官方页解析器。零依赖。 | | `lib/projection.js` | 薄适配:形状 schema + 客户端视图。 | | `lib/pricebook-store.js` | 抓取、暂存候选、采用、落盘。 | | `lib/routes.js`、`lib/guard.js` | 仅回环的 HTTP 翻译层。 | | `lib/client.js` | 胶囊与面板;按「事实 × 当前价目书」算钱。 | ## 自测 ```sh npm test # node tools/selftest.mjs —— 66 项,不联网 npm run test:live # node tools/live-check.mjs —— 抓真页面、解析、落盘、读回 npm run test:schema # node tools/schema-check.mjs —— schema ↔ 状态 ↔ 视图 一致性(从已安装副本导入) ``` `selftest` 覆盖峰谷与节假日例外、解析器(对着随仓库附带的官方页快照)、价目书校验、换一本价目书重算、按桶聚合、重放幂等、状态 JSON 安全、明细裁剪、以及客户端文案键集合。 `selftest` 覆盖峰谷与节假日例外、解析器(对着随仓库附带的官方页快照)、价目书校验、换一本价目书重算、按桶聚合、重放幂等、状态 JSON 安全、明细裁剪、客户端文案键集合,以及——因为校验器现在是本仓库自己的代码——**投影 schema 本身**(未声明字段被丢弃、类型不符按路径报错、`NaN` 被拒)。 `test:schema` 是另一种检查:它导入**profile 里实际装着的那份副本**,把 状态 → 视图 → schema 整条走一遍,因此同时验证「装上的文件能加载」与「`viewOf()` 与视图 schema 仍逐字段一致」。后者在开发中真的挂过一次,现在有专门的守卫。 ## 兼容性 - DSH **≥ 0.2.0-rc.2**(开发与验证均在 0.2.0-rc.2 上完成)。 - Node **≥ 20**。 - **零运行时依赖**:投影 schema 由插件自带的 `lib/shape.js` 校验。 ## 许可 [MIT](LICENSE) © 2026 LanFeng97。 有两处不是本插件原创,保留其上游署名: - `lib/holidays.js` —— 中国法定节假日日期,来自 [bastengao/chinese-holidays-node](https://github.com/bastengao/chinese-holidays-node)(MIT)。 - `lib/pricing.js` 的峰谷规则与价目行判定、`lib/pricebook.js` 的内置表 —— 与 [@linxin666/dsh-usage](https://github.com/zhu1090093659/dsh-web)(Apache-2.0)同一套口径。 ### 零运行时耦合 上面两处只是**署名**:规则与数据是**抄进本插件源码的**,本插件**不 `import` dsh-usage,也不 `import` 任何 `@linxin666/*` 包**。把 dsh-usage 卸载掉、或它升级/改名,本插件照常工作——反过来说,官方调价时这两份**副本**要各自更新(这就是「同口径」想表达的意思)。 **零运行时依赖。** 没有要顺带安装的东西,也没有要跟进同步的东西:投影 schema 来自插件自带的 `lib/shape.js`(理由见上文「为什么形状校验是手写的」)。 ## 相关 - [dsh-workspace-create-dialog](https://github.com/LanFeng97/dsh-workspace-create-dialog) —— 同一作者的姊妹插件,做「创建工作区」对话框。