@lancecheney/dsh-deepseek-balance 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 +10 -0
- package/README.zh.md +10 -0
- package/lib/client.js +9 -7
- package/lib/index.js +73 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) client plu
|
|
|
6
6
|
Spent ¥0.12 | Balance ¥47.17 | Peak | ¥27.0⁺/M output
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
Clicking the badge opens a right-side drawer with full usage stats:
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
9
15
|
## Features
|
|
10
16
|
|
|
11
17
|
- **Balance** — live DeepSeek API balance from `GET /user/balance`, proxied server-side so your API key never reaches the browser.
|
|
@@ -39,6 +45,10 @@ then restart `dsh web` and refresh the page.
|
|
|
39
45
|
|
|
40
46
|
Requires a `DEEPSEEK_API_KEY` configured through the harness credential store (or the `DEEPSEEK_API_KEY` environment variable).
|
|
41
47
|
|
|
48
|
+
## Security
|
|
49
|
+
|
|
50
|
+
The balance is proxied server-side: the browser only ever talks to the local `/api/deepseek-balance` route, which the host half forwards to DeepSeek. Your API key is resolved from the harness credential store (or the `DEEPSEEK_API_KEY` environment variable) and is only used server-side in the `Authorization: Bearer ...` header when calling DeepSeek — it never reaches the browser. The badge only shows a masked preview (`sk-8ce0e*****d899`) so you can tell which key is in use.
|
|
51
|
+
|
|
42
52
|
## Notes
|
|
43
53
|
|
|
44
54
|
- The **Spent** figure is an estimate: all tokens in a session are priced at the current period's rate. Real billing is computed by DeepSeek.
|
package/README.zh.md
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
消耗 ¥0.12 | 余额 ¥47.17 | 高峰 | ¥27.0⁺/M 输出
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
点开徽章会打开右侧抽屉面板,展示完整用量统计:
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
9
15
|
## 功能
|
|
10
16
|
|
|
11
17
|
- **余额** — 实时 DeepSeek API 余额(`GET /user/balance`),由服务端代理请求,浏览器不会接触你的 API Key。
|
|
@@ -39,6 +45,10 @@ dsh plugin --profile web add @lancecheney/dsh-deepseek-balance
|
|
|
39
45
|
|
|
40
46
|
需要已在 harness 凭据存储中配置 `DEEPSEEK_API_KEY`(或设置 `DEEPSEEK_API_KEY` 环境变量)。
|
|
41
47
|
|
|
48
|
+
## 安全
|
|
49
|
+
|
|
50
|
+
余额接口走服务端代理:浏览器只请求本地 `/api/deepseek-balance` 路由,由 host 半区转发到 DeepSeek。API Key 从 harness 凭据存储(或 `DEEPSEEK_API_KEY` 环境变量)解析,仅在服务端调用 DeepSeek 时放入 `Authorization: Bearer ...` 请求头,绝不进入浏览器。界面只显示脱敏后的预览(`sk-8ce0e*****d899`),方便确认当前用的是哪个密钥。
|
|
51
|
+
|
|
42
52
|
## 说明
|
|
43
53
|
|
|
44
54
|
- **消耗** 是估算值:会话内所有 token 都按当前时段价格计算,实际扣费以 DeepSeek 服务端为准。
|
package/lib/client.js
CHANGED
|
@@ -25,15 +25,16 @@ window.__ModuleLoader__.load({
|
|
|
25
25
|
const MODEL_META = { "deepseek-v4-pro": { label: "DeepSeek-V4-Pro" }, "deepseek-v4-flash": { label: "DeepSeek-V4-Flash" } };
|
|
26
26
|
const FALLBACK_PRICING = {
|
|
27
27
|
effectiveFrom: "2026-08-17T00:00:00+08:00",
|
|
28
|
+
weekendFrom: "2026-08-23T00:00:00+08:00",
|
|
28
29
|
currencies: {
|
|
29
|
-
CNY: { symbol: "¥", models: { "deepseek-v4-pro": { legacy: { hit: 0.025, miss: 3.0, output: 6.0 }, peak: { hit: 0.30, miss: 9.0, output: 27.0 }, offPeak: { hit: 0.15, miss: 4.5, output: 13.5 } }, "deepseek-v4-flash": { legacy: { hit: 0.02, miss: 1.0, output: 2.0 }, peak: { hit: 0.10, miss: 3.0, output: 9.0 }, offPeak: { hit: 0.05, miss: 1.5, output: 4.5 } } } },
|
|
30
|
-
USD: { symbol: "$", models: { "deepseek-v4-pro": { legacy: { hit: 0.003625, miss: 0.435, output: 0.87 }, peak: { hit: 0.044, miss: 1.32, output: 3.96 }, offPeak: { hit: 0.022, miss: 0.66, output: 1.98 } }, "deepseek-v4-flash": { legacy: { hit: 0.0028, miss: 0.14, output: 0.28 }, peak: { hit: 0.014, miss: 0.44, output: 1.32 }, offPeak: { hit: 0.007, miss: 0.22, output: 0.66 } } } }
|
|
30
|
+
CNY: { symbol: "¥", models: { "deepseek-v4-pro": { legacy: { hit: 0.025, miss: 3.0, output: 6.0 }, peak: { hit: 0.30, miss: 9.0, output: 27.0 }, offPeak: { hit: 0.15, miss: 4.5, output: 13.5 } }, "deepseek-v4-flash": { legacy: { hit: 0.02, miss: 1.0, output: 2.0 }, peak: { hit: 0.10, miss: 3.0, output: 9.0 }, offPeak: { hit: 0.05, miss: 1.5, output: 4.5 } }, "deepseek-v4-flash-vision-exp": { legacy: { hit: 0.02, miss: 1.0, output: 2.0 }, peak: { hit: 0.10, miss: 3.0, output: 9.0 }, offPeak: { hit: 0.05, miss: 1.5, output: 4.5 } } } },
|
|
31
|
+
USD: { symbol: "$", models: { "deepseek-v4-pro": { legacy: { hit: 0.003625, miss: 0.435, output: 0.87 }, peak: { hit: 0.044, miss: 1.32, output: 3.96 }, offPeak: { hit: 0.022, miss: 0.66, output: 1.98 } }, "deepseek-v4-flash": { legacy: { hit: 0.0028, miss: 0.14, output: 0.28 }, peak: { hit: 0.014, miss: 0.44, output: 1.32 }, offPeak: { hit: 0.007, miss: 0.22, output: 0.66 } }, "deepseek-v4-flash-vision-exp": { legacy: { hit: 0.0028, miss: 0.14, output: 0.28 }, peak: { hit: 0.014, miss: 0.44, output: 1.32 }, offPeak: { hit: 0.007, miss: 0.22, output: 0.66 } } } }
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
const DEFAULT_MODEL = "deepseek-v4-pro";
|
|
34
35
|
|
|
35
36
|
function beijingDecimalHour(now) { let h = 0, m = 0; try { const p = new Intl.DateTimeFormat("en-GB", { timeZone: "Asia/Shanghai", hour12: false, hour: "2-digit", minute: "2-digit" }).formatToParts(now); for (const x of p) { if (x.type === "hour") h = Number(x.value); if (x.type === "minute") m = Number(x.value); } } catch { h = now.getHours(); m = now.getMinutes(); } return h + m / 60; }
|
|
36
|
-
function periodFor(now, effectiveFrom) { const eff = Date.parse(effectiveFrom); if (Number.isFinite(eff) && now.getTime() < eff) return "legacy"; const h = beijingDecimalHour(now); return (h >= 9 && h < 12) || (h >= 14 && h < 18) ? "peak" : "offPeak"; }
|
|
37
|
+
function periodFor(now, effectiveFrom, weekendFrom) { const eff = Date.parse(effectiveFrom); if (Number.isFinite(eff) && now.getTime() < eff) return "legacy"; const wk = Date.parse(weekendFrom); const d = new Date(now.getTime() + 8 * 3600 * 1000); const day = d.getUTCDay(); if ((day === 0 || day === 6) && Number.isFinite(wk) && now.getTime() >= wk) return "offPeak"; const h = beijingDecimalHour(now); return (h >= 9 && h < 12) || (h >= 14 && h < 18) ? "peak" : "offPeak"; }
|
|
37
38
|
function firstBalance(data) { const infos = data && data.balance_infos; if (!Array.isArray(infos) || infos.length === 0) return null; return infos.find((r) => r.currency === "CNY") || infos[0]; }
|
|
38
39
|
function fmtPrice(v) { const n = Number(v); return Number.isFinite(n) ? String(n) : String(v); }
|
|
39
40
|
function fmtMoney(v) { const n = Number(v); if (!Number.isFinite(n)) return "—"; if (n <= 0) return "0.00"; if (n < 0.01) return n.toFixed(4); if (n < 1) return n.toFixed(3); return n.toFixed(2); }
|
|
@@ -89,6 +90,7 @@ window.__ModuleLoader__.load({
|
|
|
89
90
|
|
|
90
91
|
const pricing = state.pricing || FALLBACK_PRICING;
|
|
91
92
|
const effectiveFrom = pricing.effectiveFrom || FALLBACK_PRICING.effectiveFrom;
|
|
93
|
+
const weekendFrom = pricing.weekendFrom || FALLBACK_PRICING.weekendFrom;
|
|
92
94
|
const balanceRow = state.phase === "ok" && state.balance ? firstBalance(state.balance) : null;
|
|
93
95
|
const currency = balanceRow && balanceRow.currency ? balanceRow.currency : "CNY";
|
|
94
96
|
const dict = currency === "USD" ? en : zh;
|
|
@@ -102,7 +104,7 @@ window.__ModuleLoader__.load({
|
|
|
102
104
|
const modelId = currentSelection && currentSelection.model ? currentSelection.model : DEFAULT_MODEL;
|
|
103
105
|
const reasoningEffort = currentSelection ? currentSelection.reasoningEffort : void 0;
|
|
104
106
|
|
|
105
|
-
const period = periodFor(new Date(), effectiveFrom);
|
|
107
|
+
const period = periodFor(new Date(), effectiveFrom, weekendFrom);
|
|
106
108
|
const isPeak = period === "peak";
|
|
107
109
|
const isLegacy = period === "legacy";
|
|
108
110
|
const model = models[modelId] || models[DEFAULT_MODEL] || FALLBACK_PRICING.currencies.CNY.models[DEFAULT_MODEL];
|
|
@@ -233,7 +235,7 @@ window.__ModuleLoader__.load({
|
|
|
233
235
|
stat(tr("stat.total"), usageData ? fmtTokens(usageData.totalTokens) : "…"), sepBar("s1"),
|
|
234
236
|
stat(tr("stat.dailyPeak"), usageData ? fmtTokens(usageData.dailyPeakTokens) : "…"), sepBar("s2"),
|
|
235
237
|
stat(tr("stat.longest"), usageData ? fmtDuration(usageData.longestChatMs) : "…"), sepBar("s3"),
|
|
236
|
-
stat(tr("stat.
|
|
238
|
+
stat(tr("stat.todayTokens"), usageData && usageData.days && usageData.days[beijingDayKey(Date.now())] ? fmtTokens(usageData.days[beijingDayKey(Date.now())].tokens) : "…"), sepBar("s4"),
|
|
237
239
|
stat(tr("stat.longStreak"), usageData ? usageData.longestStreak + tr("unit.day") : "…")
|
|
238
240
|
),
|
|
239
241
|
react.createElement("div", { className: P.balance },
|
|
@@ -321,7 +323,7 @@ window.__ModuleLoader__.load({
|
|
|
321
323
|
"balance.unavailable": "余额不可用",
|
|
322
324
|
"panel.stats": "统计", "panel.balance": "余额与消耗", "panel.topup": "跳转API官网", "panel.save": "保存",
|
|
323
325
|
"panel.topSessions": "消耗最多的对话", "panel.month": "每日分布", "panel.monthPeriod": "每日时段分布", "panel.monthTokens": "每日 Token 消耗", "panel.peakSessions": "高峰消耗排行", "panel.modelEffort": "模型与思考强度",
|
|
324
|
-
"stat.total": "累计 Token", "stat.dailyPeak": "每日峰值", "stat.longest": "最长聊天", "stat.
|
|
326
|
+
"stat.total": "累计 Token", "stat.dailyPeak": "每日峰值", "stat.longest": "最长聊天", "stat.longStreak": "最长连续", "stat.todayTokens": "今日 Token", "stat.today": "今日消耗", "stat.cumulative": "累计消耗",
|
|
325
327
|
"unit.day": " 天",
|
|
326
328
|
"legend.flat": "平价", "legend.peak": "高峰", "legend.off": "空闲", "legend.none": "无数据", "legend.less": "少", "legend.more": "多"
|
|
327
329
|
};
|
|
@@ -331,7 +333,7 @@ window.__ModuleLoader__.load({
|
|
|
331
333
|
"balance.unavailable": "balance unavailable",
|
|
332
334
|
"panel.stats": "Stats", "panel.balance": "Balance & Spend", "panel.topup": "API site", "panel.save": "Save",
|
|
333
335
|
"panel.topSessions": "Top conversations", "panel.month": "Daily", "panel.monthPeriod": "Daily period", "panel.monthTokens": "Daily token usage", "panel.peakSessions": "Peak-hour ranking", "panel.modelEffort": "Model & effort",
|
|
334
|
-
"stat.total": "Total tokens", "stat.dailyPeak": "Daily peak", "stat.longest": "Longest chat", "stat.
|
|
336
|
+
"stat.total": "Total tokens", "stat.dailyPeak": "Daily peak", "stat.longest": "Longest chat", "stat.longStreak": "Longest streak", "stat.todayTokens": "Today tokens", "stat.today": "Today", "stat.cumulative": "Total",
|
|
335
337
|
"unit.day": "d",
|
|
336
338
|
"legend.flat": "Flat", "legend.peak": "Peak", "legend.off": "Off-peak", "legend.none": "None", "legend.less": "Less", "legend.more": "More"
|
|
337
339
|
};
|
package/lib/index.js
CHANGED
|
@@ -29,6 +29,7 @@ const PRICING_FETCH_TIMEOUT_MS = 15000;
|
|
|
29
29
|
/** Last-known-good prices: legacy + the 2026-08-17 peak/off-peak table, per currency. */
|
|
30
30
|
const FALLBACK_PRICING = {
|
|
31
31
|
effectiveFrom: "2026-08-17T00:00:00+08:00",
|
|
32
|
+
weekendFrom: "2026-08-23T00:00:00+08:00",
|
|
32
33
|
currencies: {
|
|
33
34
|
CNY: {
|
|
34
35
|
symbol: "¥",
|
|
@@ -42,6 +43,11 @@ const FALLBACK_PRICING = {
|
|
|
42
43
|
legacy: { hit: 0.025, miss: 3.0, output: 6.0 },
|
|
43
44
|
peak: { hit: 0.30, miss: 9.0, output: 27.0 },
|
|
44
45
|
offPeak: { hit: 0.15, miss: 4.5, output: 13.5 }
|
|
46
|
+
},
|
|
47
|
+
"deepseek-v4-flash-vision-exp": {
|
|
48
|
+
legacy: { hit: 0.02, miss: 1.0, output: 2.0 },
|
|
49
|
+
peak: { hit: 0.10, miss: 3.0, output: 9.0 },
|
|
50
|
+
offPeak: { hit: 0.05, miss: 1.5, output: 4.5 }
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
},
|
|
@@ -57,6 +63,11 @@ const FALLBACK_PRICING = {
|
|
|
57
63
|
legacy: { hit: 0.003625, miss: 0.435, output: 0.87 },
|
|
58
64
|
peak: { hit: 0.044, miss: 1.32, output: 3.96 },
|
|
59
65
|
offPeak: { hit: 0.022, miss: 0.66, output: 1.98 }
|
|
66
|
+
},
|
|
67
|
+
"deepseek-v4-flash-vision-exp": {
|
|
68
|
+
legacy: { hit: 0.0028, miss: 0.14, output: 0.28 },
|
|
69
|
+
peak: { hit: 0.014, miss: 0.44, output: 1.32 },
|
|
70
|
+
offPeak: { hit: 0.007, miss: 0.22, output: 0.66 }
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
}
|
|
@@ -136,81 +147,84 @@ const number = (match, index) => {
|
|
|
136
147
|
return Number.isFinite(value) ? value : void 0;
|
|
137
148
|
};
|
|
138
149
|
|
|
139
|
-
/** Parse the zh-CN docs page: CNY
|
|
150
|
+
/** Parse the zh-CN docs page: CNY peak/off-peak table (3 columns). */
|
|
140
151
|
function parseCnPricing(html) {
|
|
141
152
|
const text = stripTags(html);
|
|
142
|
-
const
|
|
143
|
-
const re = new RegExp(
|
|
153
|
+
const grab = (label) => {
|
|
154
|
+
const re = new RegExp(label + "\\s*空闲时段\\s*([\\d.]+)元\\s*([\\d.]+)元\\s*([\\d.]+)元\\s*高峰时段\\s*([\\d.]+)元\\s*([\\d.]+)元\\s*([\\d.]+)元");
|
|
144
155
|
const m = text.match(re);
|
|
145
156
|
if (!m) return void 0;
|
|
146
157
|
return {
|
|
147
|
-
offPeak:
|
|
148
|
-
peak:
|
|
158
|
+
offPeak: [number(m, 1), number(m, 2), number(m, 3)],
|
|
159
|
+
peak: [number(m, 4), number(m, 5), number(m, 6)]
|
|
149
160
|
};
|
|
150
161
|
};
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let legacy;
|
|
163
|
-
const lm = text.match(/百万tokens输入(缓存命中)\s*([\d.]+)元\s*([\d.]+)元\s*百万tokens输入(缓存未命中)\s*([\d.]+)元\s*([\d.]+)元\s*百万tokens输出\s*([\d.]+)元\s*([\d.]+)元/);
|
|
164
|
-
if (lm) {
|
|
165
|
-
legacy = {
|
|
166
|
-
flash: { hit: number(lm, 1), miss: number(lm, 3), output: number(lm, 5) },
|
|
167
|
-
pro: { hit: number(lm, 2), miss: number(lm, 4), output: number(lm, 6) }
|
|
168
|
-
};
|
|
169
|
-
}
|
|
162
|
+
const hit = grab("百万tokens输入\\s*(缓存命中)");
|
|
163
|
+
const miss = grab("百万tokens输入\\s*(缓存未命中)");
|
|
164
|
+
const out = grab("百万tokens输出");
|
|
165
|
+
if (!hit || !miss || !out) throw new Error("CNY peak/off-peak table not found");
|
|
166
|
+
|
|
167
|
+
const mk = (i) => ({
|
|
168
|
+
offPeak: { hit: hit.offPeak[i], miss: miss.offPeak[i], output: out.offPeak[i] },
|
|
169
|
+
peak: { hit: hit.peak[i], miss: miss.peak[i], output: out.peak[i] }
|
|
170
|
+
});
|
|
170
171
|
|
|
171
172
|
return {
|
|
172
|
-
effectiveFrom,
|
|
173
173
|
models: {
|
|
174
|
-
"deepseek-v4-flash":
|
|
175
|
-
"deepseek-v4-pro":
|
|
174
|
+
"deepseek-v4-flash": mk(0),
|
|
175
|
+
"deepseek-v4-pro": mk(1),
|
|
176
|
+
"deepseek-v4-flash-vision-exp": mk(2)
|
|
176
177
|
}
|
|
177
178
|
};
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
/** Parse the English docs page: USD
|
|
181
|
+
/** Parse the English docs page: USD peak/off-peak table (3 columns). */
|
|
181
182
|
function parseUsModels(html) {
|
|
182
183
|
const text = stripTags(html);
|
|
183
|
-
const
|
|
184
|
-
const re = new RegExp(
|
|
184
|
+
const grab = (label) => {
|
|
185
|
+
const re = new RegExp(label + "\\s+OFF-PEAK\\s+\\$([\\d.]+)\\s+\\$([\\d.]+)\\s+\\$([\\d.]+)\\s+PEAK\\s+\\$([\\d.]+)\\s+\\$([\\d.]+)\\s+\\$([\\d.]+)", "i");
|
|
185
186
|
const m = text.match(re);
|
|
186
187
|
if (!m) return void 0;
|
|
187
188
|
return {
|
|
188
|
-
offPeak:
|
|
189
|
-
peak:
|
|
189
|
+
offPeak: [number(m, 1), number(m, 2), number(m, 3)],
|
|
190
|
+
peak: [number(m, 4), number(m, 5), number(m, 6)]
|
|
190
191
|
};
|
|
191
192
|
};
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
pro: { hit: number(lm, 2), miss: number(lm, 4), output: number(lm, 6) }
|
|
202
|
-
};
|
|
203
|
-
}
|
|
193
|
+
const hit = grab("1M INPUT TOKENS\\s*\\(CACHE HIT\\)");
|
|
194
|
+
const miss = grab("1M INPUT TOKENS\\s*\\(CACHE MISS\\)");
|
|
195
|
+
const out = grab("1M OUTPUT TOKENS");
|
|
196
|
+
if (!hit || !miss || !out) throw new Error("USD peak/off-peak table not found");
|
|
197
|
+
|
|
198
|
+
const mk = (i) => ({
|
|
199
|
+
offPeak: { hit: hit.offPeak[i], miss: miss.offPeak[i], output: out.offPeak[i] },
|
|
200
|
+
peak: { hit: hit.peak[i], miss: miss.peak[i], output: out.peak[i] }
|
|
201
|
+
});
|
|
204
202
|
|
|
205
203
|
return {
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
models: {
|
|
205
|
+
"deepseek-v4-flash": mk(0),
|
|
206
|
+
"deepseek-v4-pro": mk(1),
|
|
207
|
+
"deepseek-v4-flash-vision-exp": mk(2)
|
|
208
|
+
}
|
|
208
209
|
};
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
const pricing = { data: FALLBACK_PRICING, fetchedAt: 0, source: "fallback" };
|
|
212
213
|
let pricingRefresh = null;
|
|
213
214
|
|
|
215
|
+
function mergeLegacy(models, fallbackModels) {
|
|
216
|
+
const result = {};
|
|
217
|
+
for (const [id, m] of Object.entries(models)) {
|
|
218
|
+
const fb = fallbackModels[id];
|
|
219
|
+
result[id] = {
|
|
220
|
+
...(fb && fb.legacy ? { legacy: fb.legacy } : {}),
|
|
221
|
+
peak: m.peak,
|
|
222
|
+
offPeak: m.offPeak
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
214
228
|
async function refreshPricing(ctx) {
|
|
215
229
|
if (pricingRefresh) return pricingRefresh;
|
|
216
230
|
pricingRefresh = (async () => {
|
|
@@ -218,6 +232,7 @@ async function refreshPricing(ctx) {
|
|
|
218
232
|
if (ctx?.logger?.warn) ctx.logger.warn(`deepseek-balance: ${message}`);
|
|
219
233
|
};
|
|
220
234
|
let effectiveFrom = FALLBACK_PRICING.effectiveFrom;
|
|
235
|
+
let weekendFrom = FALLBACK_PRICING.weekendFrom;
|
|
221
236
|
let cnModels = FALLBACK_PRICING.currencies.CNY.models;
|
|
222
237
|
let usModels = FALLBACK_PRICING.currencies.USD.models;
|
|
223
238
|
try {
|
|
@@ -228,8 +243,7 @@ async function refreshPricing(ctx) {
|
|
|
228
243
|
if (cnRes.ok) {
|
|
229
244
|
try {
|
|
230
245
|
const parsed = parseCnPricing(await cnRes.text());
|
|
231
|
-
|
|
232
|
-
cnModels = parsed.models;
|
|
246
|
+
cnModels = mergeLegacy(parsed.models, FALLBACK_PRICING.currencies.CNY.models);
|
|
233
247
|
} catch (error) {
|
|
234
248
|
warn(`CNY pricing parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
249
|
}
|
|
@@ -238,7 +252,8 @@ async function refreshPricing(ctx) {
|
|
|
238
252
|
}
|
|
239
253
|
if (usRes.ok) {
|
|
240
254
|
try {
|
|
241
|
-
|
|
255
|
+
const parsed = parseUsModels(await usRes.text());
|
|
256
|
+
usModels = mergeLegacy(parsed.models, FALLBACK_PRICING.currencies.USD.models);
|
|
242
257
|
} catch (error) {
|
|
243
258
|
warn(`USD pricing parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
259
|
}
|
|
@@ -247,6 +262,7 @@ async function refreshPricing(ctx) {
|
|
|
247
262
|
}
|
|
248
263
|
pricing.data = {
|
|
249
264
|
effectiveFrom,
|
|
265
|
+
weekendFrom,
|
|
250
266
|
currencies: {
|
|
251
267
|
CNY: { symbol: "¥", models: cnModels },
|
|
252
268
|
USD: { symbol: "$", models: usModels }
|
|
@@ -333,9 +349,12 @@ function beijingDayKey(ms) {
|
|
|
333
349
|
return new Date(ms + 8 * 3600 * 1000).toISOString().slice(0, 10);
|
|
334
350
|
}
|
|
335
351
|
|
|
336
|
-
function periodOfUsage(time, effectiveFromMs) {
|
|
352
|
+
function periodOfUsage(time, effectiveFromMs, weekendFromMs) {
|
|
337
353
|
if (time < effectiveFromMs) return "flat";
|
|
338
|
-
const
|
|
354
|
+
const d = new Date(time + 8 * 3600 * 1000);
|
|
355
|
+
const day = d.getUTCDay();
|
|
356
|
+
if ((day === 0 || day === 6) && Number.isFinite(weekendFromMs) && time >= weekendFromMs) return "offPeak";
|
|
357
|
+
const hour = d.getUTCHours();
|
|
339
358
|
return (hour >= 9 && hour < 12) || (hour >= 14 && hour < 18) ? "peak" : "offPeak";
|
|
340
359
|
}
|
|
341
360
|
|
|
@@ -378,6 +397,7 @@ function aggregateSessions(archivedIds = []) {
|
|
|
378
397
|
if (!existsSync(root)) return summary;
|
|
379
398
|
const effectiveFromMs = Date.parse(pricing.data.effectiveFrom || FALLBACK_PRICING.effectiveFrom);
|
|
380
399
|
if (!Number.isFinite(effectiveFromMs)) return summary;
|
|
400
|
+
const weekendFromMs = Date.parse(pricing.data.weekendFrom || FALLBACK_PRICING.weekendFrom);
|
|
381
401
|
|
|
382
402
|
let wsDirs = [];
|
|
383
403
|
try { wsDirs = readdirSync(root); } catch { return summary; }
|
|
@@ -440,7 +460,7 @@ function aggregateSessions(archivedIds = []) {
|
|
|
440
460
|
else if (r.type === "assistant/chunk" && r.data?.chunk?.type === "usage") usage = r.data.chunk.usage;
|
|
441
461
|
if (usage && time !== null && dedupe.has(r)) {
|
|
442
462
|
const t = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) + (usage.cacheReadTokens ?? 0);
|
|
443
|
-
const period = periodOfUsage(time, effectiveFromMs);
|
|
463
|
+
const period = periodOfUsage(time, effectiveFromMs, weekendFromMs);
|
|
444
464
|
const dayKey = beijingDayKey(time);
|
|
445
465
|
tokens += t;
|
|
446
466
|
if (period === "peak") peakTokens += t;
|
|
@@ -705,7 +725,7 @@ function apply(ctx, config = {}) {
|
|
|
705
725
|
spend: computeSpend({ byModel: s.byModel || {} }, usageCache.currency || "CNY"),
|
|
706
726
|
apiKeyPreview: key,
|
|
707
727
|
tokenName: resolveTokenName(config),
|
|
708
|
-
effectiveFrom: pricing.data.effectiveFrom,
|
|
728
|
+
effectiveFrom: pricing.data.effectiveFrom, weekendFrom: pricing.data.weekendFrom,
|
|
709
729
|
fetchedAt: usageCache.at,
|
|
710
730
|
source: pricing.source
|
|
711
731
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lancecheney/dsh-deepseek-balance",
|
|
3
3
|
"description": "Session-header DeepSeek API balance, per-conversation spend, and peak/off-peak pricing indicator",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|