@gamegeek-saikel/dsh-cost-meter 0.4.0 → 0.5.0

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.md CHANGED
@@ -29,7 +29,7 @@ Then start the harness:
29
29
  npx @deepseek-ai/dsh web
30
30
  ```
31
31
 
32
- If you have the DSH CLI installed globally, you can also use `dsh` instead of `npx @deepseek-ai/dsh`. To install into another profile, replace `web` with your profile name. The plugin declares and is verified against DSH `^0.1.7-rc.2`. The host half requires Node `^22.19.0 || >=24.0.0` and pnpm `11.7.0` for development.
32
+ If you have the DSH CLI installed globally, you can also use `dsh` instead of `npx @deepseek-ai/dsh`. To install into another profile, replace `web` with your profile name. The plugin declares and is verified against DSH `^0.2.0-rc.2`. The host half requires Node `^22.19.0 || >=24.0.0` and pnpm `11.7.0` for development.
33
33
 
34
34
  ## Overview
35
35
 
@@ -63,7 +63,7 @@ Once installed, the plugin contributes five browser surfaces (all text shown in
63
63
  | Cost view tab | `conversation.view` | Whole-conversation totals (main + every subagent nesting level), category totals, the per-subagent list with its depth, and the per-reply anchored ledger |
64
64
  | Per-reply cost chip | `conversation.chat.assistant-actions` | The anchored cost of one finalized reply as the row's own stat capsule — the same trigger + anchored dialog the shipped 用量 and 用时 capsules use (28px pill, hover fill, `aria-expanded` dialog above the trigger). It closes the row's stat run: after 用量 and 用时, before the timestamp, spaced by the row's own gap, and coloured by the band that priced it (green off-peak, red peak). The dialog lists the three billed categories, the band with its multiplier, the model, and the snapshot version; the slot hands over the message id, which the `sessionCostIndex` projection resolves to ledger coordinates (dash `—` when unpriced) |
65
65
  | Header pill | `conversation.session.header.utilities` | Anchored total, or a live `≈ ¥x.xx (estimate)` while streaming; click for the detail panel |
66
- | Plugin configuration page | `plugins.row.config` | The cost-meter row's page under 设置 → 插件 (DSH 0.1.7 hosts a plugin's own configuration there, keyed `<package>#<row id>`): per-model overrides, OpenRouter aliases, cache-read discount, FX mode, toggles, and manual refresh. The editable fields are the schema's `volatile()` ones — the deployment fields (endpoint, credential reference, refresh cadence, trusted hosts, history cap) stay hand-edited in the profile patch, which is exactly the split DSH's configuration projection can write |
66
+ | Plugin configuration page | `plugins.row.config` | The cost-meter row's page under 设置 → 插件 (DSH 0.2.0 hosts a plugin's own configuration there, keyed `<package>#<row id>`): per-model overrides, OpenRouter aliases, cache-read discount, FX mode, toggles, and manual refresh. The editable fields are the schema's `volatile()` ones — the deployment fields (endpoint, credential reference, refresh cadence, trusted hosts, history cap) stay hand-edited in the profile patch, which is exactly the split DSH's configuration projection can write |
67
67
 
68
68
  The `/cost-meter` host route serves the balance snapshot, the pricebook view, and the subagent totals over GET, and applies manual refresh over POST (`{"action":"refresh"}`). Like the `/api` fence, the route only answers requests whose `Host` header names a loopback address or a declared trusted host — the DNS-rebinding-safe check.
69
69
 
package/README.zh-CN.md CHANGED
@@ -29,7 +29,7 @@ npx @deepseek-ai/dsh plugin --profile web add @gamegeek-saikel/dsh-cost-meter
29
29
  npx @deepseek-ai/dsh web
30
30
  ```
31
31
 
32
- 如果已全局安装 DSH CLI,也可以使用 `dsh` 代替 `npx @deepseek-ai/dsh`。安装到其他 profile 时,把 `web` 替换成你的 profile 名称即可。插件声明并已验证兼容 DSH `^0.1.7-rc.2`。开发环境要求 Node `^22.19.0 || >=24.0.0` 与 pnpm `11.7.0`。
32
+ 如果已全局安装 DSH CLI,也可以使用 `dsh` 代替 `npx @deepseek-ai/dsh`。安装到其他 profile 时,把 `web` 替换成你的 profile 名称即可。插件声明并已验证兼容 DSH `^0.2.0-rc.2`。开发环境要求 Node `^22.19.0 || >=24.0.0` 与 pnpm `11.7.0`。
33
33
 
34
34
  ## 概述
35
35
 
@@ -63,7 +63,7 @@ DeepSeek 的价格随时间变化(官方价目表、USD→CNY 汇率、2026-08
63
63
  | 花费标签页 | `conversation.view` | 全对话总花费(主会话 + 任意层级的子代理)、分类小计、带层级的子代理列表与逐回复锚定账本 |
64
64
  | 每条回复成本小标签 | `conversation.chat.assistant-actions` | 单条已定稿回复的锚定成本,做成与官方「用量」「用时」完全同形的统计胶囊(28px 药丸、悬停底色、`aria-expanded` 展开锚定在触发件上方的对话框);位置收在统计串末:在「用量」「用时」之后、末尾时刻文本之前,间距沿用行自身的 gap;按计价档位着色(闲时绿、高峰红);对话框列出三类计费、档位与倍率、模型与快照版本;插槽只给出消息 id,由 `sessionCostIndex` 投影解析为账本坐标(无价格时显示 `—`) |
65
65
  | 头部胶囊 | `conversation.session.header.utilities` | 锚定总花费;流式中显示 `预计 ¥x.xx(估算)`;点击展开详情面板 |
66
- | 插件配置页 | `plugins.row.config` | 设置 → 插件 中 cost-meter 行自己的配置页(DSH 0.1.7 以 `<包名>#<行 id>` 为键托管插件自带配置):按模型覆盖价、OpenRouter 别名、缓存折扣、汇率模式、开关与立即刷新。可编辑字段即 schema 中标记 `volatile()` 的字段——部署类字段(端点、凭据引用、刷新周期、可信主机、历史上限)仍在 profile patch 中手工维护,这正是 DSH 配置投影能够写入的边界 |
66
+ | 插件配置页 | `plugins.row.config` | 设置 → 插件 中 cost-meter 行自己的配置页(DSH 0.2.0 以 `<包名>#<行 id>` 为键托管插件自带配置):按模型覆盖价、OpenRouter 别名、缓存折扣、汇率模式、开关与立即刷新。可编辑字段即 schema 中标记 `volatile()` 的字段——部署类字段(端点、凭据引用、刷新周期、可信主机、历史上限)仍在 profile patch 中手工维护,这正是 DSH 配置投影能够写入的边界 |
67
67
 
68
68
  `/cost-meter` 宿主路由通过 GET 提供余额快照、价格簿视图与子代理合计;通过 POST(`{"action":"refresh"}`)执行手动刷新。与 `/api` 围栏一致,路由只应答 `Host` 头为回环地址或已声明可信主机的请求——这是防 DNS 重绑定的安全校验。
69
69
 
package/lib/client.js CHANGED
@@ -1379,7 +1379,7 @@ window.__ModuleLoader__.load({
1379
1379
  //#endregion
1380
1380
  //#region src/client/CostPluginCard.tsx
1381
1381
  /**
1382
- * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 renders a
1382
+ * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 renders a
1383
1383
  * row's own configuration through the Plugins page's `plugins.row.config`
1384
1384
  * slot, which dispatches the contribution twice: `view: 'summary'` supplies
1385
1385
  * the row's one-liner, and `view: 'page'` is the body of the page the row's
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["PANEL_GAP","PANEL_MARGIN","MEASURE_STYLE","memo","useState","useRef","useAnchoredPosition","css","useMemo","IconGaugeOutlineRegular","createPortal","Fragment","memo","useState","css","Tag","memo","useState","useRef","useAnchoredPosition","css","IconGaugeOutlineRegular","createPortal","memo","useState","useRef","useMemo","css","Tag","memo","useState","css","Button"],"sources":["../src/client/locales.ts","../src/pricing.ts","../src/client/cost-math.ts","../src/client/format.ts","../src/client/SessionCostLine.tsx","../src/client/CostView.tsx","../src/client/AssistantCostChip.tsx","../src/client/SessionCostPill.tsx","../src/client/CostPluginCard.tsx","../src/client/index.ts"],"sourcesContent":["/** `cost-meter` namespace dictionaries. */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'cost-meter'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n // View tab\n 'view.cost': '花费',\n 'view.empty': '暂无花费数据',\n 'view.total': '总花费',\n 'view.uncached': '输入(缓存未命中)',\n 'view.cacheRead': '输入(缓存命中)',\n 'view.output': '输出',\n 'view.cacheWriteTokens': '缓存写入',\n 'view.unpriced': '{count} 步无价格',\n 'view.steps': '每轮回复花费',\n\n // Step list\n 'step.turn': '第 {turn} 轮',\n 'step.model': '模型 {model}',\n 'step.time': '{time}',\n 'step.tokens': '未命中 {uncached} · 缓存读 {read} · 缓存写 {write} · 输出 {output}',\n 'step.cost': '花费 {amount}',\n 'step.snapshot': '快照 · {time}',\n 'step.unpriced': '—({reason})',\n 'reason.NO_MODEL': '无模型信息',\n 'reason.NO_PRICE': '无价格',\n\n // Shared amounts\n 'cost.label': '本会话 {amount}',\n 'line.uncached': '非缓存输入 {amount}',\n 'line.cacheRead': '缓存输入 {amount}',\n 'line.output': '输出 {amount}',\n 'line.subagent': '子代理 {amount}',\n 'subagent.depth': '第 {depth} 层',\n 'pill.label': '花费 {amount}',\n 'pill.estimate': '预计 {amount}',\n 'pill.estimateNote': '估算',\n 'pill.detail.total': '总花费 {amount}',\n 'pill.detail.snapshot': '价格快照({time})',\n 'pill.detail.model': '模型 {model}',\n 'pill.detail.source': '价格来源 {source}',\n 'source.official': '官方页面',\n 'source.fallback': '内置回退',\n 'source.manual': '手动',\n 'source.openrouter': 'OpenRouter',\n 'source.none': '无',\n 'band.peak': '高峰价',\n 'band.offPeak': '闲时价',\n 'band.single': '单价',\n 'price.peakExtra': '高峰 · 比闲时多 {multiplier} 倍',\n 'price.offPeakSaving': '闲时 · 比高峰省 {percent}',\n 'price.peakRatio': '高峰 {multiplier}',\n 'price.offPeakRatio': '闲时 {multiplier}',\n\n // Chip\n 'chip.title': '本回复花费 {amount}',\n 'chip.detail.title': '本回复花费',\n 'chip.detail.band': '计价档位',\n 'chip.detail.model': '模型',\n 'chip.detail.snapshot': '价格快照',\n 'chip.unpriced': '—',\n\n // Balance (inherited posture)\n 'balance.label': '余额 {amount}',\n 'balance.failed': '余额不可用',\n 'balance.detail': '余额 {amount}(赠送 {granted} · 充值 {toppedUp})',\n 'balance.suspended': '账户已暂停付费请求',\n 'refreshedAt': '更新于 {time}',\n\n // Dock capsule detail (the shipped stat-dialog shape: `<dt>` label, `<dd>` value)\n 'dock.detail.title': '本会话花费',\n 'dock.row.session': '本会话',\n 'dock.row.subagent': '子代理(含下级)',\n 'dock.row.own': '本会话自身',\n 'dock.row.uncached': '输入(缓存未命中)',\n 'dock.row.cacheRead': '输入(缓存命中)',\n 'dock.row.output': '输出',\n 'dock.row.balance': '账户余额',\n 'dock.row.model': '模型',\n 'dock.row.snapshot': '价格快照',\n 'dock.row.source': '价格来源',\n 'dock.row.updated': '更新时间',\n 'dock.row.unpriced': '无价格步数',\n\n // Plugin configuration page\n 'settings.title': '花费计价',\n 'settings.summary': '快照锚定的会话花费与价格表',\n 'settings.current': '当前快照 · {source} · {time}',\n 'settings.noSnapshot': '尚无价格快照',\n 'settings.refresh': '立即刷新',\n 'settings.refreshing': '刷新中…',\n 'settings.overrides': '按模型价目表(手动覆盖,元/百万 tokens)',\n 'settings.aliases': 'OpenRouter 别名映射',\n 'settings.discount': '缓存折扣系数(OpenRouter)',\n 'settings.fx': '汇率(USD→CNY)',\n 'settings.fx.auto': '自动',\n 'settings.fx.manual': '手动',\n 'settings.balance': '显示余额',\n 'settings.openrouter': '允许 OpenRouter 回退',\n 'settings.save': '保存',\n 'settings.discard': '丢弃',\n 'settings.saved': '已保存',\n 'settings.rejected': '主机拒绝了这次修改',\n 'settings.unavailable': '此部署未向本页提供可编辑的配置',\n 'settings.overridden': '已自定义',\n 'settings.error': '操作失败:{error}',\n 'settings.jsonHint': 'JSON 对象,键为模型,值含 single/offPeak/peak 价格桶',\n} as const\n\n/** English dictionary, key-identical to the Chinese source of truth. */\nexport const en: Record<ConversationCostKey, string> = {\n 'view.cost': 'Cost',\n 'view.empty': 'No cost data yet',\n 'view.total': 'Total',\n 'view.uncached': 'Input (cache miss)',\n 'view.cacheRead': 'Input (cache hit)',\n 'view.output': 'Output',\n 'view.cacheWriteTokens': 'Cache write',\n 'view.unpriced': '{count} steps unpriced',\n 'view.steps': 'Per-reply cost',\n\n 'step.turn': 'Turn {turn}',\n 'step.model': 'Model {model}',\n 'step.time': '{time}',\n 'step.tokens': 'miss {uncached} · read {read} · write {write} · out {output}',\n 'step.cost': 'Cost {amount}',\n 'step.snapshot': 'snapshot · {time}',\n 'step.unpriced': '—({reason})',\n 'reason.NO_MODEL': 'no model info',\n 'reason.NO_PRICE': 'no price',\n\n 'cost.label': 'Session {amount}',\n 'line.uncached': 'Uncached input {amount}',\n 'line.cacheRead': 'Cached input {amount}',\n 'line.output': 'Output {amount}',\n 'line.subagent': 'Subagents {amount}',\n 'subagent.depth': 'level {depth}',\n 'pill.label': 'Cost {amount}',\n 'pill.estimate': '≈ {amount}',\n 'pill.estimateNote': 'estimate',\n 'pill.detail.total': 'Total {amount}',\n 'pill.detail.snapshot': 'Price snapshot ({time})',\n 'pill.detail.model': 'Model {model}',\n 'pill.detail.source': 'Price source {source}',\n 'source.official': 'official page',\n 'source.fallback': 'built-in fallback',\n 'source.manual': 'manual',\n 'source.openrouter': 'OpenRouter',\n 'source.none': 'none',\n 'band.peak': 'peak',\n 'band.offPeak': 'off-peak',\n 'band.single': 'list',\n 'price.peakExtra': 'peak · {multiplier}× more than off-peak',\n 'price.offPeakSaving': 'off-peak · save {percent} vs peak',\n 'price.peakRatio': 'peak {multiplier}',\n 'price.offPeakRatio': 'off-peak {multiplier}',\n\n 'chip.title': 'This reply cost {amount}',\n 'chip.detail.title': 'Cost of this reply',\n 'chip.detail.band': 'Price band',\n 'chip.detail.model': 'Model',\n 'chip.detail.snapshot': 'Price snapshot',\n 'chip.unpriced': '—',\n\n 'balance.label': 'Balance {amount}',\n 'balance.failed': 'Balance unavailable',\n 'balance.detail': 'Balance {amount} (granted {granted} · topped up {toppedUp})',\n 'balance.suspended': 'Account suspended for paid requests',\n 'refreshedAt': 'Updated at {time}',\n\n 'dock.detail.title': 'Session cost',\n 'dock.row.session': 'This session',\n 'dock.row.subagent': 'Subagents (all levels)',\n 'dock.row.own': 'This session only',\n 'dock.row.uncached': 'Input (cache miss)',\n 'dock.row.cacheRead': 'Input (cache hit)',\n 'dock.row.output': 'Output',\n 'dock.row.balance': 'Account balance',\n 'dock.row.model': 'Model',\n 'dock.row.snapshot': 'Price snapshot',\n 'dock.row.source': 'Price source',\n 'dock.row.updated': 'Updated',\n 'dock.row.unpriced': 'Unpriced steps',\n\n 'settings.title': 'Cost meter',\n 'settings.summary': 'Snapshot-anchored session cost and price table',\n 'settings.current': 'Current snapshot · {source} · {time}',\n 'settings.noSnapshot': 'No price snapshot yet',\n 'settings.refresh': 'Refresh now',\n 'settings.refreshing': 'Refreshing…',\n 'settings.overrides': 'Per-model prices (manual overrides, CNY per 1M tokens)',\n 'settings.aliases': 'OpenRouter alias map',\n 'settings.discount': 'Cache-read discount (OpenRouter)',\n 'settings.fx': 'Exchange rate (USD→CNY)',\n 'settings.fx.auto': 'Auto',\n 'settings.fx.manual': 'Manual',\n 'settings.balance': 'Show balance',\n 'settings.openrouter': 'Allow OpenRouter fallback',\n 'settings.save': 'Save',\n 'settings.discard': 'Discard',\n 'settings.saved': 'Saved',\n 'settings.rejected': 'The Host refused this edit',\n 'settings.unavailable': 'This deployment serves no editable configuration to this page',\n 'settings.overridden': 'overridden',\n 'settings.error': 'Operation failed: {error}',\n 'settings.jsonHint': 'JSON object keyed by model; values carry single/offPeak/peak price buckets',\n}\n\n/** Key domain of the `cost-meter` namespace (zh is the source of truth). */\nexport type ConversationCostKey = keyof typeof zh\n","/**\n * Official DeepSeek pricing: fetches the pricing page\n * (api-docs.deepseek.com/zh-cn/quick_start/pricing/) and parses the price\n * table, the peak/off-peak bands, and the peak-hour schedule, so price\n * changes, model renames, and schedule changes never require a plugin update.\n *\n * The page's shape as of 2026-09-10: one combined table whose bucket rows\n * carry the OFF-PEAK cells then the PEAK cells of the `deepseek-flash` and\n * `deepseek-v4-pro` columns, plus a footnote stating that peak hours are\n * Beijing time 09:00-12:00 / 14:00-18:00 Monday through Friday (01:00-04:00 /\n * 06:00-10:00 UTC), everything else — including all of Saturday and Sunday —\n * off-peak. The retired ids `deepseek-v4-flash` and\n * `deepseek-v4-flash-vision-exp` no longer have a column; the page states\n * they are served by V4.1-Flash and billed at the Flash price, which is what\n * the `flash` pricing key resolves them to.\n *\n * The parser is deliberately tolerant: it matches price cells next to the\n * bucket labels and model names anywhere in the HTML, so reordering or\n * rewording still yields values when the numbers are present; failures\n * degrade to the built-in presets rather than throwing.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/pricing\n */\n\nimport type { CurrentPricing, PeakHourRange, PeakModelPricing, PeakPricing, PeakSchedule, PriceBucket, PricingSnapshot } from './types.ts'\n\n/** Official pricing page URL (zh-cn). */\nexport const PRICING_URL = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/'\n\n/** Official pricing page URL (en). */\nexport const PRICING_URL_EN = 'https://api-docs.deepseek.com/quick_start/pricing/'\n\n/** Official peak-pricing rollout: 2026-08-17 00:00 Beijing time (UTC+8). */\nexport const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0)\n\n/**\n * Built-in fallback zh peak schedule: the official page's windows in Beijing\n * time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything else,\n * including all of Saturday and Sunday, off-peak).\n */\nexport const PEAK_SCHEDULE_ZH: PeakSchedule = {\n timezone: 'Asia/Shanghai',\n ranges: [[9, 12], [14, 18]],\n weekdaysOnly: true,\n}\n\n/**\n * Built-in fallback en peak schedule. Kept as a separate constant from the zh\n * schedule because the English pricing page states the same windows in UTC;\n * each pricebook parses its own page and falls back to this locale-matched\n * default only when the page carries no schedule.\n */\nexport const PEAK_SCHEDULE_EN: PeakSchedule = {\n timezone: 'UTC',\n ranges: [[1, 4], [6, 10]],\n weekdaysOnly: true,\n}\n\n/**\n * Built-in fallback prices: the pre-rollout official list for the Flash and\n * Pro models (CNY per 1M tokens), kept as the historical `single` tier that\n * prices usage from before the 2026-08-17 peak rollout. The Flash column is\n * served by `deepseek-flash`; the retired ids `deepseek-v4-flash` and\n * `deepseek-v4-flash-vision-exp` are billed at the same Flash price.\n * DeepSeek bills cache writes at the uncached input price (its wire usage\n * reports only cache-hit vs cache-miss buckets), so no separate cache-write\n * rate exists.\n */\nexport const FALLBACK_CURRENT: CurrentPricing = {\n flash: { cacheReadPerMillion: 0.02, inputPerMillion: 1, outputPerMillion: 2 },\n pro: { cacheReadPerMillion: 0.025, inputPerMillion: 3, outputPerMillion: 6 },\n}\n\n/**\n * Built-in fallback for the peak table: the 2026-09-10 official list (CNY per\n * 1M tokens), i.e. the `deepseek-flash` and `deepseek-v4-pro` columns. The\n * peak half is twice the off-peak half, as the page's footnote states. These\n * values back every snapshot taken while the page is unreachable, so they must\n * track the live page — a stale pair silently reprices usage (the Flash column\n * used to be 0.05 / 1.5 / 4.5 off-peak before the V4.1-Flash price cut).\n */\nexport const FALLBACK_PEAK: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.02, inputPerMillion: 1, outputPerMillion: 4 },\n peak: { cacheReadPerMillion: 0.04, inputPerMillion: 2, outputPerMillion: 8 },\n },\n pro: {\n offPeak: { cacheReadPerMillion: 0.15, inputPerMillion: 4.5, outputPerMillion: 13.5 },\n peak: { cacheReadPerMillion: 0.3, inputPerMillion: 9, outputPerMillion: 27 },\n },\n}\n\n/** Built-in fallback USD prices from the English pricing page. */\nexport const FALLBACK_CURRENT_USD: CurrentPricing = {\n flash: { cacheReadPerMillion: 0.007, inputPerMillion: 0.22, outputPerMillion: 0.66 },\n pro: { cacheReadPerMillion: 0.022, inputPerMillion: 0.66, outputPerMillion: 1.98 },\n}\n\n/**\n * Built-in fallback USD peak table from the English pricing page\n * (2026-09-10 list: Flash 0.003 / 0.15 / 0.6 off-peak, Pro 0.022 / 0.66 /\n * 1.98 off-peak, the peak half doubling each).\n */\nexport const FALLBACK_PEAK_USD: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.003, inputPerMillion: 0.15, outputPerMillion: 0.6 },\n peak: { cacheReadPerMillion: 0.006, inputPerMillion: 0.3, outputPerMillion: 1.2 },\n },\n pro: {\n offPeak: { ...FALLBACK_CURRENT_USD.pro },\n peak: { cacheReadPerMillion: 0.044, inputPerMillion: 1.32, outputPerMillion: 3.96 },\n },\n}\n\n/** Strip HTML tags to plain text (keeps cell order). */\nexport function stripHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<[^>]+>/g, ' ')\n .replace(/&nbsp;/gi, ' ')\n .replace(/&amp;/gi, '&')\n .replace(/&lt;/gi, '<')\n .replace(/&gt;/gi, '>')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** Every CNY price in a cell run, in document order. */\nfunction priceValues(text: string): number[] {\n const values: number[] = []\n for (const match of text.matchAll(/(\\d+(?:\\.\\d+)?)\\s*元/g)) {\n const value = Number(match[1])\n if (Number.isFinite(value)) values.push(value)\n }\n return values\n}\n\n/** Every USD price in a cell run, in document order. */\nfunction priceValuesUsd(text: string): number[] {\n const values: number[] = []\n for (const match of text.matchAll(/\\$?\\s*(\\d+(?:\\.\\d+)?)/g)) {\n const value = Number(match[1])\n if (Number.isFinite(value)) values.push(value)\n }\n return values\n}\n\n/** The result of parsing one locale's pricing table(s). */\ninterface LocalePricing {\n /** The current page's list prices: legacy single-price cells, or the off-peak cells of the combined table. */\n current: CurrentPricing\n /** Peak/off-peak table when the page carried one. */\n peak?: PeakPricing\n /** The separate pre-rollout single-price table, when the page still carried it. */\n legacyCurrent?: CurrentPricing\n}\n\n/** One bucket row's parsed cells. */\ninterface BucketCells {\n cacheRead: number[]\n input: number[]\n output: number[]\n}\n\n/** Build the `column`-th model bucket from three bucket rows. */\nfunction bucketAt(cells: BucketCells, column: number): PriceBucket {\n return {\n cacheReadPerMillion: cells.cacheRead[column]!,\n inputPerMillion: cells.input[column]!,\n outputPerMillion: cells.output[column]!,\n }\n}\n\n/** Build the current list (flash, pro, and vision when present). */\nfunction currentAt(cells: BucketCells, columns: number): CurrentPricing {\n return {\n flash: bucketAt(cells, 0),\n pro: bucketAt(cells, 1),\n ...(columns > 2 ? { vision: bucketAt(cells, 2) } : {}),\n }\n}\n\n/** Build the peak/off-peak pair for the `column`-th model. */\nfunction peakAt(offPeakCells: BucketCells, peakCells: BucketCells, column: number): PeakModelPricing {\n return {\n offPeak: bucketAt(offPeakCells, column),\n peak: bucketAt(peakCells, column),\n }\n}\n\n/** Build the peak table (flash, pro, and vision when present). */\nfunction peakPricingAt(offPeakCells: BucketCells, peakCells: BucketCells, columns: number): PeakPricing {\n return {\n flash: peakAt(offPeakCells, peakCells, 0),\n pro: peakAt(offPeakCells, peakCells, 1),\n ...(columns > 2 ? { vision: peakAt(offPeakCells, peakCells, 2) } : {}),\n }\n}\n\n/** Three bucket-row runs of the Chinese pricing table, when present. */\nconst ZH_PRICING_SEGMENTS_RE = /百万tokens输入\\s*[((]\\s*缓存命中\\s*[))]\\s*([\\s\\S]*?)百万tokens输入\\s*[((]\\s*缓存未命中\\s*[))]\\s*([\\s\\S]*?)百万tokens输出\\s*([\\s\\S]*?)(?:并发限制|扣费规则|$)/i\n\n/**\n * Parse the Chinese pricing table(s). The 2026-08-21 page carries one\n * combined table — bucket rows whose cells are OFF-PEAK × 3 models then\n * PEAK × 3 models — while the earlier page carried a separate legacy\n * single-price table plus a model-row peak table.\n * @param html - the raw Chinese pricing page.\n * @returns the parsed table(s), or undefined when the bucket labels are absent.\n */\nfunction parseZhPricing(html: string): LocalePricing | undefined {\n const text = stripHtml(html)\n const hit = ZH_PRICING_SEGMENTS_RE.exec(text)\n if (hit === null) return undefined\n const rows = [hit[1] ?? '', hit[2] ?? '', hit[3] ?? '']\n\n const hasBands = rows.every(row => /空闲时段/.test(row) && /高峰时段/.test(row))\n if (hasBands) {\n const offPeakRows = rows.map(row => {\n const values = priceValues(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(0, values.length / 2)\n })\n const peakRows = rows.map(row => {\n const values = priceValues(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(values.length / 2)\n })\n if (offPeakRows.some(row => row === undefined) || peakRows.some(row => row === undefined)) return undefined\n const columns = offPeakRows[0]!.length\n if (offPeakRows.some(row => row!.length !== columns)) return undefined\n const offPeakCells: BucketCells = {\n cacheRead: offPeakRows[0]!,\n input: offPeakRows[1]!,\n output: offPeakRows[2]!,\n }\n const peakCells: BucketCells = {\n cacheRead: peakRows[0]!,\n input: peakRows[1]!,\n output: peakRows[2]!,\n }\n return {\n current: currentAt(offPeakCells, columns),\n peak: peakPricingAt(offPeakCells, peakCells, columns),\n }\n }\n\n const currentRows = rows.map(priceValues)\n const columns = currentRows[0]?.length\n if (columns !== 2 && columns !== 3) return undefined\n if (currentRows.some(row => row.length !== columns)) return undefined\n const cells: BucketCells = {\n cacheRead: currentRows[0]!,\n input: currentRows[1]!,\n output: currentRows[2]!,\n }\n const current = currentAt(cells, columns)\n return { current, legacyCurrent: current }\n}\n\n/**\n * Parse the legacy Chinese peak-pricing table: model rows with off-peak and\n * peak cells, e.g. `deepseek-v4-flash 空闲时段 0.05 1.5 4.5 高峰时段 0.10 3.0 9.0`.\n * @param text - the stripped pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nfunction parseLegacyPeakTableZh(text: string): PeakPricing | undefined {\n const flash = /deepseek-v4-flash\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n const pro = /deepseek-v4-pro\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n if (flash === null || pro === null) return undefined\n const model = (row: RegExpExecArray): PeakModelPricing => ({\n offPeak: {\n cacheReadPerMillion: Number(row[1]),\n inputPerMillion: Number(row[2]),\n outputPerMillion: Number(row[3]),\n },\n peak: {\n cacheReadPerMillion: Number(row[4]),\n inputPerMillion: Number(row[5]),\n outputPerMillion: Number(row[6]),\n },\n })\n const vision = /deepseek-v4-flash-vision-exp\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n return {\n flash: model(flash),\n pro: model(pro),\n ...(vision === null ? {} : { vision: model(vision) }),\n }\n}\n\n/**\n * Parse the Chinese current single-price table: three rows labeled with the\n * bucket names, each carrying the model price cells (2 columns before\n * 2026-08-21, 3 columns afterwards — on the combined page these are the\n * off-peak cells).\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\nexport function parseCurrentTable(html: string): CurrentPricing | undefined {\n return parseZhPricing(html)?.current\n}\n\n/**\n * Parse the Chinese peak-pricing table: either the PEAK cells of the\n * 2026-08-21 combined table, or the earlier separate model-row table.\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when neither peak table is present.\n */\nexport function parsePeakTable(html: string): PeakPricing | undefined {\n const combined = parseZhPricing(html)\n return combined?.peak ?? parseLegacyPeakTableZh(stripHtml(html))\n}\n\n/** Three bucket-row runs of the English pricing table, when present. */\nconst EN_PRICING_SEGMENTS_RE = /1M\\s+INPUT\\s+TOKENS\\s*[((]?\\s*CACHE\\s+HIT\\s*[))]?\\s*([\\s\\S]*?)1M\\s+INPUT\\s+TOKENS\\s*[((]?\\s*CACHE\\s+MISS\\s*[))]?\\s*([\\s\\S]*?)1M\\s+OUTPUT\\s+TOKENS\\s*([\\s\\S]*?)(?:Concurrency\\s+Limit|Deduction\\s+Rules|$)/i\n\n/**\n * Parse the English pricing table(s). The 2026-08-21 page lists all three\n * buckets as rows with OFF-PEAK and PEAK cells (2 model columns before the\n * update, 3 afterwards); earlier pages carried a plain single-price table.\n */\nfunction parseEnglishPricing(html: string): LocalePricing | undefined {\n const text = stripHtml(html)\n const hit = EN_PRICING_SEGMENTS_RE.exec(text)\n if (hit === null) return undefined\n const rows = [hit[1] ?? '', hit[2] ?? '', hit[3] ?? '']\n\n const hasBands = rows.every(row =>\n /\\bOFF-PEAK\\b/i.test(row) && /(?:^|\\s)PEAK(?:\\s|$)/i.test(row))\n if (hasBands) {\n const offPeakRows = rows.map(row => {\n const values = priceValuesUsd(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(0, values.length / 2)\n })\n const peakRows = rows.map(row => {\n const values = priceValuesUsd(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(values.length / 2)\n })\n if (offPeakRows.some(row => row === undefined) || peakRows.some(row => row === undefined)) return undefined\n const columns = offPeakRows[0]!.length\n if (offPeakRows.some(row => row!.length !== columns)) return undefined\n const offPeakCells: BucketCells = {\n cacheRead: offPeakRows[0]!,\n input: offPeakRows[1]!,\n output: offPeakRows[2]!,\n }\n const peakCells: BucketCells = {\n cacheRead: peakRows[0]!,\n input: peakRows[1]!,\n output: peakRows[2]!,\n }\n return {\n current: currentAt(offPeakCells, columns),\n peak: peakPricingAt(offPeakCells, peakCells, columns),\n }\n }\n\n const currentRows = rows.map(priceValuesUsd)\n const columns = currentRows[0]?.length\n if (columns !== 2 && columns !== 3) return undefined\n if (currentRows.some(row => row.length !== columns)) return undefined\n const cells: BucketCells = {\n cacheRead: currentRows[0]!,\n input: currentRows[1]!,\n output: currentRows[2]!,\n }\n const current = currentAt(cells, columns)\n return { current, legacyCurrent: current }\n}\n\n/**\n * Parse the English current single-price table (or the off-peak cells of the\n * combined table).\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\nexport function parseCurrentTableEn(html: string): CurrentPricing | undefined {\n return parseEnglishPricing(html)?.current\n}\n\n/**\n * Parse the English peak-pricing table.\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nexport function parsePeakTableEn(html: string): PeakPricing | undefined {\n return parseEnglishPricing(html)?.peak\n}\n\n/** Two `HH:MM-HH:MM` windows (the second one after a separator). */\nconst SCHEDULE_WINDOWS_RE = /(\\d{1,2}):(\\d{2})\\s*[-–—~至]\\s*(\\d{1,2}):(\\d{2})\\s*(?:[、,,;]\\s*)?(?:and\\s*)?(\\d{1,2}):(\\d{2})\\s*[-–—~至]\\s*(\\d{1,2}):(\\d{2})/i\n\n/**\n * Parse the peak-hour schedule from a pricing page: the two half-open\n * windows next to the localized peak label (`高峰时段` / `peak hours`), plus\n * the timezone the windows are expressed in and the Monday–Friday\n * restriction when the page states one. The Chinese page states Beijing\n * time; the English page states UTC — a Beijing/UTC+8 mention wins, a bare\n * UTC mention selects UTC, anything else defaults to Beijing time.\n * @param html - the raw pricing page.\n * @param locale - which page's wording to look for.\n * @returns the parsed schedule, or undefined when the page carries none.\n */\nexport function parsePeakSchedule(html: string, locale: 'zh' | 'en' = 'zh'): PeakSchedule | undefined {\n const text = stripHtml(html)\n // The pricing table itself repeats `高峰时段` as a band label, so the zh\n // search targets the schedule sentence (the label followed by a time).\n const labelMatch = locale === 'zh'\n ? /高峰时段[^。]{0,160}?\\d{1,2}:\\d{2}/i.exec(text)\n : /peak\\s*hours?/i.exec(text)\n if (labelMatch === null) return undefined\n const window = text.slice(labelMatch.index, labelMatch.index + 220)\n const match = SCHEDULE_WINDOWS_RE.exec(window)\n if (match === null) return undefined\n const ranges: PeakHourRange[] = [\n [Number(match[1]), Number(match[3])],\n [Number(match[5]), Number(match[7])],\n ]\n if (ranges.some(([start, end]) =>\n !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > 24 || start >= end)) {\n return undefined\n }\n const weekdaysOnly = /(?:周一|星期[一二三四五]|工作日)/.test(window)\n || /\\b(?:Monday\\s+through\\s+Friday|weekdays?)\\b/i.test(window)\n if (/\\b(?:beijing|北京时间|china standard)\\b/i.test(window) || /\\butc\\s*\\+\\s*8\\b/i.test(window)) {\n return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n }\n if (/\\butc\\b/i.test(window)) {\n return { timezone: 'UTC', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n }\n return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n}\n\n/**\n * Fetch and parse the official pricing page.\n * @param fetchImpl - fetch-compatible function (injected for testability).\n * @param timeoutMs - abort timeout.\n * @returns the parsed snapshot; `error` is set when fetch/parse failed.\n */\nexport async function fetchPricing(\n fetchImpl: typeof fetch = globalThis.fetch,\n timeoutMs = 15_000,\n locale: 'zh' | 'en' = 'zh',\n): Promise<PricingSnapshot> {\n const fetchedAt = Date.now()\n const currency = locale === 'en' ? 'USD' : 'CNY'\n const url = locale === 'en' ? PRICING_URL_EN : PRICING_URL\n const fallbackCurrent = locale === 'en' ? FALLBACK_CURRENT_USD : FALLBACK_CURRENT\n const fallbackPeak = locale === 'en' ? FALLBACK_PEAK_USD : FALLBACK_PEAK\n const fallbackSchedule = locale === 'en' ? PEAK_SCHEDULE_EN : PEAK_SCHEDULE_ZH\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n let response: Response\n try {\n response = await fetchImpl(url, { signal: controller.signal })\n } finally {\n clearTimeout(timer)\n }\n if (!response.ok) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: `pricing page HTTP ${response.status}` }\n }\n const html = await response.text()\n const parsed = locale === 'en' ? parseEnglishPricing(html) : parseZhPricing(html)\n if (parsed === undefined) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: 'pricing table not found' }\n }\n const peak = parsed.peak ?? (locale === 'en' ? parsePeakTableEn(html) : parsePeakTable(html))\n const { current, legacyCurrent } = parsed\n const schedule = parsePeakSchedule(html, locale) ?? fallbackSchedule\n const now = Date.now()\n const peakActive = now >= PEAK_PRICING_START_MS\n return {\n fetchedAt,\n currency,\n current,\n ...(legacyCurrent === undefined ? {} : { legacyCurrent }),\n ...(peak === undefined ? {} : { peak }),\n peakActive,\n effective: effectivePricing(current, peak, peakActive, new Date(now), schedule),\n ...(peakActive ? { band: isPeakHour(new Date(now), schedule) ? 'peak' : 'offPeak' } : {}),\n schedule,\n }\n } catch (error) {\n return {\n fetchedAt,\n currency,\n current: fallbackCurrent,\n peakActive: false,\n effective: fallbackCurrent,\n schedule: fallbackSchedule,\n error: error instanceof Error ? error.message : String(error)\n }\n }\n}\n\n/**\n * Whether the given instant is a peak-pricing hour under one schedule: the\n * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook\n * passes the schedule parsed from its own (zh or en) pricing page. When the\n * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's\n * timezone are off-peak all day.\n * @param now - the instant to classify.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns true during peak hours.\n */\nexport function isPeakHour(now: Date = new Date(), schedule: PeakSchedule = PEAK_SCHEDULE_ZH): boolean {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: schedule.timezone,\n hour: 'numeric',\n hour12: false,\n weekday: 'short',\n }).formatToParts(now)\n if (schedule.weekdaysOnly === true) {\n const weekday = parts.find(part => part.type === 'weekday')?.value\n if (weekday === 'Sat' || weekday === 'Sun') return false\n }\n const hour = Number(parts.find(part => part.type === 'hour')?.value)\n if (Number.isNaN(hour)) return false\n return schedule.ranges.some(([start, end]) => hour >= start && hour < end)\n}\n\n/**\n * The price bucket in effect for one model at the given moment: the\n * peak/off-peak band once the rollout is live (with the parsed table, or the\n * built-in fallback), otherwise the current official list.\n * @param current - the current list prices.\n * @param peak - the upcoming peak table, when parsed.\n * @param peakActive - whether the rollout has started.\n * @param now - the instant to price for.\n * @param model - `flash` or `pro`.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns the bucket to apply.\n */\nexport function effectiveBucket(\n current: CurrentPricing,\n peak: PeakPricing | undefined,\n peakActive: boolean,\n now: Date,\n model: 'flash' | 'pro',\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): PriceBucket {\n if (peakActive) {\n const table = peak?.[model] ?? FALLBACK_PEAK[model]\n return isPeakHour(now, schedule) ? table.peak : table.offPeak\n }\n return current[model]\n}\n\n/** Per-model effective prices at the given moment. */\nexport function effectivePricing(\n current: CurrentPricing,\n peak: PeakPricing | undefined,\n peakActive: boolean,\n now: Date,\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): CurrentPricing {\n return {\n flash: effectiveBucket(current, peak, peakActive, now, 'flash', schedule),\n pro: effectiveBucket(current, peak, peakActive, now, 'pro', schedule),\n }\n}","/**\n * Client-side price lookup and streaming-estimate helpers.\n *\n * The authoritative anchored ledger arrives through the `sessionCost`\n * projection; this module only mirrors the host pricebook math for display\n * and for the live estimate (which by nature uses CURRENT prices and is\n * labeled 估算/estimate). The lookup logic intentionally mirrors\n * `src/pricebook.ts` — keep the two in sync.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/client/cost-math\n */\n\nimport { isPeakHour, PEAK_PRICING_START_MS, PEAK_SCHEDULE_ZH } from '../pricing.ts'\nimport type {\n ModelPrice,\n PeakSchedule,\n PriceBand,\n PriceBucket,\n PricebookSnapshot,\n SessionCostStep,\n SessionCostTotals,\n SubagentCost,\n} from '../types.ts'\n\n/** Sum totals (this module's mirror of the host `sumTotals`). */\nexport function sumTotals(totals: readonly SessionCostTotals[]): SessionCostTotals {\n const sum: SessionCostTotals = {\n uncachedCost: 0,\n cacheReadCost: 0,\n outputCost: 0,\n cost: 0,\n pricedSteps: 0,\n unpricedSteps: 0,\n steps: 0,\n }\n for (const entry of totals) {\n sum.uncachedCost += entry.uncachedCost\n sum.cacheReadCost += entry.cacheReadCost\n sum.outputCost += entry.outputCost\n sum.cost += entry.cost\n sum.pricedSteps += entry.pricedSteps\n sum.unpricedSteps += entry.unpricedSteps\n sum.steps += entry.steps\n }\n return sum\n}\n\n/** Total spend of every subagent (CNY); 0 without subagents. */\nexport function subagentSpend(subagents: readonly SubagentCost[] | undefined): number {\n if (subagents === undefined || subagents.length === 0) return 0\n return sumTotals(subagents.map(entry => entry.totals)).cost\n}\n\n/**\n * Select the conversation's main ledger, the host-aggregated subagent rows,\n * and their combined totals together, so no surface has to remember which\n * pointer to compare against which total.\n *\n * All three are derived consistently: `combined` sums the main ledger with\n * EVERY subagent row the host served — including nested ones — and is defined\n * whenever either side has data. A conversation whose own ledger is not\n * materialized yet (a session switch mid-flight, or a host that serves no main\n * ledger) therefore still totals its subagents instead of rendering nothing.\n *\n * `main` is `null` when the main ledger is absent, in which case `mainSpend`\n * is unknown (the dock shows 0 for a session with no spend of its own).\n * @param main - the main session's projection totals, or undefined.\n * @param subagents - the host-aggregated subagent rows.\n * @returns the main readout, the subagent rows, combined totals, and both spend figures.\n */\nexport function costLookupFor(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): {\n main: SessionCostTotals | undefined\n subagents: readonly SubagentCost[]\n combined: SessionCostTotals | undefined\n mainSpend: number\n subagentTotal: number\n} {\n const rows = subagents ?? []\n const combined = combineTotals(main, rows)\n return {\n main,\n subagents: rows,\n combined,\n mainSpend: main?.cost ?? 0,\n subagentTotal: sumTotals(rows.map(entry => entry.totals)).cost,\n }\n}\n\n/**\n * The conversation-wide totals: the main session's ledger plus every\n * subagent's ledger (host-aggregated, all nesting levels). Undefined only when\n * neither side carries a ledger.\n * @param main - the main session's projection totals.\n * @param subagents - the host-aggregated subagent totals.\n * @returns the combined totals, or undefined without any ledger.\n */\nexport function combineTotals(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): SessionCostTotals | undefined {\n const rows = subagents ?? []\n if (main === undefined) {\n return rows.length === 0 ? undefined : sumTotals(rows.map(entry => entry.totals))\n }\n if (rows.length === 0) return main\n return sumTotals([main, ...rows.map(entry => entry.totals)])\n}\n\n/**\n * The price band in effect at one instant (mirror of the host helper). The\n * schedule defaults to the zh fallback; live readouts pass the schedule of\n * the pricebook they display (the zh and en pages may state different\n * windows).\n * @param time - epoch millis.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns the band.\n */\nexport function bandForTime(time: number, schedule: PeakSchedule = PEAK_SCHEDULE_ZH): PriceBand {\n if (time < PEAK_PRICING_START_MS) return 'single'\n return isPeakHour(new Date(time), schedule) ? 'peak' : 'offPeak'\n}\n\n/** The bucket of one model's entry for one band (mirror of the host helper). */\nexport function bucketOf(modelPrice: ModelPrice, band: PriceBand): PriceBucket | undefined {\n if (band === 'single') return modelPrice.single ?? modelPrice.offPeak ?? modelPrice.peak\n if (band === 'peak') return modelPrice.peak ?? modelPrice.offPeak ?? modelPrice.single\n return modelPrice.offPeak ?? modelPrice.peak ?? modelPrice.single\n}\n\n/** Map a model id to its official pricing key (mirror of the host helper). */\nexport function pricingKeyOfModel(model: string): 'pro' | 'flash' {\n const lower = model.toLowerCase()\n return lower.includes('pro') ? 'pro' : 'flash'\n}\n\n/** Candidate canonical price keys for one model (mirror of the host helper). */\nexport function modelKeys(provider: string | undefined, model: string): string[] {\n const keys: string[] = []\n if (provider !== undefined && provider.length > 0) keys.push(`${provider}/${model}`)\n keys.push(model)\n const lower = model.toLowerCase()\n const deepseekish = lower.includes('deepseek')\n || (provider !== undefined && provider.toLowerCase().includes('deepseek'))\n if (deepseekish) keys.push(pricingKeyOfModel(model))\n return keys\n}\n\n/** Pick the snapshot effective at `time` (newest ≤ time, else earliest). */\nexport function snapshotAt(snapshots: readonly PricebookSnapshot[], time: number): PricebookSnapshot | undefined {\n let best: PricebookSnapshot | undefined\n for (const snapshot of snapshots) {\n // `>=` keeps the NEWER snapshot (later in the array) on an effectiveAt tie.\n if (snapshot.effectiveAt <= time && (best === undefined || snapshot.effectiveAt >= best.effectiveAt)) best = snapshot\n }\n return best ?? (snapshots.length > 0 ? snapshots[0] : undefined)\n}\n\n/** Resolve one model's price entry in a snapshot through the key chain. */\nexport function modelPriceOf(\n snapshot: PricebookSnapshot | null | undefined,\n provider: string | undefined,\n model: string,\n): ModelPrice | undefined {\n if (snapshot === undefined || snapshot === null) return undefined\n for (const key of modelKeys(provider, model)) {\n const entry = snapshot.prices[key]\n if (entry !== undefined) return entry\n }\n return undefined\n}\n\n/** Resolve one model's bucket at one instant from a snapshot. */\nexport function bucketAt(\n snapshot: PricebookSnapshot | undefined,\n provider: string | undefined,\n model: string,\n time: number,\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): PriceBucket | undefined {\n const entry = modelPriceOf(snapshot, provider, model)\n if (entry === undefined) return undefined\n return bucketOf(entry, bandForTime(time, schedule))\n}\n\n/**\n * The peak-to-off-peak price multiplier for one model's entry, when the\n * entry carries both bands. The multiplier is the ratio of the summed peak\n * bucket rates to the summed off-peak bucket rates; official DeepSeek prices\n * scale all buckets by the same factor, so this is a stable “peak is N× the\n * off-peak price” display value.\n * @param snapshot - the pricebook snapshot to read.\n * @param provider - the provider route, when known.\n * @param model - the model id.\n * @returns the peak/off-peak multiplier, or null when either band is absent.\n */\nexport function peakOffPeakMultiplier(\n snapshot: PricebookSnapshot | null | undefined,\n provider: string | undefined,\n model: string,\n): number | null {\n const entry = modelPriceOf(snapshot, provider, model)\n if (entry === undefined) return null\n const peak = entry.peak\n const offPeak = entry.offPeak\n if (peak === undefined || offPeak === undefined) return null\n const offPeakTotal = offPeak.cacheReadPerMillion + offPeak.inputPerMillion + offPeak.outputPerMillion\n if (offPeakTotal === 0) return null\n const peakTotal = peak.cacheReadPerMillion + peak.inputPerMillion + peak.outputPerMillion\n return peakTotal / offPeakTotal\n}\n\n/** CJK characters (the /1.5 density class). */\nconst CJK_RE = /[\\u3400-\\u4dbf\\u4e00-\\u9fff]/g\n\n/**\n * Heuristic character→token estimate: CJK ÷ 1.5, everything else ÷ 4\n * (the streaming estimate's output side).\n * @param text - the accumulated text.\n * @returns the estimated token count.\n */\nexport function estimateTokens(text: string): number {\n const cjk = (text.match(CJK_RE) ?? []).length\n return Math.ceil(cjk / 1.5 + (text.length - cjk) / 4)\n}\n\n/**\n * Tokens of the streamed text/reasoning blocks of a partial assistant.\n * @param blocks - the partial's content blocks (text/reasoning carry `text`).\n * @returns the estimated token count.\n */\nexport function blocksOutputTokens(blocks: readonly { kind?: string; text?: string }[] | undefined): number {\n let tokens = 0\n for (const block of blocks ?? []) {\n if ((block.kind === 'text' || block.kind === 'reasoning') && typeof block.text === 'string' && block.text.length > 0) {\n tokens += estimateTokens(block.text)\n }\n }\n return tokens\n}\n\n/**\n * The cache-read ratio of the newest completed step (input side of the\n * estimate). Absent history conservatively prices all input as uncached\n * (upper bound).\n * @param steps - the projection's step ledger.\n * @returns the ratio in [0, 1].\n */\nexport function cacheReadRatioOf(steps: readonly SessionCostStep[]): number {\n for (let i = steps.length - 1; i >= 0; i -= 1) {\n const step = steps[i]\n const total = step.uncachedInputTokens + step.cacheReadTokens + step.cacheWriteTokens\n if (step.priced && total > 0) return step.cacheReadTokens / total\n }\n return 0\n}\n\n/**\n * The live estimate of the running reply: input at the projected context\n * size (weighted by the cache ratio) plus output at the streamed-token\n * heuristic, both at CURRENT prices. Always labeled 估算/estimate — it is\n * replaced by the projection's exact anchored value once the step settles.\n * @param bucket - the current price bucket of the session model.\n * @param inputTokens - the projected prompt size for the next request.\n * @param cachedRatio - the cache-read ratio from recent completed steps.\n * @param outputTokens - the streamed output tokens so far.\n * @returns the estimated CNY cost.\n */\nexport function estimateCost(\n bucket: PriceBucket,\n inputTokens: number,\n cachedRatio: number,\n outputTokens: number,\n): number {\n const inputCost = inputTokens / 1_000_000\n * (bucket.cacheReadPerMillion * cachedRatio + bucket.inputPerMillion * (1 - cachedRatio))\n const outputCost = outputTokens / 1_000_000 * bucket.outputPerMillion\n return inputCost + outputCost\n}\n","/**\n * Shared display formatting for amounts and timestamps.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/client/format\n */\n\n/**\n * Common currency symbols; unknown currencies render as a bare code.\n * @param currency - ISO 4217 code.\n * @returns the display symbol.\n */\nexport function currencySymbol(currency: string): string {\n switch (currency) {\n case 'CNY':\n return '¥'\n case 'USD':\n return '$'\n case 'EUR':\n return '€'\n default:\n return `${currency} `\n }\n}\n\n/** Detect the display currency from the active locale dictionary. */\nexport function displayCurrency(t: (key: string, params?: Record<string, string>) => string): 'CNY' | 'USD' {\n return t('view.cost') === 'Cost' ? 'USD' : 'CNY'\n}\n\n/**\n * Compact money: two decimals above 1, four down to 0.01, six below.\n * Costs are CNY 元 with 4-decimal display precision; zero renders as 0.00.\n * @param value - the amount.\n * @returns the fixed-width display string.\n */\nexport function formatMoney(value: number): string {\n if (value === 0) return '0.00'\n if (value >= 1) return value.toFixed(2)\n if (value >= 0.01) return value.toFixed(4)\n return value.toFixed(6)\n}\n\n/** Time formatting for snapshot stamps and step rows. */\nexport function formatTime(epochMs: number): string {\n return new Date(epochMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })\n}\n\n/**\n * Compact price multiplier, e.g. `2.0×` or `0.5×`.\n * @param multiplier - the ratio to display.\n * @returns the fixed one-decimal multiplier string.\n */\nexport function formatMultiplier(multiplier: number): string {\n return `${multiplier.toFixed(1)}×`\n}\n/**\n * Compact percentage, e.g. `50%`.\n * @param fraction - the ratio in [0, 1].\n * @returns the whole-percent string.\n */\nexport function formatPercent(fraction: number): string {\n return `${Math.round(fraction * 100)}%`\n}\n","/**\n * Composer-dock readout: the current session's anchored spend (the\n * `sessionCost` projection — price-snapshot-anchored, never recomputed at\n * current prices) plus the DeepSeek account balance (host `/cost-meter` route),\n * refreshed every minute.\n *\n * Presentation copies the shipped dock readout (`StatsPills` in ui-chat): a\n * single capsule holding an icon plus a label whose parts are joined by the\n * shipped `·` separator. Clicking it opens the product's stat-dialog panel\n * shape — title with the figure, rule, then a `<dl>` of labelled rows — which\n * carries the billed categories, the subagent rows, the anchored price snapshot\n * and the balance detail that the capsule has no room for.\n *\n * The component is deliberately defensive: every derived string is computed\n * inside a guarded block so a single bad field can never take down the whole\n * dock entry (the slot system retires crashed entries).\n */\nimport { Fragment, memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconGaugeOutlineRegular,\n useAnchoredPosition,\n useDismissOnOutsidePointer,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { BalanceSnapshot, ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { costLookupFor } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatTime } from './format.ts'\nimport css from './SessionCostLine.module.css'\n\n/** Host route serving the balance snapshot and the pricebook view. */\nexport const ENDPOINT = '/cost-meter'\n/** Balance refresh interval in milliseconds. */\nexport const REFRESH_MS = 60_000\n/** Distance between the capsule and its detail panel, in pixels. */\nconst PANEL_GAP = 8\n/** Minimum distance the detail panel keeps from the viewport edge, in pixels. */\nconst PANEL_MARGIN = 12\n/** Pre-measurement placement: mounted but invisible, so the first frame can size it. */\nconst MEASURE_STYLE = { visibility: 'hidden', left: 0, top: 0 } as const\n\n/** Session-cost namespace key domain for the locale seat. */\nexport type CostLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostLineProps {\n useProjection: UseProjection\n sessionId: string\n t: CostLocale\n}\n\n/** Compose the tooltip's balance detail from an ok snapshot. */\nfunction balanceTooltip(balance: Extract<BalanceSnapshot, { ok: true }>, t: CostLocale): string {\n if (balance.balance === null) return t('balance.failed')\n const symbol = currencySymbol(balance.balance.currency)\n const amount = `${symbol}${formatMoney(balance.balance.total)}`\n const granted = `${symbol}${formatMoney(balance.balance.granted)}`\n const toppedUp = `${symbol}${formatMoney(balance.balance.toppedUp)}`\n const detail = t('balance.detail', { amount, granted, toppedUp })\n return balance.isAvailable ? detail : `${detail} · ${t('balance.suspended')}`\n}\n\n/** The tooltip's cost detail rows from the anchored projection. Never throws. */\nexport function costDetail(cost: SessionCostProjection | undefined, model: string | undefined, t: CostLocale, currency: 'CNY' | 'USD' = 'CNY'): string[] {\n const rows: string[] = []\n try {\n if (cost === undefined) return rows\n const symbol = currencySymbol(currency)\n const totals = cost.totals\n if (totals !== undefined && totals.pricedSteps > 0) {\n rows.push(t('cost.label', { amount: `${symbol}${formatMoney(totals.cost)}` }))\n rows.push(t('view.uncached') + ` ${symbol}${formatMoney(totals.uncachedCost)}`)\n rows.push(t('view.cacheRead') + ` ${symbol}${formatMoney(totals.cacheReadCost)}`)\n rows.push(t('view.output') + ` ${symbol}${formatMoney(totals.outputCost)}`)\n }\n if (model !== undefined) rows.push(t('step.model', { model }))\n const steps = cost.steps\n if (steps !== undefined && steps.length > 0) {\n const last = steps[steps.length - 1]\n if (last !== undefined && last.snapshotVersion !== null) {\n rows.push(t('pill.detail.snapshot', { time: formatTime(last.snapshotEffectiveAt ?? last.time) }))\n }\n }\n if (totals !== undefined && totals.unpricedSteps > 0) {\n rows.push(t('view.unpriced', { count: String(totals.unpricedSteps) }))\n }\n } catch {\n /* a malformed field must never take down the dock entry */\n }\n return rows\n}\n\nexport const SessionCostLine = memo(function SessionCostLine({ useProjection, sessionId, t }: SessionCostLineProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const [failed, setFailed] = useState(false)\n const [open, setOpen] = useState(false)\n const rootRef = useRef<HTMLSpanElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n const pos = useAnchoredPosition({ open, anchorRef: rootRef, panelRef, side: 'top', gap: PANEL_GAP, margin: PANEL_MARGIN })\n useDismissOnOutsidePointer(rootRef, open, setOpen, panelRef)\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [open])\n useEffect(() => {\n let alive = true\n let timer: number | undefined\n const endpoint = `${ENDPOINT}?session=${encodeURIComponent(sessionId)}&currency=${currency}`\n const load = (): void => {\n void fetch(endpoint, { cache: 'no-store' })\n .then((res) => {\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n return res.json() as Promise<ConversationCostResponse>\n })\n .then((data) => {\n if (!alive) return\n setResponse(data)\n setFailed(false)\n })\n .catch(() => {\n if (alive) setFailed(true)\n })\n }\n load()\n timer = window.setInterval(load, REFRESH_MS)\n return () => {\n alive = false\n if (timer !== undefined) window.clearInterval(timer)\n }\n }, [sessionId, currency])\n\n const balance = response?.balance\n const balanceEnabled = response?.pricebook?.balanceEnabled !== false\n // The dock line totals the WHOLE conversation: the main ledger plus every\n // subagent row the host served, nested levels included.\n const lookup = costLookupFor(cost?.totals, response?.subagents)\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n // The dock readout must never vanish on a session switch: switching\n // sessions re-binds the projection hook, and until the new session's\n // baseline has carried the sessionCost key useProjection returns\n // undefined (capability absent - never a usable signal). Treat a\n // missing ledger as a zero ledger so the row renders CNY 0.00\n // instead of disappearing while the baseline is in flight.\n const mainSpend = lookup.mainSpend\n\n // One capsule, as terse as the shipped stats capsules: the WHOLE\n // conversation's anchored spend — the root ledger plus every subagent row the\n // host served, at every nesting level — and the account balance, joined by the\n // shipped separator. The panel below decomposes that same figure.\n const symbol = currencySymbol(currency)\n const money = (value: number): string => `${symbol}${formatMoney(value)}`\n const totalSpend = combined?.cost ?? mainSpend + subSpend\n let label: ReactNode = null\n let balanceValue: string | null = null\n try {\n const okBalance = balance?.ok === true ? balance : null\n const balanceText = !balanceEnabled\n ? null\n : okBalance !== null && okBalance.balance !== null\n ? t('balance.label', { amount: `${currencySymbol(okBalance.balance.currency)}${formatMoney(okBalance.balance.total)}` })\n : failed ? t('balance.failed') : null\n if (balanceText !== null) balanceValue = balanceText\n label = (\n <>\n {t('cost.label', { amount: money(totalSpend) })}\n {balanceText !== null && (\n <>\n <span className={css.sep} aria-hidden>·</span>\n {balanceText}\n </>\n )}\n </>\n )\n } catch {\n /* keep the capsule alive */\n }\n if (label === null) return null\n\n const model = cost?.model?.model\n const subagents = lookup.subagents\n const pricebook = response?.pricebook\n const snapshot = pricebook?.current ?? null\n const lastStep = cost?.steps !== undefined && cost.steps.length > 0 ? cost.steps[cost.steps.length - 1] : undefined\n // The panel is the shipped stat-dialog shape: `<dt>` label, `<dd>` value. The\n // headline restates the capsule; delegated spend is then split out as a\n // breakdown of that same total (never an addition to it), followed by the\n // billed categories, the per-subagent rows, and the price provenance.\n const detailRows = useMemo(() => {\n const rows: { key: string; label: string; value: string }[] = []\n const row = (key: string, rowLabel: string, value: string | null | undefined): void => {\n if (value === null || value === undefined || value === '') return\n rows.push({ key, label: rowLabel, value })\n }\n try {\n row('session', t('dock.row.session'), money(totalSpend))\n if (subSpend > 0) {\n row('subagent', t('dock.row.subagent'), money(subSpend))\n row('own', t('dock.row.own'), money(totalSpend - subSpend))\n }\n if (combined !== undefined && combined.pricedSteps > 0) {\n row('uncached', t('dock.row.uncached'), money(combined.uncachedCost))\n row('cacheRead', t('dock.row.cacheRead'), money(combined.cacheReadCost))\n row('output', t('dock.row.output'), money(combined.outputCost))\n }\n // One row per subagent, depth-tagged: the same tree the Cost tab lists.\n for (const subagent of subagents) {\n const name = subagent.depth > 1\n ? `${subagent.sessionId.slice(0, 8)} (${t('subagent.depth', { depth: String(subagent.depth) })})`\n : subagent.sessionId.slice(0, 8)\n row(`agent:${subagent.sessionId}`, name, money(subagent.totals?.cost ?? 0))\n }\n row('balance', t('dock.row.balance'), balanceValue)\n row('model', t('dock.row.model'), model ?? null)\n row('snapshot', t('dock.row.snapshot'), lastStep !== undefined && lastStep.snapshotVersion !== null\n ? `${formatTime(lastStep.snapshotEffectiveAt ?? lastStep.time)}${lastStep.band === null ? '' : ` · ${t(`band.${lastStep.band}` as 'band.single')}`}`\n : null)\n if (snapshot !== null && (snapshot.source === 'official' || snapshot.source === 'fallback' || snapshot.source === 'manual' || snapshot.source === 'openrouter')) {\n row('source', t('dock.row.source'), t(`source.${snapshot.source}` as 'source.manual'))\n }\n if (combined !== undefined && combined.unpricedSteps > 0) row('unpriced', t('dock.row.unpriced'), String(combined.unpricedSteps))\n row('updated', t('dock.row.updated'), response === null ? null : formatTime(balance?.ok === true ? balance.fetchedAt : Date.now()))\n } catch {\n /* keep the panel alive */\n }\n return rows\n }, [combined, totalSpend, subSpend, subagents, balanceValue, balance, model, lastStep, snapshot, response, money, t])\n\n return (\n <span className={css.anchor} ref={rootRef} data-testid=\"session-cost-line\" data-composer-cost>\n <button\n type=\"button\"\n className={css.pill}\n data-cost-pill=\"total\"\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-label={t('dock.detail.title')}\n onClick={() => setOpen(prev => !prev)}\n >\n <IconGaugeOutlineRegular />\n <span className={css.label}>{label}</span>\n </button>\n {open && createPortal(\n <div\n ref={panelRef}\n className={css.panel}\n role=\"dialog\"\n aria-label={t('dock.detail.title')}\n data-testid=\"session-cost-detail\"\n style={pos ?? MEASURE_STYLE}\n >\n <div className={css.title}>\n <span className={css.titleLabel}><IconGaugeOutlineRegular />{t('dock.detail.title')}</span>\n <span className={css.titleValue}>{money(totalSpend)}</span>\n </div>\n <div className={css.titleRule} aria-hidden />\n <dl className={css.details} data-session-cost-details>\n {detailRows.map(entry => (\n <Fragment key={entry.key}>\n <dt>{entry.label}</dt>\n <dd>{entry.value}</dd>\n </Fragment>\n ))}\n </dl>\n </div>,\n document.body,\n )}\n </span>\n )\n})\n","/**\n * Cost view tab: the whole conversation's anchored cost ledger — the total\n * (main session + subagent sessions, host-aggregated), the three billed\n * categories (uncached input incl. cache writes, cache reads, output), a\n * per-subagent breakdown, and the per-reply list (turn, time, model, token\n * split, step cost, anchored snapshot version and effective time). Reads the\n * `sessionCost` projection plus the host-aggregated subagent totals; nothing\n * is recomputed at current prices. Each row's band rides the shipped `Tag`\n * capsule (11px/17px, `success` off-peak / `danger` peak), the same tag\n * geometry every other tag control in the product uses.\n */\nimport { memo, useEffect, useState } from 'react'\nimport { Tag } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, costLookupFor, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport css from './CostView.module.css'\n\nexport type CostViewLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface CostViewProps {\n useProjection: UseProjection\n sessionId: string\n t: CostViewLocale\n}\n\n/** Map an unpriced reason to its localized copy. */\nfunction reasonText(step: SessionCostStep, t: CostViewLocale): string {\n return t(`reason.${step.unpricedReason ?? 'NO_PRICE'}` as 'reason.NO_PRICE')\n}\n\n/**\n * Indent a subagent row by its root-relative depth, so the nested delegation\n * chain reads as a chain. Depth `-1` (`0`) marks a row the runtime fallback\n * discovered, which states no distance: it keeps the top-level inset.\n */\nfunction subagentIndent(depth: number): { paddingLeft: string } {\n return { paddingLeft: `${Math.max(0, depth - 1) * 14}px` }\n}\n\nexport const CostView = memo(function CostView({ useProjection, sessionId, t }: CostViewProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const symbol = currencySymbol(currency)\n\n useEffect(() => {\n let alive = true\n void fetch(`/cost-meter?session=${encodeURIComponent(sessionId)}&currency=${currency}`, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the tab keeps the last good aggregation */ })\n return () => {\n alive = false\n }\n }, [sessionId, currency])\n\n const rows = cost?.steps ?? []\n const lookup = costLookupFor(cost?.totals, response?.subagents)\n const subagents = lookup.subagents\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n const money = (value: number | null): string => value === null ? '—' : `${symbol}${formatMoney(value)}`\n\n // A conversation whose OWN ledger is not materialized yet but whose\n // subagents spent money is not \"no cost data\": the subagent section below\n // still has something true to say.\n if (combined === undefined) {\n return <div className={css.root} data-testid=\"cost-view-empty\">{t('view.empty')}</div>\n }\n const totals = combined\n\n return (\n <div className={css.root} data-testid=\"cost-view\">\n <section className={css.summary}>\n <div className={css.totalRow}>\n <span className={css.totalLabel}>{t('view.total')}</span>\n <span className={css.totalValue}>{money(totals.cost)}</span>\n </div>\n <div className={css.categories}>\n <span>{t('view.uncached')}: {money(totals.uncachedCost)}</span>\n <span>{t('view.cacheRead')}: {money(totals.cacheReadCost)}</span>\n <span>{t('view.output')}: {money(totals.outputCost)}</span>\n </div>\n {totals.unpricedSteps > 0 && (\n <div className={css.unpricedNote}>{t('view.unpriced', { count: String(totals.unpricedSteps) })}</div>\n )}\n {subagents.length > 0 && (\n <section className={css.subagents} data-testid=\"cost-view-subagents\">\n <div className={css.unpricedNote}>{t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` })}</div>\n <ol className={css.stepList}>\n {subagents.map(sub => (\n <li key={sub.sessionId} className={css.step} data-testid=\"cost-subagent\" data-depth={String(sub.depth)}>\n <div className={css.stepHead}>\n <span className={css.stepTurn} style={subagentIndent(sub.depth)}>{sub.sessionId.slice(0, 8)}</span>\n {sub.depth > 1 && (\n <span className={css.stepModel}>{t('subagent.depth', { depth: String(sub.depth) })}</span>\n )}\n {sub.label !== undefined && <span className={css.stepModel}>{sub.label}</span>}\n <span className={css.stepCost}>{money(sub.totals?.cost ?? null)}</span>\n </div>\n <div className={css.stepBody}>\n <span>{t('view.uncached')}: {money(sub.totals?.uncachedCost ?? null)}</span>\n <span>{t('view.cacheRead')}: {money(sub.totals?.cacheReadCost ?? null)}</span>\n <span>{t('view.output')}: {money(sub.totals?.outputCost ?? null)}</span>\n </div>\n </li>\n ))}\n </ol>\n </section>\n )}\n </section>\n\n <section className={css.steps}>\n <h3 className={css.stepsTitle}>{t('view.steps')}</h3>\n <ol className={css.stepList}>\n {rows.map((step) => {\n // The band is anchored to the ROUND's own time: the ledger's\n // `band` was fixed at fold time from the usage event's time (the\n // host schedule of that moment), never recomputed against the\n // clock now — so an old reply keeps the color it was billed at.\n const stepBand = step.band ?? bandForTime(step.time)\n const stepRatio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, step.provider, step.model)\n const stepBandLabel = stepBand === 'peak'\n ? stepRatio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(stepRatio) })\n : stepBand === 'offPeak'\n ? stepRatio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / stepRatio) })\n : null\n return (\n <li key={`${step.turn}:${step.step}`} className={css.step} data-testid=\"cost-step\" data-band={stepBand === 'single' ? undefined : stepBand}>\n <div className={css.stepHead}>\n <span className={css.stepTurn}>{t('step.turn', { turn: String(step.turn) })}</span>\n <span className={css.stepModel}>{t('step.model', { model: step.model })}</span>\n <span className={css.stepTime}>{formatTime(step.time)}</span>\n <span className={css.stepCost}>\n {money(step.cost)}\n {stepBandLabel !== null && <Tag tone={stepBand === 'peak' ? 'danger' : 'success'}>{stepBandLabel}</Tag>}\n </span>\n </div>\n <div className={css.stepBody}>\n <span className={css.stepTokens}>\n {t('step.tokens', {\n uncached: String(step.uncachedInputTokens),\n read: String(step.cacheReadTokens),\n write: String(step.cacheWriteTokens),\n output: String(step.outputTokens),\n })}\n </span>\n <span className={css.stepMeta}>\n {step.priced && step.snapshotVersion !== null\n ? t('step.snapshot', {\n time: formatTime(step.snapshotEffectiveAt ?? step.time),\n })\n : t('step.unpriced', { reason: reasonText(step, t) })}\n </span>\n </div>\n </li>\n );\n })}\n </ol>\n </section>\n </div>\n )\n})\n","/**\n * Per-reply cost, rendered in the completed Turn's action row. Contributed\n * through the `conversation.chat.assistant-actions` list slot, whose entries\n * DSH 0.1.5 draws in the row's action area (after the copy action, ahead of the\n * branch button); a flex `order` then seats the capsule after the shipped 用量\n * and 用时 capsules and before the row's timestamp, closing the stat run with\n * the row's own 8px gaps between neighbours.\n *\n * The control is the row's own stat-capsule shape — the same trigger + portaled\n * dialog the shipped 用量 and 用时 capsules use (28px pill, 13px/24px tertiary\n * label, hover fill, an `aria-expanded` dialog anchored above the trigger\n * through `useAnchoredPosition` + `useDismissOnOutsidePointer`). Clicking it\n * opens the reply's anchored breakdown: the three billed categories, the band\n * that priced it, the model, and the snapshot version.\n *\n * The price itself is the band-coloured label (green off-peak, red peak,\n * neutral before the rollout), so the row carries the status without a second\n * tag capsule. The slot hands over only the finalized message id, so the\n * `sessionCostIndex` projection resolves it to the (turn, step) coordinates the\n * anchored ledger is keyed by; the Turn's own steps are then summed. Every\n * price is the anchored snapshot value, never a current-price recompute; an\n * unpriced Turn renders `—` (the Cost tab explains why).\n */\nimport { memo, useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconGaugeOutlineRegular,\n useAnchoredPosition,\n useDismissOnOutsidePointer,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport css from './AssistantCostChip.module.css'\n\nexport type ChipLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface AssistantCostChipProps {\n /** Durable id of the finalized assistant message owning this action row. */\n messageId: string\n useProjection: UseProjection\n t: ChipLocale\n}\n\n/** Distance kept between the trigger and its dialog (the shipped stat panels' own gap). */\nconst PANEL_GAP = 8\n/** Distance kept between the dialog and the viewport edges. */\nconst PANEL_MARGIN = 12\n/** Pre-measurement placement: mounted but invisible, so the first frame can size it. */\nconst MEASURE_STYLE = { visibility: 'hidden', left: 0, top: 0 } as const\n\n/** The projection step for one (turn, step) coordinate, if the ledger has it. */\nexport function stepOf(\n steps: readonly SessionCostStep[] | undefined,\n turn: number,\n step: number,\n): SessionCostStep | undefined {\n return steps?.find(entry => entry.turn === turn && entry.step === step)\n}\n\n/** The Turn's own ledger steps, in fold order. */\nexport function stepsOfTurn(\n steps: readonly SessionCostStep[] | undefined,\n turn: number,\n): SessionCostStep[] {\n return steps?.filter(entry => entry.turn === turn) ?? []\n}\n\nexport const AssistantCostChip = memo(function AssistantCostChip({ messageId, useProjection, t }: AssistantCostChipProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const index = useProjection('sessionCostIndex')\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const [open, setOpen] = useState(false)\n const rootRef = useRef<HTMLSpanElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n const pos = useAnchoredPosition({ open, anchorRef: rootRef, panelRef, side: 'top', gap: PANEL_GAP, margin: PANEL_MARGIN })\n useDismissOnOutsidePointer(rootRef, open, setOpen, panelRef)\n\n useEffect(() => {\n let alive = true\n void fetch(`/cost-meter?currency=${currency}`, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the chip keeps the last good pricebook */ })\n return () => {\n alive = false\n }\n }, [currency])\n\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [open])\n\n // The action row carries the message id; the index carries its coordinates.\n const coordinates = index?.steps[messageId]\n const steps = coordinates === undefined ? [] : stepsOfTurn(cost?.steps, coordinates.turn)\n const priced = steps.filter(entry => entry.cost !== null)\n if (steps.length === 0) return null\n if (priced.length === 0) {\n // Unpriced turn: show the dash; the native title explains why.\n const reason = steps[0]?.unpricedReason === 'NO_MODEL' ? t('reason.NO_MODEL') : t('reason.NO_PRICE')\n return (\n <span className={css.unpriced} data-cost-chip title={reason} data-testid=\"cost-chip-unpriced\">{t('chip.unpriced')}</span>\n )\n }\n\n const symbol = currencySymbol(currency)\n const money = (value: number): string => `${symbol}${formatMoney(value)}`\n const amount = money(priced.reduce((sum, entry) => sum + (entry.cost ?? 0), 0))\n // Anchored to the Turn's own (last) step: the ledger's `band` was fixed at\n // fold time from the usage event's time, so the capsule is coloured by the\n // band that actually priced this reply — not the band of the clock now.\n const last = priced[priced.length - 1]\n if (last === undefined) return null\n const band = last.band ?? bandForTime(last.time)\n const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, last.provider, last.model)\n const bandLabel = band === 'peak'\n ? ratio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(ratio) })\n : band === 'offPeak'\n ? ratio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / ratio) })\n : null\n\n return (\n <span\n className={css.root}\n ref={rootRef}\n data-cost-chip\n data-band={band === 'single' ? undefined : band}\n data-testid=\"cost-chip\"\n >\n <button\n type=\"button\"\n className={css.trigger}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen(prev => !prev)}\n >\n <IconGaugeOutlineRegular />\n <span className={css.label}>{amount}</span>\n </button>\n {open && createPortal(\n <div\n ref={panelRef}\n className={css.panel}\n role=\"dialog\"\n aria-label={t('chip.detail.title')}\n style={pos ?? MEASURE_STYLE}\n data-testid=\"cost-chip-detail\"\n >\n <div className={css.title}>\n <span className={css.titleLabel}><IconGaugeOutlineRegular />{t('chip.detail.title')}</span>\n <span className={css.titleValue}>{amount}</span>\n </div>\n <div className={css.titleRule} aria-hidden />\n <dl className={css.details}>\n <dt>{t('view.uncached')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.uncachedCost, 0))}</dd>\n <dt>{t('view.cacheRead')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.cacheReadCost, 0))}</dd>\n <dt>{t('view.output')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.outputCost, 0))}</dd>\n <dt>{t('chip.detail.band')}</dt>\n <dd>{bandLabel ?? t('band.single')}</dd>\n <dt>{t('chip.detail.model')}</dt>\n <dd>{last.model}</dd>\n {last.snapshotVersion !== null && (\n <>\n <dt>{t('chip.detail.snapshot')}</dt>\n <dd>{t('step.snapshot', { version: String(last.snapshotVersion), time: formatTime(last.snapshotEffectiveAt ?? last.time) })}</dd>\n </>\n )}\n </dl>\n </div>,\n document.body,\n )}\n </span>\n )\n})\n","/**\n * Session header pill (right-aligned utility): the conversation's anchored\n * total cost, or — while the assistant is replying — a live estimate of the\n * TOTAL once the running reply settles (`预计 ¥x.xx(进行中)`): the already\n * anchored ledger total plus the running reply's estimated input/output.\n * Clicking the pill toggles a detail panel: totals by category, model, and\n * the anchored price snapshot; the panel dismisses on outside clicks or\n * Escape.\n *\n * The estimate is explicitly labeled 估算/estimate: input is priced at the\n * projected context size weighted by the newest completed step's cache\n * ratio (conservative all-uncached without history), output at the\n * character heuristic — both at CURRENT snapshot prices.\n *\n * The band rides the shipped `Tag` capsule (11px/17px, `success` off-peak /\n * `danger` peak) so the header reads the same tag geometry as every other tag\n * control in the product.\n */\nimport { memo, useEffect, useMemo, useRef, useState, type RefObject } from 'react'\nimport { Tag } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only: merges the contextPressure key into SessionProjectionMap.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport type { ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { bandForTime, blocksOutputTokens, bucketAt, cacheReadRatioOf, costLookupFor, estimateCost, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport { ENDPOINT, REFRESH_MS } from './SessionCostLine.tsx'\nimport css from './SessionCostPill.module.css'\n\nexport type PillLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostPillProps {\n useSession: SnapshotSelectorHook<SessionSnapshot>\n useProjection: UseProjection\n sessionId: string\n t: PillLocale\n}\n\n/** Read the session's running flag (the pill's estimate gate). */\nfunction runningOf(snapshot: SessionSnapshot): boolean {\n return snapshot.running\n}\n\n/** Close the popover on outside clicks or Escape (product detail-panel posture). */\nfunction useDismissOutside(open: boolean, onDismiss: () => void, anchor: RefObject<HTMLElement | null>): void {\n useEffect(() => {\n if (!open) return\n const onPointerDown = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null\n if (anchor.current !== null && target !== null && !anchor.current.contains(target)) onDismiss()\n }\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') onDismiss()\n }\n document.addEventListener('mousedown', onPointerDown)\n document.addEventListener('touchstart', onPointerDown)\n document.addEventListener('keydown', onKeyDown)\n return () => {\n document.removeEventListener('mousedown', onPointerDown)\n document.removeEventListener('touchstart', onPointerDown)\n document.removeEventListener('keydown', onKeyDown)\n }\n }, [open, onDismiss, anchor])\n}\n\nexport const SessionCostPill = memo(function SessionCostPill({ useSession, useProjection, sessionId, t }: SessionCostPillProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const pressure = useProjection('contextPressure')\n const running = useSession(runningOf)\n // DSH 0.1.5 no longer exposes the streaming partial message to plugins\n // (streams are folded into the durable assistant/message event); the live\n // estimate therefore projects the running reply's input side only and\n // leaves its output unestimated.\n const partial = null\n const [open, setOpen] = useState(false)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const wrapRef = useRef<HTMLDivElement | null>(null)\n useDismissOutside(open, () => setOpen(false), wrapRef)\n\n useEffect(() => {\n let alive = true\n let timer: number | undefined\n const endpoint = `${ENDPOINT}?session=${encodeURIComponent(sessionId)}&currency=${currency}`\n const load = (): void => {\n void fetch(endpoint, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the pill keeps the last good pricebook */ })\n }\n load()\n timer = window.setInterval(load, REFRESH_MS)\n return () => {\n alive = false\n if (timer !== undefined) window.clearInterval(timer)\n }\n }, [sessionId, currency])\n\n const projection = cost\n const model = projection?.model ?? null\n const snapshot = response?.pricebook.current ?? null\n const schedule = response?.pricebook.schedule\n // The pill reads the WHOLE conversation: the main ledger plus every subagent\n // row the host served (all nesting levels), so a nested delegation is folded\n // into the same number the dock line and the Cost tab show.\n const lookup = costLookupFor(projection?.totals, response?.subagents)\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n const symbol = currencySymbol(currency)\n\n // The estimate projects the TOTAL once the running reply settles: the\n // already-anchored ledger total (main + subagents) plus this reply's\n // estimated input/output (subagent replies cannot be projected live).\n const estimate = useMemo(() => {\n if (!running || projection === undefined) return null\n const bucket = bucketAt(snapshot ?? undefined, model?.provider, model?.model ?? 'unknown', Date.now(), schedule)\n if (bucket === undefined) return null\n const inputTokens = pressure?.projectedTokens ?? pressure?.pressureTokens ?? 0\n const cachedRatio = cacheReadRatioOf(projection.steps)\n const outputTokens = 0\n const stepEstimate = estimateCost(bucket, inputTokens, cachedRatio, outputTokens)\n return (combined?.cost ?? 0) + stepEstimate\n }, [running, projection, combined, snapshot, schedule, model, pressure, partial, currency])\n\n // Absent main ledger + present subagent rows still renders: the pill exists\n // to answer \"what did this conversation cost\", and a conversation whose own\n // steps are not materialized yet still spent its subagents' money.\n if (combined === undefined && estimate === null) return null\n\n const total = combined?.cost ?? 0\n const label = estimate !== null\n ? t('pill.estimate', { amount: `${symbol}${formatMoney(estimate)}` })\n : t('pill.label', { amount: `${symbol}${formatMoney(total)}` })\n\n const last = projection?.steps.length !== undefined && projection.steps.length > 0\n ? projection.steps[projection.steps.length - 1]\n : undefined\n\n const sourceKey = snapshot?.source ?? null\n const source = sourceKey === null ? t('source.none') : t(`source.${sourceKey}` as 'source.manual')\n\n // The pill's band is a LIVE readout: the price regime in effect RIGHT NOW,\n // classified against the schedule of the pricebook being displayed (the zh\n // and en pages may state different windows). The per-reply chip and the\n // cost tab, by contrast, use each step's ANCHORED band.\n const band = bandForTime(Date.now(), schedule)\n const peakRatio = peakOffPeakMultiplier(snapshot ?? undefined, model?.provider, model?.model ?? 'unknown')\n const bandLabel = band === 'peak'\n ? peakRatio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(peakRatio) })\n : band === 'offPeak'\n ? peakRatio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / peakRatio) })\n : null\n\n return (\n <div className={css.wrap} ref={wrapRef}>\n <button\n type=\"button\"\n className={css.pill}\n data-testid=\"cost-pill\"\n data-running={estimate !== null}\n data-band={band === 'single' ? undefined : band}\n onClick={() => setOpen(prev => !prev)}\n aria-expanded={open}\n >\n {label}\n {bandLabel !== null && <Tag tone={band === 'peak' ? 'danger' : 'success'}>{bandLabel}</Tag>}\n {estimate !== null && <span className={css.estimateNote}>· {t('pill.estimateNote')}</span>}\n </button>\n {open && (\n <div className={css.detail} data-testid=\"cost-pill-detail\">\n <div className={css.detailRow + ' ' + css.detailTotal}>{t('pill.detail.total', { amount: `${symbol}${formatMoney(total)}` })}</div>\n {bandLabel !== null && (\n <div className={css.detailRow + ' ' + css.detailRowSecondary} data-testid=\"cost-pill-detail-band\">{bandLabel}</div>\n )}\n {combined !== undefined && combined.pricedSteps > 0 && (\n <>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.uncached')}: {symbol}{formatMoney(combined.uncachedCost)}</div>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.cacheRead')}: {symbol}{formatMoney(combined.cacheReadCost)}</div>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.output')}: {symbol}{formatMoney(combined.outputCost)}</div>\n </>\n )}\n {subSpend > 0 && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` })}</div>\n )}\n {model !== null && (\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('pill.detail.model', { model: model.model })}</div>\n )}\n {last !== undefined && last.snapshotVersion !== null && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>\n {t('pill.detail.snapshot', { time: formatTime(last.snapshotEffectiveAt ?? last.time) })}\n {last.band !== null && ` · ${t(`band.${last.band}` as 'band.single')}`}\n </div>\n )}\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('pill.detail.source', { source })}</div>\n {projection !== undefined && projection.totals.unpricedSteps > 0 && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('view.unpriced', { count: String(projection.totals.unpricedSteps) })}</div>\n )}\n {estimate !== null && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('pill.estimateNote')}</div>\n )}\n </div>\n )}\n </div>\n )\n})\n","/**\n * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 renders a\n * row's own configuration through the Plugins page's `plugins.row.config`\n * slot, which dispatches the contribution twice: `view: 'summary'` supplies\n * the row's one-liner, and `view: 'page'` is the body of the page the row's\n * configure control opens — the page itself draws the title, icon, and crumb.\n *\n * The page owner hands the form seat in `form`: `state` is the accepted,\n * schema-resolved value of this plugin's Loader entry with the revision that\n * fences the next write, and `mutate` submits path edits against that\n * revision. There is no settings scope to bind any more; writes ride the same\n * profile-patch path the generated forms use, so the card stages, saves, and\n * persists like every other plugin configuration. The JSON textareas carry the\n * two structured fields (per-model price overrides, OpenRouter aliases); the\n * refresh button is a plain action against the plugin's own host route.\n */\nimport { memo, useEffect, useState } from 'react'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConfigPageForm } from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ConversationCostResponse } from '../types.ts'\nimport { formatTime } from './format.ts'\nimport css from './CostPluginCard.module.css'\n\nexport type PluginCardLocale = PropsLocale<'cost-meter'>['t']\n\n/**\n * One path-addressed edit the configuration page submits. Derived from the\n * form contract rather than imported, so the card depends on the page's own\n * seat and not on a wire package it never calls.\n */\ntype ConfigOp = Parameters<ConfigPageForm['mutate']>[0][number]\n\nexport interface CostPluginCardProps {\n /** Which view the Plugins page asks for: the row's one-liner or the page body. */\n view: 'summary' | 'page'\n /** Host-owned values and write actions; the page omits it outside its own row page. */\n form?: ConfigPageForm | undefined\n t: PluginCardLocale\n}\n\n/** One staged field group the card writes through the form. */\ninterface Staged {\n overridesText: string\n aliasesText: string\n cacheReadDiscount: string\n fxMode: 'auto' | 'manual'\n manualRate: string\n balanceEnabled: boolean\n openRouterEnabled: boolean\n}\n\ninterface SectionShape {\n overrides?: Record<string, unknown>\n aliases?: Record<string, string>\n cacheReadDiscount?: number\n fxMode?: 'auto' | 'manual'\n manualRate?: number\n balanceEnabled?: boolean\n openRouterEnabled?: boolean\n}\n\nconst json = (value: unknown): string => JSON.stringify(value ?? {}, null, 2)\n/** The section's set of user-overridden fields, read from the raw user layer. */\nconst overridden = (user: unknown): Record<string, unknown> =>\n typeof user === 'object' && user !== null && !Array.isArray(user) ? user as Record<string, unknown> : {}\n\nexport const CostPluginCard = memo(function CostPluginCard({ view, form, t }: CostPluginCardProps) {\n const [busy, setBusy] = useState(false)\n const [status, setStatus] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [staged, setStaged] = useState<Staged | null>(null)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n\n const snapshot = form?.state as ConfigFormSnapshot<SectionShape> | undefined\n const value = snapshot?.value\n\n // Seed the staged form from the latest accepted section. The snapshot\n // reference is stable until the Host value actually changes, so an\n // in-progress edit survives an unrelated re-render of the Plugins page.\n useEffect(() => {\n if (value === undefined) return\n setStaged({\n overridesText: json(value.overrides),\n aliasesText: json(value.aliases),\n cacheReadDiscount: String(value.cacheReadDiscount ?? 0.25),\n fxMode: value.fxMode ?? 'auto',\n manualRate: String(value.manualRate ?? 7.2),\n balanceEnabled: value.balanceEnabled ?? true,\n openRouterEnabled: value.openRouterEnabled ?? true,\n })\n }, [value])\n\n // Snapshot info for the row's one-liner (source/time, no version numbers).\n useEffect(() => {\n if (view !== 'page') return undefined\n let alive = true\n const load = (): void => {\n void fetch('/cost-meter', { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* keep the last snapshot info */ })\n }\n load()\n return () => {\n alive = false\n }\n }, [view])\n\n const current = response?.pricebook.current ?? null\n const source = current === null\n ? t('source.none')\n : t(`source.${current.source}` as 'source.manual')\n\n // The row's one-liner: the Plugins page shows it as the row's description\n // fallback (a package that ships its own description keeps it). It states\n // what the page configures rather than fetching one, so listing the rows\n // costs no request.\n if (view === 'summary') return <span className={css.summary}>{t('settings.summary')}</span>\n\n /**\n * Submit one batch of field edits as path operations against the revision\n * the editor read. An unchosen select/number keeps its accepted value, so\n * the batch always restates the whole card.\n */\n const save = (): void => {\n if (staged === null || form === undefined) return\n setBusy(true)\n setStatus(null)\n setError(null)\n let overrides: Record<string, unknown>\n let aliases: Record<string, string>\n try {\n overrides = JSON.parse(staged.overridesText) as Record<string, unknown>\n aliases = JSON.parse(staged.aliasesText) as Record<string, string>\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n setBusy(false)\n return\n }\n const ops: ConfigOp[] = [\n { op: 'set', path: ['overrides'], value: overrides },\n { op: 'set', path: ['aliases'], value: aliases },\n { op: 'set', path: ['fxMode'], value: staged.fxMode },\n { op: 'set', path: ['balanceEnabled'], value: staged.balanceEnabled },\n { op: 'set', path: ['openRouterEnabled'], value: staged.openRouterEnabled },\n ]\n const discount = Number(staged.cacheReadDiscount)\n if (Number.isFinite(discount) && discount >= 0 && discount <= 1) {\n ops.push({ op: 'set', path: ['cacheReadDiscount'], value: discount })\n }\n const rate = Number(staged.manualRate)\n if (Number.isFinite(rate) && rate > 0) ops.push({ op: 'set', path: ['manualRate'], value: rate })\n void form.mutate(ops, snapshot?.revision)\n .then((accepted) => {\n if (accepted) setStatus(t('settings.saved'))\n else setError(t('settings.rejected'))\n }, (cause: unknown) => {\n setError(cause instanceof Error ? cause.message : String(cause))\n })\n .finally(() => setBusy(false))\n }\n\n const refresh = (): void => {\n setBusy(true)\n setStatus(null)\n setError(null)\n void fetch('/cost-meter', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ action: 'refresh' }),\n })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (data !== null) setResponse(data)\n setStatus(t('settings.saved'))\n })\n .catch((cause: unknown) => setError(cause instanceof Error ? cause.message : String(cause)))\n .finally(() => setBusy(false))\n }\n\n // A namespace the Host does not serve to this client (or one whose connection\n // keeps preferences process-local) has no form seat: the page still opens,\n // but nothing here can be edited.\n if (form === undefined || snapshot === undefined || snapshot.status === 'unavailable') {\n return (\n <div className={css.page} data-testid=\"cost-plugin-card\">\n {error !== null && <p className={css.error}>{t('settings.error', { error })}</p>}\n <p className={css.hint}>{t('settings.unavailable')}</p>\n <div className={css.footer}>\n {status !== null && <span className={css.status}>{status}</span>}\n <span className={css.spacer} />\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>\n {busy ? t('settings.refreshing') : t('settings.refresh')}\n </Button>\n </div>\n </div>\n )\n }\n\n const user = overridden(snapshot.user)\n const readOnly = !snapshot.writable\n const hintText = current === null\n ? t('settings.noSnapshot')\n : `${t('settings.current', { source, time: formatTime(current.effectiveAt) })}`\n\n return (\n <div className={css.page} data-testid=\"cost-plugin-card\">\n <p className={css.hint}>{hintText}</p>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.discount')}</span>\n {'cacheReadDiscount' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <input\n className={css.input}\n type=\"number\"\n min={0}\n max={1}\n step={0.01}\n disabled={readOnly}\n value={staged?.cacheReadDiscount ?? ''}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, cacheReadDiscount: event.target.value })}\n />\n </div>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.fx')}</span>\n {('fxMode' in user || 'manualRate' in user) && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <div className={css.inline}>\n <select\n className={css.select}\n disabled={readOnly}\n value={staged?.fxMode ?? 'auto'}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, fxMode: event.target.value as 'auto' | 'manual' })}\n >\n <option value=\"auto\">{t('settings.fx.auto')}</option>\n <option value=\"manual\">{t('settings.fx.manual')}</option>\n </select>\n <input\n className={css.input}\n type=\"number\"\n min={0.001}\n step={0.01}\n disabled={readOnly || staged?.fxMode === 'auto'}\n value={staged?.manualRate ?? ''}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, manualRate: event.target.value })}\n />\n </div>\n </div>\n <label className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.balance')}</span>\n {'balanceEnabled' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n <input\n className={css.checkbox}\n type=\"checkbox\"\n disabled={readOnly}\n checked={staged?.balanceEnabled ?? true}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, balanceEnabled: event.target.checked })}\n />\n </div>\n </label>\n <label className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.openrouter')}</span>\n {'openRouterEnabled' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n <input\n className={css.checkbox}\n type=\"checkbox\"\n disabled={readOnly}\n checked={staged?.openRouterEnabled ?? true}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, openRouterEnabled: event.target.checked })}\n />\n </div>\n </label>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.overrides')}</span>\n {'overrides' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <textarea\n className={css.textarea}\n value={staged?.overridesText ?? ''}\n spellCheck={false}\n readOnly={readOnly}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, overridesText: event.target.value })}\n />\n <div className={css.hint}>{t('settings.jsonHint')}</div>\n </div>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.aliases')}</span>\n {'aliases' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <textarea\n className={css.textarea}\n value={staged?.aliasesText ?? ''}\n spellCheck={false}\n readOnly={readOnly}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, aliasesText: event.target.value })}\n />\n </div>\n {error !== null && <p className={css.error}>{t('settings.error', { error })}</p>}\n <div className={css.footer}>\n {status !== null && <span className={css.status}>{status}</span>}\n <span className={css.spacer} />\n <Button variant=\"outline\" onClick={() => setStaged(null)} disabled={busy || readOnly}>{t('settings.discard')}</Button>\n <Button\n variant=\"primary\"\n onClick={save}\n disabled={busy || readOnly || snapshot.status !== 'ready' || staged === null}\n >\n {t('settings.save')}\n </Button>\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>{busy ? t('settings.refreshing') : t('settings.refresh')}</Button>\n </div>\n </div>\n )\n})\n","/**\n * Cost-meter plugin, browser half: contributes the cost surfaces —\n * the composer-dock readout (anchored session spend + account balance), the\n * Cost view tab, the per-reply cost at the end of each completed Turn's\n * timing row, the header pill with the live streaming estimate, and the\n * plugin configuration page (设置 → 插件 → cost-meter 行). The anchored ledger\n * arrives through the `sessionCost` projection; the balance and the pricebook\n * arrive over the trust-fenced host `/cost-meter` route; the editable\n * configuration binds the `cost-meter` Loader entry through the Plugins page's\n * `plugins.row.config` contribution, whose `form` seat carries the values and\n * the revision-fenced write.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: pulls the ui-conversation SlotMap merge (composer.dock etc.).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the ui-session standard-props merge (useSession /\n// useProjection / sessionId seats) and the ui-chat SlotMap merge\n// (conversation.chat.assistant-actions).\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-chat/client'\n// Type-only: pulls the ui-renderer Context augmentation (ctx.slots).\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the Plugins page slot merge (plugins.row.config) and the\n// config-page form contract the page hands a row's configuration page.\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\n// Type-only: pulls the shared config-form types (ConfigForm types live in the\n// settings package even though DSH 0.1.7 hosts the page on the Plugins page).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport { en, NS, zh, type ConversationCostKey } from './locales.ts'\nimport { SessionCostLine } from './SessionCostLine.tsx'\nimport { CostView } from './CostView.tsx'\nimport { AssistantCostChip } from './AssistantCostChip.tsx'\nimport { SessionCostPill } from './SessionCostPill.tsx'\nimport { CostPluginCard } from './CostPluginCard.tsx'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Cost meter and account balance copy. */\n 'cost-meter': ConversationCostKey\n }\n}\n\n/** The plugin entry id this package's configuration page is keyed by. */\nexport const SETTINGS_NAMESPACE = 'cost-meter'\n\n/** The package whose bundle declares the `cost-meter` row. */\nexport const PACKAGE_NAME = '@gamegeek-saikel/dsh-cost-meter'\n\n/**\n * The `plugins.row.config` dispatch key: `<bundle package name>#<row id>`,\n * exactly as this package's cordis.patch.yml declares the row. The Plugins\n * page reads it to pair the row with this configuration page.\n */\nexport const ROW_CONFIG_KEY = `${PACKAGE_NAME}#${SETTINGS_NAMESPACE}`\n\n/** Required services for locale registration and the slot contributions. */\nexport const inject = ['sessions', 'slots', 'locale']\n\n/**\n * Client plugin body: register the dictionaries and the five slot entries.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-cost-meter: dictionaries')\n const t = ctx.locale.bind(NS)\n\n // Composer-dock readout: anchored session spend + balance (after the\n // shipped stats line, order 0).\n ctx.slots.inject(\n 'conversation.composer.dock',\n () => ctx.slots.register({\n name: 'conversation.composer.dock',\n id: 'cost-meter',\n order: 1,\n locale: NS,\n }, SessionCostLine),\n )\n\n // Cost view tab (after trajectory at order 10).\n ctx.slots.inject(\n 'conversation.view',\n () => ctx.slots.register({\n name: 'conversation.view',\n id: 'cost',\n order: 20,\n locale: NS,\n label: () => t('view.cost'),\n }, CostView),\n )\n\n // Per-reply cost in the completed Turn's action row\n // (copy · branch · 用量 · 用时 · ¥3.02 · 9月4日 19:47): DSH 0.1.5 renders the\n // assistant-actions entries in that row's action area and hands each one the\n // finalized message id, so the chip resolves its Turn through the\n // `sessionCostIndex` projection; the capsule's own `order` then closes the\n // stat run after 用量 / 用时 and before the row's timestamp. The `turnTail`\n // chain would instead render on its own line above the row.\n ctx.slots.inject(\n 'conversation.chat.assistant-actions',\n () => ctx.slots.register({\n name: 'conversation.chat.assistant-actions',\n id: 'cost-meter',\n order: 20,\n locale: NS,\n }, AssistantCostChip),\n )\n\n // Header pill: total / live estimate with the expandable detail.\n // Negative order keeps it left of the built-in Session log utility.\n ctx.slots.inject(\n 'conversation.session.header.utilities',\n () => ctx.slots.register({\n name: 'conversation.session.header.utilities',\n id: 'cost-meter',\n order: -10,\n locale: NS,\n }, SessionCostPill),\n )\n\n // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 hosts one\n // plugin row's own configuration under `plugins.row.config`, keyed by\n // `<bundle package name>#<row id>`; the Host half opts out of the generated\n // schema form (`settings.configure({ auto: false })`), so this page is what\n // the row's configure control opens.\n ctx.slots.inject(\n 'plugins.row.config',\n () => ctx.slots.register({\n name: 'plugins.row.config',\n key: ROW_CONFIG_KEY,\n locale: NS,\n }, CostPluginCard),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["PANEL_GAP","PANEL_MARGIN","MEASURE_STYLE","memo","useState","useRef","useAnchoredPosition","css","useMemo","IconGaugeOutlineRegular","createPortal","Fragment","memo","useState","css","Tag","memo","useState","useRef","useAnchoredPosition","css","IconGaugeOutlineRegular","createPortal","memo","useState","useRef","useMemo","css","Tag","memo","useState","css","Button"],"sources":["../src/client/locales.ts","../src/pricing.ts","../src/client/cost-math.ts","../src/client/format.ts","../src/client/SessionCostLine.tsx","../src/client/CostView.tsx","../src/client/AssistantCostChip.tsx","../src/client/SessionCostPill.tsx","../src/client/CostPluginCard.tsx","../src/client/index.ts"],"sourcesContent":["/** `cost-meter` namespace dictionaries. */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'cost-meter'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n // View tab\n 'view.cost': '花费',\n 'view.empty': '暂无花费数据',\n 'view.total': '总花费',\n 'view.uncached': '输入(缓存未命中)',\n 'view.cacheRead': '输入(缓存命中)',\n 'view.output': '输出',\n 'view.cacheWriteTokens': '缓存写入',\n 'view.unpriced': '{count} 步无价格',\n 'view.steps': '每轮回复花费',\n\n // Step list\n 'step.turn': '第 {turn} 轮',\n 'step.model': '模型 {model}',\n 'step.time': '{time}',\n 'step.tokens': '未命中 {uncached} · 缓存读 {read} · 缓存写 {write} · 输出 {output}',\n 'step.cost': '花费 {amount}',\n 'step.snapshot': '快照 · {time}',\n 'step.unpriced': '—({reason})',\n 'reason.NO_MODEL': '无模型信息',\n 'reason.NO_PRICE': '无价格',\n\n // Shared amounts\n 'cost.label': '本会话 {amount}',\n 'line.uncached': '非缓存输入 {amount}',\n 'line.cacheRead': '缓存输入 {amount}',\n 'line.output': '输出 {amount}',\n 'line.subagent': '子代理 {amount}',\n 'subagent.depth': '第 {depth} 层',\n 'pill.label': '花费 {amount}',\n 'pill.estimate': '预计 {amount}',\n 'pill.estimateNote': '估算',\n 'pill.detail.total': '总花费 {amount}',\n 'pill.detail.snapshot': '价格快照({time})',\n 'pill.detail.model': '模型 {model}',\n 'pill.detail.source': '价格来源 {source}',\n 'source.official': '官方页面',\n 'source.fallback': '内置回退',\n 'source.manual': '手动',\n 'source.openrouter': 'OpenRouter',\n 'source.none': '无',\n 'band.peak': '高峰价',\n 'band.offPeak': '闲时价',\n 'band.single': '单价',\n 'price.peakExtra': '高峰 · 比闲时多 {multiplier} 倍',\n 'price.offPeakSaving': '闲时 · 比高峰省 {percent}',\n 'price.peakRatio': '高峰 {multiplier}',\n 'price.offPeakRatio': '闲时 {multiplier}',\n\n // Chip\n 'chip.title': '本回复花费 {amount}',\n 'chip.detail.title': '本回复花费',\n 'chip.detail.band': '计价档位',\n 'chip.detail.model': '模型',\n 'chip.detail.snapshot': '价格快照',\n 'chip.unpriced': '—',\n\n // Balance (inherited posture)\n 'balance.label': '余额 {amount}',\n 'balance.failed': '余额不可用',\n 'balance.detail': '余额 {amount}(赠送 {granted} · 充值 {toppedUp})',\n 'balance.suspended': '账户已暂停付费请求',\n 'refreshedAt': '更新于 {time}',\n\n // Dock capsule detail (the shipped stat-dialog shape: `<dt>` label, `<dd>` value)\n 'dock.detail.title': '本会话花费',\n 'dock.row.session': '本会话',\n 'dock.row.subagent': '子代理(含下级)',\n 'dock.row.own': '本会话自身',\n 'dock.row.uncached': '输入(缓存未命中)',\n 'dock.row.cacheRead': '输入(缓存命中)',\n 'dock.row.output': '输出',\n 'dock.row.balance': '账户余额',\n 'dock.row.model': '模型',\n 'dock.row.snapshot': '价格快照',\n 'dock.row.source': '价格来源',\n 'dock.row.updated': '更新时间',\n 'dock.row.unpriced': '无价格步数',\n\n // Plugin configuration page\n 'settings.title': '花费计价',\n 'settings.summary': '快照锚定的会话花费与价格表',\n 'settings.current': '当前快照 · {source} · {time}',\n 'settings.noSnapshot': '尚无价格快照',\n 'settings.refresh': '立即刷新',\n 'settings.refreshing': '刷新中…',\n 'settings.overrides': '按模型价目表(手动覆盖,元/百万 tokens)',\n 'settings.aliases': 'OpenRouter 别名映射',\n 'settings.discount': '缓存折扣系数(OpenRouter)',\n 'settings.fx': '汇率(USD→CNY)',\n 'settings.fx.auto': '自动',\n 'settings.fx.manual': '手动',\n 'settings.balance': '显示余额',\n 'settings.openrouter': '允许 OpenRouter 回退',\n 'settings.save': '保存',\n 'settings.discard': '丢弃',\n 'settings.saved': '已保存',\n 'settings.rejected': '主机拒绝了这次修改',\n 'settings.unavailable': '此部署未向本页提供可编辑的配置',\n 'settings.overridden': '已自定义',\n 'settings.error': '操作失败:{error}',\n 'settings.jsonHint': 'JSON 对象,键为模型,值含 single/offPeak/peak 价格桶',\n} as const\n\n/** English dictionary, key-identical to the Chinese source of truth. */\nexport const en: Record<ConversationCostKey, string> = {\n 'view.cost': 'Cost',\n 'view.empty': 'No cost data yet',\n 'view.total': 'Total',\n 'view.uncached': 'Input (cache miss)',\n 'view.cacheRead': 'Input (cache hit)',\n 'view.output': 'Output',\n 'view.cacheWriteTokens': 'Cache write',\n 'view.unpriced': '{count} steps unpriced',\n 'view.steps': 'Per-reply cost',\n\n 'step.turn': 'Turn {turn}',\n 'step.model': 'Model {model}',\n 'step.time': '{time}',\n 'step.tokens': 'miss {uncached} · read {read} · write {write} · out {output}',\n 'step.cost': 'Cost {amount}',\n 'step.snapshot': 'snapshot · {time}',\n 'step.unpriced': '—({reason})',\n 'reason.NO_MODEL': 'no model info',\n 'reason.NO_PRICE': 'no price',\n\n 'cost.label': 'Session {amount}',\n 'line.uncached': 'Uncached input {amount}',\n 'line.cacheRead': 'Cached input {amount}',\n 'line.output': 'Output {amount}',\n 'line.subagent': 'Subagents {amount}',\n 'subagent.depth': 'level {depth}',\n 'pill.label': 'Cost {amount}',\n 'pill.estimate': '≈ {amount}',\n 'pill.estimateNote': 'estimate',\n 'pill.detail.total': 'Total {amount}',\n 'pill.detail.snapshot': 'Price snapshot ({time})',\n 'pill.detail.model': 'Model {model}',\n 'pill.detail.source': 'Price source {source}',\n 'source.official': 'official page',\n 'source.fallback': 'built-in fallback',\n 'source.manual': 'manual',\n 'source.openrouter': 'OpenRouter',\n 'source.none': 'none',\n 'band.peak': 'peak',\n 'band.offPeak': 'off-peak',\n 'band.single': 'list',\n 'price.peakExtra': 'peak · {multiplier}× more than off-peak',\n 'price.offPeakSaving': 'off-peak · save {percent} vs peak',\n 'price.peakRatio': 'peak {multiplier}',\n 'price.offPeakRatio': 'off-peak {multiplier}',\n\n 'chip.title': 'This reply cost {amount}',\n 'chip.detail.title': 'Cost of this reply',\n 'chip.detail.band': 'Price band',\n 'chip.detail.model': 'Model',\n 'chip.detail.snapshot': 'Price snapshot',\n 'chip.unpriced': '—',\n\n 'balance.label': 'Balance {amount}',\n 'balance.failed': 'Balance unavailable',\n 'balance.detail': 'Balance {amount} (granted {granted} · topped up {toppedUp})',\n 'balance.suspended': 'Account suspended for paid requests',\n 'refreshedAt': 'Updated at {time}',\n\n 'dock.detail.title': 'Session cost',\n 'dock.row.session': 'This session',\n 'dock.row.subagent': 'Subagents (all levels)',\n 'dock.row.own': 'This session only',\n 'dock.row.uncached': 'Input (cache miss)',\n 'dock.row.cacheRead': 'Input (cache hit)',\n 'dock.row.output': 'Output',\n 'dock.row.balance': 'Account balance',\n 'dock.row.model': 'Model',\n 'dock.row.snapshot': 'Price snapshot',\n 'dock.row.source': 'Price source',\n 'dock.row.updated': 'Updated',\n 'dock.row.unpriced': 'Unpriced steps',\n\n 'settings.title': 'Cost meter',\n 'settings.summary': 'Snapshot-anchored session cost and price table',\n 'settings.current': 'Current snapshot · {source} · {time}',\n 'settings.noSnapshot': 'No price snapshot yet',\n 'settings.refresh': 'Refresh now',\n 'settings.refreshing': 'Refreshing…',\n 'settings.overrides': 'Per-model prices (manual overrides, CNY per 1M tokens)',\n 'settings.aliases': 'OpenRouter alias map',\n 'settings.discount': 'Cache-read discount (OpenRouter)',\n 'settings.fx': 'Exchange rate (USD→CNY)',\n 'settings.fx.auto': 'Auto',\n 'settings.fx.manual': 'Manual',\n 'settings.balance': 'Show balance',\n 'settings.openrouter': 'Allow OpenRouter fallback',\n 'settings.save': 'Save',\n 'settings.discard': 'Discard',\n 'settings.saved': 'Saved',\n 'settings.rejected': 'The Host refused this edit',\n 'settings.unavailable': 'This deployment serves no editable configuration to this page',\n 'settings.overridden': 'overridden',\n 'settings.error': 'Operation failed: {error}',\n 'settings.jsonHint': 'JSON object keyed by model; values carry single/offPeak/peak price buckets',\n}\n\n/** Key domain of the `cost-meter` namespace (zh is the source of truth). */\nexport type ConversationCostKey = keyof typeof zh\n","/**\n * Official DeepSeek pricing: fetches the pricing page\n * (api-docs.deepseek.com/zh-cn/quick_start/pricing/) and parses the price\n * table, the peak/off-peak bands, and the peak-hour schedule, so price\n * changes, model renames, and schedule changes never require a plugin update.\n *\n * The page's shape as of 2026-09-10: one combined table whose bucket rows\n * carry the OFF-PEAK cells then the PEAK cells of the `deepseek-flash` and\n * `deepseek-v4-pro` columns, plus a footnote stating that peak hours are\n * Beijing time 09:00-12:00 / 14:00-18:00 Monday through Friday (01:00-04:00 /\n * 06:00-10:00 UTC), everything else — including all of Saturday and Sunday —\n * off-peak. The retired ids `deepseek-v4-flash` and\n * `deepseek-v4-flash-vision-exp` no longer have a column; the page states\n * they are served by V4.1-Flash and billed at the Flash price, which is what\n * the `flash` pricing key resolves them to.\n *\n * The parser is deliberately tolerant: it matches price cells next to the\n * bucket labels and model names anywhere in the HTML, so reordering or\n * rewording still yields values when the numbers are present; failures\n * degrade to the built-in presets rather than throwing.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/pricing\n */\n\nimport type { CurrentPricing, PeakHourRange, PeakModelPricing, PeakPricing, PeakSchedule, PriceBucket, PricingSnapshot } from './types.ts'\n\n/** Official pricing page URL (zh-cn). */\nexport const PRICING_URL = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/'\n\n/** Official pricing page URL (en). */\nexport const PRICING_URL_EN = 'https://api-docs.deepseek.com/quick_start/pricing/'\n\n/** Official peak-pricing rollout: 2026-08-17 00:00 Beijing time (UTC+8). */\nexport const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0)\n\n/**\n * Built-in fallback zh peak schedule: the official page's windows in Beijing\n * time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything else,\n * including all of Saturday and Sunday, off-peak).\n */\nexport const PEAK_SCHEDULE_ZH: PeakSchedule = {\n timezone: 'Asia/Shanghai',\n ranges: [[9, 12], [14, 18]],\n weekdaysOnly: true,\n}\n\n/**\n * Built-in fallback en peak schedule. Kept as a separate constant from the zh\n * schedule because the English pricing page states the same windows in UTC;\n * each pricebook parses its own page and falls back to this locale-matched\n * default only when the page carries no schedule.\n */\nexport const PEAK_SCHEDULE_EN: PeakSchedule = {\n timezone: 'UTC',\n ranges: [[1, 4], [6, 10]],\n weekdaysOnly: true,\n}\n\n/**\n * Built-in fallback prices: the pre-rollout official list for the Flash and\n * Pro models (CNY per 1M tokens), kept as the historical `single` tier that\n * prices usage from before the 2026-08-17 peak rollout. The Flash column is\n * served by `deepseek-flash`; the retired ids `deepseek-v4-flash` and\n * `deepseek-v4-flash-vision-exp` are billed at the same Flash price.\n * DeepSeek bills cache writes at the uncached input price (its wire usage\n * reports only cache-hit vs cache-miss buckets), so no separate cache-write\n * rate exists.\n */\nexport const FALLBACK_CURRENT: CurrentPricing = {\n flash: { cacheReadPerMillion: 0.02, inputPerMillion: 1, outputPerMillion: 2 },\n pro: { cacheReadPerMillion: 0.025, inputPerMillion: 3, outputPerMillion: 6 },\n}\n\n/**\n * Built-in fallback for the peak table: the 2026-09-10 official list (CNY per\n * 1M tokens), i.e. the `deepseek-flash` and `deepseek-v4-pro` columns. The\n * peak half is twice the off-peak half, as the page's footnote states. These\n * values back every snapshot taken while the page is unreachable, so they must\n * track the live page — a stale pair silently reprices usage (the Flash column\n * used to be 0.05 / 1.5 / 4.5 off-peak before the V4.1-Flash price cut).\n */\nexport const FALLBACK_PEAK: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.02, inputPerMillion: 1, outputPerMillion: 4 },\n peak: { cacheReadPerMillion: 0.04, inputPerMillion: 2, outputPerMillion: 8 },\n },\n pro: {\n offPeak: { cacheReadPerMillion: 0.15, inputPerMillion: 4.5, outputPerMillion: 13.5 },\n peak: { cacheReadPerMillion: 0.3, inputPerMillion: 9, outputPerMillion: 27 },\n },\n}\n\n/** Built-in fallback USD prices from the English pricing page. */\nexport const FALLBACK_CURRENT_USD: CurrentPricing = {\n flash: { cacheReadPerMillion: 0.007, inputPerMillion: 0.22, outputPerMillion: 0.66 },\n pro: { cacheReadPerMillion: 0.022, inputPerMillion: 0.66, outputPerMillion: 1.98 },\n}\n\n/**\n * Built-in fallback USD peak table from the English pricing page\n * (2026-09-10 list: Flash 0.003 / 0.15 / 0.6 off-peak, Pro 0.022 / 0.66 /\n * 1.98 off-peak, the peak half doubling each).\n */\nexport const FALLBACK_PEAK_USD: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.003, inputPerMillion: 0.15, outputPerMillion: 0.6 },\n peak: { cacheReadPerMillion: 0.006, inputPerMillion: 0.3, outputPerMillion: 1.2 },\n },\n pro: {\n offPeak: { ...FALLBACK_CURRENT_USD.pro },\n peak: { cacheReadPerMillion: 0.044, inputPerMillion: 1.32, outputPerMillion: 3.96 },\n },\n}\n\n/** Strip HTML tags to plain text (keeps cell order). */\nexport function stripHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<[^>]+>/g, ' ')\n .replace(/&nbsp;/gi, ' ')\n .replace(/&amp;/gi, '&')\n .replace(/&lt;/gi, '<')\n .replace(/&gt;/gi, '>')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** Every CNY price in a cell run, in document order. */\nfunction priceValues(text: string): number[] {\n const values: number[] = []\n for (const match of text.matchAll(/(\\d+(?:\\.\\d+)?)\\s*元/g)) {\n const value = Number(match[1])\n if (Number.isFinite(value)) values.push(value)\n }\n return values\n}\n\n/** Every USD price in a cell run, in document order. */\nfunction priceValuesUsd(text: string): number[] {\n const values: number[] = []\n for (const match of text.matchAll(/\\$?\\s*(\\d+(?:\\.\\d+)?)/g)) {\n const value = Number(match[1])\n if (Number.isFinite(value)) values.push(value)\n }\n return values\n}\n\n/** The result of parsing one locale's pricing table(s). */\ninterface LocalePricing {\n /** The current page's list prices: legacy single-price cells, or the off-peak cells of the combined table. */\n current: CurrentPricing\n /** Peak/off-peak table when the page carried one. */\n peak?: PeakPricing\n /** The separate pre-rollout single-price table, when the page still carried it. */\n legacyCurrent?: CurrentPricing\n}\n\n/** One bucket row's parsed cells. */\ninterface BucketCells {\n cacheRead: number[]\n input: number[]\n output: number[]\n}\n\n/** Build the `column`-th model bucket from three bucket rows. */\nfunction bucketAt(cells: BucketCells, column: number): PriceBucket {\n return {\n cacheReadPerMillion: cells.cacheRead[column]!,\n inputPerMillion: cells.input[column]!,\n outputPerMillion: cells.output[column]!,\n }\n}\n\n/** Build the current list (flash, pro, and vision when present). */\nfunction currentAt(cells: BucketCells, columns: number): CurrentPricing {\n return {\n flash: bucketAt(cells, 0),\n pro: bucketAt(cells, 1),\n ...(columns > 2 ? { vision: bucketAt(cells, 2) } : {}),\n }\n}\n\n/** Build the peak/off-peak pair for the `column`-th model. */\nfunction peakAt(offPeakCells: BucketCells, peakCells: BucketCells, column: number): PeakModelPricing {\n return {\n offPeak: bucketAt(offPeakCells, column),\n peak: bucketAt(peakCells, column),\n }\n}\n\n/** Build the peak table (flash, pro, and vision when present). */\nfunction peakPricingAt(offPeakCells: BucketCells, peakCells: BucketCells, columns: number): PeakPricing {\n return {\n flash: peakAt(offPeakCells, peakCells, 0),\n pro: peakAt(offPeakCells, peakCells, 1),\n ...(columns > 2 ? { vision: peakAt(offPeakCells, peakCells, 2) } : {}),\n }\n}\n\n/** Three bucket-row runs of the Chinese pricing table, when present. */\nconst ZH_PRICING_SEGMENTS_RE = /百万tokens输入\\s*[((]\\s*缓存命中\\s*[))]\\s*([\\s\\S]*?)百万tokens输入\\s*[((]\\s*缓存未命中\\s*[))]\\s*([\\s\\S]*?)百万tokens输出\\s*([\\s\\S]*?)(?:并发限制|扣费规则|$)/i\n\n/**\n * Parse the Chinese pricing table(s). The 2026-08-21 page carries one\n * combined table — bucket rows whose cells are OFF-PEAK × 3 models then\n * PEAK × 3 models — while the earlier page carried a separate legacy\n * single-price table plus a model-row peak table.\n * @param html - the raw Chinese pricing page.\n * @returns the parsed table(s), or undefined when the bucket labels are absent.\n */\nfunction parseZhPricing(html: string): LocalePricing | undefined {\n const text = stripHtml(html)\n const hit = ZH_PRICING_SEGMENTS_RE.exec(text)\n if (hit === null) return undefined\n const rows = [hit[1] ?? '', hit[2] ?? '', hit[3] ?? '']\n\n const hasBands = rows.every(row => /空闲时段/.test(row) && /高峰时段/.test(row))\n if (hasBands) {\n const offPeakRows = rows.map(row => {\n const values = priceValues(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(0, values.length / 2)\n })\n const peakRows = rows.map(row => {\n const values = priceValues(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(values.length / 2)\n })\n if (offPeakRows.some(row => row === undefined) || peakRows.some(row => row === undefined)) return undefined\n const columns = offPeakRows[0]!.length\n if (offPeakRows.some(row => row!.length !== columns)) return undefined\n const offPeakCells: BucketCells = {\n cacheRead: offPeakRows[0]!,\n input: offPeakRows[1]!,\n output: offPeakRows[2]!,\n }\n const peakCells: BucketCells = {\n cacheRead: peakRows[0]!,\n input: peakRows[1]!,\n output: peakRows[2]!,\n }\n return {\n current: currentAt(offPeakCells, columns),\n peak: peakPricingAt(offPeakCells, peakCells, columns),\n }\n }\n\n const currentRows = rows.map(priceValues)\n const columns = currentRows[0]?.length\n if (columns !== 2 && columns !== 3) return undefined\n if (currentRows.some(row => row.length !== columns)) return undefined\n const cells: BucketCells = {\n cacheRead: currentRows[0]!,\n input: currentRows[1]!,\n output: currentRows[2]!,\n }\n const current = currentAt(cells, columns)\n return { current, legacyCurrent: current }\n}\n\n/**\n * Parse the legacy Chinese peak-pricing table: model rows with off-peak and\n * peak cells, e.g. `deepseek-v4-flash 空闲时段 0.05 1.5 4.5 高峰时段 0.10 3.0 9.0`.\n * @param text - the stripped pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nfunction parseLegacyPeakTableZh(text: string): PeakPricing | undefined {\n const flash = /deepseek-v4-flash\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n const pro = /deepseek-v4-pro\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n if (flash === null || pro === null) return undefined\n const model = (row: RegExpExecArray): PeakModelPricing => ({\n offPeak: {\n cacheReadPerMillion: Number(row[1]),\n inputPerMillion: Number(row[2]),\n outputPerMillion: Number(row[3]),\n },\n peak: {\n cacheReadPerMillion: Number(row[4]),\n inputPerMillion: Number(row[5]),\n outputPerMillion: Number(row[6]),\n },\n })\n const vision = /deepseek-v4-flash-vision-exp\\s+空闲时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+高峰时段\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元\\s+(\\d+(?:\\.\\d+)?)元/i.exec(text)\n return {\n flash: model(flash),\n pro: model(pro),\n ...(vision === null ? {} : { vision: model(vision) }),\n }\n}\n\n/**\n * Parse the Chinese current single-price table: three rows labeled with the\n * bucket names, each carrying the model price cells (2 columns before\n * 2026-08-21, 3 columns afterwards — on the combined page these are the\n * off-peak cells).\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\nexport function parseCurrentTable(html: string): CurrentPricing | undefined {\n return parseZhPricing(html)?.current\n}\n\n/**\n * Parse the Chinese peak-pricing table: either the PEAK cells of the\n * 2026-08-21 combined table, or the earlier separate model-row table.\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when neither peak table is present.\n */\nexport function parsePeakTable(html: string): PeakPricing | undefined {\n const combined = parseZhPricing(html)\n return combined?.peak ?? parseLegacyPeakTableZh(stripHtml(html))\n}\n\n/** Three bucket-row runs of the English pricing table, when present. */\nconst EN_PRICING_SEGMENTS_RE = /1M\\s+INPUT\\s+TOKENS\\s*[((]?\\s*CACHE\\s+HIT\\s*[))]?\\s*([\\s\\S]*?)1M\\s+INPUT\\s+TOKENS\\s*[((]?\\s*CACHE\\s+MISS\\s*[))]?\\s*([\\s\\S]*?)1M\\s+OUTPUT\\s+TOKENS\\s*([\\s\\S]*?)(?:Concurrency\\s+Limit|Deduction\\s+Rules|$)/i\n\n/**\n * Parse the English pricing table(s). The 2026-08-21 page lists all three\n * buckets as rows with OFF-PEAK and PEAK cells (2 model columns before the\n * update, 3 afterwards); earlier pages carried a plain single-price table.\n */\nfunction parseEnglishPricing(html: string): LocalePricing | undefined {\n const text = stripHtml(html)\n const hit = EN_PRICING_SEGMENTS_RE.exec(text)\n if (hit === null) return undefined\n const rows = [hit[1] ?? '', hit[2] ?? '', hit[3] ?? '']\n\n const hasBands = rows.every(row =>\n /\\bOFF-PEAK\\b/i.test(row) && /(?:^|\\s)PEAK(?:\\s|$)/i.test(row))\n if (hasBands) {\n const offPeakRows = rows.map(row => {\n const values = priceValuesUsd(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(0, values.length / 2)\n })\n const peakRows = rows.map(row => {\n const values = priceValuesUsd(row)\n if (values.length !== 4 && values.length !== 6) return undefined\n return values.slice(values.length / 2)\n })\n if (offPeakRows.some(row => row === undefined) || peakRows.some(row => row === undefined)) return undefined\n const columns = offPeakRows[0]!.length\n if (offPeakRows.some(row => row!.length !== columns)) return undefined\n const offPeakCells: BucketCells = {\n cacheRead: offPeakRows[0]!,\n input: offPeakRows[1]!,\n output: offPeakRows[2]!,\n }\n const peakCells: BucketCells = {\n cacheRead: peakRows[0]!,\n input: peakRows[1]!,\n output: peakRows[2]!,\n }\n return {\n current: currentAt(offPeakCells, columns),\n peak: peakPricingAt(offPeakCells, peakCells, columns),\n }\n }\n\n const currentRows = rows.map(priceValuesUsd)\n const columns = currentRows[0]?.length\n if (columns !== 2 && columns !== 3) return undefined\n if (currentRows.some(row => row.length !== columns)) return undefined\n const cells: BucketCells = {\n cacheRead: currentRows[0]!,\n input: currentRows[1]!,\n output: currentRows[2]!,\n }\n const current = currentAt(cells, columns)\n return { current, legacyCurrent: current }\n}\n\n/**\n * Parse the English current single-price table (or the off-peak cells of the\n * combined table).\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\nexport function parseCurrentTableEn(html: string): CurrentPricing | undefined {\n return parseEnglishPricing(html)?.current\n}\n\n/**\n * Parse the English peak-pricing table.\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nexport function parsePeakTableEn(html: string): PeakPricing | undefined {\n return parseEnglishPricing(html)?.peak\n}\n\n/** Two `HH:MM-HH:MM` windows (the second one after a separator). */\nconst SCHEDULE_WINDOWS_RE = /(\\d{1,2}):(\\d{2})\\s*[-–—~至]\\s*(\\d{1,2}):(\\d{2})\\s*(?:[、,,;]\\s*)?(?:and\\s*)?(\\d{1,2}):(\\d{2})\\s*[-–—~至]\\s*(\\d{1,2}):(\\d{2})/i\n\n/**\n * Parse the peak-hour schedule from a pricing page: the two half-open\n * windows next to the localized peak label (`高峰时段` / `peak hours`), plus\n * the timezone the windows are expressed in and the Monday–Friday\n * restriction when the page states one. The Chinese page states Beijing\n * time; the English page states UTC — a Beijing/UTC+8 mention wins, a bare\n * UTC mention selects UTC, anything else defaults to Beijing time.\n * @param html - the raw pricing page.\n * @param locale - which page's wording to look for.\n * @returns the parsed schedule, or undefined when the page carries none.\n */\nexport function parsePeakSchedule(html: string, locale: 'zh' | 'en' = 'zh'): PeakSchedule | undefined {\n const text = stripHtml(html)\n // The pricing table itself repeats `高峰时段` as a band label, so the zh\n // search targets the schedule sentence (the label followed by a time).\n const labelMatch = locale === 'zh'\n ? /高峰时段[^。]{0,160}?\\d{1,2}:\\d{2}/i.exec(text)\n : /peak\\s*hours?/i.exec(text)\n if (labelMatch === null) return undefined\n const window = text.slice(labelMatch.index, labelMatch.index + 220)\n const match = SCHEDULE_WINDOWS_RE.exec(window)\n if (match === null) return undefined\n const ranges: PeakHourRange[] = [\n [Number(match[1]), Number(match[3])],\n [Number(match[5]), Number(match[7])],\n ]\n if (ranges.some(([start, end]) =>\n !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > 24 || start >= end)) {\n return undefined\n }\n const weekdaysOnly = /(?:周一|星期[一二三四五]|工作日)/.test(window)\n || /\\b(?:Monday\\s+through\\s+Friday|weekdays?)\\b/i.test(window)\n if (/\\b(?:beijing|北京时间|china standard)\\b/i.test(window) || /\\butc\\s*\\+\\s*8\\b/i.test(window)) {\n return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n }\n if (/\\butc\\b/i.test(window)) {\n return { timezone: 'UTC', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n }\n return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }\n}\n\n/**\n * Fetch and parse the official pricing page.\n * @param fetchImpl - fetch-compatible function (injected for testability).\n * @param timeoutMs - abort timeout.\n * @returns the parsed snapshot; `error` is set when fetch/parse failed.\n */\nexport async function fetchPricing(\n fetchImpl: typeof fetch = globalThis.fetch,\n timeoutMs = 15_000,\n locale: 'zh' | 'en' = 'zh',\n): Promise<PricingSnapshot> {\n const fetchedAt = Date.now()\n const currency = locale === 'en' ? 'USD' : 'CNY'\n const url = locale === 'en' ? PRICING_URL_EN : PRICING_URL\n const fallbackCurrent = locale === 'en' ? FALLBACK_CURRENT_USD : FALLBACK_CURRENT\n const fallbackPeak = locale === 'en' ? FALLBACK_PEAK_USD : FALLBACK_PEAK\n const fallbackSchedule = locale === 'en' ? PEAK_SCHEDULE_EN : PEAK_SCHEDULE_ZH\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n let response: Response\n try {\n response = await fetchImpl(url, { signal: controller.signal })\n } finally {\n clearTimeout(timer)\n }\n if (!response.ok) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: `pricing page HTTP ${response.status}` }\n }\n const html = await response.text()\n const parsed = locale === 'en' ? parseEnglishPricing(html) : parseZhPricing(html)\n if (parsed === undefined) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: 'pricing table not found' }\n }\n const peak = parsed.peak ?? (locale === 'en' ? parsePeakTableEn(html) : parsePeakTable(html))\n const { current, legacyCurrent } = parsed\n const schedule = parsePeakSchedule(html, locale) ?? fallbackSchedule\n const now = Date.now()\n const peakActive = now >= PEAK_PRICING_START_MS\n return {\n fetchedAt,\n currency,\n current,\n ...(legacyCurrent === undefined ? {} : { legacyCurrent }),\n ...(peak === undefined ? {} : { peak }),\n peakActive,\n effective: effectivePricing(current, peak, peakActive, new Date(now), schedule),\n ...(peakActive ? { band: isPeakHour(new Date(now), schedule) ? 'peak' : 'offPeak' } : {}),\n schedule,\n }\n } catch (error) {\n return {\n fetchedAt,\n currency,\n current: fallbackCurrent,\n peakActive: false,\n effective: fallbackCurrent,\n schedule: fallbackSchedule,\n error: error instanceof Error ? error.message : String(error)\n }\n }\n}\n\n/**\n * Whether the given instant is a peak-pricing hour under one schedule: the\n * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook\n * passes the schedule parsed from its own (zh or en) pricing page. When the\n * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's\n * timezone are off-peak all day.\n * @param now - the instant to classify.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns true during peak hours.\n */\nexport function isPeakHour(now: Date = new Date(), schedule: PeakSchedule = PEAK_SCHEDULE_ZH): boolean {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: schedule.timezone,\n hour: 'numeric',\n hour12: false,\n weekday: 'short',\n }).formatToParts(now)\n if (schedule.weekdaysOnly === true) {\n const weekday = parts.find(part => part.type === 'weekday')?.value\n if (weekday === 'Sat' || weekday === 'Sun') return false\n }\n const hour = Number(parts.find(part => part.type === 'hour')?.value)\n if (Number.isNaN(hour)) return false\n return schedule.ranges.some(([start, end]) => hour >= start && hour < end)\n}\n\n/**\n * The price bucket in effect for one model at the given moment: the\n * peak/off-peak band once the rollout is live (with the parsed table, or the\n * built-in fallback), otherwise the current official list.\n * @param current - the current list prices.\n * @param peak - the upcoming peak table, when parsed.\n * @param peakActive - whether the rollout has started.\n * @param now - the instant to price for.\n * @param model - `flash` or `pro`.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns the bucket to apply.\n */\nexport function effectiveBucket(\n current: CurrentPricing,\n peak: PeakPricing | undefined,\n peakActive: boolean,\n now: Date,\n model: 'flash' | 'pro',\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): PriceBucket {\n if (peakActive) {\n const table = peak?.[model] ?? FALLBACK_PEAK[model]\n return isPeakHour(now, schedule) ? table.peak : table.offPeak\n }\n return current[model]\n}\n\n/** Per-model effective prices at the given moment. */\nexport function effectivePricing(\n current: CurrentPricing,\n peak: PeakPricing | undefined,\n peakActive: boolean,\n now: Date,\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): CurrentPricing {\n return {\n flash: effectiveBucket(current, peak, peakActive, now, 'flash', schedule),\n pro: effectiveBucket(current, peak, peakActive, now, 'pro', schedule),\n }\n}","/**\n * Client-side price lookup and streaming-estimate helpers.\n *\n * The authoritative anchored ledger arrives through the `sessionCost`\n * projection; this module only mirrors the host pricebook math for display\n * and for the live estimate (which by nature uses CURRENT prices and is\n * labeled 估算/estimate). The lookup logic intentionally mirrors\n * `src/pricebook.ts` — keep the two in sync.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/client/cost-math\n */\n\nimport { isPeakHour, PEAK_PRICING_START_MS, PEAK_SCHEDULE_ZH } from '../pricing.ts'\nimport type {\n ModelPrice,\n PeakSchedule,\n PriceBand,\n PriceBucket,\n PricebookSnapshot,\n SessionCostStep,\n SessionCostTotals,\n SubagentCost,\n} from '../types.ts'\n\n/** Sum totals (this module's mirror of the host `sumTotals`). */\nexport function sumTotals(totals: readonly SessionCostTotals[]): SessionCostTotals {\n const sum: SessionCostTotals = {\n uncachedCost: 0,\n cacheReadCost: 0,\n outputCost: 0,\n cost: 0,\n pricedSteps: 0,\n unpricedSteps: 0,\n steps: 0,\n }\n for (const entry of totals) {\n sum.uncachedCost += entry.uncachedCost\n sum.cacheReadCost += entry.cacheReadCost\n sum.outputCost += entry.outputCost\n sum.cost += entry.cost\n sum.pricedSteps += entry.pricedSteps\n sum.unpricedSteps += entry.unpricedSteps\n sum.steps += entry.steps\n }\n return sum\n}\n\n/** Total spend of every subagent (CNY); 0 without subagents. */\nexport function subagentSpend(subagents: readonly SubagentCost[] | undefined): number {\n if (subagents === undefined || subagents.length === 0) return 0\n return sumTotals(subagents.map(entry => entry.totals)).cost\n}\n\n/**\n * Select the conversation's main ledger, the host-aggregated subagent rows,\n * and their combined totals together, so no surface has to remember which\n * pointer to compare against which total.\n *\n * All three are derived consistently: `combined` sums the main ledger with\n * EVERY subagent row the host served — including nested ones — and is defined\n * whenever either side has data. A conversation whose own ledger is not\n * materialized yet (a session switch mid-flight, or a host that serves no main\n * ledger) therefore still totals its subagents instead of rendering nothing.\n *\n * `main` is `null` when the main ledger is absent, in which case `mainSpend`\n * is unknown (the dock shows 0 for a session with no spend of its own).\n * @param main - the main session's projection totals, or undefined.\n * @param subagents - the host-aggregated subagent rows.\n * @returns the main readout, the subagent rows, combined totals, and both spend figures.\n */\nexport function costLookupFor(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): {\n main: SessionCostTotals | undefined\n subagents: readonly SubagentCost[]\n combined: SessionCostTotals | undefined\n mainSpend: number\n subagentTotal: number\n} {\n const rows = subagents ?? []\n const combined = combineTotals(main, rows)\n return {\n main,\n subagents: rows,\n combined,\n mainSpend: main?.cost ?? 0,\n subagentTotal: sumTotals(rows.map(entry => entry.totals)).cost,\n }\n}\n\n/**\n * The conversation-wide totals: the main session's ledger plus every\n * subagent's ledger (host-aggregated, all nesting levels). Undefined only when\n * neither side carries a ledger.\n * @param main - the main session's projection totals.\n * @param subagents - the host-aggregated subagent totals.\n * @returns the combined totals, or undefined without any ledger.\n */\nexport function combineTotals(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): SessionCostTotals | undefined {\n const rows = subagents ?? []\n if (main === undefined) {\n return rows.length === 0 ? undefined : sumTotals(rows.map(entry => entry.totals))\n }\n if (rows.length === 0) return main\n return sumTotals([main, ...rows.map(entry => entry.totals)])\n}\n\n/**\n * The price band in effect at one instant (mirror of the host helper). The\n * schedule defaults to the zh fallback; live readouts pass the schedule of\n * the pricebook they display (the zh and en pages may state different\n * windows).\n * @param time - epoch millis.\n * @param schedule - the peak-hour schedule to classify against.\n * @returns the band.\n */\nexport function bandForTime(time: number, schedule: PeakSchedule = PEAK_SCHEDULE_ZH): PriceBand {\n if (time < PEAK_PRICING_START_MS) return 'single'\n return isPeakHour(new Date(time), schedule) ? 'peak' : 'offPeak'\n}\n\n/** The bucket of one model's entry for one band (mirror of the host helper). */\nexport function bucketOf(modelPrice: ModelPrice, band: PriceBand): PriceBucket | undefined {\n if (band === 'single') return modelPrice.single ?? modelPrice.offPeak ?? modelPrice.peak\n if (band === 'peak') return modelPrice.peak ?? modelPrice.offPeak ?? modelPrice.single\n return modelPrice.offPeak ?? modelPrice.peak ?? modelPrice.single\n}\n\n/** Map a model id to its official pricing key (mirror of the host helper). */\nexport function pricingKeyOfModel(model: string): 'pro' | 'flash' {\n const lower = model.toLowerCase()\n return lower.includes('pro') ? 'pro' : 'flash'\n}\n\n/** Candidate canonical price keys for one model (mirror of the host helper). */\nexport function modelKeys(provider: string | undefined, model: string): string[] {\n const keys: string[] = []\n if (provider !== undefined && provider.length > 0) keys.push(`${provider}/${model}`)\n keys.push(model)\n const lower = model.toLowerCase()\n const deepseekish = lower.includes('deepseek')\n || (provider !== undefined && provider.toLowerCase().includes('deepseek'))\n if (deepseekish) keys.push(pricingKeyOfModel(model))\n return keys\n}\n\n/** Pick the snapshot effective at `time` (newest ≤ time, else earliest). */\nexport function snapshotAt(snapshots: readonly PricebookSnapshot[], time: number): PricebookSnapshot | undefined {\n let best: PricebookSnapshot | undefined\n for (const snapshot of snapshots) {\n // `>=` keeps the NEWER snapshot (later in the array) on an effectiveAt tie.\n if (snapshot.effectiveAt <= time && (best === undefined || snapshot.effectiveAt >= best.effectiveAt)) best = snapshot\n }\n return best ?? (snapshots.length > 0 ? snapshots[0] : undefined)\n}\n\n/** Resolve one model's price entry in a snapshot through the key chain. */\nexport function modelPriceOf(\n snapshot: PricebookSnapshot | null | undefined,\n provider: string | undefined,\n model: string,\n): ModelPrice | undefined {\n if (snapshot === undefined || snapshot === null) return undefined\n for (const key of modelKeys(provider, model)) {\n const entry = snapshot.prices[key]\n if (entry !== undefined) return entry\n }\n return undefined\n}\n\n/** Resolve one model's bucket at one instant from a snapshot. */\nexport function bucketAt(\n snapshot: PricebookSnapshot | undefined,\n provider: string | undefined,\n model: string,\n time: number,\n schedule: PeakSchedule = PEAK_SCHEDULE_ZH,\n): PriceBucket | undefined {\n const entry = modelPriceOf(snapshot, provider, model)\n if (entry === undefined) return undefined\n return bucketOf(entry, bandForTime(time, schedule))\n}\n\n/**\n * The peak-to-off-peak price multiplier for one model's entry, when the\n * entry carries both bands. The multiplier is the ratio of the summed peak\n * bucket rates to the summed off-peak bucket rates; official DeepSeek prices\n * scale all buckets by the same factor, so this is a stable “peak is N× the\n * off-peak price” display value.\n * @param snapshot - the pricebook snapshot to read.\n * @param provider - the provider route, when known.\n * @param model - the model id.\n * @returns the peak/off-peak multiplier, or null when either band is absent.\n */\nexport function peakOffPeakMultiplier(\n snapshot: PricebookSnapshot | null | undefined,\n provider: string | undefined,\n model: string,\n): number | null {\n const entry = modelPriceOf(snapshot, provider, model)\n if (entry === undefined) return null\n const peak = entry.peak\n const offPeak = entry.offPeak\n if (peak === undefined || offPeak === undefined) return null\n const offPeakTotal = offPeak.cacheReadPerMillion + offPeak.inputPerMillion + offPeak.outputPerMillion\n if (offPeakTotal === 0) return null\n const peakTotal = peak.cacheReadPerMillion + peak.inputPerMillion + peak.outputPerMillion\n return peakTotal / offPeakTotal\n}\n\n/** CJK characters (the /1.5 density class). */\nconst CJK_RE = /[\\u3400-\\u4dbf\\u4e00-\\u9fff]/g\n\n/**\n * Heuristic character→token estimate: CJK ÷ 1.5, everything else ÷ 4\n * (the streaming estimate's output side).\n * @param text - the accumulated text.\n * @returns the estimated token count.\n */\nexport function estimateTokens(text: string): number {\n const cjk = (text.match(CJK_RE) ?? []).length\n return Math.ceil(cjk / 1.5 + (text.length - cjk) / 4)\n}\n\n/**\n * Tokens of the streamed text/reasoning blocks of a partial assistant.\n * @param blocks - the partial's content blocks (text/reasoning carry `text`).\n * @returns the estimated token count.\n */\nexport function blocksOutputTokens(blocks: readonly { kind?: string; text?: string }[] | undefined): number {\n let tokens = 0\n for (const block of blocks ?? []) {\n if ((block.kind === 'text' || block.kind === 'reasoning') && typeof block.text === 'string' && block.text.length > 0) {\n tokens += estimateTokens(block.text)\n }\n }\n return tokens\n}\n\n/**\n * The cache-read ratio of the newest completed step (input side of the\n * estimate). Absent history conservatively prices all input as uncached\n * (upper bound).\n * @param steps - the projection's step ledger.\n * @returns the ratio in [0, 1].\n */\nexport function cacheReadRatioOf(steps: readonly SessionCostStep[]): number {\n for (let i = steps.length - 1; i >= 0; i -= 1) {\n const step = steps[i]\n const total = step.uncachedInputTokens + step.cacheReadTokens + step.cacheWriteTokens\n if (step.priced && total > 0) return step.cacheReadTokens / total\n }\n return 0\n}\n\n/**\n * The live estimate of the running reply: input at the projected context\n * size (weighted by the cache ratio) plus output at the streamed-token\n * heuristic, both at CURRENT prices. Always labeled 估算/estimate — it is\n * replaced by the projection's exact anchored value once the step settles.\n * @param bucket - the current price bucket of the session model.\n * @param inputTokens - the projected prompt size for the next request.\n * @param cachedRatio - the cache-read ratio from recent completed steps.\n * @param outputTokens - the streamed output tokens so far.\n * @returns the estimated CNY cost.\n */\nexport function estimateCost(\n bucket: PriceBucket,\n inputTokens: number,\n cachedRatio: number,\n outputTokens: number,\n): number {\n const inputCost = inputTokens / 1_000_000\n * (bucket.cacheReadPerMillion * cachedRatio + bucket.inputPerMillion * (1 - cachedRatio))\n const outputCost = outputTokens / 1_000_000 * bucket.outputPerMillion\n return inputCost + outputCost\n}\n","/**\n * Shared display formatting for amounts and timestamps.\n *\n * @module @gamegeek-saikel/dsh-cost-meter/client/format\n */\n\n/**\n * Common currency symbols; unknown currencies render as a bare code.\n * @param currency - ISO 4217 code.\n * @returns the display symbol.\n */\nexport function currencySymbol(currency: string): string {\n switch (currency) {\n case 'CNY':\n return '¥'\n case 'USD':\n return '$'\n case 'EUR':\n return '€'\n default:\n return `${currency} `\n }\n}\n\n/** Detect the display currency from the active locale dictionary. */\nexport function displayCurrency(t: (key: string, params?: Record<string, string>) => string): 'CNY' | 'USD' {\n return t('view.cost') === 'Cost' ? 'USD' : 'CNY'\n}\n\n/**\n * Compact money: two decimals above 1, four down to 0.01, six below.\n * Costs are CNY 元 with 4-decimal display precision; zero renders as 0.00.\n * @param value - the amount.\n * @returns the fixed-width display string.\n */\nexport function formatMoney(value: number): string {\n if (value === 0) return '0.00'\n if (value >= 1) return value.toFixed(2)\n if (value >= 0.01) return value.toFixed(4)\n return value.toFixed(6)\n}\n\n/** Time formatting for snapshot stamps and step rows. */\nexport function formatTime(epochMs: number): string {\n return new Date(epochMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })\n}\n\n/**\n * Compact price multiplier, e.g. `2.0×` or `0.5×`.\n * @param multiplier - the ratio to display.\n * @returns the fixed one-decimal multiplier string.\n */\nexport function formatMultiplier(multiplier: number): string {\n return `${multiplier.toFixed(1)}×`\n}\n/**\n * Compact percentage, e.g. `50%`.\n * @param fraction - the ratio in [0, 1].\n * @returns the whole-percent string.\n */\nexport function formatPercent(fraction: number): string {\n return `${Math.round(fraction * 100)}%`\n}\n","/**\n * Composer-dock readout: the current session's anchored spend (the\n * `sessionCost` projection — price-snapshot-anchored, never recomputed at\n * current prices) plus the DeepSeek account balance (host `/cost-meter` route),\n * refreshed every minute.\n *\n * Presentation copies the shipped dock readout (`StatsPills` in ui-chat): a\n * single capsule holding an icon plus a label whose parts are joined by the\n * shipped `·` separator. Clicking it opens the product's stat-dialog panel\n * shape — title with the figure, rule, then a `<dl>` of labelled rows — which\n * carries the billed categories, the subagent rows, the anchored price snapshot\n * and the balance detail that the capsule has no room for.\n *\n * The component is deliberately defensive: every derived string is computed\n * inside a guarded block so a single bad field can never take down the whole\n * dock entry (the slot system retires crashed entries).\n */\nimport { Fragment, memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconGaugeOutlineRegular,\n useAnchoredPosition,\n useDismissOnOutsidePointer,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { BalanceSnapshot, ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { costLookupFor } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatTime } from './format.ts'\nimport css from './SessionCostLine.module.css'\n\n/** Host route serving the balance snapshot and the pricebook view. */\nexport const ENDPOINT = '/cost-meter'\n/** Balance refresh interval in milliseconds. */\nexport const REFRESH_MS = 60_000\n/** Distance between the capsule and its detail panel, in pixels. */\nconst PANEL_GAP = 8\n/** Minimum distance the detail panel keeps from the viewport edge, in pixels. */\nconst PANEL_MARGIN = 12\n/** Pre-measurement placement: mounted but invisible, so the first frame can size it. */\nconst MEASURE_STYLE = { visibility: 'hidden', left: 0, top: 0 } as const\n\n/** Session-cost namespace key domain for the locale seat. */\nexport type CostLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostLineProps {\n useProjection: UseProjection\n sessionId: string\n t: CostLocale\n}\n\n/** Compose the tooltip's balance detail from an ok snapshot. */\nfunction balanceTooltip(balance: Extract<BalanceSnapshot, { ok: true }>, t: CostLocale): string {\n if (balance.balance === null) return t('balance.failed')\n const symbol = currencySymbol(balance.balance.currency)\n const amount = `${symbol}${formatMoney(balance.balance.total)}`\n const granted = `${symbol}${formatMoney(balance.balance.granted)}`\n const toppedUp = `${symbol}${formatMoney(balance.balance.toppedUp)}`\n const detail = t('balance.detail', { amount, granted, toppedUp })\n return balance.isAvailable ? detail : `${detail} · ${t('balance.suspended')}`\n}\n\n/** The tooltip's cost detail rows from the anchored projection. Never throws. */\nexport function costDetail(cost: SessionCostProjection | undefined, model: string | undefined, t: CostLocale, currency: 'CNY' | 'USD' = 'CNY'): string[] {\n const rows: string[] = []\n try {\n if (cost === undefined) return rows\n const symbol = currencySymbol(currency)\n const totals = cost.totals\n if (totals !== undefined && totals.pricedSteps > 0) {\n rows.push(t('cost.label', { amount: `${symbol}${formatMoney(totals.cost)}` }))\n rows.push(t('view.uncached') + ` ${symbol}${formatMoney(totals.uncachedCost)}`)\n rows.push(t('view.cacheRead') + ` ${symbol}${formatMoney(totals.cacheReadCost)}`)\n rows.push(t('view.output') + ` ${symbol}${formatMoney(totals.outputCost)}`)\n }\n if (model !== undefined) rows.push(t('step.model', { model }))\n const steps = cost.steps\n if (steps !== undefined && steps.length > 0) {\n const last = steps[steps.length - 1]\n if (last !== undefined && last.snapshotVersion !== null) {\n rows.push(t('pill.detail.snapshot', { time: formatTime(last.snapshotEffectiveAt ?? last.time) }))\n }\n }\n if (totals !== undefined && totals.unpricedSteps > 0) {\n rows.push(t('view.unpriced', { count: String(totals.unpricedSteps) }))\n }\n } catch {\n /* a malformed field must never take down the dock entry */\n }\n return rows\n}\n\nexport const SessionCostLine = memo(function SessionCostLine({ useProjection, sessionId, t }: SessionCostLineProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const [failed, setFailed] = useState(false)\n const [open, setOpen] = useState(false)\n const rootRef = useRef<HTMLSpanElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n const pos = useAnchoredPosition({ open, anchorRef: rootRef, panelRef, side: 'top', gap: PANEL_GAP, margin: PANEL_MARGIN })\n useDismissOnOutsidePointer(rootRef, open, setOpen, panelRef)\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [open])\n useEffect(() => {\n let alive = true\n let timer: number | undefined\n const endpoint = `${ENDPOINT}?session=${encodeURIComponent(sessionId)}&currency=${currency}`\n const load = (): void => {\n void fetch(endpoint, { cache: 'no-store' })\n .then((res) => {\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n return res.json() as Promise<ConversationCostResponse>\n })\n .then((data) => {\n if (!alive) return\n setResponse(data)\n setFailed(false)\n })\n .catch(() => {\n if (alive) setFailed(true)\n })\n }\n load()\n timer = window.setInterval(load, REFRESH_MS)\n return () => {\n alive = false\n if (timer !== undefined) window.clearInterval(timer)\n }\n }, [sessionId, currency])\n\n const balance = response?.balance\n const balanceEnabled = response?.pricebook?.balanceEnabled !== false\n // The dock line totals the WHOLE conversation: the main ledger plus every\n // subagent row the host served, nested levels included.\n const lookup = costLookupFor(cost?.totals, response?.subagents)\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n // The dock readout must never vanish on a session switch: switching\n // sessions re-binds the projection hook, and until the new session's\n // baseline has carried the sessionCost key useProjection returns\n // undefined (capability absent - never a usable signal). Treat a\n // missing ledger as a zero ledger so the row renders CNY 0.00\n // instead of disappearing while the baseline is in flight.\n const mainSpend = lookup.mainSpend\n\n // One capsule, as terse as the shipped stats capsules: the WHOLE\n // conversation's anchored spend — the root ledger plus every subagent row the\n // host served, at every nesting level — and the account balance, joined by the\n // shipped separator. The panel below decomposes that same figure.\n const symbol = currencySymbol(currency)\n const money = (value: number): string => `${symbol}${formatMoney(value)}`\n const totalSpend = combined?.cost ?? mainSpend + subSpend\n let label: ReactNode = null\n let balanceValue: string | null = null\n try {\n const okBalance = balance?.ok === true ? balance : null\n const balanceText = !balanceEnabled\n ? null\n : okBalance !== null && okBalance.balance !== null\n ? t('balance.label', { amount: `${currencySymbol(okBalance.balance.currency)}${formatMoney(okBalance.balance.total)}` })\n : failed ? t('balance.failed') : null\n if (balanceText !== null) balanceValue = balanceText\n label = (\n <>\n {t('cost.label', { amount: money(totalSpend) })}\n {balanceText !== null && (\n <>\n <span className={css.sep} aria-hidden>·</span>\n {balanceText}\n </>\n )}\n </>\n )\n } catch {\n /* keep the capsule alive */\n }\n if (label === null) return null\n\n const model = cost?.model?.model\n const subagents = lookup.subagents\n const pricebook = response?.pricebook\n const snapshot = pricebook?.current ?? null\n const lastStep = cost?.steps !== undefined && cost.steps.length > 0 ? cost.steps[cost.steps.length - 1] : undefined\n // The panel is the shipped stat-dialog shape: `<dt>` label, `<dd>` value. The\n // headline restates the capsule; delegated spend is then split out as a\n // breakdown of that same total (never an addition to it), followed by the\n // billed categories, the per-subagent rows, and the price provenance.\n const detailRows = useMemo(() => {\n const rows: { key: string; label: string; value: string }[] = []\n const row = (key: string, rowLabel: string, value: string | null | undefined): void => {\n if (value === null || value === undefined || value === '') return\n rows.push({ key, label: rowLabel, value })\n }\n try {\n row('session', t('dock.row.session'), money(totalSpend))\n if (subSpend > 0) {\n row('subagent', t('dock.row.subagent'), money(subSpend))\n row('own', t('dock.row.own'), money(totalSpend - subSpend))\n }\n if (combined !== undefined && combined.pricedSteps > 0) {\n row('uncached', t('dock.row.uncached'), money(combined.uncachedCost))\n row('cacheRead', t('dock.row.cacheRead'), money(combined.cacheReadCost))\n row('output', t('dock.row.output'), money(combined.outputCost))\n }\n // One row per subagent, depth-tagged: the same tree the Cost tab lists.\n for (const subagent of subagents) {\n const name = subagent.depth > 1\n ? `${subagent.sessionId.slice(0, 8)} (${t('subagent.depth', { depth: String(subagent.depth) })})`\n : subagent.sessionId.slice(0, 8)\n row(`agent:${subagent.sessionId}`, name, money(subagent.totals?.cost ?? 0))\n }\n row('balance', t('dock.row.balance'), balanceValue)\n row('model', t('dock.row.model'), model ?? null)\n row('snapshot', t('dock.row.snapshot'), lastStep !== undefined && lastStep.snapshotVersion !== null\n ? `${formatTime(lastStep.snapshotEffectiveAt ?? lastStep.time)}${lastStep.band === null ? '' : ` · ${t(`band.${lastStep.band}` as 'band.single')}`}`\n : null)\n if (snapshot !== null && (snapshot.source === 'official' || snapshot.source === 'fallback' || snapshot.source === 'manual' || snapshot.source === 'openrouter')) {\n row('source', t('dock.row.source'), t(`source.${snapshot.source}` as 'source.manual'))\n }\n if (combined !== undefined && combined.unpricedSteps > 0) row('unpriced', t('dock.row.unpriced'), String(combined.unpricedSteps))\n row('updated', t('dock.row.updated'), response === null ? null : formatTime(balance?.ok === true ? balance.fetchedAt : Date.now()))\n } catch {\n /* keep the panel alive */\n }\n return rows\n }, [combined, totalSpend, subSpend, subagents, balanceValue, balance, model, lastStep, snapshot, response, money, t])\n\n return (\n <span className={css.anchor} ref={rootRef} data-testid=\"session-cost-line\" data-composer-cost>\n <button\n type=\"button\"\n className={css.pill}\n data-cost-pill=\"total\"\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-label={t('dock.detail.title')}\n onClick={() => setOpen(prev => !prev)}\n >\n <IconGaugeOutlineRegular />\n <span className={css.label}>{label}</span>\n </button>\n {open && createPortal(\n <div\n ref={panelRef}\n className={css.panel}\n role=\"dialog\"\n aria-label={t('dock.detail.title')}\n data-testid=\"session-cost-detail\"\n style={pos ?? MEASURE_STYLE}\n >\n <div className={css.title}>\n <span className={css.titleLabel}><IconGaugeOutlineRegular />{t('dock.detail.title')}</span>\n <span className={css.titleValue}>{money(totalSpend)}</span>\n </div>\n <div className={css.titleRule} aria-hidden />\n <dl className={css.details} data-session-cost-details>\n {detailRows.map(entry => (\n <Fragment key={entry.key}>\n <dt>{entry.label}</dt>\n <dd>{entry.value}</dd>\n </Fragment>\n ))}\n </dl>\n </div>,\n document.body,\n )}\n </span>\n )\n})\n","/**\n * Cost view tab: the whole conversation's anchored cost ledger — the total\n * (main session + subagent sessions, host-aggregated), the three billed\n * categories (uncached input incl. cache writes, cache reads, output), a\n * per-subagent breakdown, and the per-reply list (turn, time, model, token\n * split, step cost, anchored snapshot version and effective time). Reads the\n * `sessionCost` projection plus the host-aggregated subagent totals; nothing\n * is recomputed at current prices. Each row's band rides the shipped `Tag`\n * capsule (11px/17px, `success` off-peak / `danger` peak), the same tag\n * geometry every other tag control in the product uses.\n */\nimport { memo, useEffect, useState } from 'react'\nimport { Tag } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, costLookupFor, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport css from './CostView.module.css'\n\nexport type CostViewLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface CostViewProps {\n useProjection: UseProjection\n sessionId: string\n t: CostViewLocale\n}\n\n/** Map an unpriced reason to its localized copy. */\nfunction reasonText(step: SessionCostStep, t: CostViewLocale): string {\n return t(`reason.${step.unpricedReason ?? 'NO_PRICE'}` as 'reason.NO_PRICE')\n}\n\n/**\n * Indent a subagent row by its root-relative depth, so the nested delegation\n * chain reads as a chain. Depth `-1` (`0`) marks a row the runtime fallback\n * discovered, which states no distance: it keeps the top-level inset.\n */\nfunction subagentIndent(depth: number): { paddingLeft: string } {\n return { paddingLeft: `${Math.max(0, depth - 1) * 14}px` }\n}\n\nexport const CostView = memo(function CostView({ useProjection, sessionId, t }: CostViewProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const symbol = currencySymbol(currency)\n\n useEffect(() => {\n let alive = true\n void fetch(`/cost-meter?session=${encodeURIComponent(sessionId)}&currency=${currency}`, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the tab keeps the last good aggregation */ })\n return () => {\n alive = false\n }\n }, [sessionId, currency])\n\n const rows = cost?.steps ?? []\n const lookup = costLookupFor(cost?.totals, response?.subagents)\n const subagents = lookup.subagents\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n const money = (value: number | null): string => value === null ? '—' : `${symbol}${formatMoney(value)}`\n\n // A conversation whose OWN ledger is not materialized yet but whose\n // subagents spent money is not \"no cost data\": the subagent section below\n // still has something true to say.\n if (combined === undefined) {\n return <div className={css.root} data-testid=\"cost-view-empty\">{t('view.empty')}</div>\n }\n const totals = combined\n\n return (\n <div className={css.root} data-testid=\"cost-view\">\n <section className={css.summary}>\n <div className={css.totalRow}>\n <span className={css.totalLabel}>{t('view.total')}</span>\n <span className={css.totalValue}>{money(totals.cost)}</span>\n </div>\n <div className={css.categories}>\n <span>{t('view.uncached')}: {money(totals.uncachedCost)}</span>\n <span>{t('view.cacheRead')}: {money(totals.cacheReadCost)}</span>\n <span>{t('view.output')}: {money(totals.outputCost)}</span>\n </div>\n {totals.unpricedSteps > 0 && (\n <div className={css.unpricedNote}>{t('view.unpriced', { count: String(totals.unpricedSteps) })}</div>\n )}\n {subagents.length > 0 && (\n <section className={css.subagents} data-testid=\"cost-view-subagents\">\n <div className={css.unpricedNote}>{t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` })}</div>\n <ol className={css.stepList}>\n {subagents.map(sub => (\n <li key={sub.sessionId} className={css.step} data-testid=\"cost-subagent\" data-depth={String(sub.depth)}>\n <div className={css.stepHead}>\n <span className={css.stepTurn} style={subagentIndent(sub.depth)}>{sub.sessionId.slice(0, 8)}</span>\n {sub.depth > 1 && (\n <span className={css.stepModel}>{t('subagent.depth', { depth: String(sub.depth) })}</span>\n )}\n {sub.label !== undefined && <span className={css.stepModel}>{sub.label}</span>}\n <span className={css.stepCost}>{money(sub.totals?.cost ?? null)}</span>\n </div>\n <div className={css.stepBody}>\n <span>{t('view.uncached')}: {money(sub.totals?.uncachedCost ?? null)}</span>\n <span>{t('view.cacheRead')}: {money(sub.totals?.cacheReadCost ?? null)}</span>\n <span>{t('view.output')}: {money(sub.totals?.outputCost ?? null)}</span>\n </div>\n </li>\n ))}\n </ol>\n </section>\n )}\n </section>\n\n <section className={css.steps}>\n <h3 className={css.stepsTitle}>{t('view.steps')}</h3>\n <ol className={css.stepList}>\n {rows.map((step) => {\n // The band is anchored to the ROUND's own time: the ledger's\n // `band` was fixed at fold time from the usage event's time (the\n // host schedule of that moment), never recomputed against the\n // clock now — so an old reply keeps the color it was billed at.\n const stepBand = step.band ?? bandForTime(step.time)\n const stepRatio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, step.provider, step.model)\n const stepBandLabel = stepBand === 'peak'\n ? stepRatio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(stepRatio) })\n : stepBand === 'offPeak'\n ? stepRatio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / stepRatio) })\n : null\n return (\n <li key={`${step.turn}:${step.step}`} className={css.step} data-testid=\"cost-step\" data-band={stepBand === 'single' ? undefined : stepBand}>\n <div className={css.stepHead}>\n <span className={css.stepTurn}>{t('step.turn', { turn: String(step.turn) })}</span>\n <span className={css.stepModel}>{t('step.model', { model: step.model })}</span>\n <span className={css.stepTime}>{formatTime(step.time)}</span>\n <span className={css.stepCost}>\n {money(step.cost)}\n {stepBandLabel !== null && <Tag tone={stepBand === 'peak' ? 'danger' : 'success'}>{stepBandLabel}</Tag>}\n </span>\n </div>\n <div className={css.stepBody}>\n <span className={css.stepTokens}>\n {t('step.tokens', {\n uncached: String(step.uncachedInputTokens),\n read: String(step.cacheReadTokens),\n write: String(step.cacheWriteTokens),\n output: String(step.outputTokens),\n })}\n </span>\n <span className={css.stepMeta}>\n {step.priced && step.snapshotVersion !== null\n ? t('step.snapshot', {\n time: formatTime(step.snapshotEffectiveAt ?? step.time),\n })\n : t('step.unpriced', { reason: reasonText(step, t) })}\n </span>\n </div>\n </li>\n );\n })}\n </ol>\n </section>\n </div>\n )\n})\n","/**\n * Per-reply cost, rendered in the completed Turn's action row. Contributed\n * through the `conversation.chat.assistant-actions` list slot, whose entries\n * DSH 0.1.5 draws in the row's action area (after the copy action, ahead of the\n * branch button); a flex `order` then seats the capsule after the shipped 用量\n * and 用时 capsules and before the row's timestamp, closing the stat run with\n * the row's own 8px gaps between neighbours.\n *\n * The control is the row's own stat-capsule shape — the same trigger + portaled\n * dialog the shipped 用量 and 用时 capsules use (28px pill, 13px/24px tertiary\n * label, hover fill, an `aria-expanded` dialog anchored above the trigger\n * through `useAnchoredPosition` + `useDismissOnOutsidePointer`). Clicking it\n * opens the reply's anchored breakdown: the three billed categories, the band\n * that priced it, the model, and the snapshot version.\n *\n * The price itself is the band-coloured label (green off-peak, red peak,\n * neutral before the rollout), so the row carries the status without a second\n * tag capsule. The slot hands over only the finalized message id, so the\n * `sessionCostIndex` projection resolves it to the (turn, step) coordinates the\n * anchored ledger is keyed by; the Turn's own steps are then summed. Every\n * price is the anchored snapshot value, never a current-price recompute; an\n * unpriced Turn renders `—` (the Cost tab explains why).\n */\nimport { memo, useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconGaugeOutlineRegular,\n useAnchoredPosition,\n useDismissOnOutsidePointer,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport css from './AssistantCostChip.module.css'\n\nexport type ChipLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface AssistantCostChipProps {\n /** Durable id of the finalized assistant message owning this action row. */\n messageId: string\n useProjection: UseProjection\n t: ChipLocale\n}\n\n/** Distance kept between the trigger and its dialog (the shipped stat panels' own gap). */\nconst PANEL_GAP = 8\n/** Distance kept between the dialog and the viewport edges. */\nconst PANEL_MARGIN = 12\n/** Pre-measurement placement: mounted but invisible, so the first frame can size it. */\nconst MEASURE_STYLE = { visibility: 'hidden', left: 0, top: 0 } as const\n\n/** The projection step for one (turn, step) coordinate, if the ledger has it. */\nexport function stepOf(\n steps: readonly SessionCostStep[] | undefined,\n turn: number,\n step: number,\n): SessionCostStep | undefined {\n return steps?.find(entry => entry.turn === turn && entry.step === step)\n}\n\n/** The Turn's own ledger steps, in fold order. */\nexport function stepsOfTurn(\n steps: readonly SessionCostStep[] | undefined,\n turn: number,\n): SessionCostStep[] {\n return steps?.filter(entry => entry.turn === turn) ?? []\n}\n\nexport const AssistantCostChip = memo(function AssistantCostChip({ messageId, useProjection, t }: AssistantCostChipProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const index = useProjection('sessionCostIndex')\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const [open, setOpen] = useState(false)\n const rootRef = useRef<HTMLSpanElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n const pos = useAnchoredPosition({ open, anchorRef: rootRef, panelRef, side: 'top', gap: PANEL_GAP, margin: PANEL_MARGIN })\n useDismissOnOutsidePointer(rootRef, open, setOpen, panelRef)\n\n useEffect(() => {\n let alive = true\n void fetch(`/cost-meter?currency=${currency}`, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the chip keeps the last good pricebook */ })\n return () => {\n alive = false\n }\n }, [currency])\n\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [open])\n\n // The action row carries the message id; the index carries its coordinates.\n const coordinates = index?.steps[messageId]\n const steps = coordinates === undefined ? [] : stepsOfTurn(cost?.steps, coordinates.turn)\n const priced = steps.filter(entry => entry.cost !== null)\n if (steps.length === 0) return null\n if (priced.length === 0) {\n // Unpriced turn: show the dash; the native title explains why.\n const reason = steps[0]?.unpricedReason === 'NO_MODEL' ? t('reason.NO_MODEL') : t('reason.NO_PRICE')\n return (\n <span className={css.unpriced} data-cost-chip title={reason} data-testid=\"cost-chip-unpriced\">{t('chip.unpriced')}</span>\n )\n }\n\n const symbol = currencySymbol(currency)\n const money = (value: number): string => `${symbol}${formatMoney(value)}`\n const amount = money(priced.reduce((sum, entry) => sum + (entry.cost ?? 0), 0))\n // Anchored to the Turn's own (last) step: the ledger's `band` was fixed at\n // fold time from the usage event's time, so the capsule is coloured by the\n // band that actually priced this reply — not the band of the clock now.\n const last = priced[priced.length - 1]\n if (last === undefined) return null\n const band = last.band ?? bandForTime(last.time)\n const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, last.provider, last.model)\n const bandLabel = band === 'peak'\n ? ratio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(ratio) })\n : band === 'offPeak'\n ? ratio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / ratio) })\n : null\n\n return (\n <span\n className={css.root}\n ref={rootRef}\n data-cost-chip\n data-band={band === 'single' ? undefined : band}\n data-testid=\"cost-chip\"\n >\n <button\n type=\"button\"\n className={css.trigger}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen(prev => !prev)}\n >\n <IconGaugeOutlineRegular />\n <span className={css.label}>{amount}</span>\n </button>\n {open && createPortal(\n <div\n ref={panelRef}\n className={css.panel}\n role=\"dialog\"\n aria-label={t('chip.detail.title')}\n style={pos ?? MEASURE_STYLE}\n data-testid=\"cost-chip-detail\"\n >\n <div className={css.title}>\n <span className={css.titleLabel}><IconGaugeOutlineRegular />{t('chip.detail.title')}</span>\n <span className={css.titleValue}>{amount}</span>\n </div>\n <div className={css.titleRule} aria-hidden />\n <dl className={css.details}>\n <dt>{t('view.uncached')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.uncachedCost, 0))}</dd>\n <dt>{t('view.cacheRead')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.cacheReadCost, 0))}</dd>\n <dt>{t('view.output')}</dt>\n <dd>{money(priced.reduce((sum, entry) => sum + entry.outputCost, 0))}</dd>\n <dt>{t('chip.detail.band')}</dt>\n <dd>{bandLabel ?? t('band.single')}</dd>\n <dt>{t('chip.detail.model')}</dt>\n <dd>{last.model}</dd>\n {last.snapshotVersion !== null && (\n <>\n <dt>{t('chip.detail.snapshot')}</dt>\n <dd>{t('step.snapshot', { version: String(last.snapshotVersion), time: formatTime(last.snapshotEffectiveAt ?? last.time) })}</dd>\n </>\n )}\n </dl>\n </div>,\n document.body,\n )}\n </span>\n )\n})\n","/**\n * Session header pill (right-aligned utility): the conversation's anchored\n * total cost, or — while the assistant is replying — a live estimate of the\n * TOTAL once the running reply settles (`预计 ¥x.xx(进行中)`): the already\n * anchored ledger total plus the running reply's estimated input/output.\n * Clicking the pill toggles a detail panel: totals by category, model, and\n * the anchored price snapshot; the panel dismisses on outside clicks or\n * Escape.\n *\n * The estimate is explicitly labeled 估算/estimate: input is priced at the\n * projected context size weighted by the newest completed step's cache\n * ratio (conservative all-uncached without history), output at the\n * character heuristic — both at CURRENT snapshot prices.\n *\n * The band rides the shipped `Tag` capsule (11px/17px, `success` off-peak /\n * `danger` peak) so the header reads the same tag geometry as every other tag\n * control in the product.\n */\nimport { memo, useEffect, useMemo, useRef, useState, type RefObject } from 'react'\nimport { Tag } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only: merges the contextPressure key into SessionProjectionMap.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport type { ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { bandForTime, blocksOutputTokens, bucketAt, cacheReadRatioOf, costLookupFor, estimateCost, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier, formatTime } from './format.ts'\nimport { ENDPOINT, REFRESH_MS } from './SessionCostLine.tsx'\nimport css from './SessionCostPill.module.css'\n\nexport type PillLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostPillProps {\n useSession: SnapshotSelectorHook<SessionSnapshot>\n useProjection: UseProjection\n sessionId: string\n t: PillLocale\n}\n\n/** Read the session's running flag (the pill's estimate gate). */\nfunction runningOf(snapshot: SessionSnapshot): boolean {\n return snapshot.running\n}\n\n/** Close the popover on outside clicks or Escape (product detail-panel posture). */\nfunction useDismissOutside(open: boolean, onDismiss: () => void, anchor: RefObject<HTMLElement | null>): void {\n useEffect(() => {\n if (!open) return\n const onPointerDown = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null\n if (anchor.current !== null && target !== null && !anchor.current.contains(target)) onDismiss()\n }\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') onDismiss()\n }\n document.addEventListener('mousedown', onPointerDown)\n document.addEventListener('touchstart', onPointerDown)\n document.addEventListener('keydown', onKeyDown)\n return () => {\n document.removeEventListener('mousedown', onPointerDown)\n document.removeEventListener('touchstart', onPointerDown)\n document.removeEventListener('keydown', onKeyDown)\n }\n }, [open, onDismiss, anchor])\n}\n\nexport const SessionCostPill = memo(function SessionCostPill({ useSession, useProjection, sessionId, t }: SessionCostPillProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const pressure = useProjection('contextPressure')\n const running = useSession(runningOf)\n // DSH 0.1.5 no longer exposes the streaming partial message to plugins\n // (streams are folded into the durable assistant/message event); the live\n // estimate therefore projects the running reply's input side only and\n // leaves its output unestimated.\n const partial = null\n const [open, setOpen] = useState(false)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n const wrapRef = useRef<HTMLDivElement | null>(null)\n useDismissOutside(open, () => setOpen(false), wrapRef)\n\n useEffect(() => {\n let alive = true\n let timer: number | undefined\n const endpoint = `${ENDPOINT}?session=${encodeURIComponent(sessionId)}&currency=${currency}`\n const load = (): void => {\n void fetch(endpoint, { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* the pill keeps the last good pricebook */ })\n }\n load()\n timer = window.setInterval(load, REFRESH_MS)\n return () => {\n alive = false\n if (timer !== undefined) window.clearInterval(timer)\n }\n }, [sessionId, currency])\n\n const projection = cost\n const model = projection?.model ?? null\n const snapshot = response?.pricebook.current ?? null\n const schedule = response?.pricebook.schedule\n // The pill reads the WHOLE conversation: the main ledger plus every subagent\n // row the host served (all nesting levels), so a nested delegation is folded\n // into the same number the dock line and the Cost tab show.\n const lookup = costLookupFor(projection?.totals, response?.subagents)\n const combined = lookup.combined\n const subSpend = lookup.subagentTotal\n const symbol = currencySymbol(currency)\n\n // The estimate projects the TOTAL once the running reply settles: the\n // already-anchored ledger total (main + subagents) plus this reply's\n // estimated input/output (subagent replies cannot be projected live).\n const estimate = useMemo(() => {\n if (!running || projection === undefined) return null\n const bucket = bucketAt(snapshot ?? undefined, model?.provider, model?.model ?? 'unknown', Date.now(), schedule)\n if (bucket === undefined) return null\n const inputTokens = pressure?.projectedTokens ?? pressure?.pressureTokens ?? 0\n const cachedRatio = cacheReadRatioOf(projection.steps)\n const outputTokens = 0\n const stepEstimate = estimateCost(bucket, inputTokens, cachedRatio, outputTokens)\n return (combined?.cost ?? 0) + stepEstimate\n }, [running, projection, combined, snapshot, schedule, model, pressure, partial, currency])\n\n // Absent main ledger + present subagent rows still renders: the pill exists\n // to answer \"what did this conversation cost\", and a conversation whose own\n // steps are not materialized yet still spent its subagents' money.\n if (combined === undefined && estimate === null) return null\n\n const total = combined?.cost ?? 0\n const label = estimate !== null\n ? t('pill.estimate', { amount: `${symbol}${formatMoney(estimate)}` })\n : t('pill.label', { amount: `${symbol}${formatMoney(total)}` })\n\n const last = projection?.steps.length !== undefined && projection.steps.length > 0\n ? projection.steps[projection.steps.length - 1]\n : undefined\n\n const sourceKey = snapshot?.source ?? null\n const source = sourceKey === null ? t('source.none') : t(`source.${sourceKey}` as 'source.manual')\n\n // The pill's band is a LIVE readout: the price regime in effect RIGHT NOW,\n // classified against the schedule of the pricebook being displayed (the zh\n // and en pages may state different windows). The per-reply chip and the\n // cost tab, by contrast, use each step's ANCHORED band.\n const band = bandForTime(Date.now(), schedule)\n const peakRatio = peakOffPeakMultiplier(snapshot ?? undefined, model?.provider, model?.model ?? 'unknown')\n const bandLabel = band === 'peak'\n ? peakRatio === null ? t('band.peak') : t('price.peakRatio', { multiplier: formatMultiplier(peakRatio) })\n : band === 'offPeak'\n ? peakRatio === null ? t('band.offPeak') : t('price.offPeakRatio', { multiplier: formatMultiplier(1 / peakRatio) })\n : null\n\n return (\n <div className={css.wrap} ref={wrapRef}>\n <button\n type=\"button\"\n className={css.pill}\n data-testid=\"cost-pill\"\n data-running={estimate !== null}\n data-band={band === 'single' ? undefined : band}\n onClick={() => setOpen(prev => !prev)}\n aria-expanded={open}\n >\n {label}\n {bandLabel !== null && <Tag tone={band === 'peak' ? 'danger' : 'success'}>{bandLabel}</Tag>}\n {estimate !== null && <span className={css.estimateNote}>· {t('pill.estimateNote')}</span>}\n </button>\n {open && (\n <div className={css.detail} data-testid=\"cost-pill-detail\">\n <div className={css.detailRow + ' ' + css.detailTotal}>{t('pill.detail.total', { amount: `${symbol}${formatMoney(total)}` })}</div>\n {bandLabel !== null && (\n <div className={css.detailRow + ' ' + css.detailRowSecondary} data-testid=\"cost-pill-detail-band\">{bandLabel}</div>\n )}\n {combined !== undefined && combined.pricedSteps > 0 && (\n <>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.uncached')}: {symbol}{formatMoney(combined.uncachedCost)}</div>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.cacheRead')}: {symbol}{formatMoney(combined.cacheReadCost)}</div>\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('view.output')}: {symbol}{formatMoney(combined.outputCost)}</div>\n </>\n )}\n {subSpend > 0 && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` })}</div>\n )}\n {model !== null && (\n <div className={css.detailRow + ' ' + css.detailRowSecondary}>{t('pill.detail.model', { model: model.model })}</div>\n )}\n {last !== undefined && last.snapshotVersion !== null && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>\n {t('pill.detail.snapshot', { time: formatTime(last.snapshotEffectiveAt ?? last.time) })}\n {last.band !== null && ` · ${t(`band.${last.band}` as 'band.single')}`}\n </div>\n )}\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('pill.detail.source', { source })}</div>\n {projection !== undefined && projection.totals.unpricedSteps > 0 && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('view.unpriced', { count: String(projection.totals.unpricedSteps) })}</div>\n )}\n {estimate !== null && (\n <div className={css.detailRow + ' ' + css.detailRowMuted}>{t('pill.estimateNote')}</div>\n )}\n </div>\n )}\n </div>\n )\n})\n","/**\n * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 renders a\n * row's own configuration through the Plugins page's `plugins.row.config`\n * slot, which dispatches the contribution twice: `view: 'summary'` supplies\n * the row's one-liner, and `view: 'page'` is the body of the page the row's\n * configure control opens — the page itself draws the title, icon, and crumb.\n *\n * The page owner hands the form seat in `form`: `state` is the accepted,\n * schema-resolved value of this plugin's Loader entry with the revision that\n * fences the next write, and `mutate` submits path edits against that\n * revision. There is no settings scope to bind any more; writes ride the same\n * profile-patch path the generated forms use, so the card stages, saves, and\n * persists like every other plugin configuration. The JSON textareas carry the\n * two structured fields (per-model price overrides, OpenRouter aliases); the\n * refresh button is a plain action against the plugin's own host route.\n */\nimport { memo, useEffect, useState } from 'react'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConfigPageForm } from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ConversationCostResponse } from '../types.ts'\nimport { formatTime } from './format.ts'\nimport css from './CostPluginCard.module.css'\n\nexport type PluginCardLocale = PropsLocale<'cost-meter'>['t']\n\n/**\n * One path-addressed edit the configuration page submits. Derived from the\n * form contract rather than imported, so the card depends on the page's own\n * seat and not on a wire package it never calls.\n */\ntype ConfigOp = Parameters<ConfigPageForm['mutate']>[0][number]\n\nexport interface CostPluginCardProps {\n /** Which view the Plugins page asks for: the row's one-liner or the page body. */\n view: 'summary' | 'page'\n /** Host-owned values and write actions; the page omits it outside its own row page. */\n form?: ConfigPageForm | undefined\n t: PluginCardLocale\n}\n\n/** One staged field group the card writes through the form. */\ninterface Staged {\n overridesText: string\n aliasesText: string\n cacheReadDiscount: string\n fxMode: 'auto' | 'manual'\n manualRate: string\n balanceEnabled: boolean\n openRouterEnabled: boolean\n}\n\ninterface SectionShape {\n overrides?: Record<string, unknown>\n aliases?: Record<string, string>\n cacheReadDiscount?: number\n fxMode?: 'auto' | 'manual'\n manualRate?: number\n balanceEnabled?: boolean\n openRouterEnabled?: boolean\n}\n\nconst json = (value: unknown): string => JSON.stringify(value ?? {}, null, 2)\n/** The section's set of user-overridden fields, read from the raw user layer. */\nconst overridden = (user: unknown): Record<string, unknown> =>\n typeof user === 'object' && user !== null && !Array.isArray(user) ? user as Record<string, unknown> : {}\n\nexport const CostPluginCard = memo(function CostPluginCard({ view, form, t }: CostPluginCardProps) {\n const [busy, setBusy] = useState(false)\n const [status, setStatus] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [staged, setStaged] = useState<Staged | null>(null)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\n\n const snapshot = form?.state as ConfigFormSnapshot<SectionShape> | undefined\n const value = snapshot?.value\n\n // Seed the staged form from the latest accepted section. The snapshot\n // reference is stable until the Host value actually changes, so an\n // in-progress edit survives an unrelated re-render of the Plugins page.\n useEffect(() => {\n if (value === undefined) return\n setStaged({\n overridesText: json(value.overrides),\n aliasesText: json(value.aliases),\n cacheReadDiscount: String(value.cacheReadDiscount ?? 0.25),\n fxMode: value.fxMode ?? 'auto',\n manualRate: String(value.manualRate ?? 7.2),\n balanceEnabled: value.balanceEnabled ?? true,\n openRouterEnabled: value.openRouterEnabled ?? true,\n })\n }, [value])\n\n // Snapshot info for the row's one-liner (source/time, no version numbers).\n useEffect(() => {\n if (view !== 'page') return undefined\n let alive = true\n const load = (): void => {\n void fetch('/cost-meter', { cache: 'no-store' })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (!alive || data === null) return\n setResponse(data)\n })\n .catch(() => { /* keep the last snapshot info */ })\n }\n load()\n return () => {\n alive = false\n }\n }, [view])\n\n const current = response?.pricebook.current ?? null\n const source = current === null\n ? t('source.none')\n : t(`source.${current.source}` as 'source.manual')\n\n // The row's one-liner: the Plugins page shows it as the row's description\n // fallback (a package that ships its own description keeps it). It states\n // what the page configures rather than fetching one, so listing the rows\n // costs no request.\n if (view === 'summary') return <span className={css.summary}>{t('settings.summary')}</span>\n\n /**\n * Submit one batch of field edits as path operations against the revision\n * the editor read. An unchosen select/number keeps its accepted value, so\n * the batch always restates the whole card.\n */\n const save = (): void => {\n if (staged === null || form === undefined) return\n setBusy(true)\n setStatus(null)\n setError(null)\n let overrides: Record<string, unknown>\n let aliases: Record<string, string>\n try {\n overrides = JSON.parse(staged.overridesText) as Record<string, unknown>\n aliases = JSON.parse(staged.aliasesText) as Record<string, string>\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n setBusy(false)\n return\n }\n const ops: ConfigOp[] = [\n { op: 'set', path: ['overrides'], value: overrides },\n { op: 'set', path: ['aliases'], value: aliases },\n { op: 'set', path: ['fxMode'], value: staged.fxMode },\n { op: 'set', path: ['balanceEnabled'], value: staged.balanceEnabled },\n { op: 'set', path: ['openRouterEnabled'], value: staged.openRouterEnabled },\n ]\n const discount = Number(staged.cacheReadDiscount)\n if (Number.isFinite(discount) && discount >= 0 && discount <= 1) {\n ops.push({ op: 'set', path: ['cacheReadDiscount'], value: discount })\n }\n const rate = Number(staged.manualRate)\n if (Number.isFinite(rate) && rate > 0) ops.push({ op: 'set', path: ['manualRate'], value: rate })\n void form.mutate(ops, snapshot?.revision)\n .then((accepted) => {\n if (accepted) setStatus(t('settings.saved'))\n else setError(t('settings.rejected'))\n }, (cause: unknown) => {\n setError(cause instanceof Error ? cause.message : String(cause))\n })\n .finally(() => setBusy(false))\n }\n\n const refresh = (): void => {\n setBusy(true)\n setStatus(null)\n setError(null)\n void fetch('/cost-meter', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ action: 'refresh' }),\n })\n .then(res => (res.ok ? res.json() as Promise<ConversationCostResponse> : null))\n .then((data) => {\n if (data !== null) setResponse(data)\n setStatus(t('settings.saved'))\n })\n .catch((cause: unknown) => setError(cause instanceof Error ? cause.message : String(cause)))\n .finally(() => setBusy(false))\n }\n\n // A namespace the Host does not serve to this client (or one whose connection\n // keeps preferences process-local) has no form seat: the page still opens,\n // but nothing here can be edited.\n if (form === undefined || snapshot === undefined || snapshot.status === 'unavailable') {\n return (\n <div className={css.page} data-testid=\"cost-plugin-card\">\n {error !== null && <p className={css.error}>{t('settings.error', { error })}</p>}\n <p className={css.hint}>{t('settings.unavailable')}</p>\n <div className={css.footer}>\n {status !== null && <span className={css.status}>{status}</span>}\n <span className={css.spacer} />\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>\n {busy ? t('settings.refreshing') : t('settings.refresh')}\n </Button>\n </div>\n </div>\n )\n }\n\n const user = overridden(snapshot.user)\n const readOnly = !snapshot.writable\n const hintText = current === null\n ? t('settings.noSnapshot')\n : `${t('settings.current', { source, time: formatTime(current.effectiveAt) })}`\n\n return (\n <div className={css.page} data-testid=\"cost-plugin-card\">\n <p className={css.hint}>{hintText}</p>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.discount')}</span>\n {'cacheReadDiscount' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <input\n className={css.input}\n type=\"number\"\n min={0}\n max={1}\n step={0.01}\n disabled={readOnly}\n value={staged?.cacheReadDiscount ?? ''}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, cacheReadDiscount: event.target.value })}\n />\n </div>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.fx')}</span>\n {('fxMode' in user || 'manualRate' in user) && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <div className={css.inline}>\n <select\n className={css.select}\n disabled={readOnly}\n value={staged?.fxMode ?? 'auto'}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, fxMode: event.target.value as 'auto' | 'manual' })}\n >\n <option value=\"auto\">{t('settings.fx.auto')}</option>\n <option value=\"manual\">{t('settings.fx.manual')}</option>\n </select>\n <input\n className={css.input}\n type=\"number\"\n min={0.001}\n step={0.01}\n disabled={readOnly || staged?.fxMode === 'auto'}\n value={staged?.manualRate ?? ''}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, manualRate: event.target.value })}\n />\n </div>\n </div>\n <label className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.balance')}</span>\n {'balanceEnabled' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n <input\n className={css.checkbox}\n type=\"checkbox\"\n disabled={readOnly}\n checked={staged?.balanceEnabled ?? true}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, balanceEnabled: event.target.checked })}\n />\n </div>\n </label>\n <label className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.openrouter')}</span>\n {'openRouterEnabled' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n <input\n className={css.checkbox}\n type=\"checkbox\"\n disabled={readOnly}\n checked={staged?.openRouterEnabled ?? true}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, openRouterEnabled: event.target.checked })}\n />\n </div>\n </label>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.overrides')}</span>\n {'overrides' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <textarea\n className={css.textarea}\n value={staged?.overridesText ?? ''}\n spellCheck={false}\n readOnly={readOnly}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, overridesText: event.target.value })}\n />\n <div className={css.hint}>{t('settings.jsonHint')}</div>\n </div>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.aliases')}</span>\n {'aliases' in user && <span className={css.overridden}>{t('settings.overridden')}</span>}\n </div>\n <textarea\n className={css.textarea}\n value={staged?.aliasesText ?? ''}\n spellCheck={false}\n readOnly={readOnly}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, aliasesText: event.target.value })}\n />\n </div>\n {error !== null && <p className={css.error}>{t('settings.error', { error })}</p>}\n <div className={css.footer}>\n {status !== null && <span className={css.status}>{status}</span>}\n <span className={css.spacer} />\n <Button variant=\"outline\" onClick={() => setStaged(null)} disabled={busy || readOnly}>{t('settings.discard')}</Button>\n <Button\n variant=\"primary\"\n onClick={save}\n disabled={busy || readOnly || snapshot.status !== 'ready' || staged === null}\n >\n {t('settings.save')}\n </Button>\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>{busy ? t('settings.refreshing') : t('settings.refresh')}</Button>\n </div>\n </div>\n )\n})\n","/**\n * Cost-meter plugin, browser half: contributes the cost surfaces —\n * the composer-dock readout (anchored session spend + account balance), the\n * Cost view tab, the per-reply cost at the end of each completed Turn's\n * timing row, the header pill with the live streaming estimate, and the\n * plugin configuration page (设置 → 插件 → cost-meter 行). The anchored ledger\n * arrives through the `sessionCost` projection; the balance and the pricebook\n * arrive over the trust-fenced host `/cost-meter` route; the editable\n * configuration binds the `cost-meter` Loader entry through the Plugins page's\n * `plugins.row.config` contribution, whose `form` seat carries the values and\n * the revision-fenced write.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: pulls the ui-conversation SlotMap merge (composer.dock etc.).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the ui-session standard-props merge (useSession /\n// useProjection / sessionId seats) and the ui-chat SlotMap merge\n// (conversation.chat.assistant-actions).\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-chat/client'\n// Type-only: pulls the ui-renderer Context augmentation (ctx.slots).\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the Plugins page slot merge (plugins.row.config) and the\n// config-page form contract the page hands a row's configuration page.\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\n// Type-only: pulls the shared config-form types (ConfigForm types live in the\n// settings package even though DSH 0.2.0 hosts the page on the Plugins page).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport { en, NS, zh, type ConversationCostKey } from './locales.ts'\nimport { SessionCostLine } from './SessionCostLine.tsx'\nimport { CostView } from './CostView.tsx'\nimport { AssistantCostChip } from './AssistantCostChip.tsx'\nimport { SessionCostPill } from './SessionCostPill.tsx'\nimport { CostPluginCard } from './CostPluginCard.tsx'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Cost meter and account balance copy. */\n 'cost-meter': ConversationCostKey\n }\n}\n\n/** The plugin entry id this package's configuration page is keyed by. */\nexport const SETTINGS_NAMESPACE = 'cost-meter'\n\n/** The package whose bundle declares the `cost-meter` row. */\nexport const PACKAGE_NAME = '@gamegeek-saikel/dsh-cost-meter'\n\n/**\n * The `plugins.row.config` dispatch key: `<bundle package name>#<row id>`,\n * exactly as this package's cordis.patch.yml declares the row. The Plugins\n * page reads it to pair the row with this configuration page.\n */\nexport const ROW_CONFIG_KEY = `${PACKAGE_NAME}#${SETTINGS_NAMESPACE}`\n\n/** Required services for locale registration and the slot contributions. */\nexport const inject = ['sessions', 'slots', 'locale']\n\n/**\n * Client plugin body: register the dictionaries and the five slot entries.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-cost-meter: dictionaries')\n const t = ctx.locale.bind(NS)\n\n // Composer-dock readout: anchored session spend + balance (after the\n // shipped stats line, order 0).\n ctx.slots.inject(\n 'conversation.composer.dock',\n () => ctx.slots.register({\n name: 'conversation.composer.dock',\n id: 'cost-meter',\n order: 1,\n locale: NS,\n }, SessionCostLine),\n )\n\n // Cost view tab (after trajectory at order 10).\n ctx.slots.inject(\n 'conversation.view',\n () => ctx.slots.register({\n name: 'conversation.view',\n id: 'cost',\n order: 20,\n locale: NS,\n label: () => t('view.cost'),\n }, CostView),\n )\n\n // Per-reply cost in the completed Turn's action row\n // (copy · branch · 用量 · 用时 · ¥3.02 · 9月4日 19:47): DSH 0.1.5 renders the\n // assistant-actions entries in that row's action area and hands each one the\n // finalized message id, so the chip resolves its Turn through the\n // `sessionCostIndex` projection; the capsule's own `order` then closes the\n // stat run after 用量 / 用时 and before the row's timestamp. The `turnTail`\n // chain would instead render on its own line above the row.\n ctx.slots.inject(\n 'conversation.chat.assistant-actions',\n () => ctx.slots.register({\n name: 'conversation.chat.assistant-actions',\n id: 'cost-meter',\n order: 20,\n locale: NS,\n }, AssistantCostChip),\n )\n\n // Header pill: total / live estimate with the expandable detail.\n // Negative order keeps it left of the built-in Session log utility.\n ctx.slots.inject(\n 'conversation.session.header.utilities',\n () => ctx.slots.register({\n name: 'conversation.session.header.utilities',\n id: 'cost-meter',\n order: -10,\n locale: NS,\n }, SessionCostPill),\n )\n\n // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 hosts one\n // plugin row's own configuration under `plugins.row.config`, keyed by\n // `<bundle package name>#<row id>`; the Host half opts out of the generated\n // schema form (`settings.configure({ auto: false })`), so this page is what\n // the row's configure control opens.\n ctx.slots.inject(\n 'plugins.row.config',\n () => ctx.slots.register({\n name: 'plugins.row.config',\n key: ROW_CONFIG_KEY,\n locale: NS,\n }, CostPluginCard),\n )\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -2601,7 +2601,7 @@ const SettingsFields = {
2601
2601
  /**
2602
2602
  * Plugin configuration schema.
2603
2603
  *
2604
- * DSH 0.1.7 derives the page a plugin exposes from this schema alone: a field
2604
+ * DSH 0.2.0 derives the page a plugin exposes from this schema alone: a field
2605
2605
  * is editable only beneath a `volatile()` node (the nearest volatile ancestor
2606
2606
  * decides, so the mode splits cleanly into deployment config edited in the
2607
2607
  * profile patch and the live preferences the Plugins page writes). The
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 renders a
3
+ * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 renders a
4
4
  * row's own configuration through the Plugins page's `plugins.row.config`
5
5
  * slot, which dispatches the contribution twice: `view: 'summary'` supplies
6
6
  * the row's one-liner, and `view: 'page'` is the body of the page the row's
@@ -60,7 +60,7 @@ export function apply(ctx) {
60
60
  order: -10,
61
61
  locale: NS,
62
62
  }, SessionCostPill));
63
- // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 hosts one
63
+ // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 hosts one
64
64
  // plugin row's own configuration under `plugins.row.config`, keyed by
65
65
  // `<bundle package name>#<row id>`; the Host half opts out of the generated
66
66
  // schema form (`settings.configure({ auto: false })`), so this page is what
@@ -76,7 +76,7 @@ export interface Config {
76
76
  /**
77
77
  * Plugin configuration schema.
78
78
  *
79
- * DSH 0.1.7 derives the page a plugin exposes from this schema alone: a field
79
+ * DSH 0.2.0 derives the page a plugin exposes from this schema alone: a field
80
80
  * is editable only beneath a `volatile()` node (the nearest volatile ancestor
81
81
  * decides, so the mode splits cleanly into deployment config edited in the
82
82
  * profile patch and the live preferences the Plugins page writes). The
@@ -78,7 +78,7 @@ const SettingsFields = {
78
78
  /**
79
79
  * Plugin configuration schema.
80
80
  *
81
- * DSH 0.1.7 derives the page a plugin exposes from this schema alone: a field
81
+ * DSH 0.2.0 derives the page a plugin exposes from this schema alone: a field
82
82
  * is editable only beneath a `volatile()` node (the nearest volatile ancestor
83
83
  * decides, so the mode splits cleanly into deployment config edited in the
84
84
  * profile patch and the live preferences the Plugins page writes). The
@@ -308,7 +308,7 @@ export async function apply(ctx, config) {
308
308
  void pricebookCny.close();
309
309
  void pricebookUsd.close();
310
310
  }, 'cost-meter: pricebook domains');
311
- // ── Plugin configuration. DSH 0.1.7 projects each Loader entry's own Config
311
+ // ── Plugin configuration. DSH 0.2.0 projects each Loader entry's own Config
312
312
  // schema into the Plugins page and persists edits through the profile patch,
313
313
  // so a change re-applies this entry with the new config: there is no
314
314
  // settings namespace to register, and no watch to keep. The volatile fields
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gamegeek-saikel/dsh-cost-meter",
3
3
  "description": "Cost tracking plugin for the DeepSeek Harness Web GUI — snapshot-anchored per-turn pricing, account balance, and live cost estimates.",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.7.0",
7
7
  "engines": {
@@ -63,42 +63,42 @@
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@deepseek-ai/cordis": "^4.0.4",
66
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
67
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
68
- "@deepseek-ai/dsh-invariants": "^0.1.7-rc.2",
69
- "@deepseek-ai/dsh-session": "^0.1.7-rc.2",
70
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
71
- "@deepseek-ai/dsh-session-projection": "^0.1.7-rc.2",
72
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
73
- "@deepseek-ai/dsh-storage-domain": "^0.1.7-rc.2",
74
- "@deepseek-ai/dsh-token-meter": "^0.1.7-rc.2",
66
+ "@deepseek-ai/dsh-credentials": "^0.2.0-rc.2",
67
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
68
+ "@deepseek-ai/dsh-invariants": "^0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-session": "^0.2.0-rc.2",
70
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
71
+ "@deepseek-ai/dsh-session-projection": "^0.2.0-rc.2",
72
+ "@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-storage-domain": "^0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-token-meter": "^0.2.0-rc.2",
75
75
  "react": "^18.2.0",
76
76
  "react-dom": "^18.2.0",
77
77
  "zod": "^4.4.3"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@deepseek-ai/cordis": "^4.0.4",
81
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
82
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
83
- "@deepseek-ai/dsh-invariants": "^0.1.7-rc.2",
84
- "@deepseek-ai/dsh-session": "^0.1.7-rc.2",
85
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
86
- "@deepseek-ai/dsh-session-projection": "^0.1.7-rc.2",
87
- "@deepseek-ai/dsh-subagent": "^0.1.7-rc.2",
88
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
89
- "@deepseek-ai/dsh-storage-domain": "^0.1.7-rc.2",
90
- "@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.2",
91
- "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
92
- "@deepseek-ai/dsh-client-store": "^0.1.7-rc.2",
93
- "@deepseek-ai/dsh-client-ui-chat": "^0.1.7-rc.2",
94
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2",
95
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2",
96
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
97
- "@deepseek-ai/dsh-client-ui-session": "^0.1.7-rc.2",
98
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
99
- "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.2",
100
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
101
- "@deepseek-ai/dsh-token-meter": "^0.1.7-rc.2",
81
+ "@deepseek-ai/dsh-credentials": "^0.2.0-rc.2",
82
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
83
+ "@deepseek-ai/dsh-invariants": "^0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-session": "^0.2.0-rc.2",
85
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
86
+ "@deepseek-ai/dsh-session-projection": "^0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-subagent": "^0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
89
+ "@deepseek-ai/dsh-storage-domain": "^0.2.0-rc.2",
90
+ "@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.2",
91
+ "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
92
+ "@deepseek-ai/dsh-client-store": "^0.2.0-rc.2",
93
+ "@deepseek-ai/dsh-client-ui-chat": "^0.2.0-rc.2",
94
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
95
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
96
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.2",
97
+ "@deepseek-ai/dsh-client-ui-session": "^0.2.0-rc.2",
98
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
99
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.0-rc.2",
100
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.2",
101
+ "@deepseek-ai/dsh-token-meter": "^0.2.0-rc.2",
102
102
  "@deepseek-ai/schemastery": "^3.18.4",
103
103
  "@types/node": "^22.20.0",
104
104
  "@types/react": "~18.3.1",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 renders a
2
+ * Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 renders a
3
3
  * row's own configuration through the Plugins page's `plugins.row.config`
4
4
  * slot, which dispatches the contribution twice: `view: 'summary'` supplies
5
5
  * the row's one-liner, and `view: 'page'` is the body of the page the row's
@@ -1,10 +1,10 @@
1
1
  /* Header utility pill: anchored total / live estimate + expandable detail.
2
2
  Geometry mirrors the controls that share the
3
- `conversation.session.header.utilities` row in DSH 0.1.7 — the shipped
3
+ `conversation.session.header.utilities` row in DSH 0.2.0 — the shipped
4
4
  open-in-app split capsule is 24px tall with `--dsw-radius-sm` (8px), a 0.5px
5
5
  border-l4 hairline, 11px/16px type, 4px gaps and 3px/5px padding — so the
6
6
  pill reads as one of the row's controls instead of standing a size apart.
7
- (0.1.5 used a 28px/14px control here; 0.1.7 has no such size.) The detail
7
+ (0.1.5 used a 28px/14px control here; 0.2.0 has no such size.) The detail
8
8
  panel rides the product overlay surface (--dsw-specific-menu) and dismisses
9
9
  on outside clicks or Escape, like the other header controls. */
10
10
 
@@ -24,7 +24,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
24
24
  // config-page form contract the page hands a row's configuration page.
25
25
  import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
26
26
  // Type-only: pulls the shared config-form types (ConfigForm types live in the
27
- // settings package even though DSH 0.1.7 hosts the page on the Plugins page).
27
+ // settings package even though DSH 0.2.0 hosts the page on the Plugins page).
28
28
  import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
29
29
  import type {} from '@deepseek-ai/dsh-client-locale/client'
30
30
  import { en, NS, zh, type ConversationCostKey } from './locales.ts'
@@ -118,7 +118,7 @@ export function apply(ctx: ClientContext): void {
118
118
  }, SessionCostPill),
119
119
  )
120
120
 
121
- // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.1.7 hosts one
121
+ // Plugin configuration page (设置 → 插件 → cost-meter 行). DSH 0.2.0 hosts one
122
122
  // plugin row's own configuration under `plugins.row.config`, keyed by
123
123
  // `<bundle package name>#<row id>`; the Host half opts out of the generated
124
124
  // schema form (`settings.configure({ auto: false })`), so this page is what
package/src/index.ts CHANGED
@@ -145,7 +145,7 @@ const SettingsFields = {
145
145
  /**
146
146
  * Plugin configuration schema.
147
147
  *
148
- * DSH 0.1.7 derives the page a plugin exposes from this schema alone: a field
148
+ * DSH 0.2.0 derives the page a plugin exposes from this schema alone: a field
149
149
  * is editable only beneath a `volatile()` node (the nearest volatile ancestor
150
150
  * decides, so the mode splits cleanly into deployment config edited in the
151
151
  * profile patch and the live preferences the Plugins page writes). The
@@ -395,7 +395,7 @@ export async function apply(ctx: Context, config?: Config): Promise<void> {
395
395
  void pricebookUsd.close()
396
396
  }, 'cost-meter: pricebook domains')
397
397
 
398
- // ── Plugin configuration. DSH 0.1.7 projects each Loader entry's own Config
398
+ // ── Plugin configuration. DSH 0.2.0 projects each Loader entry's own Config
399
399
  // schema into the Plugins page and persists edits through the profile patch,
400
400
  // so a change re-applies this entry with the new config: there is no
401
401
  // settings namespace to register, and no watch to keep. The volatile fields