@kenz1117/dsh-ui-usage-billing 1.4.8 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/client.js +5 -5
- package/lib/index.js +59 -14
- package/lib/types/client/pricing.d.ts +11 -5
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -298,16 +298,22 @@ function isPeakHour(beijingHour) {
|
|
|
298
298
|
/**
|
|
299
299
|
* 由时刻(epoch 毫秒)推断计费时段;时刻未知/非法时按高峰计(保守:未知
|
|
300
300
|
* 时刻不低估成本,与社区 dsh-usage-chart 的 tierAt 语义一致)。
|
|
301
|
-
*
|
|
301
|
+
* 周末(北京时间周六/周日)与中国法定节假日全天不区分峰谷,统一按低谷价
|
|
302
|
+
* (官方 2026-09-19 口径:调休上班的周末、法定节假日全天均按空闲时段计费;
|
|
303
|
+
* 调休上班日全部落在周末,故由周末分支覆盖,无需单列)。
|
|
302
304
|
* @param timeMs - Unix epoch 毫秒;null/undefined/NaN 视为未知。
|
|
303
305
|
*/
|
|
304
306
|
function tierAt(timeMs) {
|
|
305
307
|
if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return "peak";
|
|
306
|
-
return tierAtWithBounds(timeMs, TIER_BOUNDARY_MINUTES);
|
|
308
|
+
return tierAtWithBounds(timeMs, TIER_BOUNDARY_MINUTES, true);
|
|
307
309
|
}
|
|
308
|
-
/**
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
/**
|
|
311
|
+
* 按给定峰段边界判档:低谷日全天低谷;工作日分钟数落在任一 [b(i), b(i+1)) 峰段即为高峰。
|
|
312
|
+
* @param holidays - 是否把中国法定节假日并入低谷日。该官方口径只适用于 DeepSeek 按量分时;
|
|
313
|
+
* 智谱 Coding Plan 的积分口径未见节假日说明,故默认不启用,由调用点显式传入。
|
|
314
|
+
*/
|
|
315
|
+
function tierAtWithBounds(timeMs, bounds, holidays = false) {
|
|
316
|
+
if (isBeijingWeekend(timeMs) || holidays && isBeijingHoliday(timeMs)) return "offPeak";
|
|
311
317
|
const minute = Math.floor(beijingMillisOfDay(timeMs) / 6e4);
|
|
312
318
|
for (let i = 0; i + 1 < bounds.length; i += 2) {
|
|
313
319
|
const start = bounds[i] ?? 0;
|
|
@@ -321,6 +327,28 @@ function isBeijingWeekend(timeMs) {
|
|
|
321
327
|
const day = new Date(timeMs + 288e5).getUTCDay();
|
|
322
328
|
return day === 0 || day === 6;
|
|
323
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* 中国法定节假日 —— **生成块,勿手改**。数据源
|
|
332
|
+
* [NateScarlet/holiday-cn](https://github.com/NateScarlet/holiday-cn)(逐年抓取国务院公告;
|
|
333
|
+
* 2026 年依据 https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm)。
|
|
334
|
+
*
|
|
335
|
+
* 只收录峰谷计费起点({@link WEEKEND_OFFPEAK_START_MS},北京时间 2026-08-23 00:00)之后的日期:
|
|
336
|
+
* 更早的调用由 v1 规则定价,不涉及节假日。表中没有的年份退化为「仅周末低谷」,
|
|
337
|
+
* 国务院公布下一年安排后在此追加一条即可。
|
|
338
|
+
*
|
|
339
|
+
* 编码为空格分隔的 `YYYYMMDD` 列表(按年分组、同年日期连续排列,便于逐年追加):
|
|
340
|
+
* 每个键长度固定为 8,故 `includes` 不会跨键误匹配;相比「区间 + 运行时展开」
|
|
341
|
+
* 省掉一段解析代码——client / index 两个 face 都要过 256 KiB 单文件上限。
|
|
342
|
+
*/
|
|
343
|
+
const CHINA_HOLIDAYS = "20260925 20260926 20260927 20261001 20261002 20261003 20261004 20261005 20261006 20261007";
|
|
344
|
+
/** 北京日期键 YYYYMMDD(与宿主时区无关)。 */
|
|
345
|
+
function beijingDateKey(timeMs) {
|
|
346
|
+
return new Date(timeMs + 288e5).toISOString().slice(0, 10).replace(/-/g, "");
|
|
347
|
+
}
|
|
348
|
+
/** 时刻是否落在北京时间法定节假日(仅覆盖 {@link CHINA_HOLIDAYS} 内已收录的年份)。 */
|
|
349
|
+
function isBeijingHoliday(timeMs) {
|
|
350
|
+
return CHINA_HOLIDAYS.includes(beijingDateKey(timeMs));
|
|
351
|
+
}
|
|
324
352
|
/** 峰谷切换边界(北京时间的当日分钟数):09:00 / 12:00 / 14:00 / 18:00。 */
|
|
325
353
|
const TIER_BOUNDARY_MINUTES = [
|
|
326
354
|
540,
|
|
@@ -5157,6 +5185,21 @@ async function requestJson(url, init, timeoutMs) {
|
|
|
5157
5185
|
maxDelayMs: 2e3
|
|
5158
5186
|
});
|
|
5159
5187
|
}
|
|
5188
|
+
/**
|
|
5189
|
+
* 订阅端点挂在服务 origin 下(/coding、/zen、/api、/alpha 前缀),而用户在
|
|
5190
|
+
* chat 路由上配置的 baseUrl 通常带路径前缀(如 CommandCode 网关的
|
|
5191
|
+
* `https://api.commandcode.ai/provider/v1`,issue #75)——整段拼接会把 chat
|
|
5192
|
+
* 前缀带进订阅端点导致 404。只取 origin;未配置回退官方默认,非法值同样
|
|
5193
|
+
* 回退(订阅查询走「未读出」兜底而非抛错)。
|
|
5194
|
+
*/
|
|
5195
|
+
function subscriptionOrigin(configBaseUrl, fallback) {
|
|
5196
|
+
if (configBaseUrl === void 0 || configBaseUrl.trim() === "") return fallback;
|
|
5197
|
+
try {
|
|
5198
|
+
return new URL(configBaseUrl).origin;
|
|
5199
|
+
} catch {
|
|
5200
|
+
return fallback;
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5160
5203
|
/** Parse one Kimi limit window entry. */
|
|
5161
5204
|
function kimiWindow(value, kind) {
|
|
5162
5205
|
if (value === null || typeof value !== "object") return null;
|
|
@@ -5194,7 +5237,7 @@ function parseKimi(body) {
|
|
|
5194
5237
|
/** Collect the Kimi For Coding quota. */
|
|
5195
5238
|
async function collectKimi(keys, config, timeoutMs) {
|
|
5196
5239
|
const apiKey = keys.kimiApiKey.trim();
|
|
5197
|
-
const base = config.baseUrl
|
|
5240
|
+
const base = subscriptionOrigin(config.baseUrl, "https://api.kimi.com");
|
|
5198
5241
|
if (apiKey === "") return {
|
|
5199
5242
|
provider: config.provider,
|
|
5200
5243
|
displayName: "Kimi For Coding",
|
|
@@ -5379,7 +5422,7 @@ function parseOpenCodeGoApi(body) {
|
|
|
5379
5422
|
/** Collect the OpenCode Go quota. */
|
|
5380
5423
|
async function collectOpenCodeGo(keys, config, timeoutMs) {
|
|
5381
5424
|
const apiKey = keys.opencodeApiKey.trim();
|
|
5382
|
-
const base = config.baseUrl
|
|
5425
|
+
const base = subscriptionOrigin(config.baseUrl, "https://opencode.ai");
|
|
5383
5426
|
if (apiKey === "") return {
|
|
5384
5427
|
provider: config.provider,
|
|
5385
5428
|
displayName: "OpenCode Go",
|
|
@@ -5452,12 +5495,14 @@ function parseMiniMaxRemains(body) {
|
|
|
5452
5495
|
* Resolve the MiniMax API host based on the configured provider id.
|
|
5453
5496
|
*
|
|
5454
5497
|
* 国内开发者走 MiniMax(`api.minimaxi.com`),海外走 MiniMax(`minimaxi.com`)。
|
|
5455
|
-
* User-explicit `config.baseUrl` wins when set, so deployments in
|
|
5456
|
-
* region can still override the auto-pick (e.g. proxies / staging)
|
|
5498
|
+
* User-explicit `config.baseUrl` wins by origin when set, so deployments in
|
|
5499
|
+
* either region can still override the auto-pick (e.g. proxies / staging);
|
|
5500
|
+
* chat path prefixes (e.g. `/v1`) are dropped — `miniMaxEndpoint` re-adds the
|
|
5501
|
+
* version segment itself.
|
|
5457
5502
|
*/
|
|
5458
5503
|
function resolveMiniMaxBaseUrl(config) {
|
|
5459
|
-
|
|
5460
|
-
return config.
|
|
5504
|
+
const fallback = config.provider === "minimax-cn" || config.provider === "minimax-token-plan-cn" ? "https://api.minimaxi.com" : "https://www.minimaxi.com";
|
|
5505
|
+
return subscriptionOrigin(config.baseUrl, fallback);
|
|
5461
5506
|
}
|
|
5462
5507
|
/** Display name for a MiniMax quota row, aligned with the display-name map. */
|
|
5463
5508
|
function minmaxDisplayName(provider) {
|
|
@@ -5539,7 +5584,7 @@ function parseOpenRouterCredits(body) {
|
|
|
5539
5584
|
/** Collect the OpenRouter prepaid credits usage. */
|
|
5540
5585
|
async function collectOpenRouter(keys, config, timeoutMs) {
|
|
5541
5586
|
const apiKey = keys.openrouterApiKey.trim();
|
|
5542
|
-
const base = config.baseUrl
|
|
5587
|
+
const base = subscriptionOrigin(config.baseUrl, "https://openrouter.ai");
|
|
5543
5588
|
if (apiKey === "") return {
|
|
5544
5589
|
provider: config.provider,
|
|
5545
5590
|
displayName: "OpenRouter",
|
|
@@ -5601,7 +5646,7 @@ function parseAnthropicUsage(body) {
|
|
|
5601
5646
|
/** Collect the Claude Pro/Max subscription usage via the OAuth usage endpoint. */
|
|
5602
5647
|
async function collectAnthropic(keys, config, timeoutMs) {
|
|
5603
5648
|
const token = keys.anthropicApiKey.trim();
|
|
5604
|
-
const base = config.baseUrl
|
|
5649
|
+
const base = subscriptionOrigin(config.baseUrl, "https://api.anthropic.com");
|
|
5605
5650
|
const displayName = "Claude (Anthropic)";
|
|
5606
5651
|
if (token === "") return {
|
|
5607
5652
|
provider: config.provider,
|
|
@@ -5665,7 +5710,7 @@ function parseCommandCodeCredits(body) {
|
|
|
5665
5710
|
/** Collect the CommandCode quota (5h/weekly windows + monthly credits). */
|
|
5666
5711
|
async function collectCommandCode(keys, config, timeoutMs) {
|
|
5667
5712
|
const apiKey = keys.commandcodeApiKey.trim();
|
|
5668
|
-
const base = config.baseUrl
|
|
5713
|
+
const base = subscriptionOrigin(config.baseUrl, "https://api.commandcode.ai");
|
|
5669
5714
|
const displayName = SUBSCRIPTION_DISPLAY_NAMES["commandcode"] ?? "CommandCode";
|
|
5670
5715
|
if (apiKey === "") return {
|
|
5671
5716
|
provider: config.provider,
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* Google-style two-band billing is modeled per model: Gemini's Flex tier
|
|
11
11
|
* prices spare-capacity traffic at -50%; DeepSeek splits peak
|
|
12
12
|
* (weekdays 09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate —
|
|
13
|
-
* weekends (Sat/Sun, Beijing)
|
|
13
|
+
* weekends (Sat/Sun, Beijing) and Chinese statutory holidays are charged at
|
|
14
|
+
* the off-peak rate all day (see {@link CHINA_HOLIDAYS}).
|
|
14
15
|
* The estimator mixes both bands by a configured peak share ({@link DEFAULT_PEAK_SHARE}).
|
|
15
16
|
*
|
|
16
17
|
* Time-limited launch promos ({@link PricePromo}) never mutate the catalog:
|
|
@@ -182,7 +183,9 @@ export declare function isPeakHour(beijingHour: number): boolean;
|
|
|
182
183
|
/**
|
|
183
184
|
* 由时刻(epoch 毫秒)推断计费时段;时刻未知/非法时按高峰计(保守:未知
|
|
184
185
|
* 时刻不低估成本,与社区 dsh-usage-chart 的 tierAt 语义一致)。
|
|
185
|
-
*
|
|
186
|
+
* 周末(北京时间周六/周日)与中国法定节假日全天不区分峰谷,统一按低谷价
|
|
187
|
+
* (官方 2026-09-19 口径:调休上班的周末、法定节假日全天均按空闲时段计费;
|
|
188
|
+
* 调休上班日全部落在周末,故由周末分支覆盖,无需单列)。
|
|
186
189
|
* @param timeMs - Unix epoch 毫秒;null/undefined/NaN 视为未知。
|
|
187
190
|
*/
|
|
188
191
|
export declare function tierAt(timeMs: number | null | undefined): PriceTierId;
|
|
@@ -191,9 +194,12 @@ export declare function tierAt(timeMs: number | null | undefined): PriceTierId;
|
|
|
191
194
|
*
|
|
192
195
|
* 下一切换点统一定义为档位真正变化的最近边界:自当前时刻起逐天扫描工作日的
|
|
193
196
|
* 09:00 / 12:00 / 14:00 / 18:00,候选时刻的档位由 {@link tierAt} 判定——
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
+
* 周末(周六/周日)与法定节假日北京全天低谷、没有边界,扫描自然跳过;工作日
|
|
198
|
+
* 深夜跨周末时落到周一 09:00 而非周末伪边界(issue #33)。
|
|
199
|
+
* 最坏情形是「长假 + 相邻周末」:最长连休 8 天(2025-10-01 ~ 10-08 型)时,
|
|
200
|
+
* 假期前最后一个工作日深夜的下一切换可落在约 9.4 天之后——故扫描窗口取
|
|
201
|
+
* 10 天。原实现取 7 天(注释记「最坏 63h」),在连休下会扫不到真边界而落进
|
|
202
|
+
* 兜底伪边界。
|
|
197
203
|
* @param nowMs - 当前时刻(epoch 毫秒)。
|
|
198
204
|
* @returns 当前档位与到下一切换边界的毫秒数。
|
|
199
205
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-ui-usage-billing",
|
|
3
3
|
"description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.10",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|