@rayadesu/dsh-client-ui-billing 0.3.12 → 0.3.13

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: a866a9fc6e47278d35a8acf3a797a6230b8314e2
6
- README.zh.md: 3a6993fc7690572fb3d30f084882c6f2e72d207d
5
+ README.md: 4f5111e619c2fe63ad58f31d77a1c87fe545c751
6
+ README.zh.md: 39d503d8496a482f832d56c8a784cf38b9c0c2d6
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- Web billing feature owner: contributes one entry to `conversation.session.header.utilities` that mounts its own `billing` Remote and renders the DeepSeek account balance, this session's billed spend (read from the host-pushed `billingTodaySpend` projection, with `billing/getSessionSpend` as the fallback when the projection registry is absent), today's spend across every session (`billing/getTodaySpend`), today's per-session ranking (`billing/getTodaySessionsSpend`), and per-turn costs (`billing/getSessionTurnSpends`) as a top-right label box. The host half of the capability lives in [`dsh-llm-billing`](../llm-billing/README.md), which owns the `/user/balance` transport, the peak/off-peak pricing table, and the `billing` Remote namespace; this package mounts that Remote and renders what it returns.
5
+ Web billing feature owner: contributes one entry to `conversation.session.header.utilities` that mounts its own `billing` Remote and renders the DeepSeek account balance, this conversation's billed spend (this session's own spend read from the host-pushed `billingTodaySpend` projection, with `billing/getSessionSpend` as the fallback when the projection registry is absent, plus the subagent sessions it delegated from `billing/getDelegatedSpend`), today's spend across every session (`billing/getTodaySpend`), today's per-session ranking (`billing/getTodaySessionsSpend`, one row per conversation — the host has already merged every subagent session's spend into the row of the session that delegated it), and per-turn costs (`billing/getSessionTurnSpends`) as a top-right label box. The host half of the capability lives in [`dsh-llm-billing`](../llm-billing/README.md), which owns the `/user/balance` transport, the peak/off-peak pricing table, and the `billing` Remote namespace; this package mounts that Remote and renders what it returns.
6
6
 
7
- The trigger shows two lines — the remaining balance and this conversation's billed spend ("本轮对话花费 ¥X") and renders immediately from the last settled balance (kept per plugin instance), revalidating in the background; before the first balance ever lands it renders nothing. Clicking it opens a label box with the remaining amount, then today's billed token count with today's all-session spend beside it (今日 Token / 今日花费, one row; the count sums the three billed buckets of every priced row, so a cache-hit input token and an output token each count once, and it renders in DSH's own compact notation with DSH's ` tok` unit — `517 tok`, `12.2K tok`, `1.2M tok`: below 1e3 the plain integer, below 1e6 the shared `K` unit, otherwise `M`, keeping one decimal under 100 and rounding from 100 up), then this session's billed spend on its own line under them (本会话花费, priced per usage sample at the official peak/off-peak rate peak windows apply weekdays Mon–Fri only, weekends are off-peak; a forked session prices only its own post-fork work); a bare parenthesized amount rides right after the session amount ((¥X) — that is **this session's share of today**, taken from the already-fetched today-session ranking and matched by the current session id, so it is neither the ranking's first row nor the day's total. It renders **only when the two amounts disagree**, which is exactly the day-crossing case; an unsettled ranking renders nothing (unknown is not a disagreement, and a session that has not crossed a Beijing day bills exactly its own total today, so the share would just repeat the amount beside it); next come one priced row per model with the cost breakdown under DSH's own bucket wording and row order ("未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z" — DSH's `message.turnUsage` labels; the host prices cache writes at the miss rate, so they ride the uncached-input bucket), a manual refresh action, and a short spend disclaimer on the `?` button (only DeepSeek and MiMo models are estimated, each message priced at its own peak/off-peak rate; the next line names the parenthesized amount on the session row — this session's spend today — and the last line is a bare version like `v0.3.12`, stamped in at build time from this package's manifest, so a report can name the build; the full rate schedule lives in the host README, because the DSH tooltip bubble fits a short label only, is clipped at the viewport edge when long, and cannot be hovered — the hint is placed below the button, the side whose viewport fit can flip). The remaining-amount row (`API 剩余金额`) renders larger than the spend rows below it (今日 Token and 今日花费, and 本会话花费 with its parenthesized today share). Below the model rows the panel ends with the **today session-spend ranking** (`今日会话花费`): sessions sorted by today's cost, highest first, each row a fixed right-aligned rank column, then the same middot separator the cost breakdown uses ("·" with the same size, tone, and spacing), then the session's durable title (renames sync automatically), then its amount — the title keeps its indented rank column; at most `SESSION_RANKING_LIMIT` (10) rows render, with a "…还有 N 个会话" hint for the rest. Refreshing keeps the last values visible rather than blanking them, and a refresh failure retains the last good value. A session or day without priced usage shows a "no usage recorded" word rather than a fabricated figure. A failure — no API key configured, a rejected credential, a transport error — renders a muted "Balance unavailable" word whose tooltip carries the Remote's own error message.
7
+ The trigger shows two lines — the remaining balance and this conversation's billed spend — where the balance line is the panel's headline label without its `API` prefix (`剩余金额` / `Balance`, the chip being narrow) and the spend line is the panel's own `label.sessionSpend` (`本会话花费` / `This session`), word for word; it renders immediately from the last settled balance (kept per plugin instance), revalidating in the background, and before the first balance ever lands it renders nothing. Clicking it opens a label box with the remaining amount, then today's billed token count with today's all-session spend beside it (今日 Token / 今日花费, one row; the count sums the three billed buckets of every priced row, so a cache-hit input token and an output token each count once, and it renders in DSH's own compact notation with DSH's ` tok` unit — `517 tok`, `12.2K tok`, `1.2M tok`: below 1e3 the plain integer, below 1e6 the shared `K` unit, otherwise `M`, keeping one decimal under 100 and rounding from 100 up), with the day's cache-hit share right after that figure, bare and unparenthesized — DSH's own hit-rate rule, an integer percent that grows decimals only as far as a partial hit needs to stay below 100 (`99`, `99.5`, `99.95`), and nothing at all when the day billed no prompt-side input — then today's two bucket detail lines directly under it (tokens on top, costs below, each line on its own with its natural ` · ` spacing, in the per-model breakdown line's typography but on the third section's row spacing (the today session-spend ranking's tight 6px rhythm) — no column alignment between them; DSH's bucket wording and row order; every spend amount renders at three significant digits via `formatSpendSignificant` — the day figure and its bucket costs, this session's line, each per-model row and its bucket line, and the ranking amounts — but never finer than four decimals, so an amount below ¥0.0001 reads `¥0`; the balance line alone keeps `formatSpend`'s up-to-four decimals), then this conversation's billed spend on its own line under them (本会话花费 — this session's own spend plus the subagent sessions it delegated, their model rows merged in, priced per usage sample at the official peak/off-peak rate: peak windows apply weekdays Mon–Fri only, weekends are off-peak; a forked session prices only its own post-fork work); a bare amount rides right after it (¥X) — that is **this conversation's share of today**, taken from the already-fetched today-session ranking and matched by the current session id, so it is neither the ranking's first row nor the day's total. It reads the row's `total`, which counts the same tree as the amount beside it (the whole conversation), so the trailing number always stays the part of that amount which fell on the day. It renders **only when the conversation did NOT start today** — the session's own creation day decides, reported as `crossedDay` by `billing/getDelegatedSpend`, never a comparison of the two amounts: the session figure is live while the ranking row is 60-second cached, so mid-turn they differ by a few cents and a comparison would flash the rider for a session created today. An unsettled ranking renders nothing (the share itself is what is conditional), a session that started today never shows one (it billed everything it ever billed today), and neither does a session that is itself a delegated subagent child its spend rides its delegator's ranking row, so its own share is unknown rather than zero; next come one priced row per model with the cost breakdown under DSH's own bucket wording and row order ("未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z" — DSH's `message.turnUsage` labels; the host prices cache writes at the miss rate, so they ride the uncached-input bucket; a lone priced model renders no name row at all — neither its name (which says nothing new in a session that only ever billed one model) nor its amount (which repeats 本会话花费 above), leaving only its bucket line, which takes over the name row's own 2px header gap — while two or more models get a named row each), a manual refresh action, and a short spend disclaimer on the `?` button (only DeepSeek and MiMo models are estimated, each message priced at its own peak/off-peak rate; the next line states that the amounts include the subagent sessions this session delegated; the line after that names the amount trailing the session row — this conversation's spend today — and the last line is a bare version like `v0.3.13`, stamped in at build time from this package's manifest, so a report can name the build; the full rate schedule lives in the host README, because the DSH tooltip bubble fits a short label only, is clipped at the viewport edge when long, and cannot be hovered — the hint is placed below the button, the side whose viewport fit can flip). The remaining-amount row (`API 剩余金额`) renders larger than the spend rows below it (今日 Token and 今日花费, and 本会话花费 with its trailing today share). Below the model rows the panel ends with the **today session-spend ranking** (`今日会话花费`): conversations sorted by today's cost, highest first — one row per conversation, because the host has already merged each subagent session's spend into the row of the session that delegated it — each row a fixed right-aligned rank column, then the same middot separator the cost breakdown uses ("·" with the same size, tone, and spacing), then the session's durable title (renames sync automatically), then its amount — the title keeps its indented rank column; at most `SESSION_RANKING_LIMIT` (10) rows render, with a "…还有 N 个会话" hint for the rest. Refreshing keeps the last values visible rather than blanking them, and a refresh failure retains the last good value. A session or day without priced usage shows a "no usage recorded" word rather than a fabricated figure. A failure — no API key configured, a rejected credential, a transport error — renders a muted "Balance unavailable" word whose tooltip carries the Remote's own error message.
8
8
 
9
9
  The package also contributes the **turn-cost entry** (`billing-turn-cost`, order 20) to ui-chat's `conversation.chat.assistant-actions` list slot — the same strip `ui-message-feedback` uses, so entries coexist; the entry's own CSS `order: 1` sorts it after every order-0 sibling, so the amount lands at the **end** of the actions row (after the clock). The entry renders a plain static `¥X` (no icon, no label, no interaction) for each completed turn's closing assistant message, resolved from a shared per-session `messageId → cost` map (`billing/getSessionTurnSpends`, one fetch per session, coalesced across concurrently mounting rows; a message id the map does not know triggers exactly one refetch for a newly completed turn). Turns that priced to zero (no DeepSeek usage) and failed loads render nothing, so a Remote outage never clutters the row.
10
10
 
@@ -17,9 +17,9 @@ The badge is account-level even though the slot is session-scoped: the header ut
17
17
  The spends follow the conversation and the balance is cached, not polled:
18
18
 
19
19
  - **Session spend is pushed, not fetched** — the host folds each session's spend into the client-visible `billingTodaySpend` projection and pushes every change; the badge subscribes with the framework `useProjection` seat, so the session line is live with zero Remote calls while the projection registry is composed. When the key is absent, the same line falls back to `billing/getSessionSpend` — the same face and ladder the unregistered composer spend card reads.
20
- - **Today's spend follows settled turns, debounced** — the component subscribes to the current session's running flag through the framework `useSession` seat. When a prompt turn settles (the flag flips back to idle) it recomputes today's spend (`billing/getTodaySpend`; the 今日 Token figure on that same row is summed from the returned model rows in place, with no extra request); the recompute is debounced for two seconds, so a burst of turns (an agent continuing across turns) prices once instead of once per turn. The ranking (`billing/getTodaySessionsSpend`) is fetched only while the detail panel is open (and on refresh), so a badge that stays closed never pays for the all-session scan; the parenthesized today share on the 本会话花费 row reads that same fetch, so it costs no extra request and moves with the ranking (and until that ranking settles no share is rendered at all, because only a disagreement between the two amounts shows one).
20
+ - **Today's spend follows settled turns, debounced** — the component subscribes to the current session's running flag through the framework `useSession` seat. When a prompt turn settles (the flag flips back to idle) it recomputes today's spend (`billing/getTodaySpend`; the 今日 Token figure on that same row is summed from the returned model rows in place, with no extra request); the recompute is debounced for two seconds, so a burst of turns (an agent continuing across turns) prices once instead of once per turn. The same settle refreshes this session's delegated subtotal (`billing/getDelegatedSpend`), which is what keeps the session amount a whole-conversation figure as subagents finish. The ranking (`billing/getTodaySessionsSpend`) is fetched only while the detail panel is open (and on refresh), so a badge that stays closed never pays for the all-session scan; the trailing today share on the 本会话花费 row reads that same fetch (the row's `total`), so it costs no extra request and moves with the ranking (and until that ranking settles no share is rendered at all, because only a disagreement between the two amounts shows one).
21
21
  - **Balance is TTL-cached** — the account balance is fetched on mount, on session switch, and on the explicit refresh action. The host reuses one snapshot for up to 15 seconds (`force` on the manual refresh bypasses it) and aborts a request after 5 seconds; the browser keeps the last settled value so a session switch renders the amount immediately. There is still no polling and no automatic refetch.
22
- - **Refresh keeps the last values** — an in-flight refresh leaves the previous values on screen, and a failed refresh retains the last good value instead of blanking it. The refresh action calls `billing/getBalance(true)`, `billing/getTodaySpend(true)`, and `billing/getTodaySessionsSpend(true)`, which bypass the host-side caches; the turn-triggered recompute and the mount/session-switch reads use the cached path.
22
+ - **Refresh keeps the last values** — an in-flight refresh leaves the previous values on screen, and a failed refresh retains the last good value instead of blanking it. The refresh action calls `billing/getBalance(true)`, `billing/getTodaySpend(true)`, `billing/getDelegatedSpend(sessionId, true)`, and `billing/getTodaySessionsSpend(true)`, which bypass the host-side caches; the turn-triggered recompute and the mount/session-switch reads use the cached path.
23
23
 
24
24
  ## Model Experience
25
25
 
@@ -32,8 +32,9 @@ None; the package never assembles or sends provider requests, and its one RPC is
32
32
  ## Known Limitations and Deferred Work
33
33
 
34
34
  - **First balance line only** — the balance reads the primary (`balance_infos[0]`) currency line; other currency lines are not shown.
35
- - **Ranking capped at 10** — the panel shows at most `SESSION_RANKING_LIMIT` sessions, with a "…N more sessions" hint.
35
+ - **Ranking capped at 10** — the panel shows at most `SESSION_RANKING_LIMIT` conversations, with a "…N more sessions" hint. The cap applies after the host's subagent roll-up, so one row is one conversation rather than one delegation.
36
+ - **The delegated subtotal moves with the turn, not per event** — the session amount's own part is live (the pushed projection moves as the turn streams), while the subagent part comes from `billing/getDelegatedSpend` on mount, on manual refresh, and when a turn settles, served from the same 60-second host cache as today's spend. A subagent that burns money mid-turn therefore lands on the parent's amount at the next read, not per event.
36
37
  - **Turn cost needs a finalized closing message** — interrupted turns have no actions row, so no turn cost is shown for them.
37
- - **Today's tokens count priced rows only** — the figure sums the three billing buckets of the model rows `billing/getTodaySpend` returns (models with a rate row), so a model without one contributes no tokens; cache writes ride the cache-miss bucket.
38
+ - **Today's tokens count priced rows only** — the figure sums the three billing buckets of the model rows `billing/getTodaySpend` returns (models with a rate row), so a model without one contributes no tokens; cache writes ride the cache-miss bucket. The hit-rate figure riding that row takes the prompt side only — cache read over cache read plus uncached input — so output tokens never enter its denominator, exactly the split DSH's own `billedInputTokens` makes. The panel types in exactly **three levels**, defined once as custom properties on `.panel` in its stylesheet with every member referencing its own level's tokens: **level one** 11px/16px tertiary tabular-nums for the bucket detail lines (`未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z`), a whole today session-spend ranking row (rank, middot, title, amount), the "…N more sessions" footer, and the two trailing figures (this session's share of today, the day's cache-hit share); **level two** 12px/18px secondary for the model name with its row amount and the 今日会话花费 title — a level's text and its numbers may differ in tone: a level-two member's amount uses that level's own number tone (`--billing-type-2-number-tone`, one step below its label), and any future level-two amount follows it; **level three** 13px/18px primary tabular-nums for the 今日 Token, 今日花费 and 本会话花费 rows.
38
39
  - **The composer spend card has no seat yet** — its registration is parked (see above) until the host exposes a child slot in the composer stats row or row grouping on the dock's list spec; until then the input box shows only the built-in token/time pills. It prices what the host's table prices (DeepSeek and MiMo) while that token row counts every token, and cache writes ride its uncached-input row.
39
40
  - **Balance is up to 15s stale** — the host reuses one snapshot for 15 seconds and the browser keeps the last settled value: spending from another client does not move the shown value until the TTL expires, a manual refresh, or a browser reload (there is no polling).
package/README.zh.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡献一个条目,自己挂载 `billing` Remote,并渲染 DeepSeek 账户余额、本会话的计费花费(读宿主推送的 `billingTodaySpend` 投影,投影注册表不存在时回退 `billing/getSessionSpend`)、今日所有会话的计费花费(`billing/getTodaySpend`)、今日按会话的排行(`billing/getTodaySessionsSpend`)与各回合花费(`billing/getSessionTurnSpends`),在右上角渲染成一个标签框。该能力的宿主侧在 [`dsh-llm-billing`](../llm-billing/README.md),它拥有 `/user/balance` 传输、峰谷计价表与 `billing` Remote 命名空间;本包挂载那个 Remote 并渲染它返回的内容。
5
+ Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡献一个条目,自己挂载 `billing` Remote,并渲染 DeepSeek 账户余额、本会话的计费花费(本会话自身的花费读宿主推送的 `billingTodaySpend` 投影,投影注册表不存在时回退 `billing/getSessionSpend`;再加上它委派的子代理会话,来自 `billing/getDelegatedSpend`)、今日所有会话的计费花费(`billing/getTodaySpend`)、今日按会话的排行(`billing/getTodaySessionsSpend`,每行一个**对话**——宿主已把每个子代理会话的花费并入委派它的那个会话行里)与各回合花费(`billing/getSessionTurnSpends`),在右上角渲染成一个标签框。该能力的宿主侧在 [`dsh-llm-billing`](../llm-billing/README.md),它拥有 `/user/balance` 传输、峰谷计价表与 `billing` Remote 命名空间;本包挂载那个 Remote 并渲染它返回的内容。
6
6
 
7
- 触发器显示两行——剩余额度与本轮对话的计费花费(「本轮对话花费 ¥X」)——有上次已落定的余额就立即渲染(按插件实例保留),后台校验;首次余额尚未到达时不渲染任何东西。点击后展开一个标签框:剩余金额、今日计费 token 数与今日所有会话合计(「今日 Token」与「今日花费」同一行——token 数把每个计价行的三个计费桶相加,缓存读取与输出各算一个 token,并按 DSH 自己的紧凑记数法加 ` tok` 单位渲染:`517 tok`、`12.2K tok`、`1.2M tok`——<1e3 原整数、<1e6 用共享 `K` 单位、再往上用 `M`,缩放值 <100 保留一位小数、≥100 取整)、再下一行是本会话的计费花费(「本会话花费」,按官方峰/谷单价逐条用量样本计价——高峰窗口仅周一至周五适用,周末按低谷价——分叉会话只按分叉后的自有工作计价;金额后可能紧跟一个括号括起来的纯金额(`(¥X)`),那是**本会话今日份**的花费,取自已拉取的今日会话排行、按当前会话 id 匹配,所以既不是排行首行、也不是当日合计——**只在两个金额对不上时才渲染**,也就是会话跨了北京时间自然日的情况;排行未落定则不渲染任何括号,因为「未知」不算对不上,而没跨天的会话今日份恰好等于会话总额、括号只会重复旁边的数字)、每个模型一行的花费及其分项(用 DSH 自己的桶名与行序:「未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z」,即 ui-chat `message.turnUsage` 那套文案;宿主把缓存写入按未命中单价计价,所以它并入「未缓存输入」桶)、手动刷新按钮,以及「?」按钮上的一句简短花费说明(只估算 DeepSeek 与 MiMo 模型、按每条消息自身时刻的峰谷单价计价;下一行说明会话行括号里的金额就是本会话今日花费,最后一行顶格是纯版本号(如 `v0.3.12`)——构建期从本包 manifest 注入,便于反馈时指明所用版本;费率明细在宿主 README —— DSH 的悬停气泡只装得下短文案,过长会在视口边缘被裁掉且无法悬停阅读,所以说明放在按钮下方,纵向适配可翻转的那一侧)。剩余金额行(`API 剩余金额`)字号大于下方花费行(`今日 Token`/`今日花费`,以及带括号金额的 `本会话花费`)。模型行下方是**今日会话花费排行**(`今日会话花费`):按今日花费从高到低排列的会话列表,每行以右对齐的名次列开头,后接与花费分项相同的小圆点分隔符(「·」,字号、色调与间距一致),再是会话的持久标题(重命名自动同步),最后是金额——标题在名次列后缩进对齐;最多渲染 `SESSION_RANKING_LIMIT`(10)行,其余以「…还有 N 个会话」提示。刷新时旧值不消失,刷新失败保留上一次有效值。没有可计价消耗的会话或日子显示「暂无消耗记录」而不是编造数字。失败——未配置 API key、凭据被拒绝、传输错误——渲染弱化的「额度不可用」,其提示携带 Remote 自己的错误信息。
7
+ 触发器显示两行——剩余余额与本轮对话的计费花费——主行是面板标题文案去掉 `API` 前缀的短版(`剩余金额`,徽标较窄),副行用的就是面板的 `label.sessionSpend`(`本会话花费`),一字不差;有上次已落定的余额就立即渲染(按插件实例保留),后台校验;首次余额尚未到达时不渲染任何东西。点击后展开一个标签框:剩余金额、今日计费 token 数与今日所有会话合计(「今日 Token」与「今日花费」同一行——token 数把每个计价行的三个计费桶相加,缓存读取与输出各算一个 token,并按 DSH 自己的紧凑记数法加 ` tok` 单位渲染:`517 tok`、`12.2K tok`、`1.2M tok`——<1e3 原整数、<1e6 用共享 `K` 单位、再往上用 `M`,缩放值 <100 保留一位小数、≥100 取整)——紧跟在 token 数值后面的是当天的**缓存命中率**(裸数字,无括号、无文字说明),按 DSH 官方命中率规则渲染:整数百分比,只有部分命中会被四舍五入到 100% 时才逐位多留小数(`99`、`99.5`、`99.95`),当天没有 prompt 侧输入就完全不显示——紧跟该行下面是**今日两行桶明细**(token 在上、花费在下,两行各自成行、各自保持自然的 ` · ` 间距,两行之间不做列对齐;桶名与行序用 DSH 的,排版沿用下方模型分项那一套,行距用第三部分(今日会话花费排行)那一套紧凑节奏;**所有花费金额都用 `formatSpendSignificant` 按三位有效数字渲染**(今日花费与它的三个桶、本会话行、每个模型行及其分项、排行金额),但**不细于四位小数**——低于 ¥0.0001 的金额显示 `¥0`;只有余额行保持 `formatSpend` 的至多四位小数)、再下一行是本会话的计费花费(「本会话花费」——本会话自身的花费加上它委派的子代理会话,模型分项同样合并,按官方峰/谷单价逐条用量样本计价:高峰窗口仅周一至周五适用,周末按低谷价;分叉会话只按分叉后的自有工作计价;金额后可能紧跟一个纯金额(`¥X`),那是**本次对话今日份**的花费,取自已拉取的今日会话排行、按当前会话 id 匹配,所以既不是排行首行、也不是当日合计——它读的是该行的 `total`,与旁边那个金额数的是同一棵树(整次对话);**只在这段对话不是今天才创建时才渲染**:判定用会话自身的创建日(`billing/getDelegatedSpend` 返回的 `crossedDay`),不再比较两个金额——会话金额是实时的、排行行有 60 秒缓存,回合中途两者本来就会差几分钱,比较会导致今天新建的会话闪出这个数字;排行未落定则不渲染它(它本身才是那个条件量),今天创建的对话永远不显示,而当前会话自己就是被委派的子代理会话时也不显示——它的花费落在委派它的那个会话的排行行里,它自己的今日份是「未知」而不是 0)、每个模型一行的花费及其分项(用 DSH 自己的桶名与行序:「未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z」,即 ui-chat `message.turnUsage` 那套文案;宿主把缓存写入按未命中单价计价,所以它并入「未缓存输入」桶);**只有一个计价模型时整行模型行都不渲染**——名字在只用一种模型的会话里说明不了什么,金额又是上一行「本会话花费」的数字,于是只剩它下面那行桶明细,信息不丢(没有名字行时该明细行接手名字行那份 2px 表头间距)、手动刷新按钮,以及「?」按钮上的一句简短花费说明(只估算 DeepSeek 与 MiMo 模型、按每条消息自身时刻的峰谷单价计价;下一行说明金额含本会话委派的子代理会话;再下一行说明会话行紧跟的金额就是本次对话今日花费,最后一行顶格是纯版本号(如 `v0.3.13`)——构建期从本包 manifest 注入,便于反馈时指明所用版本;费率明细在宿主 README —— DSH 的悬停气泡只装得下短文案,过长会在视口边缘被裁掉且无法悬停阅读,所以说明放在按钮下方,纵向适配可翻转的那一侧)。剩余金额行(`API 剩余金额`)字号大于下方花费行(`今日 Token`/`今日花费`,以及带紧跟金额的 `本会话花费`)。模型行下方是**今日会话花费排行**(`今日会话花费`):按今日花费从高到低排列的**对话**列表(宿主已把每个子代理会话的花费并入委派它的那个会话行,所以一行就是一个对话,而不是一次委派),每行以右对齐的名次列开头,后接与花费分项相同的小圆点分隔符(「·」,字号、色调与间距一致),再是会话的持久标题(重命名自动同步),最后是金额——标题在名次列后缩进对齐;最多渲染 `SESSION_RANKING_LIMIT`(10)行,其余以「…还有 N 个会话」提示。刷新时旧值不消失,刷新失败保留上一次有效值。没有可计价消耗的会话或日子显示「暂无消耗记录」而不是编造数字。失败——未配置 API key、凭据被拒绝、传输错误——渲染弱化的「额度不可用」,其提示携带 Remote 自己的错误信息。
8
8
 
9
9
  本包还贡献**本轮花费条目**(`billing-turn-cost`,order 20)到 ui-chat 的 `conversation.chat.assistant-actions` list slot——与 `ui-message-feedback` 同一条操作条,多条目共存;条目自身的 CSS `order: 1` 把它排到所有 order-0 兄弟之后,金额因此落在操作行**行尾**(时钟之后)。它为每个已完成回合的收尾助手消息渲染一个纯静态 `¥X`(无图标、无文字标签、不可点击),金额来自每会话共享的 `messageId → 金额` 映射(`billing/getSessionTurnSpends`,每会话只拉一次,并发挂载的行共享同一请求;映射里没有的消息 id 只触发一次重拉,对应当前刚完成的回合)。回合没有 DeepSeek 用量(花费为 0)或加载失败时不渲染,Remote 故障不会污染操作行。
10
10
 
@@ -17,9 +17,9 @@ Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡
17
17
  花费跟随会话、额度带缓存但不轮询:
18
18
 
19
19
  - **本会话花费靠推送,不靠拉取** —— 宿主把每个会话的花费折进客户端可见的 `billingTodaySpend` 投影并推送每次变化;徽标用框架 `useProjection` 座位订阅,因此投影注册表装配时该行零 Remote 调用、实时更新。键不存在时同一行回退到 `billing/getSessionSpend` —— 与未注册的输入框花费卡片读的是同一个面、同一条回退。
20
- - **今日花费随回合结束更新(去抖)** —— 组件通过框架 `useSession` 座位订阅当前会话的「运行中」标志。每当一轮 prompt 回合结束(标志翻回空闲)就重算**今日花费**(`billing/getTodaySpend`;同一行的**今日 Token** 由这次读取的模型行原地求和,不发额外请求);重算有 2 秒去抖,回合突发(agent 连续多轮)只计价一次。**排行**(`billing/getTodaySessionsSpend`)只在详情面板打开时(以及手动刷新时)拉取,徽标一直关着就不会为全量会话扫描买单;「本会话花费」行括号里的今日份金额也来自这次排行读取,所以它不额外发请求,代价是与排行同进同退(排行未落定时不渲染括号——两个金额对不上才显示,而「未知」不算对不上)。
20
+ - **今日花费随回合结束更新(去抖)** —— 组件通过框架 `useSession` 座位订阅当前会话的「运行中」标志。每当一轮 prompt 回合结束(标志翻回空闲)就重算**今日花费**(`billing/getTodaySpend`;同一行的**今日 Token** 由这次读取的模型行原地求和,不发额外请求);重算有 2 秒去抖,回合突发(agent 连续多轮)只计价一次。同一次回合结束也会刷新本会话的子代理小计(`billing/getDelegatedSpend`)——这正是让会话金额在子代理跑完后仍是「整次对话」口径的来源。**排行**(`billing/getTodaySessionsSpend`)只在详情面板打开时(以及手动刷新时)拉取,徽标一直关着就不会为全量会话扫描买单;「本会话花费」行紧跟的今日份金额也来自这次排行读取(读该行的 `total`),所以它不额外发请求,代价是与排行同进同退(排行未落定时不渲染它——两个金额对不上才显示,而「未知」不算对不上)。
21
21
  - **额度带 TTL 缓存** —— 账户余额在挂载、切换会话、点刷新按钮时拉取。宿主 15 秒内复用同一份快照(手动刷新带 `force` 绕过),单次请求 5 秒超时;浏览器保留最后一次已落定值,切会话时立即渲染。仍然**没有轮询、不会自动重拉**。
22
- - **刷新保留旧值** —— 刷新在途时旧值留在屏幕上,刷新失败保留上一次有效值,不清空。刷新按钮调用 `billing/getBalance(true)`、`billing/getTodaySpend(true)` 与 `billing/getTodaySessionsSpend(true)`,绕过宿主侧缓存;回合结束触发的重算与挂载/切换会话的读取走缓存路径。
22
+ - **刷新保留旧值** —— 刷新在途时旧值留在屏幕上,刷新失败保留上一次有效值,不清空。刷新按钮调用 `billing/getBalance(true)`、`billing/getTodaySpend(true)`、`billing/getDelegatedSpend(sessionId, true)` 与 `billing/getTodaySessionsSpend(true)`,绕过宿主侧缓存;回合结束触发的重算与挂载/切换会话的读取走缓存路径。
23
23
 
24
24
  ## 模型体验
25
25
 
@@ -32,8 +32,10 @@ Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡
32
32
  ## 已知限制与暂缓事项
33
33
 
34
34
  - **只取首条余额线** —— 余额读取主要(`balance_infos[0]`)币种行;其它币种行不显示。
35
- - **排行上限 10** —— 面板最多显示 `SESSION_RANKING_LIMIT` 个会话,其余以「…还有 N 个会话」提示。
35
+ - **排行上限 10** —— 面板最多显示 `SESSION_RANKING_LIMIT` 个对话,其余以「…还有 N 个会话」提示。上限在宿主的子代理合并之后生效,所以一行就是一个对话,而不是一次委派。
36
+ - **子代理小计随回合更新,不逐事件跟进** —— 会话金额里本会话自身那部分是实时的(推送投影随回合更新),子代理那部分来自 `billing/getDelegatedSpend`:挂载、手动刷新与回合结束时各拉一次,并与今日花费共用同一个 60 秒宿主缓存。因此子代理在回合中途烧掉的钱会在下一次读取时落到父会话金额上,而不是逐事件实时跳动。
36
37
  - **本轮花费需要已定稿的收尾消息** —— 中断的回合没有操作行,不显示本轮花费。
37
- - **今日 Token 只数计价行** —— 它统计的是 `billing/getTodaySpend` 返回的那些模型行(即价目表里有费率行的模型)的三个计费桶之和;没有费率行的模型不计 token,缓存写入并入「未缓存输入」桶(与分项同一口径)。
38
+ - **今日 Token 只数计价行** —— 它统计的是 `billing/getTodaySpend` 返回的那些模型行(即价目表里有费率行的模型)的三个计费桶之和;没有费率行的模型不计 token,缓存写入并入「未缓存输入」桶(与分项同一口径)。该行紧跟的命中率只按 **prompt 侧**两个桶算(缓存读取 ÷(缓存读取 + 未缓存输入)),输出不进分母,与 DSH `billedInputTokens` 的口径一致。
39
+ - **面板排版只有三级** —— 三级字体定义在面板样式表的 `.panel` 上(每个成员只引用自己那一级的 token,改一处即整级生效):**一级** 11px/16px tertiary tabular-nums,用于桶明细行(`未缓存输入 ¥X · 缓存读取 ¥Y · 输出 ¥Z`)、今日会话排行整行(名次、分隔点、标题与金额)、「…还有 N 个会话」以及两个紧跟数字(本会话今日份金额、当天缓存命中率);**二级** 12px/18px secondary,用于模型名与其行金额、`今日会话花费` 标题——**同一级里文字与数字的色调允许不同**:二级成员的金额用该级自己的数字色调 `--billing-type-2-number-tone`(比文字暗一档),今后二级成员的金额一律照此办理;**三级** 13px/18px primary tabular-nums,用于 `今日 Token`、`今日花费`、`本会话花费` 三行(含各自的紧跟数字)。
38
40
  - **输入框花费卡片暂无席位** —— 注册已暂停(见上),等宿主在输入框统计行里开放子插槽、或在 dock list 规格上加同行分组;在那之前输入框下方只有官方自带的 token/时间 pill。它只计价宿主费率表覆盖的模型(DeepSeek 与 MiMo),而上面那条 token 行统计的是会话全部 token,缓存写入并入它的「未缓存输入」行。
39
41
  - **额度最多旧 15 秒** —— 宿主 15 秒内复用同一份快照、浏览器保留最后一次已落定值:其他客户端产生的消耗不会让界面数值自动变化,需等 TTL 过期、手动刷新或刷新浏览器(仍无轮询)。