dsh-peak-usage
给 DeepSeek Harness 的额度面板插件。装上之后,输入框下面会多出一行实时读数:
高峰 | 本次消费 ¥0.1043 | tokens 100.0k↑ / 4.5k↓ | 缓存命中 88% | 余额 ¥18.32
四个数字分别是:当前是高峰还是空闲时段、本次会话消费金额、本会话 token 用量、DeepSeek 账户余额。
- 金额按 DeepSeek 官方的峰谷分时定价计算,逐请求判定时段——不是拿当前时段的价格去乘全部 token。
- 余额从官方
GET /user/balance实时读取,三层机制保证它持续更新。 - 内置官方价格表(含核对日期与来源),装完即可算钱,不需要先手抄一遍单价。
- 零 npm 依赖,零构建步骤。 安装就是拷文件;
lib/client.js手写的就是最终产物。
独立第三方插件,面向 DSH 的
webprofile。验证环境是社区项目 DeepSeek-Harness-Desktop 打包的桌面端(v0.2.18)+ 官方 harness@deepseek-ai/dsh0.1.1-rc.2。与 DeepSeek 官方及该桌面端作者均无隶属或背书关系。详见适用环境与归属。
┌───────────────── 宿主进程(Node) ─────────────────┐
│ ctx.credentials.resolve('DEEPSEEK_API_KEY') │ key 永不出宿主
│ │ │
api.deepseek.com ◄──── GET /user/balance ──► TTL + 单飞 + 节流 │
│ │ │
│ Session.events ──► 按每个样本的 time 折叠峰/谷分桶 │
│ │ │
│ ctx.webServer.register('/plugin/dsh-peak-usage/...') │
└────────────┬───────────────────────────────────────┘
│ 同源 fetch(15s 兜底 + 用量变化即刷)
┌────────────▼───────────────────────────────────────┐
│ 浏览器:conversation.composer.dock 槽位的一行读数 │
│ + useProjection('tokenUsage') ← 框架推送的投影 │
└────────────────────────────────────────────────────┘
适用环境与归属
- 插件面向的是 DSH(DeepSeek Harness)的
webprofile,与具体客户端外壳无关——任何能跑dsh web的环境都应该能用。 - 开发与验证环境是社区项目 web-casa/DeepSeek-Harness-Desktop 打包的桌面端
DSH Desktop0.2.18(bundle idcom.yeagoo.dsh-desktop,Tauri + WKWebView),它内置的 harness 是官方@deepseek-ai/dsh0.1.1-rc.2(deepseek-ai/deepseek-harness)。「桌面端外壳」是社区项目,「harness 内核」是官方项目,两者不是一回事。 - 本插件是独立第三方项目,与 DeepSeek 官方、与该桌面端的作者均无隶属或背书关系。
- 已测试:harness
0.1.1-rc.2+ DSH Desktop0.2.18。
兼容性提示
这个插件读的是 harness 的内部约定而不是稳定 API,所以 harness 升级后可能需要适配。依赖面如下:
| 依赖 | 用途 | 敏感度 |
|---|---|---|
ctx.webServer.register() |
宿主→浏览器的承载通道 | 低(公开插件契约) |
ctx.credentials.resolve() |
取 API Key | 低 |
ctx.timeout / ctx.interval |
余额保活与延迟补刷 | 低 |
ctx.sessions.get() 与 Session.events |
读会话日志做峰谷折叠 | 中 |
SessionEvent.time、assistant/message 与 assistant/chunk 的 usage 形状 |
逐请求判定峰谷时段 | 高(最易变) |
槽位 conversation.composer.dock 及其 sessionId / useProjection props |
界面挂载点 | 中 |
内建投影 tokenUsage |
前端实时 token 数 | 中 |
harness 目前是 -rc 预发布版本,接口变动属于正常。升级后请跑 npm test 与 bash verify.sh:测试挂了就说明上面某项变了。
安装
包名与仓库名不同:npm 包名是
dsh-peak-usage,GitHub 仓库名是dsh-plugin-usage。 npm 上的dsh-plugin-usage已被另一个同类插件占用,所以包名加了peak——顺带把「峰谷计价」这个差异化写进了名字。装的时候用包名,读源码用仓库名。
方式一:插件市场(推荐)
DSH Desktop 内置了 cordis.run 插件市场。打开设置 → 插件,搜索 dsh-peak-usage 即可安装。
命令行等价做法:
dsh plugin web add dsh-peak-usage
方式一之补充:从文件安装(sideload,免审核)
想让人立刻用上、不等市场审核,就把插件打成 .tgz 发给对方,在应用里用「从文件安装」选中它:
bash pack.sh # 生成 dist/dsh-peak-usage-<版本>.tgz
⚠️ 必须是
.tgz,.zip一定被拒。 桌面端的原生校验写死了sideload file must end with .tgz、sideload path must be absolute,而且安装动作就是pnpm add <绝对路径>.tgz。所以别拿 zip 去试。pack.sh两个格式都会生成,zip 只是给人解压看的。
方式二:从源码手动安装
插件只改 $DSH_HOME(用户数据目录),不碰应用本体。
git clone https://github.com/Mirfakk/dsh-plugin-usage.git
cd dsh-plugin-usage
bash install.sh
install.sh 做三件事,且幂等(重复跑不会插两次):
- 把包拷到
$DSH_HOME/profiles/web/node_modules/dsh-peak-usage - 往
$DSH_HOME/profiles/web/cordis.patch.yml插一行插件条目 - 跑静态自检(补丁语法 + profile 能否解析到包 + bundle 是否存在)
DSH_HOME 的取值顺序:环境变量 → DSH Desktop 的 ~/Library/Application Support/com.yeagoo.dsh-desktop/harness → 命令行版 dsh 的 ~/.dsh。也可以显式指定:DSH_HOME=/path/to/home bash install.sh。
然后:
- 重启 DSH Desktop —— Web profile 里
hmr是被显式禁用的(dsh-web-app/cordis.patch.yml里- id: hmr / disabled: true),所以改cordis.patch.yml不会热挂载,必须重启进程。 - 刷新页面 —— 浏览器半的 bundle 要进
window.__DSH_BOOT__启动图,只在页面加载时装配。 bash verify.sh—— 在线自检(宿主路由 / boot 图 / bundle 可下载 / 同源闸门)。
峰谷分时定价
DeepSeek 官方价格页的口径(来源):
空闲时段价格为高峰时段价格的一半。北京时间周一至周五(不含中国法定节假日)9:00–12:00、14:00–18:00 为高峰时段;其余时段,包括周末及中国法定节假日全天均为空闲时段。
内置的官方价表(元 / 百万 tokens,核对于 2026-09-21):
| 模型 | 时段 | 输入·缓存命中 | 输入·未命中 | 输出 |
|---|---|---|---|---|
deepseek-flash |
高峰 | 0.04 | 2 | 8 |
deepseek-flash |
空闲 | 0.02 | 1 | 4 |
deepseek-v4-pro |
高峰 | 0.30 | 9.0 | 27.0 |
deepseek-v4-pro |
空闲 | 0.15 | 4.5 | 13.5 |
为什么金额必须逐请求判定时段。 空闲价正好是高峰价的一半,所以「拿当前时段的价格乘全部 token」在任何跨时段的会话上都会偏大最多一倍。本插件折叠会话日志时,用的是每个用量样本自己的 event.time,所以一次跨越 12:00 边界的会话会把两侧分别计价。鼠标悬停在金额上能看到高峰/空闲的分项。
模型别名是必需的,不是可选的糖。 官方脚注写明 deepseek-v4-flash、deepseek-v4-flash-vision-exp 仍是可调用的旧名,由 DeepSeek-V4.1-Flash 提供服务并按 Flash 价格计费。而 harness 的默认模型恰好就叫 deepseek-v4-flash,所以插件内置了别名映射(deepseek-v4-flash → deepseek-flash)。
中国法定节假日要自己填
官方节假日由国务院每年公告,代码里算不出来。插件默认清单为空——后果只是「节假日按工作日规则计价」,平日的账不会算错。要精确的话,在配置里列出来:
pricing:
schedule:
holidays: ['2026-10-01', '2026-10-02', '2026-10-03', '2026-10-04',
'2026-10-05', '2026-10-06', '2026-10-07']
余额为什么总是新的
「实时」不能靠一个定时器就算数,所以这里叠了三层:
| 层 | 机制 | 解决的问题 |
|---|---|---|
| ① 后台保活 | 宿主每 pollIntervalMs(默认 15s)刷一次 |
即便用户什么都不做,读数也持续前进 |
| ② 用量触发 | 浏览器在 tokenUsage 投影变化时立刻请求 ?fresh=1 |
用量变化 ⟺ provider 刚上报样本 ⟺ 钱刚花掉,此刻最该刷新 |
| ③ 延迟补刷 | 强制刷新后 settleDelayMs(默认 3s)再刷一次 |
官方账单往往一两秒才扣完,那一刻读到的可能还是扣费前的余额 |
②③ 都可能很频繁,所以宿主侧还有一层 minRefreshIntervalMs(默认 3s)节流:强制刷新保证的是「不晚于这个间隔」,不是「每一次都出网」。这是保护官方接口不被自己打爆的关键。
读数本身携带新鲜度:每次响应都带 fetchedAt / ageMs,超过两个保活周期没推进时界面会显示 余额 ¥18.32 ?,而不是假装它新鲜。刷新失败时保留上一次成功的读数,并在 tooltip 里说明失败原因。
配置
编辑 $DSH_HOME/profiles/web/cordis.patch.yml 里 dsh-peak-usage 那段。Web profile 的 HMR 是关闭的,所以宿主半的配置改动需要重启进程才会生效。
| 键 | 默认 | 说明 |
|---|---|---|
endpoint |
/plugin/dsh-peak-usage/summary |
路由路径,改了要同步改 lib/client.js 顶部的 ENDPOINT |
credentialsRef |
DEEPSEEK_API_KEY |
凭证引用名,与 llm-deepseek 的 apiKeyEnv 一致 |
baseURL |
https://api.deepseek.com |
官方 API 根地址 |
pollIntervalMs |
15000 |
后台保活周期 |
minRefreshIntervalMs |
3000 |
两次真实出网之间的最小间隔 |
settleDelayMs |
3000 |
强制刷新后延迟补刷的间隔,0 表示不补 |
requestTimeoutMs |
8000 |
单次余额请求超时 |
pricing.enabled |
true |
false 只显示 token 与余额,不算钱 |
pricing.currency |
CNY |
价表币种,同时决定取余额响应里的哪个币种 |
pricing.fallbackModel |
deepseek-flash |
会话模型不在价表里时用哪一行 |
pricing.models.<名>.peak/offPeak |
官方值 | 按「模型 × 时段 × 字段」深合并覆盖 |
pricing.schedule.peakWindows |
['09:00-12:00','14:00-18:00'] |
高峰窗口,北京时间 |
pricing.schedule.peakWeekdays |
[1,2,3,4,5] |
高峰星期,1=周一…7=周日 |
pricing.schedule.holidays |
[] |
法定节假日 YYYY-MM-DD 清单 |
只覆盖一个字段是完全合法的——其余字段继续取官方值,包括另一个时段:
pricing:
models:
deepseek-flash:
peak:
output: 8.5 # 只改这一项;offPeak 与其它字段保持官方值
配置写错不会让插件加载失败:非法窗口 / 星期 / 日期都会被丢弃并记进 warning,随读数一起回给前端(悬停在余额或金额上可以看到)。
自检
npm test # 等价于 bash test/run.sh
测试与脚本(
test/、install.sh、verify.sh、publish.sh)随仓库发布,不随 npm 包发布——npm 包装的是运行所需的 6 个文件。要跑测试请克隆仓库。
六个套件、97 项断言,全部离线(余额接口打桩,不联网):
| 套件 | 覆盖 |
|---|---|
test/peak.mjs |
峰谷判定:窗口边界(左闭右开)、周末、节假日、跨午夜窗口、UTC+8 固定换算(与机器时区无关) |
test/pricing.mjs |
官方价表逐项、空闲价 = 高峰价的一半、模型别名归一、深合并、逐桶计费 |
test/fold.mjs |
日志折叠:同一步的早期/最终样本只计一次、跨时段分类、替换样本跨边界、增量缓存、坏输入 |
test/handler.mjs |
宿主路由契约:含失败路径、同源闸门、新鲜度节流 |
test/preflight.mjs |
浏览器半能否执行、求值、占槽(只 require 白名单模块) |
test/render.mjs |
端到端:宿主路由的真实响应 → 浏览器半渲染出的那一行读数 |
test/render.mjs 是最有用的那一个:它跑真实的宿主处理器拿到 JSON,再喂给打桩的 fetch 让浏览器半渲染,所以「宿主改了字段名但前端没跟上」这类错误一定会被抓住。
升级时会发生什么
插件由两半组成,而它们各自在不同的时机换版本:
| 半 | 何时换版本 |
|---|---|
宿主半(lib/index.js) |
重启 DSH 进程时 |
浏览器半(lib/client.js) |
刷新页面时 |
Web profile 的 HMR 是关闭的,所以升级插件后必须重启进程 + 刷新页面。为了让这个窗口期不至于变成一句看不懂的 本次消费 —,响应里带了一个协议版本号 protocol:两边对不上时,那行读数会直接显示「宿主半版本不匹配,请重启 DSH 后刷新页面」。bash verify.sh 也会明确报出来。
改动响应结构时,请同时把 lib/index.js 的 PROTOCOL 与 lib/client.js 的 PROTOCOL 一起 +1。
已知限制
- 金额是估算,不是账单。 它等于「token × 你配置的单价」,官方没有对账接口。实际扣费以官方账单为准。
- 法定节假日需要手工维护。 见上文。
- 只统计本会话。 跨会话 / 按天累计需要宿主落盘记账,本版本没有做。
- 冷会话会降级。 会话未附着到本进程时拿不到 live log,此时金额按「当前时段」粗算并显示
≈前缀;余额不受影响。 - 金额跟着轮询走。 token 数是实时的(走内建投影推送),但金额要等宿主算完——用量变化时会立刻触发重拉,所以实际延迟是一个本地 HTTP 往返,不是 15 秒。
- HTTP 路由不在 DSH 的
/api浏览器信任围栏内(那道围栏归 connection 插件所有)。插件自己加了同源闸门:带Origin的请求必须与Host同源,sec-fetch-site: cross-site一律 403。服务默认只绑127.0.0.1,且这条路由只读。 - API Key 永远不进浏览器,宿主解析后只在该次请求的
Authorization头里用一次。
为什么不用官方的 Remote 通道
DSH 有正规的 Host↔Client 通道 ctx.remote.$mount(),但它的 README 写明 "Only strict generated contributions can mount on the Client face"——必须用仓库里 Typert 代码生成出的 descriptor,而且 dsh-api-remotes 的能力集是编译期写死的(目前只挂了 Goal 与 pluginInventory)。外挂插件挂不上去。
所以宿主半自己注册一条 ctx.webServer 路由,浏览器半 fetch 同源路径。代价是绕过了 /api 信任围栏(用同源闸门补上),换来的是这个插件不需要 TypeScript、不需要打包器、不需要 node_modules。
目录
| 路径 | 作用 |
|---|---|
lib/peak.js |
峰谷时段判定(纯函数,UTC+8 固定偏移) |
lib/pricing.js |
官方价表、模型别名、逐桶计费(纯函数) |
lib/usage-fold.js |
把 Session.events 折叠成按峰谷分桶的 token(纯函数 + 增量缓存) |
lib/index.js |
宿主半:凭证、余额、模型解析、HTTP 路由 |
lib/client.js |
浏览器半:window.__ModuleLoader__.load 注册 + composer.dock 槽位组件 |
install.sh / verify.sh |
安装 / 在线自检 |
test/ |
离线测试 |
No comments yet. Be the first to write one.