@kenz1117/dsh-ui-usage-billing 1.4.7 → 1.4.8

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
@@ -318,7 +318,7 @@ function tierAtWithBounds(timeMs, bounds) {
318
318
  }
319
319
  /** 时刻是否落在北京时间周末(周六/周日)。 */
320
320
  function isBeijingWeekend(timeMs) {
321
- const day = new Date(timeMs + 8 * 36e5).getUTCDay();
321
+ const day = new Date(timeMs + 288e5).getUTCDay();
322
322
  return day === 0 || day === 6;
323
323
  }
324
324
  /** 峰谷切换边界(北京时间的当日分钟数):09:00 / 12:00 / 14:00 / 18:00。 */
@@ -330,7 +330,7 @@ const TIER_BOUNDARY_MINUTES = [
330
330
  ];
331
331
  /** 北京时间的当日毫秒数(0–86,400,000)。 */
332
332
  function beijingMillisOfDay(timeMs) {
333
- return ((timeMs + 8 * 36e5) % 864e5 + 864e5) % 864e5;
333
+ return ((timeMs + 288e5) % 864e5 + 864e5) % 864e5;
334
334
  }
335
335
  /**
336
336
  * 内置目录与别名表的运行时注入态:数据本体在 src/builtin-catalog.ts(仅随 node
@@ -3275,8 +3275,8 @@ function createUsageAggregator(persistence, options = {}) {
3275
3275
  };
3276
3276
  const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
3277
3277
  const toModelDaySiteRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))]));
3278
- const modelKeys = new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
3279
- const hourKeys = new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
3278
+ const modelKeys = /* @__PURE__ */ new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
3279
+ const hourKeys = /* @__PURE__ */ new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
3280
3280
  const perf = modelKeys.size === 0 && hourKeys.size === 0 ? void 0 : {
3281
3281
  byModel: Object.fromEntries([...modelKeys].map((model) => {
3282
3282
  const live = perfModel.get(model);
@@ -3287,7 +3287,7 @@ function createUsageAggregator(persistence, options = {}) {
3287
3287
  byHourModel: Object.fromEntries([...hourKeys].sort().map((hour) => {
3288
3288
  const liveModels = perfHourModel.get(hour);
3289
3289
  const digestModels = perfHourDigest.get(hour);
3290
- const modelKeysInHour = new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
3290
+ const modelKeysInHour = /* @__PURE__ */ new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
3291
3291
  return [hour, Object.fromEntries([...modelKeysInHour].map((model) => {
3292
3292
  const live = liveModels?.get(model);
3293
3293
  const restored = digestModels?.get(model);
@@ -3491,7 +3491,11 @@ function tc3Authorization(secretId, secretKey, payload, timestamp) {
3491
3491
  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")}`;
3492
3492
  const hashedCanonical = createHash("sha256").update(canonicalRequest).digest("hex");
3493
3493
  const stringToSign = `TC3-HMAC-SHA256\n${String(timestamp)}\n${date}/${TOKENHUB_SERVICE}/tc3_request\n${hashedCanonical}`;
3494
- 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")}`;
3494
+ const kDate = createHmac("sha256", date).update(secretKey).digest();
3495
+ const kService = createHmac("sha256", kDate).update(TOKENHUB_SERVICE).digest();
3496
+ const kSigning = createHmac("sha256", kService).update("tc3_request").digest();
3497
+ const signature = createHmac("sha256", kSigning).update(stringToSign).digest("hex");
3498
+ return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${signature}`;
3495
3499
  }
3496
3500
  /**
3497
3501
  * 调用一次 TokenHub 管控面接口:TC3 签名 + 超时保护,返回响应 JSON 的 `Response`。
@@ -4255,7 +4259,7 @@ const declaredGate = createCooldownGate({
4255
4259
  cooldownMs: 6e4
4256
4260
  });
4257
4261
  /** 会触及原型链的路径段:一律拒绝(读结果不是文档自带字段)。 */
4258
- const FORBIDDEN_SEGMENTS = new Set([
4262
+ const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
4259
4263
  "__proto__",
4260
4264
  "constructor",
4261
4265
  "prototype"
@@ -4833,7 +4837,7 @@ function asPrice(value) {
4833
4837
  /** models.dev 常把同一模型收录在多个 provider 下(厂商 + 转售)。这些 id 是模型
4834
4838
  * 自身的厂商,其报价视为权威:转售可能加价,也可能省略 cache_read 而落到下方
4835
4839
  * `input * 0.1` 的猜测值。去重时优先采用。 */
4836
- const FIRST_PARTY_PRICE_SOURCES = new Set([
4840
+ const FIRST_PARTY_PRICE_SOURCES = /* @__PURE__ */ new Set([
4837
4841
  "anthropic",
4838
4842
  "openai",
4839
4843
  "google",
@@ -4862,7 +4866,7 @@ const FIRST_PARTY_PRICE_SOURCES = new Set([
4862
4866
  */
4863
4867
  function buildExtraModels(data) {
4864
4868
  if (data === null || typeof data !== "object") return [];
4865
- const catalogKeys = new Set([...BUILTIN_MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(BUILTIN_MODEL_KEY_ALIASES).map((key) => BUILTIN_MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4869
+ const catalogKeys = /* @__PURE__ */ new Set([...BUILTIN_MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(BUILTIN_MODEL_KEY_ALIASES).map((key) => BUILTIN_MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4866
4870
  const offers = /* @__PURE__ */ new Map();
4867
4871
  const familyVendorVotes = /* @__PURE__ */ new Map();
4868
4872
  for (const [providerId, providerDoc] of Object.entries(data)) {
@@ -5785,7 +5789,7 @@ const relayGate = createCooldownGate({
5785
5789
  /** Abort a relay fetch when the upstream hangs beyond this budget. */
5786
5790
  const FETCH_TIMEOUT_MS = 8e3;
5787
5791
  /** 指纹识别缓存 TTL(毫秒):识别结果低频变化,5 分钟内同 origin 不再重复探测。 */
5788
- const FINGERPRINT_TTL_MS = 300 * 1e3;
5792
+ const FINGERPRINT_TTL_MS = 3e5;
5789
5793
  /** 每 origin 的识别结果缓存:`kind` 是识别出的中转站程序,`at` 是探测时刻。 */
5790
5794
  const fingerprintCache = /* @__PURE__ */ new Map();
5791
5795
  /** Number, or null when the value is not a finite number (nor numeric string). */
@@ -5921,7 +5925,7 @@ function originOf(baseURL) {
5921
5925
  * 子路径只会得到 404/非中转站格式,因而面板应排除它们,避免误判为
5922
5926
  * 「未识别」。中转站面板只列真正的第三方中转程序。
5923
5927
  */
5924
- const OFFICIAL_HOSTS = new Set([
5928
+ const OFFICIAL_HOSTS = /* @__PURE__ */ new Set([
5925
5929
  "api.deepseek.com",
5926
5930
  "api.openai.com",
5927
5931
  "open.bigmodel.cn",
@@ -6191,13 +6195,13 @@ const usageBillingSettingsNs = validateSettingsNamespace(BILLING_SETTINGS_NAMESP
6191
6195
  /** 该命名空间的 wire schema:`enableUsageStatsTool` 布尔,默认关闭(issue 诉求)。 */
6192
6196
  const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false) });
6193
6197
  /** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
6194
- const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
6198
+ const PRICING_REFRESH_INTERVAL_MS = 216e5;
6195
6199
  /** 历史回放预热的延迟:等宿主启动高峰(插件加载 / 路由挂载)过去再全量折叠,
6196
6200
  * 避免抢启动期的 CPU;纯延迟不阻塞任何请求,面板提前打开也只会提前聚合。 */
6197
6201
  const WARMUP_DELAY_MS = 3e3;
6198
6202
  /** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
6199
- const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
6200
- const BALANCE_CACHE_MS = 300 * 1e3;
6203
+ const SUBSCRIPTION_CACHE_MS = 3e5;
6204
+ const BALANCE_CACHE_MS = 3e5;
6201
6205
  /** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
6202
6206
  const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
6203
6207
  /**
@@ -6214,7 +6218,7 @@ const SNAPSHOT_INTERVAL_MS = 3e5;
6214
6218
  * 轻量用户暖缓存下的聚合是毫秒级,预算几乎不会触发。 */
6215
6219
  const RESPONSE_BUDGET_MS = 1500;
6216
6220
  /** 鉴权失败告警冷却(毫秒):同一 provider 在窗口内只提示一次,避免 30 秒轮询刷屏。 */
6217
- const AUTH_WARN_COOLDOWN_MS = 1800 * 1e3;
6221
+ const AUTH_WARN_COOLDOWN_MS = 18e5;
6218
6222
  /**
6219
6223
  * 鉴权失败分类告警(P1-5):余额 / 订阅查询返回 unauthorized 时,按
6220
6224
  * `source:provider` 去重并冷却告警,提示检查 llm-pi-ai 里该 provider 的 apiKeyEnv。
@@ -6565,7 +6569,8 @@ function apply(ctx, config = {}) {
6565
6569
  })();
6566
6570
  const persistReconcileRef = () => {
6567
6571
  if (reconcileRef === null) return;
6568
- writeFileAtomic(reconcilePath, JSON.stringify({ ref: reconcileRef }), {
6572
+ const payload = JSON.stringify({ ref: reconcileRef });
6573
+ writeFileAtomic(reconcilePath, payload, {
6569
6574
  mode: 384,
6570
6575
  dirMode: 448
6571
6576
  }).catch(() => {});
@@ -6692,7 +6697,9 @@ function apply(ctx, config = {}) {
6692
6697
  }
6693
6698
  }), "usage-billing: notify-claim route");
6694
6699
  ctx.inject(["settings"], (sctx) => {
6695
- const scope = sctx.settings.register(usageBillingSettingsNs, UsageBillingSettingsSchema, { base: { enableUsageStatsTool: config.enableUsageStatsTool ?? false } });
6700
+ const legacy = sctx.settings;
6701
+ if (typeof legacy.register !== "function") return;
6702
+ const scope = legacy.register(usageBillingSettingsNs, UsageBillingSettingsSchema, { base: { enableUsageStatsTool: config.enableUsageStatsTool ?? false } });
6696
6703
  usageSettingsScope = scope;
6697
6704
  ctx.inject(["tools"], (toolsCtx) => {
6698
6705
  if (scope.get().enableUsageStatsTool) toolsCtx.tools.register(defineTool({
@@ -15,10 +15,18 @@
15
15
  import type { IncomingMessage, ServerResponse } from 'node:http';
16
16
  import type { Context } from '@deepseek-ai/cordis';
17
17
  import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
18
- import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
19
18
  import { type UsageLedgerStore, type UsagePersistence } from './aggregate.ts';
20
19
  import type { CustomBalanceConfig, DeclaredEndpointConfig, SubscriptionPlanConfig } from './pricing-shared.ts';
21
20
  import { type IdentifiedSubscriptionPlan, type SubscriptionKeys } from './subscriptions.ts';
21
+ /** 设置服务的读取面:两代宿主运行时都提供 describe(条目键为 profile entry id)。 */
22
+ interface SettingsReader {
23
+ describe(options?: {
24
+ redactSecrets?: boolean;
25
+ }): readonly {
26
+ ns: string;
27
+ value: unknown;
28
+ }[];
29
+ }
22
30
  /** 校验 Host 头是本机回环(精确 127.0.0.0/8 / ::1 / localhost 或空,供 curl 不带 Host 的极简请求)。
23
31
  * 拒绝 `127.0.0.1.attacker.com` 这类以 `127.` 开头但解析到外部的 DNS rebinding 域名:
24
32
  * 只用 `startsWith('127.')` 会被它穿透,必须精确匹配回环 IP 的字面量。 */
@@ -127,7 +135,7 @@ export interface PiAiProviderRoute {
127
135
  * @param settings - the settings service (reads the llm-pi-ai namespace).
128
136
  * @returns `<route> → { baseURL? }`;命名空间不可读时返回空。
129
137
  */
130
- export declare function readPiAiProviderRoutes(settings: SettingsProvider): Readonly<Record<string, {
138
+ export declare function readPiAiProviderRoutes(settings: SettingsReader): Readonly<Record<string, {
131
139
  baseURL?: string;
132
140
  }>>;
133
141
  /**
@@ -137,7 +145,7 @@ export declare function readPiAiProviderRoutes(settings: SettingsProvider): Read
137
145
  * @param settings - the settings service (reads the llm-pi-ai namespace).
138
146
  * @param credentials - the credentials service (resolves the env refs).
139
147
  */
140
- export declare function resolveSubscriptionKeys(settings: SettingsProvider, credentials: CredentialProvider): Promise<{
148
+ export declare function resolveSubscriptionKeys(settings: SettingsReader, credentials: CredentialProvider): Promise<{
141
149
  keys: SubscriptionKeys;
142
150
  identified: IdentifiedSubscriptionPlan[];
143
151
  }>;
@@ -159,4 +167,5 @@ export declare function adaptSessionPersistence(raw: unknown): UsagePersistence;
159
167
  * @param config - optional statsPath override.
160
168
  */
161
169
  export declare function apply(ctx: Context, config?: UsageBillingConfig): void;
170
+ export {};
162
171
  //# sourceMappingURL=index.d.ts.map
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.7",
4
+ "version": "1.4.8",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",
@@ -66,8 +66,12 @@
66
66
  "0.1.5-alpha.2": "compatible",
67
67
  "0.1.5-rc.1": "compatible",
68
68
  "0.1.5-rc.2": "compatible",
69
+ "0.1.5-rc.3": "compatible",
69
70
  "0.1.6-alpha.1": "compatible",
70
- "0.1.6-alpha.2": "compatible"
71
+ "0.1.6-alpha.2": "compatible",
72
+ "0.1.7-alpha.1": "compatible",
73
+ "0.1.7-alpha.2": "compatible",
74
+ "0.1.7-rc.1": "compatible"
71
75
  }
72
76
  }
73
77
  },
@@ -112,43 +116,69 @@
112
116
  "@deepseek-ai/cordis": "*",
113
117
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
114
118
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
115
- "@deepseek-ai/dsh-api-gateway": "0.1.2-rc.1",
116
- "@deepseek-ai/dsh-api-remotes": "0.1.2-rc.1",
117
- "@deepseek-ai/dsh-api-session-controller": "0.1.2-rc.1",
118
- "@deepseek-ai/dsh-atomic-write": "0.1.2-rc.1",
119
- "@deepseek-ai/dsh-client-locale": "0.1.2-rc.1",
120
- "@deepseek-ai/dsh-client-store": "0.1.2-rc.1",
121
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
122
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
123
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.2-rc.1",
124
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-rc.1",
125
- "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
126
- "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
127
- "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1",
128
- "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
129
- "@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
130
- "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
131
- "@deepseek-ai/dsh-scope": "0.1.2-rc.1",
132
- "@deepseek-ai/dsh-session": "0.1.2-rc.1",
133
- "@deepseek-ai/dsh-session-persistence": "0.1.2-rc.1",
134
- "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
135
- "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
136
- "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1",
119
+ "@deepseek-ai/dsh-api-gateway": "0.1.7-rc.1",
120
+ "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.1",
121
+ "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.1",
122
+ "@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
123
+ "@deepseek-ai/dsh-agent": "0.1.7-rc.1",
124
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
125
+ "@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
126
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
127
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
128
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
129
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.1",
130
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
131
+ "@deepseek-ai/dsh-credentials": "0.1.7-rc.1",
132
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
133
+ "@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
134
+ "@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
135
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
136
+ "@deepseek-ai/dsh-ptc-runtime": "0.1.7-rc.1",
137
+ "@deepseek-ai/dsh-sandbox": "0.1.7-rc.1",
138
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.1",
139
+ "@deepseek-ai/dsh-scope": "0.1.7-rc.1",
140
+ "@deepseek-ai/dsh-session": "0.1.7-rc.1",
141
+ "@deepseek-ai/dsh-session-persistence": "0.1.7-rc.1",
142
+ "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
143
+ "@deepseek-ai/dsh-system-prompt": "0.1.7-rc.1",
144
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.1",
145
+ "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
146
+ "@deepseek-ai/dsh-user-approval": "0.1.7-rc.1",
147
+ "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1",
137
148
  "@deepseek-ai/schemastery": "^3.18.2",
149
+ "@shikijs/langs": "^4.3.1",
138
150
  "@testing-library/dom": "^10.4.1",
139
151
  "@testing-library/react": "^16.1.0",
140
152
  "@types/node": "^22.20.0",
141
153
  "@types/react": "~18.3.1",
142
154
  "@types/react-dom": "^19.2.7",
155
+ "immer": "^10.1.1",
156
+ "anser": "^2.3.5",
157
+ "diff": "^9.0.0",
143
158
  "jsdom": "^26.1.0",
159
+ "katex": "^0.16.47",
144
160
  "lightningcss": "^1.32.0",
161
+ "mdast-util-from-markdown": "^2.0.3",
162
+ "mdast-util-gfm": "^3.1.0",
163
+ "mdast-util-math": "^3.0.0",
164
+ "micromark-core-commonmark": "^2.0.3",
165
+ "micromark-extension-gfm": "^3.0.0",
166
+ "micromark-extension-math": "^3.1.0",
167
+ "micromark-factory-space": "^2.0.1",
168
+ "micromark-util-character": "^2.1.1",
169
+ "micromark-util-classify-character": "^2.0.1",
170
+ "micromark-util-sanitize-uri": "^2.0.1",
171
+ "micromark-util-symbol": "^2.0.1",
145
172
  "react": "^18.2.0",
146
173
  "react-dom": "^18.2.0",
147
174
  "semver": "^7.7.2",
175
+ "shiki": "^4.3.1",
176
+ "simple-icons": "^16.32.0",
148
177
  "tsdown": "^0.22.2",
149
178
  "typescript": "^6.0.3",
150
179
  "use-sync-external-store": "1.2.0",
151
- "vitest": "^4.1.8"
180
+ "vitest": "^4.1.8",
181
+ "zustand": "~4.4.7"
152
182
  },
153
183
  "files": [
154
184
  "lib/index.js",