dsh-cost-meter 1.5.13 → 1.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.13-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.14-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -36,6 +36,7 @@ English | [中文](README.md)
36
36
  | Token usage stats | Settings page (Cost section) | All-time token totals (input/cache/output/calls) + a Codex-style 26-week daily usage heat grid that fills the settings width; hover a cell for that day's detail |
37
37
  | Today's sessions | Settings page | Per-session call count, input/cache/output tokens and cost |
38
38
  | History | Settings page | Per-day totals; retention days configurable (default 180) |
39
+ | Pre-install history import | Settings page (Data & sync) | One-click replay of all host session logs to import conversations from before the plugin was installed (missing dates are rebuilt whole; existing dates only gain previously unknown sessions; idempotent and never double-counts live metering; costs priced at per-event historical rates) |
39
40
  | Budget settings | Settings page, top | Limit, period (today / month / cumulative / custom date range), used % |
40
41
  | Price table | Settings page | Per-model off-peak / peak prices (input/output shorthand supported; cache prices derived automatically); fully editable |
41
42
  | Peak/off-peak hours display | Settings / budget / today | Shows UTC peak hours 01:00–04:00 and 06:00–10:00 with the current tier; expanded view shows a peak/off-peak period strip (current period + countdown), collapsed (rail) view shows a vertical peak/off-peak progress bar; independently toggleable |
@@ -191,22 +192,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
191
192
  dsh plugin --profile web add dsh-cost-meter
192
193
  ```
193
194
 
194
- **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.13`** — review the script before running):
195
+ **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.14`** — review the script before running):
195
196
 
196
197
  ```powershell
197
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.13/install.ps1 | iex
198
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.14/install.ps1 | iex
198
199
  ```
199
200
 
200
201
  **Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
201
202
 
202
203
  ```sh
203
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.13
204
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.14
204
205
  ```
205
206
 
206
207
  Without git, use the GitHub tag archive:
207
208
 
208
209
  ```sh
209
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.13.tar.gz
210
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.14.tar.gz
210
211
  ```
211
212
 
212
213
  After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow 六家)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.13-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.14-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -37,6 +37,7 @@
37
37
  | 今日会话明细 | 设置页 | 每个会话的调用次数、输入/缓存/输出 token 与费用 |
38
38
  | 历史记录 | 设置页 | 按天汇总,保留天数可配(默认 180 天) |
39
39
  | 历史按模型统计回填 | 设置页(按模型统计) | 按模型统计上线前的旧账本自动回放宿主会话日志重建逐模型 token/费用拆分(旧调用按当时基础价),日志已清理的部分归入「早期未分模型」残差行 |
40
+ | 导入安装前历史 | 设置页(数据与同步) | 一键回放宿主全部会话日志,把未装插件时期的对话导入账本(缺失日期整日重建,已有日期只补未知会话,幂等不与实时计费重复;金额按事件时刻历史价回推) |
40
41
  | 预算设置 | 设置页顶部 | 额度、周期(今日/本月/累计/自定义日期区间)、已用% |
41
42
  | 价格表 | 设置页 | 每模型 谷时/峰时 两档价格(支持 input/output 简写,缓存价自动补齐),增删改自由 |
42
43
  | 峰谷计价时段显示 | 设置页 / 预算 / 今日费用 | 显示 UTC 峰时段 01:00–04:00、06:00–10:00 与当前档位;展开态显示峰时/平价时段条(当前时段 + 倒计时),收起(rail)态显示竖向峰谷进度条,可单独开关 |
@@ -193,22 +194,22 @@
193
194
  dsh plugin --profile web add dsh-cost-meter
194
195
  ```
195
196
 
196
- **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.13`**,建议先下载审阅再运行):
197
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.14`**,建议先下载审阅再运行):
197
198
 
198
199
  ```powershell
199
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.13/install.ps1 | iex
200
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.14/install.ps1 | iex
200
201
  ```
201
202
 
202
203
  **或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
203
204
 
204
205
  ```sh
205
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.13
206
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.14
206
207
  ```
207
208
 
208
209
  没有 git 时可用 GitHub tag 打包直链:
209
210
 
210
211
  ```sh
211
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.13.tar.gz
212
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.14.tar.gz
212
213
  ```
213
214
 
214
215
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
package/lib/backfill.js CHANGED
@@ -14,7 +14,7 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'
14
14
  import { join } from 'node:path'
15
15
  import * as zlib from 'node:zlib'
16
16
  import { costOf, providerPriceEntryFor } from './pricing.js'
17
- import { localDayKey } from './store.js'
17
+ import { localDayKey, zeroDay } from './store.js'
18
18
 
19
19
  const ZSTD_MAGIC = 4247762216
20
20
  /** 打包行(文本/推理/工具调用增量游程)不含 header 与 usage,回放时跳过。 */
@@ -448,3 +448,136 @@ function cloneDayMap(pm) {
448
448
  }
449
449
  return out
450
450
  }
451
+
452
+ /** 汇总一组 provider:model 桶的合计(input/output/…/calls/cost)。 */
453
+ function sumDayMap(pm) {
454
+ return Object.values(pm).reduce((acc, b) => {
455
+ acc.input += b.input ?? 0
456
+ acc.output += b.output ?? 0
457
+ acc.cacheRead += b.cacheRead ?? 0
458
+ acc.cacheWrite += b.cacheWrite ?? 0
459
+ acc.reasoning += b.reasoning ?? 0
460
+ acc.calls += b.calls ?? 0
461
+ acc.cost += b.cost ?? 0
462
+ return acc
463
+ }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 })
464
+ }
465
+
466
+ /** 把合计桶累加到日期/会话条目的顶层字段上。 */
467
+ function addTotalsTo(target, totals) {
468
+ target.input += totals.input
469
+ target.output += totals.output
470
+ target.cacheRead += totals.cacheRead
471
+ target.cacheWrite += totals.cacheWrite
472
+ target.reasoning += totals.reasoning
473
+ target.calls += totals.calls
474
+ target.cost += totals.cost
475
+ }
476
+
477
+ /** 由回放结果构造账本会话条目(title/at 缺席时不写键,与 Typert schema 一致)。 */
478
+ function sessionEntryOf(sessionId, info, totals, pm) {
479
+ const entry = {
480
+ id: sessionId,
481
+ input: totals.input,
482
+ output: totals.output,
483
+ cacheRead: totals.cacheRead,
484
+ cacheWrite: totals.cacheWrite,
485
+ reasoning: totals.reasoning,
486
+ calls: totals.calls,
487
+ cost: totals.cost,
488
+ byProviderModel: cloneDayMap(pm),
489
+ }
490
+ if (typeof info.title === 'string' && info.title.length > 0) entry.title = info.title
491
+ if (Number.isFinite(info.createdAt) && info.createdAt > 0) entry.at = info.createdAt
492
+ return entry
493
+ }
494
+
495
+ /**
496
+ * 导入安装前历史(issue #27):回放全部会话日志,为账本中缺失的日期重建
497
+ * 费用条目。与 backfillLegacyLedger 的区别:后者只补账本已有日期的按模型
498
+ * 拆分,本函数面向「插件未运行时期」的日期,由设置页显式触发。
499
+ *
500
+ * 边界(幂等,绝不与实时计费重复计数):
501
+ * - 缺失日期(账本无条目,或为无任何用量的空日)→ 整日重建(合计 + 会话
502
+ * 明细 + 按模型拆分),金额按事件时刻计价(replaySessionRecords 内完成);
503
+ * - 已有日期 → 只追加账本完全未知的会话(id 不在当日条目中),既有会话
504
+ * 条目与合计结构不动,追加桶并入日期合计与 byProviderModel;
505
+ * - 同一会话跨安装时刻:实时条目已存在,安装前用量不计入(无法安全拆分)。
506
+ * @param ledger - 已打开的账本。
507
+ * @param sessionsRoot - 宿主会话根目录($DSH_HOME/sessions)。
508
+ * @returns { days, sessions, scanned } 重建/追加的日期数、新增会话数与扫描文件数。
509
+ */
510
+ export async function importLegacyHistory(ledger, sessionsRoot) {
511
+ const result = { days: 0, sessions: 0, scanned: 0 }
512
+ const bySession = new Map()
513
+ let scannedCount = 0
514
+ for (const path of listSessionLogs(sessionsRoot)) {
515
+ if ((scannedCount += 1) % 8 === 0) await new Promise(resolve => setImmediate(resolve))
516
+ result.scanned += 1
517
+ let replayed
518
+ try {
519
+ replayed = replaySessionRecords(readSessionRecords(path), ledger.config, null)
520
+ } catch {
521
+ continue // 单文件损坏不阻断整体导入。
522
+ }
523
+ if (replayed.sessionId.length === 0) continue
524
+ const existing = bySession.get(replayed.sessionId)
525
+ if (existing === undefined) {
526
+ bySession.set(replayed.sessionId, { title: replayed.title, createdAt: replayed.createdAt, days: replayed.days })
527
+ continue
528
+ }
529
+ mergeDayMaps(existing.days, replayed.days)
530
+ }
531
+ if (bySession.size === 0) return result
532
+ // 汇总每个日期下有实际用量的会话(桶 calls>0 才参与)。
533
+ const dateSessions = new Map()
534
+ for (const [sessionId, info] of bySession) {
535
+ for (const [date, pm] of Object.entries(info.days)) {
536
+ const totals = sumDayMap(pm)
537
+ if (totals.calls <= 0) continue
538
+ let list = dateSessions.get(date)
539
+ if (list === undefined) dateSessions.set(date, list = [])
540
+ list.push({ sessionId, info, pm, totals })
541
+ }
542
+ }
543
+ for (const [date, list] of dateSessions) {
544
+ const day = ledger.days[date]
545
+ const dayEmpty = day === undefined
546
+ || ((day.calls ?? 0) === 0
547
+ && (Array.isArray(day.sessions) ? day.sessions.every(s => (s?.calls ?? 0) === 0) : true))
548
+ if (dayEmpty) {
549
+ // 缺失/空日:整日重建(合计 + 每会话明细;空日残留的旧 sessions 一并丢弃)。
550
+ const target = zeroDay(date)
551
+ const aggregate = {}
552
+ for (const entry of list) {
553
+ target.sessions.push(sessionEntryOf(entry.sessionId, entry.info, entry.totals, entry.pm))
554
+ mergeBucketsInto(aggregate, entry.pm)
555
+ addTotalsTo(target, entry.totals)
556
+ result.sessions += 1
557
+ }
558
+ target.byProviderModel = aggregate
559
+ ledger.days[date] = target
560
+ result.days += 1
561
+ continue
562
+ }
563
+ // 已有日期:只追加账本完全未知的会话(幂等;已知会话的安装前用量不计)。
564
+ const known = new Set((Array.isArray(day.sessions) ? day.sessions : []).map(s => s?.id))
565
+ for (const entry of list) {
566
+ if (known.has(entry.sessionId)) continue
567
+ day.sessions.push(sessionEntryOf(entry.sessionId, entry.info, entry.totals, entry.pm))
568
+ addTotalsTo(day, entry.totals)
569
+ if (day.byProviderModel === null || typeof day.byProviderModel !== 'object') day.byProviderModel = {}
570
+ mergeBucketsInto(day.byProviderModel, entry.pm)
571
+ result.sessions += 1
572
+ result.days += 1
573
+ }
574
+ }
575
+ if (result.days > 0 || result.sessions > 0) {
576
+ // 日期键按升序重建(账本原序即升序,保证历史记录视图整洁)。
577
+ const ordered = {}
578
+ for (const key of Object.keys(ledger.days).sort()) ordered[key] = ledger.days[key]
579
+ ledger.days = ordered
580
+ ledger.scheduleWrite()
581
+ }
582
+ return result
583
+ }
package/lib/client.js CHANGED
@@ -554,6 +554,10 @@ window.__ModuleLoader__.load({
554
554
  cancel: '取消',
555
555
  syncFromDocs: '从官方文档同步价格',
556
556
  confirmReset: '确认清除全部历史?',
557
+ importLegacy: '导入安装前历史',
558
+ confirmImportLegacy: '确认导入?将回放全部会话日志,只补账本缺失的日期与会话,不影响已有记录。',
559
+ legacyImportFailed: '导入安装前历史失败:{message}',
560
+ legacyImportNote: '按当前价目表回推历史费用;已清理的日志无法回放,同一会话跨安装时刻时其安装前用量不计入。',
557
561
  confirmClear: '确认清除',
558
562
  clearAllHistory: '清除全部历史',
559
563
  lastSync: '最近同步:{time}',
@@ -851,6 +855,10 @@ window.__ModuleLoader__.load({
851
855
  cancel: 'Cancel',
852
856
  syncFromDocs: 'Sync prices from official docs',
853
857
  confirmReset: 'Clear all history?',
858
+ importLegacy: 'Import pre-install history',
859
+ confirmImportLegacy: 'Confirm import? Replays all session logs and only fills dates/sessions missing from the ledger; existing records are untouched.',
860
+ legacyImportFailed: 'Failed to import pre-install history: {message}',
861
+ legacyImportNote: 'Historical costs are estimated with the current price catalog; cleaned logs cannot be replayed, and for a session spanning the install moment its pre-install usage is not counted.',
854
862
  confirmClear: 'Confirm clear',
855
863
  clearAllHistory: 'Clear all history',
856
864
  lastSync: 'Last sync: {time}',
@@ -1261,6 +1269,11 @@ window.__ModuleLoader__.load({
1261
1269
  invocation: { kind: 'direct' }, parameters: [],
1262
1270
  result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#CostState', schema: stateCodec },
1263
1271
  },
1272
+ {
1273
+ id: 'dsh-cost-meter#costMeter/importLegacyHistory', service: 'costMeter', namespace: 'costMeter', method: 'importLegacyHistory',
1274
+ invocation: { kind: 'direct' }, parameters: [],
1275
+ result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#FetchPricesResult', schema: fetchCodec },
1276
+ },
1264
1277
  {
1265
1278
  id: 'dsh-cost-meter#costMeter/getDaySessions', service: 'costMeter', namespace: 'costMeter', method: 'getDaySessions',
1266
1279
  invocation: { kind: 'direct' },
@@ -3642,6 +3655,7 @@ window.__ModuleLoader__.load({
3642
3655
  const [message, setMessage] = useState(null)
3643
3656
  const [confirmFetch, setConfirmFetch] = useState(false)
3644
3657
  const [confirmReset, setConfirmReset] = useState(false)
3658
+ const [confirmImport, setConfirmImport] = useState(false)
3645
3659
  const [newModelId, setNewModelId] = useState('')
3646
3660
  const [busy, setBusy] = useState(false)
3647
3661
  // 价格表折叠开关(默认收起,保持设置页简洁;三角按钮展开)。
@@ -3741,6 +3755,21 @@ window.__ModuleLoader__.load({
3741
3755
  setConfirmReset(false)
3742
3756
  }
3743
3757
  }
3758
+ // 导入安装前历史(issue #27):回放宿主全部会话日志,补账本缺失的日期/会话。
3759
+ const doImportLegacy = async () => {
3760
+ if (busy) return
3761
+ setBusy(true)
3762
+ setMessage(null)
3763
+ try {
3764
+ const result = await api.importLegacyHistory()
3765
+ setMessage({ kind: 'ok', text: result.message })
3766
+ } catch (error) {
3767
+ setMessage({ kind: 'err', text: t('legacyImportFailed', { message: error?.message ?? String(error) }) })
3768
+ } finally {
3769
+ setBusy(false)
3770
+ setConfirmImport(false)
3771
+ }
3772
+ }
3744
3773
  const setField = (field, value) => {
3745
3774
  if (draft === null) return
3746
3775
  setDraft({ ...draft, [field]: value })
@@ -4177,6 +4206,12 @@ window.__ModuleLoader__.load({
4177
4206
  el('button', { className: 'cm-btn', onClick: doFetch, disabled: busy }, t('apply')),
4178
4207
  el('button', { className: 'cm-btn', onClick: () => setConfirmFetch(false) }, t('cancel')))
4179
4208
  : el('button', { className: 'cm-btn', onClick: () => setConfirmFetch(true), disabled: busy }, t('syncFromDocs')),
4209
+ confirmImport
4210
+ ? el(Fragment, null,
4211
+ el('span', { className: 'cm-hint' }, t('confirmImportLegacy')),
4212
+ el('button', { className: 'cm-btn', onClick: doImportLegacy, disabled: busy }, t('apply')),
4213
+ el('button', { className: 'cm-btn', onClick: () => setConfirmImport(false) }, t('cancel')))
4214
+ : el('button', { className: 'cm-btn', onClick: () => setConfirmImport(true), disabled: busy }, t('importLegacy')),
4180
4215
  confirmReset
4181
4216
  ? el(Fragment, null,
4182
4217
  el('span', { className: 'cm-hint' }, t('confirmReset')),
@@ -4186,6 +4221,7 @@ window.__ModuleLoader__.load({
4186
4221
  el('p', { className: 'cm-note' },
4187
4222
  t('lastSync', { time: config.fetchedAt !== null ? new Date(config.fetchedAt).toLocaleString() : t('neverSynced') })
4188
4223
  + t('source', { source: config.priceSource === 'official' ? t('sourceOfficial') : t('sourceBundled') })),
4224
+ el('p', { className: 'cm-note' }, t('legacyImportNote')),
4189
4225
  message !== null ? el('div', { className: 'cm-msg ' + message.kind }, message.text) : null))
4190
4226
  }
4191
4227
 
@@ -4257,6 +4293,15 @@ window.__ModuleLoader__.load({
4257
4293
  store.set({ status: 'ready', error: null, state })
4258
4294
  return state
4259
4295
  },
4296
+ // 导入安装前历史(issue #27):返回 { ok, message, state? },成功时刷新本地快照。
4297
+ importLegacyHistory: async () => {
4298
+ const result = await costMeter.importLegacyHistory()
4299
+ if (result === null || typeof result !== 'object' || result.ok !== true) {
4300
+ throw new Error(result?.error?.message ?? result?.value?.message ?? rpcT()('legacyImportFailed', { message: 'RPC failed' }))
4301
+ }
4302
+ if (result.value.state !== undefined) store.set({ status: 'ready', error: null, state: result.value.state })
4303
+ return result.value
4304
+ },
4260
4305
  // 按需拉取某天会话明细(issue #22),返回当日完整记录。
4261
4306
  getDaySessions: async date => call('getDaySessions', [date]),
4262
4307
  // 跨全部日期的会话排行(issue #22 不分日期视角):支持费用/时间升降序与实时顺序。
package/lib/index.js CHANGED
@@ -19,7 +19,7 @@ import { join } from 'node:path'
19
19
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
20
20
  import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
21
21
  import { Ledger, applyConfigPatch, localDayKey, pickBalanceInfo, reconcileBalanceDelta, zeroDay } from './store.js'
22
- import { backfillLegacyLedger } from './backfill.js'
22
+ import { backfillLegacyLedger, importLegacyHistory } from './backfill.js'
23
23
  import { OFFICIAL_PRICING_URL, normalizePrice, parsePricingHtml, costOf, priceEntryFor, providerPriceEntryFor, buildPriceCatalog } from './pricing.js'
24
24
  import { CODING_PLAN_PROVIDERS, CODING_PLAN_PROVIDER_IDS, queryCodingPlan, scnetTokenPlanWindows, emptyCustomBalance, queryCustomBalance } from './coding-plans.js'
25
25
  import { stateSchema } from './typert.host.js'
@@ -66,6 +66,9 @@ const SERVER_MESSAGES = {
66
66
  codingPlanRefreshed: '{provider} 额度已刷新',
67
67
  codingPlanQueryFailed: '{provider} 额度查询失败:{message}',
68
68
  scnetPlanCreditsInvalid: 'SCNet 月度 Credits 额度无效,请填写大于 0 的数值。',
69
+ legacyImportDone: '导入完成:更新 {days} 天、新增 {sessions} 个会话(扫描 {scanned} 份会话日志)。',
70
+ legacyImportNone: '没有可导入的安装前历史(扫描 {scanned} 份会话日志,缺失日期为空或已导入)。',
71
+ legacyImportFailed: '导入安装前历史失败:{message}',
69
72
  },
70
73
  en: {
71
74
  apiKeyMissing: 'DeepSeek API key not configured (configure it in Settings → Models, or export the {env} environment variable)',
@@ -103,6 +106,9 @@ const SERVER_MESSAGES = {
103
106
  codingPlanRefreshed: '{provider} quota refreshed',
104
107
  codingPlanQueryFailed: '{provider} quota query failed: {message}',
105
108
  scnetPlanCreditsInvalid: 'Invalid SCNet monthly credits quota; enter a value greater than 0.',
109
+ legacyImportDone: 'Import finished: {days} day(s) updated, {sessions} session(s) added (scanned {scanned} session logs).',
110
+ legacyImportNone: 'No pre-install history to import (scanned {scanned} session logs; missing dates are empty or already imported).',
111
+ legacyImportFailed: 'Failed to import pre-install history: {message}',
106
112
  },
107
113
  }
108
114
 
@@ -917,6 +923,28 @@ function createService(ctx, ledger) {
917
923
  return build(false)
918
924
  },
919
925
 
926
+ // 导入安装前历史(issue #27):回放宿主全部会话日志,为账本缺失的日期
927
+ // 重建费用条目(幂等:已有日期只追加未知会话,绝不与实时计费重复)。
928
+ async importLegacyHistory() {
929
+ const locale = localeOf(ledger.config)
930
+ try {
931
+ const stats = await importLegacyHistory(ledger, join(resolveDshHome(), 'sessions'))
932
+ const message = stats.days === 0 && stats.sessions === 0
933
+ ? tmsg(locale, 'legacyImportNone', { scanned: stats.scanned })
934
+ : tmsg(locale, 'legacyImportDone', stats)
935
+ return {
936
+ ok: true,
937
+ message,
938
+ state: await build(false),
939
+ }
940
+ } catch (error) {
941
+ return {
942
+ ok: false,
943
+ message: tmsg(locale, 'legacyImportFailed', { message: error instanceof Error ? error.message : String(error) }),
944
+ }
945
+ }
946
+ },
947
+
920
948
  // 按需读取某一天的完整记录(含会话明细;issue #22):history() 输出为轻量副本
921
949
  // 不含会话,历史各天的会话明细由本 RPC 展开时才拉取,避免 state 膨胀。
922
950
  async getDaySessions(date) {
@@ -342,6 +342,16 @@ export const TYPERT = {
342
342
  parameters: [],
343
343
  result: _state$codec,
344
344
  },
345
+ {
346
+ // 导入安装前历史(issue #27):回放全部会话日志,只补账本缺失的日期/会话。
347
+ id: 'dsh-cost-meter#costMeter/importLegacyHistory',
348
+ service: 'costMeter',
349
+ namespace: 'costMeter',
350
+ method: 'importLegacyHistory',
351
+ invocation: { kind: 'direct' },
352
+ parameters: [],
353
+ result: _fetch$codec,
354
+ },
345
355
  {
346
356
  id: 'dsh-cost-meter#costMeter/getDaySessions',
347
357
  service: 'costMeter',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cost-meter",
3
- "version": "1.5.13",
3
+ "version": "1.5.14",
4
4
  "description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(7 家,含 SCNet Token Plan 本地 Credits 计量)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条,界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 7 vendors (incl. local credits metering for the SCNet Token Plan), custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, in a bilingual (Chinese/English) UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",