dsh-tokenlens 1.5.3 → 1.6.2
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 +21 -0
- package/PROTOCOL.md +47 -8
- package/README.md +3 -2
- package/client.js +50 -20
- package/index.js +64 -7
- package/lib/design-plan-cost.mjs +217 -0
- package/lib/design-plan-ledger.mjs +35 -10
- package/lib/usage-stats.mjs +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
**版本记录约定**:同一发布日只保留一个章节,标题取当日**最后一个**已发布版本号;当日多次发布的改动合并到该章节,并在标题下注明当日实际发布过的版本号(npm 上仍可安装这些独立版本)。若当日已有发布,后续小改动不再单独增加版本号,而是并入下一篇日志。这样日志按天规整、不出现同日多个小版本。
|
|
6
6
|
|
|
7
|
+
## 1.6.2 — 2026-11-04
|
|
8
|
+
|
|
9
|
+
(当日已发布版本:v1.6.2。v1.6.1 的混合模型修复未单独发版,已并入本节)
|
|
10
|
+
|
|
11
|
+
- **fix(open-design): 混合模型按 credits 加权取额度,不再取最高倍率**——原实现取「用过的最高倍率」当口径,实测 0.5× 与 4× 各占一半 credits 时会把月额度报成 $15(正确应为 **$67.50**),严重低估。现改为按实际消耗加权的调和平均:`m_eff = Σcredits / Σ(credits/m)`,`月额度 = $60 / m_eff`。90% credits 走 0.5× 时是 **$97.89**,99% 时 **$117.92**,偶尔用一次 4× 模型不再摧毁额度
|
|
12
|
+
- fix(open-design): 单一模型时加权公式**精确退化**为官方表格值(0.5×→$120、1×→$60、2×→$30、4×→$15),纯模型场景不受影响
|
|
13
|
+
- fix(open-design): 无消费权重(仅知道模型列表)时回退到最高倍率,即保守口径(承诺最少剩余额度)
|
|
14
|
+
- **fix(open-design): 未收录价格的模型不再静默少算金额**——`unpricedModels` 此前算出来却没有用上,含未知模型时金额会**偏低却不提示**。现在置 `partial: true`,弹窗显示「含未收录价格的模型,金额为下限」,绝不把下限当完整值
|
|
15
|
+
- 测试:328 → 332(切换模型时金额与额度反向变化、未收录模型使金额成为下限、加权混合三种比例、单一模型退化、无权重回退最高倍率)
|
|
16
|
+
- 备注:`600,000 credits/月` 是插件由「7 天窗口上限 × 4」推算的值(官方定价表月/周 = $120/$30 = 4),**不是服务端字段**;服务端只返回 5 小时 50,000 与 7 天 150,000 两个窗口
|
|
17
|
+
|
|
18
|
+
## 1.6.0 — 2026-11-03
|
|
19
|
+
|
|
20
|
+
- **feat(open-design): 「本期 Design Plan 累计」改按官方定价表的模型单价计算**——原来是 `usedCredits ÷ creditsPerUsd`,这个算法隐含「按 1× 倍率消耗」的假设,而官方倍率表跨度 0.25×–4×:对 DeepSeek V4.1 Flash(0.5×)会**少算一半**,对 GPT-6 Luna(4×)会**多算 4 倍**。现在按每个模型的实际 Token × 官方单价(输入/输出/缓存读分别计价)累加,结果可直接与定价表比对
|
|
21
|
+
- **feat(open-design): 给出与倍率匹配的月额度**——定价表里 $120 是**列表价**口径且按倍率消耗:只用 0.5× 模型(DeepSeek V4.1 Flash)可动用**完整 $120**,1× 用 $60,2× 用 $30,4× 用 $15。弹窗改为显示「**已用 / 月额度 + 百分比**」(如 `$27.31 / $120.00 22.8%`),并注明「按 0.5× 倍率模型计价」
|
|
22
|
+
- feat(open-design): 混合模型取额度口径(v1.6.2 修正为按 credits 加权,不再是最高倍率)
|
|
23
|
+
- feat(wire): `designPlan` 新增 `monthlyCapUsd` / `capPercent` / `multiplier` / `creditsUsd`;`estimatedUsd` 语义改为「官方单价口径的消费额」,旧算法结果保留在 `creditsUsd` 便于对照
|
|
24
|
+
- feat(stats): `models[].last30Days` 增加 `input` / `output` / `cacheRead` / `cacheWrite` 拆分,供按单价计价使用(此前只有 total)
|
|
25
|
+
- fix(open-design): 单价表按官方文档转录 13 个 Design Plan 模型;`hunyuan-h4-preview` / `glm-5.3-flash-x` 不在公开价目接口里,价格缺失时按「未定价」处理并如实上报,绝不臆造
|
|
26
|
+
- 测试:318 → 328(倍率阶梯 0.5×→$120 / 1×→$60 / 2×→$30 / 4×→$15、未知模型不臆造倍率与额度、按 token 种类计价、**用控制台真实账单核对:$0.0048 列表价 × 0.5 = $0.0024 实扣**、未定价模型上报)
|
|
27
|
+
|
|
7
28
|
## 1.5.3 — 2026-11-03
|
|
8
29
|
|
|
9
30
|
- **feat(open-design): 初次配置只需一个 Control Key**——工作区 ID 改为**可选**。Control Key 本身就能确定它属于哪个工作区,插件改用 `GET /api/v1/workspaces/current` 自动识别(回退 `GET /api/v1/workspaces`,仅当可见工作区恰好一个时采纳)
|
package/PROTOCOL.md
CHANGED
|
@@ -161,24 +161,63 @@ by observing the weekly windows it sees:
|
|
|
161
161
|
"designPlan": {
|
|
162
162
|
"periodStart": "2026-11-02T09:53:18.000Z", // billing period (billing/summary)
|
|
163
163
|
"periodEnd": "2026-12-02T09:53:18.000Z",
|
|
164
|
-
"usedCredits":
|
|
165
|
-
"estimatedUsd":
|
|
164
|
+
"usedCredits": 142090, // Σ final+current weekly windows
|
|
165
|
+
"estimatedUsd": 27.31, // v1.6.0: documented list-price cost
|
|
166
|
+
"creditsUsd": 14.209, // the OLD (1x-assuming) conversion
|
|
167
|
+
"monthlyCapUsd": 120, // documented cap for this model mix
|
|
168
|
+
"multiplier": 0.5, // worst documented multiplier seen
|
|
169
|
+
"capPercent": 22.76, // estimatedUsd / monthlyCapUsd
|
|
166
170
|
"windows": 1, // weekly windows observed
|
|
167
171
|
"complete": true, // earliest window starts at periodStart
|
|
168
172
|
"since": "2026-11-02T09:53:18.000Z", // first window actually observed
|
|
169
173
|
"limitPerWindow": 150000, // the account's real weekly cap
|
|
170
174
|
"periodLimitCredits": 600000, // limitPerWindow * 4
|
|
171
|
-
"percent":
|
|
175
|
+
"percent": 23.68 // usedCredits / periodLimitCredits
|
|
172
176
|
}
|
|
173
177
|
```
|
|
174
178
|
|
|
179
|
+
### Why `estimatedUsd` is not `usedCredits / creditsPerUsd` (v1.6.0)
|
|
180
|
+
|
|
181
|
+
Open Design bills **credits**, but the pricing page states every cap in
|
|
182
|
+
**list-price dollars** and assigns each model a credit-burn **multiplier**:
|
|
183
|
+
|
|
184
|
+
| model | multiplier | monthly cap it unlocks |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| DeepSeek V4.1 Flash | 0.5× | $120 (the whole Go cap) |
|
|
187
|
+
| MiMo V2.6 Flash / DeepSeek V4 Pro / GLM-5.3 Flash … | 1× | $60 |
|
|
188
|
+
| Claude Haiku 5.5 / GLM-5.3 Flash-X | 2× | $30 |
|
|
189
|
+
| GPT-6 Luna | 4× | $15 |
|
|
190
|
+
|
|
191
|
+
So `usedCredits / creditsPerUsd` silently assumes a 1× burn: it reports **half** the
|
|
192
|
+
true list-price value for a 0.5× model and **4× too much** for a 4× model. The
|
|
193
|
+
account's own windows confirm the unit (`50,000` credits per 5h = the documented
|
|
194
|
+
$10, `150,000` per 7d = $30, i.e. 5,000 credits per list-price dollar), which is
|
|
195
|
+
why `creditsPerUsd` is 10,000 — two credits per list-price dollar at 0.5×.
|
|
196
|
+
|
|
197
|
+
`estimatedUsd` is therefore computed from the tokens actually consumed:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
cost = Σ per model ( input×p_in + output×p_out + cacheRead×p_read ) / 1e6
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
with the prices transcribed from the same page. Verified against one real console
|
|
204
|
+
receipt: input 311 + output 2,610 + cacheRead 258,560 on DeepSeek V4.1 Flash gives
|
|
205
|
+
`$0.0048` list price, ×0.5 multiplier = `$0.0024` charged — exactly what the usage
|
|
206
|
+
table shows. Note the table's "input" column is non-cached + cache, so `input` in
|
|
207
|
+
this formula **excludes** `cacheRead` or the cache bytes would be billed twice.
|
|
208
|
+
|
|
209
|
+
The cap uses the **highest** multiplier in the mix, not an average: a 4× model
|
|
210
|
+
consumes its share four times faster, so it binds. `creditsUsd` keeps the old
|
|
211
|
+
number on the wire for comparison; a client showing the old figure can migrate
|
|
212
|
+
without a wire break.
|
|
213
|
+
|
|
175
214
|
`windows.monthly` is then filled from that estimate (v1.5.1), with `resetsAt` taken
|
|
176
215
|
from `periodEnd`, so the existing monthly row and the chip's `月 N%` piece carry it
|
|
177
|
-
without a second UI. The ×4 monthly-to-weekly ratio is the
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
216
|
+
without a second UI. The ×4 monthly-to-weekly ratio is documented too: the pricing
|
|
217
|
+
table states the monthly cap as exactly four weekly windows for every tier (Go
|
|
218
|
+
$120/$30, Plus $160/$40, Pro $360/$90, Max $600/$150). A partial estimate still
|
|
219
|
+
reports a percentage but sets the window `status` to `unknown`, so it can never
|
|
220
|
+
paint a warning/critical dot.
|
|
182
221
|
|
|
183
222
|
Rules the Host guarantees:
|
|
184
223
|
|
package/README.md
CHANGED
|
@@ -327,7 +327,8 @@ Control Key 是 **Open Design 桌面端/CLI 登录时写入本机的会话凭据
|
|
|
327
327
|
- OpenCode 的额度定义见[官方文档](https://opencode.ai/docs/go/#usage-limits)(5h = 月度额度的 20%、周 = 50%、月 = 100%);接口只返回百分比,插件也只画百分比。
|
|
328
328
|
- Command Code 的 `/alpha/*` 是官方 CLI 自用的私有计费路由;免费 / 无方案账户返回空窗口(显示 `—`),插件不会用方案目录猜额度。
|
|
329
329
|
- Open Design 的窗口按账户实际返回的时长渲染:Go 同时有 5 小时和 7 天窗口,Plus / Pro / Max 只有 7 天窗口;钱包余额是独立于窗口的 USD 额度,用尽后继续扣钱包。查询要求工作区权限,鉴权失败会明确显示错误而不是 0。**只需填一个 Control Key**:工作区 ID 是可选项(v1.5.3),插件会用 Key 调 `/api/v1/workspaces/current` 自动识别你的工作区;只有账号有多个工作区、需要指定其中一个时才在管理页填「工作区 ID」。真正缺 Key 时弹窗会指出并给出「去配置」入口。窗口、余额、套餐档位是**三层独立读取**(v1.4.0):任一接口不可用时只少那一块数据,不会整套报错。
|
|
330
|
-
- **chip 的「月 N%」与「本期 Design Plan 累计」是插件本机算的估算值**(v1.5.0 / v1.
|
|
330
|
+
- **chip 的「月 N%」与「本期 Design Plan 累计」是插件本机算的估算值**(v1.5.0 / v1.6.0)。Open Design 有月度额度(Go 为每月 $120 = 4 × 每周 $30),但**没有「本账期已用」接口**——控制台的 `/billing/usage`、`/billing/coding-plan-usage` 都靠浏览器 Cookie 鉴权,Control Key 一律 401。于是插件累加它观测到的 **7 天窗口**(只记 7 天,因为 5 小时窗口测量的是同一笔消耗,相加会重复计数)。
|
|
331
|
+
- **金额按官方定价表的模型单价计算**(v1.6.0)。原来用 `usedCredits ÷ creditsPerUsd`,隐含「按 1× 倍率消耗」,而官方倍率跨度 0.25×–4×,对 DeepSeek V4.1 Flash(0.5×)会少算一半、对 GPT-6 Luna(4×)会多算 4 倍。现在按每个模型的实际 Token × 官方单价(输入/输出/缓存读分别计价)累加,弹窗显示「已用 / 月额度 + 百分比」(如 `$27.31 / $120.00 22.8%`)并注明「按 0.5× 倍率模型计价」——**只用 0.5× 模型可用满完整 $120**,1× 只能到 $60,2× 到 $30,4× 到 $15。弹窗会标注覆盖情况(「已覆盖本账期全部窗口」或「仅统计已观测的 N 个窗口」);覆盖不完整时百分比仍显示但**不会触发告警配色**,插件没运行期间错过的窗口无法计入。
|
|
331
332
|
- 未配置凭据的供应商 chip 静默隐藏;`zai`、`minimax`、`deepinfra` 属 best-effort 适配(未公开接口,失败时按可重试错误优雅降级)。
|
|
332
333
|
- 各供应商的元数据(`kind` / `label` / `shortLabel` / `baseURL` / `apiKeyRef` / `consoleUrl`)集中在 `lib/providers.mjs` 的 `PROVIDER_DEFAULTS` 与 `ADAPTERS` 中,新增适配器只需在该文件登记并补测试。
|
|
333
334
|
|
|
@@ -439,7 +440,7 @@ Host 半注册 `tokenlens_usage`,参数 `action`(默认 `snapshot`)与可
|
|
|
439
440
|
|
|
440
441
|
## 验证
|
|
441
442
|
|
|
442
|
-
- `node --test`:**
|
|
443
|
+
- `node --test`:**332 个自动化用例**,覆盖 Host 数据层、供应商适配器、统计折叠、各 harness 读取、Design Plan 周期账本与官方单价计价、窗口锚点切片、工作区自动识别、插件集成与浏览器端渲染 / 交互。
|
|
443
444
|
- 在真实环境中验证过 **5 个 harness** 的数据源;本机统计与独立的 SQLite 查询、独立的日志解析逐项对账(差额为 0),跨 harness 合并与供应商映射算术自洽。
|
|
444
445
|
- 供应商接口按各官方文档与参考客户端行为验证;未配置凭据时 chip 静默隐藏,接口异常时按可重试错误降级。
|
|
445
446
|
|
package/client.js
CHANGED
|
@@ -341,8 +341,9 @@ window.__ModuleLoader__.load({
|
|
|
341
341
|
errorOpenDesignConfig: 'Open Design 额度查询需要工作区 ID 与 Control Key(不是推理 Key)',
|
|
342
342
|
designPlanLabel: '本期 Design Plan 累计',
|
|
343
343
|
designPlanHint:
|
|
344
|
-
'Open Design
|
|
345
|
-
|
|
344
|
+
'按官方定价表的模型单价换算本机实际消耗的 Token(不是 usedCredits ÷ 汇率)。Open Design 没有月度用量接口,此值由插件在本机累计观测到的 7 天窗口得出,仅供参考',
|
|
345
|
+
designPlanMultiplier: '按 {multiplier}× 倍率模型计价',
|
|
346
|
+
designPlanPartialPricing: '含未收录价格的模型,金额为下限',
|
|
346
347
|
designPlanComplete: '已覆盖本账期全部窗口',
|
|
347
348
|
designPlanPartial: '仅统计已观测的 {count} 个窗口',
|
|
348
349
|
providerSave: '保存',
|
|
@@ -601,10 +602,11 @@ window.__ModuleLoader__.load({
|
|
|
601
602
|
providerControlKeySaved: 'Saved; paste again to replace it',
|
|
602
603
|
configureProvider: 'Configure',
|
|
603
604
|
errorOpenDesignConfig: 'Open Design quota needs a workspace ID and a Control Key (not an inference key)',
|
|
604
|
-
designPlanLabel: 'Period Design Plan
|
|
605
|
+
designPlanLabel: 'Period Design Plan',
|
|
605
606
|
designPlanHint:
|
|
606
|
-
'
|
|
607
|
-
|
|
607
|
+
'Priced from the official per-model rates over the tokens this machine actually consumed (not usedCredits ÷ rate). Open Design serves no period-usage endpoint, so the window total is accumulated locally; treat it as an estimate.',
|
|
608
|
+
designPlanMultiplier: 'priced at the {multiplier}× model rate',
|
|
609
|
+
designPlanPartialPricing: 'includes models with no documented price; the amount is a lower bound',
|
|
608
610
|
designPlanComplete: 'covers every window of this period',
|
|
609
611
|
designPlanPartial: 'only {count} observed window(s)',
|
|
610
612
|
providerConsoleURLPlaceholder: 'Leave empty for the built-in default',
|
|
@@ -1090,6 +1092,11 @@ window.__ModuleLoader__.load({
|
|
|
1090
1092
|
const windows = Number.isFinite(raw.windows) && raw.windows > 0 ? Math.round(raw.windows) : 0
|
|
1091
1093
|
const estimatedUsd = numberOrNull(raw.estimatedUsd)
|
|
1092
1094
|
const limitPerWindow = numberOrNull(raw.limitPerWindow)
|
|
1095
|
+
// v1.6.0 documented-price context: the cap the model mix entitles and the
|
|
1096
|
+
// share of it consumed. Both are optional (an older Host omits them).
|
|
1097
|
+
const monthlyCapUsd = numberOrNull(raw.monthlyCapUsd)
|
|
1098
|
+
const capPercent = numberOrNull(raw.capPercent)
|
|
1099
|
+
const multiplier = numberOrNull(raw.multiplier)
|
|
1093
1100
|
return {
|
|
1094
1101
|
periodStart: stringOrEmpty(raw.periodStart),
|
|
1095
1102
|
periodEnd: stringOrEmpty(raw.periodEnd),
|
|
@@ -1099,6 +1106,12 @@ window.__ModuleLoader__.load({
|
|
|
1099
1106
|
complete: raw.complete === true,
|
|
1100
1107
|
since: stringOrEmpty(raw.since),
|
|
1101
1108
|
limitPerWindow: limitPerWindow !== null && limitPerWindow > 0 ? limitPerWindow : null,
|
|
1109
|
+
monthlyCapUsd: monthlyCapUsd !== null && monthlyCapUsd > 0 ? monthlyCapUsd : null,
|
|
1110
|
+
capPercent,
|
|
1111
|
+
multiplier: multiplier !== null && multiplier > 0 ? multiplier : null,
|
|
1112
|
+
// v1.6.2: some consumed model had no documented price, so the dollar
|
|
1113
|
+
// figure is a lower bound and must not read as complete.
|
|
1114
|
+
partial: raw.partial === true,
|
|
1102
1115
|
}
|
|
1103
1116
|
}
|
|
1104
1117
|
|
|
@@ -4018,25 +4031,38 @@ window.__ModuleLoader__.load({
|
|
|
4018
4031
|
}
|
|
4019
4032
|
|
|
4020
4033
|
/**
|
|
4021
|
-
* The open-design period estimate row (v1.5.0).
|
|
4034
|
+
* The open-design period estimate row (v1.5.0, priced in v1.6.0).
|
|
4022
4035
|
*
|
|
4023
4036
|
* Open Design has no period-usage endpoint, so this is the Host's local
|
|
4024
|
-
* accumulation of the
|
|
4025
|
-
*
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4037
|
+
* accumulation of the observed windows. The dollar figure is the DOCUMENTED
|
|
4038
|
+
* LIST-PRICE value of the tokens consumed this period, which makes it
|
|
4039
|
+
* comparable with the published caps — that is what "can I use $120?" turns
|
|
4040
|
+
* on. The denominator is the cap those model multipliers entitle the account
|
|
4041
|
+
* to (0.5x models unlock the full $120; 1x unlocks $60).
|
|
4028
4042
|
*/
|
|
4029
4043
|
function buildDesignPlanRow(designPlan, t) {
|
|
4030
4044
|
if (designPlan === null) return null
|
|
4031
4045
|
const parts = []
|
|
4032
|
-
|
|
4046
|
+
const hasUsd = designPlan.estimatedUsd !== null
|
|
4047
|
+
if (hasUsd) {
|
|
4048
|
+
// "$27.31 / $120" — the numerator is documented list-price usage, the
|
|
4049
|
+
// denominator the documented cap for this model mix.
|
|
4050
|
+
const value =
|
|
4051
|
+
designPlan.monthlyCapUsd !== null
|
|
4052
|
+
? `${formatMoney(designPlan.estimatedUsd, 'USD')} / ${formatMoney(designPlan.monthlyCapUsd, 'USD')}`
|
|
4053
|
+
: formatMoney(designPlan.estimatedUsd, 'USD')
|
|
4033
4054
|
parts.push(
|
|
4034
|
-
h(
|
|
4035
|
-
'span',
|
|
4036
|
-
{ key: 'usd', className: 'dsh-tokenlens-percent dsh-tokenlens-capacity-money' },
|
|
4037
|
-
formatMoney(designPlan.estimatedUsd, 'USD'),
|
|
4038
|
-
),
|
|
4055
|
+
h('span', { key: 'usd', className: 'dsh-tokenlens-percent dsh-tokenlens-capacity-money' }, value),
|
|
4039
4056
|
)
|
|
4057
|
+
if (designPlan.capPercent !== null) {
|
|
4058
|
+
parts.push(
|
|
4059
|
+
h(
|
|
4060
|
+
'span',
|
|
4061
|
+
{ key: 'cap', className: 'dsh-tokenlens-percent', 'data-level': levelOf(designPlan.capPercent, 'ok') },
|
|
4062
|
+
formatPercent(designPlan.capPercent, t),
|
|
4063
|
+
),
|
|
4064
|
+
)
|
|
4065
|
+
}
|
|
4040
4066
|
} else {
|
|
4041
4067
|
parts.push(
|
|
4042
4068
|
h(
|
|
@@ -4047,10 +4073,14 @@ window.__ModuleLoader__.load({
|
|
|
4047
4073
|
)
|
|
4048
4074
|
}
|
|
4049
4075
|
const notes = []
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4076
|
+
// Say WHICH multiplier set the cap, since that is why the ceiling is not
|
|
4077
|
+
// always $120 (the pricing table: 0.5x→$120, 1x→$60, 2x→$30, 4x→$15).
|
|
4078
|
+
if (designPlan.multiplier !== null) {
|
|
4079
|
+
notes.push(t('designPlanMultiplier', { multiplier: designPlan.multiplier }))
|
|
4080
|
+
}
|
|
4081
|
+
// v1.6.2: a model without a documented price makes the figure a lower bound.
|
|
4082
|
+
// Say it rather than letting the number read as complete.
|
|
4083
|
+
if (designPlan.partial === true) notes.push(t('designPlanPartialPricing'))
|
|
4054
4084
|
if (designPlan.complete === true) notes.push(t('designPlanComplete'))
|
|
4055
4085
|
else if (designPlan.windows > 0) notes.push(t('designPlanPartial', { count: designPlan.windows }))
|
|
4056
4086
|
return h(
|
package/index.js
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
saveProviderStore,
|
|
39
39
|
} from './lib/provider-store.mjs'
|
|
40
40
|
import { makeCustomAdapter, sanitizeUsageBaseURL, validateCustomProvider } from './lib/custom-provider.mjs'
|
|
41
|
+
import { capsForModels, designPlanCostUsd } from './lib/design-plan-cost.mjs'
|
|
41
42
|
import {
|
|
42
43
|
defaultDesignPlanLedgerFile,
|
|
43
44
|
loadLedger,
|
|
@@ -83,7 +84,7 @@ export const inject = []
|
|
|
83
84
|
export const PLUGIN_NAME = 'dsh-tokenlens'
|
|
84
85
|
|
|
85
86
|
/** Plugin version; mirrors package.json (kept here so diagnostics need no fs read). */
|
|
86
|
-
export const PLUGIN_VERSION = '1.
|
|
87
|
+
export const PLUGIN_VERSION = '1.6.2'
|
|
87
88
|
|
|
88
89
|
/** Documented defaults; every field of the raw row config is optional. */
|
|
89
90
|
const DEFAULTS = {
|
|
@@ -431,12 +432,68 @@ export function apply(ctx, rawConfig) {
|
|
|
431
432
|
}
|
|
432
433
|
}
|
|
433
434
|
|
|
434
|
-
/**
|
|
435
|
-
|
|
435
|
+
/**
|
|
436
|
+
* The period estimate for the open-design snapshot, or null.
|
|
437
|
+
*
|
|
438
|
+
* v1.6.0: the dollar figure is the documented LIST-PRICE value of the tokens
|
|
439
|
+
* this period consumed, not `usedCredits / creditsPerUsd`. The latter assumes
|
|
440
|
+
* a 1x burn rate while the documented multipliers range 0.25x–4x, which made
|
|
441
|
+
* the number incomparable with the published caps (2x too low for DeepSeek
|
|
442
|
+
* V4.1 Flash, 4x too high for GPT-6 Luna). The token usage comes from the
|
|
443
|
+
* same local session-log stats the rest of the dialog uses.
|
|
444
|
+
*/
|
|
445
|
+
async function designPlanSummaryFor(providerId, designPlan) {
|
|
436
446
|
if (canonicalProviderId(providerId) !== 'open-design') return null
|
|
437
447
|
const periodStart = designPlan?.periodStart
|
|
438
448
|
if (typeof periodStart !== 'string' || periodStart === '') return null
|
|
439
|
-
|
|
449
|
+
const pricing = await designPlanPricing()
|
|
450
|
+
return summarizeDesignPlan(designPlanLedger, periodStart, pricing)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The documented-price context of the current billing period.
|
|
455
|
+
*
|
|
456
|
+
* Reads the per-model token buckets for `open-design` from the local stats
|
|
457
|
+
* (scoped to this period by the day histograms' own totals) and converts them
|
|
458
|
+
* with the documented list prices, then derives the caps those multipliers
|
|
459
|
+
* entitle the account to. Every failure degrades to `null`s, never to a throw:
|
|
460
|
+
* a pricing problem must not hide the credits/balance the Host already has.
|
|
461
|
+
*/
|
|
462
|
+
async function designPlanPricing() {
|
|
463
|
+
if (statsService === null) return null
|
|
464
|
+
let stats = null
|
|
465
|
+
try {
|
|
466
|
+
stats = await statsService.get({ fresh: false })
|
|
467
|
+
} catch {
|
|
468
|
+
return null
|
|
469
|
+
}
|
|
470
|
+
// `last30Days` is the closest wire window to a billing period (this account's
|
|
471
|
+
// is 2026-11-02 → 12-02) and it carries the per-token-kind split the
|
|
472
|
+
// documented prices need.
|
|
473
|
+
const models = Object.create(null)
|
|
474
|
+
for (const entry of Array.isArray(stats?.models) ? stats.models : []) {
|
|
475
|
+
if (canonicalProviderId(entry?.providerId) !== 'open-design') continue
|
|
476
|
+
const modelId = typeof entry.modelId === 'string' ? entry.modelId : ''
|
|
477
|
+
if (modelId === '') continue
|
|
478
|
+
const bucket = models[modelId] ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
479
|
+
bucket.input += typeof entry.last30Days?.input === 'number' ? entry.last30Days.input : 0
|
|
480
|
+
bucket.output += typeof entry.last30Days?.output === 'number' ? entry.last30Days.output : 0
|
|
481
|
+
bucket.cacheRead += typeof entry.last30Days?.cacheRead === 'number' ? entry.last30Days.cacheRead : 0
|
|
482
|
+
bucket.cacheWrite += typeof entry.last30Days?.cacheWrite === 'number' ? entry.last30Days.cacheWrite : 0
|
|
483
|
+
models[modelId] = bucket
|
|
484
|
+
}
|
|
485
|
+
const cost = designPlanCostUsd(models)
|
|
486
|
+
// v1.6.1: weight the cap by actual per-model consumption, not the worst
|
|
487
|
+
// multiplier. The caps are credit-denominated, so a mostly-0.5x account that
|
|
488
|
+
// touched a 4x model once keeps nearly the full $120.
|
|
489
|
+
const caps = capsForModels(Object.keys(models), cost.mixes)
|
|
490
|
+
return {
|
|
491
|
+
listPriceUsd: cost.priced === true ? cost.usd : null,
|
|
492
|
+
monthlyCapUsd: caps.monthlyCapUsd,
|
|
493
|
+
multiplier: caps.multiplier,
|
|
494
|
+
unpricedModels: cost.unpricedModels,
|
|
495
|
+
partial: cost.unpricedModels.length > 0,
|
|
496
|
+
}
|
|
440
497
|
}
|
|
441
498
|
|
|
442
499
|
/**
|
|
@@ -1196,12 +1253,12 @@ export function apply(ctx, rawConfig) {
|
|
|
1196
1253
|
return consoleUrl === null ? null : { console: consoleUrl }
|
|
1197
1254
|
}
|
|
1198
1255
|
|
|
1199
|
-
function snapshotFromResult(providerId, read, now) {
|
|
1256
|
+
async function snapshotFromResult(providerId, read, now) {
|
|
1200
1257
|
// v1.5.0: the Design Plan period estimate. The ledger observes every read
|
|
1201
1258
|
// (refreshes and background polls), then the current period is summarized
|
|
1202
1259
|
// into the snapshot so the dialog can show it without a second round-trip.
|
|
1203
1260
|
recordDesignPlan(read.result.designPlan ?? null, now)
|
|
1204
|
-
const designPlan = designPlanSummaryFor(providerId, read.result.designPlan)
|
|
1261
|
+
const designPlan = await designPlanSummaryFor(providerId, read.result.designPlan)
|
|
1205
1262
|
return buildSnapshot({
|
|
1206
1263
|
provider: providerInfo(providerId),
|
|
1207
1264
|
plan: read.result.plan ?? null,
|
|
@@ -1240,7 +1297,7 @@ export function apply(ctx, rawConfig) {
|
|
|
1240
1297
|
state.lastAttempts.set(canonicalId, new Date().toISOString())
|
|
1241
1298
|
try {
|
|
1242
1299
|
const read = await readProvider(canonicalId, Date.now())
|
|
1243
|
-
const snapshot = snapshotFromResult(canonicalId, read, Date.now())
|
|
1300
|
+
const snapshot = await snapshotFromResult(canonicalId, read, Date.now())
|
|
1244
1301
|
state.caches.set(canonicalId, { snapshot, fetchedAtMs: read.fetchedAtMs })
|
|
1245
1302
|
state.lastErrors.delete(canonicalId)
|
|
1246
1303
|
state.requestCounts.set(canonicalId, (state.requestCounts.get(canonicalId) ?? 0) + 1)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design Plan cost accounting (v1.6.0).
|
|
3
|
+
*
|
|
4
|
+
* WHY
|
|
5
|
+
*
|
|
6
|
+
* Open Design bills the Design Plan in "credits", never in dollars. The
|
|
7
|
+
* published pricing page (`open-design.ai/zh/model-credits/`) states the caps in
|
|
8
|
+
* LIST-PRICE dollars ($120/month, $30/7d, $10/5h for Go) and, crucially, that
|
|
9
|
+
* the credits a model BURNS are `list-price value × that model's multiplier`:
|
|
10
|
+
*
|
|
11
|
+
* DeepSeek V4.1 Flash 0.5x -> the whole $120 monthly cap is available
|
|
12
|
+
* MiMo V2.6 Flash 1x -> half of it ($60)
|
|
13
|
+
* Claude Haiku 5.5 2x -> $30
|
|
14
|
+
* GPT-6 Luna 4x -> $15
|
|
15
|
+
*
|
|
16
|
+
* So a dollar amount computed from credits alone (usedCredits / creditsPerUsd)
|
|
17
|
+
* is NOT comparable with the pricing table: it silently assumes the account
|
|
18
|
+
* burns credits at 1x, understating a 0.5x model by 2x and overstating a 4x
|
|
19
|
+
* model by 4x. The honest figure is the LIST-PRICE value of the tokens actually
|
|
20
|
+
* consumed, which is what this module computes:
|
|
21
|
+
*
|
|
22
|
+
* cost = Σ over models ( input×p_in + output×p_out + cacheRead×p_read ) / 1e6
|
|
23
|
+
*
|
|
24
|
+
* That number is directly comparable with the documented caps, and the cap that
|
|
25
|
+
* applies depends on the model mix — hence `monthlyCapFor` and
|
|
26
|
+
* `effectiveMonthlyCap`.
|
|
27
|
+
*
|
|
28
|
+
* Credits remain the authoritative *consumption* signal (the server owns the
|
|
29
|
+
* counters); prices only turn observed tokens into a dollar figure the user can
|
|
30
|
+
* check against the pricing page.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Documented per-million-token list prices (USD) and Design Plan multipliers.
|
|
35
|
+
*
|
|
36
|
+
* Transcribed from `open-design.ai/zh/model-credits/` (pricing table + the
|
|
37
|
+
* "Design Plan 模型与额度扣除倍率" table). The public
|
|
38
|
+
* `GET /api/v1/public/model-prices` endpoint serves the same input/output/
|
|
39
|
+
* cacheRead numbers; only `hunyuan-h4-preview` and `glm-5.3-flash-x` are absent
|
|
40
|
+
* from it, so their prices fall back to the endpoint at runtime when available.
|
|
41
|
+
*
|
|
42
|
+
* `multiplier` is what the DOCUMENTED table says; an unknown model gets `null`
|
|
43
|
+
* and is reported as unpriceable rather than assumed to be 1x.
|
|
44
|
+
*/
|
|
45
|
+
export const DESIGN_PLAN_MODELS = {
|
|
46
|
+
'deepseek-v4.1-flash': { multiplier: 0.5, input: 0.3, output: 1.2, cacheRead: 0.006, label: 'DeepSeek V4.1 Flash' },
|
|
47
|
+
'deepseek-v4-flash': { multiplier: 1, input: 0.44, output: 1.32, cacheRead: 0.014, label: 'DeepSeek V4 Flash' },
|
|
48
|
+
'deepseek-v4-flash-vision-exp': {
|
|
49
|
+
multiplier: 1,
|
|
50
|
+
input: 0.44,
|
|
51
|
+
output: 1.32,
|
|
52
|
+
cacheRead: 0.014,
|
|
53
|
+
label: 'DeepSeek V4 Flash Vision Exp',
|
|
54
|
+
},
|
|
55
|
+
'deepseek-v4-pro': { multiplier: 1, input: 1.32, output: 3.96, cacheRead: 0.044, label: 'DeepSeek V4 Pro' },
|
|
56
|
+
'glm-5.3-flash': { multiplier: 1, input: 0.075, output: 0.25, cacheRead: 0.015, label: 'GLM-5.3 Flash' },
|
|
57
|
+
'glm-5.3-flash-x': { multiplier: 2, input: 0.37, output: 1.25, cacheRead: 0.075, label: 'GLM-5.3 Flash-X' },
|
|
58
|
+
'kimi-k2.7-code': { multiplier: 1, input: 0.95, output: 4, cacheRead: 0.19, label: 'Kimi K2.7 Code' },
|
|
59
|
+
'mimo-v2.6-flash': { multiplier: 1, input: 0.14, output: 0.28, cacheRead: 0.0028, label: 'MiMo V2.6 Flash' },
|
|
60
|
+
'mimo-v2.6-pro': { multiplier: 1, input: 0.435, output: 0.87, cacheRead: 0.0036, label: 'MiMo V2.6 Pro' },
|
|
61
|
+
'minimax-m3.1-flash': { multiplier: 1, input: 0.15, output: 0.6, cacheRead: 0.015, label: 'MiniMax M3.1 Flash' },
|
|
62
|
+
'claude-haiku-5.5': { multiplier: 2, input: 0.1, output: 0.5, cacheRead: 0.01, label: 'Claude Haiku 5.5' },
|
|
63
|
+
'gpt-6-luna': { multiplier: 4, input: 0.1, output: 0.5, cacheRead: 0.01, label: 'GPT-6 Luna' },
|
|
64
|
+
'hunyuan-h4-preview': { multiplier: 1, input: null, output: null, cacheRead: null, label: 'Hunyuan H4 Preview' },
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The documented list-price dollar caps of the Go tier (used only as a default). */
|
|
68
|
+
export const DESIGN_PLAN_CAPS_USD = { rolling: 10, weekly: 30, monthly: 120 }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The monthly list-price cap available to a model mix.
|
|
72
|
+
*
|
|
73
|
+
* The $120 monthly cap is entirely Design Plan value and is consumed at the
|
|
74
|
+
* model's multiplier, so using ONLY a 0.5x model (DeepSeek V4.1 Flash) unlocks
|
|
75
|
+
* the full $120 of list-price usage; a 1x model reaches $60; 2x reaches $30.
|
|
76
|
+
* With a mixed history the binding constraint is NOT simply the highest
|
|
77
|
+
* multiplier: the caps are denominated in credits (600,000/month at 10,000
|
|
78
|
+
* credits per list-price dollar), and each model burns those credits at its own
|
|
79
|
+
* rate. The account's spendable list-price value is therefore
|
|
80
|
+
*
|
|
81
|
+
* 600,000 credits / m_effective
|
|
82
|
+
*
|
|
83
|
+
* where `m_effective` is the credit-weighted harmonic mean of the multipliers
|
|
84
|
+
* actually used:
|
|
85
|
+
*
|
|
86
|
+
* m_eff = Σ credits_i / Σ (credits_i / m_i), credits_i = listUsd_i × 10000 × m_i
|
|
87
|
+
*
|
|
88
|
+
* Using the plain maximum would understate a mostly-0.5x account that touched a
|
|
89
|
+
* 4x model once: 90% of the credits at 0.5x plus 10% at 4x gives $97.89, not
|
|
90
|
+
* $15. With a single model the formula reduces exactly to that model's
|
|
91
|
+
* documented cap, so the pure cases still match the pricing page.
|
|
92
|
+
*
|
|
93
|
+
* @param modelIds - the model ids in the mix, or
|
|
94
|
+
* @param mixes - a per-model `{ listUsd, multiplier }` split (preferred: it
|
|
95
|
+
* weights by real consumption). `modelIds` is used only when no split is given.
|
|
96
|
+
* @returns `{ monthlyCapUsd, weeklyCapUsd, fiveHourCapUsd, multiplier }` where
|
|
97
|
+
* `multiplier` is the effective multiplier, or all-null caps when no model is
|
|
98
|
+
* known.
|
|
99
|
+
*/
|
|
100
|
+
export function capsForModels(modelIds, mixes = null) {
|
|
101
|
+
const rows = Array.isArray(mixes)
|
|
102
|
+
? mixes
|
|
103
|
+
.map((mix) => ({
|
|
104
|
+
listUsd: typeof mix?.listUsd === 'number' && Number.isFinite(mix.listUsd) && mix.listUsd > 0 ? mix.listUsd : 0,
|
|
105
|
+
multiplier:
|
|
106
|
+
typeof mix?.multiplier === 'number' && Number.isFinite(mix.multiplier) && mix.multiplier > 0
|
|
107
|
+
? mix.multiplier
|
|
108
|
+
: null,
|
|
109
|
+
}))
|
|
110
|
+
.filter((row) => row.multiplier !== null && row.listUsd > 0)
|
|
111
|
+
: null
|
|
112
|
+
let effective = 0
|
|
113
|
+
if (rows !== null && rows.length > 0) {
|
|
114
|
+
// Credit-weighted harmonic mean (see the doc comment).
|
|
115
|
+
let credits = 0
|
|
116
|
+
let inverse = 0
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
const rowCredits = row.listUsd * row.multiplier
|
|
119
|
+
credits += rowCredits
|
|
120
|
+
inverse += rowCredits / row.multiplier
|
|
121
|
+
}
|
|
122
|
+
effective = credits > 0 && inverse > 0 ? credits / inverse : 0
|
|
123
|
+
} else {
|
|
124
|
+
// No consumption weighting available: fall back to the highest multiplier,
|
|
125
|
+
// the conservative reading (it promises the LEAST remaining capacity).
|
|
126
|
+
let worst = 0
|
|
127
|
+
for (const id of Array.isArray(modelIds) ? modelIds : []) {
|
|
128
|
+
const model = DESIGN_PLAN_MODELS[normalizeModelId(id)]
|
|
129
|
+
if (model === undefined || !Number.isFinite(model.multiplier) || model.multiplier <= 0) continue
|
|
130
|
+
if (model.multiplier > worst) worst = model.multiplier
|
|
131
|
+
}
|
|
132
|
+
effective = worst
|
|
133
|
+
}
|
|
134
|
+
if (!(effective > 0)) {
|
|
135
|
+
return { monthlyCapUsd: null, weeklyCapUsd: null, fiveHourCapUsd: null, multiplier: null }
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
// 600,000 credits / m at 10,000 credits per list-price dollar == $60 / m.
|
|
139
|
+
monthlyCapUsd: 60 / effective,
|
|
140
|
+
weeklyCapUsd: 15 / effective,
|
|
141
|
+
fiveHourCapUsd: 5 / effective,
|
|
142
|
+
multiplier: effective,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A model id as the documented table spells it (lower-case, trimmed). */
|
|
147
|
+
export function normalizeModelId(value) {
|
|
148
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The documented entry for a model id, or null. */
|
|
152
|
+
export function designPlanModel(modelId) {
|
|
153
|
+
return DESIGN_PLAN_MODELS[normalizeModelId(modelId)] ?? null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A token count as a finite non-negative number. */
|
|
157
|
+
function tokens(value) {
|
|
158
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The documented list-price cost of one bucket of tokens for one model.
|
|
163
|
+
*
|
|
164
|
+
* @returns `{ usd, priced }`; `priced` is false when the model has no documented
|
|
165
|
+
* price (the cost is then 0 for that model, and callers must say so rather
|
|
166
|
+
* than pretend the total is complete).
|
|
167
|
+
*/
|
|
168
|
+
export function modelCostUsd(modelId, bucket) {
|
|
169
|
+
const model = designPlanModel(modelId)
|
|
170
|
+
if (model === null) return { usd: 0, priced: false }
|
|
171
|
+
const input = tokens(bucket?.input)
|
|
172
|
+
const output = tokens(bucket?.output)
|
|
173
|
+
const cacheRead = tokens(bucket?.cacheRead)
|
|
174
|
+
const cacheWrite = tokens(bucket?.cacheWrite)
|
|
175
|
+
// cacheWrite has no documented Design Plan price; bill it as fresh input so a
|
|
176
|
+
// value is never silently dropped, and flag it so the caller can explain.
|
|
177
|
+
const priced =
|
|
178
|
+
Number.isFinite(model.input) && Number.isFinite(model.output) && Number.isFinite(model.cacheRead)
|
|
179
|
+
if (!priced) return { usd: 0, priced: true, unavailability: 'the model has no documented Design Plan price' }
|
|
180
|
+
const usd =
|
|
181
|
+
(input / 1e6) * model.input +
|
|
182
|
+
(output / 1e6) * model.output +
|
|
183
|
+
(cacheRead / 1e6) * model.cacheRead +
|
|
184
|
+
(cacheWrite / 1e6) * model.input
|
|
185
|
+
return { usd, priced: true }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Total documented list-price cost of a per-model token map.
|
|
190
|
+
*
|
|
191
|
+
* @param models - `{ [modelId]: { input, output, cacheRead, cacheWrite } }`
|
|
192
|
+
* @returns `{ usd, priced, unpricedModels }` where `unpricedModels` lists the
|
|
193
|
+
* ids that had tokens but no documented price (their cost is not included).
|
|
194
|
+
*/
|
|
195
|
+
export function designPlanCostUsd(models) {
|
|
196
|
+
let usd = 0
|
|
197
|
+
let priced = false
|
|
198
|
+
const unpricedModels = []
|
|
199
|
+
const mixes = []
|
|
200
|
+
for (const [modelId, bucket] of Object.entries(models ?? {})) {
|
|
201
|
+
const total = tokens(bucket?.input) + tokens(bucket?.output) + tokens(bucket?.cacheRead) + tokens(bucket?.cacheWrite)
|
|
202
|
+
if (total === 0) continue
|
|
203
|
+
const cost = modelCostUsd(modelId, bucket)
|
|
204
|
+
if (cost.priced !== true) {
|
|
205
|
+
unpricedModels.push(modelId)
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
usd += cost.usd
|
|
209
|
+
priced = true
|
|
210
|
+
// Per-model list-price spend, so the cap can be weighted by real consumption.
|
|
211
|
+
const model = designPlanModel(modelId)
|
|
212
|
+
if (model !== null && Number.isFinite(model.multiplier) && model.multiplier > 0 && cost.usd > 0) {
|
|
213
|
+
mixes.push({ modelId: normalizeModelId(modelId), listUsd: cost.usd, multiplier: model.multiplier })
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { usd, priced, unpricedModels, mixes }
|
|
217
|
+
}
|
|
@@ -234,17 +234,25 @@ export const WEEKS_PER_PERIOD = 4
|
|
|
234
234
|
/**
|
|
235
235
|
* Summarize one period: the estimated Design Plan usage the Host has observed.
|
|
236
236
|
*
|
|
237
|
+
* @param ledger - the ledger document.
|
|
238
|
+
* @param periodStart - the billing period key.
|
|
239
|
+
* @param input - optional documented-price context (v1.6.0):
|
|
240
|
+
* `{ listPriceUsd, monthlyCapUsd, multiplier }`. `listPriceUsd` replaces the
|
|
241
|
+
* credits÷rate conversion as `estimatedUsd`; it is the documented list-price
|
|
242
|
+
* value of the tokens actually consumed, which is the figure comparable with
|
|
243
|
+
* the published caps.
|
|
237
244
|
* @returns `null` when the period has no observations at all, else
|
|
238
|
-
* `{ usedCredits, estimatedUsd,
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* `
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
245
|
+
* `{ usedCredits, estimatedUsd, creditsUsd, monthlyCapUsd, multiplier,
|
|
246
|
+
* capPercent, windows, complete, since, periodStart, periodEnd,
|
|
247
|
+
* limitPerWindow, periodLimitCredits, percent }`.
|
|
248
|
+
* `percent` is `usedCredits / periodLimitCredits` (observed credits against
|
|
249
|
+
* the account's own windows); `capPercent` is `estimatedUsd / monthlyCapUsd`
|
|
250
|
+
* (documented dollars against the documented cap). The two differ per model
|
|
251
|
+
* mix and are both reported. `complete` is true only when the earliest
|
|
252
|
+
* observed window starts at the period start — otherwise earlier windows of
|
|
253
|
+
* this period were never seen and the numbers are lower bounds.
|
|
246
254
|
*/
|
|
247
|
-
export function summarizeDesignPlan(ledger, periodStart) {
|
|
255
|
+
export function summarizeDesignPlan(ledger, periodStart, input = null) {
|
|
248
256
|
const key = isoOf(periodStart)
|
|
249
257
|
if (key === '') return null
|
|
250
258
|
const period = ledger?.periods?.[key]
|
|
@@ -258,18 +266,35 @@ export function summarizeDesignPlan(ledger, periodStart) {
|
|
|
258
266
|
windows += 1
|
|
259
267
|
}
|
|
260
268
|
const creditsPerUsd = period.creditsPerUsd
|
|
261
|
-
|
|
269
|
+
// v1.6.0: `creditsPerUsd` conversion is kept ONLY as a fallback. It assumes the
|
|
270
|
+
// account burns credits at 1x, but the documented multipliers range 0.25x–4x,
|
|
271
|
+
// so it is wrong for any non-1x model (2x too low for DeepSeek V4.1 Flash).
|
|
272
|
+
// The caller passes the documented list-price cost instead.
|
|
273
|
+
const creditsUsd =
|
|
262
274
|
Number.isSafeInteger(creditsPerUsd) && creditsPerUsd > 0 ? usedCredits / creditsPerUsd : null
|
|
275
|
+
const estimatedUsd = Number.isFinite(input?.listPriceUsd) ? input.listPriceUsd : creditsUsd
|
|
263
276
|
const since = entries[0][0]
|
|
264
277
|
const limitPerWindow = entries[entries.length - 1][1].limitCredits
|
|
265
278
|
const usableWindowLimit = Number.isSafeInteger(limitPerWindow) && limitPerWindow > 0 ? limitPerWindow : null
|
|
266
279
|
const periodLimitCredits = usableWindowLimit === null ? null : usableWindowLimit * WEEKS_PER_PERIOD
|
|
267
280
|
const percent = periodLimitCredits === null ? null : (usedCredits / periodLimitCredits) * 100
|
|
281
|
+
const monthlyCapUsd = Number.isFinite(input?.monthlyCapUsd) ? input.monthlyCapUsd : null
|
|
282
|
+
const capPercent =
|
|
283
|
+
estimatedUsd !== null && monthlyCapUsd !== null && monthlyCapUsd > 0
|
|
284
|
+
? Math.min(100, Math.max(0, (estimatedUsd / monthlyCapUsd) * 100))
|
|
285
|
+
: null
|
|
268
286
|
return {
|
|
269
287
|
periodStart: key,
|
|
270
288
|
periodEnd: period.periodEnd || '',
|
|
271
289
|
usedCredits,
|
|
272
290
|
estimatedUsd,
|
|
291
|
+
creditsUsd,
|
|
292
|
+
monthlyCapUsd,
|
|
293
|
+
multiplier: Number.isFinite(input?.multiplier) ? input.multiplier : null,
|
|
294
|
+
capPercent,
|
|
295
|
+
// v1.6.2: true when some consumed model has no documented price, so
|
|
296
|
+
// `estimatedUsd` is a lower bound and the UI must say so.
|
|
297
|
+
partial: input?.partial === true,
|
|
273
298
|
windows,
|
|
274
299
|
complete: since === key,
|
|
275
300
|
since,
|
package/lib/usage-stats.mjs
CHANGED
|
@@ -665,7 +665,16 @@ export function createUsageStats(input) {
|
|
|
665
665
|
modelId: model.model,
|
|
666
666
|
today: { total: todayModel.total, messages: todayModel.messages },
|
|
667
667
|
last7Days: { total: model.last7Days.total, messages: model.last7Days.messages },
|
|
668
|
-
|
|
668
|
+
// v1.6.0: the input/output/cache split is included so a caller can price
|
|
669
|
+
// the tokens per model (the documented prices differ per token kind).
|
|
670
|
+
last30Days: {
|
|
671
|
+
total: model.last30Days.total,
|
|
672
|
+
messages: model.last30Days.messages,
|
|
673
|
+
input: model.last30Days.input,
|
|
674
|
+
output: model.last30Days.output,
|
|
675
|
+
cacheRead: model.last30Days.cacheRead,
|
|
676
|
+
cacheWrite: model.last30Days.cacheWrite,
|
|
677
|
+
},
|
|
669
678
|
allTime: { total: model.total.total, messages: model.total.messages },
|
|
670
679
|
sinceEffective: { total: model.total.total, messages: model.total.messages },
|
|
671
680
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tokenlens",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
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",
|