dsh-cost-meter 1.5.8 → 1.5.9

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.8-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.9-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.8`** — 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.9`** — review the script before running):
195
195
 
196
196
  ```powershell
197
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.8/install.ps1 | iex
197
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.9/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.8
203
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.9
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.8.tar.gz
209
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.9.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.8-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.9-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.8`**,建议先下载审阅再运行):
196
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.9`**,建议先下载审阅再运行):
197
197
 
198
198
  ```powershell
199
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.8/install.ps1 | iex
199
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.9/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.8
205
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.9
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.8.tar.gz
211
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.9.tar.gz
212
212
  ```
213
213
 
214
214
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
package/lib/backfill.js CHANGED
@@ -141,7 +141,7 @@ export function listSessionLogs(root) {
141
141
  * @param records - readSessionRecords 的输出。
142
142
  * @param config - 账本配置(prices / peak* / priceMatch / priceOverrides)。
143
143
  * @param wantDates - 只统计这些日期键(YYYY-MM-DD)内的调用;null = 全部。
144
- * @returns { sessionId, days: { date: { providerKey: 桶 } } }。
144
+ * @returns { sessionId, title, days: { date: { providerKey: 桶 } } }。
145
145
  */
146
146
  export function replaySessionRecords(records, config, wantDates = null) {
147
147
  const zeroBuckets = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 })
@@ -150,6 +150,7 @@ export function replaySessionRecords(records, config, wantDates = null) {
150
150
  return Number.isFinite(n) && n > 0 ? n : 0
151
151
  }
152
152
  let sessionId = ''
153
+ let title = ''
153
154
  let provider = 'deepseek'
154
155
  let model = 'default'
155
156
  let last = null
@@ -173,6 +174,12 @@ export function replaySessionRecords(records, config, wantDates = null) {
173
174
  sessionId = event.id
174
175
  continue
175
176
  }
177
+ // 会话标题(宿主生成的 session/title 事件;同名多次时取最后一次)。
178
+ if (event.type === 'session/title') {
179
+ const nextTitle = event.data?.title
180
+ if (typeof nextTitle === 'string' && nextTitle.trim().length > 0) title = nextTitle.trim()
181
+ continue
182
+ }
176
183
  if (PACKED_ROW_TYPES.has(event.type)) continue
177
184
  if (event.type === 'request/header') {
178
185
  const nextModel = event.data?.header?.config?.model
@@ -231,7 +238,7 @@ export function replaySessionRecords(records, config, wantDates = null) {
231
238
  shift(sample, 1)
232
239
  last = sample
233
240
  }
234
- return { sessionId, days }
241
+ return { sessionId, title, days }
235
242
  }
236
243
 
237
244
  /**
@@ -241,10 +248,10 @@ export function replaySessionRecords(records, config, wantDates = null) {
241
248
  * 写入该会话当日的回放拆分。
242
249
  * @param ledger - 已打开的账本。
243
250
  * @param sessionsRoot - 宿主会话根目录($DSH_HOME/sessions)。
244
- * @returns { days, sessions, scanned } 实际填补的日期/会话数与扫描文件数。
251
+ * @returns { days, sessions, scanned, titles } 实际填补的日期/会话数、扫描文件数与补齐的会话标题数。
245
252
  */
246
253
  export async function backfillLegacyLedger(ledger, sessionsRoot) {
247
- const result = { days: 0, sessions: 0, scanned: 0 }
254
+ const result = { days: 0, sessions: 0, scanned: 0, titles: 0 }
248
255
  const needDates = new Set()
249
256
  for (const [date, day] of Object.entries(ledger.days ?? {})) {
250
257
  if ((day?.calls ?? 0) > 0 && Object.keys(day?.byProviderModel ?? {}).length === 0) needDates.add(date)
@@ -260,8 +267,20 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
260
267
  }
261
268
  }
262
269
  }
263
- if (needDates.size === 0) return result
270
+ // 会话标题补齐:任一会话条目缺标题则需扫描(日志里的 session/title 事件是标题权威来源)
271
+ let needTitles = false
272
+ for (const day of Object.values(ledger.days ?? {})) {
273
+ for (const session of day?.sessions ?? []) {
274
+ if (typeof session?.title !== 'string' || session.title.length === 0) {
275
+ needTitles = true
276
+ break
277
+ }
278
+ }
279
+ if (needTitles) break
280
+ }
281
+ if (needDates.size === 0 && !needTitles) return result
264
282
  const bySession = new Map()
283
+ const titles = new Map()
265
284
  let scannedCount = 0
266
285
  for (const path of listSessionLogs(sessionsRoot)) {
267
286
  // 会话日志多时逐份解压会长时间占住事件循环:每 8 份让出一次,不卡宿主 UI。
@@ -269,11 +288,13 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
269
288
  result.scanned += 1
270
289
  let replayed
271
290
  try {
272
- replayed = replaySessionRecords(readSessionRecords(path), ledger.config, needDates)
291
+ replayed = replaySessionRecords(readSessionRecords(path), ledger.config, needDates.size > 0 ? needDates : new Set(['-']))
273
292
  } catch {
274
293
  continue // 单文件损坏不阻断整体回填。
275
294
  }
276
295
  if (replayed.sessionId.length === 0) continue
296
+ if (replayed.title.length > 0 && !titles.has(replayed.sessionId)) titles.set(replayed.sessionId, replayed.title)
297
+ if (needDates.size === 0) continue
277
298
  const existing = bySession.get(replayed.sessionId)
278
299
  if (existing === undefined) bySession.set(replayed.sessionId, replayed.days)
279
300
  else mergeDayMaps(existing, replayed.days)
@@ -351,7 +372,20 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
351
372
  }
352
373
  }
353
374
  }
354
- if (result.days > 0 || result.sessions > 0) ledger.scheduleWrite()
375
+ // 会话标题补齐:只填缺失的标题,不覆盖已有(幂等;实时新建的会话下次启动补齐)
376
+ if (titles.size > 0) {
377
+ for (const day of Object.values(ledger.days ?? {})) {
378
+ for (const session of day?.sessions ?? []) {
379
+ if (session === null || typeof session !== 'object') continue
380
+ if (typeof session.title === 'string' && session.title.length > 0) continue
381
+ const found = titles.get(session.id)
382
+ if (found === undefined) continue
383
+ session.title = found
384
+ result.titles += 1
385
+ }
386
+ }
387
+ }
388
+ if (result.days > 0 || result.sessions > 0 || result.titles > 0) ledger.scheduleWrite()
355
389
  return result
356
390
  }
357
391
 
package/lib/client.js CHANGED
@@ -79,6 +79,9 @@ window.__ModuleLoader__.load({
79
79
  '.cm-table tr:last-child td{border-bottom:none}',
80
80
  '.cm-table tr.cm-row-click{cursor:pointer}',
81
81
  '.cm-table tr.cm-row-click:hover td{background:var(--dsw-alias-bg-hover,rgba(127,127,127,.08))}',
82
+ // 会话单元格:标题为主行(超出省略),会话 ID 为辅行(可选显示,等宽淡化)。
83
+ '.cm-sess-title{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
84
+ '.cm-sess-id{font-size:11px;font-family:ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-tertiary)}',
82
85
  '.cm-empty{font-size:12px;color:var(--dsw-alias-label-tertiary);padding:8px 0}',
83
86
  '.cm-scroll{max-height:320px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:10px}',
84
87
  '.cm-grid{display:grid;grid-template-columns:repeat(2,minmax(220px,1fr));gap:12px}',
@@ -493,6 +496,7 @@ window.__ModuleLoader__.load({
493
496
  sessionRankLoading: '会话排行加载中…',
494
497
  sessionRankError: '会话排行加载失败',
495
498
  sessionRankLimit: '显示条数',
499
+ showSessionIdLabel: '会话列表附显会话 ID',
496
500
  history: '历史记录',
497
501
  usageTitle: 'Token 用量统计',
498
502
  usageTotal: '累计 {tokens} tokens · 输入 {input} · 缓存 {cache} · 输出 {output} · {calls} 次调用',
@@ -779,6 +783,7 @@ window.__ModuleLoader__.load({
779
783
  sessionRankLoading: 'Loading session ranking…',
780
784
  sessionRankError: 'Failed to load session ranking',
781
785
  sessionRankLimit: 'Rows',
786
+ showSessionIdLabel: 'Show session IDs in session lists',
782
787
  history: 'History',
783
788
  usageTitle: 'Token usage stats',
784
789
  usageTotal: 'All-time {tokens} tokens · input {input} · cache {cache} · output {output} · {calls} calls',
@@ -2350,7 +2355,7 @@ window.__ModuleLoader__.load({
2350
2355
  .finally(() => setBusyDate(null))
2351
2356
  }
2352
2357
  const sessionRows = sessions => sessions.map(session => el('tr', { key: session.id },
2353
- el('td', null, session.id.slice(0, 14) + '…'),
2358
+ sessionCell(session, state.config?.showSessionId === true, t),
2354
2359
  el('td', { className: 'num' }, String(session.calls)),
2355
2360
  el('td', { className: 'num' }, formatTokens(session.input)),
2356
2361
  el('td', { className: 'num' }, formatTokens(session.cacheRead + session.cacheWrite)),
@@ -2441,7 +2446,7 @@ window.__ModuleLoader__.load({
2441
2446
  el('th', { className: 'num' }, t('colCost')))),
2442
2447
  el('tbody', null, rows.map(session => el('tr', { key: session.date + ':' + session.id },
2443
2448
  el('td', null, session.date),
2444
- el('td', null, String(session.id).slice(0, 14) + '…'),
2449
+ sessionCell(session, state.config?.showSessionId === true, t),
2445
2450
  el('td', { className: 'num' }, String(session.calls)),
2446
2451
  el('td', { className: 'num' }, formatTokens(session.input)),
2447
2452
  el('td', { className: 'num' }, formatTokens((session.cacheRead ?? 0) + (session.cacheWrite ?? 0))),
@@ -2455,6 +2460,7 @@ window.__ModuleLoader__.load({
2455
2460
  const t = makeT(resolveLocale(state.config?.locale))
2456
2461
  const sessions = state.today.sessions ?? []
2457
2462
  if (sessions.length === 0) return el('p', { className: 'cm-empty' }, t('noSessionsToday'))
2463
+ const showId = state.config?.showSessionId === true
2458
2464
  return el('div', { className: 'cm-scroll' },
2459
2465
  el('table', { className: 'cm-table' },
2460
2466
  el('thead', null, el('tr', null,
@@ -2462,7 +2468,7 @@ window.__ModuleLoader__.load({
2462
2468
  el('th', { className: 'num' }, t('colInTok')), el('th', { className: 'num' }, t('colCacheTok')), el('th', { className: 'num' }, t('colOutTok')),
2463
2469
  el('th', { className: 'num' }, t('colCost')))),
2464
2470
  el('tbody', null, sessions.map(session => el('tr', { key: session.id },
2465
- el('td', null, session.id.slice(0, 14) + '…'),
2471
+ sessionCell(session, showId, t),
2466
2472
  el('td', { className: 'num' }, String(session.calls)),
2467
2473
  el('td', { className: 'num' }, formatTokens(session.input)),
2468
2474
  el('td', { className: 'num' }, formatTokens(session.cacheRead + session.cacheWrite)),
@@ -2470,6 +2476,17 @@ window.__ModuleLoader__.load({
2470
2476
  el('td', { className: 'num' }, formatMoneyUsd(session.cost, state.config)))))))
2471
2477
  }
2472
2478
 
2479
+ /** 会话单元格:标题为主行(未命名回落短 id),showId 时附显等宽短 id;悬停看完整标题与 id。 */
2480
+ function sessionCell(session, showId, t) {
2481
+ const rawTitle = typeof session.title === 'string' ? session.title.trim() : ''
2482
+ const shortId = String(session.id).slice(0, 14) + '…'
2483
+ const main = rawTitle.length > 0 ? rawTitle : shortId
2484
+ const tooltip = rawTitle.length > 0 ? rawTitle + ' · ' + session.id : session.id
2485
+ return el('td', { title: tooltip },
2486
+ el('div', { className: 'cm-sess-title' + (rawTitle.length === 0 ? ' cm-sess-id' : '') }, main),
2487
+ showId && rawTitle.length > 0 ? el('div', { className: 'cm-sess-id' }, shortId) : null)
2488
+ }
2489
+
2473
2490
  const CURRENCY_PRESETS = {
2474
2491
  CNY: { symbol: '¥', decimals: 4, exchangeRate: 7.2 },
2475
2492
  USD: { symbol: '$', decimals: 6, exchangeRate: 1 },
@@ -3781,6 +3798,13 @@ window.__ModuleLoader__.load({
3781
3798
  },
3782
3799
  el('option', { value: 'on' }, t('sidebarOn')),
3783
3800
  el('option', { value: 'off' }, t('off')))),
3801
+ el('label', { className: 'cm-check' },
3802
+ el('input', {
3803
+ type: 'checkbox',
3804
+ checked: draft?.showSessionId === true,
3805
+ onChange: event => setField('showSessionId', event.target.checked),
3806
+ }),
3807
+ el('span', null, t('showSessionIdLabel'))),
3784
3808
  el('div', { className: 'cm-grid-group' }, t('groupMoney')),
3785
3809
  el('div', { className: 'cm-field' },
3786
3810
  el('label', null, t('currencyLabel')),
package/lib/index.js CHANGED
@@ -961,8 +961,13 @@ export function apply(ctx) {
961
961
  // 宿主会话日志填补空 byProviderModel(幂等,只填空条目,不重复计数)。
962
962
  const backfillTimer = setTimeout(() => {
963
963
  backfillLegacyLedger(ledger, join(resolveDshHome(), 'sessions')).then(filled => {
964
- if (filled.days > 0 || filled.sessions > 0) {
965
- console.log(`[dsh-cost-meter] 历史按模型统计回填完成:${filled.days} / ${filled.sessions} 个会话(扫描 ${filled.scanned} 份会话日志${filled.recosted > 0 ? `,重算 ${filled.recosted} 天金额` : ''})`)
964
+ if (filled.days > 0 || filled.sessions > 0 || filled.titles > 0) {
965
+ const extras = [
966
+ filled.days > 0 || filled.sessions > 0 ? `${filled.days} 天 / ${filled.sessions} 个会话` : null,
967
+ filled.titles > 0 ? `${filled.titles} 个会话标题` : null,
968
+ filled.recosted > 0 ? `重算 ${filled.recosted} 天金额` : null,
969
+ ].filter(Boolean).join(',')
970
+ console.log(`[dsh-cost-meter] 历史按模型统计回填完成:${extras}(扫描 ${filled.scanned} 份会话日志)`)
966
971
  }
967
972
  }, error => {
968
973
  console.warn(`[dsh-cost-meter] 历史按模型统计回填失败: ${String(error?.message ?? error)}`)
package/lib/store.js CHANGED
@@ -37,6 +37,7 @@ export function defaultConfig() {
37
37
  peakEffectiveAt: DEFAULT_PEAK_EFFECTIVE_AT,
38
38
  peakWindows: DEFAULT_PEAK_WINDOWS.map(w => ({ ...w })),
39
39
  peakNotice: true, // 峰时高价时段显著提示(侧边栏预算框/今日费用/设置页预算面板)
40
+ showSessionId: false, // 会话列表中附显会话 ID(默认只显示标题,需要时开启)
40
41
  peakStyle: 'compact', // 峰谷时段条样式:compact(简洁单行/竖向同构) | classic(经典分段/胶囊芯片)
41
42
  priceMatch: 'auto', // 未知模型名自动匹配价格表:auto(去后缀/前缀/家族相似) | exact(仅精确)
42
43
  priceOverrides: {}, // 手动匹配覆盖:{ 'provider:modelId': '同provider模型 | provider:模型 | deepseek:__default__' }
@@ -153,7 +154,7 @@ function sanitizeBuckets(target) {
153
154
  * 历史版本曾写入 `reasoning: null` 等非法数值,会导致 Typert strict 状态
154
155
  * codec 拒绝整个 getState 结果(账本不可用 / 额度刷新连带失败)。
155
156
  */
156
- function sanitizeDays(days) {
157
+ export function sanitizeDays(days) {
157
158
  for (const day of Object.values(days)) {
158
159
  if (sanitizeBuckets(day) === null) continue
159
160
  day.byProviderModel = day.byProviderModel !== null && typeof day.byProviderModel === 'object' && !Array.isArray(day.byProviderModel)
@@ -169,6 +170,8 @@ function sanitizeDays(days) {
169
170
  for (const session of day.sessions) {
170
171
  if (sanitizeBuckets(session) === null) continue
171
172
  session.id = typeof session.id === 'string' ? session.id : ''
173
+ // 会话标题(由历史回填从会话日志补齐):非字符串一律剔除,防击穿 strict sessionSchema。
174
+ if (typeof session.title !== 'string' || session.title.length === 0) delete session.title
172
175
  session.byProviderModel = session.byProviderModel !== null && typeof session.byProviderModel === 'object' && !Array.isArray(session.byProviderModel)
173
176
  ? session.byProviderModel
174
177
  : {}
@@ -211,6 +214,7 @@ const VALIDATION_MESSAGES = {
211
214
  peakEffectiveAt: 'peakEffectiveAt 非法',
212
215
  peakWindows: 'peakWindows 必须是数组',
213
216
  peakNotice: 'peakNotice 必须是布尔值',
217
+ showSessionId: 'showSessionId 必须是布尔值',
214
218
  peakStyle: 'peakStyle 必须是 compact / classic',
215
219
  priceMatch: 'priceMatch 必须是 auto / exact',
216
220
  priceOverrides: 'priceOverrides 必须是字符串→字符串映射',
@@ -271,6 +275,7 @@ const VALIDATION_MESSAGES = {
271
275
  peakEffectiveAt: 'Invalid peakEffectiveAt',
272
276
  peakWindows: 'peakWindows must be an array',
273
277
  peakNotice: 'peakNotice must be a boolean',
278
+ showSessionId: 'showSessionId must be a boolean',
274
279
  peakStyle: 'peakStyle must be compact / classic',
275
280
  priceMatch: 'priceMatch must be auto / exact',
276
281
  priceOverrides: 'priceOverrides must be a string→string map',
@@ -387,6 +392,7 @@ export function applyConfigPatch(current, patch) {
387
392
  if (typeof candidate.peakEffectiveAt !== 'string') errors.push(vmsg(locale, 'peakEffectiveAt'))
388
393
  if (!Array.isArray(candidate.peakWindows)) errors.push(vmsg(locale, 'peakWindows'))
389
394
  if (typeof candidate.peakNotice !== 'boolean') errors.push(vmsg(locale, 'peakNotice'))
395
+ if (candidate.showSessionId !== undefined && typeof candidate.showSessionId !== 'boolean') errors.push(vmsg(locale, 'showSessionId'))
390
396
  if (candidate.peakStyle !== 'compact' && candidate.peakStyle !== 'classic') errors.push(vmsg(locale, 'peakStyle'))
391
397
  if (candidate.priceMatch !== 'auto' && candidate.priceMatch !== 'exact') errors.push(vmsg(locale, 'priceMatch'))
392
398
  const overrides = candidate.priceOverrides
@@ -569,6 +575,7 @@ export function sanitizeConfig(raw) {
569
575
  out.priceMatch = oneOf(out.priceMatch, ['auto', 'exact'], 'auto')
570
576
  out.decimals = Math.max(0, Math.min(10, Math.floor(Number(out.decimals) || 0)))
571
577
  out.historyDays = Math.max(7, Math.min(3650, Math.floor(Number(out.historyDays) || 180)))
578
+ out.showSessionId = out.showSessionId === true
572
579
  if (!isNum(out.exchangeRate) || out.exchangeRate <= 0) out.exchangeRate = base.exchangeRate
573
580
  if (!Array.isArray(out.peakWindows)) out.peakWindows = base.peakWindows
574
581
  else out.peakWindows = out.peakWindows.filter(w => w !== null && typeof w === 'object' && isNum(Number(w.start)) && isNum(Number(w.end)))
@@ -10,6 +10,7 @@ const num = z.number()
10
10
 
11
11
  const sessionSchema = z.object({
12
12
  id: z.string(),
13
+ title: z.string().optional(),
13
14
  input: num,
14
15
  output: num,
15
16
  cacheRead: num,
@@ -101,6 +102,7 @@ const configSchema = z.object({
101
102
  peakEffectiveAt: z.string(),
102
103
  peakWindows: z.array(z.object({ start: num, end: num })),
103
104
  peakNotice: z.boolean().optional(),
105
+ showSessionId: z.boolean().optional(),
104
106
  peakStyle: z.enum(['compact', 'classic']).optional(),
105
107
  priceMatch: z.enum(['auto', 'exact']).optional(),
106
108
  priceOverrides: z.record(z.string(), z.string()).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cost-meter",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
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",