@rayadesu/dsh-llm-billing 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record both hashes with:
4
4
  # git hash-object README.md README.zh.md
5
- README.md: 3c0ae19286d6935190659ec1e1c93b7b63ab5c14
6
- README.zh.md: 08adb0d018b376fb5fa995b29cbb6790063f4814
5
+ README.md: d6b416ee95bf7cd53032e6685fa1cc8b03ce0fa6
6
+ README.zh.md: 7370b6c25af9aba7250402f1a22ad4a3870e9b58
package/README.md CHANGED
@@ -16,28 +16,30 @@ Add the plugin to a composition (a `cordis.yml` row) and give it a credential. I
16
16
  # baseURL: https://api.deepseek.com
17
17
  ```
18
18
 
19
- The plugin registers the `billing` Remote with five methods: `getBalance()` (the parsed `/user/balance` snapshot), `getSessionSpend(sessionId)` (one session's billed cost), `getTodaySpend(force?)` (every session's billed cost on the current Beijing-time calendar day; `force` bypasses the host-side cache, for the badge's manual refresh), `getTodaySessionsSpend(force?)` (today's billed cost per session, sorted by cost descending, each row carrying the session's durable title), and `getTurnSpend(sessionId, messageId)` (one completed turn's billed cost, located by its closing assistant message id). The spend prices each `assistant/message` event's billed tokens (cache-hit input, cache-miss input including cache writes, and output including reasoning) at the official rate of the event's own Beijing-time peak/off-peak classification peak windows apply weekdays (Monday–Friday) only, and weekends are always off-peak then sums per model. A turn is the `turn/start`..`turn/end` range holding the closing message; the ranking folds each session's title from its latest `session/title` event (last-wins, so a rename is reflected as soon as its event commits and the session is re-read).
19
+ The plugin registers the `billing` Remote with six methods: `getBalance(force?)` (the parsed `/user/balance` snapshot; a snapshot younger than the 15-second host TTL is reused, `force` bypasses it, and each request aborts after 5 seconds), `getSessionSpend(sessionId)` (one session's billed cost), `getTodaySpend(force?)` (every session's billed cost on the current Beijing-time calendar day; `force` bypasses the host-side cache, for the badge's manual refresh), `getTodaySessionsSpend(force?)` (today's billed cost per session, sorted by cost descending, each row carrying the session's durable title), `getTurnSpend(sessionId, messageId)` (one completed turn's billed cost, located by its closing assistant message id), and `getSessionTurnSpends(sessionId)` (every completed turn's cost as a `messageId → total` map, folded in one pass — the transcript renders one row per message, so the client fetches this once per session instead of calling `getTurnSpend` per row). The spend prices each provider-reported usage sample — an `assistant/message`'s usage, or a failed/retried `assistant/attempt`'s stream usage, priced with the model of the latest `request/header` at the official rate revision in effect at the sample's own timestamp, classified peak/off-peak by its own Beijing-time hour. Peak windows apply weekdays (Monday–Friday) only, and weekends are always off-peak. A sample for the same `(turn, step)` replaces the earlier one and `llm/retry-started` makes the retried attempt add, matching DSH's own turn-usage disclosure; costs then sum per model. A turn is the `turn/start`..`turn/end` range holding the closing message; the ranking folds each session's title from its latest `session/title` event (last-wins, so a rename is reflected as soon as its event commits and the session is re-read).
20
20
 
21
21
  ### Today-spend read path (no full scans per message)
22
22
 
23
- `getTodaySpend()` never scans every session log per request. A 60-second Beijing-day cache with in-flight coalescing serves message-triggered reads; only the manual refresh (`force`) bypasses the time window. Behind a miss, two revision-gated strategies compute the aggregate:
23
+ `getTodaySpend()` never scans every session log per request. A 60-second Beijing-day cache with in-flight coalescing serves message-triggered reads; only the manual refresh (`force`) bypasses the time window. Behind a miss, one scan produces BOTH the aggregate and the ranking (the aggregate is the sum of the rows):
24
24
 
25
- - **Projection path** (used when `@deepseek-ai/dsh-session-projection` is composed): the plugin registers the `billingTodaySpend` projection unit, which folds each session's spend eagerly as events commit. Live sessions are read from their eager cells with zero log I/O; cold sessions ride the projection-cache ladder (`coldSnapshot`) or, without the cache service, one detached fold per session. Only sessions whose persisted revision changed since the last resolution are touched.
26
- - **Events path** (fallback without the registry): collects only today's events (Beijing-day filter during collection) with a 200 000-event cap, skipping sessions whose persisted revision is unchanged.
25
+ - **Projection path** (used when `@deepseek-ai/dsh-session-projection` is composed): the plugin registers the client-visible `billingTodaySpend` projection unit — eagerly, as soon as the registry exists, so DSH's write-behind checkpoints a row for every session at each `turn/end`. Live sessions are read from their eager cells with zero log I/O; a cold session is answered from the projection cache's zero-I/O `cachedSnapshot` row whenever that row's own latest priced day is not the queried day, and only otherwise (or when no usable row exists) inspected and folded locally. Only sessions whose persisted revision changed since the last resolution are touched, and a failed resolution is remembered by revision instead of being retried on every scan.
26
+ - **Events path** (fallback without the registry): folds each session's log with the same pricing fold (Beijing-day filter during collection, 200 000-event cap), skipping sessions whose persisted revision is unchanged.
27
27
 
28
- After the first resolution per process, steady-state reads cost only the sessions whose logs actually changed. A session whose log cannot be read is skipped with a warning instead of failing the whole day's total.
28
+ After the first resolution per process, steady-state reads cost only the sessions whose logs actually changed. A session whose log cannot be read is skipped with a warning (and remembered) instead of failing the whole day's total.
29
29
 
30
- Note: the projection path prices a session's history once, at the rates in effect when its events were folded changing `billing.models` re-prices only events folded after the change (the events path re-prices the whole log).
30
+ Note: the projection path prices a session's history once, at the rates in effect when its events were folded. Published rate revisions travel inside the pricing closure and are resolved per sample timestamp, so a re-priced series bills its own history correctly however late a log is folded; only a configuration change (editing `billing.models`) re-prices just the events folded afterwards (the events path re-prices the whole log), and the unit's `stateVersion` is bumped whenever that resolution changes so checkpointed rows are refolded instead of kept.
31
31
 
32
32
  ## Forked sessions
33
33
 
34
- A forked session (DSH's "fork" of a conversation) opens its log with a verbatim copy of its source session's events. Without special handling, the same model outputs would be billed once per copy: the child's session spend would include the inherited prefix, and today's spend would count it a second time alongside the parent's. The plugin prices only a session's OWN events — the fork boundary is the session's persisted state (`header.seedLength` on the ≤ 0.1.1-rc.2 runtime, `Session.inheritedEventCount` / `inspect().inheritedEventCount` on 0.1.2-alpha.4+, both read structurally), and every event with `seq < boundary` is treated as already billed in the source session. Fork children are therefore billed from their first new exchange onward (a freshly forked session prices to zero), today's spend counts each model output exactly once, and the same lineage-safe rule covers multi-generation forks and subagent forks (spawned with `context: 'fork'`). The boundary is the persisted value, so a resumed fork child keeps its original boundary, while a session created without a seed — ordinary sessions and cold resumes included — carries no boundary and is billed in full.
34
+ A forked session (DSH's "fork" of a conversation) opens its log with a verbatim copy of its source session's events. Without special handling, the same model outputs would be billed once per copy: the child's session spend would include the inherited prefix, and today's spend would count it a second time alongside the parent's. The plugin prices only a session's OWN events — the fork boundary is the session's persisted state (`header.seedLength` on the ≤ 0.1.1-rc.2 runtime, `Session.inheritedEventCount` / `inspect().inheritedEventCount` on 0.1.2-alpha.4+, both read structurally). The `billingTodaySpend` unit is boundary-aware (its state carries the cut and `apply` skips events below it), so the eager cell is correct for a fork child; the cold path skips the projection cache for a seeded session and folds its own events with the durable cut. Fork children are therefore billed from their first new exchange onward (a freshly forked session prices to zero), today's spend counts each model output exactly once, and the same lineage-safe rule covers multi-generation forks and subagent forks (spawned with `context: 'fork'`). The boundary is the persisted value, so a resumed fork child keeps its original boundary, while a session created without a seed — ordinary sessions and cold resumes included — carries no boundary and is billed in full.
35
35
 
36
36
  ## Runtime compatibility
37
37
 
38
38
  Since 0.1.2-alpha.4, DSH replaced the live `Session` log surface `Session.events` with `Session.snapshotEvents()` (no args = the full current log) and `Session.ownEvents()`, and moved `SessionHeader.seedLength` to `Session.inheritedEventCount` (the persistence `inspect()` result carries the value beside `meta`; `listSnapshots()` headers keep only the boolean `isSeeded`). Every log read in the plugin goes through the structural adapters `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`, which accept both the ≤ 0.1.1-rc.2 and the 0.1.2-alpha.4+ shapes — the npm release baseline (`^0.1.2-alpha.5`) and the ahead-of-npm monorepo runtime both work without modification. On an unknown surface that has neither shape the plugin fails loudly rather than silently pricing an empty log.
39
39
 
40
- The persistence service itself changed surface too: 0.1.1-rc.2 exposes `inspect(id)` / `listSnapshots()`, while the 0.1.2-alpha.5 handle-based seam (in the monorepo after the released build) exposes `open(id, 'read')` + `SessionHandle.read()` / `list()`. The scanner reads both families through `persistenceInspect` / `persistenceListSnapshots` (the handle is always closed, including after a failed read), so the same plugin serves the published alpha line and the refactored checkout.
40
+ The persistence service itself changed surface too: 0.1.1-rc.2 exposes `inspect(id)` / `listSnapshots()`, while the handle-based seam exposes `open(id, 'read')` + `SessionHandle.read()` / `list()`. The scanner reads both families through `persistenceInspect` / `persistenceListSnapshots` (the handle is always closed, including after a failed read), so the same plugin serves the published alpha line and the refactored checkout. `SessionHandle.read()` itself has two generations: it first returned the bare event array, and since DSH `9b78f99dec` (in the 0.1.5-alpha.1 checkout) it returns `{ eventState, events }`; `handleReadEvents` unwraps both, so a cold read keeps working across the change.
41
+
42
+ The projection-cache reader likewise targets the current seam: `cachedSnapshot(header, inheritedEventCount, keys)` (zero I/O, wire rows only). Its predecessor, an async `coldSnapshot(id)` that read the log itself, no longer exists, so the plugin never depends on it.
41
43
 
42
44
  ## Configuration
43
45
 
@@ -45,11 +47,17 @@ The persistence service itself changed surface too: 0.1.1-rc.2 exposes `inspect(
45
47
  | --- | --- | --- |
46
48
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential-reference (environment-variable) name resolved per call. |
47
49
  | `baseURL` | `$DEEPSEEK_BASE_URL` then `https://api.deepseek.com` | Endpoint base; `/user/balance` is appended. |
48
- | `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | Advisory display rows, in presentation order. |
50
+ | `models` | V4.1 Flash (`deepseek-flash`) + V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 series | Advisory display rows, in presentation order; they mirror DSH's `llm-deepseek` catalog (plus the retired `deepseek-v4.1-flash-expires-on-0910` preview id, kept for readable historical labels). |
49
51
  | `billing.peakHours` | 09:00–12:00, 14:00–18:00 (Beijing, weekdays) | Peak-hour windows, applied weekdays (Mon–Fri) only; weekends and all other hours are off-peak. |
50
- | `billing.models` | Published V4 rates | Per-model peak/off-peak price rows (`cacheHitInput`, `cacheMissInput`, `output`, in CNY per 1M tokens). |
52
+ | `billing.models` | Published V4 + MiMo rates | Per-model price rows (`cacheHitInput`, `cacheMissInput`, `output`, in CNY per 1M tokens) with an optional inclusive `effectiveFrom` (epoch ms). |
53
+
54
+ Override one model without dropping the others by supplying a non-empty `billing.models` list; an empty or omitted list falls back to the published defaults. Several rows may share one model: each row is a rate revision, and a usage sample is priced at the peak/off-peak pair of the revision in effect at the sample's own timestamp (a row without `effectiveFrom` is that model's base revision and also covers every earlier instant).
55
+
56
+ The published table already ships DeepSeek's adjustments, so a session or a day spanning a change is priced exactly:
51
57
 
52
- Override one model without dropping the others by supplying a non-empty `billing.models` list; an empty or omitted list falls back to the published defaults.
58
+ - **2026-09-10 12:00 Beijing** (`FLASH_SERIES_RATE_CHANGE_AT`): the whole flash series the V4.1 Flash route `deepseek-flash` (released that day and now DSH's default), V4 Flash, V4 Flash Vision Exp, and the retired preview id — drops to off-peak 0.02 / 1.0 / 4.0 with peak at twice those prices. Earlier samples, including this route's own usage from before that instant, keep the superseded rates.
59
+ - **2026-09-14 12:00 Beijing** (`V4_PRO_ROUTE_SWITCH_AT`): the V4 Pro route is announced to be served by V4.1 Flash and billed at the V4.1 Flash rates; its row carries that second revision.
60
+ - MiMo-V2.5 series: untouched by either adjustment (flat rate).
53
61
 
54
62
  ## Model Experience
55
63
 
@@ -61,6 +69,8 @@ None; its only provider call is a credential-authenticated `/user/balance` read,
61
69
 
62
70
  ## Known Limitations and Deferred Work
63
71
 
64
- - **Priced rows only** — the session and today spends only price models that have a `billing.models` row; a model without a rate row is omitted.
65
- - **Up-to-60s staleness** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds; only the manual refresh (`force`) recomputes immediately (still revision-gated, so an unchanged log costs nothing).
66
- - **Projection pricing is history-frozen** — when the projection path is active, a pricing-table change prices only events folded after the change; restart (or the events fallback) re-prices the full log.
72
+ - **Priced rows only** — the session and today spends only price models that have a `billing.models` row; a model without a rate row is omitted. An `assistant/attempt` is priced with the model of the latest `request/header`, so an attempt before any header contributes nothing.
73
+ - **Up-to-60s staleness** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds; only the manual refresh (`force`) recomputes immediately (still revision-gated, so an unchanged log costs nothing). The browser reads the live per-session value from the pushed projection instead, so the session line is never stale.
74
+ - **Balance is TTL-cached** — one `/user/balance` snapshot is reused for up to 15 seconds and each request aborts after 5 seconds; `force` (the manual refresh) bypasses the TTL.
75
+ - **Projection pricing follows the published revisions** — the projection fold resolves the rate revision per sample timestamp, so published re-pricing needs no refold; a hand-edited `billing.models` change prices only events folded after the change until the state version or the process is reset (the events fallback re-prices the full log).
76
+ - **A cached cold row may trail its log** — a cold session whose cached row covers the queried day is re-read from the log for exactness; a row whose own day is not the queried day is trusted without a read, so a session that crashed between its last checkpoint and its last event can under-report that tail until it is next read.
package/README.zh.md CHANGED
@@ -16,28 +16,30 @@
16
16
  # baseURL: https://api.deepseek.com
17
17
  ```
18
18
 
19
- 插件注册 `billing` Remote,含五个方法:`getBalance()`(解析后的 `/user/balance` 快照)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(force?)`(今日按会话的计费花费,按花费从高到低排序,每行带会话的持久标题)与 `getTurnSpend(sessionId, messageId)`(单个已完成回合的计费花费,按收尾助手消息 id 定位)。会话花费把每条 `assistant/message` 事件的计费 token(缓存命中输入、含缓存写入的未命中输入、含推理的输出)按事件自身发生时刻(北京时间)所在的峰/谷单价计价——高峰窗口仅周一至周五适用,周末全天按低谷价——再按模型汇总。一个回合即收尾消息所在的 `turn/start`..`turn/end` 区间;排行从每个会话日志里最后一条 `session/title` 事件折叠标题(last-wins,重命名事件一旦提交、会话被重新读取即反映新名字)。
19
+ 插件注册 `billing` Remote,含六个方法:`getBalance(force?)`(解析后的 `/user/balance` 快照;15 秒宿主 TTL 内复用,`force` 绕过,单次请求 5 秒超时)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(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,重命名事件一旦提交、会话被重新读取即反映新名字)。
20
20
 
21
21
  ### 今日花费读取路径(消息触发不再全量扫描)
22
22
 
23
- `getTodaySpend()` 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight 合并,服务于消息触发的读取;只有手动刷新(`force`)绕过时间窗口。缓存未命中时,两种带 revision 门控的策略计算聚合:
23
+ `getTodaySpend()` 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight 合并,服务于消息触发的读取;只有手动刷新(`force`)绕过时间窗口。缓存未命中时,**一次扫描同时产出聚合与排行**(聚合即各行之和):
24
24
 
25
- - **投影路径**(当组合中装配了 `@deepseek-ai/dsh-session-projection` 时启用):插件注册 `billingTodaySpend` 投影单元,随事件提交增量折叠每个会话的花费。live 会话零日志 I/O 直读其 eager 单元;冷会话走投影缓存阶梯(`coldSnapshot`),没有缓存服务时对每个会话做一次 detached 折叠。只有持久化 revision 在上次解析后变化过的会话才会被读取。
26
- - **事件路径**(无注册表时的回退):只收集今天的事件(收集时按北京日过滤),带 20 万事件上限,跳过持久化 revision 未变的会话。
25
+ - **投影路径**(当组合中装配了 `@deepseek-ai/dsh-session-projection` 时启用):插件注册客户端可见的 `billingTodaySpend` 投影单元——**注册表一出现就提前注册**,因此 DSH 的 write-behind 会在每个 `turn/end` 为每个会话落一行检查点。live 会话零日志 I/O 直读其 eager 单元;冷会话若投影缓存的 `cachedSnapshot` 行(零 I/O)自身的最新计价日不是查询日,就直接作答,否则(或没有可用行时)才读取日志本地折叠。只有持久化 revision 在上次解析后变化过的会话才会被读取;读取失败的会话按 revision 记住,不再每轮重试。
26
+ - **事件路径**(无注册表时的回退):对每个会话用同一套计价折叠(收集时按北京日过滤,20 万事件上限),跳过持久化 revision 未变的会话。
27
27
 
28
- 进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过,而不是让整日合计失败。
28
+ 进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过(并被记住),而不是让整日合计失败。
29
29
 
30
- 注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率——修改 `billing.models` 只影响变更后折叠的事件(事件路径会重算整个日志)。
30
+ 注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率。官方费率版本随定价闭包一起进入折叠,并按样本自身时刻解析,因此被调价的系列无论日志多晚折叠都能正确计价自身历史;只有**手工修改配置**(`billing.models`)才只影响变更后折叠的事件(事件路径会重算整个日志),而该解析口径变化时单元 `stateVersion` 会一并提升,使已落检查点被丢弃重折而不是沿用旧值。
31
31
 
32
32
  ## 分叉会话
33
33
 
34
- 分叉会话(DSH 的「分叉会话」)的日志以来源会话事件的逐字节副本开头。若不特殊处理,同一批模型输出会按副本数重复计费:子会话的会话花费会包含继承前缀,今日花费也会在父会话之外再计一次。插件只对会话的**自有事件**计费——分叉边界取自已持久化的会话状态(≤ 0.1.1-rc.2 运行时为 `header.seedLength`;0.1.2-alpha.4+ 运行时为 `Session.inheritedEventCount` / `inspect().inheritedEventCount`,两者都以结构方式读取),凡是 `seq < 边界` 的事件都视为已在来源会话计费。因此分叉子会话从分叉后的第一次新交流开始计费(刚分叉的会话花费为零),今日花费对每个模型输出只计一次,同一血缘规则同样覆盖多代分叉与 subagent 分叉(`context: 'fork'` 生成)。边界取自已持久化的值,所以恢复后的分叉子会话保持原边界;而创建时没有 seed 的会话——包括普通会话与冷恢复——不带边界,正常全额计费。
34
+ 分叉会话(DSH 的「分叉会话」)的日志以来源会话事件的逐字节副本开头。若不特殊处理,同一批模型输出会按副本数重复计费:子会话的会话花费会包含继承前缀,今日花费也会在父会话之外再计一次。插件只对会话的**自有事件**计费——分叉边界取自已持久化的会话状态(≤ 0.1.1-rc.2 运行时为 `header.seedLength`;0.1.2-alpha.4+ 运行时为 `Session.inheritedEventCount` / `inspect().inheritedEventCount`,两者都以结构方式读取)。`billingTodaySpend` 单元本身**带边界**(状态里存着切割点,`apply` 跳过其下事件),因此分叉子会话的 eager 单元直接可用;冷路径对 seeded 会话跳过投影缓存,用持久边界折叠自有事件。因此分叉子会话从分叉后的第一次新交流开始计费(刚分叉的会话花费为零),今日花费对每个模型输出只计一次,同一血缘规则同样覆盖多代分叉与 subagent 分叉(`context: 'fork'` 生成)。边界取自已持久化的值,所以恢复后的分叉子会话保持原边界;而创建时没有 seed 的会话——包括普通会话与冷恢复——不带边界,正常全额计费。
35
35
 
36
36
  ## 运行时兼容性
37
37
 
38
38
  0.1.2-alpha.4 起,DSH 把 live `Session` 的日志读取表面从 `Session.events` 改为 `Session.snapshotEvents()`(无参 = 当前全量日志)与 `Session.ownEvents()`,并把 `SessionHeader.seedLength` 移至 `Session.inheritedEventCount`(持久化侧 `inspect()` 的结果在 `meta` 之外携带该值,`listSnapshots()` 的 header 只剩布尔 `isSeeded`)。插件的所有日志读取都走结构适配器 `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`,同时接受 ≤ 0.1.1-rc.2 与 0.1.2-alpha.4+ 两种形状——npm 发布基线(`^0.1.2-alpha.5`)与超前于它的 monorepo 运行时代码均无需改动即可工作。遇到两种形状都没有的未知运行时表面时,插件会显式失败而不是静默按零花费计价。
39
39
 
40
- 持久化服务的表面同样换代:0.1.1-rc.2 提供 `inspect(id)` / `listSnapshots()`,而 handle 化的 0.1.2-alpha.5 seam(位于已发布构建之后的 monorepo 中)提供 `open(id, 'read')` + `SessionHandle.read()` / `list()`。扫描器通过 `persistenceInspect` / `persistenceListSnapshots` 同时读取两代表面(handle 总会关闭,读取失败时也一样),因此同一套插件既能服务已发布的 alpha 线,也能服务重构后的 checkout
40
+ 持久化服务的表面同样换代:0.1.1-rc.2 提供 `inspect(id)` / `listSnapshots()`,而 handle seam 提供 `open(id, 'read')` + `SessionHandle.read()` / `list()`。扫描器通过 `persistenceInspect` / `persistenceListSnapshots` 同时读取两代表面(handle 总会关闭,读取失败时也一样),因此同一套插件既能服务已发布的 alpha 线,也能服务重构后的 checkout。`SessionHandle.read()` 自身也有两代:最初返回裸事件数组,DSH `9b78f99dec`(0.1.5-alpha.1 checkout 中)起返回 `{ eventState, events }`;`handleReadEvents` 同时接受两种形状,冷读因此不受该变更影响。
41
+
42
+ 投影缓存的读取同样对准当前 seam:`cachedSnapshot(header, inheritedEventCount, keys)`(零 I/O,只读 wire 行)。它的前身——自己读日志的异步 `coldSnapshot(id)`——已不存在,插件不再依赖它。
41
43
 
42
44
  ## 配置
43
45
 
@@ -45,11 +47,17 @@
45
47
  | --- | --- | --- |
46
48
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次调用时解析的凭据引用(环境变量)名。 |
47
49
  | `baseURL` | `$DEEPSEEK_BASE_URL`,其次 `https://api.deepseek.com` | 端点基础地址;会追加 `/user/balance`。 |
48
- | `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | 展示用的模型行,按展示顺序。 |
50
+ | `models` | V4.1 Flash(`deepseek-flash`)+ V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 系列 | 展示用的模型行,按展示顺序;与 DSH `llm-deepseek` 目录对齐(另保留已退役的 `deepseek-v4.1-flash-expires-on-0910` 预览 id,让历史日志仍有可读标签)。 |
49
51
  | `billing.peakHours` | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 |
50
- | `billing.models` | 官方 V4 费率 | 每个模型的峰/谷单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token)。 |
52
+ | `billing.models` | 官方 V4 + MiMo 费率 | 每个模型的单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token),可带生效时刻 `effectiveFrom`(epoch 毫秒,含该时刻)。 |
53
+
54
+ 只想覆盖某个模型而不丢其它,就提供一个非空的 `billing.models` 列表;空或省略则回退到官方默认费率。同一个模型可以有多行:每行是一个费率版本,用量样本按**样本自身时刻**生效的那一版取峰/谷单价(不带 `effectiveFrom` 的行是该模型的基础版本,同时覆盖更早的一切时刻)。
55
+
56
+ 内置价目表已包含 DeepSeek 的两轮调整,跨越任一变更点的会话或自然日都能精确计价:
51
57
 
52
- 只想覆盖某个模型而不丢其它,就提供一个非空的 `billing.models` 列表;空或省略则回退到官方默认费率。
58
+ - **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 之前的自身用量)沿用被取代的旧价。
59
+ - **2026-09-14 12:00(北京时间)**(`V4_PRO_ROUTE_SWITCH_AT`):V4 Pro 路由按公告改由 V4.1 Flash 服务并按 V4.1 Flash 计费,该行因此带有第二个费率版本。
60
+ - MiMo-V2.5 系列不受两轮调整影响(统一费率)。
53
61
 
54
62
  ## 模型体验
55
63
 
@@ -61,6 +69,8 @@
61
69
 
62
70
  ## 已知限制与暂缓事项
63
71
 
64
- - **有费率行才计价** —— 会话花费与今日花费只统计价目表(`billing.models`)里有的模型;没有费率行的模型不计入。
65
- - **最多 60 秒延迟** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;只有手动刷新(`force`)立即重算(仍受 revision 门控,日志未变则零成本)。
66
- - **投影计价对历史冻结** —— 投影路径生效时,修改计价表只影响变更后折叠的事件;重启(或事件路径回退)才会重算整个日志。
72
+ - **有费率行才计价** —— 会话花费与今日花费只统计价目表(`billing.models`)里有的模型;没有费率行的模型不计入。`assistant/attempt` 用最近一条 `request/header` 的模型计价,因此首条 header 之前的 attempt 不计入。
73
+ - **最多 60 秒延迟** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;只有手动刷新(`force`)立即重算(仍受 revision 门控,日志未变则零成本)。浏览器端「本会话花费」读的是推送的投影值,因此不会滞后。
74
+ - **额度带 TTL 缓存** —— 一份 `/user/balance` 快照最多复用 15 秒,单次请求 5 秒超时;`force`(手动刷新)绕过 TTL。
75
+ - **投影计价跟随官方费率版本** —— 投影折叠按样本时刻解析费率版本,官方调价因此无需重折;手工改 `billing.models` 则只影响变更后折叠的事件,直到状态版本或进程重置(事件路径回退会重算整个日志)。
76
+ - **冷缓存行可能滞后于日志** —— 冷会话若缓存行覆盖查询日,会重读日志以求精确;若缓存行自身的日期不是查询日则直接采信、不读日志,因此进程在最后一次检查点之后、最后一条事件之前崩溃的会话,其尾部可能暂时少算,直到该会话被重新读取。