niliemi/dsh-billing ↗★ 0

yoka-dsh-billing

提供会话与全局 Token 计费及额度阻断 适合需要严格控制 DeepSeek 消费、设置单会话或全局预算上限的用户。

包名
yoka-dsh-billing
兼容性
待验证
版本
0.2.8
许可证
MIT
最近更新
2026年9月30日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:niliemi/dsh-billing

yoka-dsh-billing · DSH 计费插件

在 DSH Desktop 里按 token 计费:聊天输入框下方、token 环左边常驻显示本会话与全局两条费用,左侧边栏底部显示 DeepSeek 账号余额;每条费用右边有一个 ↺ 可重置计费起点(本次归零、历史只进累计),达到上限时宿主在该轮开始前阻断 LLM 请求。

● ¥0.0834 / ¥50.00 ↺      ● ¥12.3600 / ¥1000.00 ↺
  本会话本次计费 / 上限         全局本次计费 / 你设的全局上限
  • 左侧边栏底部(设置按钮旁):余额 ¥1007.20(充值 + 赠金,只作参考)。
  • 点角标打开面板;点 ↺ 重置计费起点;超额时弹确认框。
  • 圆点分三档:**绿 &refresh=1| 角标/面板所需的全部状态;带session 时附带该会话的用量与上限,refresh=1强制刷新余额 | | POST |/config | 写入汇率、守卫、**全局上限({limit},0 = 不设)**、模型单价覆盖({price:{key,value}}/{price:{key,reset:true}})、**某个会话自己的上限({sessionId, sessionLimitFor},传 0 清除该会话的上限)**;上限被余额夹取时响应带 limitClamped: true。0.2.1 的 {sessionLimit}(共用默认上限)已废弃、被忽略 | | POST | /override|{mode:"once"|"session"|"off"|"reset", sessionId?}放行 / 清除放行 | | POST |/reset|{scope:"session"|"global", sessionId?} **重置计费起点**(默认软重置 = 基线前移:本次归零、历史只进累计、token 一条不丢;scope:"global" 只动全局起点,各会话自己的起点不受影响,反之亦然);{hard:true}才是旧的**清空历史**(累计一起归零,不可恢复) | | GET |/pricing?q=| 价目表检索(精确命中优先) | | GET |/diag | 诊断:clientModules` 是否已把浏览器半边收进启动图,以及实时余额、自设全局上限与单会话上限表、本次/累计金额与各会话基线 |

守卫:仅接受同源 / 带 dsh-auth- cookie / 回环地址的请求,响应 cache-control: no-store。

权限、依赖与失败边界

面向 DSH STORE 自动审查的声明(package.json 里的 engines 与 dsh.compatibility 是机器可读的那一份,这里是同一件事的说明):

能力用在哪边界
文件系统(files)宿主半边读写插件自己的账本 ~/.dsh/billing/ledger.json(config storeFile 可指到别处)不读工作区、不读其它插件的数据;写盘是「临时文件 + 改名」的原子替换
网络(network)浏览器半边用同源 fetch 调本插件自己的宿主路由 /plugin-billing/…宿主半边不发起任何网络请求;不访问任何第三方域
子进程(commands)运行时零子进程;只有 tests/ 下的开发脚本(自测、HMR/HTTP 探针、发布)会用到 shell 与 git这些脚本不在 files 里,不随包发布
凭据(credentials)运行时零凭据;余额走宿主 deepseekAccount 服务,插件从不接触账号 tokentests/submit-remote.mjs 读本机 ~/.dsh/github-token.txt,仅供作者发布用
原生模块 / 生命周期脚本 / 运行时依赖都没有:纯 ESM,peerDependencies 只有 react(宿主已提供)装包不执行任何 install / build 脚本
  • 外部服务:唯一可选的对外依赖是宿主服务 deepseekAccount(读账号余额)。其余全部本地:计费靠内置价目表 lib/pricing.json 与账本里的 token 记录。
  • 兼容性:Node >=20(实测 Node 24.14.0 —— DSH Desktop 0.2.0-rc.2 自带的 Electron 44 运行时,也是两份自测所用的 Node);DSH >=0.2.0-rc.1;逐版本记录里 0.2.0-rc.2 = compatible,依据是本插件在该版本上长期实跑(宿主路由、双角标、余额、重置都在用);浏览器半边声明 platform: web,profile web。一次性 profile 的安装 / 启动 / 卸载尚未自动化验证,因此不声明 dshOperations(保持 unknown)。
  • 失败边界(都不影响会话本身):账本读不到或 JSON 损坏 → 从空账本开始,不抛错;写盘失败 → 记一条 billing: 账本写入失败 warn,进程内继续算;余额服务不可用(未登录 / 接口失败 / 服务缺失)→ 余额显示 — 且不夹取上限,计费与阻断照旧;价目表缺失或模型未知 → 用保守估算价 [1, 4, 0.1, 0];路由对非本机 / 非同源请求返回拒绝,只影响该请求。
  • DSH STORE 自动审查的现状:按商城自己的扫描规则复扫本仓库,运行文件里只剩两条信号——lib/index.js 的 files(账本写 JSON)与 lib/client.js 的 network(浏览器半边调自己的宿主路由)。开发脚本已放在 tests/,而商城把测试目录排除在「运行能力证据」之外,所以 commands / credentials 两条不再计入。剩下这两条是这类插件的本性,条目因此仍可能停在人工复核(商城的说明也写明:高能力项目可能保持 user-reviewed,声明本身不保证自动批准)。

安装

宿主半边跑在 DSH 进程里;浏览器半边是手写的 window.__ModuleLoader__ 包装(lib/client.js,无需构建),由 dsh-client-modules 以 combo URL 提供——注意不是 /plugins/yoka-dsh-billing/client.js(该形状返回 404),真实 URL 形如 /plugins/??yoka-dsh-billing/client.js&rev=,由启动图注入,页面上会自行加载。

安装 = 让桌面 profile 能解析到本包,并在 dsh.profile.bundles 里登记。已执行的命令:

# 在 DSH 会话内用 plugin_manager 工具,等价于 dsh plugin --profile desktop add link:
# target: link:C:\Users\walex lin\Desktop\dsh workflow\插件\dsh-billing

它会把 "yoka-dsh-billing": "link:…/dsh-billing" 写进 ~/.dsh/profiles/desktop/package.json 的 dependencies,并把 yoka-dsh-billing 追加进 dsh.profile.bundles,随后热应用(application: applied)。插件自带的 cordis.patch.yml:

- insert:
    - id: yoka-dsh-billing
      name: 'yoka-dsh-billing'
      config: { rate: 7.2, limit: 0, guard: true }

开发

node tests/build-pricing.mjs "
/dist/providers/data"   # 重新生成 lib/pricing.json
node tests/self-test.mjs          # 宿主:折叠 / 计价 / 余额 / 自设上限与夹取 / 守卫 / 路由 / 持久化 / 旧账本迁移 / 重置计费起点(全局与单会话互不影响)(153 项)
node tests/self-test-client.mjs   # 浏览器:模块外壳 / 槽位 / 双角标与 ↺、侧栏余额、面板文案与「单会话上限」独立段(每行两排:本次 / 累计)/ 两个 ↺ 互不牵连(107 项)
node tests/probe-hmr.mjs          # 核对「运行中的宿主」是否已发布本地这版 client.js(无需刷新/重启)

已知边界

  • 订阅制账号并不按 token 单价扣费,这里的金额是等效价值参考,不是账单。
  • 余额来自宿主 deepseekAccount 服务(可选依赖:拿不到就只影响余额显示与「上限夹取」,不影响计费、自设上限与单会话上限)。
  • 面板里的模型键是 provider/model;直接改单价只影响该键的用量。
  • 角标挂在 conversation.composer.dock 槽位,只在会话输入态(composer)渲染;欢迎页不显示。侧栏余额挂在 sidebar.footer.action,与设置按钮同一行,收起态(56px)只留一个圆点。
  • 浏览器半边的改动由模块图 HMR 自动跟上:dsh-client-hmr 每 500ms stat 一次各条目的 client.js,元数据一变就 rebuilt(id) 并经 /plugins/events 广播,页面会把该插件重挂载(不用刷新页面,但被重载插件的 React 状态会丢,所以打开着的面板会自己关掉)。node tests/probe-hmr.mjs 可核对当前发行的 revision。宿主半边(lib/index.js)不热重载源码,改完需要重启 DSH 才会生效——已安装的那份实例跑的是安装时的代码。
  • 面板与确认框的底色是完全不透明的:官方「菜单面」色(--dsw-specific-menu,浅 #f8f9faf0 / 深 #303136f0,94% 不透明)叠在 --dsw-alias-bg-layer-1(纯色层)上,外加 rgba(0,0,0,.34) 遮罩;面板经 react-dom 的 createPortal 挂到 document.body,避免侧栏祖先的 transform 困住 fixed 定位。