@gamegeek-saikel/dsh-cost-meter 0.1.1 → 0.1.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/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/lib/client.js +34 -14
- package/lib/client.js.map +1 -1
- package/lib/index.js +96 -21
- package/lib/types/client/AssistantCostChip.d.ts.map +1 -1
- package/lib/types/client/AssistantCostChip.js +4 -1
- package/lib/types/client/CostView.d.ts.map +1 -1
- package/lib/types/client/CostView.js +5 -1
- package/lib/types/client/SessionCostPill.d.ts.map +1 -1
- package/lib/types/client/SessionCostPill.js +8 -3
- package/lib/types/client/cost-math.d.ts +12 -4
- package/lib/types/client/cost-math.d.ts.map +1 -1
- package/lib/types/client/cost-math.js +14 -6
- package/lib/types/pricebook.d.ts +12 -5
- package/lib/types/pricebook.d.ts.map +1 -1
- package/lib/types/pricebook.js +23 -10
- package/lib/types/pricing.d.ts +33 -6
- package/lib/types/pricing.d.ts.map +1 -1
- package/lib/types/pricing.js +77 -14
- package/lib/types/types.d.ts +20 -2
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/AssistantCostChip.tsx +4 -1
- package/src/client/CostView.tsx +5 -1
- package/src/client/SessionCostPill.tsx +8 -3
- package/src/client/cost-math.ts +15 -5
- package/src/pricebook.ts +30 -9
- package/src/pricing.ts +81 -13
- package/src/types.ts +22 -2
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Chat costs in DeepSeek pricing change over time (list prices, USD→CNY exchange
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| Cost anchoring | Append-only pricebook snapshots; step cost computed once at the event's own time |
|
|
45
45
|
| Price sources | Manual override > official pricing page > built-in fallback > OpenRouter (fallback only, USD→CNY) > none |
|
|
46
|
-
| Peak pricing | 2026-08-17 00:00 Beijing rollout; peak 09:00–12:00 / 14:00–18:00 Beijing, off-peak half price |
|
|
46
|
+
| Peak pricing | 2026-08-17 00:00 Beijing rollout; peak/off-peak windows parsed from each page's own schedule (the zh and en pages may differ; fallback 09:00–12:00 / 14:00–18:00 Beijing), off-peak half price |
|
|
47
47
|
| Cost formula | Uncached input + cache reads (hit rate) + cache writes (billed at uncached input rate) + output, per 1M tokens, CNY |
|
|
48
48
|
| Account balance | Official `GET /user/balance`, cached 60 s, single in-flight request, trust-fenced route |
|
|
49
49
|
| Subagent support | BFS over the live agent tree; conversation totals = main session + descendants |
|
|
@@ -71,7 +71,7 @@ The pricebook (`src/pricebook.ts`) is the durable price source, persisted on the
|
|
|
71
71
|
|
|
72
72
|
- **Priority chain** — per canonical model key (`provider/model`, bare model, or the `flash`/`pro` pricing key for DeepSeek-family models): manual override > official page > built-in fallback > OpenRouter (fallback only, USD→CNY, cache reads at the configured discount) > none.
|
|
73
73
|
- **Snapshot selection** — `snapshotForTime` picks the newest snapshot with `effectiveAt <= event time` (pre-install sessions anchor to the first snapshot once).
|
|
74
|
-
- **Peak/off-peak** — before the 2026-08-17 rollout all steps price at the single list price; after it, the band is chosen by
|
|
74
|
+
- **Peak/off-peak** — before the 2026-08-17 rollout all steps price at the single list price; after it, the band is chosen by the EVENT's own time against the schedule of the pricebook's own page (the zh and en pages each parse their own peak windows, falling back to Beijing 09:00–12:00 / 14:00–18:00, everything else off-peak). Each step's band is anchored once at fold time, so the per-reply cards and chips always show the band that round was billed at — never the band of the moment you are looking.
|
|
75
75
|
- **Immutable ledger** — the `sessionCost` projection (`src/session-cost-projection.ts`) folds `request/header` (model) and usage-carrying events into per-step rows; a second usage sample for the same (turn, step) replaces the first (same-step finalization, not a re-price), with O(1) incremental totals.
|
|
76
76
|
|
|
77
77
|
## Project Structure
|
package/README.zh-CN.md
CHANGED
|
@@ -43,7 +43,7 @@ DeepSeek 的价格随时间变化(官方价目表、USD→CNY 汇率、以及
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| 成本锚定 | 只追加价格簿快照;步成本在事件自身时刻只计算一次 |
|
|
45
45
|
| 价格来源 | 手动覆盖 > 官方价格页 > 内置回退 > OpenRouter(仅回退,USD→CNY)> 无 |
|
|
46
|
-
| 峰值定价 | 2026-08-17 00:00
|
|
46
|
+
| 峰值定价 | 2026-08-17 00:00 北京生效;高峰/闲时窗口按各页面自己的时段表解析(中英文页面可能不同,缺省 09:00–12:00 / 14:00–18:00 北京),闲时为半价 |
|
|
47
47
|
| 成本公式 | 未命中输入 + 缓存命中(命中价)+ 缓存写入(按未命中输入价计)+ 输出,每百万 tokens,CNY |
|
|
48
48
|
| 账户余额 | 官方 `GET /user/balance`,缓存 60 秒,单飞请求,路由带信任围栏 |
|
|
49
49
|
| 子代理支持 | 沿活跃代理树 BFS;对话总花费 = 主会话 + 全部后代 |
|
|
@@ -71,7 +71,7 @@ DeepSeek 的价格随时间变化(官方价目表、USD→CNY 汇率、以及
|
|
|
71
71
|
|
|
72
72
|
- **优先级链**——按规范模型键(`provider/model`、裸模型名,或 DeepSeek 系模型的 `flash`/`pro` 定价键):手动覆盖 > 官方页面 > 内置回退 > OpenRouter(仅回退,USD→CNY,缓存读按配置折扣)> 无。
|
|
73
73
|
- **快照选取**——`snapshotForTime` 取 `effectiveAt <= 事件时间` 的最新快照(安装前的会话一次性锚到首个快照)。
|
|
74
|
-
- **峰值/闲时**——2026-08-17
|
|
74
|
+
- **峰值/闲时**——2026-08-17 上线前所有步按单一价目计费;上线后按事件自身时刻与该价格簿页面的时段表选档(中英文页面各自解析自己的高峰窗口,抓取失败时回退到北京 09:00–12:00 / 14:00–18:00,其余闲时)。每步的档位在折叠时锚定一次,逐回复卡片与回复小标签永远显示该轮对话当时被计价的档位,而不是查看时刻的档位。
|
|
75
75
|
- **不可变账本**——`sessionCost` 投影(`src/session-cost-projection.ts`)把 `request/header`(模型)与携带用量的事件折叠为逐步记录;同一 (turn, step) 的第二次用量样本**替换**第一条(同一步终结,而非重新计价),总计以 O(1) 增量维护。
|
|
76
76
|
|
|
77
77
|
## 项目结构
|
package/lib/client.js
CHANGED
|
@@ -155,6 +155,14 @@ window.__ModuleLoader__.load({
|
|
|
155
155
|
//#region src/pricing.ts
|
|
156
156
|
/** Official peak-pricing rollout: 2026-08-17 00:00 Beijing time (UTC+8). */
|
|
157
157
|
const PEAK_PRICING_START_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
|
|
158
|
+
/**
|
|
159
|
+
* Built-in fallback zh peak schedule: the official announcement's windows in
|
|
160
|
+
* Beijing time (peak 09:00-12:00 / 14:00-18:00, everything else off-peak).
|
|
161
|
+
*/
|
|
162
|
+
const PEAK_SCHEDULE_ZH = {
|
|
163
|
+
timezone: "Asia/Shanghai",
|
|
164
|
+
ranges: [[9, 12], [14, 18]]
|
|
165
|
+
};
|
|
158
166
|
/** Built-in fallback USD prices from the English pricing page. */
|
|
159
167
|
const FALLBACK_CURRENT_USD = {
|
|
160
168
|
flash: {
|
|
@@ -170,20 +178,22 @@ window.__ModuleLoader__.load({
|
|
|
170
178
|
};
|
|
171
179
|
({ ...FALLBACK_CURRENT_USD.flash }), { ...FALLBACK_CURRENT_USD.pro };
|
|
172
180
|
/**
|
|
173
|
-
* Whether the
|
|
174
|
-
* 09:00-12:00 and 14:00-18:00
|
|
181
|
+
* Whether the given instant is a peak-pricing hour under one schedule: the
|
|
182
|
+
* zh fallback is Beijing time 09:00-12:00 and 14:00-18:00; each pricebook
|
|
183
|
+
* passes the schedule parsed from its own (zh or en) pricing page.
|
|
175
184
|
* @param now - the instant to classify.
|
|
185
|
+
* @param schedule - the peak-hour schedule to classify against.
|
|
176
186
|
* @returns true during peak hours.
|
|
177
187
|
*/
|
|
178
|
-
function isPeakHour(now = /* @__PURE__ */ new Date()) {
|
|
188
|
+
function isPeakHour(now = /* @__PURE__ */ new Date(), schedule = PEAK_SCHEDULE_ZH) {
|
|
179
189
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
180
|
-
timeZone:
|
|
190
|
+
timeZone: schedule.timezone,
|
|
181
191
|
hour: "numeric",
|
|
182
192
|
hour12: false
|
|
183
193
|
}).formatToParts(now);
|
|
184
194
|
const hour = Number(parts.find((part) => part.type === "hour")?.value);
|
|
185
195
|
if (Number.isNaN(hour)) return false;
|
|
186
|
-
return
|
|
196
|
+
return schedule.ranges.some(([start, end]) => hour >= start && hour < end);
|
|
187
197
|
}
|
|
188
198
|
//#endregion
|
|
189
199
|
//#region src/client/cost-math.ts
|
|
@@ -238,10 +248,18 @@ window.__ModuleLoader__.load({
|
|
|
238
248
|
if (subagents === void 0 || subagents.length === 0) return main;
|
|
239
249
|
return sumTotals([main, ...subagents.map((entry) => entry.totals)]);
|
|
240
250
|
}
|
|
241
|
-
/**
|
|
242
|
-
|
|
251
|
+
/**
|
|
252
|
+
* The price band in effect at one instant (mirror of the host helper). The
|
|
253
|
+
* schedule defaults to the zh fallback; live readouts pass the schedule of
|
|
254
|
+
* the pricebook they display (the zh and en pages may state different
|
|
255
|
+
* windows).
|
|
256
|
+
* @param time - epoch millis.
|
|
257
|
+
* @param schedule - the peak-hour schedule to classify against.
|
|
258
|
+
* @returns the band.
|
|
259
|
+
*/
|
|
260
|
+
function bandForTime(time, schedule = PEAK_SCHEDULE_ZH) {
|
|
243
261
|
if (time < PEAK_PRICING_START_MS) return "single";
|
|
244
|
-
return isPeakHour(new Date(time)) ? "peak" : "offPeak";
|
|
262
|
+
return isPeakHour(new Date(time), schedule) ? "peak" : "offPeak";
|
|
245
263
|
}
|
|
246
264
|
/** The bucket of one model's entry for one band (mirror of the host helper). */
|
|
247
265
|
function bucketOf(modelPrice, band) {
|
|
@@ -270,10 +288,10 @@ window.__ModuleLoader__.load({
|
|
|
270
288
|
}
|
|
271
289
|
}
|
|
272
290
|
/** Resolve one model's bucket at one instant from a snapshot. */
|
|
273
|
-
function bucketAt(snapshot, provider, model, time) {
|
|
291
|
+
function bucketAt(snapshot, provider, model, time, schedule = PEAK_SCHEDULE_ZH) {
|
|
274
292
|
const entry = modelPriceOf(snapshot, provider, model);
|
|
275
293
|
if (entry === void 0) return void 0;
|
|
276
|
-
return bucketOf(entry, bandForTime(time));
|
|
294
|
+
return bucketOf(entry, bandForTime(time, schedule));
|
|
277
295
|
}
|
|
278
296
|
/**
|
|
279
297
|
* The peak-to-off-peak price multiplier for one model's entry, when the
|
|
@@ -720,7 +738,7 @@ window.__ModuleLoader__.load({
|
|
|
720
738
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
721
739
|
className: CostView_module_css_default.stepList,
|
|
722
740
|
children: rows.map((step) => {
|
|
723
|
-
const stepBand = bandForTime(
|
|
741
|
+
const stepBand = step.band ?? bandForTime(step.time);
|
|
724
742
|
const stepRatio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, step.provider, step.model);
|
|
725
743
|
const stepBandLabel = stepBand === "peak" ? stepRatio === null ? t("band.peak") : t("price.peakRatio", { multiplier: formatMultiplier(stepRatio) }) : stepBand === "offPeak" ? stepRatio === null ? t("band.offPeak") : t("price.offPeakRatio", { multiplier: formatMultiplier(1 / stepRatio) }) : null;
|
|
726
744
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -853,7 +871,7 @@ window.__ModuleLoader__.load({
|
|
|
853
871
|
});
|
|
854
872
|
}
|
|
855
873
|
const amount = `${currencySymbol(currency)}${formatMoney(ledger.cost)}`;
|
|
856
|
-
const band = bandForTime(
|
|
874
|
+
const band = ledger.band ?? bandForTime(ledger.time);
|
|
857
875
|
const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, ledger.provider, ledger.model);
|
|
858
876
|
const bandLabel = band === "peak" ? ratio === null ? t("band.peak") : t("price.peakRatio", { multiplier: formatMultiplier(ratio) }) : band === "offPeak" ? ratio === null ? t("band.offPeak") : t("price.offPeakRatio", { multiplier: formatMultiplier(1 / ratio) }) : null;
|
|
859
877
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -968,13 +986,14 @@ window.__ModuleLoader__.load({
|
|
|
968
986
|
const projection = cost;
|
|
969
987
|
const model = projection?.model ?? null;
|
|
970
988
|
const snapshot = response?.pricebook.current ?? null;
|
|
989
|
+
const schedule = response?.pricebook.schedule;
|
|
971
990
|
const subagents = response?.subagents;
|
|
972
991
|
const combined = combineTotals(projection?.totals, subagents);
|
|
973
992
|
const subSpend = subagentSpend(subagents);
|
|
974
993
|
const symbol = currencySymbol(currency);
|
|
975
994
|
const estimate = (0, react.useMemo)(() => {
|
|
976
995
|
if (!running || projection === void 0) return null;
|
|
977
|
-
const bucket = bucketAt(snapshot ?? void 0, model?.provider, model?.model ?? "unknown", Date.now());
|
|
996
|
+
const bucket = bucketAt(snapshot ?? void 0, model?.provider, model?.model ?? "unknown", Date.now(), schedule);
|
|
978
997
|
if (bucket === void 0) return null;
|
|
979
998
|
const stepEstimate = estimateCost(bucket, pressure?.projectedTokens ?? pressure?.pressureTokens ?? 0, cacheReadRatioOf(projection.steps), blocksOutputTokens(partial?.blocks));
|
|
980
999
|
return (combined?.cost ?? 0) + stepEstimate;
|
|
@@ -983,6 +1002,7 @@ window.__ModuleLoader__.load({
|
|
|
983
1002
|
projection,
|
|
984
1003
|
combined,
|
|
985
1004
|
snapshot,
|
|
1005
|
+
schedule,
|
|
986
1006
|
model,
|
|
987
1007
|
pressure,
|
|
988
1008
|
partial,
|
|
@@ -994,7 +1014,7 @@ window.__ModuleLoader__.load({
|
|
|
994
1014
|
const last = projection?.steps.length !== void 0 && projection.steps.length > 0 ? projection.steps[projection.steps.length - 1] : void 0;
|
|
995
1015
|
const sourceKey = snapshot?.source ?? null;
|
|
996
1016
|
const source = sourceKey === null ? t("source.none") : t(`source.${sourceKey}`);
|
|
997
|
-
const band = bandForTime(Date.now());
|
|
1017
|
+
const band = bandForTime(Date.now(), schedule);
|
|
998
1018
|
const peakRatio = peakOffPeakMultiplier(snapshot ?? void 0, model?.provider, model?.model ?? "unknown");
|
|
999
1019
|
const bandLabel = band === "peak" ? peakRatio === null ? t("band.peak") : t("price.peakRatio", { multiplier: formatMultiplier(peakRatio) }) : band === "offPeak" ? peakRatio === null ? t("band.offPeak") : t("price.offPeakRatio", { multiplier: formatMultiplier(1 / peakRatio) }) : null;
|
|
1000
1020
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|