dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 会话费用账单:账号余额 + 峰谷计价 + 每个会话(含子代理衍生会话)的消耗构成,输入框下方读数与右侧栏面板双入口。 DeepSeek Harness session cost breakdown: balance, peak/off-peak pricing, and how each session's spend is composed.
本插件基于 dsh-context 的源码(
main@209169d, 即 v0.66.1 之后的main)重构:src/是上游 dsh-context 的逐字源码,上游 2000+ 测试原样通过; 本仓库在其之上叠加两项自己的功能 —— **官方人民币价目表(峰谷 + 法定节假日)**与 输入框下方的常驻费用读数。
上游的代码逐行保留(除下文列出的接线文件)——注释与若干界面文案已按本仓库的
约定改写,但没有任何一条语句、表达式或类型被改动(改动范围见 NOTICE)。
因此下列上下文洞察能力全部保留:
/context 命令与 Ctrl/⌘ 弹窗;src/shared/officialRates.ts)上游只按 models.dev 的美元簿计价,并给 DeepSeek 的 peak 桶统一乘 PEAK_FACTOR = 2,
没有节假日表。本插件改为以官方人民币价目表为准:
| 模型 | 谷价(缓存命中 / 未命中 / 输出,元/百万 Token) | 峰价 |
|---|---|---|
| DeepSeek-V4.1-Flash | 0.02 / 1 / 4 | 0.04 / 2 / 8 |
| DeepSeek-V4-Pro | 0.15 / 4.5 / 13.5 | 0.3 / 9 / 27 |
holidays: ['2026-10-01', ...](YYYY-MM-DD,北京时间)覆盖。src/host/foldMetering.ts 的 isPeakAt),所以落库的桶
携带的就是当时实际适用的费率;节假日真的会记进谷价桶。src/client/components/composerDock.tsx)上游没有这个入口。收起时只占一行:本会话费用 · 余额 · 当前峰/谷;
尺寸与配色逐项对齐官方同排读数胶囊(StatsPills.module.css 的 .pill,也就是
旁边那个「48.6M tok · 缓存命中 97%」):同一个 12px 字号
(calc(var(--dsh-content-font-size-secondary, 13px) - 1px))、同一个 20px
行高、同一个 padding: 1px 8px,盒子因此同为 22px 高;圆角同为整胶囊
(999px + corner-shape: round),图标同样钉在 14×14;基色同为 label-tertiary、
悬浮同为 label-secondary、分隔符用官方的 separator-primary,且都不加粗。
并排看不会一高一矮、一圆一方,也不会像外来控件。只有峰/谷保留语义红绿——那是这一行唯一要传达的信息。
峰谷本身是实时算出来的,不是写死的标签:徽标后面跟着距离下一次翻转的倒计时,
每秒走一格(usePricingPeriod 的一秒节拍,时段没变的帧不重渲染);悬停与展开卡里还写全
峰时是哪几段(09:00–12:00、14:00–18:00,由 PEAK_WINDOWS 派生,改表即改文案)。
周末与法定节假日全天谷时,此时不报一个会让人误以为「今天还有峰时」的倒计时,
而是直接说明原因。
状态栏放不下完整读数时自动改用图标:整句「6 小时 38 分 后进入峰时」
换成时钟图标 + 紧凑读数(6h38m,悬停仍给出整句),费用、余额与峰/谷徽标照常保留。
判据是「留给本行的空间」与「完整内容需要的宽度」之比,不是拿一个写死的断点比这一行的总宽:
官方那一行里还有用量读数与上下文环,它们先占掉一截,所以总宽够、留给本行的却可能不够
(线上正是如此:行宽约 860px,官方两块占掉约 260px,留给本行的约 570px,而整句需要约 600px)。
可用宽度由「父级内容宽 − 兄弟条目需要的宽度 − 间隔」算出,需要的宽度由一句始终渲染但移出可视区
的量宽探针给出——两者都与当前渲染的是整句还是紧凑形态无关,因此不会自激
(切紧凑 → 变窄 → 又判定够宽 → 切回去)。
兄弟必须按「需要」而不是「当前占用」计:官方那两个读数胶囊是可压缩的
(min-width:0 + text-overflow:ellipsis),而本行是 flex:none;行不够宽时亏空全摊给它们,
用被压扁后的占用去算,就会出现「插件越不缩 → 官方越被挤 → 算出的余量越大」这个正反馈
(线上正是如此:官方被截成 4 ...,本行却仍是完整读数)。自然宽度(盒宽 + 被裁掉的内容)
不随 flex 分配变化,所以没有这个环。官方 StatsPills 依据的是同一个量
(min-width:0 + text-overflow:ellipsis,按实际可用宽度收缩)。
点一下展开详情卡。展开卡里嵌的就是汇总统计页的那两张卡——
Token 统计与费用统计——外加当前时段的官方价目表:同一份数据、同一种排版,
输入框下方与统计页读到的因此永远是同一回事(余额由费用卡自己那一行承担,不再重复一处)。
卡片用 position: fixed 并实测触发按钮位置,避开输入框自身的
overflow 裁剪,同时 portal 到 document.body:官方的两个浮层(ContextMeter 的
.panel、stat-dialog)都是 createPortal 到 body 的,而输入区所在的 .composerSeat
是 position: sticky 且带 z-index: 7——留在里面的话,z-index: 1100 只在那个层叠
上下文内排序,仍可能被根级元素压住。外观对齐官方 stat-dialog(stat-dialog.module.css
的 .panel,即点状态栏那枚读数胶囊浮出来的「Token 用量」面板):同一层
--dsw-specific-menu、同一个 --dsw-radius-lg 圆角、同一道发丝描边 + --dsw-elevation-prominent
阴影、同一个 z-index: 1100、同一个 padding: 16px 与 12px/18px 正文;标题行也照官方
.title + .titleRule 那套——标题 label-primary/500 字重、两端对齐、下方 8px 留白,
再压一道 0.5px 发丝线、线后 10px。
宽度与高度有意不同:官方那块只列 5 行 token 明细,本卡要装两张统计卡加一张价目表。
宽度取 min(440px, 100vw − 24px)(440px 与官方面板的上限同值):卡内几乎每个块都是
百分比宽度(.lc-dock-cards > .lc-card 的 width:100%、表格、.lc-sl 的 auto-fill 网格),
而百分比子项不贡献内在宽度,所以不能按内容定宽——那会一路塌到 264px 下限,把整张卡
压扁(图例折成两行、价目表模型名被截断)。440px 也是实测「7 行图例与 4 列价目表都不折行、
模型名不截断」的最小宽度。窄视口夹进 100vw − 24px,上限 560px,
既不会裁掉右列数值,也不会出现左右滚动条;卡内三块都显式声明 box-sizing: border-box,
宽度因此逐块对齐(基础 .lc-card 的 border-box 只声明在 .lc-root 之下,浮层里没有那个祖先)。
上方空间不足时自动翻到下方。峰谷徽标随本机时钟自动翻转。
余额由两个来源提供,按顺序尝试:① DSH 自身的账号登录(deepseekAccount 服务,不需要 API key
—— 桌面端登录账号就用这个);② API-key 提供方(没有账号服务时)。读数先画上次记住的数字,再按
60 秒周期自动校正(这个数字随每次计费请求变化,长期挂载只读一次会显示过期值)。
| 入口 | 回答的问题 |
|---|---|
| 左侧栏「统计」页 | 全部会话的汇总——Token 与费用两段分开 |
| 会话内「上下文」标签页 | 只讲上下文与 Token——页面上不出现任何金额 |
| 右侧栏「本会话汇总」面板 | 本会话汇总,分两段:Token 汇总(按上下文类别拆)与各平台的消费 |
| 输入框下方读数 | 此刻在用的那个平台的消费与余额 |
Token 与费用严格分栏:对话区那一页只讲上下文与 Token,页面上不出现任何金额 (Agent 网络节点卡也只报 token 量);费用住右侧栏「本会话汇总」面板的费用段与左侧统计页的费用段。 两侧都不把 token 数与金额并排放进同一个字符串——量纲不同,放在一起会让人以为可以互相换算。 计费 Token 卡按平台实际计费的四个桶统计,全部是 provider 上报的精确值(不是估算):
| 桶 | 含义 |
|---|---|
| 缓存输入 | cacheReadTokens:命中缓存的那部分输入 |
| 未缓存输入 | inputTokens(含缓存写入):按未命中价计费 |
| 输出 | outputTokens:已含思考 token |
src/client/components/statsPlatforms.tsx)一个会话可能同时用到多个平台,而各平台的账单单位不同:DeepSeek 官方按人民币 token 计价(含峰谷),积分制平台按次扣积分,第三方平台按美元。因此:
官方峰谷拆分(费用统计卡)只对有官方用量的会话出现,且只算当前在用的那个平台; 它同样住在费用那一段,不在上下文卡片里。
三处的费率与峰谷判定共用 client/cost.ts 的同一个入口
(estimateSessionCostByBucket),所以平台卡、卡内逐模型明细与输入框下方的读数
之间不会互相漂移。
| 本仓库 | 上游 |
|---|---|
| 官方人民币价目表 + 峰谷 + 法定节假日 | models.dev 美元簿 + 固定 PEAK_FACTOR = 2,无节假日表 |
| 输入框下方常驻读数 | 无 |
| 平台消费汇总:每个平台一张卡,卡内含逐模型明细,各按自己的账单币种 | 按界面语言换算到单一货币、平台行不摊开明细 |
| Token 与费用分栏(上下文页只讲上下文与 Token) | 费用卡与 Token 卡混排在同一个头部 |
host/config.ts 新增 holidays 字段 | 无 |
contextTimeline stateVersion 29、contextActivity 6 | 27 / 4 |
详细改动清单见 NOTICE,fork 说明见 FORK.md。
| 数据 | 来源 |
|---|---|
| 余额 | 上游 host/balance.ts:读 settings + credentials 后请求 api.deepseek.com/user/balance(插件不接触令牌) |
| 用量 | 会话日志里 assistant/message 自带的 usage,由宿主投影折叠为每(provider, model, 时段)的累计桶 |
| 价格 | 本插件的官方人民币价目表(src/shared/officialRates.ts);官方表未收录的模型走 models.dev |
| 时段 | 北京时间周一至周五 09:00–12:00、14:00–18:00(法定节假日与周末除外)为高峰 |
DSH 记录的 inputTokens 是缓存未命中的输入,cacheReadTokens 才是缓存命中的输入,
两者与 outputTokens 是互不重叠的桶。实测一条真实记录:
{inputTokens: 192, outputTokens: 238, totalTokens: 13102, cacheReadTokens: 12672}
192 + 12672 + 238 = 13102
若把 inputTokens 当成整个 prompt,就会把已缓存的 12672 tokens 按未命中价计费,
费用会被高估一个数量级。本插件按三个桶分别计价(缓存写入不单独计价,按未命中价计)。
费用为估算值,按 DSH 记录的 token 用量与官方价目表计算;实际扣费以平台账单为准。 官方表与 models.dev 都未收录的模型会计入请求数但不计费,并在界面标注,绝不会被当成免费。
这部分是上游行为,本仓库原样保留:fork / 摘要重建出来的会话是带种子的,
日志开头是祖先事件的一份拷贝,而那些请求在祖先会话里已经计过费。
宿主折叠在 session/end-seed { inherited: true } 标记处截断继承前缀,
因此子会话只计自己真实发生的调用。
本仓库的包名是 dsh-cost-breakdown,与上游 dsh-context 不同名,两者可以并存
(不建议同时启用:两套上下文入口会在界面上重复)。
从 GitHub 源安装(仓库自带编译产物,安装时不需要构建):
dsh plugin --profile web add github:liaimei/dsh-cost-breakdown
不要在
package.json里加prepare/postinstall之类的生命周期脚本。lib/是入库的产物,安装时无需构建;而 pnpm 从 git 拉依赖时会执行这些脚本, 一旦存在就会被ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED拦下——消费端只能把 带 commit SHA 的包名加进allowBuilds,等于每推一次改一次白名单。 构建请走显式的pnpm run build。
从本地检出安装(开发时用,link: 让改完 lib/ 刷新页面即生效):
dsh plugin --profile web add "dsh-cost-breakdown@link:/path/to/dsh-usage-meter"
装完重启 DSH(宿主半区不热生效),然后输入框下方会出现常驻费用读数。
dsh plugin list:该子命令并非只读——它会触发一次 reconcile
并落盘(实测会移除 lock 里已不再声明的依赖)。直接看
~/.dsh/profiles/<profile>/package.json 与 node_modules/ 更安全。POST /api/dsh-cost-breakdown/skills(带会话 cookie)
应返回 200;返回 404 说明跑的还是旧代码,重启 DSH 即可。dsh.profile.bundles 里 dsh-cost-breakdown 只出现一次,
且 cordis.patch.yml 里没有指向已移除包的 - id: <旧包名> 死条目。源码是 TypeScript(src/),产物是 lib/,由 tsdown 打包成两个入口:
| 源文件 | 产物 | 说明 |
|---|---|---|
src/host/index.ts | lib/index.js + lib/index.d.ts | 宿主半区(ESM)。改完需重启 DSH |
src/client/index.ts | lib/client.js | 浏览器半区(CJS,包在 window.__ModuleLoader__.load 里)。改完刷新页面即可 |
pnpm install
pnpm run build # tsdown -> lib/
pnpm run typecheck # tsc --noEmit -p tsconfig.tests.json
浏览器半区的依赖有纯净性闸门(tsdown.config.ts):只有 react / react-dom /
dsh-client-ui-primitives 可以留成运行时 require,其余必须内联;新增的可内联依赖要同时
加进 onlyBundle。
NODE_OPTIONS=--localstorage-file=/tmp/ls.db pnpm exec vitest run --coverage
tests/compat/bundle.spec.ts 直接加载构建产物 lib/client.js,验证交接协议、
CSS 通道与槽位注册,所以跑之前要先 pnpm run build;tests/shared/officialRates.spec.ts(价表 / 峰谷边界 / 节假日 / 回退)、
tests/client/components/composerDock.spec.ts(常驻读数与展开卡)、
tests/client/components/statsCost.spec.ts(费用统计卡:分桶 / 峰谷 / 余额 / 空状态)。tsdown:macOS 会拒绝 app 签名内 rolldown 的原生 .node
(different Team IDs)。用普通 Node(/opt/homebrew/bin/node)构建,运行时不受影响。localStorage:不给 --localstorage-file 时它是 undefined,
会让十个客户端 spec 失败(上游同样如此)。因此命令里带
NODE_OPTIONS=--localstorage-file=/tmp/ls.db。holidays: ['2026-10-01', ...] 覆盖,或更新 src/shared/officialRates.ts 里的 DEFAULT_HOLIDAYS。| 项 | 说明 |
|---|---|
| 读会话日志 | 只读观测本机会话事件,用于统计 token 用量 |
| 读账户余额 | 通过上游 host/balance.ts 读 settings + credentials 后请求 api.deepseek.com(插件不接触令牌) |
| 外部网络 | 前端 models.dev 价目表(官方表未收录的模型才需要)+ 余额查询;其余全部本地计算,不上报任何数据 |
| 本地存储 | 只存上次记住的余额(localStorage);不写文件、不建数据库 |
| 遥测 | 无 |
| 写操作 | 无。插件只读,不修改会话、配置或代码 |
Apache-2.0 © 2026 liaimei(fork 自 Apache-2.0 的 dsh-context,见 NOTICE)