dsh-cost-meter 1.5.8 → 1.5.10
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 +5 -5
- package/README.md +5 -5
- package/lib/backfill.js +52 -7
- package/lib/client.js +94 -23
- package/lib/index.js +32 -7
- package/lib/store.js +11 -1
- package/lib/typert.host.js +10 -3
- 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)
|
|
@@ -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.
|
|
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.10`** — review the script before running):
|
|
195
195
|
|
|
196
196
|
```powershell
|
|
197
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
197
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.10/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.
|
|
203
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.10
|
|
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.
|
|
209
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.10.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
|
-
[](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)
|
|
@@ -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.
|
|
196
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.10`**,建议先下载审阅再运行):
|
|
197
197
|
|
|
198
198
|
```powershell
|
|
199
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
199
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.10/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.
|
|
205
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.10
|
|
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.
|
|
211
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.10.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, createdAt, 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,8 @@ 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 = ''
|
|
154
|
+
let createdAt = 0
|
|
153
155
|
let provider = 'deepseek'
|
|
154
156
|
let model = 'default'
|
|
155
157
|
let last = null
|
|
@@ -171,6 +173,14 @@ export function replaySessionRecords(records, config, wantDates = null) {
|
|
|
171
173
|
if (event === null || typeof event !== 'object') continue
|
|
172
174
|
if (event.type === 'session' && typeof event.id === 'string') {
|
|
173
175
|
sessionId = event.id
|
|
176
|
+
const created = Number(event.createdAt)
|
|
177
|
+
if (Number.isFinite(created) && created > 0) createdAt = created
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
// 会话标题(宿主生成的 session/title 事件;同名多次时取最后一次)。
|
|
181
|
+
if (event.type === 'session/title') {
|
|
182
|
+
const nextTitle = event.data?.title
|
|
183
|
+
if (typeof nextTitle === 'string' && nextTitle.trim().length > 0) title = nextTitle.trim()
|
|
174
184
|
continue
|
|
175
185
|
}
|
|
176
186
|
if (PACKED_ROW_TYPES.has(event.type)) continue
|
|
@@ -231,7 +241,7 @@ export function replaySessionRecords(records, config, wantDates = null) {
|
|
|
231
241
|
shift(sample, 1)
|
|
232
242
|
last = sample
|
|
233
243
|
}
|
|
234
|
-
return { sessionId, days }
|
|
244
|
+
return { sessionId, title, createdAt, days }
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
/**
|
|
@@ -241,10 +251,10 @@ export function replaySessionRecords(records, config, wantDates = null) {
|
|
|
241
251
|
* 写入该会话当日的回放拆分。
|
|
242
252
|
* @param ledger - 已打开的账本。
|
|
243
253
|
* @param sessionsRoot - 宿主会话根目录($DSH_HOME/sessions)。
|
|
244
|
-
* @returns { days, sessions, scanned }
|
|
254
|
+
* @returns { days, sessions, scanned, titles } 实际填补的日期/会话数、扫描文件数与补齐的会话标题数。
|
|
245
255
|
*/
|
|
246
256
|
export async function backfillLegacyLedger(ledger, sessionsRoot) {
|
|
247
|
-
const result = { days: 0, sessions: 0, scanned: 0 }
|
|
257
|
+
const result = { days: 0, sessions: 0, scanned: 0, titles: 0 }
|
|
248
258
|
const needDates = new Set()
|
|
249
259
|
for (const [date, day] of Object.entries(ledger.days ?? {})) {
|
|
250
260
|
if ((day?.calls ?? 0) > 0 && Object.keys(day?.byProviderModel ?? {}).length === 0) needDates.add(date)
|
|
@@ -260,8 +270,21 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
|
|
|
260
270
|
}
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
|
-
|
|
273
|
+
// 会话标题/时间戳补齐:任一字段缺失则需扫描(日志里的 session/title / createdAt 是权威来源)。
|
|
274
|
+
let needTitles = false
|
|
275
|
+
for (const day of Object.values(ledger.days ?? {})) {
|
|
276
|
+
for (const session of day?.sessions ?? []) {
|
|
277
|
+
if (typeof session?.title !== 'string' || session.title.length === 0 || !Number.isFinite(Number(session?.at))) {
|
|
278
|
+
needTitles = true
|
|
279
|
+
break
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (needTitles) break
|
|
283
|
+
}
|
|
284
|
+
if (needDates.size === 0 && !needTitles) return result
|
|
264
285
|
const bySession = new Map()
|
|
286
|
+
const titles = new Map()
|
|
287
|
+
const createdAts = new Map()
|
|
265
288
|
let scannedCount = 0
|
|
266
289
|
for (const path of listSessionLogs(sessionsRoot)) {
|
|
267
290
|
// 会话日志多时逐份解压会长时间占住事件循环:每 8 份让出一次,不卡宿主 UI。
|
|
@@ -269,11 +292,14 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
|
|
|
269
292
|
result.scanned += 1
|
|
270
293
|
let replayed
|
|
271
294
|
try {
|
|
272
|
-
replayed = replaySessionRecords(readSessionRecords(path), ledger.config, needDates)
|
|
295
|
+
replayed = replaySessionRecords(readSessionRecords(path), ledger.config, needDates.size > 0 ? needDates : new Set(['-']))
|
|
273
296
|
} catch {
|
|
274
297
|
continue // 单文件损坏不阻断整体回填。
|
|
275
298
|
}
|
|
276
299
|
if (replayed.sessionId.length === 0) continue
|
|
300
|
+
if (replayed.title.length > 0 && !titles.has(replayed.sessionId)) titles.set(replayed.sessionId, replayed.title)
|
|
301
|
+
if (replayed.createdAt > 0 && !createdAts.has(replayed.sessionId)) createdAts.set(replayed.sessionId, replayed.createdAt)
|
|
302
|
+
if (needDates.size === 0) continue
|
|
277
303
|
const existing = bySession.get(replayed.sessionId)
|
|
278
304
|
if (existing === undefined) bySession.set(replayed.sessionId, replayed.days)
|
|
279
305
|
else mergeDayMaps(existing, replayed.days)
|
|
@@ -351,7 +377,26 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
|
|
|
351
377
|
}
|
|
352
378
|
}
|
|
353
379
|
}
|
|
354
|
-
|
|
380
|
+
// 会话标题/时间戳补齐:只填缺失字段,不覆盖已有(幂等;实时新建的会话下次启动补齐)。
|
|
381
|
+
if (titles.size > 0 || createdAts.size > 0) {
|
|
382
|
+
for (const day of Object.values(ledger.days ?? {})) {
|
|
383
|
+
for (const session of day?.sessions ?? []) {
|
|
384
|
+
if (session === null || typeof session !== 'object') continue
|
|
385
|
+
if ((typeof session.title !== 'string' || session.title.length === 0) && titles.size > 0) {
|
|
386
|
+
const found = titles.get(session.id)
|
|
387
|
+
if (found !== undefined) {
|
|
388
|
+
session.title = found
|
|
389
|
+
result.titles += 1
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (!Number.isFinite(Number(session.at)) && createdAts.size > 0) {
|
|
393
|
+
const foundAt = createdAts.get(session.id)
|
|
394
|
+
if (foundAt !== undefined) session.at = foundAt
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (result.days > 0 || result.sessions > 0 || result.titles > 0) ledger.scheduleWrite()
|
|
355
400
|
return result
|
|
356
401
|
}
|
|
357
402
|
|
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}',
|
|
@@ -488,11 +491,18 @@ window.__ModuleLoader__.load({
|
|
|
488
491
|
historyNoSessions: '该日无会话明细(早期数据或会话日志已清理)',
|
|
489
492
|
historySessionsError: '会话明细加载失败',
|
|
490
493
|
sessionRankTitle: '按会话统计(全部历史)',
|
|
491
|
-
sessionRankHint: '
|
|
494
|
+
sessionRankHint: '不按日期分组,全部历史会话;排序与条数可切换',
|
|
492
495
|
sessionRankEmpty: '暂无会话数据',
|
|
493
496
|
sessionRankLoading: '会话排行加载中…',
|
|
494
497
|
sessionRankError: '会话排行加载失败',
|
|
495
498
|
sessionRankLimit: '显示条数',
|
|
499
|
+
showSessionIdLabel: '会话列表附显会话 ID',
|
|
500
|
+
sessionRankSort: '排序',
|
|
501
|
+
sessionSortCostDesc: '费用 高→低',
|
|
502
|
+
sessionSortCostAsc: '费用 低→高',
|
|
503
|
+
sessionSortTimeDesc: '时间 新→旧',
|
|
504
|
+
sessionSortTimeAsc: '时间 旧→新',
|
|
505
|
+
sessionSortRecent: '实时顺序',
|
|
496
506
|
history: '历史记录',
|
|
497
507
|
usageTitle: 'Token 用量统计',
|
|
498
508
|
usageTotal: '累计 {tokens} tokens · 输入 {input} · 缓存 {cache} · 输出 {output} · {calls} 次调用',
|
|
@@ -774,11 +784,18 @@ window.__ModuleLoader__.load({
|
|
|
774
784
|
historyNoSessions: 'No session details for this day (early data or session logs cleaned)',
|
|
775
785
|
historySessionsError: 'Failed to load session details',
|
|
776
786
|
sessionRankTitle: 'By session (all history)',
|
|
777
|
-
sessionRankHint: 'All historical sessions
|
|
787
|
+
sessionRankHint: 'All historical sessions, not grouped by date; sort and row count are switchable',
|
|
778
788
|
sessionRankEmpty: 'No session data yet',
|
|
779
789
|
sessionRankLoading: 'Loading session ranking…',
|
|
780
790
|
sessionRankError: 'Failed to load session ranking',
|
|
781
791
|
sessionRankLimit: 'Rows',
|
|
792
|
+
showSessionIdLabel: 'Show session IDs in session lists',
|
|
793
|
+
sessionRankSort: 'Sort',
|
|
794
|
+
sessionSortCostDesc: 'Cost high→low',
|
|
795
|
+
sessionSortCostAsc: 'Cost low→high',
|
|
796
|
+
sessionSortTimeDesc: 'Time new→old',
|
|
797
|
+
sessionSortTimeAsc: 'Time old→new',
|
|
798
|
+
sessionSortRecent: 'Recent order',
|
|
782
799
|
history: 'History',
|
|
783
800
|
usageTitle: 'Token usage stats',
|
|
784
801
|
usageTotal: 'All-time {tokens} tokens · input {input} · cache {cache} · output {output} · {calls} calls',
|
|
@@ -1177,6 +1194,10 @@ window.__ModuleLoader__.load({
|
|
|
1177
1194
|
if (!Number.isFinite(Number(v))) fail('limit', 'number')
|
|
1178
1195
|
return Number(v)
|
|
1179
1196
|
})
|
|
1197
|
+
const sortCodec = codecOf(v => {
|
|
1198
|
+
if (typeof v !== 'string') fail('sort', 'string')
|
|
1199
|
+
return v
|
|
1200
|
+
})
|
|
1180
1201
|
const topSessionsCodec = codecOf(v => {
|
|
1181
1202
|
if (v === null || typeof v !== 'object' || Array.isArray(v)) fail('topSessions', 'object')
|
|
1182
1203
|
return v
|
|
@@ -1238,7 +1259,11 @@ window.__ModuleLoader__.load({
|
|
|
1238
1259
|
{
|
|
1239
1260
|
id: 'dsh-cost-meter#costMeter/getTopSessions', service: 'costMeter', namespace: 'costMeter', method: 'getTopSessions',
|
|
1240
1261
|
invocation: { kind: 'direct' },
|
|
1241
|
-
parameters: [
|
|
1262
|
+
parameters: [
|
|
1263
|
+
{ name: 'limit', wire: 'limit', source: 'json', codec: { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionLimit', schema: limitCodec } },
|
|
1264
|
+
{ name: 'sort', wire: 'sort', source: 'json', codec: { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionSort', schema: sortCodec } },
|
|
1265
|
+
{ name: 'dir', wire: 'dir', source: 'json', codec: { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionSortDir', schema: sortCodec } },
|
|
1266
|
+
],
|
|
1242
1267
|
result: { mode: 'strict', typeSymbol: 'dsh-cost-meter#TopSessions', schema: topSessionsCodec },
|
|
1243
1268
|
},
|
|
1244
1269
|
],
|
|
@@ -2330,6 +2355,19 @@ window.__ModuleLoader__.load({
|
|
|
2330
2355
|
el('p', { className: 'cm-card-sub' }, props.sub))
|
|
2331
2356
|
}
|
|
2332
2357
|
|
|
2358
|
+
// 历史记录折叠面板(issue #22):三角展开/收起,内部为按天表格(日期行再展开会话明细)。
|
|
2359
|
+
function HistoryPanel(props) {
|
|
2360
|
+
const { state, api } = props
|
|
2361
|
+
const t = makeT(resolveLocale(state.config?.locale))
|
|
2362
|
+
const [open, setOpen] = useState(false)
|
|
2363
|
+
return el('div', { className: 'cm-budget' },
|
|
2364
|
+
el('div', { className: 'cm-budget-head' },
|
|
2365
|
+
el('button', { type: 'button', className: 'cm-collapse-h', 'aria-expanded': String(open), onClick: () => setOpen(!open) },
|
|
2366
|
+
el('span', { className: 'cm-caret' + (open ? ' open' : '') }),
|
|
2367
|
+
el('h3', { className: 'cm-h' }, t('history')))),
|
|
2368
|
+
open ? el('div', { className: 'cm-collapse-body' }, el(HistoryTable, { state, api })) : null)
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2333
2371
|
function HistoryTable(props) {
|
|
2334
2372
|
const { state, api } = props
|
|
2335
2373
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
@@ -2350,7 +2388,7 @@ window.__ModuleLoader__.load({
|
|
|
2350
2388
|
.finally(() => setBusyDate(null))
|
|
2351
2389
|
}
|
|
2352
2390
|
const sessionRows = sessions => sessions.map(session => el('tr', { key: session.id },
|
|
2353
|
-
|
|
2391
|
+
sessionCell(session, state.config?.showSessionId === true, t),
|
|
2354
2392
|
el('td', { className: 'num' }, String(session.calls)),
|
|
2355
2393
|
el('td', { className: 'num' }, formatTokens(session.input)),
|
|
2356
2394
|
el('td', { className: 'num' }, formatTokens(session.cacheRead + session.cacheWrite)),
|
|
@@ -2389,19 +2427,22 @@ window.__ModuleLoader__.load({
|
|
|
2389
2427
|
}))))
|
|
2390
2428
|
}
|
|
2391
2429
|
|
|
2392
|
-
// 按会话统计(issue #22 不分日期视角)
|
|
2430
|
+
// 按会话统计(issue #22 不分日期视角):全部历史会话排行,默认收起、展开时按需拉取;排序可切换。
|
|
2393
2431
|
function SessionRankPanel(props) {
|
|
2394
2432
|
const { state, api } = props
|
|
2395
2433
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
2396
2434
|
const [open, setOpen] = useState(false)
|
|
2397
2435
|
const [limit, setLimit] = useState(100)
|
|
2436
|
+
// 排序模式:cost-desc / cost-asc / time-desc / time-asc / recent(实时顺序)。
|
|
2437
|
+
const [sortMode, setSortMode] = useState('cost-desc')
|
|
2398
2438
|
const [rows, setRows] = useState(null)
|
|
2399
2439
|
const [busy, setBusy] = useState(false)
|
|
2400
2440
|
const [err, setErr] = useState(false)
|
|
2401
|
-
const load = n => {
|
|
2441
|
+
const load = (n, mode) => {
|
|
2442
|
+
const [sort, dir] = mode === 'recent' ? ['recent', 'desc'] : mode.split('-')
|
|
2402
2443
|
setBusy(true)
|
|
2403
2444
|
setErr(false)
|
|
2404
|
-
api.getTopSessions(n)
|
|
2445
|
+
api.getTopSessions(n, sort, dir)
|
|
2405
2446
|
.then(res => setRows(Array.isArray(res?.sessions) ? res.sessions : []))
|
|
2406
2447
|
.catch(() => setErr(true))
|
|
2407
2448
|
.finally(() => setBusy(false))
|
|
@@ -2409,13 +2450,17 @@ window.__ModuleLoader__.load({
|
|
|
2409
2450
|
const toggle = () => {
|
|
2410
2451
|
setOpen(o => {
|
|
2411
2452
|
const next = !o
|
|
2412
|
-
if (next && rows === null && !busy) load(limit)
|
|
2453
|
+
if (next && rows === null && !busy) load(limit, sortMode)
|
|
2413
2454
|
return next
|
|
2414
2455
|
})
|
|
2415
2456
|
}
|
|
2416
2457
|
const changeLimit = n => {
|
|
2417
2458
|
setLimit(n)
|
|
2418
|
-
if (open || rows !== null) load(n)
|
|
2459
|
+
if (open || rows !== null) load(n, sortMode)
|
|
2460
|
+
}
|
|
2461
|
+
const changeSort = mode => {
|
|
2462
|
+
setSortMode(mode)
|
|
2463
|
+
if (open || rows !== null) load(limit, mode)
|
|
2419
2464
|
}
|
|
2420
2465
|
return el('div', { className: 'cm-budget' },
|
|
2421
2466
|
el('div', { className: 'cm-budget-head' },
|
|
@@ -2424,12 +2469,21 @@ window.__ModuleLoader__.load({
|
|
|
2424
2469
|
el('h3', { className: 'cm-h' }, t('sessionRankTitle')))),
|
|
2425
2470
|
open ? el('div', { className: 'cm-collapse-body' },
|
|
2426
2471
|
el('p', { className: 'cm-hint' }, t('sessionRankHint')),
|
|
2427
|
-
el('div', {
|
|
2428
|
-
el('
|
|
2429
|
-
|
|
2430
|
-
el('
|
|
2431
|
-
|
|
2432
|
-
|
|
2472
|
+
el('div', { style: { display: 'flex', gap: '12px', flexWrap: 'wrap', margin: '6px 0' } },
|
|
2473
|
+
el('div', { className: 'cm-field' },
|
|
2474
|
+
el('label', null, t('sessionRankSort')),
|
|
2475
|
+
el('select', { className: 'cm-input', value: sortMode, onChange: event => changeSort(event.target.value) },
|
|
2476
|
+
el('option', { value: 'cost-desc' }, t('sessionSortCostDesc')),
|
|
2477
|
+
el('option', { value: 'cost-asc' }, t('sessionSortCostAsc')),
|
|
2478
|
+
el('option', { value: 'time-desc' }, t('sessionSortTimeDesc')),
|
|
2479
|
+
el('option', { value: 'time-asc' }, t('sessionSortTimeAsc')),
|
|
2480
|
+
el('option', { value: 'recent' }, t('sessionSortRecent')))),
|
|
2481
|
+
el('div', { className: 'cm-field' },
|
|
2482
|
+
el('label', null, t('sessionRankLimit')),
|
|
2483
|
+
el('select', { className: 'cm-input', value: String(limit), onChange: event => changeLimit(Number(event.target.value)) },
|
|
2484
|
+
el('option', { value: '50' }, '50'),
|
|
2485
|
+
el('option', { value: '100' }, '100'),
|
|
2486
|
+
el('option', { value: '200' }, '200')))),
|
|
2433
2487
|
busy ? el('p', { className: 'cm-empty' }, t('sessionRankLoading'))
|
|
2434
2488
|
: err ? el('p', { className: 'cm-empty' }, t('sessionRankError'))
|
|
2435
2489
|
: rows === null || rows.length === 0 ? el('p', { className: 'cm-empty' }, t('sessionRankEmpty'))
|
|
@@ -2441,7 +2495,7 @@ window.__ModuleLoader__.load({
|
|
|
2441
2495
|
el('th', { className: 'num' }, t('colCost')))),
|
|
2442
2496
|
el('tbody', null, rows.map(session => el('tr', { key: session.date + ':' + session.id },
|
|
2443
2497
|
el('td', null, session.date),
|
|
2444
|
-
|
|
2498
|
+
sessionCell(session, state.config?.showSessionId === true, t),
|
|
2445
2499
|
el('td', { className: 'num' }, String(session.calls)),
|
|
2446
2500
|
el('td', { className: 'num' }, formatTokens(session.input)),
|
|
2447
2501
|
el('td', { className: 'num' }, formatTokens((session.cacheRead ?? 0) + (session.cacheWrite ?? 0))),
|
|
@@ -2455,6 +2509,7 @@ window.__ModuleLoader__.load({
|
|
|
2455
2509
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
2456
2510
|
const sessions = state.today.sessions ?? []
|
|
2457
2511
|
if (sessions.length === 0) return el('p', { className: 'cm-empty' }, t('noSessionsToday'))
|
|
2512
|
+
const showId = state.config?.showSessionId === true
|
|
2458
2513
|
return el('div', { className: 'cm-scroll' },
|
|
2459
2514
|
el('table', { className: 'cm-table' },
|
|
2460
2515
|
el('thead', null, el('tr', null,
|
|
@@ -2462,7 +2517,7 @@ window.__ModuleLoader__.load({
|
|
|
2462
2517
|
el('th', { className: 'num' }, t('colInTok')), el('th', { className: 'num' }, t('colCacheTok')), el('th', { className: 'num' }, t('colOutTok')),
|
|
2463
2518
|
el('th', { className: 'num' }, t('colCost')))),
|
|
2464
2519
|
el('tbody', null, sessions.map(session => el('tr', { key: session.id },
|
|
2465
|
-
|
|
2520
|
+
sessionCell(session, showId, t),
|
|
2466
2521
|
el('td', { className: 'num' }, String(session.calls)),
|
|
2467
2522
|
el('td', { className: 'num' }, formatTokens(session.input)),
|
|
2468
2523
|
el('td', { className: 'num' }, formatTokens(session.cacheRead + session.cacheWrite)),
|
|
@@ -2470,6 +2525,17 @@ window.__ModuleLoader__.load({
|
|
|
2470
2525
|
el('td', { className: 'num' }, formatMoneyUsd(session.cost, state.config)))))))
|
|
2471
2526
|
}
|
|
2472
2527
|
|
|
2528
|
+
/** 会话单元格:标题为主行(未命名回落短 id),showId 时附显等宽短 id;悬停看完整标题与 id。 */
|
|
2529
|
+
function sessionCell(session, showId, t) {
|
|
2530
|
+
const rawTitle = typeof session.title === 'string' ? session.title.trim() : ''
|
|
2531
|
+
const shortId = String(session.id).slice(0, 14) + '…'
|
|
2532
|
+
const main = rawTitle.length > 0 ? rawTitle : shortId
|
|
2533
|
+
const tooltip = rawTitle.length > 0 ? rawTitle + ' · ' + session.id : session.id
|
|
2534
|
+
return el('td', { title: tooltip },
|
|
2535
|
+
el('div', { className: 'cm-sess-title' + (rawTitle.length === 0 ? ' cm-sess-id' : '') }, main),
|
|
2536
|
+
showId && rawTitle.length > 0 ? el('div', { className: 'cm-sess-id' }, shortId) : null)
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2473
2539
|
const CURRENCY_PRESETS = {
|
|
2474
2540
|
CNY: { symbol: '¥', decimals: 4, exchangeRate: 7.2 },
|
|
2475
2541
|
USD: { symbol: '$', decimals: 6, exchangeRate: 1 },
|
|
@@ -3736,10 +3802,8 @@ window.__ModuleLoader__.load({
|
|
|
3736
3802
|
el('div', null,
|
|
3737
3803
|
el('h3', { className: 'cm-h' }, t('todaySessions')),
|
|
3738
3804
|
el(TodaySessions, { state, t })),
|
|
3739
|
-
// 历史
|
|
3740
|
-
el(
|
|
3741
|
-
el('h3', { className: 'cm-h' }, t('history')),
|
|
3742
|
-
el(HistoryTable, { state, api })),
|
|
3805
|
+
// 历史(三角折叠面板;日期行可再展开会话明细)
|
|
3806
|
+
el(HistoryPanel, { state, api }),
|
|
3743
3807
|
// 按会话统计(全部历史,不分日期;issue #22)
|
|
3744
3808
|
el(SessionRankPanel, { state, api }),
|
|
3745
3809
|
// 显示设置
|
|
@@ -3781,6 +3845,13 @@ window.__ModuleLoader__.load({
|
|
|
3781
3845
|
},
|
|
3782
3846
|
el('option', { value: 'on' }, t('sidebarOn')),
|
|
3783
3847
|
el('option', { value: 'off' }, t('off')))),
|
|
3848
|
+
el('label', { className: 'cm-check' },
|
|
3849
|
+
el('input', {
|
|
3850
|
+
type: 'checkbox',
|
|
3851
|
+
checked: draft?.showSessionId === true,
|
|
3852
|
+
onChange: event => setField('showSessionId', event.target.checked),
|
|
3853
|
+
}),
|
|
3854
|
+
el('span', null, t('showSessionIdLabel'))),
|
|
3784
3855
|
el('div', { className: 'cm-grid-group' }, t('groupMoney')),
|
|
3785
3856
|
el('div', { className: 'cm-field' },
|
|
3786
3857
|
el('label', null, t('currencyLabel')),
|
|
@@ -4159,8 +4230,8 @@ window.__ModuleLoader__.load({
|
|
|
4159
4230
|
},
|
|
4160
4231
|
// 按需拉取某天会话明细(issue #22),返回当日完整记录。
|
|
4161
4232
|
getDaySessions: async date => call('getDaySessions', [date]),
|
|
4162
|
-
//
|
|
4163
|
-
getTopSessions: async limit => call('getTopSessions', [limit]),
|
|
4233
|
+
// 跨全部日期的会话排行(issue #22 不分日期视角):支持费用/时间升降序与实时顺序。
|
|
4234
|
+
getTopSessions: async (limit, sort, dir) => call('getTopSessions', [limit, sort, dir]),
|
|
4164
4235
|
refreshBalance: async () => {
|
|
4165
4236
|
const result = await costMeter.refreshBalance()
|
|
4166
4237
|
if (result === null || typeof result !== 'object' || result.ok !== true) {
|
package/lib/index.js
CHANGED
|
@@ -909,17 +909,28 @@ function createService(ctx, ledger) {
|
|
|
909
909
|
return day === undefined ? zeroDay(date) : ledger.copyDay(day)
|
|
910
910
|
},
|
|
911
911
|
|
|
912
|
-
//
|
|
913
|
-
|
|
912
|
+
// 跨全部日期返回前 N 个会话(issue #22 按会话视角,不分日期)。
|
|
913
|
+
// sort:cost(费用) | time(会话创建时间) | recent(实时顺序,即账本/侧边栏顺序);dir:asc | desc。
|
|
914
|
+
async getTopSessions(limit, sort = 'cost', dir = 'desc') {
|
|
914
915
|
const n = Math.max(1, Math.min(500, Math.floor(Number(limit)) || 100))
|
|
916
|
+
const sortKey = sort === 'time' || sort === 'recent' ? sort : 'cost'
|
|
917
|
+
const asc = dir === 'asc'
|
|
915
918
|
const all = []
|
|
916
|
-
|
|
919
|
+
const dateKeys = Object.keys(ledger.days)
|
|
920
|
+
// recent 的降序 = 侧边栏直觉的「新会话在前」:日期倒序 + 每日会话倒序。
|
|
921
|
+
if (sortKey === 'recent' && !asc) dateKeys.reverse()
|
|
922
|
+
for (const date of dateKeys) {
|
|
923
|
+
const day = ledger.days[date]
|
|
917
924
|
if (!Array.isArray(day.sessions)) continue
|
|
918
|
-
|
|
925
|
+
const rows = day.sessions.slice()
|
|
926
|
+
if (sortKey === 'recent' && !asc) rows.reverse()
|
|
927
|
+
for (const s of rows) {
|
|
919
928
|
if (s === null || typeof s !== 'object') continue
|
|
920
929
|
all.push({
|
|
921
930
|
date,
|
|
922
931
|
id: String(s.id ?? ''),
|
|
932
|
+
title: typeof s.title === 'string' && s.title.length > 0 ? s.title : undefined,
|
|
933
|
+
at: Number.isFinite(Number(s.at)) && Number(s.at) > 0 ? Number(s.at) : undefined,
|
|
923
934
|
input: s.input ?? 0,
|
|
924
935
|
output: s.output ?? 0,
|
|
925
936
|
cacheRead: s.cacheRead ?? 0,
|
|
@@ -931,7 +942,16 @@ function createService(ctx, ledger) {
|
|
|
931
942
|
})
|
|
932
943
|
}
|
|
933
944
|
}
|
|
934
|
-
all.sort((a, b) => b.cost - a.cost)
|
|
945
|
+
if (sortKey === 'cost') all.sort((a, b) => asc ? a.cost - b.cost : b.cost - a.cost)
|
|
946
|
+
else if (sortKey === 'time') {
|
|
947
|
+
// 无时间戳的条目排末尾。
|
|
948
|
+
all.sort((a, b) => {
|
|
949
|
+
const ta = Number.isFinite(a.at) ? a.at : asc ? Number.MAX_SAFE_INTEGER : 0
|
|
950
|
+
const tb = Number.isFinite(b.at) ? b.at : asc ? Number.MAX_SAFE_INTEGER : 0
|
|
951
|
+
return asc ? ta - tb : tb - ta
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
// recent 已按构造顺序排好,不再重排。
|
|
935
955
|
return { sessions: all.slice(0, n) }
|
|
936
956
|
},
|
|
937
957
|
}
|
|
@@ -961,8 +981,13 @@ export function apply(ctx) {
|
|
|
961
981
|
// 宿主会话日志填补空 byProviderModel(幂等,只填空条目,不重复计数)。
|
|
962
982
|
const backfillTimer = setTimeout(() => {
|
|
963
983
|
backfillLegacyLedger(ledger, join(resolveDshHome(), 'sessions')).then(filled => {
|
|
964
|
-
if (filled.days > 0 || filled.sessions > 0) {
|
|
965
|
-
|
|
984
|
+
if (filled.days > 0 || filled.sessions > 0 || filled.titles > 0) {
|
|
985
|
+
const extras = [
|
|
986
|
+
filled.days > 0 || filled.sessions > 0 ? `${filled.days} 天 / ${filled.sessions} 个会话` : null,
|
|
987
|
+
filled.titles > 0 ? `${filled.titles} 个会话标题` : null,
|
|
988
|
+
filled.recosted > 0 ? `重算 ${filled.recosted} 天金额` : null,
|
|
989
|
+
].filter(Boolean).join(',')
|
|
990
|
+
console.log(`[dsh-cost-meter] 历史按模型统计回填完成:${extras}(扫描 ${filled.scanned} 份会话日志)`)
|
|
966
991
|
}
|
|
967
992
|
}, error => {
|
|
968
993
|
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,10 @@ 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
|
|
175
|
+
// 会话时间戳(实时入账/回填补齐):非正数剔除。
|
|
176
|
+
if (!Number.isFinite(Number(session.at)) || Number(session.at) <= 0) delete session.at
|
|
172
177
|
session.byProviderModel = session.byProviderModel !== null && typeof session.byProviderModel === 'object' && !Array.isArray(session.byProviderModel)
|
|
173
178
|
? session.byProviderModel
|
|
174
179
|
: {}
|
|
@@ -211,6 +216,7 @@ const VALIDATION_MESSAGES = {
|
|
|
211
216
|
peakEffectiveAt: 'peakEffectiveAt 非法',
|
|
212
217
|
peakWindows: 'peakWindows 必须是数组',
|
|
213
218
|
peakNotice: 'peakNotice 必须是布尔值',
|
|
219
|
+
showSessionId: 'showSessionId 必须是布尔值',
|
|
214
220
|
peakStyle: 'peakStyle 必须是 compact / classic',
|
|
215
221
|
priceMatch: 'priceMatch 必须是 auto / exact',
|
|
216
222
|
priceOverrides: 'priceOverrides 必须是字符串→字符串映射',
|
|
@@ -271,6 +277,7 @@ const VALIDATION_MESSAGES = {
|
|
|
271
277
|
peakEffectiveAt: 'Invalid peakEffectiveAt',
|
|
272
278
|
peakWindows: 'peakWindows must be an array',
|
|
273
279
|
peakNotice: 'peakNotice must be a boolean',
|
|
280
|
+
showSessionId: 'showSessionId must be a boolean',
|
|
274
281
|
peakStyle: 'peakStyle must be compact / classic',
|
|
275
282
|
priceMatch: 'priceMatch must be auto / exact',
|
|
276
283
|
priceOverrides: 'priceOverrides must be a string→string map',
|
|
@@ -387,6 +394,7 @@ export function applyConfigPatch(current, patch) {
|
|
|
387
394
|
if (typeof candidate.peakEffectiveAt !== 'string') errors.push(vmsg(locale, 'peakEffectiveAt'))
|
|
388
395
|
if (!Array.isArray(candidate.peakWindows)) errors.push(vmsg(locale, 'peakWindows'))
|
|
389
396
|
if (typeof candidate.peakNotice !== 'boolean') errors.push(vmsg(locale, 'peakNotice'))
|
|
397
|
+
if (candidate.showSessionId !== undefined && typeof candidate.showSessionId !== 'boolean') errors.push(vmsg(locale, 'showSessionId'))
|
|
390
398
|
if (candidate.peakStyle !== 'compact' && candidate.peakStyle !== 'classic') errors.push(vmsg(locale, 'peakStyle'))
|
|
391
399
|
if (candidate.priceMatch !== 'auto' && candidate.priceMatch !== 'exact') errors.push(vmsg(locale, 'priceMatch'))
|
|
392
400
|
const overrides = candidate.priceOverrides
|
|
@@ -569,6 +577,7 @@ export function sanitizeConfig(raw) {
|
|
|
569
577
|
out.priceMatch = oneOf(out.priceMatch, ['auto', 'exact'], 'auto')
|
|
570
578
|
out.decimals = Math.max(0, Math.min(10, Math.floor(Number(out.decimals) || 0)))
|
|
571
579
|
out.historyDays = Math.max(7, Math.min(3650, Math.floor(Number(out.historyDays) || 180)))
|
|
580
|
+
out.showSessionId = out.showSessionId === true
|
|
572
581
|
if (!isNum(out.exchangeRate) || out.exchangeRate <= 0) out.exchangeRate = base.exchangeRate
|
|
573
582
|
if (!Array.isArray(out.peakWindows)) out.peakWindows = base.peakWindows
|
|
574
583
|
else out.peakWindows = out.peakWindows.filter(w => w !== null && typeof w === 'object' && isNum(Number(w.start)) && isNum(Number(w.end)))
|
|
@@ -790,6 +799,7 @@ export class Ledger {
|
|
|
790
799
|
let session = sessions.find(s => s.id === sessionId)
|
|
791
800
|
if (session === undefined) {
|
|
792
801
|
session = zeroSession(sessionId)
|
|
802
|
+
session.at = atMs // 会话首次入账时刻(按会话排行「按时间排序」用)。
|
|
793
803
|
sessions.push(session)
|
|
794
804
|
if (sessions.length > MAX_SESSIONS_PER_DAY) sessions = sessions.slice(-MAX_SESSIONS_PER_DAY)
|
|
795
805
|
day.sessions = sessions
|
package/lib/typert.host.js
CHANGED
|
@@ -10,6 +10,8 @@ const num = z.number()
|
|
|
10
10
|
|
|
11
11
|
const sessionSchema = z.object({
|
|
12
12
|
id: z.string(),
|
|
13
|
+
title: z.string().optional(),
|
|
14
|
+
at: num.optional(),
|
|
13
15
|
input: num,
|
|
14
16
|
output: num,
|
|
15
17
|
cacheRead: num,
|
|
@@ -101,6 +103,7 @@ const configSchema = z.object({
|
|
|
101
103
|
peakEffectiveAt: z.string(),
|
|
102
104
|
peakWindows: z.array(z.object({ start: num, end: num })),
|
|
103
105
|
peakNotice: z.boolean().optional(),
|
|
106
|
+
showSessionId: z.boolean().optional(),
|
|
104
107
|
peakStyle: z.enum(['compact', 'classic']).optional(),
|
|
105
108
|
priceMatch: z.enum(['auto', 'exact']).optional(),
|
|
106
109
|
priceOverrides: z.record(z.string(), z.string()).optional(),
|
|
@@ -252,6 +255,8 @@ const _day$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayRecord', sch
|
|
|
252
255
|
const _date$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#DayKey', schema: z.string() }
|
|
253
256
|
const _topSessions$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#TopSessions', schema: topSessionsSchema }
|
|
254
257
|
const _limit$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionLimit', schema: num }
|
|
258
|
+
const _sort$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionSort', schema: z.string() }
|
|
259
|
+
const _dir$codec = { mode: 'strict', typeSymbol: 'dsh-cost-meter#SessionSortDir', schema: z.string() }
|
|
255
260
|
|
|
256
261
|
export const TYPERT = {
|
|
257
262
|
package: 'dsh-cost-meter',
|
|
@@ -353,6 +358,8 @@ export const TYPERT = {
|
|
|
353
358
|
invocation: { kind: 'direct' },
|
|
354
359
|
parameters: [
|
|
355
360
|
{ name: 'limit', wire: 'limit', source: 'json', codec: _limit$codec },
|
|
361
|
+
{ name: 'sort', wire: 'sort', source: 'json', codec: _sort$codec },
|
|
362
|
+
{ name: 'dir', wire: 'dir', source: 'json', codec: _dir$codec },
|
|
356
363
|
],
|
|
357
364
|
result: _topSessions$codec,
|
|
358
365
|
},
|
|
@@ -433,9 +440,9 @@ export const TYPERT = {
|
|
|
433
440
|
{
|
|
434
441
|
kind: 'method',
|
|
435
442
|
name: 'getTopSessions',
|
|
436
|
-
signature: 'getTopSessions(limit: number): TopSessions',
|
|
437
|
-
summary: '
|
|
438
|
-
jsDoc: '/**\n *
|
|
443
|
+
signature: 'getTopSessions(limit: number, sort: string, dir: string): TopSessions',
|
|
444
|
+
summary: '跨全部日期按指定排序返回前 N 个会话(不分日期视角)。Return the top N sessions across all days with the given sort.',
|
|
445
|
+
jsDoc: '/**\n * 跨全部日期返回前 N 个会话(每条带所属日期/标题/创建时刻)。\n * @param limit - 返回条数上限(服务端限制 1-500)。\n * @param sort - cost(费用) | time(创建时间) | recent(实时顺序)。\n * @param dir - asc | desc(默认 desc)。\n * @returns 会话列表(含 date/title/at 字段)。\n * Return the top N sessions across all days (each tagged with date/title/createdAt).\n * @param limit - Max rows to return (server clamps to 1-500).\n * @param sort - cost | time | recent (ledger/sidebar order).\n * @param dir - asc | desc (defaults to desc).\n * @returns Session list with date/title/at fields.\n */',
|
|
439
446
|
},
|
|
440
447
|
],
|
|
441
448
|
types: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.10",
|
|
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",
|