dsh-cost-meter 1.5.7 → 1.5.8

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.7-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.8-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)
@@ -191,22 +191,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
191
191
  dsh plugin --profile web add dsh-cost-meter
192
192
  ```
193
193
 
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.7`** — review the script before running):
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.8`** — review the script before running):
195
195
 
196
196
  ```powershell
197
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.7/install.ps1 | iex
197
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.8/install.ps1 | iex
198
198
  ```
199
199
 
200
200
  **Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
201
201
 
202
202
  ```sh
203
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.7
203
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.8
204
204
  ```
205
205
 
206
206
  Without git, use the GitHub tag archive:
207
207
 
208
208
  ```sh
209
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.7.tar.gz
209
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.8.tar.gz
210
210
  ```
211
211
 
212
212
  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.7-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.8-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)
@@ -193,22 +193,22 @@
193
193
  dsh plugin --profile web add dsh-cost-meter
194
194
  ```
195
195
 
196
- **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.7`**,建议先下载审阅再运行):
196
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.8`**,建议先下载审阅再运行):
197
197
 
198
198
  ```powershell
199
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.7/install.ps1 | iex
199
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.8/install.ps1 | iex
200
200
  ```
201
201
 
202
202
  **或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
203
203
 
204
204
  ```sh
205
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.7
205
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.8
206
206
  ```
207
207
 
208
208
  没有 git 时可用 GitHub tag 打包直链:
209
209
 
210
210
  ```sh
211
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.7.tar.gz
211
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.8.tar.gz
212
212
  ```
213
213
 
214
214
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
package/lib/client.js CHANGED
@@ -77,6 +77,8 @@ window.__ModuleLoader__.load({
77
77
  '.cm-table th{color:var(--dsw-alias-label-tertiary);font-weight:500}',
78
78
  '.cm-table td.num,.cm-table th.num{text-align:right;font-variant-numeric:tabular-nums}',
79
79
  '.cm-table tr:last-child td{border-bottom:none}',
80
+ '.cm-table tr.cm-row-click{cursor:pointer}',
81
+ '.cm-table tr.cm-row-click:hover td{background:var(--dsw-alias-bg-hover,rgba(127,127,127,.08))}',
80
82
  '.cm-empty{font-size:12px;color:var(--dsw-alias-label-tertiary);padding:8px 0}',
81
83
  '.cm-scroll{max-height:320px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:10px}',
82
84
  '.cm-grid{display:grid;grid-template-columns:repeat(2,minmax(220px,1fr));gap:12px}',
@@ -481,6 +483,16 @@ window.__ModuleLoader__.load({
481
483
  cardTotal: '累计费用',
482
484
  cardTotalSub: '自账本建立以来 · 调用 {calls} 次',
483
485
  todaySessions: '今日会话',
486
+ historyExpandHint: '点击日期行可展开当日会话明细',
487
+ historySessionsLoading: '会话明细加载中…',
488
+ historyNoSessions: '该日无会话明细(早期数据或会话日志已清理)',
489
+ historySessionsError: '会话明细加载失败',
490
+ sessionRankTitle: '按会话统计(全部历史)',
491
+ sessionRankHint: '不按日期分组,全部历史会话按费用降序',
492
+ sessionRankEmpty: '暂无会话数据',
493
+ sessionRankLoading: '会话排行加载中…',
494
+ sessionRankError: '会话排行加载失败',
495
+ sessionRankLimit: '显示条数',
484
496
  history: '历史记录',
485
497
  usageTitle: 'Token 用量统计',
486
498
  usageTotal: '累计 {tokens} tokens · 输入 {input} · 缓存 {cache} · 输出 {output} · {calls} 次调用',
@@ -757,6 +769,16 @@ window.__ModuleLoader__.load({
757
769
  cardTotal: 'All time',
758
770
  cardTotalSub: 'Since the ledger was created · Calls {calls}',
759
771
  todaySessions: "Today's sessions",
772
+ historyExpandHint: 'Click a date row to expand its session details',
773
+ historySessionsLoading: 'Loading session details…',
774
+ historyNoSessions: 'No session details for this day (early data or session logs cleaned)',
775
+ historySessionsError: 'Failed to load session details',
776
+ sessionRankTitle: 'By session (all history)',
777
+ sessionRankHint: 'All historical sessions sorted by cost, not grouped by date',
778
+ sessionRankEmpty: 'No session data yet',
779
+ sessionRankLoading: 'Loading session ranking…',
780
+ sessionRankError: 'Failed to load session ranking',
781
+ sessionRankLimit: 'Rows',
760
782
  history: 'History',
761
783
  usageTitle: 'Token usage stats',
762
784
  usageTotal: 'All-time {tokens} tokens · input {input} · cache {cache} · output {output} · {calls} calls',
@@ -1143,6 +1165,22 @@ window.__ModuleLoader__.load({
1143
1165
  if (typeof v !== 'string') fail('provider', 'string')
1144
1166
  return v
1145
1167
  })
1168
+ const dateCodec = codecOf(v => {
1169
+ if (typeof v !== 'string') fail('date', 'string')
1170
+ return v
1171
+ })
1172
+ const dayCodec = codecOf(v => {
1173
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) fail('day', 'object')
1174
+ return v
1175
+ })
1176
+ const limitCodec = codecOf(v => {
1177
+ if (!Number.isFinite(Number(v))) fail('limit', 'number')
1178
+ return Number(v)
1179
+ })
1180
+ const topSessionsCodec = codecOf(v => {
1181
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) fail('topSessions', 'object')
1182
+ return v
1183
+ })
1146
1184
 
1147
1185
  // ── RPC 贡献(与服务端 ./typert 清单一一对应) ───────────────────────────
1148
1186
 
@@ -1191,6 +1229,18 @@ window.__ModuleLoader__.load({
1191
1229
  invocation: { kind: 'direct' }, parameters: [],
1192
1230
  result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#CostState', schema: stateCodec },
1193
1231
  },
1232
+ {
1233
+ id: 'dsh-cost-meter#costMeter/getDaySessions', service: 'costMeter', namespace: 'costMeter', method: 'getDaySessions',
1234
+ invocation: { kind: 'direct' },
1235
+ parameters: [{ name: 'date', wire: 'date', source: 'json', codec: { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayKey', schema: dateCodec } }],
1236
+ result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayRecord', schema: dayCodec },
1237
+ },
1238
+ {
1239
+ id: 'dsh-cost-meter#costMeter/getTopSessions', service: 'costMeter', namespace: 'costMeter', method: 'getTopSessions',
1240
+ invocation: { kind: 'direct' },
1241
+ parameters: [{ name: 'limit', wire: 'limit', source: 'json', codec: { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionLimit', schema: limitCodec } }],
1242
+ result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#TopSessions', schema: topSessionsCodec },
1243
+ },
1194
1244
  ],
1195
1245
  }
1196
1246
 
@@ -2281,23 +2331,123 @@ window.__ModuleLoader__.load({
2281
2331
  }
2282
2332
 
2283
2333
  function HistoryTable(props) {
2284
- const { state } = props
2334
+ const { state, api } = props
2285
2335
  const t = makeT(resolveLocale(state.config?.locale))
2336
+ // 点击日期行展开当日会话明细(issue #22):按需经 getDaySessions 拉取并缓存。
2337
+ const [openDate, setOpenDate] = useState(null)
2338
+ const [cache, setCache] = useState({})
2339
+ const [busyDate, setBusyDate] = useState(null)
2286
2340
  const rows = state.history ?? []
2287
2341
  if (rows.length === 0) return el('p', { className: 'cm-empty' }, t('noHistory'))
2342
+ const toggle = date => {
2343
+ if (openDate === date) { setOpenDate(null); return }
2344
+ setOpenDate(date)
2345
+ if (cache[date] !== undefined || busyDate !== null) return
2346
+ setBusyDate(date)
2347
+ api.getDaySessions(date)
2348
+ .then(day => setCache(c => ({ ...c, [date]: Array.isArray(day?.sessions) ? day.sessions : [] })))
2349
+ .catch(() => setCache(c => ({ ...c, [date]: 'error' })))
2350
+ .finally(() => setBusyDate(null))
2351
+ }
2352
+ const sessionRows = sessions => sessions.map(session => el('tr', { key: session.id },
2353
+ el('td', null, session.id.slice(0, 14) + '…'),
2354
+ el('td', { className: 'num' }, String(session.calls)),
2355
+ el('td', { className: 'num' }, formatTokens(session.input)),
2356
+ el('td', { className: 'num' }, formatTokens(session.cacheRead + session.cacheWrite)),
2357
+ el('td', { className: 'num' }, formatTokens(session.output)),
2358
+ el('td', { className: 'num' }, formatMoneyUsd(session.cost, state.config))))
2288
2359
  return el('div', { className: 'cm-scroll' },
2360
+ el('p', { className: 'cm-hint', style: { padding: '6px 10px 0' } }, t('historyExpandHint')),
2289
2361
  el('table', { className: 'cm-table' },
2290
2362
  el('thead', null, el('tr', null,
2291
2363
  el('th', null, t('colDate')), el('th', { className: 'num' }, t('colCalls')),
2292
2364
  el('th', { className: 'num' }, t('colInTok')), el('th', { className: 'num' }, t('colCacheTok')), el('th', { className: 'num' }, t('colOutTok')),
2293
2365
  el('th', { className: 'num' }, t('colCost')))),
2294
- el('tbody', null, rows.map(day => el('tr', { key: day.date },
2295
- el('td', null, day.date),
2296
- el('td', { className: 'num' }, String(day.calls)),
2297
- el('td', { className: 'num' }, formatTokens(day.input)),
2298
- el('td', { className: 'num' }, formatTokens(day.cacheRead + day.cacheWrite)),
2299
- el('td', { className: 'num' }, formatTokens(day.output)),
2300
- el('td', { className: 'num' }, formatMoneyUsd(day.cost, state.config)))))))
2366
+ el('tbody', null, rows.flatMap(day => {
2367
+ const base = el('tr', { key: day.date, className: 'cm-row-click', onClick: () => toggle(day.date) },
2368
+ el('td', null, (openDate === day.date ? '▾ ' : '') + day.date),
2369
+ el('td', { className: 'num' }, String(day.calls)),
2370
+ el('td', { className: 'num' }, formatTokens(day.input)),
2371
+ el('td', { className: 'num' }, formatTokens(day.cacheRead + day.cacheWrite)),
2372
+ el('td', { className: 'num' }, formatTokens(day.output)),
2373
+ el('td', { className: 'num' }, formatMoneyUsd(day.cost, state.config)))
2374
+ if (openDate !== day.date) return [base]
2375
+ const cached = cache[day.date]
2376
+ const detail = busyDate === day.date && cached === undefined
2377
+ ? el('p', { className: 'cm-empty' }, t('historySessionsLoading'))
2378
+ : cached === 'error'
2379
+ ? el('p', { className: 'cm-empty' }, t('historySessionsError'))
2380
+ : !Array.isArray(cached) || cached.length === 0
2381
+ ? el('p', { className: 'cm-empty' }, t('historyNoSessions'))
2382
+ : el('table', { className: 'cm-table' },
2383
+ el('thead', null, el('tr', null,
2384
+ el('th', null, t('colSession')), el('th', { className: 'num' }, t('colCalls')),
2385
+ el('th', { className: 'num' }, t('colInTok')), el('th', { className: 'num' }, t('colCacheTok')), el('th', { className: 'num' }, t('colOutTok')),
2386
+ el('th', { className: 'num' }, t('colCost')))),
2387
+ el('tbody', null, sessionRows(cached)))
2388
+ return [base, el('tr', { key: day.date + '-sessions' }, el('td', { colSpan: 6 }, detail))]
2389
+ }))))
2390
+ }
2391
+
2392
+ // 按会话统计(issue #22 不分日期视角):全部历史会话按费用降序,默认收起、展开时按需拉取。
2393
+ function SessionRankPanel(props) {
2394
+ const { state, api } = props
2395
+ const t = makeT(resolveLocale(state.config?.locale))
2396
+ const [open, setOpen] = useState(false)
2397
+ const [limit, setLimit] = useState(100)
2398
+ const [rows, setRows] = useState(null)
2399
+ const [busy, setBusy] = useState(false)
2400
+ const [err, setErr] = useState(false)
2401
+ const load = n => {
2402
+ setBusy(true)
2403
+ setErr(false)
2404
+ api.getTopSessions(n)
2405
+ .then(res => setRows(Array.isArray(res?.sessions) ? res.sessions : []))
2406
+ .catch(() => setErr(true))
2407
+ .finally(() => setBusy(false))
2408
+ }
2409
+ const toggle = () => {
2410
+ setOpen(o => {
2411
+ const next = !o
2412
+ if (next && rows === null && !busy) load(limit)
2413
+ return next
2414
+ })
2415
+ }
2416
+ const changeLimit = n => {
2417
+ setLimit(n)
2418
+ if (open || rows !== null) load(n)
2419
+ }
2420
+ return el('div', { className: 'cm-budget' },
2421
+ el('div', { className: 'cm-budget-head' },
2422
+ el('button', { type: 'button', className: 'cm-collapse-h', 'aria-expanded': String(open), onClick: toggle },
2423
+ el('span', { className: 'cm-caret' + (open ? ' open' : '') }),
2424
+ el('h3', { className: 'cm-h' }, t('sessionRankTitle')))),
2425
+ open ? el('div', { className: 'cm-collapse-body' },
2426
+ el('p', { className: 'cm-hint' }, t('sessionRankHint')),
2427
+ el('div', { className: 'cm-field', style: { margin: '6px 0' } },
2428
+ el('label', null, t('sessionRankLimit')),
2429
+ el('select', { className: 'cm-input', value: String(limit), onChange: event => changeLimit(Number(event.target.value)) },
2430
+ el('option', { value: '50' }, '50'),
2431
+ el('option', { value: '100' }, '100'),
2432
+ el('option', { value: '200' }, '200'))),
2433
+ busy ? el('p', { className: 'cm-empty' }, t('sessionRankLoading'))
2434
+ : err ? el('p', { className: 'cm-empty' }, t('sessionRankError'))
2435
+ : rows === null || rows.length === 0 ? el('p', { className: 'cm-empty' }, t('sessionRankEmpty'))
2436
+ : el('div', { className: 'cm-scroll' },
2437
+ el('table', { className: 'cm-table' },
2438
+ el('thead', null, el('tr', null,
2439
+ el('th', null, t('colDate')), el('th', null, t('colSession')), el('th', { className: 'num' }, t('colCalls')),
2440
+ el('th', { className: 'num' }, t('colInTok')), el('th', { className: 'num' }, t('colCacheTok')), el('th', { className: 'num' }, t('colOutTok')),
2441
+ el('th', { className: 'num' }, t('colCost')))),
2442
+ el('tbody', null, rows.map(session => el('tr', { key: session.date + ':' + session.id },
2443
+ el('td', null, session.date),
2444
+ el('td', null, String(session.id).slice(0, 14) + '…'),
2445
+ el('td', { className: 'num' }, String(session.calls)),
2446
+ el('td', { className: 'num' }, formatTokens(session.input)),
2447
+ el('td', { className: 'num' }, formatTokens((session.cacheRead ?? 0) + (session.cacheWrite ?? 0))),
2448
+ el('td', { className: 'num' }, formatTokens(session.output)),
2449
+ el('td', { className: 'num' }, formatMoneyUsd(session.cost, state.config))))))))
2450
+ : null)
2301
2451
  }
2302
2452
 
2303
2453
  function TodaySessions(props) {
@@ -3589,7 +3739,9 @@ window.__ModuleLoader__.load({
3589
3739
  // 历史
3590
3740
  el('div', null,
3591
3741
  el('h3', { className: 'cm-h' }, t('history')),
3592
- el(HistoryTable, { state, t })),
3742
+ el(HistoryTable, { state, api })),
3743
+ // 按会话统计(全部历史,不分日期;issue #22)
3744
+ el(SessionRankPanel, { state, api }),
3593
3745
  // 显示设置
3594
3746
  el('div', null,
3595
3747
  el('h3', { className: 'cm-h' }, t('displaySettings')),
@@ -4005,6 +4157,10 @@ window.__ModuleLoader__.load({
4005
4157
  store.set({ status: 'ready', error: null, state })
4006
4158
  return state
4007
4159
  },
4160
+ // 按需拉取某天会话明细(issue #22),返回当日完整记录。
4161
+ getDaySessions: async date => call('getDaySessions', [date]),
4162
+ // 跨全部日期按费用降序的会话排行(issue #22 不分日期视角)。
4163
+ getTopSessions: async limit => call('getTopSessions', [limit]),
4008
4164
  refreshBalance: async () => {
4009
4165
  const result = await costMeter.refreshBalance()
4010
4166
  if (result === null || typeof result !== 'object' || result.ok !== true) {
package/lib/index.js CHANGED
@@ -18,7 +18,7 @@ import fs from 'node:fs'
18
18
  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
- import { Ledger, applyConfigPatch, localDayKey, reconcileBalanceDelta } from './store.js'
21
+ import { Ledger, applyConfigPatch, localDayKey, reconcileBalanceDelta, zeroDay } from './store.js'
22
22
  import { backfillLegacyLedger } 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, emptyCustomBalance, queryCustomBalance } from './coding-plans.js'
@@ -898,6 +898,42 @@ function createService(ctx, ledger) {
898
898
  ledger.scheduleWrite()
899
899
  return build(false)
900
900
  },
901
+
902
+ // 按需读取某一天的完整记录(含会话明细;issue #22):history() 输出为轻量副本
903
+ // 不含会话,历史各天的会话明细由本 RPC 展开时才拉取,避免 state 膨胀。
904
+ async getDaySessions(date) {
905
+ if (typeof date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(date)) {
906
+ throw new Error('invalid date')
907
+ }
908
+ const day = ledger.days[date]
909
+ return day === undefined ? zeroDay(date) : ledger.copyDay(day)
910
+ },
911
+
912
+ // 跨全部日期按费用降序返回前 N 个会话(issue #22 按会话视角,不分日期)。
913
+ async getTopSessions(limit) {
914
+ const n = Math.max(1, Math.min(500, Math.floor(Number(limit)) || 100))
915
+ const all = []
916
+ for (const [date, day] of Object.entries(ledger.days)) {
917
+ if (!Array.isArray(day.sessions)) continue
918
+ for (const s of day.sessions) {
919
+ if (s === null || typeof s !== 'object') continue
920
+ all.push({
921
+ date,
922
+ id: String(s.id ?? ''),
923
+ input: s.input ?? 0,
924
+ output: s.output ?? 0,
925
+ cacheRead: s.cacheRead ?? 0,
926
+ cacheWrite: s.cacheWrite ?? 0,
927
+ reasoning: s.reasoning ?? 0,
928
+ calls: s.calls ?? 0,
929
+ cost: s.cost ?? 0,
930
+ byProviderModel: s.byProviderModel ?? {},
931
+ })
932
+ }
933
+ }
934
+ all.sort((a, b) => b.cost - a.cost)
935
+ return { sessions: all.slice(0, n) }
936
+ },
901
937
  }
902
938
  Object.defineProperty(service, 'typertRemote', {
903
939
  configurable: false,
package/lib/store.js CHANGED
@@ -125,7 +125,7 @@ export function localDayKey(ms) {
125
125
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
126
126
  }
127
127
 
128
- function zeroDay(date) {
128
+ export function zeroDay(date) {
129
129
  return { date, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0, byProviderModel: {}, sessions: [] }
130
130
  }
131
131
 
@@ -33,6 +33,13 @@ const daySchema = z.object({
33
33
  sessions: z.array(sessionSchema),
34
34
  })
35
35
 
36
+ // 带所属日期的会话条目(issue #22 按会话排行:不分日期视角)。
37
+ const datedSessionSchema = sessionSchema.extend({ date: z.string() })
38
+
39
+ const topSessionsSchema = z.object({
40
+ sessions: z.array(datedSessionSchema),
41
+ })
42
+
36
43
  const priceTierSchema = z.object({
37
44
  cacheHit: num,
38
45
  cacheMiss: num,
@@ -241,6 +248,10 @@ const _state$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#CostState', s
241
248
  const _patch$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#ConfigPatch', schema: patchSchema }
242
249
  const _fetch$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#FetchPricesResult', schema: fetchPricesSchema }
243
250
  const _provider$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#CodingPlanProvider', schema: z.string() }
251
+ const _day$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayRecord', schema: daySchema }
252
+ const _date$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayKey', schema: z.string() }
253
+ const _topSessions$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#TopSessions', schema: topSessionsSchema }
254
+ const _limit$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionLimit', schema: num }
244
255
 
245
256
  export const TYPERT = {
246
257
  package: 'dsh-cost-meter',
@@ -323,6 +334,28 @@ export const TYPERT = {
323
334
  parameters: [],
324
335
  result: _state$codec,
325
336
  },
337
+ {
338
+ id: 'dsh-cost-meter#costMeter/getDaySessions',
339
+ service: 'costMeter',
340
+ namespace: 'costMeter',
341
+ method: 'getDaySessions',
342
+ invocation: { kind: 'direct' },
343
+ parameters: [
344
+ { name: 'date', wire: 'date', source: 'json', codec: _date$codec },
345
+ ],
346
+ result: _day$codec,
347
+ },
348
+ {
349
+ id: 'dsh-cost-meter#costMeter/getTopSessions',
350
+ service: 'costMeter',
351
+ namespace: 'costMeter',
352
+ method: 'getTopSessions',
353
+ invocation: { kind: 'direct' },
354
+ parameters: [
355
+ { name: 'limit', wire: 'limit', source: 'json', codec: _limit$codec },
356
+ ],
357
+ result: _topSessions$codec,
358
+ },
326
359
  ],
327
360
  model: {
328
361
  services: [
@@ -390,6 +423,20 @@ export const TYPERT = {
390
423
  summary: '清空全部历史记录。Clear all history records.',
391
424
  jsDoc: '/**\n * 清空全部历史记录。\n * @returns 清空后的完整快照。\n * Clear all history records.\n * @returns The full snapshot after clearing.\n */',
392
425
  },
426
+ {
427
+ kind: 'method',
428
+ name: 'getDaySessions',
429
+ signature: 'getDaySessions(date: string): DayRecord',
430
+ summary: '按需读取某一天的完整记录(含会话明细)。Read a day\'s full record on demand (with session details).',
431
+ jsDoc: '/**\n * 按需读取某一天的完整记录(含会话明细;历史记录列表为轻量副本不含会话)。\n * @param date - 本地日期键 YYYY-MM-DD。\n * @returns 该日完整记录;不存在时返回零值记录。\n * Read a day\'s full record on demand (with session details; the history list is a light copy without sessions).\n * @param date - Local day key YYYY-MM-DD.\n * @returns The full day record; a zero record when absent.\n */',
432
+ },
433
+ {
434
+ kind: 'method',
435
+ name: 'getTopSessions',
436
+ signature: 'getTopSessions(limit: number): TopSessions',
437
+ summary: '跨全部日期按费用降序返回前 N 个会话(不分日期视角)。Return the top N sessions by cost across all days.',
438
+ jsDoc: '/**\n * 跨全部日期按费用降序返回前 N 个会话(每条带所属日期;不含今日实时条目之外的去重)。\n * @param limit - 返回条数上限(服务端限制 1-500)。\n * @returns 会话列表(含 date 字段)。\n * Return the top N sessions by cost across all days (each tagged with its date).\n * @param limit - Max rows to return (server clamps to 1-500).\n * @returns Session list with a date field.\n */',
439
+ },
393
440
  ],
394
441
  types: [],
395
442
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cost-meter",
3
- "version": "1.5.7",
3
+ "version": "1.5.8",
4
4
  "description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(6 家)、自定义 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 6 vendors, 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",