@kenz1117/dsh-ui-usage-billing 1.4.0 → 1.4.1

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/lib/index.js CHANGED
@@ -6,6 +6,7 @@ import { SessionLogOffset } from "@deepseek-ai/dsh-session/types";
6
6
  import { defineTool } from "@deepseek-ai/dsh-tools";
7
7
  import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
8
8
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
9
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
9
10
  import z from "@deepseek-ai/schemastery";
10
11
  import { createHash, createHmac } from "node:crypto";
11
12
  //#region lib/types/client/plan-knowledge.js
@@ -316,7 +317,7 @@ function tierAtWithBounds(timeMs, bounds) {
316
317
  }
317
318
  /** 时刻是否落在北京时间周末(周六/周日)。 */
318
319
  function isBeijingWeekend(timeMs) {
319
- const day = new Date(timeMs + 8 * 36e5).getUTCDay();
320
+ const day = new Date(timeMs + 288e5).getUTCDay();
320
321
  return day === 0 || day === 6;
321
322
  }
322
323
  /** 峰谷切换边界(北京时间的当日分钟数):09:00 / 12:00 / 14:00 / 18:00。 */
@@ -328,7 +329,7 @@ const TIER_BOUNDARY_MINUTES = [
328
329
  ];
329
330
  /** 北京时间的当日毫秒数(0–86,400,000)。 */
330
331
  function beijingMillisOfDay(timeMs) {
331
- return ((timeMs + 8 * 36e5) % 864e5 + 864e5) % 864e5;
332
+ return ((timeMs + 288e5) % 864e5 + 864e5) % 864e5;
332
333
  }
333
334
  /**
334
335
  * Built-in catalog of current mainstream models as of 2026-08-16, priced from
@@ -445,6 +446,18 @@ const MODEL_CATALOG = [
445
446
  note: "限时 5 折"
446
447
  }
447
448
  },
449
+ {
450
+ key: "glm-5.3-flashx",
451
+ name: "GLM-5.3-FlashX",
452
+ provider: "智谱 AI",
453
+ colorVar: "dsw-static-blue-300",
454
+ price: {
455
+ currency: "CNY",
456
+ input: 2,
457
+ cacheHit: .57,
458
+ output: 7
459
+ }
460
+ },
448
461
  {
449
462
  key: "glm-4.6",
450
463
  name: "GLM-4.6",
@@ -881,6 +894,18 @@ const MODEL_CATALOG = [
881
894
  output: 4
882
895
  }
883
896
  },
897
+ {
898
+ key: "hunyuan-hy4-preview",
899
+ name: "混元 Hy4 Preview",
900
+ provider: "腾讯混元",
901
+ colorVar: "dsw-static-amber-400",
902
+ price: {
903
+ currency: "CNY",
904
+ input: 6,
905
+ cacheHit: .3,
906
+ output: 18
907
+ }
908
+ },
884
909
  {
885
910
  key: "hunyuan-t1",
886
911
  name: "混元 T1",
@@ -1479,6 +1504,8 @@ const MODEL_KEY_ALIASES = {
1479
1504
  "glm-5-turbo": "glm-5-turbo",
1480
1505
  "glm-5.1": "glm-5.1",
1481
1506
  "glm-5.3-flash": "glm-5.3-flash",
1507
+ "glm-5.3-flashx": "glm-5.3-flashx",
1508
+ "glm-5.3-flash-x": "glm-5.3-flashx",
1482
1509
  "glm-5v-turbo": "glm-5v-turbo",
1483
1510
  "glm-5v.1": "glm-5v-turbo",
1484
1511
  "claude-opus-4-6": "claude-opus-4-6",
@@ -1500,6 +1527,10 @@ const MODEL_KEY_ALIASES = {
1500
1527
  "command-r-08-2024": "command-r-08-2024",
1501
1528
  "command-r": "command-r-08-2024",
1502
1529
  "hy3": "hunyuan",
1530
+ "hy4-preview": "hunyuan-hy4-preview",
1531
+ "hy4": "hunyuan-hy4-preview",
1532
+ "hunyuan-hy4-preview": "hunyuan-hy4-preview",
1533
+ "hunyuan-hy4": "hunyuan-hy4-preview",
1503
1534
  "longcat-2.0": "longcat-2.0",
1504
1535
  "longcat-2": "longcat-2.0",
1505
1536
  "minicpm-v-4.5": "minicpm-v-4.5",
@@ -1530,6 +1561,7 @@ const MODEL_KEY_ALIASES = {
1530
1561
  "glm-5.2f": "glm-5.2-fast",
1531
1562
  "kimi-k3-fast": "kimi-k3-fast",
1532
1563
  "kimi-k3f": "kimi-k3-fast",
1564
+ "kimi-for-coding": "kimi-k2.8-preview",
1533
1565
  "kimi-k2.7-code-fast": "kimi-k2.7-code-fast",
1534
1566
  "kimi-k2.7-code-f": "kimi-k2.7-code-fast",
1535
1567
  "kimi-k2.6-fast": "kimi-k2.6-fast",
@@ -3084,8 +3116,8 @@ function createUsageAggregator(persistence, options = {}) {
3084
3116
  };
3085
3117
  const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
3086
3118
  const toModelDaySiteRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))]));
3087
- const modelKeys = new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
3088
- const hourKeys = new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
3119
+ const modelKeys = /* @__PURE__ */ new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
3120
+ const hourKeys = /* @__PURE__ */ new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
3089
3121
  const perf = modelKeys.size === 0 && hourKeys.size === 0 ? void 0 : {
3090
3122
  byModel: Object.fromEntries([...modelKeys].map((model) => {
3091
3123
  const live = perfModel.get(model);
@@ -3096,7 +3128,7 @@ function createUsageAggregator(persistence, options = {}) {
3096
3128
  byHourModel: Object.fromEntries([...hourKeys].sort().map((hour) => {
3097
3129
  const liveModels = perfHourModel.get(hour);
3098
3130
  const digestModels = perfHourDigest.get(hour);
3099
- const modelKeysInHour = new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
3131
+ const modelKeysInHour = /* @__PURE__ */ new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
3100
3132
  return [hour, Object.fromEntries([...modelKeysInHour].map((model) => {
3101
3133
  const live = liveModels?.get(model);
3102
3134
  const restored = digestModels?.get(model);
@@ -3299,7 +3331,11 @@ function tc3Authorization(secretId, secretKey, payload, timestamp) {
3299
3331
  const canonicalRequest = `POST\n/\n\n${`content-type:application/json; charset=utf-8\nhost:${TOKENHUB_HOST}\n`}\ncontent-type;host\n${createHash("sha256").update(payload).digest("hex")}`;
3300
3332
  const hashedCanonical = createHash("sha256").update(canonicalRequest).digest("hex");
3301
3333
  const stringToSign = `TC3-HMAC-SHA256\n${String(timestamp)}\n${date}/${TOKENHUB_SERVICE}/tc3_request\n${hashedCanonical}`;
3302
- return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${createHmac("sha256", createHmac("sha256", createHmac("sha256", createHmac("sha256", date).update(secretKey).digest()).update(TOKENHUB_SERVICE).digest()).update("tc3_request").digest()).update(stringToSign).digest("hex")}`;
3334
+ const kDate = createHmac("sha256", date).update(secretKey).digest();
3335
+ const kService = createHmac("sha256", kDate).update(TOKENHUB_SERVICE).digest();
3336
+ const kSigning = createHmac("sha256", kService).update("tc3_request").digest();
3337
+ const signature = createHmac("sha256", kSigning).update(stringToSign).digest("hex");
3338
+ return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${signature}`;
3303
3339
  }
3304
3340
  /**
3305
3341
  * 调用一次 TokenHub 管控面接口:TC3 签名 + 超时保护,返回响应 JSON 的 `Response`。
@@ -4063,7 +4099,7 @@ const declaredGate = createCooldownGate({
4063
4099
  cooldownMs: 6e4
4064
4100
  });
4065
4101
  /** 会触及原型链的路径段:一律拒绝(读结果不是文档自带字段)。 */
4066
- const FORBIDDEN_SEGMENTS = new Set([
4102
+ const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
4067
4103
  "__proto__",
4068
4104
  "constructor",
4069
4105
  "prototype"
@@ -4613,7 +4649,7 @@ function asPrice(value) {
4613
4649
  */
4614
4650
  function buildExtraModels(data) {
4615
4651
  if (data === null || typeof data !== "object") return [];
4616
- const catalogKeys = new Set([...MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(MODEL_KEY_ALIASES).map((key) => MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4652
+ const catalogKeys = /* @__PURE__ */ new Set([...MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(MODEL_KEY_ALIASES).map((key) => MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4617
4653
  const extras = [];
4618
4654
  for (const [providerId, providerDoc] of Object.entries(data)) {
4619
4655
  if (providerDoc === null || typeof providerDoc !== "object") continue;
@@ -5463,7 +5499,7 @@ const relayGate = createCooldownGate({
5463
5499
  /** Abort a relay fetch when the upstream hangs beyond this budget. */
5464
5500
  const FETCH_TIMEOUT_MS = 8e3;
5465
5501
  /** 指纹识别缓存 TTL(毫秒):识别结果低频变化,5 分钟内同 origin 不再重复探测。 */
5466
- const FINGERPRINT_TTL_MS = 300 * 1e3;
5502
+ const FINGERPRINT_TTL_MS = 3e5;
5467
5503
  /** 每 origin 的识别结果缓存:`kind` 是识别出的中转站程序,`at` 是探测时刻。 */
5468
5504
  const fingerprintCache = /* @__PURE__ */ new Map();
5469
5505
  /** Number, or null when the value is not a finite number (nor numeric string). */
@@ -5599,7 +5635,7 @@ function originOf(baseURL) {
5599
5635
  * 子路径只会得到 404/非中转站格式,因而面板应排除它们,避免误判为
5600
5636
  * 「未识别」。中转站面板只列真正的第三方中转程序。
5601
5637
  */
5602
- const OFFICIAL_HOSTS = new Set([
5638
+ const OFFICIAL_HOSTS = /* @__PURE__ */ new Set([
5603
5639
  "api.deepseek.com",
5604
5640
  "api.openai.com",
5605
5641
  "open.bigmodel.cn",
@@ -5852,13 +5888,13 @@ const usageBillingSettingsNs = validateSettingsNamespace(BILLING_SETTINGS_NAMESP
5852
5888
  /** 该命名空间的 wire schema:`enableUsageStatsTool` 布尔,默认关闭(issue 诉求)。 */
5853
5889
  const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false) });
5854
5890
  /** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
5855
- const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
5891
+ const PRICING_REFRESH_INTERVAL_MS = 216e5;
5856
5892
  /** 历史回放预热的延迟:等宿主启动高峰(插件加载 / 路由挂载)过去再全量折叠,
5857
5893
  * 避免抢启动期的 CPU;纯延迟不阻塞任何请求,面板提前打开也只会提前聚合。 */
5858
5894
  const WARMUP_DELAY_MS = 3e3;
5859
5895
  /** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
5860
- const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
5861
- const BALANCE_CACHE_MS = 300 * 1e3;
5896
+ const SUBSCRIPTION_CACHE_MS = 3e5;
5897
+ const BALANCE_CACHE_MS = 3e5;
5862
5898
  /** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
5863
5899
  const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
5864
5900
  /**
@@ -5875,7 +5911,7 @@ const SNAPSHOT_INTERVAL_MS = 3e5;
5875
5911
  * 轻量用户暖缓存下的聚合是毫秒级,预算几乎不会触发。 */
5876
5912
  const RESPONSE_BUDGET_MS = 1500;
5877
5913
  /** 鉴权失败告警冷却(毫秒):同一 provider 在窗口内只提示一次,避免 30 秒轮询刷屏。 */
5878
- const AUTH_WARN_COOLDOWN_MS = 1800 * 1e3;
5914
+ const AUTH_WARN_COOLDOWN_MS = 18e5;
5879
5915
  /**
5880
5916
  * 鉴权失败分类告警(P1-5):余额 / 订阅查询返回 unauthorized 时,按
5881
5917
  * `source:provider` 去重并冷却告警,提示检查 llm-pi-ai 里该 provider 的 apiKeyEnv。
@@ -6209,9 +6245,10 @@ function adaptSessionPersistence(raw) {
6209
6245
  function apply(ctx, config = {}) {
6210
6246
  let usageSettingsScope;
6211
6247
  const cwd = process.cwd();
6212
- const snapshotPath = config.snapshotPath ?? join(homedir(), ".dsh/.dsh-usage-stats.json");
6213
- const ledgerStore = createFileUsageLedgerStore(config.ledgerPath ?? join(homedir(), ".dsh/.dsh-usage-ledger.json"));
6214
- const reconcilePath = config.reconcilePath ?? join(homedir(), ".dsh/.dsh-usage-reconcile.json");
6248
+ const dshHome = resolveDshHome();
6249
+ const snapshotPath = config.snapshotPath ?? join(dshHome, ".dsh-usage-stats.json");
6250
+ const ledgerStore = createFileUsageLedgerStore(config.ledgerPath ?? join(dshHome, ".dsh-usage-ledger.json"));
6251
+ const reconcilePath = config.reconcilePath ?? join(dshHome, ".dsh-usage-reconcile.json");
6215
6252
  let reconcileRef = null;
6216
6253
  (async () => {
6217
6254
  try {
@@ -6224,7 +6261,8 @@ function apply(ctx, config = {}) {
6224
6261
  })();
6225
6262
  const persistReconcileRef = () => {
6226
6263
  if (reconcileRef === null) return;
6227
- writeFileAtomic(reconcilePath, JSON.stringify({ ref: reconcileRef }), {
6264
+ const payload = JSON.stringify({ ref: reconcileRef });
6265
+ writeFileAtomic(reconcilePath, payload, {
6228
6266
  mode: 384,
6229
6267
  dirMode: 448
6230
6268
  }).catch(() => {});
@@ -31,13 +31,14 @@ export declare function guardLoopback(req: IncomingMessage, res: ServerResponse)
31
31
  export interface UsageBillingConfig {
32
32
  /** Absolute path to a `.dsh-usage-stats.json` fallback file. */
33
33
  statsPath?: string;
34
- /** 统计快照的持久化路径;默认 `~/.dsh/.dsh-usage-stats.json`。
34
+ /** 统计快照的持久化路径;默认 `<harness home>/.dsh-usage-stats.json`
35
+ * (harness home = `DSH_HOME` 环境变量或 `~/.dsh`,见 resolveDshHome)。
35
36
  * 测试注入临时目录以隔离真实家目录(聚合失败回退与快照落盘都走此路径)。 */
36
37
  snapshotPath?: string;
37
- /** 独立持久用量账本的绝对路径;默认 `~/.dsh/.dsh-usage-ledger.json`。
38
+ /** 独立持久用量账本的绝对路径;默认 `<harness home>/.dsh-usage-ledger.json`。
38
39
  * 账本与会话日志解耦,因此永久删除会话不会抹掉已经观测到的用量。 */
39
40
  ledgerPath?: string;
40
- /** 余额差对账基准的持久化路径;默认 `~/.dsh/.dsh-usage-reconcile.json`。 */
41
+ /** 余额差对账基准的持久化路径;默认 `<harness home>/.dsh-usage-reconcile.json`。 */
41
42
  reconcilePath?: string;
42
43
  /** 订阅制(coding / token / agent plan)provider id 列表;默认 kimi-coding、xiaomi-token-plan-cn。 */
43
44
  subscriptionProviders?: string[];
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.0",
4
+ "version": "1.4.1",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",
@@ -65,7 +65,8 @@
65
65
  "0.1.5-alpha.1": "compatible",
66
66
  "0.1.5-alpha.2": "compatible",
67
67
  "0.1.5-rc.1": "compatible",
68
- "0.1.5-rc.2": "compatible"
68
+ "0.1.5-rc.2": "compatible",
69
+ "0.1.6-alpha.1": "compatible"
69
70
  }
70
71
  }
71
72
  },
@@ -93,6 +94,7 @@
93
94
  "@deepseek-ai/dsh-client-ui-slots": "*",
94
95
  "@deepseek-ai/dsh-credentials": "*",
95
96
  "@deepseek-ai/dsh-host-webserver": "*",
97
+ "@deepseek-ai/dsh-home-paths": "*",
96
98
  "@deepseek-ai/dsh-invariants": "*",
97
99
  "@deepseek-ai/dsh-llm": "*",
98
100
  "@deepseek-ai/dsh-session": "*",
@@ -122,6 +124,7 @@
122
124
  "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
123
125
  "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
124
126
  "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1",
127
+ "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
125
128
  "@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
126
129
  "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
127
130
  "@deepseek-ai/dsh-scope": "0.1.2-rc.1",