概览
dsh-budget-watcher
README / ZH
插件文档
目录摘要
dsh.pub 核对固定版本的组合包契约、运行时事实与分发语义;完整 README 请查看源仓库。
在 GitHub 阅读完整 READMELIMITATIONS
已知限制
完整的 61 条限制写在英文 [README](README.md#known-limitations) 里,**以那一份为准**。下面按主题重新编排过, 和英文版逐条对应,只是措辞更紧凑一些;如果两边有出入,请以英文版为准。 ### 余额 1. **数据来自公开的余额接口。** 它不是 DeepSeek 官方支持的 API,随时可能变更甚至消失。接口变了,插件会报错,而不是瞎猜。 2. **接口返回的 `total_balance` 会原样显示,不做重算**,所以你看到的就是接口返回的值。 3. **没有 `is_available`,也拿不到赠送额度的有效期。** 赠送额度什么时候过期、以后计费方式怎么变,都在插件的视野之外。 4. **余额本身是粗略的。** 接口只给两位小数,而且可能存在结算延迟。 5. **余额接口返回的是 API 密钥对应账户的余额**,不是网页登录的开放平台账户余额。 6. **是轮询,不是推送。** 这里没有 SSE 流,所以面板只能靠轮询。而且这里有**两个刻意分开的间隔**: 面板每隔几秒轮询一次它自己的本地宿主接口(任务运行时 2 秒,空闲时 3 秒),这样才能及时发现新的提示词; 而 `refreshIntervalMs`(最小 15 秒,默认 60 秒)决定的是**宿主**复用同一个余额应答多久之后才再次调用 DeepSeek。 把这两个混为一谈正是 `0.6.3` 修掉的 bug:面板继承了 60 秒的轮询周期,看起来就像卡住了,直到有人去按刷新。 7. **除了同源围栏之外,这个接口没有鉴权。** 它不在 DSH 需要鉴权的 `/api` 前缀下面,所以插件自己做回环地址和同源检查。 它只返回余额数字,从不返回凭证。如果你的部署绑定了非回环地址,那么在那个网络上任何人都能访问这个接口, 除非你保持 `allowNonLoopback` 关闭。 8. **围栏相信自己看到的 Host 头。** 这一点只有在绑定回环地址时才成立。 9. **网页上能看到余额,但这个页面本身并不安全。** 任何能打开这个界面的人都能看到账户余额。 10. **只实现了 DeepSeek。** 其他 provider 都还没做。 ### 客户端界面 11. **浮层是直接操作 DOM 来定位的**,没有走 DSH 的布局系统。拖拽后的位置存在 `localStorage` 里,清理浏览器数据就会重置。 12. **折叠成胶囊之后刻意不能拖动**,免得它被不小心碰走。 13. **没有做界面本地化。** 面板上的文案是硬编码的英文。本文件只是文档翻译,不是界面翻译。 14. **宿主模块在一个进程里只会被导入一次。** 所以改了已经加载的插件源码,必须重启 DSH 才会生效; 而客户端那一半每次请求都从磁盘重新读取,于是它会很乐意地给一个过期的宿主画出界面。 每个 payload 里都带 `pluginVersion`,就是为了让这种情况能被一眼看出来。 15. **DSH 桌面应用自带的 profile 不能从命令行安装**,它由 Electron 应用独占管理。 ### 成本估算 16. **价目表是一份快照,不是订阅。** 价格写在 `lib/cost.js` 里,同时记录了读取日期(`PRICING_READ_ON`),这个日期也会出现在 payload 里。 DeepSeek 改价不会被自动发现,面板会继续很自信地报着旧价格。 17. **没有考虑中国的法定节假日。** 高峰和空闲是按 UTC 时间窗口算出来的,但节假日的排除没有做—— 官方文档没公布这份日历,而且每年都变。`PRICING_READ_ON` 之后的第一个节假日,工作日会被按高峰计价, 也就是**把成本算高了**。对花钱告警来说,这是偏安全的方向。 18. **价目表是两张各自公布的表,不是互相换算出来的。** 插件两张都带着,按你选的币种计价;选 `auto` 时跟随当前展示的余额钱包。 两种币种的数字不是彼此的换算结果,而且只支持这两种,其他会回落到美元表。 19. **只统计当前还活着的 agent 树。** 花费归属于轮询时仍然活着的会话,加上本进程已经记住的会话。 如果一个铺开的任务在插件加载**之前**就已经结束,它不在活跃列表里,那部分子代理花费不会被算进会话总计; 根会话自己的轮次仍然会计入。 20. **记住的总计可能比活跃的总计更大。** 这是刻意的:已经结束的子代理会继续贡献,这样数字永远不会往回掉。 21. **`ownEvents()` 和 `snapshotEvents()` 在 DSH 里已经废弃。** 它们是插件唯一能读到整份日志的途径, 而 DSH 自己的说明里禁止新的生产环境调用。每次调用都包了 try/catch:万一以后版本把它们移除了, 成本部分会消失,但余额仍然照常工作。 22. **没有价格模型的轮次只上报数量,不估算金额**,显示为 `N turn(s) unpriced`。静默地显示成零,会被读成「那一轮免费」。 23. **重试用的模型是推断出来的。** `assistant/attempt` 事件不带模型名,所以按该轮的模型来计价,再回落到会话自己的请求上下文。 如果某一轮在重试途中换了模型,这次重试就会按错误的价格计算。 24. **推理 token 不单独计价。** DSH 确实会填 `reasoningTokens`,但它是输出 token 的子集,重复计价只会高估。 25. **按 token 计价,而 token 的切分是模型自己的事。** 这是目前唯一可用的粒度。 26. **多模态输入只按文本计价**,图片之类的计价方式不在统计范围内。 ### 宿主插件与加载 27. **本插件是普通的 Cordis 插件**,不依赖任何私有 API。 28. **配置校验依赖 `@deepseek-ai/schemastery` 能被解析到。** 它被声明为 peer dependency, 因为 DSH 的 profile 解析会拦截 peer 名;缺了它,`Config` 会静默变成 `undefined`,配置就永远不会被校验。 29. **不要添加 `@deepseek-ai/dsh*` 作为 peer。** 兼容性闸门会检查这些版本范围,一旦不满足,整行都会被静默跳过。 30. **刻意没有使用 `.volatile()`。** 它确实能让改动不重载 fiber,但字段会变成必须在每次读取时解引用的 getter 对象; 普通字段加一次 fiber 重载更简单,而且确实验证过能生效。 ### 轮次成本与「烧」 31. **烧是累计平均,不是瞬时速率。** 成本是随着每个结算步成块到达的,瞬时速率会在每一步冒出一个尖峰、在步与步之间读作零。 32. **正在跑的轮次,烧在步与步之间会往下衰减。** 分子只在结算时变化,分母每秒都在变。 33. **实时数字是在浏览器里算的。** 宿主发来花费和轮次起点,面板本地按 1 Hz 走时重算,这样显示是活的,又不需要每秒发一个请求。 34. **一轮的数字包含它的子代理,按时间窗口归因。** 这是刻意的,但也意味着:如果某个更早轮次的长命子代理, 它的调用落在了本轮的窗口里,就会被算进来。反过来,轮次结束**之后**还在继续的工作不会算, 所以定格的数字可能低估一个活得比根轮次更久的铺开任务。 35. **实时烧会来回跳。** 15 秒窗口在这段时间内没有任何结算时就会读作 `0`,只要某一步的响应慢过 15 秒就会这样。 这是真实读数,不是 bug,也正是需要 `average burn` 作为稳定对照的原因。 36. **轮次历史有上限,而且只覆盖根会话。** 对比表保留最近 10 个已结束的轮次,不做持久化,重启就是空的,也没法跨会话比较。 37. **走时用的是宿主的时钟。** payload 里带着宿主的 `serverNow`,页面据此校正偏移,所以浏览器时钟漂移不会扭曲轮次时长。 它假设两者在同一台机器上,对本插件提供的回环接口来说这个假设成立。 ### 中断轮次 38. **轮次定位是建议性的,不是原子的。** 这里是最重要的一条。`Agent` 比任何一轮都活得久, 所以手里拿着一个引用,标识的是 *agent* 而不是某一轮;在读取实时烧和调用 `cancel` 之间, 确实存在一个窗口,轮次可能已经结束、后继的一轮已经开始,而 API 里没有任何东西可以「取消第 N 轮」。 插件在调用前后各采样一次开放的轮次边界,这挡不住误伤,但能把一次静默的误伤变成日志里的一条 `raced-next-turn`。 如果它在你没预料到的时候触发了,就去日志里找这一行。 39. **默认关闭,而且在看清真实烧率之前,最好让它保持关闭。** `terminateAbovePerHour` 默认是 `0`。 中断任务具有破坏性,而它判断依据的是从 15 秒窗口算出来的估算值,阈值设低了就会打断正常工作。 建议先把 `burnWarnPerHour` 设好,观察你的轮次实际烧多少,再把上限设在它上面。 40. **检查发生在读接口里。** 是否中断是在余额 `GET` 请求里决定的,这是读操作带副作用,并不优雅。
