@rayadesu/dsh-llm-billing 0.3.12 → 0.3.14
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 +17 -4
- package/README.zh.md +17 -4
- package/lib/index.js +302 -60
- package/lib/typert.host.js +79 -6
- package/lib/typert.remote-client.d.ts +3 -1
- package/lib/typert.remote-client.js +79 -6
- package/lib/types/balance.d.ts +22 -2
- package/lib/types/balance.js +19 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.js +24 -5
- package/lib/types/today-spend.d.ts +192 -47
- package/lib/types/today-spend.js +270 -58
- package/lib/types/types.d.ts +50 -3
- package/package.json +20 -20
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: c3ca750612bab07f27a0d7d72acbcd80257fc7d5
|
|
6
|
+
README.zh.md: 1bc217580dfe245ae96a1c537af213dfd79ef482
|
package/README.md
CHANGED
|
@@ -16,26 +16,38 @@ 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
|
|
19
|
+
The plugin registers the `billing` Remote with seven 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 conversation, sorted by cost descending, each row carrying the session's durable title and its own share of the day — see [Subagent sessions](#subagent-sessions-ride-their-parents-row)), `getDelegatedSpend(sessionId, force?)` (the subagent part of one conversation — see [Subagent sessions](#subagent-sessions-ride-their-parents-row)), `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, one scan produces
|
|
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 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
|
-
- **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)
|
|
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.
|
|
27
|
+
|
|
28
|
+
Both paths therefore treat the revision gate as a **cache, never as a filter**: an unchanged log costs no I/O and still contributes its full spend and title to the aggregate and the ranking on every scan. A gate that skipped an unchanged session without adopting the resolution it already held would silently shrink today's total (and drop ranking rows) on every scan after the first — the projection path avoids that with its resolved-unit memory, the events path with the same memory now shared by both.
|
|
27
29
|
|
|
28
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.
|
|
29
31
|
|
|
30
32
|
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
33
|
|
|
34
|
+
### Subagent sessions ride their parent's row
|
|
35
|
+
|
|
36
|
+
A subagent child is work the delegating conversation paid for, not a session the user opened, so the day's ranking lists **conversations**: every subagent row is folded into the row of the top-level session at the root of its `parentSession` chain before the rows are sorted. DSH stamps a child's durable header with `origin: 'subagent'`, the delegating session's id, and a `delegationDepth` of parent depth + 1 (both markers are read structurally, so either one alone identifies a child); a multi-generation delegation therefore lands on the same root row. A **user fork** carries `parentSession` too but neither subagent marker, and stays a row of its own.
|
|
37
|
+
|
|
38
|
+
Each merged row reports two amounts: `total` is the conversation's whole day (the session plus every subagent it delegated, transitively) and `ownTotal` is the session's own spend, equal to `total` when no descendant priced anything that day. The day **aggregate** (`getTodaySpend`) is unchanged: it sums the same sessions either way, so the roll-up never moves money between totals — it only regroups rows. A top-level session whose own day was empty but whose subagents priced something still gets a row (`ownTotal` 0), titled from its log when the scan resolved it.
|
|
39
|
+
|
|
40
|
+
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
|
+
|
|
42
|
+
The badge shows that subtree added to the session's own live spend: `本会话花费` therefore reads as the **conversation's** amount (the chip and the panel carry the same label). The same read also reports `crossedDay` — whether the queried session was created before the current Beijing day — which is what gates the panel's parenthesized today share: the share reads the ranking row's `total` (merged against merged, both counting the same tree), but whether it is shown at all depends on that creation day rather than on comparing two amounts, because the live session figure and the 60-second-cached ranking row differ mid-turn by design.
|
|
43
|
+
|
|
32
44
|
## Forked sessions
|
|
33
45
|
|
|
34
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` / `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
47
|
|
|
36
48
|
## Runtime compatibility
|
|
37
49
|
|
|
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.
|
|
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` (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.6-alpha.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.
|
|
39
51
|
|
|
40
52
|
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
53
|
|
|
@@ -70,6 +82,7 @@ None; its only provider call is a credential-authenticated `/user/balance` read,
|
|
|
70
82
|
## Known Limitations and Deferred Work
|
|
71
83
|
|
|
72
84
|
- **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
|
+
- **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.
|
|
73
86
|
- **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; the parenthesized today share on that same line comes from the already-fetched today-session ranking (`getTodaySessionsSpend()`, under the same 60-second cache and revision gate), so that one number can trail by up to 60 seconds.
|
|
74
87
|
- **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
88
|
- **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).
|
package/README.zh.md
CHANGED
|
@@ -16,26 +16,38 @@
|
|
|
16
16
|
# baseURL: https://api.deepseek.com
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
插件注册 `billing` Remote
|
|
19
|
+
插件注册 `billing` Remote,含七个方法:`getBalance(force?)`(解析后的 `/user/balance` 快照;15 秒宿主 TTL 内复用,`force` 绕过,单次请求 5 秒超时)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(force?)`(今日按**对话**的计费花费,按花费从高到低排序,每行带会话的持久标题与该会话自身今日份金额——见[子代理会话并入父会话行](#子代理会话并入父会话行))、`getDelegatedSpend(sessionId, 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
|
|
23
|
+
`getTodaySpend()` 每次请求都不会全量扫描所有会话日志。一个 60 秒的北京日缓存带 in-flight 合并,服务于消息触发的读取;只有手动刷新(`force`)绕过时间窗口。缓存未命中时,**一次扫描同时产出聚合、排行与子代理读取所需的全会话累计**(聚合即排行各行之和):
|
|
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
|
-
- **事件路径**(无注册表时的回退):对每个会话用同一套计价折叠(收集时按北京日过滤,20
|
|
26
|
+
- **事件路径**(无注册表时的回退):对每个会话用同一套计价折叠(收集时按北京日过滤,20 万事件上限)。持久化 revision 未变的会话,改用本扫描器之前为**同一个 revision** 折出的结果作答,而不是重新读取;被事件上限截断的那一趟不留记忆,下一趟会重读被截断的部分。
|
|
27
|
+
|
|
28
|
+
两条路径因此都把 revision 门控当作**缓存,而不是过滤器**:日志未变既不花 I/O,也照样把完整的金额与标题计入合计与排行。若门控只是「跳过未变的会话」而不采用手上已有的解析结果,第一趟之后的每一趟都会悄悄少算今日合计(并丢掉排行行)——投影路径靠已解析单元的记忆避免,事件路径现在与它共用同一份记忆。
|
|
27
29
|
|
|
28
30
|
进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过(并被记住),而不是让整日合计失败。
|
|
29
31
|
|
|
30
32
|
注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率。官方费率版本随定价闭包一起进入折叠,并按样本自身时刻解析,因此被调价的系列无论日志多晚折叠都能正确计价自身历史;只有**手工修改配置**(`billing.models`)才只影响变更后折叠的事件(事件路径会重算整个日志),而该解析口径变化时单元 `stateVersion` 会一并提升,使已落检查点被丢弃重折而不是沿用旧值。
|
|
31
33
|
|
|
34
|
+
### 子代理会话并入父会话行
|
|
35
|
+
|
|
36
|
+
子代理(subagent)子会话是委派它的那次对话花的钱,而不是用户打开的会话,所以今日排行列的是**对话**:每一行子代理会话在排序之前都被并入其 `parentSession` 链顶端那个顶层会话的行里。DSH 会在子会话的持久 header 上盖 `origin: 'subagent'`、委派方会话 id 与 `delegationDepth`(父深度 + 1),两个标记都以结构方式读取,任一存在即认定为子会话,因此多代委派(子代理又派子代理)同样落到同一个顶层行。**用户手动分叉**的会话虽然也带 `parentSession`,但不带任何 subagent 标记,仍然是自己一行。
|
|
37
|
+
|
|
38
|
+
合并后的行给出两个金额:`total` 是这次对话的整日花费(本会话加上它(递归)委派的每个子代理),`ownTotal` 是本会话自己的花费,当天没有任何后代计价时两者相等。整日**合计**(`getTodaySpend`)不变:两条路径求和的是同一批会话,合并只是把行重新归组,不会在总额之间搬钱。顶层会话自己当天没计价、但它的子代理计价了,它照样有一行(`ownTotal` 为 0),标题取自扫描时折出的日志。
|
|
39
|
+
|
|
40
|
+
浏览器还需要同一套「对话」口径去显示整日/整份日志的花费,而会话自己的日志无法为它的委派子会话计价——所以有了 `getDelegatedSpend(sessionId)`:返回那棵子树的金额,即该会话(递归)委派的每个子代理会话、按其日志覆盖的每一天求和,并把模型行合并(分项仍然加得起来)。它与今日花费**共用同一次缓存扫描**(那一趟本来就会折出每个会话的全会话累计,因此这里不额外读日志),并额外给出 `isSubagent`——被查询的会话自己是不是委派子会话(是的话它的花费落在委派它的那个会话的排行行里)。用户手动分叉永远不会被算作委派子会话(它带 `parentSession`,但不带任何 subagent 标记)。
|
|
41
|
+
|
|
42
|
+
徽标把这棵子树加到会话自身的实时花费上:`本会话花费` 因此读作**整次对话**的金额(徽标与面板共用同一文案)。同一次读取还会给出 `crossedDay`——被查询的会话是否创建于当前北京日之前——它是面板括号里「今日份」的开关:金额仍取排行行的 `total`(两边都是合并口径、数的是同一棵树),但**是否显示**取决于创建日,而不是比较两个金额,因为实时的会话数字与 60 秒缓存的排行行在回合中途本来就会不一致。
|
|
43
|
+
|
|
32
44
|
## 分叉会话
|
|
33
45
|
|
|
34
46
|
分叉会话(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
47
|
|
|
36
48
|
## 运行时兼容性
|
|
37
49
|
|
|
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.
|
|
50
|
+
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.6-alpha.1`)与超前于它的 monorepo 运行时代码均无需改动即可工作。遇到两种形状都没有的未知运行时表面时,插件会显式失败而不是静默按零花费计价。排行的血缘读取同样以结构方式完成:header 里既没有 `origin: 'subagent'` 也没有非零 `delegationDepth` 的会话(旧日志,或创建时就没有这两个字段的会话)直接视作顶层会话、保留自己一行。
|
|
39
51
|
|
|
40
52
|
持久化服务的表面同样换代: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
53
|
|
|
@@ -70,6 +82,7 @@
|
|
|
70
82
|
## 已知限制与暂缓事项
|
|
71
83
|
|
|
72
84
|
- **有费率行才计价** —— 会话花费与今日花费只统计价目表(`billing.models`)里有的模型;没有费率行的模型不计入。`assistant/attempt` 用最近一条 `request/header` 的模型计价,因此首条 header 之前的 attempt 不计入。
|
|
85
|
+
- **合并行可能没有标题** —— 子代理的父会话不在本次扫描范围内时(例如父日志已被删除或归档),该行仍按其 header 里写的父会话 id 归属,但父会话日志从未被读取,合并行因此没有标题,浏览器显示「未命名」兜底。
|
|
73
86
|
- **最多 60 秒延迟** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;只有手动刷新(`force`)立即重算(仍受 revision 门控,日志未变则零成本)。浏览器端「本会话花费」读的是推送的投影值,因此不会滞后;它括号里的今日份金额取自已拉取的今日会话排行(`getTodaySessionsSpend()`,同样受 60 秒缓存与 revision 门控),所以那一项最多滞后 60 秒。
|
|
74
87
|
- **额度带 TTL 缓存** —— 一份 `/user/balance` 快照最多复用 15 秒,单次请求 5 秒超时;`force`(手动刷新)绕过 TTL。
|
|
75
88
|
- **投影计价跟随官方费率版本** —— 投影折叠按样本时刻解析费率版本,官方调价因此无需重折;手工改 `billing.models` 则只影响变更后折叠的事件,直到状态版本或进程重置(事件路径回退会重算整个日志)。
|