@gamegeek-saikel/dsh-cost-meter 0.2.0 → 0.2.1

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
@@ -44,7 +44,7 @@ Chat costs in DeepSeek pricing change over time (list prices, USD→CNY exchange
44
44
  | Cost anchoring | Append-only pricebook snapshots; step cost computed once at the event's own time |
45
45
  | Price sources | Manual override > official pricing page > built-in fallback > OpenRouter (fallback only, USD→CNY) > none |
46
46
  | Official page | Parses both the 2026-08-21 combined zh/en tables (including the `deepseek-v4-flash-vision-exp` column and the English UTC schedule) and the legacy split tables; the built-in historical list still anchors `single` when the page no longer carries one |
47
- | Peak pricing | 2026-08-17 00:00 Beijing rollout; peak/off-peak windows parsed from each page's own schedule (the zh and en pages may differ; fallback 09:00–12:00 / 14:00–18:00 Beijing), off-peak half price |
47
+ | Peak pricing | 2026-08-17 00:00 Beijing rollout; peak windows apply Monday–Friday only (the zh and en pages may state different timezones; fallback 09:00–12:00 / 14:00–18:00 Beijing), weekends and all other hours off-peak at half price |
48
48
  | Cost formula | Uncached input + cache reads (hit rate) + cache writes (billed at uncached input rate) + output, per 1M tokens, CNY |
49
49
  | Account balance | Official `GET /user/balance`, cached 60 s, single in-flight request, trust-fenced route |
50
50
  | Subagent support | BFS over the live agent tree; conversation totals = main session + descendants |
@@ -72,7 +72,7 @@ The pricebook (`src/pricebook.ts`) is the durable price source, persisted on the
72
72
 
73
73
  - **Priority chain** — per canonical model key (`provider/model`, bare model, or the `flash`/`pro` pricing key for DeepSeek-family models): manual override > official page > built-in fallback > OpenRouter (fallback only, USD→CNY, cache reads at the configured discount) > none.
74
74
  - **Snapshot selection** — `snapshotForTime` picks the newest snapshot with `effectiveAt <= event time` (pre-install sessions anchor to the first snapshot once).
75
- - **Peak/off-peak** — before the 2026-08-17 rollout all steps price at the single list price; after it, the band is chosen by the EVENT's own time against the schedule of the pricebook's own page (the zh and en pages each parse their own peak windows — the redesigned English page states UTC — falling back to Beijing 09:00–12:00 / 14:00–18:00, everything else off-peak). When the combined page has no legacy single-price column, `single` keeps anchoring to the built-in historical list; a page that still carries a separate legacy table wins. Each step's band is anchored once at fold time, so the per-reply cards and chips always show the band that round was billed at — never the band of the moment you are looking.
75
+ - **Peak/off-peak** — before the 2026-08-17 rollout all steps price at the single list price; after it, the band is chosen by the EVENT's own time against the schedule of the pricebook's own page (the zh and en pages each parse their own peak windows — the redesigned English page states UTC — falling back to Beijing 09:00–12:00 / 14:00–18:00, everything else off-peak). The official pages restrict peak windows to Monday–Friday, so Saturdays and Sundays are off-peak all day in the schedule's own timezone. When the combined page has no legacy single-price column, `single` keeps anchoring to the built-in historical list; a page that still carries a separate legacy table wins. Each step's band is anchored once at fold time, so the per-reply cards and chips always show the band that round was billed at — never the band of the moment you are looking.
76
76
  - **Immutable ledger** — the `sessionCost` projection (`src/session-cost-projection.ts`) folds `request/header` (model) and usage-carrying events into per-step rows; a second usage sample for the same (turn, step) replaces the first (same-step finalization, not a re-price), with O(1) incremental totals.
77
77
 
78
78
  ## Project Structure
package/README.zh-CN.md CHANGED
@@ -44,7 +44,7 @@ DeepSeek 的价格随时间变化(官方价目表、USD→CNY 汇率、以及
44
44
  | 成本锚定 | 只追加价格簿快照;步成本在事件自身时刻只计算一次 |
45
45
  | 价格来源 | 手动覆盖 > 官方价格页 > 内置回退 > OpenRouter(仅回退,USD→CNY)> 无 |
46
46
  | 官方页适配 | 同时解析 2026-08-21 新版中英文页面(含 `deepseek-v4-flash-vision-exp` 第三列与英文 UTC 时段)和旧版分表;无旧单价的页面继续用内置历史单价锚定 |
47
- | 峰值定价 | 2026-08-17 00:00 北京生效;高峰/闲时窗口按各页面自己的时段表解析(中英文页面可能不同,缺省 09:00–12:00 / 14:00–18:00 北京),闲时为半价 |
47
+ | 峰值定价 | 2026-08-17 00:00 北京生效;高峰窗口仅周一至周五(中英文页面时区可能不同,缺省 09:00–12:00 / 14:00–18:00 北京),周六、周日全天及其余时段为半价闲时 |
48
48
  | 成本公式 | 未命中输入 + 缓存命中(命中价)+ 缓存写入(按未命中输入价计)+ 输出,每百万 tokens,CNY |
49
49
  | 账户余额 | 官方 `GET /user/balance`,缓存 60 秒,单飞请求,路由带信任围栏 |
50
50
  | 子代理支持 | 沿活跃代理树 BFS;对话总花费 = 主会话 + 全部后代 |
@@ -72,7 +72,7 @@ DeepSeek 的价格随时间变化(官方价目表、USD→CNY 汇率、以及
72
72
 
73
73
  - **优先级链**——按规范模型键(`provider/model`、裸模型名,或 DeepSeek 系模型的 `flash`/`pro` 定价键):手动覆盖 > 官方页面 > 内置回退 > OpenRouter(仅回退,USD→CNY,缓存读按配置折扣)> 无。
74
74
  - **快照选取**——`snapshotForTime` 取 `effectiveAt <= 事件时间` 的最新快照(安装前的会话一次性锚到首个快照)。
75
- - **峰值/闲时**——2026-08-17 上线前所有步按单一价目计费;上线后按事件自身时刻与该价格簿页面的时段表选档(中英文页面各自解析自己的高峰窗口,英文新版为 UTC,抓取失败时回退到北京 09:00–12:00 / 14:00–18:00,其余闲时)。新版合并表没有旧单价列时,`single` 继续锚定内置历史单价;页面若有独立旧表,则优先用它。每步的档位在折叠时锚定一次,逐回复卡片与回复小标签永远显示该轮对话当时被计价的档位,而不是查看时刻的档位。
75
+ - **峰值/闲时**——2026-08-17 上线前所有步按单一价目计费;上线后按事件自身时刻与该价格簿页面的时段表选档(中英文页面各自解析自己的高峰窗口,英文新版为 UTC,抓取失败时回退到北京 09:00–12:00 / 14:00–18:00,其余闲时)。官方页面限定高峰仅周一至周五,因此北京时间周六、周日全天为闲时。新版合并表没有旧单价列时,`single` 继续锚定内置历史单价;页面若有独立旧表,则优先用它。每步的档位在折叠时锚定一次,逐回复卡片与回复小标签永远显示该轮对话当时被计价的档位,而不是查看时刻的档位。
76
76
  - **不可变账本**——`sessionCost` 投影(`src/session-cost-projection.ts`)把 `request/header`(模型)与携带用量的事件折叠为逐步记录;同一 (turn, step) 的第二次用量样本**替换**第一条(同一步终结,而非重新计价),总计以 O(1) 增量维护。
77
77
 
78
78
  ## 项目结构
package/lib/client.js CHANGED
@@ -157,11 +157,13 @@ window.__ModuleLoader__.load({
157
157
  const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
158
158
  /**
159
159
  * Built-in fallback zh peak schedule: the official announcement's windows in
160
- * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
160
+ * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything
161
+ * else, including all of Saturday and Sunday, off-peak).
161
162
  */
162
163
  const PEAK_SCHEDULE_ZH = {
163
164
  timezone: "Asia/Shanghai",
164
- ranges: [[9, 12], [14, 18]]
165
+ ranges: [[9, 12], [14, 18]],
166
+ weekdaysOnly: true
165
167
  };
166
168
  /** Built-in fallback USD prices from the English pricing page. */
167
169
  const FALLBACK_CURRENT_USD = {
@@ -180,7 +182,9 @@ window.__ModuleLoader__.load({
180
182
  /**
181
183
  * Whether the given instant is a peak-pricing hour under one schedule: the
182
184
  * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
183
- * passes the schedule parsed from its own (zh or en) pricing page.
185
+ * passes the schedule parsed from its own (zh or en) pricing page. When the
186
+ * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's
187
+ * timezone are off-peak all day.
184
188
  * @param now - the instant to classify.
185
189
  * @param schedule - the peak-hour schedule to classify against.
186
190
  * @returns true during peak hours.
@@ -189,8 +193,13 @@ window.__ModuleLoader__.load({
189
193
  const parts = new Intl.DateTimeFormat("en-US", {
190
194
  timeZone: schedule.timezone,
191
195
  hour: "numeric",
192
- hour12: false
196
+ hour12: false,
197
+ weekday: "short"
193
198
  }).formatToParts(now);
199
+ if (schedule.weekdaysOnly === true) {
200
+ const weekday = parts.find((part) => part.type === "weekday")?.value;
201
+ if (weekday === "Sat" || weekday === "Sun") return false;
202
+ }
194
203
  const hour = Number(parts.find((part) => part.type === "hour")?.value);
195
204
  if (Number.isNaN(hour)) return false;
196
205
  return schedule.ranges.some(([start, end]) => hour >= start && hour < end);
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["memo","useState","useMemo","css","Tooltip","memo","useState","css","memo","useState","css","memo","useState","useRef","useMemo","css","memo","useSyncExternalStore","useState","css","IconChevronDownOutline14","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 '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.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 // Plugin configuration card\n 'settings.title': '花费计价',\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.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 '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.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 'settings.title': 'Cost meter',\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.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 both the\n * current list prices and the upcoming peak/off-peak table, so price changes\n * and the 2026-08-17 peak-pricing rollout never require a plugin update.\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 announcement's windows in\n * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).\n */\nexport const PEAK_SCHEDULE_ZH: PeakSchedule = {\n timezone: 'Asia/Shanghai',\n ranges: [[9, 12], [14, 18]],\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 may state different windows or a\n * different timezone; each pricebook parses its own page and falls back to\n * this locale-matched default only when the page carries no schedule.\n */\nexport const PEAK_SCHEDULE_EN: PeakSchedule = {\n timezone: 'Asia/Shanghai',\n ranges: [[9, 12], [14, 18]],\n}\n\n/**\n * Built-in fallback prices: the official list for deepseek-v4-flash and\n * deepseek-v4-pro (CNY per 1M tokens). DeepSeek bills cache writes at the\n * uncached input price (its wire usage reports only cache-hit vs cache-miss\n * buckets), so no separate cache-write 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 upcoming peak table: the 2026-08-17 schedule\n * (peak hours double the off-peak half).\n */\nexport const FALLBACK_PEAK: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.05, inputPerMillion: 1.5, outputPerMillion: 4.5 },\n peak: { cacheReadPerMillion: 0.1, inputPerMillion: 3, outputPerMillion: 9 },\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/** Built-in fallback USD peak table from the English pricing page. */\nexport const FALLBACK_PEAK_USD: PeakPricing = {\n flash: {\n offPeak: { ...FALLBACK_CURRENT_USD.flash },\n peak: { cacheReadPerMillion: 0.014, inputPerMillion: 0.44, outputPerMillion: 1.32 },\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. The Chinese page states Beijing\n * time; the English page may state Beijing time or UTC — a Beijing/UTC+8\n * mention wins, a bare UTC mention selects UTC, anything else defaults to\n * 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 if (/\\b(?:beijing|北京时间|china standard)\\b/i.test(window) || /\\butc\\s*\\+\\s*8\\b/i.test(window)) {\n return { timezone: 'Asia/Shanghai', ranges }\n }\n if (/\\butc\\b/i.test(window)) {\n return { timezone: 'UTC', ranges }\n }\n return { timezone: 'Asia/Shanghai', ranges }\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.\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 }).formatToParts(now)\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 * The conversation-wide totals: the main session's ledger plus every\n * subagent's ledger (host-aggregated). Undefined while the main ledger is\n * absent; identical to the main totals without subagents.\n * @param main - the main session's projection totals.\n * @param subagents - the host-aggregated subagent totals.\n * @returns the combined totals, or undefined.\n */\nexport function combineTotals(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): SessionCostTotals | undefined {\n if (main === undefined) return undefined\n if (subagents === undefined || subagents.length === 0) return main\n return sumTotals([main, ...subagents.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\n * `/cost-meter` route), refreshed every minute. The hover detail\n * breaks the spend into the three billed categories (uncached input incl.\n * cache writes, cache reads, output) and names the anchored price snapshot.\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 { memo, useEffect, useMemo, useState } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { BalanceSnapshot, ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { combineTotals, subagentSpend } 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\n/** Session-cost namespace key domain for the locale seat. */\nexport type CostLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostLineProps {\n useSession: SnapshotSelectorHook<ConversationSnapshot>\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 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 const subagents = response?.subagents\n const combined = combineTotals(cost?.totals, subagents)\n const subSpend = subagentSpend(subagents)\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 = cost?.totals?.cost ?? 0\n\n const parts: string[] = []\n try {\n const symbol = currencySymbol(currency)\n // Breakdown first (cached input, uncached input, output — combined with\n // subagents), then the main session total, then the subagent total, then\n // the balance.\n if (combined !== undefined && combined.pricedSteps > 0) {\n parts.push(t('line.cacheRead', { amount: `${symbol}${formatMoney(combined.cacheReadCost)}` }))\n parts.push(t('line.uncached', { amount: `${symbol}${formatMoney(combined.uncachedCost)}` }))\n parts.push(t('line.output', { amount: `${symbol}${formatMoney(combined.outputCost)}` }))\n }\n parts.push(t('cost.label', { amount: `${symbol}${formatMoney(mainSpend)}` }))\n if (subSpend > 0) {\n parts.push(t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` }))\n }\n if (balanceEnabled && balance?.ok === true && balance.balance !== null) {\n parts.push(t('balance.label', { amount: `${currencySymbol(balance.balance.currency)}${formatMoney(balance.balance.total)}` }))\n } else if (balanceEnabled && failed) {\n parts.push(t('balance.failed'))\n }\n } catch {\n /* keep the row alive */\n }\n if (parts.length === 0) return null\n\n const model = cost?.model?.model\n const tooltipParts = useMemo(() => {\n const rows: string[] = []\n try {\n rows.push(...costDetail(cost, model, t, currency))\n if (subSpend > 0) {\n rows.push(t('line.subagent', { amount: `${currencySymbol(currency)}${formatMoney(subSpend)}` }))\n }\n const pricebook = response?.pricebook\n if (pricebook !== undefined && pricebook.current !== null) {\n const source = pricebook.current.source\n if (source === 'official' || source === 'fallback' || source === 'manual' || source === 'openrouter') {\n rows.push(t('pill.detail.source', { source: t(`source.${source}` as 'source.manual') }))\n }\n }\n if (balanceEnabled && balance?.ok === true) rows.push(balanceTooltip(balance, t))\n if (response !== null) {\n rows.push(t('refreshedAt', { time: formatTime(balance?.ok === true ? balance.fetchedAt : Date.now()) }))\n }\n } catch {\n /* keep the row alive */\n }\n return rows\n }, [cost, model, t, balance, balanceEnabled, response, subSpend, currency])\n\n const line = parts.join(' · ')\n const tooltip = tooltipParts.filter(Boolean).join(' · ')\n if (tooltip.length === 0) {\n return <div className={css.root} data-testid=\"session-cost-line\">{line}</div>\n }\n return (\n <Tooltip label={tooltip} side=\"top\" delayMs={500}>\n <div className={css.root} data-testid=\"session-cost-line\">{line}</div>\n </Tooltip>\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.\n */\nimport { memo, useEffect, useState } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, combineTotals, peakOffPeakMultiplier, subagentSpend } 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\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 if (cost === undefined || cost.steps.length === 0) {\n return <div className={css.root} data-testid=\"cost-view-empty\">{t('view.empty')}</div>\n }\n\n const rows = cost.steps\n const combined = combineTotals(cost.totals, response?.subagents)\n const totals = combined ?? cost.totals\n const subSpend = subagentSpend(response?.subagents)\n const money = (value: number | null): string => value === null ? '—' : `${symbol}${formatMoney(value)}`\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 {response !== null && response.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 {response.subagents.map(sub => (\n <li key={sub.sessionId} className={css.step}>\n <div className={css.stepHead}>\n <span className={css.stepTurn}>{sub.sessionId.slice(0, 8)}</span>\n <span className={css.stepCost}>{money(sub.totals.cost)}</span>\n </div>\n <div className={css.stepBody}>\n <span>{t('view.uncached')}: {money(sub.totals.uncachedCost)}</span>\n <span>{t('view.cacheRead')}: {money(sub.totals.cacheReadCost)}</span>\n <span>{t('view.output')}: {money(sub.totals.outputCost)}</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 && <span className={css.bandBadge} data-band={stepBand} data-testid=\"cost-step-band\">{stepBandLabel}</span>}\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 chip: the anchored cost of ONE finalized reply. Rendered\n * through the assistant-actions slot but visually joined to the reply's\n * timing row (01:51 · 用时 · 首 token · tok/s): a flex `order` places it\n * after the clock text, and the type matches the clock's (label-tertiary,\n * 14px) with a dot separator, so it reads as part of the same metadata run.\n * The `messageId` owner prop maps to the assistant node's (turn, step),\n * which addresses the projection's step ledger — the cost is the anchored\n * snapshot value, never a current-price recompute. Unpriced replies render\n * `—` (the Cost tab explains why).\n */\nimport { memo, useEffect, useState } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { MessageId } from '@deepseek-ai/dsh-llm/brand'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier } from './format.ts'\nimport css from './AssistantCostChip.module.css'\n\nexport type ChipLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface AssistantCostChipProps {\n /** Stable identity of the finalized assistant message the actions address. */\n messageId: MessageId\n useSession: SnapshotSelectorHook<ConversationSnapshot>\n useProjection: UseProjection\n t: ChipLocale\n}\n\n/**\n * Locate a finalized assistant node by its message id and return its\n * (turn, step) coordinates, or null when the node is out of window.\n * @param snapshot - the conversation snapshot.\n * @param messageId - the addressed message identity.\n * @returns the step coordinates, or null.\n */\nexport function stepOfMessage(\n snapshot: ConversationSnapshot,\n messageId: MessageId,\n): { turn: number; step: number } | null {\n const nodes = snapshot.chat.legacy.nodes\n for (let i = nodes.length - 1; i >= 0; i -= 1) {\n const node = nodes[i]\n if (node?.kind === 'assistant' && node.messageId === messageId && node.turn !== undefined) {\n return { turn: node.turn, step: node.step }\n }\n }\n return null\n}\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\nexport const AssistantCostChip = memo(function AssistantCostChip({ messageId, useSession, useProjection, t }: AssistantCostChipProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const step = useSession(snapshot => stepOfMessage(snapshot, messageId))\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const ledger = step === null ? undefined : stepOf(cost?.steps, step.turn, step.step)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\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 if (ledger === undefined || ledger.cost === null) {\n if (ledger === undefined) return null\n // Unpriced reply: show the dash; the native title explains why.\n const reason = ledger.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 amount = `${currencySymbol(currency)}${formatMoney(ledger.cost)}`\n // Anchored to the ROUND's own time: the ledger's `band` was fixed at fold\n // time from the usage event's time, so the badge shows the band that\n // actually priced this reply — not the band of the clock right now.\n const band = ledger.band ?? bandForTime(ledger.time)\n const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, ledger.provider, ledger.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 className={css.root} data-cost-chip title={t('chip.title', { amount })} data-testid=\"cost-chip\">\n {amount}\n {bandLabel !== null && <span className={css.bandBadge} data-band={band} data-testid=\"cost-chip-band\">{bandLabel}</span>}\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 */\nimport { memo, useEffect, useMemo, useRef, useState, type RefObject } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/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, combineTotals, estimateCost, peakOffPeakMultiplier, subagentSpend } 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<ConversationSnapshot>\n useProjection: UseProjection\n sessionId: string\n t: PillLocale\n}\n\n/** Read the conversation's running flag (the pill's estimate gate). */\nfunction runningOf(snapshot: ConversationSnapshot): 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 const partial = useSession(snapshot => snapshot.partial)\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 const subagents = response?.subagents\n const combined = combineTotals(projection?.totals, subagents)\n const subSpend = subagentSpend(subagents)\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 = blocksOutputTokens(partial?.blocks)\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 if (projection === 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 && <span className={css.bandBadge} data-testid=\"cost-pill-band\">{bandLabel}</span>}\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 card (设置 → 插件 → cost-meter): the standard\n * DSH plugin-card shape (collapsible header, disclosure body, save/discard\n * footer) over the `cost-meter` settings namespace. Every editable\n * field binds the settings scope (`ctx.settingsScope.bind`) — the same\n * transport the shipped llm-deepseek / bash / web-search cards use — so the\n * card looks, stages, and persists like every other plugin configuration.\n * The JSON textareas carry the two structured fields (per-model price\n * overrides, OpenRouter aliases); the refresh button is a plain action.\n */\nimport { memo, useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { Button, IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/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\nexport interface CostPluginCardProps {\n /** The bound settings scope for the `cost-meter` namespace. */\n scope: SettingsScope<unknown>\n t: PluginCardLocale\n}\n\n/** One staged field group the card writes through the scope. */\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\nexport const CostPluginCard = memo(function CostPluginCard({ scope, t }: CostPluginCardProps) {\n const snapshot = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n ) as SettingsScopeSnapshot<SectionShape>\n\n const [open, setOpen] = useState(false)\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 // Seed the staged form from the latest accepted section.\n const value = snapshot.value\n useEffect(() => {\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 header hint (source/time, no version numbers).\n useEffect(() => {\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 }, [])\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 const write = async (field: string, fieldValue: unknown): Promise<void> => {\n setBusy(true)\n setStatus(null)\n setError(null)\n try {\n await scope.set(field, fieldValue)\n setStatus(t('settings.saved'))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }\n\n const save = (): void => {\n if (staged === null) return\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 return\n }\n void write('overrides', overrides)\n void write('aliases', aliases)\n const discount = Number(staged.cacheReadDiscount)\n if (Number.isFinite(discount) && discount >= 0 && discount <= 1) void write('cacheReadDiscount', discount)\n const rate = Number(staged.manualRate)\n if (Number.isFinite(rate) && rate > 0) void write('manualRate', rate)\n if (staged.fxMode === 'auto' || staged.fxMode === 'manual') void write('fxMode', staged.fxMode)\n void write('balanceEnabled', staged.balanceEnabled)\n void write('openRouterEnabled', staged.openRouterEnabled)\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 const hint = current === null\n ? t('settings.noSnapshot')\n : `${t('settings.current', { source, time: formatTime(current.effectiveAt) })}`\n\n return (\n <li className={css.card + (open ? ` ${css.cardOpen}` : '')} data-testid=\"cost-plugin-card\">\n <button type=\"button\" className={css.header} onClick={() => setOpen(prev => !prev)} aria-expanded={open}>\n <span className={css.headText}>\n <span className={css.name}>{t('settings.title')}</span>\n <span className={css.description}>{hint}</span>\n </span>\n {error !== null && <span className={css.pending}>{t('settings.error', { error })}</span>}\n <IconChevronDownOutline14 className={css.chevron + (open ? ` ${css.chevronOpen}` : '')} />\n </button>\n {open && (\n <div className={css.body}>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.discount')}</span>\n </div>\n <input\n className={css.input}\n type=\"number\"\n min={0}\n max={1}\n step={0.01}\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 </div>\n <div className={css.inline}>\n <select\n className={css.select}\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 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 <input\n className={css.checkbox}\n type=\"checkbox\"\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 <input\n className={css.checkbox}\n type=\"checkbox\"\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 </div>\n <textarea\n className={css.textarea}\n value={staged?.overridesText ?? ''}\n spellCheck={false}\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 </div>\n <textarea\n className={css.textarea}\n value={staged?.aliasesText ?? ''}\n spellCheck={false}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, aliasesText: event.target.value })}\n />\n </div>\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)}>{t('settings.discard')}</Button>\n <Button variant=\"primary\" onClick={save} disabled={busy || snapshot.status !== 'ready'}>{t('settings.save')}</Button>\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>{busy ? t('settings.refreshing') : t('settings.refresh')}</Button>\n </div>\n </div>\n )}\n </li>\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 chip, the header pill with the live\n * streaming estimate, and the plugin configuration card (设置 → 插件). The\n * anchored ledger arrives through the `sessionCost` projection; the balance\n * and the pricebook arrive over the trust-fenced host `/cost-meter`\n * route; the editable configuration binds the `cost-meter` settings\n * namespace through the standard settings scope.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\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 settings-domain SlotMap merges (settings.plugin.item)\n// and the settingsScope service.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the plugin-card slot merge (settings.plugin.item).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/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 settings namespace this plugin owns (mirrors the host registration). */\nexport const SETTINGS_NAMESPACE = 'cost-meter'\n\n/** Required services for locale registration, slot contributions, and the settings scope. */\nexport const inject = ['sessions', 'slots', 'locale', 'settingsScope']\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 const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE })\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 chip (after the feedback action at order 10).\n ctx.slots.inject(\n 'conversation.chat.assistant-actions',\n () => ctx.slots.register({\n name: 'conversation.chat.assistant-actions',\n id: 'cost',\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 card (设置 → 插件), standard plugin-card shape over\n // the `cost-meter` settings namespace. The slot is keyed by the settings\n // namespace the card edits, so the tab pairs this card with the Host-served\n // `cost-meter` namespace.\n ctx.slots.inject(\n 'settings.plugin.item',\n () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: SETTINGS_NAMESPACE,\n locale: NS,\n inject: () => ({ scope }),\n }, CostPluginCard),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["memo","useState","useMemo","css","Tooltip","memo","useState","css","memo","useState","css","memo","useState","useRef","useMemo","css","memo","useSyncExternalStore","useState","css","IconChevronDownOutline14","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 '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.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 // Plugin configuration card\n 'settings.title': '花费计价',\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.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 '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.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 'settings.title': 'Cost meter',\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.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 both the\n * current list prices and the upcoming peak/off-peak table, so price changes\n * and the 2026-08-17 peak-pricing rollout never require a plugin update.\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 announcement's windows in\n * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything\n * else, 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 official list for deepseek-v4-flash and\n * deepseek-v4-pro (CNY per 1M tokens). DeepSeek bills cache writes at the\n * uncached input price (its wire usage reports only cache-hit vs cache-miss\n * buckets), so no separate cache-write 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 upcoming peak table: the 2026-08-17 schedule\n * (peak hours double the off-peak half).\n */\nexport const FALLBACK_PEAK: PeakPricing = {\n flash: {\n offPeak: { cacheReadPerMillion: 0.05, inputPerMillion: 1.5, outputPerMillion: 4.5 },\n peak: { cacheReadPerMillion: 0.1, inputPerMillion: 3, outputPerMillion: 9 },\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/** Built-in fallback USD peak table from the English pricing page. */\nexport const FALLBACK_PEAK_USD: PeakPricing = {\n flash: {\n offPeak: { ...FALLBACK_CURRENT_USD.flash },\n peak: { cacheReadPerMillion: 0.014, inputPerMillion: 0.44, outputPerMillion: 1.32 },\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 * The conversation-wide totals: the main session's ledger plus every\n * subagent's ledger (host-aggregated). Undefined while the main ledger is\n * absent; identical to the main totals without subagents.\n * @param main - the main session's projection totals.\n * @param subagents - the host-aggregated subagent totals.\n * @returns the combined totals, or undefined.\n */\nexport function combineTotals(\n main: SessionCostTotals | undefined,\n subagents: readonly SubagentCost[] | undefined,\n): SessionCostTotals | undefined {\n if (main === undefined) return undefined\n if (subagents === undefined || subagents.length === 0) return main\n return sumTotals([main, ...subagents.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\n * `/cost-meter` route), refreshed every minute. The hover detail\n * breaks the spend into the three billed categories (uncached input incl.\n * cache writes, cache reads, output) and names the anchored price snapshot.\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 { memo, useEffect, useMemo, useState } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { BalanceSnapshot, ConversationCostResponse, SessionCostProjection } from '../types.ts'\nimport { combineTotals, subagentSpend } 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\n/** Session-cost namespace key domain for the locale seat. */\nexport type CostLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface SessionCostLineProps {\n useSession: SnapshotSelectorHook<ConversationSnapshot>\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 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 const subagents = response?.subagents\n const combined = combineTotals(cost?.totals, subagents)\n const subSpend = subagentSpend(subagents)\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 = cost?.totals?.cost ?? 0\n\n const parts: string[] = []\n try {\n const symbol = currencySymbol(currency)\n // Breakdown first (cached input, uncached input, output — combined with\n // subagents), then the main session total, then the subagent total, then\n // the balance.\n if (combined !== undefined && combined.pricedSteps > 0) {\n parts.push(t('line.cacheRead', { amount: `${symbol}${formatMoney(combined.cacheReadCost)}` }))\n parts.push(t('line.uncached', { amount: `${symbol}${formatMoney(combined.uncachedCost)}` }))\n parts.push(t('line.output', { amount: `${symbol}${formatMoney(combined.outputCost)}` }))\n }\n parts.push(t('cost.label', { amount: `${symbol}${formatMoney(mainSpend)}` }))\n if (subSpend > 0) {\n parts.push(t('line.subagent', { amount: `${symbol}${formatMoney(subSpend)}` }))\n }\n if (balanceEnabled && balance?.ok === true && balance.balance !== null) {\n parts.push(t('balance.label', { amount: `${currencySymbol(balance.balance.currency)}${formatMoney(balance.balance.total)}` }))\n } else if (balanceEnabled && failed) {\n parts.push(t('balance.failed'))\n }\n } catch {\n /* keep the row alive */\n }\n if (parts.length === 0) return null\n\n const model = cost?.model?.model\n const tooltipParts = useMemo(() => {\n const rows: string[] = []\n try {\n rows.push(...costDetail(cost, model, t, currency))\n if (subSpend > 0) {\n rows.push(t('line.subagent', { amount: `${currencySymbol(currency)}${formatMoney(subSpend)}` }))\n }\n const pricebook = response?.pricebook\n if (pricebook !== undefined && pricebook.current !== null) {\n const source = pricebook.current.source\n if (source === 'official' || source === 'fallback' || source === 'manual' || source === 'openrouter') {\n rows.push(t('pill.detail.source', { source: t(`source.${source}` as 'source.manual') }))\n }\n }\n if (balanceEnabled && balance?.ok === true) rows.push(balanceTooltip(balance, t))\n if (response !== null) {\n rows.push(t('refreshedAt', { time: formatTime(balance?.ok === true ? balance.fetchedAt : Date.now()) }))\n }\n } catch {\n /* keep the row alive */\n }\n return rows\n }, [cost, model, t, balance, balanceEnabled, response, subSpend, currency])\n\n const line = parts.join(' · ')\n const tooltip = tooltipParts.filter(Boolean).join(' · ')\n if (tooltip.length === 0) {\n return <div className={css.root} data-testid=\"session-cost-line\">{line}</div>\n }\n return (\n <Tooltip label={tooltip} side=\"top\" delayMs={500}>\n <div className={css.root} data-testid=\"session-cost-line\">{line}</div>\n </Tooltip>\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.\n */\nimport { memo, useEffect, useState } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, combineTotals, peakOffPeakMultiplier, subagentSpend } 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\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 if (cost === undefined || cost.steps.length === 0) {\n return <div className={css.root} data-testid=\"cost-view-empty\">{t('view.empty')}</div>\n }\n\n const rows = cost.steps\n const combined = combineTotals(cost.totals, response?.subagents)\n const totals = combined ?? cost.totals\n const subSpend = subagentSpend(response?.subagents)\n const money = (value: number | null): string => value === null ? '—' : `${symbol}${formatMoney(value)}`\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 {response !== null && response.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 {response.subagents.map(sub => (\n <li key={sub.sessionId} className={css.step}>\n <div className={css.stepHead}>\n <span className={css.stepTurn}>{sub.sessionId.slice(0, 8)}</span>\n <span className={css.stepCost}>{money(sub.totals.cost)}</span>\n </div>\n <div className={css.stepBody}>\n <span>{t('view.uncached')}: {money(sub.totals.uncachedCost)}</span>\n <span>{t('view.cacheRead')}: {money(sub.totals.cacheReadCost)}</span>\n <span>{t('view.output')}: {money(sub.totals.outputCost)}</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 && <span className={css.bandBadge} data-band={stepBand} data-testid=\"cost-step-band\">{stepBandLabel}</span>}\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 chip: the anchored cost of ONE finalized reply. Rendered\n * through the assistant-actions slot but visually joined to the reply's\n * timing row (01:51 · 用时 · 首 token · tok/s): a flex `order` places it\n * after the clock text, and the type matches the clock's (label-tertiary,\n * 14px) with a dot separator, so it reads as part of the same metadata run.\n * The `messageId` owner prop maps to the assistant node's (turn, step),\n * which addresses the projection's step ledger — the cost is the anchored\n * snapshot value, never a current-price recompute. Unpriced replies render\n * `—` (the Cost tab explains why).\n */\nimport { memo, useEffect, useState } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { MessageId } from '@deepseek-ai/dsh-llm/brand'\nimport type { ConversationCostResponse, SessionCostStep } from '../types.ts'\nimport { bandForTime, peakOffPeakMultiplier } from './cost-math.ts'\nimport { currencySymbol, displayCurrency, formatMoney, formatMultiplier } from './format.ts'\nimport css from './AssistantCostChip.module.css'\n\nexport type ChipLocale = PropsLocale<'cost-meter'>['t']\n\nexport interface AssistantCostChipProps {\n /** Stable identity of the finalized assistant message the actions address. */\n messageId: MessageId\n useSession: SnapshotSelectorHook<ConversationSnapshot>\n useProjection: UseProjection\n t: ChipLocale\n}\n\n/**\n * Locate a finalized assistant node by its message id and return its\n * (turn, step) coordinates, or null when the node is out of window.\n * @param snapshot - the conversation snapshot.\n * @param messageId - the addressed message identity.\n * @returns the step coordinates, or null.\n */\nexport function stepOfMessage(\n snapshot: ConversationSnapshot,\n messageId: MessageId,\n): { turn: number; step: number } | null {\n const nodes = snapshot.chat.legacy.nodes\n for (let i = nodes.length - 1; i >= 0; i -= 1) {\n const node = nodes[i]\n if (node?.kind === 'assistant' && node.messageId === messageId && node.turn !== undefined) {\n return { turn: node.turn, step: node.step }\n }\n }\n return null\n}\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\nexport const AssistantCostChip = memo(function AssistantCostChip({ messageId, useSession, useProjection, t }: AssistantCostChipProps) {\n const currency = displayCurrency(t as (key: string, params?: Record<string, string>) => string)\n const step = useSession(snapshot => stepOfMessage(snapshot, messageId))\n const costCny = useProjection('sessionCost')\n const costUsd = useProjection('sessionCostUsd')\n const cost = currency === 'USD' ? costUsd : costCny\n const ledger = step === null ? undefined : stepOf(cost?.steps, step.turn, step.step)\n const [response, setResponse] = useState<ConversationCostResponse | null>(null)\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 if (ledger === undefined || ledger.cost === null) {\n if (ledger === undefined) return null\n // Unpriced reply: show the dash; the native title explains why.\n const reason = ledger.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 amount = `${currencySymbol(currency)}${formatMoney(ledger.cost)}`\n // Anchored to the ROUND's own time: the ledger's `band` was fixed at fold\n // time from the usage event's time, so the badge shows the band that\n // actually priced this reply — not the band of the clock right now.\n const band = ledger.band ?? bandForTime(ledger.time)\n const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, ledger.provider, ledger.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 className={css.root} data-cost-chip title={t('chip.title', { amount })} data-testid=\"cost-chip\">\n {amount}\n {bandLabel !== null && <span className={css.bandBadge} data-band={band} data-testid=\"cost-chip-band\">{bandLabel}</span>}\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 */\nimport { memo, useEffect, useMemo, useRef, useState, type RefObject } from 'react'\nimport type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/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, combineTotals, estimateCost, peakOffPeakMultiplier, subagentSpend } 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<ConversationSnapshot>\n useProjection: UseProjection\n sessionId: string\n t: PillLocale\n}\n\n/** Read the conversation's running flag (the pill's estimate gate). */\nfunction runningOf(snapshot: ConversationSnapshot): 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 const partial = useSession(snapshot => snapshot.partial)\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 const subagents = response?.subagents\n const combined = combineTotals(projection?.totals, subagents)\n const subSpend = subagentSpend(subagents)\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 = blocksOutputTokens(partial?.blocks)\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 if (projection === 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 && <span className={css.bandBadge} data-testid=\"cost-pill-band\">{bandLabel}</span>}\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 card (设置 → 插件 → cost-meter): the standard\n * DSH plugin-card shape (collapsible header, disclosure body, save/discard\n * footer) over the `cost-meter` settings namespace. Every editable\n * field binds the settings scope (`ctx.settingsScope.bind`) — the same\n * transport the shipped llm-deepseek / bash / web-search cards use — so the\n * card looks, stages, and persists like every other plugin configuration.\n * The JSON textareas carry the two structured fields (per-model price\n * overrides, OpenRouter aliases); the refresh button is a plain action.\n */\nimport { memo, useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { Button, IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/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\nexport interface CostPluginCardProps {\n /** The bound settings scope for the `cost-meter` namespace. */\n scope: SettingsScope<unknown>\n t: PluginCardLocale\n}\n\n/** One staged field group the card writes through the scope. */\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\nexport const CostPluginCard = memo(function CostPluginCard({ scope, t }: CostPluginCardProps) {\n const snapshot = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n ) as SettingsScopeSnapshot<SectionShape>\n\n const [open, setOpen] = useState(false)\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 // Seed the staged form from the latest accepted section.\n const value = snapshot.value\n useEffect(() => {\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 header hint (source/time, no version numbers).\n useEffect(() => {\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 }, [])\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 const write = async (field: string, fieldValue: unknown): Promise<void> => {\n setBusy(true)\n setStatus(null)\n setError(null)\n try {\n await scope.set(field, fieldValue)\n setStatus(t('settings.saved'))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }\n\n const save = (): void => {\n if (staged === null) return\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 return\n }\n void write('overrides', overrides)\n void write('aliases', aliases)\n const discount = Number(staged.cacheReadDiscount)\n if (Number.isFinite(discount) && discount >= 0 && discount <= 1) void write('cacheReadDiscount', discount)\n const rate = Number(staged.manualRate)\n if (Number.isFinite(rate) && rate > 0) void write('manualRate', rate)\n if (staged.fxMode === 'auto' || staged.fxMode === 'manual') void write('fxMode', staged.fxMode)\n void write('balanceEnabled', staged.balanceEnabled)\n void write('openRouterEnabled', staged.openRouterEnabled)\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 const hint = current === null\n ? t('settings.noSnapshot')\n : `${t('settings.current', { source, time: formatTime(current.effectiveAt) })}`\n\n return (\n <li className={css.card + (open ? ` ${css.cardOpen}` : '')} data-testid=\"cost-plugin-card\">\n <button type=\"button\" className={css.header} onClick={() => setOpen(prev => !prev)} aria-expanded={open}>\n <span className={css.headText}>\n <span className={css.name}>{t('settings.title')}</span>\n <span className={css.description}>{hint}</span>\n </span>\n {error !== null && <span className={css.pending}>{t('settings.error', { error })}</span>}\n <IconChevronDownOutline14 className={css.chevron + (open ? ` ${css.chevronOpen}` : '')} />\n </button>\n {open && (\n <div className={css.body}>\n <div className={css.field}>\n <div className={css.head}>\n <span className={css.label}>{t('settings.discount')}</span>\n </div>\n <input\n className={css.input}\n type=\"number\"\n min={0}\n max={1}\n step={0.01}\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 </div>\n <div className={css.inline}>\n <select\n className={css.select}\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 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 <input\n className={css.checkbox}\n type=\"checkbox\"\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 <input\n className={css.checkbox}\n type=\"checkbox\"\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 </div>\n <textarea\n className={css.textarea}\n value={staged?.overridesText ?? ''}\n spellCheck={false}\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 </div>\n <textarea\n className={css.textarea}\n value={staged?.aliasesText ?? ''}\n spellCheck={false}\n onChange={event => setStaged(prev => prev === null ? prev : { ...prev, aliasesText: event.target.value })}\n />\n </div>\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)}>{t('settings.discard')}</Button>\n <Button variant=\"primary\" onClick={save} disabled={busy || snapshot.status !== 'ready'}>{t('settings.save')}</Button>\n <Button variant=\"outline\" onClick={refresh} disabled={busy}>{busy ? t('settings.refreshing') : t('settings.refresh')}</Button>\n </div>\n </div>\n )}\n </li>\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 chip, the header pill with the live\n * streaming estimate, and the plugin configuration card (设置 → 插件). The\n * anchored ledger arrives through the `sessionCost` projection; the balance\n * and the pricebook arrive over the trust-fenced host `/cost-meter`\n * route; the editable configuration binds the `cost-meter` settings\n * namespace through the standard settings scope.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\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 settings-domain SlotMap merges (settings.plugin.item)\n// and the settingsScope service.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the plugin-card slot merge (settings.plugin.item).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/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 settings namespace this plugin owns (mirrors the host registration). */\nexport const SETTINGS_NAMESPACE = 'cost-meter'\n\n/** Required services for locale registration, slot contributions, and the settings scope. */\nexport const inject = ['sessions', 'slots', 'locale', 'settingsScope']\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 const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE })\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 chip (after the feedback action at order 10).\n ctx.slots.inject(\n 'conversation.chat.assistant-actions',\n () => ctx.slots.register({\n name: 'conversation.chat.assistant-actions',\n id: 'cost',\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 card (设置 → 插件), standard plugin-card shape over\n // the `cost-meter` settings namespace. The slot is keyed by the settings\n // namespace the card edits, so the tab pairs this card with the Host-served\n // `cost-meter` namespace.\n ctx.slots.inject(\n 'settings.plugin.item',\n () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: SETTINGS_NAMESPACE,\n locale: NS,\n inject: () => ({ scope }),\n }, CostPluginCard),\n )\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -802,21 +802,24 @@ const PRICING_URL_EN = "https://api-docs.deepseek.com/quick_start/pricing/";
802
802
  const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
803
803
  /**
804
804
  * Built-in fallback zh peak schedule: the official announcement's windows in
805
- * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
805
+ * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything
806
+ * else, including all of Saturday and Sunday, off-peak).
806
807
  */
807
808
  const PEAK_SCHEDULE_ZH = {
808
809
  timezone: "Asia/Shanghai",
809
- ranges: [[9, 12], [14, 18]]
810
+ ranges: [[9, 12], [14, 18]],
811
+ weekdaysOnly: true
810
812
  };
811
813
  /**
812
814
  * Built-in fallback en peak schedule. Kept as a separate constant from the zh
813
- * schedule because the English pricing page may state different windows or a
814
- * different timezone; each pricebook parses its own page and falls back to
815
- * this locale-matched default only when the page carries no schedule.
815
+ * schedule because the English pricing page states the same windows in UTC;
816
+ * each pricebook parses its own page and falls back to this locale-matched
817
+ * default only when the page carries no schedule.
816
818
  */
817
819
  const PEAK_SCHEDULE_EN = {
818
- timezone: "Asia/Shanghai",
819
- ranges: [[9, 12], [14, 18]]
820
+ timezone: "UTC",
821
+ ranges: [[1, 4], [6, 10]],
822
+ weekdaysOnly: true
820
823
  };
821
824
  /**
822
825
  * Built-in fallback prices: the official list for deepseek-v4-flash and
@@ -1143,10 +1146,10 @@ const SCHEDULE_WINDOWS_RE = /(\d{1,2}):(\d{2})\s*[-–—~至]\s*(\d{1,2}):(\d{2
1143
1146
  /**
1144
1147
  * Parse the peak-hour schedule from a pricing page: the two half-open
1145
1148
  * windows next to the localized peak label (`高峰时段` / `peak hours`), plus
1146
- * the timezone the windows are expressed in. The Chinese page states Beijing
1147
- * time; the English page may state Beijing time or UTC — a Beijing/UTC+8
1148
- * mention wins, a bare UTC mention selects UTC, anything else defaults to
1149
- * Beijing time.
1149
+ * the timezone the windows are expressed in and the Monday–Friday
1150
+ * restriction when the page states one. The Chinese page states Beijing
1151
+ * time; the English page states UTC a Beijing/UTC+8 mention wins, a bare
1152
+ * UTC mention selects UTC, anything else defaults to Beijing time.
1150
1153
  * @param html - the raw pricing page.
1151
1154
  * @param locale - which page's wording to look for.
1152
1155
  * @returns the parsed schedule, or undefined when the page carries none.
@@ -1160,17 +1163,21 @@ function parsePeakSchedule(html, locale = "zh") {
1160
1163
  if (match === null) return void 0;
1161
1164
  const ranges = [[Number(match[1]), Number(match[3])], [Number(match[5]), Number(match[7])]];
1162
1165
  if (ranges.some(([start, end]) => !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > 24 || start >= end)) return;
1166
+ const weekdaysOnly = /(?:周一|星期[一二三四五]|工作日)/.test(window) || /\b(?:Monday\s+through\s+Friday|weekdays?)\b/i.test(window);
1163
1167
  if (/\b(?:beijing|北京时间|china standard)\b/i.test(window) || /\butc\s*\+\s*8\b/i.test(window)) return {
1164
1168
  timezone: "Asia/Shanghai",
1165
- ranges
1169
+ ranges,
1170
+ ...weekdaysOnly ? { weekdaysOnly: true } : {}
1166
1171
  };
1167
1172
  if (/\butc\b/i.test(window)) return {
1168
1173
  timezone: "UTC",
1169
- ranges
1174
+ ranges,
1175
+ ...weekdaysOnly ? { weekdaysOnly: true } : {}
1170
1176
  };
1171
1177
  return {
1172
1178
  timezone: "Asia/Shanghai",
1173
- ranges
1179
+ ranges,
1180
+ ...weekdaysOnly ? { weekdaysOnly: true } : {}
1174
1181
  };
1175
1182
  }
1176
1183
  /**
@@ -1245,7 +1252,9 @@ async function fetchPricing(fetchImpl = globalThis.fetch, timeoutMs = 15e3, loca
1245
1252
  /**
1246
1253
  * Whether the given instant is a peak-pricing hour under one schedule: the
1247
1254
  * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
1248
- * passes the schedule parsed from its own (zh or en) pricing page.
1255
+ * passes the schedule parsed from its own (zh or en) pricing page. When the
1256
+ * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's
1257
+ * timezone are off-peak all day.
1249
1258
  * @param now - the instant to classify.
1250
1259
  * @param schedule - the peak-hour schedule to classify against.
1251
1260
  * @returns true during peak hours.
@@ -1254,8 +1263,13 @@ function isPeakHour(now = /* @__PURE__ */ new Date(), schedule = PEAK_SCHEDULE_Z
1254
1263
  const parts = new Intl.DateTimeFormat("en-US", {
1255
1264
  timeZone: schedule.timezone,
1256
1265
  hour: "numeric",
1257
- hour12: false
1266
+ hour12: false,
1267
+ weekday: "short"
1258
1268
  }).formatToParts(now);
1269
+ if (schedule.weekdaysOnly === true) {
1270
+ const weekday = parts.find((part) => part.type === "weekday")?.value;
1271
+ if (weekday === "Sat" || weekday === "Sun") return false;
1272
+ }
1259
1273
  const hour = Number(parts.find((part) => part.type === "hour")?.value);
1260
1274
  if (Number.isNaN(hour)) return false;
1261
1275
  return schedule.ranges.some(([start, end]) => hour >= start && hour < end);
@@ -20,14 +20,15 @@ export declare const PRICING_URL_EN = "https://api-docs.deepseek.com/quick_start
20
20
  export declare const PEAK_PRICING_START_MS: number;
21
21
  /**
22
22
  * Built-in fallback zh peak schedule: the official announcement's windows in
23
- * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
23
+ * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything
24
+ * else, including all of Saturday and Sunday, off-peak).
24
25
  */
25
26
  export declare const PEAK_SCHEDULE_ZH: PeakSchedule;
26
27
  /**
27
28
  * Built-in fallback en peak schedule. Kept as a separate constant from the zh
28
- * schedule because the English pricing page may state different windows or a
29
- * different timezone; each pricebook parses its own page and falls back to
30
- * this locale-matched default only when the page carries no schedule.
29
+ * schedule because the English pricing page states the same windows in UTC;
30
+ * each pricebook parses its own page and falls back to this locale-matched
31
+ * default only when the page carries no schedule.
31
32
  */
32
33
  export declare const PEAK_SCHEDULE_EN: PeakSchedule;
33
34
  /**
@@ -80,10 +81,10 @@ export declare function parsePeakTableEn(html: string): PeakPricing | undefined;
80
81
  /**
81
82
  * Parse the peak-hour schedule from a pricing page: the two half-open
82
83
  * windows next to the localized peak label (`高峰时段` / `peak hours`), plus
83
- * the timezone the windows are expressed in. The Chinese page states Beijing
84
- * time; the English page may state Beijing time or UTC — a Beijing/UTC+8
85
- * mention wins, a bare UTC mention selects UTC, anything else defaults to
86
- * Beijing time.
84
+ * the timezone the windows are expressed in and the Monday–Friday
85
+ * restriction when the page states one. The Chinese page states Beijing
86
+ * time; the English page states UTC a Beijing/UTC+8 mention wins, a bare
87
+ * UTC mention selects UTC, anything else defaults to Beijing time.
87
88
  * @param html - the raw pricing page.
88
89
  * @param locale - which page's wording to look for.
89
90
  * @returns the parsed schedule, or undefined when the page carries none.
@@ -99,7 +100,9 @@ export declare function fetchPricing(fetchImpl?: typeof fetch, timeoutMs?: numbe
99
100
  /**
100
101
  * Whether the given instant is a peak-pricing hour under one schedule: the
101
102
  * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
102
- * passes the schedule parsed from its own (zh or en) pricing page.
103
+ * passes the schedule parsed from its own (zh or en) pricing page. When the
104
+ * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's
105
+ * timezone are off-peak all day.
103
106
  * @param now - the instant to classify.
104
107
  * @param schedule - the peak-hour schedule to classify against.
105
108
  * @returns true during peak hours.
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAmC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE1I,yCAAyC;AACzC,eAAO,MAAM,WAAW,6DAA6D,CAAA;AAErF,sCAAsC;AACtC,eAAO,MAAM,cAAc,uDAAuD,CAAA;AAElF,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,QAAkC,CAAA;AAEpE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAG9B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAG9B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAG9B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,WAS3B,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,oBAAoB,EAAE,cAGlC,CAAA;AAED,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,EAAE,WAS/B,CAAA;AAED,wDAAwD;AACxD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW9C;AAqKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGpE;AA6DD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEtE;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,IAAI,GAAG,IAAW,GAAG,YAAY,GAAG,SAAS,CA0BpG;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,GAAE,OAAO,KAAwB,EAC1C,SAAS,SAAS,EAClB,MAAM,GAAE,IAAI,GAAG,IAAW,GACzB,OAAO,CAAC,eAAe,CAAC,CAmD1B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,IAAiB,EAAE,QAAQ,GAAE,YAA+B,GAAG,OAAO,CASrG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,UAAU,EAAE,OAAO,EACnB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GAAG,KAAK,EACtB,QAAQ,GAAE,YAA+B,GACxC,WAAW,CAMb;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,UAAU,EAAE,OAAO,EACnB,GAAG,EAAE,IAAI,EACT,QAAQ,GAAE,YAA+B,GACxC,cAAc,CAKhB"}
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAmC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE1I,yCAAyC;AACzC,eAAO,MAAM,WAAW,6DAA6D,CAAA;AAErF,sCAAsC;AACtC,eAAO,MAAM,cAAc,uDAAuD,CAAA;AAElF,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,QAAkC,CAAA;AAEpE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAI9B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAI9B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAG9B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,WAS3B,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,oBAAoB,EAAE,cAGlC,CAAA;AAED,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,EAAE,WAS/B,CAAA;AAED,wDAAwD;AACxD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW9C;AAqKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGpE;AA6DD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEtE;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,IAAI,GAAG,IAAW,GAAG,YAAY,GAAG,SAAS,CA4BpG;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,GAAE,OAAO,KAAwB,EAC1C,SAAS,SAAS,EAClB,MAAM,GAAE,IAAI,GAAG,IAAW,GACzB,OAAO,CAAC,eAAe,CAAC,CAmD1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,IAAiB,EAAE,QAAQ,GAAE,YAA+B,GAAG,OAAO,CAcrG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,UAAU,EAAE,OAAO,EACnB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GAAG,KAAK,EACtB,QAAQ,GAAE,YAA+B,GACxC,WAAW,CAMb;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,UAAU,EAAE,OAAO,EACnB,GAAG,EAAE,IAAI,EACT,QAAQ,GAAE,YAA+B,GACxC,cAAc,CAKhB"}
@@ -19,21 +19,24 @@ export const PRICING_URL_EN = 'https://api-docs.deepseek.com/quick_start/pricing
19
19
  export const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
20
20
  /**
21
21
  * Built-in fallback zh peak schedule: the official announcement's windows in
22
- * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
22
+ * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything
23
+ * else, including all of Saturday and Sunday, off-peak).
23
24
  */
24
25
  export const PEAK_SCHEDULE_ZH = {
25
26
  timezone: 'Asia/Shanghai',
26
27
  ranges: [[9, 12], [14, 18]],
28
+ weekdaysOnly: true,
27
29
  };
28
30
  /**
29
31
  * Built-in fallback en peak schedule. Kept as a separate constant from the zh
30
- * schedule because the English pricing page may state different windows or a
31
- * different timezone; each pricebook parses its own page and falls back to
32
- * this locale-matched default only when the page carries no schedule.
32
+ * schedule because the English pricing page states the same windows in UTC;
33
+ * each pricebook parses its own page and falls back to this locale-matched
34
+ * default only when the page carries no schedule.
33
35
  */
34
36
  export const PEAK_SCHEDULE_EN = {
35
- timezone: 'Asia/Shanghai',
36
- ranges: [[9, 12], [14, 18]],
37
+ timezone: 'UTC',
38
+ ranges: [[1, 4], [6, 10]],
39
+ weekdaysOnly: true,
37
40
  };
38
41
  /**
39
42
  * Built-in fallback prices: the official list for deepseek-v4-flash and
@@ -337,10 +340,10 @@ const SCHEDULE_WINDOWS_RE = /(\d{1,2}):(\d{2})\s*[-–—~至]\s*(\d{1,2}):(\d{2
337
340
  /**
338
341
  * Parse the peak-hour schedule from a pricing page: the two half-open
339
342
  * windows next to the localized peak label (`高峰时段` / `peak hours`), plus
340
- * the timezone the windows are expressed in. The Chinese page states Beijing
341
- * time; the English page may state Beijing time or UTC — a Beijing/UTC+8
342
- * mention wins, a bare UTC mention selects UTC, anything else defaults to
343
- * Beijing time.
343
+ * the timezone the windows are expressed in and the Monday–Friday
344
+ * restriction when the page states one. The Chinese page states Beijing
345
+ * time; the English page states UTC a Beijing/UTC+8 mention wins, a bare
346
+ * UTC mention selects UTC, anything else defaults to Beijing time.
344
347
  * @param html - the raw pricing page.
345
348
  * @param locale - which page's wording to look for.
346
349
  * @returns the parsed schedule, or undefined when the page carries none.
@@ -365,13 +368,15 @@ export function parsePeakSchedule(html, locale = 'zh') {
365
368
  if (ranges.some(([start, end]) => !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > 24 || start >= end)) {
366
369
  return undefined;
367
370
  }
371
+ const weekdaysOnly = /(?:周一|星期[一二三四五]|工作日)/.test(window)
372
+ || /\b(?:Monday\s+through\s+Friday|weekdays?)\b/i.test(window);
368
373
  if (/\b(?:beijing|北京时间|china standard)\b/i.test(window) || /\butc\s*\+\s*8\b/i.test(window)) {
369
- return { timezone: 'Asia/Shanghai', ranges };
374
+ return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) };
370
375
  }
371
376
  if (/\butc\b/i.test(window)) {
372
- return { timezone: 'UTC', ranges };
377
+ return { timezone: 'UTC', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) };
373
378
  }
374
- return { timezone: 'Asia/Shanghai', ranges };
379
+ return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) };
375
380
  }
376
381
  /**
377
382
  * Fetch and parse the official pricing page.
@@ -436,7 +441,9 @@ export async function fetchPricing(fetchImpl = globalThis.fetch, timeoutMs = 15_
436
441
  /**
437
442
  * Whether the given instant is a peak-pricing hour under one schedule: the
438
443
  * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
439
- * passes the schedule parsed from its own (zh or en) pricing page.
444
+ * passes the schedule parsed from its own (zh or en) pricing page. When the
445
+ * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's
446
+ * timezone are off-peak all day.
440
447
  * @param now - the instant to classify.
441
448
  * @param schedule - the peak-hour schedule to classify against.
442
449
  * @returns true during peak hours.
@@ -446,7 +453,13 @@ export function isPeakHour(now = new Date(), schedule = PEAK_SCHEDULE_ZH) {
446
453
  timeZone: schedule.timezone,
447
454
  hour: 'numeric',
448
455
  hour12: false,
456
+ weekday: 'short',
449
457
  }).formatToParts(now);
458
+ if (schedule.weekdaysOnly === true) {
459
+ const weekday = parts.find(part => part.type === 'weekday')?.value;
460
+ if (weekday === 'Sat' || weekday === 'Sun')
461
+ return false;
462
+ }
450
463
  const hour = Number(parts.find(part => part.type === 'hour')?.value);
451
464
  if (Number.isNaN(hour))
452
465
  return false;
@@ -112,6 +112,12 @@ export interface PeakSchedule {
112
112
  timezone: string;
113
113
  /** Half-open peak windows in `timezone` wall-clock hours. */
114
114
  ranges: readonly PeakHourRange[];
115
+ /**
116
+ * True when the page restricts the peak windows to Monday–Friday; the
117
+ * official pages added this restriction on 2026-08-21, making Saturdays
118
+ * and Sundays off-peak all day in the schedule's own timezone.
119
+ */
120
+ weekdaysOnly?: boolean;
115
121
  }
116
122
  /**
117
123
  * One pricing snapshot served to the browser: the parsed official page plus
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
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.2.0",
4
+ "version": "0.2.1",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.7.0",
7
7
  "engines": {
package/src/pricing.ts CHANGED
@@ -25,22 +25,25 @@ export const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0)
25
25
 
26
26
  /**
27
27
  * Built-in fallback zh peak schedule: the official announcement's windows in
28
- * Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
28
+ * Beijing time (peak 09:00-12:00 / 14:00-18:00, Monday–Friday; everything
29
+ * else, including all of Saturday and Sunday, off-peak).
29
30
  */
30
31
  export const PEAK_SCHEDULE_ZH: PeakSchedule = {
31
32
  timezone: 'Asia/Shanghai',
32
33
  ranges: [[9, 12], [14, 18]],
34
+ weekdaysOnly: true,
33
35
  }
34
36
 
35
37
  /**
36
38
  * Built-in fallback en peak schedule. Kept as a separate constant from the zh
37
- * schedule because the English pricing page may state different windows or a
38
- * different timezone; each pricebook parses its own page and falls back to
39
- * this locale-matched default only when the page carries no schedule.
39
+ * schedule because the English pricing page states the same windows in UTC;
40
+ * each pricebook parses its own page and falls back to this locale-matched
41
+ * default only when the page carries no schedule.
40
42
  */
41
43
  export const PEAK_SCHEDULE_EN: PeakSchedule = {
42
- timezone: 'Asia/Shanghai',
43
- ranges: [[9, 12], [14, 18]],
44
+ timezone: 'UTC',
45
+ ranges: [[1, 4], [6, 10]],
46
+ weekdaysOnly: true,
44
47
  }
45
48
 
46
49
  /**
@@ -371,10 +374,10 @@ const SCHEDULE_WINDOWS_RE = /(\d{1,2}):(\d{2})\s*[-–—~至]\s*(\d{1,2}):(\d{2
371
374
  /**
372
375
  * Parse the peak-hour schedule from a pricing page: the two half-open
373
376
  * windows next to the localized peak label (`高峰时段` / `peak hours`), plus
374
- * the timezone the windows are expressed in. The Chinese page states Beijing
375
- * time; the English page may state Beijing time or UTC — a Beijing/UTC+8
376
- * mention wins, a bare UTC mention selects UTC, anything else defaults to
377
- * Beijing time.
377
+ * the timezone the windows are expressed in and the Monday–Friday
378
+ * restriction when the page states one. The Chinese page states Beijing
379
+ * time; the English page states UTC a Beijing/UTC+8 mention wins, a bare
380
+ * UTC mention selects UTC, anything else defaults to Beijing time.
378
381
  * @param html - the raw pricing page.
379
382
  * @param locale - which page's wording to look for.
380
383
  * @returns the parsed schedule, or undefined when the page carries none.
@@ -398,13 +401,15 @@ export function parsePeakSchedule(html: string, locale: 'zh' | 'en' = 'zh'): Pea
398
401
  !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > 24 || start >= end)) {
399
402
  return undefined
400
403
  }
404
+ const weekdaysOnly = /(?:周一|星期[一二三四五]|工作日)/.test(window)
405
+ || /\b(?:Monday\s+through\s+Friday|weekdays?)\b/i.test(window)
401
406
  if (/\b(?:beijing|北京时间|china standard)\b/i.test(window) || /\butc\s*\+\s*8\b/i.test(window)) {
402
- return { timezone: 'Asia/Shanghai', ranges }
407
+ return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }
403
408
  }
404
409
  if (/\butc\b/i.test(window)) {
405
- return { timezone: 'UTC', ranges }
410
+ return { timezone: 'UTC', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }
406
411
  }
407
- return { timezone: 'Asia/Shanghai', ranges }
412
+ return { timezone: 'Asia/Shanghai', ranges, ...(weekdaysOnly ? { weekdaysOnly: true } : {}) }
408
413
  }
409
414
 
410
415
  /**
@@ -473,7 +478,9 @@ export async function fetchPricing(
473
478
  /**
474
479
  * Whether the given instant is a peak-pricing hour under one schedule: the
475
480
  * zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
476
- * passes the schedule parsed from its own (zh or en) pricing page.
481
+ * passes the schedule parsed from its own (zh or en) pricing page. When the
482
+ * schedule is Monday–Friday only, Saturdays and Sundays in the schedule's
483
+ * timezone are off-peak all day.
477
484
  * @param now - the instant to classify.
478
485
  * @param schedule - the peak-hour schedule to classify against.
479
486
  * @returns true during peak hours.
@@ -483,7 +490,12 @@ export function isPeakHour(now: Date = new Date(), schedule: PeakSchedule = PEAK
483
490
  timeZone: schedule.timezone,
484
491
  hour: 'numeric',
485
492
  hour12: false,
493
+ weekday: 'short',
486
494
  }).formatToParts(now)
495
+ if (schedule.weekdaysOnly === true) {
496
+ const weekday = parts.find(part => part.type === 'weekday')?.value
497
+ if (weekday === 'Sat' || weekday === 'Sun') return false
498
+ }
487
499
  const hour = Number(parts.find(part => part.type === 'hour')?.value)
488
500
  if (Number.isNaN(hour)) return false
489
501
  return schedule.ranges.some(([start, end]) => hour >= start && hour < end)
package/src/types.ts CHANGED
@@ -125,6 +125,12 @@ export interface PeakSchedule {
125
125
  timezone: string
126
126
  /** Half-open peak windows in `timezone` wall-clock hours. */
127
127
  ranges: readonly PeakHourRange[]
128
+ /**
129
+ * True when the page restricts the peak windows to Monday–Friday; the
130
+ * official pages added this restriction on 2026-08-21, making Saturdays
131
+ * and Sundays off-peak all day in the schedule's own timezone.
132
+ */
133
+ weekdaysOnly?: boolean
128
134
  }
129
135
 
130
136
  /**