@rayadesu/dsh-llm-billing 0.3.16 → 0.3.18
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 +2 -2
- package/README.md +19 -8
- package/README.zh.md +20 -9
- package/icon.svg +14 -0
- package/lib/index.js +749 -416
- package/lib/typert.host.js +17 -7
- package/lib/typert.remote-client.js +17 -7
- package/lib/types/balance.d.ts +10 -0
- package/lib/types/balance.js +79 -13
- package/lib/types/beijing-time.d.ts +52 -0
- package/lib/types/beijing-time.js +88 -0
- package/lib/types/billing.d.ts +27 -388
- package/lib/types/billing.js +28 -627
- package/lib/types/cache.d.ts +110 -0
- package/lib/types/cache.js +155 -0
- package/lib/types/fold.d.ts +93 -0
- package/lib/types/fold.js +188 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/persistence.d.ts +113 -0
- package/lib/types/persistence.js +63 -0
- package/lib/types/pricing-table.d.ts +43 -0
- package/lib/types/pricing-table.js +118 -0
- package/lib/types/pricing.d.ts +203 -0
- package/lib/types/pricing.js +203 -0
- package/lib/types/projection.js +14 -0
- package/lib/types/session-lineage.d.ts +70 -0
- package/lib/types/session-lineage.js +99 -0
- package/lib/types/spend.d.ts +58 -0
- package/lib/types/spend.js +190 -0
- package/lib/types/today-spend.d.ts +27 -233
- package/lib/types/today-spend.js +73 -254
- package/lib/types/types.d.ts +28 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +21 -17
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: f494f3c1ab67b689b246e0bb729043dbb82d9ca3
|
|
6
|
+
README.zh.md: cfdd8cdbb2e17ae95647365a3753e38f56429017
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The plugin registers the `billing` Remote with seven methods: `getBalance(force?
|
|
|
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
|
|
23
|
+
`getTodaySpend()` never scans every session log per request. A 60-second Beijing-day cache with in-flight coalescing serves message-triggered reads, and **stale-while-revalidate** takes the wait out of the rest: once the window lapses, a plain reader holding a value for the queried day is answered from that value immediately while the scan runs behind it, so a session switch never waits on the day's whole-session scan. A **forced** read blocks for the new value instead — that is the manual refresh, and the badge's post-turn recompute, which has just changed the answer and must not be handed the value from before it (a plain read there would report each turn's cost one turn late). A day with no value yet (the first read, or a Beijing-day rollover: yesterday's total is never served as today's) waits too, because there is nothing honest to show. The window is stamped when a scan **completes**, not when it starts: a pass slower than its own TTL would otherwise be born expired and every read after it would start another pass back-to-back. Behind a miss, one scan produces the aggregate, the ranking, AND the whole-session totals the delegated read sums (the aggregate is the sum of the ranking rows):
|
|
24
24
|
|
|
25
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
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). A session whose persisted revision is unchanged is answered from the fold this scanner already priced for that exact revision instead of being re-read, and a pass cut short by the event cap remembers nothing — so the next one re-reads what it cut short.
|
|
@@ -29,6 +29,8 @@ Both paths therefore treat the revision gate as a **cache, never as a filter**:
|
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
32
|
+
Both paths fold **synchronously on the host's main loop** — the same loop that serves the GUI's own round trips (switching model, creating a session) — so a scan hands that loop back every `SCAN_YIELD_SESSIONS` (8) sessions, and the cold fan-out yields on the same cadence (`yieldToEventLoop`). A scan that never yielded held the loop for its entire duration, which is what made those two actions appear frozen while a cold scan ran.
|
|
33
|
+
|
|
32
34
|
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.
|
|
33
35
|
|
|
34
36
|
### Subagent sessions ride their parent's row
|
|
@@ -39,17 +41,17 @@ Each merged row reports two amounts: `total` is the conversation's whole day (th
|
|
|
39
41
|
|
|
40
42
|
The browser needs the same conversation view for a whole day's or a whole log's worth of spend, and a session's own log cannot price its delegation children — so `getDelegatedSpend(sessionId)` returns that subtree: every subagent session the given session delegated, transitively, summed across every day its log covers, with per-model rows merged so a breakdown still adds up. It is served from the SAME cached pass as today's spend (the scan folds each session's whole-session total anyway, so this costs no second read), and it also reports `isSubagent` — whether the queried session is itself a delegated child, whose spend therefore rides the ranking row of the session that delegated it. A user fork is never counted as a delegated child (it carries `parentSession` but no subagent marker).
|
|
41
43
|
|
|
42
|
-
The
|
|
44
|
+
The composer pill adds that subtree to the session's own live spend, so its amount reads as the **conversation's** amount — the pill is the only surface that shows it; the header badge's second line and the panel behind it both report today's account-level figures instead, and the two read the same state.
|
|
43
45
|
|
|
44
46
|
## Forked sessions
|
|
45
47
|
|
|
46
|
-
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` / `
|
|
48
|
+
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` (live) / `SessionHandle.inheritedEventCount` (the opened handle) 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.
|
|
47
49
|
|
|
48
50
|
## Runtime compatibility
|
|
49
51
|
|
|
50
|
-
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` (
|
|
52
|
+
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` (live) / `SessionHandle.inheritedEventCount` (the handle the persistence `open(id, 'read')` returns). Every log read in the plugin goes through the structural adapters `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`: the live surface is the one that kept both shapes — `liveSessionEvents` still accepts the ≤ 0.1.1-rc.2 `events` snapshot and the 0.1.2-alpha.4+ `snapshotEvents()` reader — while `forkBoundaryOf` prefers the exact `inheritedEventCount` (live or handle) and otherwise falls back to the durable `header.seedLength`, so the npm release baseline (`^0.2.0-rc.1`) 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. The ranking's lineage read is structural in the same way: a header that carries neither `origin: 'subagent'` nor a non-zero `delegationDepth` (an older log, or a session created without them) simply reads as a top-level session and keeps its own row.
|
|
51
53
|
|
|
52
|
-
The persistence service
|
|
54
|
+
The persistence service is handle-based only: it exposes `open(id, 'read')` + `SessionHandle.read()` / `list()` (DSH 0.1.2-alpha.5+, the single family the plugin's `^0.2.0-rc.1` peer targets). The scanner reads this one surface through `persistenceInspect` / `persistenceListSnapshots` — `persistenceListSnapshots` is a thin call to `list()`, and `persistenceInspect` opens the handle, reads, and closes it in a `finally` block (so the handle always closes, including after a failed read). The legacy `inspect(id)` / `listSnapshots()` surface was removed (Item 7), so production now serves only the handle family. `SessionHandle.read()` itself still has two return shapes — the bare event array, and since DSH `9b78f99dec` (0.1.5-alpha.1) `{ eventState, events }`; `handleReadEvents` unwraps both, so a cold read survives that generation change.
|
|
53
55
|
|
|
54
56
|
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.
|
|
55
57
|
|
|
@@ -59,7 +61,7 @@ The projection-cache reader likewise targets the current seam: `cachedSnapshot(h
|
|
|
59
61
|
| --- | --- | --- |
|
|
60
62
|
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential-reference (environment-variable) name resolved per call. |
|
|
61
63
|
| `baseURL` | `$DEEPSEEK_BASE_URL` then `https://api.deepseek.com` | Endpoint base; `/user/balance` is appended. |
|
|
62
|
-
| `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). |
|
|
64
|
+
| `models` | V4.1 Flash (`deepseek-flash`) + V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5/V2.6 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). |
|
|
63
65
|
| `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. |
|
|
64
66
|
| `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). |
|
|
65
67
|
|
|
@@ -69,7 +71,16 @@ The published table already ships DeepSeek's adjustments, so a session or a day
|
|
|
69
71
|
|
|
70
72
|
- **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.
|
|
71
73
|
- **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.
|
|
72
|
-
- MiMo-V2.5 series: untouched by either adjustment (flat rate).
|
|
74
|
+
- MiMo-V2.5 and V2.6 series: untouched by either adjustment (flat rate; V2.6 shipped 2026-09-22 at V2.5’s published rates).
|
|
75
|
+
|
|
76
|
+
## Plugin manager metadata
|
|
77
|
+
|
|
78
|
+
The plugin manager shows this package's row with a localized title and description plus an icon, all read from the package itself (the host evaluates no plugin code):
|
|
79
|
+
|
|
80
|
+
- `locale/en.json` and `locale/zh.json` — `{"meta": {"title": …, "description": …}}`, resolved against the active interface language with English as the fallback.
|
|
81
|
+
- `icon.svg` — a self-contained SVG (no external font or image references), declared as the package's top-level `icon` field.
|
|
82
|
+
|
|
83
|
+
Both are resolved through the package's `exports` map, so `./locale/*.json` must stay exported — dropping it makes the locale files resolve to `ERR_PACKAGE_PATH_NOT_EXPORTED`, which the host treats as "no metadata" and silently falls back to the bare package name. `locale/*.json` and `icon.svg` must also stay in `files`, or a packed tarball ships neither.
|
|
73
84
|
|
|
74
85
|
## Model Experience
|
|
75
86
|
|
|
@@ -83,7 +94,7 @@ None; its only provider call is a credential-authenticated `/user/balance` read,
|
|
|
83
94
|
|
|
84
95
|
- **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.
|
|
85
96
|
- **A merged row can be untitled** — a subagent whose parent session is not part of the scan (a deleted or archived parent log, for instance) is still attributed to the parent id its own header names, but that parent's log is never read, so the merged row reports no title and the browser shows its untitled fallback.
|
|
86
|
-
- **Up-to-60s staleness** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds;
|
|
97
|
+
- **Up-to-60s staleness, never an unasked wait** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds; past that window a plain read gets the last value at once and the scan runs behind it, so a reader never waits for the day's scan unless it asked to. A forced read (the manual refresh, and the badge's post-turn recompute) waits for a fresh recompute — still revision-gated, so an unchanged log costs nothing. The browser reads the live per-session value from the pushed projection instead, so the composer pill's amount is never stale; today's figures — the badge's second line and the panel's row — are account-level and have no pushed source behind them, so they move on mount, on the manual refresh, on a panel open, and on the post-turn recompute, and the ranking they can ride (`getTodaySessionsSpend()`, under the same 60-second cache and revision gate) can trail by up to 60 seconds.
|
|
87
98
|
- **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.
|
|
88
99
|
- **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).
|
|
89
100
|
- **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
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
### 今日花费读取路径(消息触发不再全量扫描)
|
|
22
22
|
|
|
23
|
-
`getTodaySpend()` 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight
|
|
23
|
+
`getTodaySpend()` 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight 合并,服务于消息触发的读取;此外 **stale-while-revalidate** 把「等」这件事也拿掉了:窗口一过期,普通读者手上只要有当天的值就立刻拿到它,重扫放到后台跑,因此切会话永远不会等当天那次全会话扫描。**带 `force` 的读取**则相反——它会阻塞等新值:这就是手动刷新,以及徽标在回合结束后的重算(这一轮刚刚改变了答案,不能把「这一轮之前的值」给它,否则今日行每轮都慢一拍)。当天还没有值的读取同样阻塞(首次读取,或跨北京日翻篇:昨天的合计绝不会当作今天的值返回),因为那时没有可诚实展示的东西。窗口按扫描**完成时刻**盖章,而不是开始时刻:慢于自身 TTL 的一趟扫描否则一出生就是过期状态,之后每次读取都会紧接着再起一趟。缓存未命中时,**一次扫描同时产出聚合、排行与子代理读取所需的全会话累计**(聚合即排行各行之和):
|
|
24
24
|
|
|
25
25
|
- **投影路径**(当组合中装配了 `@deepseek-ai/dsh-session-projection` 时启用):插件注册客户端可见的 `billingTodaySpend` 投影单元——**注册表一出现就提前注册**,因此 DSH 的 write-behind 会在每个 `turn/end` 为每个会话落一行检查点。live 会话零日志 I/O 直读其 eager 单元;冷会话若投影缓存的 `cachedSnapshot` 行(零 I/O)自身的最新计价日不是查询日,就直接作答,否则(或没有可用行时)才读取日志本地折叠。只有持久化 revision 在上次解析后变化过的会话才会被读取;读取失败的会话按 revision 记住,不再每轮重试。
|
|
26
26
|
- **事件路径**(无注册表时的回退):对每个会话用同一套计价折叠(收集时按北京日过滤,20 万事件上限)。持久化 revision 未变的会话,改用本扫描器之前为**同一个 revision** 折出的结果作答,而不是重新读取;被事件上限截断的那一趟不留记忆,下一趟会重读被截断的部分。
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过(并被记住),而不是让整日合计失败。
|
|
31
31
|
|
|
32
|
+
两条路径的折叠都是**宿主主事件循环上的同步计算**——也就是处理 GUI 自身往返(切模型、新建会话)的那个循环——所以扫描每折叠 `SCAN_YIELD_SESSIONS`(8)个会话就让出一次事件循环,冷会话并发扇出也按同样节奏让出(`yieldToEventLoop`)。不让出的扫描会把该循环占满整趟,那正是冷扫期间这两个操作看起来「卡死」的原因。
|
|
33
|
+
|
|
32
34
|
注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率。官方费率版本随定价闭包一起进入折叠,并按样本自身时刻解析,因此被调价的系列无论日志多晚折叠都能正确计价自身历史;只有**手工修改配置**(`billing.models`)才只影响变更后折叠的事件(事件路径会重算整个日志),而该解析口径变化时单元 `stateVersion` 会一并提升,使已落检查点被丢弃重折而不是沿用旧值。
|
|
33
35
|
|
|
34
36
|
### 子代理会话并入父会话行
|
|
@@ -39,17 +41,17 @@
|
|
|
39
41
|
|
|
40
42
|
浏览器还需要同一套「对话」口径去显示整日/整份日志的花费,而会话自己的日志无法为它的委派子会话计价——所以有了 `getDelegatedSpend(sessionId)`:返回那棵子树的金额,即该会话(递归)委派的每个子代理会话、按其日志覆盖的每一天求和,并把模型行合并(分项仍然加得起来)。它与今日花费**共用同一次缓存扫描**(那一趟本来就会折出每个会话的全会话累计,因此这里不额外读日志),并额外给出 `isSubagent`——被查询的会话自己是不是委派子会话(是的话它的花费落在委派它的那个会话的排行行里)。用户手动分叉永远不会被算作委派子会话(它带 `parentSession`,但不带任何 subagent 标记)。
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
输入框 pill 把这棵子树加到会话自身的实时花费上,因此 pill 的金额读作**整次对话**的金额——pill 是唯一显示它的界面;徽标第二行与它后面的面板报的是账户级的今日口径,两者读的是同一份 state。
|
|
43
45
|
|
|
44
46
|
## 分叉会话
|
|
45
47
|
|
|
46
|
-
分叉会话(DSH 的「分叉会话」)的日志以来源会话事件的逐字节副本开头。若不特殊处理,同一批模型输出会按副本数重复计费:子会话的会话花费会包含继承前缀,今日花费也会在父会话之外再计一次。插件只对会话的**自有事件**计费——分叉边界取自已持久化的会话状态(≤ 0.1.1-rc.2 运行时为 `header.seedLength`;0.1.2-alpha.4+ 运行时为 `Session.inheritedEventCount
|
|
48
|
+
分叉会话(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 的会话——包括普通会话与冷恢复——不带边界,正常全额计费。
|
|
47
49
|
|
|
48
50
|
## 运行时兼容性
|
|
49
51
|
|
|
50
|
-
0.1.2-alpha.4 起,DSH 把 live `Session` 的日志读取表面从 `Session.events` 改为 `Session.snapshotEvents()`(无参 = 当前全量日志)与 `Session.ownEvents()`,并把 `SessionHeader.seedLength` 移至 `Session.inheritedEventCount`(持久化侧 `
|
|
52
|
+
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` 的会话(旧日志,或创建时就没有这两个字段的会话)直接视作顶层会话、保留自己一行。
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
持久化服务现在是「仅 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` 同时接受两种形状,冷读因此不受该代际变更影响。
|
|
53
55
|
|
|
54
56
|
投影缓存的读取同样对准当前 seam:`cachedSnapshot(header, inheritedEventCount, keys)`(零 I/O,只读 wire 行)。它的前身——自己读日志的异步 `coldSnapshot(id)`——已不存在,插件不再依赖它。
|
|
55
57
|
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
| --- | --- | --- |
|
|
60
62
|
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次调用时解析的凭据引用(环境变量)名。 |
|
|
61
63
|
| `baseURL` | `$DEEPSEEK_BASE_URL`,其次 `https://api.deepseek.com` | 端点基础地址;会追加 `/user/balance`。 |
|
|
62
|
-
| `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,让历史日志仍有可读标签)。 |
|
|
64
|
+
| `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,让历史日志仍有可读标签)。 |
|
|
63
65
|
| `billing.peakHours` | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 |
|
|
64
66
|
| `billing.models` | 官方 V4 + MiMo 费率 | 每个模型的单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token),可带生效时刻 `effectiveFrom`(epoch 毫秒,含该时刻)。 |
|
|
65
67
|
|
|
@@ -69,7 +71,16 @@
|
|
|
69
71
|
|
|
70
72
|
- **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 之前的自身用量)沿用被取代的旧价。
|
|
71
73
|
- **2026-09-14 12:00(北京时间)**(`V4_PRO_ROUTE_SWITCH_AT`):V4 Pro 路由按公告改由 V4.1 Flash 服务并按 V4.1 Flash 计费,该行因此带有第二个费率版本。
|
|
72
|
-
- MiMo-V2.5
|
|
74
|
+
- MiMo-V2.5 与 V2.6 系列不受两轮调整影响(统一费率;V2.6 于 2026 年 9 月 22 日发布,沿用 V2.5 公布的费率)。
|
|
75
|
+
|
|
76
|
+
## 插件管理页的展示元数据
|
|
77
|
+
|
|
78
|
+
插件管理页上本包那一行显示的标题、描述与图标都取自包自身(宿主不执行任何插件代码):
|
|
79
|
+
|
|
80
|
+
- `locale/en.json` 与 `locale/zh.json` —— 形状 `{"meta": {"title": …, "description": …}}`,按当前界面语言解析、回落英文。
|
|
81
|
+
- `icon.svg` —— 自包含 SVG(不引用任何外部字体或图片),由 `package.json` 顶层的 `icon` 字段声明。
|
|
82
|
+
|
|
83
|
+
两者都经包的 `exports` 解析,所以 `./locale/*.json` 必须保持导出——去掉它会让 locale 文件解析成 `ERR_PACKAGE_PATH_NOT_EXPORTED`,宿主把它当作「没有元信息」,静默回落到裸包名。`locale/*.json` 与 `icon.svg` 也必须留在 `files` 里,否则打出的 tarball 两个都不带。
|
|
73
84
|
|
|
74
85
|
## 模型体验
|
|
75
86
|
|
|
@@ -81,9 +92,9 @@
|
|
|
81
92
|
|
|
82
93
|
## 已知限制与暂缓事项
|
|
83
94
|
|
|
84
|
-
-
|
|
95
|
+
- **有费率行才计价,且不靠名字猜** —— 会话花费与今日花费只按价目表(`billing.models`)里该模型自己的行计价;没有行就不计价,但用量会记为 `unpriced` 并按模型告警一次(其中写明模型名),而不是无声丢弃。要给它计价,在 `billing.models` 补一行即可。`assistant/attempt` 用最近一条 `request/header` 的模型计价,因此首条 header 之前的 attempt 不计入。
|
|
85
96
|
- **合并行可能没有标题** —— 子代理的父会话不在本次扫描范围内时(例如父日志已被删除或归档),该行仍按其 header 里写的父会话 id 归属,但父会话日志从未被读取,合并行因此没有标题,浏览器显示「未命名」兜底。
|
|
86
|
-
- **最多 60
|
|
97
|
+
- **最多 60 秒延迟,但不会让人白等** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;过了这个窗口,普通读取直接拿到上一次的值、重扫放到后台,因此读者从不等待当天那次扫描(除非它主动要求等)。带 `force` 的读取(手动刷新,以及徽标在回合结束后的重算)会等一次重算——仍受 revision 门控,日志未变则零成本。浏览器端的输入框 pill 读的是推送的投影值,因此不会滞后;今日口径(徽标第二行与面板那一行)是账户级的、没有推送源,只在挂载、手动刷新、打开面板与回合结束重算时变动,而它可能跟随的今日会话排行(`getTodaySessionsSpend()`,同样受 60 秒缓存与 revision 门控)最多滞后 60 秒。
|
|
87
98
|
- **额度带 TTL 缓存** —— 一份 `/user/balance` 快照最多复用 15 秒,单次请求 5 秒超时;`force`(手动刷新)绕过 TTL。
|
|
88
99
|
- **投影计价跟随官方费率版本** —— 投影折叠按样本时刻解析费率版本,官方调价因此无需重折;手工改 `billing.models` 则只影响变更后折叠的事件,直到状态版本或进程重置(事件路径回退会重算整个日志)。
|
|
89
100
|
- **冷缓存行可能滞后于日志** —— 冷会话若缓存行覆盖查询日,会重读日志以求精确;若缓存行自身的日期不是查询日则直接采信、不读日志,因此进程在最后一次检查点之后、最后一条事件之前崩溃的会话,其尾部可能暂时少算,直到该会话被重新读取。
|
package/icon.svg
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" role="img" aria-label="Billing Core">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="24" y1="8.0" x2="24" y2="40.0" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0" stop-color="#2f6fed"/>
|
|
5
|
+
<stop offset=".5" stop-color="#35b8f0"/>
|
|
6
|
+
<stop offset="1" stop-color="#3fddbe"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<circle cx="24" cy="24" r="14.53" fill="none" stroke="url(#g)" stroke-width="2.94"/>
|
|
10
|
+
<path d="M33.5 24.0 L32.9 24.4 L32.0 24.7 L31.1 24.9 L30.5 25.1 L30.0 25.3 L29.5 25.5 L29.1 25.6 L28.8 25.7 L28.5 25.9 L28.3 26.0 L28.1 26.1 L27.9 26.3 L27.7 26.4 L27.6 26.5 L27.4 26.6 L27.2 26.7 L27.1 26.8 L26.9 26.9 L26.8 27.1 L26.7 27.2 L26.6 27.4 L26.5 27.6 L26.4 27.7 L26.3 27.9 L26.1 28.1 L26.0 28.3 L25.9 28.6 L25.8 28.9 L25.6 29.2 L25.5 29.6 L25.3 30.1 L25.2 30.6 L25.0 31.2 L24.7 32.1 L24.4 33.0 L24.0 33.5 L24.0 29.5 L24.2 29.2 L24.4 28.7 L24.6 28.3 L24.7 27.9 L24.8 27.4 L24.8 27.1 L24.9 26.8 L25.0 26.6 L25.0 26.5 L25.1 26.3 L25.1 26.1 L25.2 26.0 L25.2 25.9 L25.3 25.8 L25.4 25.8 L25.4 25.7 L25.5 25.6 L25.5 25.5 L25.6 25.5 L25.7 25.4 L25.8 25.4 L25.8 25.3 L26.0 25.3 L26.1 25.2 L26.2 25.2 L26.4 25.1 L26.6 25.1 L26.8 25.0 L27.0 24.9 L27.3 24.9 L27.6 24.8 L27.9 24.7 L28.3 24.6 L28.7 24.4 L29.3 24.2 L29.5 24.0 Z" fill="url(#g)" transform="rotate(0 24 24)"/>
|
|
11
|
+
<path d="M33.5 24.0 L32.9 24.4 L32.0 24.7 L31.1 24.9 L30.5 25.1 L30.0 25.3 L29.5 25.5 L29.1 25.6 L28.8 25.7 L28.5 25.9 L28.3 26.0 L28.1 26.1 L27.9 26.3 L27.7 26.4 L27.6 26.5 L27.4 26.6 L27.2 26.7 L27.1 26.8 L26.9 26.9 L26.8 27.1 L26.7 27.2 L26.6 27.4 L26.5 27.6 L26.4 27.7 L26.3 27.9 L26.1 28.1 L26.0 28.3 L25.9 28.6 L25.8 28.9 L25.6 29.2 L25.5 29.6 L25.3 30.1 L25.2 30.6 L25.0 31.2 L24.7 32.1 L24.4 33.0 L24.0 33.5 L24.0 29.5 L24.2 29.2 L24.4 28.7 L24.6 28.3 L24.7 27.9 L24.8 27.4 L24.8 27.1 L24.9 26.8 L25.0 26.6 L25.0 26.5 L25.1 26.3 L25.1 26.1 L25.2 26.0 L25.2 25.9 L25.3 25.8 L25.4 25.8 L25.4 25.7 L25.5 25.6 L25.5 25.5 L25.6 25.5 L25.7 25.4 L25.8 25.4 L25.8 25.3 L26.0 25.3 L26.1 25.2 L26.2 25.2 L26.4 25.1 L26.6 25.1 L26.8 25.0 L27.0 24.9 L27.3 24.9 L27.6 24.8 L27.9 24.7 L28.3 24.6 L28.7 24.4 L29.3 24.2 L29.5 24.0 Z" fill="url(#g)" transform="rotate(90 24 24)"/>
|
|
12
|
+
<path d="M33.5 24.0 L32.9 24.4 L32.0 24.7 L31.1 24.9 L30.5 25.1 L30.0 25.3 L29.5 25.5 L29.1 25.6 L28.8 25.7 L28.5 25.9 L28.3 26.0 L28.1 26.1 L27.9 26.3 L27.7 26.4 L27.6 26.5 L27.4 26.6 L27.2 26.7 L27.1 26.8 L26.9 26.9 L26.8 27.1 L26.7 27.2 L26.6 27.4 L26.5 27.6 L26.4 27.7 L26.3 27.9 L26.1 28.1 L26.0 28.3 L25.9 28.6 L25.8 28.9 L25.6 29.2 L25.5 29.6 L25.3 30.1 L25.2 30.6 L25.0 31.2 L24.7 32.1 L24.4 33.0 L24.0 33.5 L24.0 29.5 L24.2 29.2 L24.4 28.7 L24.6 28.3 L24.7 27.9 L24.8 27.4 L24.8 27.1 L24.9 26.8 L25.0 26.6 L25.0 26.5 L25.1 26.3 L25.1 26.1 L25.2 26.0 L25.2 25.9 L25.3 25.8 L25.4 25.8 L25.4 25.7 L25.5 25.6 L25.5 25.5 L25.6 25.5 L25.7 25.4 L25.8 25.4 L25.8 25.3 L26.0 25.3 L26.1 25.2 L26.2 25.2 L26.4 25.1 L26.6 25.1 L26.8 25.0 L27.0 24.9 L27.3 24.9 L27.6 24.8 L27.9 24.7 L28.3 24.6 L28.7 24.4 L29.3 24.2 L29.5 24.0 Z" fill="url(#g)" transform="rotate(180 24 24)"/>
|
|
13
|
+
<path d="M33.5 24.0 L32.9 24.4 L32.0 24.7 L31.1 24.9 L30.5 25.1 L30.0 25.3 L29.5 25.5 L29.1 25.6 L28.8 25.7 L28.5 25.9 L28.3 26.0 L28.1 26.1 L27.9 26.3 L27.7 26.4 L27.6 26.5 L27.4 26.6 L27.2 26.7 L27.1 26.8 L26.9 26.9 L26.8 27.1 L26.7 27.2 L26.6 27.4 L26.5 27.6 L26.4 27.7 L26.3 27.9 L26.1 28.1 L26.0 28.3 L25.9 28.6 L25.8 28.9 L25.6 29.2 L25.5 29.6 L25.3 30.1 L25.2 30.6 L25.0 31.2 L24.7 32.1 L24.4 33.0 L24.0 33.5 L24.0 29.5 L24.2 29.2 L24.4 28.7 L24.6 28.3 L24.7 27.9 L24.8 27.4 L24.8 27.1 L24.9 26.8 L25.0 26.6 L25.0 26.5 L25.1 26.3 L25.1 26.1 L25.2 26.0 L25.2 25.9 L25.3 25.8 L25.4 25.8 L25.4 25.7 L25.5 25.6 L25.5 25.5 L25.6 25.5 L25.7 25.4 L25.8 25.4 L25.8 25.3 L26.0 25.3 L26.1 25.2 L26.2 25.2 L26.4 25.1 L26.6 25.1 L26.8 25.0 L27.0 24.9 L27.3 24.9 L27.6 24.8 L27.9 24.7 L28.3 24.6 L28.7 24.4 L29.3 24.2 L29.5 24.0 Z" fill="url(#g)" transform="rotate(270 24 24)"/>
|
|
14
|
+
</svg>
|