@gamegeek-saikel/dsh-cost-meter 0.1.2 → 0.1.3
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/lib/client.js
CHANGED
|
@@ -1490,8 +1490,7 @@ window.__ModuleLoader__.load({
|
|
|
1490
1490
|
}, SessionCostPill));
|
|
1491
1491
|
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1492
1492
|
name: "settings.plugin.item",
|
|
1493
|
-
|
|
1494
|
-
order: 30,
|
|
1493
|
+
key: SETTINGS_NAMESPACE,
|
|
1495
1494
|
locale: NS,
|
|
1496
1495
|
inject: () => ({ scope })
|
|
1497
1496
|
}, CostPluginCard));
|
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, 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/** Number regex: `0.02`, `1`, `2`, `3.0` etc. Deliberately non-global: a\n * shared global regex leaks `lastIndex` across `exec` calls and would skip\n * the second price cell (the pro price). */\nconst PRICE_RE = /(\\d+(?:\\.\\d+)?)\\s*元/\n\n/** English price regex: matches `$0.02`, `$0.002`, `0.02` etc. */\nconst PRICE_RE_EN = /\\$?\\s*(\\d+(?:\\.\\d+)?)/\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(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** Parse one price cell text like `0.02元` into a number; undefined when absent. */\nfunction parsePriceCell(text: string): number | undefined {\n const match = PRICE_RE.exec(text)\n if (match === null) return undefined\n const value = Number(match[1])\n return Number.isFinite(value) ? value : undefined\n}\n\n/** The second price cell of a row (the pro price), stripped of the first. */\n\n/** Parse one English price cell text like `$0.02` into a number. */\nfunction parsePriceCellUsd(text: string): number | undefined {\n const match = PRICE_RE_EN.exec(text)\n if (match === null) return undefined\n const value = Number(match[1])\n return Number.isFinite(value) ? value : undefined\n}\n\n/** The second price cell of an English row, stripped of the first. */\nfunction secondPriceCellUsd(text: string): string {\n return text.replace(/^\\s*\\$?\\s*(\\d+(?:\\.\\d+)?)/, '')\n}\nfunction secondPriceCell(text: string): string {\n return text.replace(/^\\s*(\\d+(?:\\.\\d+)?元)/, '')\n}\n\n/**\n * Parse the current single-price table: three rows labeled with the bucket\n * names, each carrying the flash and pro price 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 const hit = /百万tokens输入(缓存命中)([\\s\\S]{0,400}?)百万tokens输入(缓存未命中)([\\s\\S]{0,400}?)百万tokens输出([\\s\\S]{0,400}?)(?:并发限制|<\\/table)/i.exec(stripHtml(html))\n if (hit === null) return undefined\n const cacheReadCell = hit[1] ?? ''\n const inputCell = hit[2] ?? ''\n const outputCell = hit[3] ?? ''\n const cacheReadFlash = parsePriceCell(cacheReadCell)\n const cacheReadPro = parsePriceCell(secondPriceCell(cacheReadCell))\n const inputFlash = parsePriceCell(inputCell)\n const inputPro = parsePriceCell(secondPriceCell(inputCell))\n const outputFlash = parsePriceCell(outputCell)\n const outputPro = parsePriceCell(secondPriceCell(outputCell))\n if (cacheReadFlash === undefined || inputFlash === undefined || outputFlash === undefined) {\n return undefined\n }\n return {\n flash: {\n cacheReadPerMillion: cacheReadFlash,\n inputPerMillion: inputFlash,\n outputPerMillion: outputFlash,\n },\n pro: {\n cacheReadPerMillion: cacheReadPro ?? cacheReadFlash,\n inputPerMillion: inputPro ?? inputFlash,\n outputPerMillion: outputPro ?? outputFlash,\n },\n }\n}\n\n/**\n * Parse the upcoming peak-pricing table: model rows with off-peak and peak\n * cells, e.g. `deepseek-v4-flash 空闲时段 0.05 1.5 4.5 高峰时段 0.10 3.0 9.0`.\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nexport function parsePeakTable(html: string): PeakPricing | undefined {\n const text = stripHtml(html)\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 return {\n flash: {\n offPeak: {\n cacheReadPerMillion: Number(flash[1]),\n inputPerMillion: Number(flash[2]),\n outputPerMillion: Number(flash[3]),\n },\n peak: {\n cacheReadPerMillion: Number(flash[4]),\n inputPerMillion: Number(flash[5]),\n outputPerMillion: Number(flash[6]),\n },\n },\n pro: {\n offPeak: {\n cacheReadPerMillion: Number(pro[1]),\n inputPerMillion: Number(pro[2]),\n outputPerMillion: Number(pro[3]),\n },\n peak: {\n cacheReadPerMillion: Number(pro[4]),\n inputPerMillion: Number(pro[5]),\n outputPerMillion: Number(pro[6]),\n },\n },\n }\n}\n\n/**\n * Parse the English current single-price table.\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\n/**\n * Parse the English combined pricing table. The English page lists all three\n * buckets as rows with OFF-PEAK and PEAK cells, so we extract both the current\n * off-peak list and the peak table from the same markup.\n */\nfunction parseEnglishPricing(html: string): { current: CurrentPricing; peak: PeakPricing } | undefined {\n const text = stripHtml(html)\n const hit = /1M INPUT TOKENS \\(CACHE HIT\\)\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+1M INPUT TOKENS \\(CACHE MISS\\)\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+1M OUTPUT TOKENS\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)/i.exec(text)\n if (hit === null) return undefined\n const n = (index: number): number => Number(hit[index])\n const current: CurrentPricing = {\n flash: {\n cacheReadPerMillion: n(1),\n inputPerMillion: n(5),\n outputPerMillion: n(9),\n },\n pro: {\n cacheReadPerMillion: n(2),\n inputPerMillion: n(6),\n outputPerMillion: n(10),\n },\n }\n const peak: PeakPricing = {\n flash: {\n offPeak: {\n cacheReadPerMillion: n(1),\n inputPerMillion: n(5),\n outputPerMillion: n(9),\n },\n peak: {\n cacheReadPerMillion: n(3),\n inputPerMillion: n(7),\n outputPerMillion: n(11),\n },\n },\n pro: {\n offPeak: {\n cacheReadPerMillion: n(2),\n inputPerMillion: n(6),\n outputPerMillion: n(10),\n },\n peak: {\n cacheReadPerMillion: n(4),\n inputPerMillion: n(8),\n outputPerMillion: n(12),\n },\n },\n }\n return { current, peak }\n}\n\n/**\n * Parse the English current single-price 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 upcoming 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 const label = locale === 'zh' ? '高峰时段' : /peak\\s*hours?/i\n const labelIndex = typeof label === 'string' ? text.indexOf(label) : text.search(label)\n if (labelIndex < 0) return undefined\n const window = text.slice(labelIndex, labelIndex + 200)\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 parseCurrent = locale === 'en' ? parseCurrentTableEn : parseCurrentTable\n const parsePeak = locale === 'en' ? parsePeakTableEn : parsePeakTable\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 current = parseCurrent(html)\n if (current === undefined) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: 'pricing table not found' }\n }\n const peak = parsePeak(html)\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 ...(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: keyof CurrentPricing,\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)}¤cy=${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)}¤cy=${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)}¤cy=${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.\n ctx.slots.inject(\n 'settings.plugin.item',\n () => ctx.slots.register({\n name: 'settings.plugin.item',\n id: 'cost-meter',\n order: 30,\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, 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/** Number regex: `0.02`, `1`, `2`, `3.0` etc. Deliberately non-global: a\n * shared global regex leaks `lastIndex` across `exec` calls and would skip\n * the second price cell (the pro price). */\nconst PRICE_RE = /(\\d+(?:\\.\\d+)?)\\s*元/\n\n/** English price regex: matches `$0.02`, `$0.002`, `0.02` etc. */\nconst PRICE_RE_EN = /\\$?\\s*(\\d+(?:\\.\\d+)?)/\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(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** Parse one price cell text like `0.02元` into a number; undefined when absent. */\nfunction parsePriceCell(text: string): number | undefined {\n const match = PRICE_RE.exec(text)\n if (match === null) return undefined\n const value = Number(match[1])\n return Number.isFinite(value) ? value : undefined\n}\n\n/** The second price cell of a row (the pro price), stripped of the first. */\n\n/** Parse one English price cell text like `$0.02` into a number. */\nfunction parsePriceCellUsd(text: string): number | undefined {\n const match = PRICE_RE_EN.exec(text)\n if (match === null) return undefined\n const value = Number(match[1])\n return Number.isFinite(value) ? value : undefined\n}\n\n/** The second price cell of an English row, stripped of the first. */\nfunction secondPriceCellUsd(text: string): string {\n return text.replace(/^\\s*\\$?\\s*(\\d+(?:\\.\\d+)?)/, '')\n}\nfunction secondPriceCell(text: string): string {\n return text.replace(/^\\s*(\\d+(?:\\.\\d+)?元)/, '')\n}\n\n/**\n * Parse the current single-price table: three rows labeled with the bucket\n * names, each carrying the flash and pro price 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 const hit = /百万tokens输入(缓存命中)([\\s\\S]{0,400}?)百万tokens输入(缓存未命中)([\\s\\S]{0,400}?)百万tokens输出([\\s\\S]{0,400}?)(?:并发限制|<\\/table)/i.exec(stripHtml(html))\n if (hit === null) return undefined\n const cacheReadCell = hit[1] ?? ''\n const inputCell = hit[2] ?? ''\n const outputCell = hit[3] ?? ''\n const cacheReadFlash = parsePriceCell(cacheReadCell)\n const cacheReadPro = parsePriceCell(secondPriceCell(cacheReadCell))\n const inputFlash = parsePriceCell(inputCell)\n const inputPro = parsePriceCell(secondPriceCell(inputCell))\n const outputFlash = parsePriceCell(outputCell)\n const outputPro = parsePriceCell(secondPriceCell(outputCell))\n if (cacheReadFlash === undefined || inputFlash === undefined || outputFlash === undefined) {\n return undefined\n }\n return {\n flash: {\n cacheReadPerMillion: cacheReadFlash,\n inputPerMillion: inputFlash,\n outputPerMillion: outputFlash,\n },\n pro: {\n cacheReadPerMillion: cacheReadPro ?? cacheReadFlash,\n inputPerMillion: inputPro ?? inputFlash,\n outputPerMillion: outputPro ?? outputFlash,\n },\n }\n}\n\n/**\n * Parse the upcoming peak-pricing table: model rows with off-peak and peak\n * cells, e.g. `deepseek-v4-flash 空闲时段 0.05 1.5 4.5 高峰时段 0.10 3.0 9.0`.\n * @param html - the raw pricing page.\n * @returns the parsed table, or undefined when either model row is absent.\n */\nexport function parsePeakTable(html: string): PeakPricing | undefined {\n const text = stripHtml(html)\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 return {\n flash: {\n offPeak: {\n cacheReadPerMillion: Number(flash[1]),\n inputPerMillion: Number(flash[2]),\n outputPerMillion: Number(flash[3]),\n },\n peak: {\n cacheReadPerMillion: Number(flash[4]),\n inputPerMillion: Number(flash[5]),\n outputPerMillion: Number(flash[6]),\n },\n },\n pro: {\n offPeak: {\n cacheReadPerMillion: Number(pro[1]),\n inputPerMillion: Number(pro[2]),\n outputPerMillion: Number(pro[3]),\n },\n peak: {\n cacheReadPerMillion: Number(pro[4]),\n inputPerMillion: Number(pro[5]),\n outputPerMillion: Number(pro[6]),\n },\n },\n }\n}\n\n/**\n * Parse the English current single-price table.\n * @param html - the raw English pricing page.\n * @returns the parsed table, or undefined when the labels are absent.\n */\n/**\n * Parse the English combined pricing table. The English page lists all three\n * buckets as rows with OFF-PEAK and PEAK cells, so we extract both the current\n * off-peak list and the peak table from the same markup.\n */\nfunction parseEnglishPricing(html: string): { current: CurrentPricing; peak: PeakPricing } | undefined {\n const text = stripHtml(html)\n const hit = /1M INPUT TOKENS \\(CACHE HIT\\)\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+1M INPUT TOKENS \\(CACHE MISS\\)\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+1M OUTPUT TOKENS\\s+OFF-PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+PEAK\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)\\s+\\$?\\s*(\\d+(?:\\.\\d+)?)/i.exec(text)\n if (hit === null) return undefined\n const n = (index: number): number => Number(hit[index])\n const current: CurrentPricing = {\n flash: {\n cacheReadPerMillion: n(1),\n inputPerMillion: n(5),\n outputPerMillion: n(9),\n },\n pro: {\n cacheReadPerMillion: n(2),\n inputPerMillion: n(6),\n outputPerMillion: n(10),\n },\n }\n const peak: PeakPricing = {\n flash: {\n offPeak: {\n cacheReadPerMillion: n(1),\n inputPerMillion: n(5),\n outputPerMillion: n(9),\n },\n peak: {\n cacheReadPerMillion: n(3),\n inputPerMillion: n(7),\n outputPerMillion: n(11),\n },\n },\n pro: {\n offPeak: {\n cacheReadPerMillion: n(2),\n inputPerMillion: n(6),\n outputPerMillion: n(10),\n },\n peak: {\n cacheReadPerMillion: n(4),\n inputPerMillion: n(8),\n outputPerMillion: n(12),\n },\n },\n }\n return { current, peak }\n}\n\n/**\n * Parse the English current single-price 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 upcoming 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 const label = locale === 'zh' ? '高峰时段' : /peak\\s*hours?/i\n const labelIndex = typeof label === 'string' ? text.indexOf(label) : text.search(label)\n if (labelIndex < 0) return undefined\n const window = text.slice(labelIndex, labelIndex + 200)\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 parseCurrent = locale === 'en' ? parseCurrentTableEn : parseCurrentTable\n const parsePeak = locale === 'en' ? parsePeakTableEn : parsePeakTable\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 current = parseCurrent(html)\n if (current === undefined) {\n return { fetchedAt, currency, current: fallbackCurrent, peakActive: false, effective: fallbackCurrent, schedule: fallbackSchedule, error: 'pricing table not found' }\n }\n const peak = parsePeak(html)\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 ...(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: keyof CurrentPricing,\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)}¤cy=${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)}¤cy=${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)}¤cy=${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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAS3E,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAOnE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,2CAA2C;QAC3C,YAAY,EAAE,mBAAmB,CAAA;KAClC;CACF;AAED,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,eAAe,CAAA;AAE9C,6FAA6F;AAC7F,eAAO,MAAM,MAAM,UAAmD,CAAA;AAEtE;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAS3E,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAOnE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,2CAA2C;QAC3C,YAAY,EAAE,mBAAmB,CAAA;KAClC;CACF;AAED,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,eAAe,CAAA;AAE9C,6FAA6F;AAC7F,eAAO,MAAM,MAAM,UAAmD,CAAA;AAEtE;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiE9C"}
|
|
@@ -48,11 +48,12 @@ export function apply(ctx) {
|
|
|
48
48
|
locale: NS,
|
|
49
49
|
}, SessionCostPill));
|
|
50
50
|
// Plugin configuration card (设置 → 插件), standard plugin-card shape over
|
|
51
|
-
// the `cost-meter` settings namespace.
|
|
51
|
+
// the `cost-meter` settings namespace. The slot is keyed by the settings
|
|
52
|
+
// namespace the card edits, so the tab pairs this card with the Host-served
|
|
53
|
+
// `cost-meter` namespace.
|
|
52
54
|
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
|
53
55
|
name: 'settings.plugin.item',
|
|
54
|
-
|
|
55
|
-
order: 30,
|
|
56
|
+
key: SETTINGS_NAMESPACE,
|
|
56
57
|
locale: NS,
|
|
57
58
|
inject: () => ({ scope }),
|
|
58
59
|
}, CostPluginCard));
|
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.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.7.0",
|
|
7
7
|
"engines": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.5 <0.2.0",
|
|
92
92
|
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.5 <0.2.0",
|
|
93
93
|
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.5 <0.2.0",
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.0-rc.
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.0-rc.7 <0.2.0",
|
|
95
95
|
"@deepseek-ai/dsh-token-meter": ">=0.1.0-rc.5 <0.2.0",
|
|
96
96
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
97
97
|
"@types/node": "^22.20.0",
|
package/src/client/index.ts
CHANGED
|
@@ -94,13 +94,14 @@ export function apply(ctx: ClientContext): void {
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
// Plugin configuration card (设置 → 插件), standard plugin-card shape over
|
|
97
|
-
// the `cost-meter` settings namespace.
|
|
97
|
+
// the `cost-meter` settings namespace. The slot is keyed by the settings
|
|
98
|
+
// namespace the card edits, so the tab pairs this card with the Host-served
|
|
99
|
+
// `cost-meter` namespace.
|
|
98
100
|
ctx.slots.inject(
|
|
99
101
|
'settings.plugin.item',
|
|
100
102
|
() => ctx.slots.register({
|
|
101
103
|
name: 'settings.plugin.item',
|
|
102
|
-
|
|
103
|
-
order: 30,
|
|
104
|
+
key: SETTINGS_NAMESPACE,
|
|
104
105
|
locale: NS,
|
|
105
106
|
inject: () => ({ scope }),
|
|
106
107
|
}, CostPluginCard),
|