Vai al contenuto principale
K

dsh-session-cost

kidli1412/dsh-session-cost

Stima del costo della sessione in una status bar nella parte inferiore della conversazione (prezzi dei token per modello, CNY) più il saldo live dell'account DeepSeek tramite la balance API ufficiale.

Installazione

dsh plugin --profile web add github:kidli1412/dsh-session-cost

README

dsh-session-cost

DSH(DeepSeek Harness)Web 插件:把本次会话的 Token 费用估算与 DeepSeek API 余额并入输入框下方的自带统计栏。

  • 费用估算:服务端按模型逐条计价——从会话事件日志折叠出每个模型的输入/输出/缓存命中 token(语义与 dsh-token-meter 的 tokenUsage 投影一致),再按 CNY 单价表(lib/cost.js)计算费用,混合多模型的会话也精确。
  • 余额查询:复用官方余额接口 GET {baseURL}/user/balance(参考插件 dsh-usage-stats 的余额方案),凭据经 DSH 的 credentials 缝解析,2 分钟内存缓存 + 单飞防抖;?refresh=1 可强制绕过缓存(状态栏的 ⟳ 手动刷新即用此参数)。
  • 每 30 秒刷新费用、每 5 分钟刷新余额;token 用量变化后自动触发费用刷新;点击统计栏里的费用 pill 展开分模型明细与余额构成(充值/赠送),面板内 ⟳ 手动刷新(强制查询上游,成功后短暂显示"已更新 HH:MM"),面板最后一行直接改低余额阈值。

界面

费用/余额是一个与自带统计项同款的可点击 pill,追加在自带统计栏同一行;若该会话自带统计栏整行都不存在(全新会话,或冷会话加载的头一秒),pill 会先独立显示在输入框上方(同一套字号/内边距/圆角),统计栏一出现就自动并回那一行:

自带统计栏里的费用 pill(第三个)与点击展开的明细面板

  • 点击 pill 在统计栏上方展开明细面板,用的是自带两个 pill 点击展开时的同一套皮肤(圆角 12、--dsw-specific-menu 背景、标题 + 分隔线 + dt/dd 网格、12/18 字号):标题行左侧「费用」、右侧总额;网格里每个模型一行(输入/输出 tokens 与费用,跨峰谷时附 高峰/空闲 拆分),然后是余额与充值/赠送构成——读数网格只有数字;再往下是「更新于 HH:MM + ⟳ 手动刷新」一行,其下一行就是低余额阈值输入框(低余额阈值 [ 10 ] 元,见下);最底是计价说明。点击面板外或按 Esc 关闭。
  • 面板以 position:fixed 挂到 document.body 并做视口夹取(与原生 stat dialog 相同的 measure→place 流程、同样的 8px 间距 / 12px 边距),所以不会被任何祖先容器的 overflow 裁掉;数据更新走原地 patch(不重建节点),因此面板开着时刷新数值不会闪断、也不会丢焦点。
  • DSH 0.1.5 起自带统计栏改为 StatsPills:居中的 flex 行、由带图标的 pill 组成(data-composer-stats 标记),取代了此前单行省略号文本的 StatsLine。本插件的 pill 因此按同一套 13/20 字号层级、同一 1px 8px 内边距与 24px 圆角、同一 hover / aria-expanded 背景渲染,自带 ¥ 图标,视觉上与自带 pill 齐平。
  • 自带统计栏会"迟到":StatsPills 在会话有步骤或 token 之前返回 null(整行都不存在),所以插件必须能在统计栏之后挂载的情况下仍然接上去——见下文「兼容性」。
  • 数据也会迟到:刚重启 host 时首次费用/余额响应还在路上,组件却已经挂载(费用等第一次 summary、余额等上游查询或缓存)。所以观察器无条件安装:曾经在"暂时没东西可画"时干脆不装,结果没有任何人在等统计栏出现,费用段要等页面刷新(数据已被预热)才显示——这就是「每次重启后打开界面都要刷新一次」的原因。现在首个 payload 到达前 sync() 只是不画东西,观察器始终在岗。
  • 切换会话时整个输入区会被拆掉重建:打开一个没有客户端缓存的会话要等约 1 秒,这一秒里锚点处于已脱离文档的子树中。观察器过去只在"宿主仍然连在文档里"时才重新挂载自己——一次这样的回调就让它永久失效,费用段再也回不来,只能刷新页面(刷新等于重新挂载、装上新的观察器)。现在重新挂载是无条件的(脱离文档的节点依然会派发子树变更),另有一个 1 秒看门狗兜底:只要该显示的 pill 不在位(节点被 React 抹掉、统计栏被整体换掉、锚点换了父节点)就重新接回去。
  • 自带统计栏的布局一律不改:0.1.5 的统计栏是居中 flex 行(width:100%、max-width:748px、gap:12px,且不做 overflow 裁剪),追加的费用段只是这一组里的第三项,宽度本来就够——插件不写统计栏的任何行内样式。0.1.x 早期版本曾需要把统计行放宽才能显示追加段(≤ 0.1.4 的 StatsLine 有 748px 上限 + 省略号截断),那个补丁已在 0.2.3 删除。

设置项就在明细面板里、「更新于 / ⟳」的下一行:低余额阈值 [ 10 ] 元,改完按回车或点开别处即生效。从 0.3.0 起这个值作为本插件的 profile 配置保存(DSH 0.2.0 的设置模型:Config → 以 entry id session-cost 为键的表单 → 写进 profile patch),旧的 ~/.dsh/settings.yaml 阈值会被 Host 在升级后一次性抢救回配置;0.1.1 及更早版本的 localStorage 配置也会在首次加载时自动迁移。

  • 低余额阈值(默认 10 元):余额低于该值时显示为红色,达到或高于时显示为黑色。变红同时作用于明细面板里的「账户余额」和统计栏 pill 的余额读数:

余额低于阈值时读数变红

0.2.6 起不再有「设置 → 插件 → 插件配置 → 会话费用显示」那张卡片:这个阈值只影响本面板与 pill 的颜色,所以编辑器搬进了面板本身(持久化的 namespace 没变,旧值原样沿用)。

0.1.5 起移除了「独立状态栏」显示方式(统计栏下方单独一行),只保留并入统计栏;旧配置里的 displayMode 键会被忽略。

展开面板内容(示例):

费用                                    ¥1.9400
────────────────────────────────────────────────
deepseek-v4-flash     输入 169,013 · 输出 46,512 · ¥1.8900
deepseek-v4-pro       输入 1,000 · 输出 500 · ¥0.0500 · 高峰 ¥0.02 · 空闲 ¥0.03
余额                                    ¥36.44
充值余额                                ¥30.00
赠送余额                                 ¥6.44
更新于 10:32                                 ⟳
低余额阈值                              [ 10 ] 元
费用为估算值:token 用量来自会话日志,单价见官方定价页(…)。

安装

从 npm 安装:

dsh plugin --profile web add @kidli1412/dsh-session-cost

从 GitHub 安装:

dsh plugin --profile web add github:KIDLi1412/dsh-session-cost

本地开发(链接安装,改动即时生效):

dsh plugin --profile web add link:path/to/dsh-session-cost

安装后重启 dsh web,浏览器硬刷新(Ctrl+Shift+R)。打开任意会话即可在自带统计行末尾看到费用与余额。

移除:

dsh plugin --profile web remove @kidli1412/dsh-session-cost

兼容性 / Compatibility

  • DSH:manifest 通过 dsh.compatibility.dshReleases 将当前版本线 0.2.0-rc.2 声明为 compatible(DSH STORE 的精确逐版本兼容证据;仅范围声明不会恢复上架)。插件使用的客户端注入(dsh-api-remotes / dsh-client-connection / dsh-client-locale / dsh-client-ui-conversation / dsh-client-ui-settings)、客户端服务(slots / locale / configForms)与 Host 服务(webServer 精确路由、settings.describe / settings.update、credentials.resolve、session.seq + session.eventAt)在 0.2.0 版本线上均已在真实安装中核对。
  • Node:^22.19.0 || >=24.0.0(与 DSH 一致)。
  • 宿主要求(dsh-market 显示):engines.dsh: ^0.2.0-rc.2,并将运行时依赖的 lockstep 宿主包声明为 peerDependencies(dsh-host-webserver / dsh-session / dsh-credentials / dsh-settings 与客户端模块 dsh-api-remotes / dsh-client-connection / dsh-client-locale / dsh-client-ui-conversation / dsh-client-ui-settings,均为 ^0.2.0-rc.2);插件市场会据此显示"宿主要求"并判断与当前 DSH 是否匹配。这些包由 DSH 运行时提供、本插件从不 import,所以同时标为 peerDependenciesMeta.optional——npm/pnpm 不再把它们装进依赖树(0.2.0 起它们彼此还有 peer 关系,沿用旧的自动安装会直接解析冲突),而"宿主要求"的语义不受影响。
  • 0.3.0(DSH 0.2.0 适配):DSH 0.2.0 换了三套契约,宿主兼容闸门会直接拒绝挂载声明不符的 bundle(真实现象:插件在市场里显示"不兼容"、rows: []、统计栏里什么都没有)——
    1. 宿主要求必须落在 0.2.0 线上:闸门逐条比对 peerDependencies 里的 @deepseek-ai/dsh* 与运行时版本(semver.satisfies(..., { includePrerelease: true })),^0.1.5-rc.1 不满足 0.2.0-rc.2(caret 封顶在 <0.2.0),因此本版把 engines.dsh 与 9 个宿主 peer 一起提到 ^0.2.0-rc.2。顺带把宿主包标为 optional peer,见上一条。
    2. 设置模型重做:0.1.x 的"插件自建 settings namespace + settings.register(ns, schema)"没了,settings.get(ns) 也没了。现在插件导出的 Config(schemastery)就是设置表单,以profile entry id(本插件 bundle patch 里的 id: session-cost)为键;Host 通过 ctx.settings.describe() 读别人的条目、ctx.settings.update(ns, patch) 写;浏览器端用 ctx.configForms.get("session-cost")(getSnapshot / subscribe / set),旧的 ctx.settingsScope.bind({ namespace }) 已从客户端消失。本插件的配置存取因此整体改到这上面(面板里的阈值输入框 UI 不变)。
    3. 统计栏与面板的皮肤同步:.bOPqQW_root 不再自带宽度/内边距(width:100% + 748px 上限 + 侧边 clearance 移交给 composer 的 dock 容器),字号降到 calc(--dsh-content-font-size-secondary - 1px),pill 圆角由 24px 改为 999px + corner-shape:round,stat-dialog 面板改用 --dsw-radius-lg 并加 backdrop-filter。本插件的 pill、solo 兜底行与明细面板按同一套值重绘,视觉仍与自带 pill 齐平。
    4. 整站浏览器鉴权:0.2.0 给 dsh web 的每个请求加了签名 cookie(dsh web authentication required),插件的两个端点因此只有带 cookie 的同源页面能到达;Host 侧的 loopback 精确路由fence 保留为第二道防线。

    升级时会自动抢救旧阈值:DSH 自己的 settings.yaml 导入按 entry id 进行,而当时本插件正因上面的 peer 不符被拒挂载,导入失败后值只留在 ~/.dsh/settings.yaml.imported 里。本版 Host 在启动后(等 Loader settle)读一次该文档,若条目还没有用户值就把非默认的 lowBalanceThreshold 写进配置——一次性、只读、失败即跳过。

  • 0.2.0(DSH 0.1.5 适配 + 交互重做):三处必须改动,否则统计栏里完全看不到费用/余额段——
    1. 不再 require @deepseek-ai/dsh-client-ui-primitives。0.1.5 起该包不再随 DSH 安装(依赖树里已无此包,客户端模块图因此没有这一行),而 plugin bundle 的 require() 对未注册模块是抛错的(loader 的 loud 语义),一处 require 就会让整个客户端 half 加载失败:dock 锚点、合并段、面板全部消失。本插件的图标改为内联 SVG 自绘,bundle 不再依赖任何可选宿主模块。
    2. 统计栏标记与定位:优先按 data-composer-stats 属性定位(0.1.5 新增),文本 N 轮 · M 步 只作旧版回退且改为非锚定匹配(0.1.5 的 pill 文本已无 · 分隔)。
    3. 两个"迟到"必须同时处理:StatsPills 在会话有步骤/token 前返回 null(统计栏迟到)→ 观察器必须监听容器子树(childList + characterData + subtree);重启 host 后首次 summary/余额响应仍在路上(数据迟到)→ 观察器必须无条件安装。
    4. 价格与模型 id 同步跟进:0.1.5 把 V4-Flash 路由成短 id deepseek-flash,且官方在 2026-09-10 发布 V4.1-Flash 并调价;本版引入价格世代模型(legacy / v4:* / 当前 peak/offpeak)与别名解析,详见下文「定价表」。 交互上,费用/余额段从"悬停气泡"改为与自带统计项同款的可点击 pill + 点击展开面板。
  • 0.2.1(切会话丢费用 + 统计栏缺席时的显示):
    1. 切会话不再丢 pill。切换会话(尤其是没有客户端缓存、要等约 1 秒的旧会话)时整个输入区被拆掉重建,锚点会短暂处于脱离文档的子树中,而 0.2.0 只在宿主仍 isConnected 时才重新挂载观察器——一次这样的回调就让观察器永久失效:费用消失,切回新会话也不恢复,必须刷新页面(刷新等于重新挂载、装上新的观察器)。0.2.1 无条件重挂(脱离文档的子树照样派发变更),并加 1 秒看门狗兜底补挂;观察器也改为每个组件挂载只装一次,数据变化走 sync() 推入(不再每 30 秒拆掉重建节点)。
    2. 统计栏缺席时先挂在锚点自身([data-solo],字号/内边距/最大宽度照抄 .bOPqQW_root),统计栏一出现即挪回栏内——全新会话与冷会话加载中都有费用读数。
  • 0.2.2(修复 0.2.1 的点击展开回归):0.2.1 让合并节点不再每次数据变化都重建,于是暴露出一个更早就存在的缺陷——面板是 document.body 的 portal,而原地 patch 用 node.querySelector("[data-slot=panel]") 找它,恒为 null。后果是面板里的读数(总额、余额、更新时间)从来不刷新,并且 0.2.1 之后点击 pill 打不开面板(展开态写不进那个"找不到的"面板)。0.2.2 改为通过 mergePanels 注册表(panelOf)取面板,展开/收起、读数更新都真正落在同一个面板元素上;同时处理器改为点击时从组件的稳定 state 对象上读取(节点寿命已跨越多次渲染,刷新按钮必须作用于当前会话)。
  • 0.2.3(去掉统计栏宽度补丁):≤ 0.1.4 的 StatsLine 自带 748px 上限 + overflow:hidden + 省略号截断,会把追加的费用段裁掉,早期版本的插件因此用行内样式把统计行"放宽 + 取消裁剪"(效果同 zh_pro「统计全显示」,自包含、不依赖它,卸载时按 WeakMap 记下的原值精确还原)。0.1.5 的 StatsPills 根节点(.bOPqQW_root)是居中 flex 行(width:100%、max-width:748px、gap:12px),既没有 overflow:hidden 也不做省略号截断——追加段只是这一组里的第三项,宽度本来就够,补丁已删除,插件不再写自带统计栏的任何行内样式(dom-smoke 断言统计栏的行内样式在挂载 / 重新挂载 / 卸载后逐项不变,重新引入任何 max-width 写入都会失败)。
  • 0.2.5(展开面板的原地更新补全):面板列表里的每个单元格本身就是 slot(dt 是 row-label,dd 是 row-value / panel-balance),而原地 patch 过去用 cell.querySelector("[data-slot=row-value]") 去取它们——querySelector 只搜后代,于是每个单元格都返回 null、patchText 直接跳过:手动 ⟳ 刷新后「账户余额」会跟着变(它按 slot 名单独 patch),「充值余额 / 赠送余额」和逐模型读数却停在旧值。0.2.5 改为直接 patch 单元格自身,并补齐两处:data-warn 的同步(模型变成未计价时那一行要变红)与列表在原位增长 / 收缩(暂无 token 用量 ↔ 模型行,节点自身 slot 数不变时不再抛错或被吞掉)。
  • 0.2.7(阈值行落到「更新于 / ⟳」下面一行):0.2.6 把它放在读数网格(dt/dd)的最后一行,读数和设置混在一起;现在它是面板里独立的一行、紧跟在更新时间与 ⟳ 刷新下面,左边标签、右边输入框(与读数同一右边界)。读数网格因此恢复为纯数字(模型行 + 余额构成),设置项与面板的控制按钮聚在一起。位置之外的行为不变(提交/回车、拒绝空值与负数、刷新不覆盖半途输入、写回同一个 namespace)。
  • 0.2.6(低余额阈值搬进面板):阈值原本是「设置 → 插件 → 插件配置 → 会话费用显示」里的一张卡片,而它唯一的作用就是给本面板与 pill 的余额读数上色——要改颜色得先离开看颜色的地方。0.2.6 把编辑器搬进明细面板,并取消 settings.plugin.item 注册(客户端从此只注册 composer dock 一个槽位)。持久化路径没变:仍是 session-cost settings namespace → ~/.dsh/settings.yaml,旧值原样沿用,0.1.1 及更早的 localStorage 迁移逻辑仍在。空串或负数不提交并把输入框恢复成已存值。
  • 0.1.8(DSH 0.1.2 适配):rc.1 起 live session 不再携带 .events 数组——事件总数读 session.seq、逐条读 session.eventAt(seq)(与官方 dsh-token-meter 相同的读法),费用折叠已适配;客户端注入模块列表同步为新架构模块(见上)。
  • 降级说明:dshReleases 只列当前版本线(0.2.0-rc.2),engines.dsh 也是 ^0.2.0-rc.2。需要 DSH 0.1.5 线的用户请使用 0.2.7;需要 0.1.2 线的请使用 0.1.9(更早的 0.1.x 见各自版本的 README)。

架构

文件角色
lib/index.js服务端:导出 Config(lowBalanceThreshold,DSH 0.2.0 据此生成设置表单);GET /api/session-cost/summary?session=<id>(增量折叠会话事件并按模型计价)、GET /api/session-cost/balance(DeepSeek 余额,loopback-only 精确路由,?refresh=1 强制绕过缓存);经 settings.describe() 读 llm-deepseek 条目、经 credentials 缝解析 API Key;启动后一次性抢救旧 settings.yaml 里的阈值
lib/cost.js纯函数:按模型 token 折叠(replace-last-sample 语义)+ CNY 单价表 + 费用计算
lib/balance.js纯函数:DeepSeek 余额接口查询与状态归一化
lib/client.js浏览器端:只注册 conversation.composer.dock 槽位(id session-cost, order 100);把费用/余额 pill 追加进自带统计栏 DOM(startStatsRowObserver:子树 MutationObserver + 无条件重挂 + 1 秒看门狗,统计栏迟到/被 React 重渲染/切会话重建后都会重新挂载;统计栏不存在时先挂在锚点自身;updateMergeNode 原地 patch 数值;不改统计栏自身的任何样式),点击展开挂到 document.body 的明细面板(placeOpenPanel 做视口夹取),面板里「更新于 / ⟳」下面一行是低余额阈值输入框(经 ctx.configForms.get("session-cost") 读写本插件配置)

费用为估算值:token 用量来自会话日志中 provider 上报的 usage 样本,单价表为写死的默认值,价格变动后请更新 lib/cost.js 的 DEFAULT_PRICING(或通过插件配置 pricing 覆盖)。

定价表(默认,CNY / 百万 tokens)

取自官方定价页(模型 & 价格 中文版)。计费为峰谷 + 价格世代两层:

  • 峰谷:高峰 = 北京时间工作日 9:00–12:00、14:00–18:00(官网英文页写作 UTC 周一至周五 01:00–04:00 / 06:00–10:00),高峰价 = 空闲价的 2 倍;2026-08-23 0 时起周末(周六、周日)全天按空闲价。
  • 价格世代(同一条会话可以跨越多次调价,插件按每条 usage 样本的事件时间归属世代,不回溯改价):
世代生效区间(北京时间)说明
peak / offpeak2026-09-10 0:00 起(V4.1-Flash 发布)当前价,见下表
v4:peak / v4:offpeak2026-08-17 0:00 – 2026-09-09V4 时代的峰谷价(Flash ¥1.5/3、¥0.05/0.10、¥4.5/9),V4_ERA_PRICING
legacy2026-08-17 0:00 之前平峰旧价(Flash ¥1、¥0.02、¥2),LEGACY_PRICING

当前价(2026-09-10 起,CNY / 百万 tokens):

模型 id输入(缓存未命中)空闲 / 高峰输入(缓存命中)空闲 / 高峰输出 空闲 / 高峰
deepseek-flash(V4.1-Flash,DSH 0.1.5 实际路由的 id)¥1 / ¥2¥0.02 / ¥0.04¥4 / ¥8
deepseek-v4-flash(已下线,别名到 V4.1-Flash)¥1 / ¥2¥0.02 / ¥0.04¥4 / ¥8
deepseek-v41-flash(同一模型的另一种拼写)¥1 / ¥2¥0.02 / ¥0.04¥4 / ¥8
deepseek-v4-flash-vision-exp(已下线,别名到 V4.1-Flash)¥1 / ¥2¥0.02 / ¥0.04¥4 / ¥8
deepseek-v4-pro(V4-Pro-0813,官方确认 2026-09-14 后继续提供)¥4.5 / ¥9¥0.15 / ¥0.30¥13.5 / ¥27
deepseek-chat / deepseek-reasoner(V3 遗留名,2026-07-24 起别名到 Flash)¥1 / ¥2¥0.02 / ¥0.04¥4 / ¥8

模型 id 会变,变了就会静默算成 ¥0:DSH 0.1.5 把 V4-Flash 路由成短 id deepseek-flash(界面显示 "DeepSeek-V41-Flash"),而旧表里只有 deepseek-v4-flash —— 找不到单价 → 整场会话费用恒为 0,这正是 0.1.5 升级后的"费用一直是 0"。因此本版:

  • 当前表列出全部仍被接受的 id(含已下线的旧名,避免历史会话读成未计价);
  • 匹配改为名称边界前缀:deepseek-v4-flash-2026-01 这类带日期后缀的 id 归到 deepseek-v4-flash,而 deepseek-v99 这种不同型号不会被误当成 flash,而是判为未计价;世代表(V4_ERA_PRICING/LEGACY_PRICING)通过 PRICING_ALIASES 做同样的别名解析,旧世代里的新 id 也按旧价计费;
  • 明细面板里未匹配到单价的模型显示红色 未计价,不再伪装成 ¥0;GET /api/session-cost/summary 附带 diagnostics(事件数 / 已识别模型 / 已计价模型数),便于一眼定位。

cacheWrite 无 DeepSeek 等价项(上下文缓存自动命中计费),默认按缓存未命中输入价计(分时段),避免低估。

明细面板会显示高峰 / 空闲 / V4 价 / 旧价的费用拆分(跨时段或跨世代时)。

官方英文页另有美元价格(Flash $0.15/$0.30 输入、$0.003/$0.006 缓存命中、$0.6/$1.2 输出),与本表的人民币价按同一份价目表换算,插件统一按人民币计价(与余额接口的 CNY 口径一致)。

插件配置(可选)可覆盖定价——平峰格式(所有时段同价)或分时段格式:

# ~/.dsh/settings.yaml 或 profile 插件配置
session-cost:
  pricing:
    deepseek-v4-flash:
      input: 1
      cacheRead: 0.02
      cacheWrite: 1
      output: 2
    # 或分时段(offpeak/peak 各自覆盖,未给字段继承默认):
    # deepseek-v4-pro:
    #   offpeak: { input: 4.5, output: 13.5 }
    #   peak: { input: 9, output: 27 }

pricing 与配置卡写入的 lowBalanceThreshold 共存于同一个 session-cost: section,互不覆盖(schemastery 解析保留未知键;pricing 仍由服务端从插件 config 读取)。

安全

  • 两个端点均为 loopback-only 精确路由(peer socket 地址 + Host 双重校验),浏览器同源调用。
  • API Key 不落盘:请求时经 credentials 缝解析 llm-deepseek 命名空间的 apiKeyEnv(默认 DEEPSEEK_API_KEY)。
  • 余额缓存仅存于内存,2 分钟 TTL。

License

MIT

Plugin correlati