dsh-tokenlens 1.7.5 → 1.7.6
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/CHANGELOG.md +12 -0
- package/PROTOCOL.md +36 -6
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/client.js +49 -7
- package/index.js +36 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
**版本记录约定**:同一发布日只保留一个章节,标题取当日**最后一个**已发布版本号;当日多次发布的改动合并到该章节,并在标题下注明当日实际发布过的版本号(npm 上仍可安装这些独立版本)。若当日已有发布,后续小改动不再单独增加版本号,而是并入下一篇日志。这样日志按天规整、不出现同日多个小版本。
|
|
6
6
|
|
|
7
|
+
## 1.7.6 — 2026-11-06
|
|
8
|
+
|
|
9
|
+
(当日已发布版本:v1.6.5 / v1.6.6 / v1.6.7 / v1.7.0 / v1.7.1 / v1.7.2 / v1.7.3 / v1.7.4 / v1.7.5 / v1.7.6)
|
|
10
|
+
|
|
11
|
+
- **feat(ui): 版本号左侧新增仓库链接**——弹窗底部一行现在是 `[GitHub] 插件 v1.7.6 · 已是最新版本`,链接指向 `github.com/senyingxidi/dsh-tokenlens`,`target=_blank` + `rel=noopener`,带 `title` 说明;图标沿用同一套 stroke 图标(`aria-hidden`,对读屏透明)
|
|
12
|
+
- **fix(ui): TPS 悬停索引不再跨视图残留**——`speedHover` 原先只存在默认状态里,切换「当前供应商 / 全部 Harness」、切换「数据 / 图表」、切换供应商时都不会重置,于是同一个索引会指向新序列里的另一天(或越界)。现在它与 `chartHover` 一起被清理
|
|
13
|
+
- **fix(ui): 单点 TPS 曲线的命中区溢出**——只有一天样本时命中列从负 x 开始,左半区域无法触发悬停;现在把它夹进 viewBox
|
|
14
|
+
- **fix(stats): 启动期统计结果可能带着未锚定的窗口被缓存**——重启后「套餐起点」要等第一次额度读取才知道,而在此之前算出的统计结果会按 TTL 一直复用。现在 Host 在锚点集合变化后强制重算一次
|
|
15
|
+
- **perf: 模型切换不再无谓重取**——只有额度真正随模型变化的供应商(当前仅 `commandcode`)才会在切换模型时丢弃快照;在 DeepSeek / OpenCode 等模型无关的供应商上切模型不再触发上游请求。客户端也只在 `commandcode` 下补发一次快照
|
|
16
|
+
- 文档:`PROTOCOL.md` 补上 `credits.poolUsd / allowanceUsd / allowanceModel / allowanceBasis`、`speed` 序列,以及 v1.7.2 的窗口锚定与 v1.7.3 的分母结论
|
|
17
|
+
- 测试:370 → 373(仓库链接的位置/属性/图标、悬停状态清理、按供应商差异化的缓存失效)
|
|
18
|
+
|
|
7
19
|
## 1.7.5 — 2026-11-06
|
|
8
20
|
|
|
9
21
|
(当日已发布版本:v1.6.5 / v1.6.6 / v1.6.7 / v1.7.0 / v1.7.1 / v1.7.2 / v1.7.3 / v1.7.4 / v1.7.5)
|
package/PROTOCOL.md
CHANGED
|
@@ -84,7 +84,15 @@ from `dsh-app://app/` and forwards non-asset paths to the Host).
|
|
|
84
84
|
"monthly": { … } | null
|
|
85
85
|
} | null, // null for balance-only providers
|
|
86
86
|
"balance": { "amount": 2.26, "currency": "CNY", "granted": 0, "toppedUp": 2.26 } | null,
|
|
87
|
-
"credits": {
|
|
87
|
+
"credits": {
|
|
88
|
+
"remaining": 4.5, "granted": 10, "purchased": 0,
|
|
89
|
+
// v1.7.2: the SELECTED model's advertised allowance. Advertising only: the
|
|
90
|
+
// monthly percentage is always measured against the credit pool (v1.7.3),
|
|
91
|
+
// because that is what the provider's own console does.
|
|
92
|
+
"poolUsd": 10, "allowanceUsd": 14,
|
|
93
|
+
"allowanceModel": "deepseek/deepseek-v4.1-flash" | null,
|
|
94
|
+
"allowanceBasis": "model" | "pool" | "unknown"
|
|
95
|
+
} | null,
|
|
88
96
|
"account": { "name": "…", "userName": "…" } | null, // non-secret display info
|
|
89
97
|
"links": { "console": "https://opencode.ai/console" } | null, // provider-owned web page, follows the selection
|
|
90
98
|
"fetchedAt": "2026-10-25T09:03:00.000Z",
|
|
@@ -129,9 +137,13 @@ plugin never sizes a month from a plan catalog.
|
|
|
129
137
|
|
|
130
138
|
**The denominator is deliberately model-independent.** GOAT advertises "up to $100" and lists
|
|
131
139
|
per-model allowances ($100 on DeepSeek V4.1 Flash, $60 on several others) because the same
|
|
132
|
-
credits stretch further on a discounted model; the pool itself is one number.
|
|
133
|
-
|
|
134
|
-
`
|
|
140
|
+
credits stretch further on a discounted model; the pool itself is one number. Measured against
|
|
141
|
+
the provider's own console: with $2.94 spent it prints 每月限额 **4%**, i.e. `2.94 / 70 = 4.2%`;
|
|
142
|
+
`2.94 / 100` would print 2.9%. So the percentage is pool-based, and the per-model allowance is
|
|
143
|
+
reported as `credits.allowanceUsd` for context only (v1.7.3). The model *is* forwarded with the
|
|
144
|
+
request (`session.modelSelection.next.model`) so that the allowance can be resolved, and the Host
|
|
145
|
+
drops the cached snapshot on a model switch **only** for providers whose allowance actually
|
|
146
|
+
follows the model (`MODEL_SENSITIVE_ALLOWANCE`, currently `commandcode`).
|
|
135
147
|
|
|
136
148
|
`windowLimits.limited` is a **capability flag** ("this plan paces you with rolling windows"),
|
|
137
149
|
not a state: the live payload carries `limited: true` together with `fiveHour.exceeded: false`.
|
|
@@ -311,7 +323,11 @@ provider effective-date fallback (`effectiveDay`, `effectiveDays`,
|
|
|
311
323
|
folder basename, used by the client to match a sidebar hover card; the full
|
|
312
324
|
`cwd` is Host-only and never sent on the wire), the rolling 5-hour local window
|
|
313
325
|
(`fiveHours`, DSH-only because external harnesses are day-granular), and the
|
|
314
|
-
bounded `daily[]` series used by the chart view
|
|
326
|
+
bounded `daily[]` series used by the chart view, the `speed` series behind the
|
|
327
|
+
TPS trend (v1.7.0), and the subscription window (`effectiveDay` is the plan's
|
|
328
|
+
period start when the Host learned it, and `sinceEffective` is summed from the
|
|
329
|
+
per-day buckets inside `[window start, today]` rather than borrowed from the
|
|
330
|
+
30-day or all-time bucket — v1.7.2). Absolute local paths are
|
|
315
331
|
stripped from the wire; free-form error messages have path substrings replaced
|
|
316
332
|
with `[path]`.
|
|
317
333
|
|
|
@@ -357,7 +373,7 @@ with `[path]`.
|
|
|
357
373
|
"last30Days": { "total": …, "messages": … },
|
|
358
374
|
"allTime": { "total": …, "messages": … } }
|
|
359
375
|
],
|
|
360
|
-
"daily": [ // <=
|
|
376
|
+
"daily": [ // <=30 days (v1.7.0), ascending; chart series
|
|
361
377
|
{ "day": "2026-10-25",
|
|
362
378
|
"total": { "total": 123456, "messages": 42 },
|
|
363
379
|
"hours": [0, 0, 0, 1234, 5678, …], // 24 integers: local-hour token totals
|
|
@@ -366,6 +382,20 @@ with `[path]`.
|
|
|
366
382
|
"hours": [0, 0, 0, 500, 734, …] }
|
|
367
383
|
} }
|
|
368
384
|
],
|
|
385
|
+
// v1.7.0: output-speed (TPS) series. `tps` is output tokens over the summed
|
|
386
|
+
// model time of the steps that produced them (`step/start` ->
|
|
387
|
+
// `assistant/message`), so a step's tool calls — which are emitted after the
|
|
388
|
+
// message — do not dilute it. A day with no positive duration is omitted
|
|
389
|
+
// instead of being given an infinite rate. `total.tps` is null when the
|
|
390
|
+
// window holds no samples. Each provider row carries its own `speed` object
|
|
391
|
+
// with the same shape, following the same window as its tokens (v1.7.2).
|
|
392
|
+
"speed": {
|
|
393
|
+
"byDay": [
|
|
394
|
+
{ "day": "2026-11-05", "outputTokens": 516951, "seconds": 8493.1,
|
|
395
|
+
"tps": 60.9, "steps": 859 }
|
|
396
|
+
],
|
|
397
|
+
"total": { "outputTokens": 939643, "seconds": 14123.9, "tps": 66.5, "steps": 1424 }
|
|
398
|
+
},
|
|
369
399
|
"zcode": { // extra-harness detail
|
|
370
400
|
"dbPath": "C:\\Users\\…\\.zcode\\cli\\db\\db.sqlite",
|
|
371
401
|
"available": true, "error": null, "rows": 65, "cached": false,
|
package/README.en.md
CHANGED
|
@@ -144,7 +144,7 @@ Host bridge route: `/api/tokenlens`; agent tool: `tokenlens_usage`.
|
|
|
144
144
|
|
|
145
145
|
## Development
|
|
146
146
|
|
|
147
|
-
Node.js ≥ 24, zero runtime dependencies: `node --test` runs the
|
|
147
|
+
Node.js ≥ 24, zero runtime dependencies: `node --test` runs the 373 tests.
|
|
148
148
|
|
|
149
149
|
Release: `node scripts/bump-version.mjs <version> "<note>"` updates `package.json`, `PLUGIN_VERSION` and `CHANGELOG.md`; see [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
|
|
150
150
|
|
package/README.md
CHANGED
|
@@ -445,7 +445,7 @@ Host 半注册 `tokenlens_usage`,参数 `action`(默认 `snapshot`)与可
|
|
|
445
445
|
|
|
446
446
|
## 验证
|
|
447
447
|
|
|
448
|
-
- `node --test`:**
|
|
448
|
+
- `node --test`:**373 个自动化用例**,覆盖 Host 数据层、供应商适配器(含小米 MiMo 控制台 Cookie 适配器、Command Code 月度额度反推)、统计折叠、各 harness 读取、Design Plan 周期账本与额度计价、TPS 速度序列、窗口锚点切片、工作区自动识别、插件集成与浏览器端渲染 / 交互。
|
|
449
449
|
- 在真实环境中验证过 **5 个 harness** 的数据源;本机统计与独立的 SQLite 查询、独立的日志解析逐项对账(差额为 0),跨 harness 合并与供应商映射算术自洽。
|
|
450
450
|
- 供应商接口按各官方文档与参考客户端行为验证;未配置凭据时 chip 静默隐藏,接口异常时按可重试错误降级。
|
|
451
451
|
|
package/client.js
CHANGED
|
@@ -165,6 +165,8 @@ window.__ModuleLoader__.load({
|
|
|
165
165
|
}
|
|
166
166
|
/** The npm package page the 「打开 npm」 update links point at. */
|
|
167
167
|
const NPM_PACKAGE_URL = 'https://www.npmjs.com/package/dsh-tokenlens'
|
|
168
|
+
/** The source repository, linked to the left of the version (v1.7.6). */
|
|
169
|
+
const REPO_URL = 'https://github.com/senyingxidi/dsh-tokenlens'
|
|
168
170
|
|
|
169
171
|
const zh = {
|
|
170
172
|
title: '词元透镜(dsh-TokenLens)',
|
|
@@ -272,6 +274,8 @@ window.__ModuleLoader__.load({
|
|
|
272
274
|
capacityTokenNoEstimate: '预计可消耗 —',
|
|
273
275
|
capacityTokenEstimateHint: '本机 Token 记录 ÷ 当前套餐百分比估算,仅供参考',
|
|
274
276
|
pluginVersion: '插件 v{version}',
|
|
277
|
+
repoLink: 'GitHub',
|
|
278
|
+
repoLinkHint: '在 GitHub 上打开 dsh-TokenLens 仓库(源码、更新日志、问题反馈)',
|
|
275
279
|
statsToday: '今日',
|
|
276
280
|
statsAllTime: '总计',
|
|
277
281
|
statsInput: '输入',
|
|
@@ -547,6 +551,8 @@ window.__ModuleLoader__.load({
|
|
|
547
551
|
capacityTokenNoEstimate: 'Est. quota —',
|
|
548
552
|
capacityTokenEstimateHint: 'Local token records ÷ current plan percentage; estimate only',
|
|
549
553
|
pluginVersion: 'Plugin v{version}',
|
|
554
|
+
repoLink: 'GitHub',
|
|
555
|
+
repoLinkHint: 'Open the dsh-TokenLens repository on GitHub (source, changelog, issues)',
|
|
550
556
|
statsToday: 'Today',
|
|
551
557
|
statsAllTime: 'All time',
|
|
552
558
|
statsInput: 'Input',
|
|
@@ -774,6 +780,10 @@ window.__ModuleLoader__.load({
|
|
|
774
780
|
windows: ['M2.6 3.4h10.8v9.2H2.6z', 'M8 3.4v9.2', 'M2.6 7.9h10.8'],
|
|
775
781
|
coin: ['M8 2.4a5.6 5.6 0 1 0 0 11.2A5.6 5.6 0 0 0 8 2.4Z', 'M8 5.2v5.6', 'M6.3 6.6h3.4', 'M6.3 9.4h3.4'],
|
|
776
782
|
check: ['M3 8.4 6.2 11.4 13 4.4'],
|
|
783
|
+
// v1.7.6: the repository mark, drawn stroke-only like every other icon.
|
|
784
|
+
github: [
|
|
785
|
+
'M8 2.4a5.6 5.6 0 0 0-1.7 10.9c.3.1.4-.1.4-.3v-1c-1.5.3-1.9-.7-1.9-.7-.2-.6-.6-.7-.6-.7-.5-.4 0-.4 0-.4.5 0 .8.5.8.5.5.9 1.3.6 1.7.5 0-.4.2-.7.3-.8-1.3-.1-2.6-.6-2.6-2.7 0-.6.2-1.1.6-1.5-.1-.1-.2-.7 0-1.4 0 0 .5-.2 1.6.6a5.4 5.4 0 0 1 2.8 0c1.1-.8 1.6-.6 1.6-.6.3.7.1 1.3.1 1.4.3.4.5.9.5 1.5 0 2.1-1.3 2.6-2.6 2.7.2.2.4.5.4 1v1.5c0 .2.1.4.4.3A5.6 5.6 0 0 0 8 2.4Z',
|
|
786
|
+
],
|
|
777
787
|
alert: ['M8 2.5 14 13.2H2z', 'M8 6.2v3.2', 'M8 11.3h.01'],
|
|
778
788
|
spinner: ['M8 2.5a5.5 5.5 0 1 1-5.5 5.5', 'M8 5.5v2.8'],
|
|
779
789
|
ban: ['M3.2 3.2 12.8 12.8', 'M8 2.4a5.6 5.6 0 1 0 0 11.2A5.6 5.6 0 0 0 8 2.4Z'],
|
|
@@ -2206,6 +2216,9 @@ window.__ModuleLoader__.load({
|
|
|
2206
2216
|
detailsWindow: settings.detailsWindow,
|
|
2207
2217
|
chartHover: null,
|
|
2208
2218
|
chartDay: null,
|
|
2219
|
+
// v1.7.6: the TPS hover index is view state like the chart's, so it must
|
|
2220
|
+
// not survive a provider switch (the series behind it changed).
|
|
2221
|
+
speedHover: null,
|
|
2209
2222
|
}
|
|
2210
2223
|
}
|
|
2211
2224
|
|
|
@@ -3312,7 +3325,12 @@ window.__ModuleLoader__.load({
|
|
|
3312
3325
|
if (modelChanged) store.set({ modelId: nextModel })
|
|
3313
3326
|
syncProvider(providerId)
|
|
3314
3327
|
const after = store.get()
|
|
3315
|
-
|
|
3328
|
+
// v1.7.6: only Command Code's allowance follows the model, so only its
|
|
3329
|
+
// snapshots are worth refetching on a switch (see index.js
|
|
3330
|
+
// MODEL_SENSITIVE_ALLOWANCE). Everyone else keeps the cached snapshot.
|
|
3331
|
+
const modelSensitive =
|
|
3332
|
+
after.providerId !== null && canonicalProviderId(after.providerId) === 'commandcode'
|
|
3333
|
+
if (modelChanged && modelSensitive && after.inFlight !== true) {
|
|
3316
3334
|
void requestUsage('snapshot')
|
|
3317
3335
|
}
|
|
3318
3336
|
}
|
|
@@ -3614,7 +3632,11 @@ window.__ModuleLoader__.load({
|
|
|
3614
3632
|
.dsh-tokenlens-chart-drill-title{color:var(--dsw-alias-label-secondary);font-size:var(--dsw-alias-font-size-sm,12px);line-height:1.4;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
3615
3633
|
/* 明细 donut window switch (v1.2.0): one pill row under the details title. */
|
|
3616
3634
|
.dsh-tokenlens-details-window{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
|
|
3617
|
-
.dsh-tokenlens-version{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;text-align:right;opacity:.75;overflow-wrap:anywhere}
|
|
3635
|
+
.dsh-tokenlens-version{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;text-align:right;opacity:.75;overflow-wrap:anywhere;display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-wrap:wrap}
|
|
3636
|
+
/* v1.7.6: the repository link sits left of the version text. */
|
|
3637
|
+
.dsh-tokenlens-repo{display:inline-flex;align-items:center;gap:4px;color:inherit;text-decoration:none;flex:0 0 auto;border-radius:var(--dsw-radius-sm);transition:color .12s ease,opacity .12s ease}
|
|
3638
|
+
.dsh-tokenlens-repo:hover,.dsh-tokenlens-repo:focus-visible{color:var(--dsw-alias-label-primary,currentColor);opacity:1;text-decoration:underline}
|
|
3639
|
+
.dsh-tokenlens-version-text{flex:0 0 auto}
|
|
3618
3640
|
/* Update status on the plugin-version line (dialog body): wraps on narrow screens. */
|
|
3619
3641
|
.dsh-tokenlens-version-update{white-space:normal;overflow-wrap:anywhere}
|
|
3620
3642
|
.dsh-tokenlens-version .dsh-tokenlens-update-link{color:inherit;font-size:inherit;vertical-align:baseline}
|
|
@@ -4495,7 +4517,7 @@ window.__ModuleLoader__.load({
|
|
|
4495
4517
|
'data-scope': scope,
|
|
4496
4518
|
'data-active': effective === scope ? 'true' : 'false',
|
|
4497
4519
|
disabled: stats === null,
|
|
4498
|
-
onClick: () => store.set({ statsScope: scope, chartHover: null, chartDay: null }),
|
|
4520
|
+
onClick: () => store.set({ statsScope: scope, chartHover: null, chartDay: null, speedHover: null }),
|
|
4499
4521
|
},
|
|
4500
4522
|
label,
|
|
4501
4523
|
)
|
|
@@ -4521,7 +4543,7 @@ window.__ModuleLoader__.load({
|
|
|
4521
4543
|
'data-active': active === view ? 'true' : 'false',
|
|
4522
4544
|
'aria-pressed': active === view ? 'true' : 'false',
|
|
4523
4545
|
onClick: () => {
|
|
4524
|
-
if (active !== view) store.set({ statsView: view, chartHover: null, chartDay: null })
|
|
4546
|
+
if (active !== view) store.set({ statsView: view, chartHover: null, chartDay: null, speedHover: null })
|
|
4525
4547
|
},
|
|
4526
4548
|
},
|
|
4527
4549
|
Icon(iconName, 12),
|
|
@@ -6173,9 +6195,11 @@ window.__ModuleLoader__.load({
|
|
|
6173
6195
|
'rect',
|
|
6174
6196
|
{
|
|
6175
6197
|
key: `hit-${entry.day}-${index}`,
|
|
6176
|
-
|
|
6198
|
+
// v1.7.6: clamp the hit column into the viewBox — a single-day
|
|
6199
|
+
// series used to start at a negative x and lose half its area.
|
|
6200
|
+
x: Math.max(0, Math.min(width - 1, points[index].x - columnWidth / 2)).toFixed(1),
|
|
6177
6201
|
y: 0,
|
|
6178
|
-
width: Math.max(1, columnWidth).toFixed(1),
|
|
6202
|
+
width: Math.max(1, Math.min(columnWidth, width)).toFixed(1),
|
|
6179
6203
|
height: String(height),
|
|
6180
6204
|
fill: 'transparent',
|
|
6181
6205
|
'data-speed-day': entry.day,
|
|
@@ -7804,11 +7828,29 @@ window.__ModuleLoader__.load({
|
|
|
7804
7828
|
const body = [buildManageButton(state, t), statsArea]
|
|
7805
7829
|
const displayVersion = currentPluginVersionOf(state)
|
|
7806
7830
|
if (displayVersion !== '') {
|
|
7831
|
+
// v1.7.6: the repository link sits to the LEFT of the version, so the
|
|
7832
|
+
// version stays the right-most element of the row.
|
|
7833
|
+
const repo = h(
|
|
7834
|
+
'a',
|
|
7835
|
+
{
|
|
7836
|
+
key: 'repo',
|
|
7837
|
+
className: 'dsh-tokenlens-repo',
|
|
7838
|
+
href: REPO_URL,
|
|
7839
|
+
target: '_blank',
|
|
7840
|
+
rel: 'noreferrer noopener',
|
|
7841
|
+
'data-repo-link': 'true',
|
|
7842
|
+
title: t('repoLinkHint'),
|
|
7843
|
+
'aria-label': t('repoLinkHint'),
|
|
7844
|
+
},
|
|
7845
|
+
Icon('github', 13),
|
|
7846
|
+
t('repoLink'),
|
|
7847
|
+
)
|
|
7807
7848
|
body.push(
|
|
7808
7849
|
h(
|
|
7809
7850
|
'div',
|
|
7810
7851
|
{ key: 'version', className: 'dsh-tokenlens-version' },
|
|
7811
|
-
|
|
7852
|
+
repo,
|
|
7853
|
+
h('span', { key: 'text', className: 'dsh-tokenlens-version-text' }, t('pluginVersion', { version: displayVersion })),
|
|
7812
7854
|
// Update status sits right next to the plugin version: 有新版本 vX
|
|
7813
7855
|
// (link to the npm page) or 已是最新版本, only for a successful check.
|
|
7814
7856
|
buildVersionUpdateStatus(state, t),
|
package/index.js
CHANGED
|
@@ -84,7 +84,16 @@ export const inject = []
|
|
|
84
84
|
export const PLUGIN_NAME = 'dsh-tokenlens'
|
|
85
85
|
|
|
86
86
|
/** Plugin version; mirrors package.json (kept here so diagnostics need no fs read). */
|
|
87
|
-
export const PLUGIN_VERSION = '1.7.
|
|
87
|
+
export const PLUGIN_VERSION = '1.7.6'
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Providers whose monthly allowance depends on the SELECTED model (v1.7.6).
|
|
91
|
+
*
|
|
92
|
+
* Only these need their cached snapshot dropped when the picker changes: for
|
|
93
|
+
* everyone else a model switch does not alter a single number, so refetching
|
|
94
|
+
* would be pure waste.
|
|
95
|
+
*/
|
|
96
|
+
const MODEL_SENSITIVE_ALLOWANCE = new Set(['commandcode'])
|
|
88
97
|
|
|
89
98
|
/** Documented defaults; every field of the raw row config is optional. */
|
|
90
99
|
const DEFAULTS = {
|
|
@@ -1460,8 +1469,7 @@ export function apply(ctx, rawConfig) {
|
|
|
1460
1469
|
return catalog
|
|
1461
1470
|
}
|
|
1462
1471
|
|
|
1463
|
-
const statsService = config.stats.enabled
|
|
1464
|
-
? createUsageStats({
|
|
1472
|
+
const statsService = config.stats.enabled ? createUsageStats({
|
|
1465
1473
|
dshHome: config.stats.dshHome !== '' ? config.stats.dshHome : dshHomeOf(process.env),
|
|
1466
1474
|
config,
|
|
1467
1475
|
providerCatalog: providerModelCatalog,
|
|
@@ -1614,6 +1622,14 @@ export function apply(ctx, rawConfig) {
|
|
|
1614
1622
|
return scrubMessagePaths(out)
|
|
1615
1623
|
}
|
|
1616
1624
|
|
|
1625
|
+
/**
|
|
1626
|
+
* The subscription-anchor set the last stats result was computed with (v1.7.6).
|
|
1627
|
+
*
|
|
1628
|
+
* Empty until a plan period start is known; a change invalidates the cached
|
|
1629
|
+
* stats result so the 最近X天 window is anchored immediately.
|
|
1630
|
+
*/
|
|
1631
|
+
let statsAnchorSignature = '[]'
|
|
1632
|
+
|
|
1617
1633
|
async function getStats(options = {}) {
|
|
1618
1634
|
if (statsService === null) {
|
|
1619
1635
|
return {
|
|
@@ -1622,11 +1638,18 @@ export function apply(ctx, rawConfig) {
|
|
|
1622
1638
|
}
|
|
1623
1639
|
}
|
|
1624
1640
|
try {
|
|
1641
|
+
const starts = Object.fromEntries(state.subscriptionStarts)
|
|
1642
|
+
// v1.7.6: a cached stats result computed BEFORE a plan period start was
|
|
1643
|
+
// known would keep the un-anchored window until the TTL expired. Recompute
|
|
1644
|
+
// once whenever the anchor set changes, so the window is right immediately.
|
|
1645
|
+
const signature = JSON.stringify(Object.entries(starts).sort())
|
|
1646
|
+
const anchorChanged = signature !== statsAnchorSignature
|
|
1625
1647
|
const stats = await statsService.get({
|
|
1626
|
-
fresh: options.fresh === true,
|
|
1648
|
+
fresh: options.fresh === true || (signature !== '[]' && anchorChanged),
|
|
1627
1649
|
// v1.7.2: anchor each subscription provider's window to its plan start.
|
|
1628
|
-
subscriptionStarts:
|
|
1650
|
+
subscriptionStarts: starts,
|
|
1629
1651
|
})
|
|
1652
|
+
if (signature !== '[]') statsAnchorSignature = signature
|
|
1630
1653
|
// v0.22.1: only subscription-backed providers use the 最近X天 window;
|
|
1631
1654
|
// pay-as-you-go/balance providers keep the fixed 最近30天 column.
|
|
1632
1655
|
const decorated =
|
|
@@ -1835,6 +1858,10 @@ export function apply(ctx, rawConfig) {
|
|
|
1835
1858
|
* V4.1 Flash $100 while its credit pool is $70), so a model switch must also
|
|
1836
1859
|
* drop the cached snapshot — otherwise the chip would keep showing the old
|
|
1837
1860
|
* allowance until the next poll.
|
|
1861
|
+
*
|
|
1862
|
+
* v1.7.6: only for providers whose allowance is actually model-sensitive.
|
|
1863
|
+
* Invalidating on every provider made switching models on DeepSeek/OpenCode
|
|
1864
|
+
* throw away a perfectly good snapshot and refetch it.
|
|
1838
1865
|
*/
|
|
1839
1866
|
function rememberRequestedModel(body, providerId) {
|
|
1840
1867
|
const modelId = typeof body?.modelId === 'string' ? body.modelId.trim() : ''
|
|
@@ -1842,8 +1869,10 @@ export function apply(ctx, rawConfig) {
|
|
|
1842
1869
|
const id = canonicalProviderId(providerId)
|
|
1843
1870
|
if (state.selectedModels.get(id) === modelId) return
|
|
1844
1871
|
state.selectedModels.set(id, modelId)
|
|
1845
|
-
|
|
1846
|
-
|
|
1872
|
+
if (MODEL_SENSITIVE_ALLOWANCE.has(id)) {
|
|
1873
|
+
state.caches.delete(id)
|
|
1874
|
+
state.flights.delete(id)
|
|
1875
|
+
}
|
|
1847
1876
|
noteStatus('client-model', { providerId: id, modelId })
|
|
1848
1877
|
}
|
|
1849
1878
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tokenlens",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "dsh-TokenLens (词元透镜) — provider-aware plan capacity plus local token statistics (today/7d/30d/all-time, by harness/provider/model/workspace) for DSH.",
|
|
6
6
|
"license": "MIT",
|