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 +6 -5
- package/README.md +6 -5
- package/lib/backfill.js +134 -1
- package/lib/client.js +45 -0
- package/lib/index.js +29 -1
- package/lib/typert.host.js +10 -0
- package/package.json +1 -1
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
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](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.
|
|
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.
|
|
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.
|
|
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.
|
|
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) {
|
package/lib/typert.host.js
CHANGED
|
@@ -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.
|
|
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",
|