dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
给DeepSeek Harness Web GUI用的会话token计费插件:在会话标题栏显示本次会话的实时花费,点开可以看输入 / 缓存命中 / 缓存写入 / 输出 / 推理的分项、按模型的账单,以及当前用的是高峰还是空闲时段价。
给 DeepSeek Harness(DSH)Web GUI 用的 会话 token 计费插件:在会话标题栏显示本次会话的实时花费,点开可以看输入 / 缓存命中 / 缓存写入 / 输出 / 推理的分项、按模型的账单,以及当前用的是高峰还是空闲时段价。旁边还有一枚账户余额徽标,显示那个为这些 token 付钱的 DeepSeek 钱包还剩多少。
它是一个真正的插件包(不是一个临时脚本):Host 半注册一个 tokenBilling 会话投影负责算钱,并开一条只读的余额路由;浏览器半只负责显示,自己不碰任何密钥。
会话标题栏右侧: ● ¥0.0132 13.4k ● 余额 ¥342.18
│ │
│ └── 点一下(28px 官方头部控件规格)
│ ┌──────────────────────────────────────┐
│ │ DeepSeek 开放平台账户 [刷新] ×│
│ ├──────────────────────────────────────┤
│ │ ¥342.18 │
│ │ API Key 对应的账户 · 更新于 14:07:31 │
│ │ 账户总余额 ¥342.18 │
│ │ 充值余额 ¥342.18 │
│ │ 赠送余额 ¥0 │
│ │ 每 60 秒自动刷新。余额是账户可用额度,│
│ │ 与旁边的本会话花费是两回事。 │
│ └──────────────────────────────────────┘
└── 点一下
┌──────────────────────────────────────┐
│ 本会话 Token 计费 [高峰计价] [¥CNY] ×│
├──────────────────────────────────────┤
│ ¥0.0132 │
│ 13.4k tokens · 3 次调用 · 2 轮 │
│ 输入 · 未命中缓存 1.0k │
│ 输入 · 缓存命中 10.0k │
│ 输出 2.4k │
│ 其中推理 500 │
├──────────────────────────────────────┤
│ 按模型 │
│ deepseek-flash 13.4k · 3 次 ¥0.0132
├──────────────────────────────────────┤
│ 官方价目(人民币 / 百万 tokens) │
│ deepseek-flash 命中 ¥0.04 · 未命中 ¥2 · 输出 ¥8
│ deepseek-v4-pro 命中 ¥0.30 · 未命中 ¥9 · 输出 ¥27
└──────────────────────────────────────┘
面板皮肤、分项网格、芯片几何都照搬官方组件(stat-dialog.module.css / ui-primitives 的 Tag),token 与依据见文末「已知限制」。
需要:DSH(Web / web profile)· Node 22+ · pnpm 在 PATH 上(dsh plugin 会转发给它)。
# 1) 从 GitHub 安装(最省事:不经过任何本地文件,profile 也不会记住路径)
dsh plugin --profile web add github:<you>/dsh-token-billing
# 2) 从 Release 里下载的 tarball 安装(可校验,适合离线转发)
dsh plugin --profile web add ./dsh-token-billing-0.2.3.tgz
# 3) 从目录安装(改代码时用这个)
dsh plugin --profile web add C:\path\to\dsh-token-billing
dsh plugin add 会做两件事:把这个包作为依赖装进 profile,并因为它声明了 dsh.bundle 而把 cordis.patch.yml 里那一行加进组合层。
lib/是提交进仓库的,这不是疏忽。lib/index.js(Host 半)与lib/client.js(浏览器半)是 DSH 真正加载的两个产物。pnpm 默认拒绝执行 git 依赖的构建脚本(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED),所以仓库不能靠prepare在安装时现编译——那样add github:…会直接失败。把lib/提交进去,是让 git 安装、tarball 安装、目录安装三条路都能工作的前提。CI 会重新构建并 difflib/,防止它与src/脱节。
装 tarball 就别删那个 tarball。 profile 记住的是文件的路径(例如
file:C:/…/dsh-token-billing-0.2.3.tgz),文件一旦不在了,后续任何dsh plugin/pnpm install都会以ENOENT失败(作者实测踩过一次;恢复方式是先dsh plugin --profile <name> remove dsh-token-billing去掉失效引用,再重新add)。把它放在稳定目录,或者直接装目录 / 从 npm / 从 git 装,就没有这个约束。
从目录(
link:)安装时,插件目录自己必须有node_modules。dsh plugin add <目录>装出来的是link:依赖,而 pnpm 不会为link:依赖安装它自己的依赖;运行时又是从被链接目录的真实路径去解析zod的。所以目录里没有node_modules时,profile 的node_modules里不会有zod,插件的import { z } from 'zod'也解析不到 —— 后果是整行不激活:dsh: warning: 1 entry did not activate token-billing (dsh-token-billing): failed to import徽标会整枚消失(不是显示
--)。修法就是在插件目录跑一次pnpm install(作者实测踩过一次)。tarball / npm / git 安装没有这个问题,因为那时依赖装在 profile 里。
# 2) 不启动也可以先看组合层(注意:--dump-config 会重写 profile 里的 cordis.yml,需要该目录可写)
dsh --profile web --dump-config # 应该能看到 "# == dsh-token-billing" 这一层
# 3) 想确认它能被真正挂载,又不启服务器:--help 会组装并挂载整棵插件树但不绑定端口
dsh --profile web --help # 有任何包解析不到、或某一行始终不激活,这里会报出来
# 4) 重启该 profile —— 运行中的 profile 不会热更新 bundle 集合
重启后打开任意会话,标题栏右侧就会出现那两枚徽标。本会话还没有账单记录时花费显示 ¥--,发出第一条模型回复后立即变成实际金额(投影是推送式的,不需要刷新页面);余额徽标在第一次查询返回后显示金额。
卸载:dsh plugin --profile web remove dsh-token-billing(依赖和组合层一起移除),然后重启。
任何 profile 都能手工加这一行,效果一样:
# $DSH_HOME/profiles/web/cordis.patch.yml
- insert:
- id: token-billing
name: dsh-token-billing
前提是这个包在 profile 的 node_modules 里能解析到(例如它已作为依赖装进该 profile,或放在 DSH 的模块回退目录下)。
reasoning 已经含在 output 里(官方 completion_tokens 包含推理),只做展示、不重复计费。缓存写入按未命中输入价计(DeepSeek 不单独收缓存写入费)。tokenUsage 投影同一套采样规则 —— 同一个 turn/step 的再次上报是替换而不是累加,llm/retry-started 会关闭替换位。这样徽标上的 token 数与 GUI 里既有的用量面板保持一致。deepseek-chat、deepseek-reasoner、deepseek-v4-flash 等归一映射到现行价格行。内置默认(高峰价,单位:每百万 tokens):
| 币种 | 模型 | 缓存命中 | 缓存未命中 | 输出 |
|---|---|---|---|---|
| CNY | deepseek-flash | ¥0.04 | ¥2 | ¥8 |
| CNY | deepseek-v4-pro | ¥0.30 | ¥9 | ¥27 |
| USD | deepseek-flash | $0.006 | $0.3 | $1.2 |
| USD | deepseek-v4-pro | $0.044 | $1.32 | $3.96 |
官方声明价格可能调整。看到变动就按下面任一种方式改,别改
lib/。
余额不是算出来的,是问出来的,而且只有 Host 半去问。浏览器半只调用本包自己的一条同源只读路由(GET /token-billing/balance),拿到的是数字,不是凭据 —— 页面里既没有 API Key,也没有任何指向 DeepSeek 的跨站请求。
Host 半按这个顺序取数:
ctx.deepseekAccount.getBalance())。这是设置 → 账户里显示的那个平台账号,取到的是「充值余额 + 赠送余额」两个钱包;它们本来分列,总额是插件自己加的。ctx.credentials 解析 DEEPSEEK_API_KEY(或配置里指定的名字),再请求 GET https://api.deepseek.com/user/balance。这一条不需要授权任何账号,只要有可用的 Key 就行。两条路都取不到时,徽标显示 余额 --,面板里说明原因(没登录也没 Key / 网络不通 / 接口超时 / 接口拒绝 / 返回无法识别),不会把失败显示成 ¥0。
其他行为:
refreshMs(默认 60 秒),失败结果复用 failureMs(默认 15 秒)。所以多个徽标、多个标签页合起来每次刷新周期最多打一次上游接口;面板上的「刷新」按钮会带 ?refresh=1 跳过缓存。refreshMs 请求一次本地路由,不直连 DeepSeek。balance.enabled: false 时 Host 半不查询上游,只回一个「已关闭」的答复,浏览器半据此整枚隐藏余额徽标(而不是留一个永远坏掉的徽标)。价格是组合配置,不是硬编码。在自己的 profile patch 里覆盖这一行即可 —— patch 会整体替换该行的 config,所以要把用到的键写全:
# $DSH_HOME/profiles/web/cordis.patch.yml
- id: token-billing
config:
prices:
CNY:
deepseek-flash: { hit: 0.04, miss: 2, output: 8 }
deepseek-v4-pro: { hit: 0.3, miss: 9, output: 27 }
my-local-model: { hit: 0.1, miss: 1.5, output: 6 } # 新增一行
USD:
deepseek-flash: { hit: 0.006, miss: 0.3, output: 1.2 }
aliases:
my-gateway-model: deepseek-flash # 把自定义路由映射到已有价目
没写的键继续用内置默认值。改完重启 profile,历史账单会立刻按新价重算(折叠状态里只有 token,没有任何金额,所以改价不会让缓存失效、也不需要重放日志)。
# $DSH_HOME/profiles/web/cordis.patch.yml
- id: token-billing
config:
balance:
enabled: true # false = 不查上游,隐藏余额徽标
apiKeyEnv: DEEPSEEK_API_KEY # 用哪个凭据引用去解析 API Key
baseUrl: https://api.deepseek.com # 自建网关就改成自己的地址
path: /user/balance
refreshMs: 60000 # 成功结果缓存多久
failureMs: 15000 # 失败结果缓存多久
timeoutMs: 10000
route: /token-billing/balance # 浏览器读数的同源路由
同样地,patch 会整体替换 config,所以覆盖余额时别把 prices / aliases 弄丢;只想改价格就只写 prices / aliases 即可(余额键会回到默认值)。
改 src/host.js 顶部的 DEFAULT_PRICES / DEFAULT_ALIASES,然后:
node build.mjs # 重新生成 lib/
npm pack # → dsh-token-billing-0.2.3.tgz
对方 dsh plugin --profile <name> add ./dsh-token-billing-0.2.3.tgz 即可。也可以直接发目录 / 发布到 npm / dsh plugin add github:<you>/dsh-token-billing。
npm pack 打包,不要用 tar czf。 npm/pnpm 安装 tarball 时会剥掉第一层路径分量,所以 tarball 顶层必须是 package/ 这个包裹目录。npm pack 会自动加上;tar czf 打出来的扁平包会让 lib/、src/、test/ 塌缩进包根目录(lib/index.js 变成 index.js,src/client.js 还会覆盖 lib/client.js),装完 main 指向的文件不存在 —— 整行不激活、徽标整枚消失。CI 里有一条断言专门盯这个。lib/),所以对方安装时不需要构建权限、不需要允许构建脚本、不需要联网(zod 是唯一依赖,pnpm 会装)。lib/ 提交在仓库里,所以 add github:… 与装 tarball 得到的是同一份东西。本包没有 prepare 脚本是有意的 —— pnpm ≥10 默认拒绝执行 git 依赖的构建脚本(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED),留着 prepare 反而会让 git 安装直接失败。想改代码就 node build.mjs 手动构建,CI 会检查 lib/ 与 src/ 是否同步。dsh-token-billing/
├── package.json dsh.bundle(组合层)+ dsh.client(浏览器半)
├── cordis.patch.yml 插入那一行;注释里写了怎么覆盖价格与余额
├── src/host.js Host 半源码:tokenBilling 会话投影(含价目与折叠)+ 余额路由
├── src/client.js 浏览器半源码:两枚徽标 + 两个面板(CJS bundle 主体)
├── build.mjs 零依赖构建:拼出 lib/index.js 与 lib/client.js
├── lib/ 构建产物(提交/发布的就是它,装的时候直接读它)
│ ├── index.js ← src/host.js
│ └── client.js ← src/client.js 包上 window.__ModuleLoader__.load({...})
└── test/projection.test.mjs 34 个用例:折叠、计价、配置覆盖、余额两条取数路径与缓存、bundle 装载与渲染
ctx.sessionProjections.register() 注册一个 tokenBilling 投影单元(和官方 sessionStats、tokenUsage 同一套机制)。投影是纯函数折叠 + 框架负责派发,所以:durable 日志就是唯一事实来源、分页和压缩都不会改变数字、结果会按 session.seq 缓存、并且天然推送到浏览器(useProjection('tokenBilling'))。wire.view 在出线时套用 —— 这就是「改价不用重放日志」的原因。余额同理不进任何折叠状态。lib/client.js 复刻了 packages/client/tsdown.client.ts 的产物契约(window.__ModuleLoader__.load({ id, factory: (require) => {...} }),classic script,require 只取基线模块 react / react-dom)。这样整包零构建依赖:不需要 TypeScript、不需要 tsdown、不需要 monorepo。两个面板都用 ReactDOM.createPortal 挂到 document.body(官方 stats 面板也是这个做法),所以不会被标题栏裁掉,也不需要跨入口共享 store。pnpm install # 或 npm install —— 本包只有一个运行时依赖
node build.mjs # 改完 src/ 后重新生成 lib/
pnpm test # = node test/projection.test.mjs
zod,而且它是必需的:投影注册表的契约本身就是 zod schema(stateSchema / wire.viewSchema)—— 框架在恢复持久化折叠状态、以及每次把 wire 值发给浏览器之前,都会调用它们的 .parse()。装进 profile 时由 dsh plugin add 跑 pnpm 自动装上;裸目录里要跑构建或测试,先 pnpm install。node_modules/ 与 pnpm-lock.yaml 只服务本地开发:files 白名单里没有它们,不会进 tarball。建议把 pnpm-lock.yaml 提交到仓库,安装才可复现。src/ 之后一定要重新 build.mjs —— 运行时读的是 lib/,不是 src/。build.mjs 会拒绝把含 import/export 语句的浏览器主体打进 bundle(那种文件会被模块加载器判为解析失败)。pnpm test / node test/projection.test.mjs 跑;别用 node --test test/,它会为每个测试文件 spawn 子进程,在受限沙箱里会因管道 EPERM 失败。ctx.locale 与字典注册)。要英文界面直接改 src/client.js 里的字符串并重新构建。src/host.js 的 DEFAULT_PRICES 和 CURRENCIES 里加上,并在 src/client.js 的 symbolOf 里给出符号。refreshMs 轮询,所以刚发生的大额消费可能要过几十秒才反映在徽标上;想立刻看就点面板里的「刷新」(它会跳过 Host 端缓存)。GET /user/balance(官方直接给出总额与两个分项)。两条都拿不到时显示 -- 并说明原因,绝不会把失败显示成 ¥0。webServer 上的同源路由;纯终端 / ACP 环境里路由不存在,徽标也不会出现(Host 半用 ctx.inject(['webServer'], …) 注册,缺这个服务时插件照常加载,投影仍然工作)。conversation.session.header.utilities 这个 slot(由 @deepseek-ai/dsh-client-ui-conversation 声明)。纯终端 / ACP 等没有 Web 会话头的界面看不到徽标;Host 半的投影仍然照常工作,tokenBilling 键对其他消费者(例如自定义视图)也是可读的。--dump-config 不是只读的:它会重写 profile 里的 cordis.yml,在受限环境下需要该目录写权限。0.5px solid var(--dsw-alias-border-l4) 发丝边框、14px 胶囊圆角、11px/400 字号、hover 用 --dsw-alias-interactive-bg-hover 铺底),规格抄自 packages/client/ui-open-in-app/src/client/OpenInAppAction.module.css。注意 --dsw-alias-border-l4、--dsw-alias-interactive-bg-hover、--dsw-font-family 不在运行时 token 查询/覆盖列表里(那份列表只暴露 13 个 alias,边框类只有 -l1/-l2),但它们在上游主题表里真实存在、官方组件也在用,因此在页面里能正常解析;只是不能用 theme.overrideTokens 覆盖它们。MIT,见 LICENSE。