npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rayadesu/dsh-llm-billing

v1.0.0

Published

Standalone DeepSeek account-balance and session-spend provider exposed through the billing Remote

Readme

@rayadesu/dsh-llm-billing

English | 中文

独立的宿主插件,把 DeepSeek 账户余额与会话花费变成 billing Remote。它拥有 /user/balance 传输、峰谷计价表与每会话的计费花费计价,因此部署可以在不把这个能力与聊天补全适配器耦合的前提下,展示「还剩多少、这个会话花了多少」。浏览器侧是 dsh-client-ui-billing。

安装

把插件加进组合(一个 cordis.yml 行)并给它一个凭据。它先从凭据 seam(或 apiKeyEnv 指定的环境变量)解析 API key,再从 baseURL、其次 $DEEPSEEK_BASE_URL、最后公共 API 解析端点。

- id: llm-billing
  name: '@rayadesu/dsh-llm-billing'
  config:
    # apiKeyEnv: DEEPSEEK_API_KEY   # default
    # baseURL: https://api.deepseek.com

插件注册 billing Remote,含七个方法:getBalance(force?)(解析后的 /user/balance 快照;15 秒宿主 TTL 内复用,force 绕过,单次请求 5 秒超时)、getSessionSpend(sessionId)(单个会话的计费花费)、getTodaySpend(force?)(当前北京时间自然日内所有会话的计费花费合计;force 绕过宿主侧缓存,供徽标手动刷新使用)、getTodaySessionsSpend(force?)(今日按对话的计费花费,按花费从高到低排序,每行带会话的持久标题与该会话自身今日份金额——见子代理会话并入父会话行)、getDelegatedSpend(sessionId, force?)(一次对话的子代理部分——见同一节)、getTurnSpend(sessionId, messageId)(单个已完成回合的计费花费,按收尾助手消息 id 定位)与 getSessionTurnSpends(sessionId)(该会话所有已完成回合的 messageId → 金额 映射,一趟折叠——对话每个消息行都要显示金额,客户端因此每会话只拉一次,而不是逐行调用 getTurnSpend)。计价的样本来源有两处:assistant/message 自身的 usage,以及失败/重试的 assistant/attempt 内嵌 stream 里的 usage(后者用最近一条 request/header 的模型),各按样本自身发生时刻(北京时间)所在的峰/谷单价、以及该时刻生效的官方费率版本计价——高峰窗口仅周一至周五适用,周末全天按低谷价。同一 (turn, step) 的后一份样本替换前一份,llm/retry-started 之后重试的那次累加,与 DSH 自己的回合用量口径一致;随后按模型汇总。一个回合即收尾消息所在的 turn/start..turn/end 区间;排行从每个会话日志里最后一条 session/title 事件折叠标题(last-wins,重命名事件一旦提交、会话被重新读取即反映新名字)。

今日花费读取路径(消息触发不再全量扫描)

getTodaySpend() 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight 合并,服务于消息触发的读取;此外 stale-while-revalidate 把「等」这件事也拿掉了:窗口一过期,普通读者手上只要有当天的值就立刻拿到它,重扫放到后台跑,因此切会话永远不会等当天那次全会话扫描。带 force 的读取则相反——它会阻塞等新值:这就是手动刷新,以及徽标在回合结束后的重算(这一轮刚刚改变了答案,不能把「这一轮之前的值」给它,否则今日行每轮都慢一拍)。当天还没有值的读取同样阻塞(首次读取,或跨北京日翻篇:昨天的合计绝不会当作今天的值返回),因为那时没有可诚实展示的东西。窗口按扫描完成时刻盖章,而不是开始时刻:慢于自身 TTL 的一趟扫描否则一出生就是过期状态,之后每次读取都会紧接着再起一趟。缓存未命中时,一次扫描同时产出聚合、排行与子代理读取所需的全会话累计(聚合即排行各行之和):

  • 投影路径(当组合中装配了 @deepseek-ai/dsh-session-projection 时启用):插件注册客户端可见的 billingTodaySpend 投影单元——注册表一出现就提前注册,因此 DSH 的 write-behind 会在每个 turn/end 为每个会话落一行检查点。live 会话零日志 I/O 直读其 eager 单元;冷会话若投影缓存的 cachedSnapshot 行(零 I/O)自身的最新计价日不是查询日,就直接作答,否则(或没有可用行时)才读取日志本地折叠。只有持久化 revision 在上次解析后变化过的会话才会被读取;读取失败的会话按 revision 记住,不再每轮重试。
  • 事件路径(无注册表时的回退):对每个会话用同一套计价折叠(收集时按北京日过滤,20 万事件上限)。持久化 revision 未变的会话,改用本扫描器之前为同一个 revision 折出的结果作答,而不是重新读取;被事件上限截断的那一趟不留记忆,下一趟会重读被截断的部分。

两条路径因此都把 revision 门控当作缓存,而不是过滤器:日志未变既不花 I/O,也照样把完整的金额与标题计入合计与排行。若门控只是「跳过未变的会话」而不采用手上已有的解析结果,第一趟之后的每一趟都会悄悄少算今日合计(并丢掉排行行)——投影路径靠已解析单元的记忆避免,事件路径现在与它共用同一份记忆。

进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过(并被记住),而不是让整日合计失败。

两条路径的折叠都是宿主主事件循环上的同步计算——也就是处理 GUI 自身往返(切模型、新建会话)的那个循环——所以扫描每折叠 SCAN_YIELD_SESSIONS(8)个会话就让出一次事件循环,冷会话并发扇出也按同样节奏让出(yieldToEventLoop)。不让出的扫描会把该循环占满整趟,那正是冷扫期间这两个操作看起来「卡死」的原因。

注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率。官方费率版本随定价闭包一起进入折叠,并按样本自身时刻解析,因此被调价的系列无论日志多晚折叠都能正确计价自身历史;只有手工修改配置(billing.models)才只影响变更后折叠的事件(事件路径会重算整个日志),而该解析口径变化时单元 stateVersion 会一并提升,使已落检查点被丢弃重折而不是沿用旧值。

子代理会话并入父会话行

子代理(subagent)子会话是委派它的那次对话花的钱,而不是用户打开的会话,所以今日排行列的是对话:每一行子代理会话在排序之前都被并入其 parentSession 链顶端那个顶层会话的行里。DSH 会在子会话的持久 header 上盖 origin: 'subagent'、委派方会话 id 与 delegationDepth(父深度 + 1),两个标记都以结构方式读取,任一存在即认定为子会话,因此多代委派(子代理又派子代理)同样落到同一个顶层行。用户手动分叉的会话虽然也带 parentSession,但不带任何 subagent 标记,仍然是自己一行。

合并后的行给出两个金额:total 是这次对话的整日花费(本会话加上它(递归)委派的每个子代理),ownTotal 是本会话自己的花费,当天没有任何后代计价时两者相等。整日合计(getTodaySpend)不变:两条路径求和的是同一批会话,合并只是把行重新归组,不会在总额之间搬钱。顶层会话自己当天没计价、但它的子代理计价了,它照样有一行(ownTotal 为 0),标题取自扫描时折出的日志。

浏览器还需要同一套「对话」口径去显示整日/整份日志的花费,而会话自己的日志无法为它的委派子会话计价——所以有了 getDelegatedSpend(sessionId):返回那棵子树的金额,即该会话(递归)委派的每个子代理会话、按其日志覆盖的每一天求和,并把模型行合并(分项仍然加得起来)。它与今日花费共用同一次缓存扫描(那一趟本来就会折出每个会话的全会话累计,因此这里不额外读日志),并额外给出 isSubagent——被查询的会话自己是不是委派子会话(是的话它的花费落在委派它的那个会话的排行行里)。用户手动分叉永远不会被算作委派子会话(它带 parentSession,但不带任何 subagent 标记)。

输入框 pill 把这棵子树加到会话自身的实时花费上,因此 pill 的金额读作整次对话的金额——pill 是唯一显示它的界面;徽标第二行与它后面的面板报的是账户级的今日口径,两者读的是同一份 state。

分叉会话

分叉会话(DSH 的「分叉会话」)的日志以来源会话事件的逐字节副本开头。若不特殊处理,同一批模型输出会按副本数重复计费:子会话的会话花费会包含继承前缀,今日花费也会在父会话之外再计一次。插件只对会话的自有事件计费——分叉边界取自已持久化的会话状态(≤ 0.1.1-rc.2 运行时为 header.seedLength;0.1.2-alpha.4+ 运行时为 Session.inheritedEventCount(live)/SessionHandle.inheritedEventCount(打开的 handle),两者都以结构方式读取)。billingTodaySpend 单元本身带边界(状态里存着切割点,apply 跳过其下事件),因此分叉子会话的 eager 单元直接可用;冷路径对 seeded 会话跳过投影缓存,用持久边界折叠自有事件。因此分叉子会话从分叉后的第一次新交流开始计费(刚分叉的会话花费为零),今日花费对每个模型输出只计一次,同一血缘规则同样覆盖多代分叉与 subagent 分叉(context: 'fork' 生成)。边界取自已持久化的值,所以恢复后的分叉子会话保持原边界;而创建时没有 seed 的会话——包括普通会话与冷恢复——不带边界,正常全额计费。

运行时兼容性

0.1.2-alpha.4 起,DSH 把 live Session 的日志读取表面从 Session.events 改为 Session.snapshotEvents()(无参 = 当前全量日志)与 Session.ownEvents(),并把 SessionHeader.seedLength 移至 Session.inheritedEventCount(live)/SessionHandle.inheritedEventCount(持久化侧 open(id, 'read') 返回的 handle)。插件的所有日志读取都走结构适配器 liveSessionEvents / forkBoundaryOf / isSeededSession:live 表面是保留两代形状的那一个——liveSessionEvents 同时接受 ≤ 0.1.1-rc.2 的 events 快照与 0.1.2-alpha.4+ 的 snapshotEvents() 读取器——而 forkBoundaryOf 优先用精确的 inheritedEventCount(live 或 handle),否则回退到持久化的 header.seedLength,因此 npm 发布基线(^0.2.0-rc.1)与超前于它的 monorepo 运行时代码均无需改动即可工作。遇到两种形状都没有的未知运行时表面时,插件会显式失败而不是静默按零花费计价。排行的血缘读取同样以结构方式完成:header 里既没有 origin: 'subagent' 也没有非零 delegationDepth 的会话(旧日志,或创建时就没有这两个字段的会话)直接视作顶层会话、保留自己一行。

持久化服务现在是「仅 handle」一面:提供 open(id, 'read') + SessionHandle.read() / list()(DSH 0.1.2-alpha.5+,正是插件 ^0.2.0-rc.1 peer 目标的那一族)。扫描器只走这一面:persistenceListSnapshots 就是对 list() 的薄封装,persistenceInspect 打开 handle、读取、并在 finally 里关闭(因此无论读取成功与否,handle 总会关闭)。旧的 inspect(id) / listSnapshots() 表面已删除(Item 7),生产现在只服务 handle 这一族。SessionHandle.read() 本身仍有两代返回形状——最初返回裸事件数组,DSH 9b78f99dec(0.1.5-alpha.1)起返回 { eventState, events };handleReadEvents 同时接受两种形状,冷读因此不受该代际变更影响。

投影缓存的读取同样对准当前 seam:cachedSnapshot(header, inheritedEventCount, keys)(零 I/O,只读 wire 行)。它的前身——自己读日志的异步 coldSnapshot(id)——已不存在,插件不再依赖它。

配置

| 字段 | 默认 | 含义 | | --- | --- | --- | | apiKeyEnv | DEEPSEEK_API_KEY | 每次调用时解析的凭据引用(环境变量)名。 | | baseURL | $DEEPSEEK_BASE_URL,其次 https://api.deepseek.com | 端点基础地址;会追加 /user/balance。 | | models | V4.1 Flash(deepseek-flash)+ V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5/V2.6 系列 | 展示用的模型行,按展示顺序;与 DSH llm-deepseek 目录对齐(另保留已退役的 deepseek-v4.1-flash-expires-on-0910 预览 id,让历史日志仍有可读标签)。 | | billing.peakHours | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 | | billing.models | 官方 V4 + MiMo 费率 | 每个模型的单价行(cacheHitInput、cacheMissInput、output,单位:元/百万 token),可带生效时刻 effectiveFrom(epoch 毫秒,含该时刻)。 |

只想覆盖某个模型而不丢其它,就提供一个非空的 billing.models 列表;空或省略则回退到官方默认费率。同一个模型可以有多行:每行是一个费率版本,用量样本按样本自身时刻生效的那一版取峰/谷单价(不带 effectiveFrom 的行是该模型的基础版本,同时覆盖更早的一切时刻)。

内置价目表已包含 DeepSeek 的两轮调整,跨越任一变更点的会话或自然日都能精确计价:

  • 2026-09-10 12:00(北京时间)(FLASH_SERIES_RATE_CHANGE_AT):整个 flash 系列——V4.1 Flash 路由 deepseek-flash(当日发布,现为 DSH 默认模型)、V4 Flash、V4 Flash Vision Exp 以及已退役的预览 id——降为谷时 0.02 / 1.0 / 4.0,峰时为其两倍;更早的样本(含该路由 12:00 之前的自身用量)沿用被取代的旧价。
  • 2026-09-14 12:00(北京时间)(V4_PRO_ROUTE_SWITCH_AT):V4 Pro 路由按公告改由 V4.1 Flash 服务并按 V4.1 Flash 计费,该行因此带有第二个费率版本。
  • MiMo-V2.5 与 V2.6 系列不受两轮调整影响(统一费率;V2.6 于 2026 年 9 月 22 日发布,沿用 V2.5 公布的费率)。

插件管理页的展示元数据

插件管理页上本包那一行显示的标题、描述与图标都取自包自身(宿主不执行任何插件代码):

  • locale/en.json 与 locale/zh.json —— 形状 {"meta": {"title": …, "description": …}},按当前界面语言解析、回落英文。
  • icon.svg —— 自包含 SVG(不引用任何外部字体或图片),由 package.json 顶层的 icon 字段声明。

两者都经包的 exports 解析,所以 ./locale/*.json 必须保持导出——去掉它会让 locale 文件解析成 ERR_PACKAGE_PATH_NOT_EXPORTED,宿主把它当作「没有元信息」,静默回落到裸包名。locale/*.json 与 icon.svg 也必须留在 files 里,否则打出的 tarball 两个都不带。

模型体验

无,因为本包是 provider 与会话事实的只读 Remote 投影,不触及 prompt、消息、schema、流或工具结果。

KV Cache effect

无;它唯一的 provider 调用是一次带凭据的 /user/balance 读取,不落在 provider 的 KV 缓存里。

已知限制与暂缓事项

  • 有费率行才计价,且不靠名字猜 —— 会话花费与今日花费只按价目表(billing.models)里该模型自己的行计价;没有行就不计价,但用量会记为 unpriced 并按模型告警一次(其中写明模型名),而不是无声丢弃。要给它计价,在 billing.models 补一行即可。assistant/attempt 用最近一条 request/header 的模型计价,因此首条 header 之前的 attempt 不计入。
  • 合并行可能没有标题 —— 子代理的父会话不在本次扫描范围内时(例如父日志已被删除或归档),该行仍按其 header 里写的父会话 id 归属,但父会话日志从未被读取,合并行因此没有标题,浏览器显示「未命名」兜底。
  • 最多 60 秒延迟,但不会让人白等 —— getTodaySpend() 由宿主侧缓存服务最多 60 秒;过了这个窗口,普通读取直接拿到上一次的值、重扫放到后台,因此读者从不等待当天那次扫描(除非它主动要求等)。带 force 的读取(手动刷新,以及徽标在回合结束后的重算)会等一次重算——仍受 revision 门控,日志未变则零成本。浏览器端的输入框 pill 读的是推送的投影值,因此不会滞后;今日口径(徽标第二行与面板那一行)是账户级的、没有推送源,只在挂载、手动刷新、打开面板与回合结束重算时变动,而它可能跟随的今日会话排行(getTodaySessionsSpend(),同样受 60 秒缓存与 revision 门控)最多滞后 60 秒。
  • 额度带 TTL 缓存 —— 一份 /user/balance 快照最多复用 15 秒,单次请求 5 秒超时;force(手动刷新)绕过 TTL。
  • 投影计价跟随官方费率版本 —— 投影折叠按样本时刻解析费率版本,官方调价因此无需重折;手工改 billing.models 则只影响变更后折叠的事件,直到状态版本或进程重置(事件路径回退会重算整个日志)。
  • 冷缓存行可能滞后于日志 —— 冷会话若缓存行覆盖查询日,会重读日志以求精确;若缓存行自身的日期不是查询日则直接采信、不读日志,因此进程在最后一次检查点之后、最后一条事件之前崩溃的会话,其尾部可能暂时少算,直到该会话被重新读取。