# dsh-switch-cost [English](README.md) | 中文 把当前会话算两遍价:一遍按实际跑的那条线路,一遍按价目表里其他每条线路。前一半和别的用量插件一样回答「这次花了多少」,后一半回答它们不回答的那个问题——「同样这些流量换个地方要多少」。 会话用量折叠成 token 桶,键是**线路 + UTC 小时**。分时计价的厂商因此是逐小时定价的,而不是按你提问那一刻恰好生效的那档价。折叠只记录「什么时候」,价目表决定那个小时值多少钱。 ## 安装 ```bash dsh plugin add dsh-switch-cost ``` 可组合进任何提供 `sessionProjections` 和 `tools` 的装配。两处注册都放在 `ctx.inject(['sessionProjections'], …)` 里面——没有组合投影注册表的装配两样都拿不到,而不是拿到一个永远回答零的工具。 ### 配置 ```yaml switch-cost: alternatives: 8 # 报告多少条其他线路,从便宜到贵 ``` ## 服务面 本插件不注册服务,只注册一个投影单元和一个工具。 ### 投影:`switchCost` 仅宿主(无 `wire`),`stateVersion: 1`。状态: ```ts { routes: { [`${provider}/${model}`]: { byHour: { [`${utcDay}-${utcHour}`]: Buckets } } }, route: string | null, last: { turn, step, routeKey, hourKey, buckets } | null, } ``` `Buckets` 是 `{ input, cacheRead, cacheWrite, output }`。小时键是稀疏的;`utcDay` 取 0–6,周日为 0。 一个 step 会上报两次用量——一次是 `assistant/chunk` 里 type 为 `usage` 的分片,一次是组装完的 `assistant/message`。折叠按 `(turn, step)` 取后到者:第二个样本**替换**第一个而不是累加,并且冲销是从第一个样本**当初入账的那个小时**里做的,不是替换到达的那个小时。跨小时边界的 step 因此不会留下一个幽灵桶。不关心的事件返回同一个状态引用,这是投影驱动的硬要求。 在任何 `request/context` 之前到达的用量记在 `unknown/unknown` 名下,不丢弃。 ### 工具:`switch_cost` 无参数。读调用方 agent 会话的投影状态,返回: ```ts { tokens, // 整会话桶合计 actual: [{ route, tokens, cost, costText, ratesApplied, pricedAs, source, checkedAt, layer }], actualTotal, actualTotalText, alternatives: [{ route, cost, costText, versusActual, source, checkedAt, layer }], unpriced?: [{ route, tokens }], caveats: string[], } ``` 没有归属 agent 会话的调用会被拒绝。价目表里查不到的线路进 `unpriced` 并且不计入总额——绝不悄悄按零计价。 渲染出来的样例: ``` Ran on deepseek-official/deepseek-v4-flash: $0.0416 (peak and off-peak hours both billed) Same tokens, other routes: deepseek-official/deepseek-v4-flash-vision-exp $0.0416 +0% openai/gpt-5.6-luna $0.0513 +23% deepseek-official/deepseek-v4-pro $0.1260 +203% zhipuai/glm-4.7 $0.1446 +247% anthropic/claude-haiku-4-5 $0.2452 +489% moonshotai/kimi-k2.6 $0.2707 +550% ``` ## 价目表 `dsh-switch-cost/prices` 导出价目表。每一行都带 `source`、`checkedAt` 和 `layer`。 两层,厂商层优先: - `layer: 'vendor'`——`checkedAt` 当天从厂商自己的价格页读到的。 - `layer: 'models.dev'`——取自社区目录。 厂商层优先,一是因为目录有滞后,二是更要紧的:目录里根本没有分时计价的表示方式。`off_peak`、`peak`、`time_of_use` 在它的 schema 里一个都不存在,`tiers` 字段只承载上下文长度档位。一天两档价的厂商在那里表达不出来,所以照抄目录的插件会把这类会话按一个既不是峰价也不是谷价的数字算掉。 ### 分时计价 一行可以带 `tariff` 而不是单一的 `flat` 块。随包发布的是 DeepSeek 那套: ```js { peakWindowsUtc: [[1, 4], [6, 10]], peakWeekdaysUtc: [1, 2, 3, 4, 5] } ``` 即公布的北京时间 09:00–12:00 和 14:00–18:00,工作日,周末全谷。规则在 UTC 下判定,而对这两个窗口来说这是**精确**的而非近似:窗口覆盖的每个小时都满足 `hour + 8 < 24`,加上北京时区偏移不会跨日,因此凡是可能是峰时的小时,UTC 的星期几就等于北京的星期几。有一条测试直接断言这个性质,将来窗口一旦越过 UTC 16:00 就会失败——那时在 UTC 下判定就不再安全,规则得换成真正的时区处理。 ## 模型体验 ### 模型看到什么 一个空参数 schema 的工具,描述为报告会话成本与跨线路对比。结果文本给出每条线路、成本、是否两档价都计了,以及从便宜到贵的备选线路和相对实际支出的带符号百分比。末行重申这是等 token 数下的公开价格对比。 ### Token 开销 一个很小的固定 schema。结果随跑过的线路数加 `alternatives` 行增长——默认 8 条时大约 200–400 token。 ### KV Cache 影响 无。本插件给已经记录下来的用量定价,从不组装或发送厂商请求,也不向系统提示贡献任何内容。 ## 本插件不主张什么 下面这些随每一次结果一起返回,而不只是写在这里,因为一个由模型读取的结果否则会被当成账单讲出去。 - **Token 数是实际跑的那个模型产出的。** 换个模型分词方式不同,不会产出这些数。这是等 token 数下的公开价格对比,不预测另一个模型的账单。 - **缓存读/写的拆分是原样搬过去的。** 各家的提示缓存在缓存什么、存多久、写入怎么收费上都不一样。缓存机制差异越大的线路,这一行越不可靠。 - **所有价格都是按量付费的标价。** 订阅套餐、预付额度包、免费额度都没有建模。 - **价格是一份有日期的快照。** 每行报告自己的来源和日期;按数字做决定前先核对。 厂商没有单独公布缓存写入价的,这部分 token 按输入价计——缓存写入的 token 就是缓存未命中的 token——并且结果里会置 `cacheWriteBilledAtInputRate`,让这次推算可见而不是被默认接受。 ## 开发 ```bash pnpm install pnpm test ``` ## 许可证 MIT