@kenz1117/dsh-ui-usage-billing 1.4.15 → 1.4.16

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
@@ -5192,10 +5192,38 @@ const subscriptionGate = createCooldownGate({
5192
5192
  failures: 3,
5193
5193
  cooldownMs: 6e4
5194
5194
  });
5195
+ /**
5196
+ * 解析 OpenCode 客户端凭据文档(~/.local/share/opencode/auth.json)里的
5197
+ * OpenCode Go token。两种形态都收:
5198
+ * - 平铺:`{"token": "..."}` / `{"key": "..."}` / `{"apiKey": "..."}` / 裸字符串;
5199
+ * - 按 provider 分桶(实测形态):`{"opencode-go": {"type": "api", "key": "..."}}`,
5200
+ * 桶序 opencode-go → opencode → zen 即优先序。
5201
+ * 识别不出返回空串,绝不抛错——这只是一次便利回退。
5202
+ */
5203
+ function parseOpenCodeAuthDocument(document) {
5204
+ if (typeof document === "string" && document.trim() !== "") return document.trim();
5205
+ if (document === null || typeof document !== "object") return "";
5206
+ const record = document;
5207
+ const topToken = record["token"] ?? record["key"] ?? record["apiKey"];
5208
+ if (typeof topToken === "string" && topToken !== "") return topToken;
5209
+ for (const bucket of [
5210
+ "opencode-go",
5211
+ "opencode",
5212
+ "zen"
5213
+ ]) {
5214
+ const entry = record[bucket];
5215
+ if (entry === null || typeof entry !== "object") continue;
5216
+ const entryRecord = entry;
5217
+ const bucketToken = entryRecord["key"] ?? entryRecord["token"] ?? entryRecord["apiKey"];
5218
+ if (typeof bucketToken === "string" && bucketToken !== "") return bucketToken;
5219
+ }
5220
+ return "";
5221
+ }
5195
5222
  /** 空凭据:全部未配置时的初始值。 */
5196
5223
  const EMPTY_SUBSCRIPTION_KEYS = {
5197
5224
  kimiApiKey: "",
5198
5225
  zaiApiKey: "",
5226
+ zaiCnApiKey: "",
5199
5227
  opencodeApiKey: "",
5200
5228
  minmaxApiKey: "",
5201
5229
  openrouterApiKey: "",
@@ -5242,6 +5270,7 @@ function isSubscriptionProviderId(providerId) {
5242
5270
  const SUBSCRIPTION_ADAPTERS = {
5243
5271
  "kimi-coding": { collect: collectKimi },
5244
5272
  "zai-coding-cn": { collect: collectZai },
5273
+ "zai-coding": { collect: collectZai },
5245
5274
  "opencode": { collect: collectOpenCodeGo },
5246
5275
  "opencode-go": { collect: collectOpenCodeGo },
5247
5276
  "minimax": { collect: collectMiniMax },
@@ -5269,7 +5298,8 @@ function identifySubscriptionPlans(providers) {
5269
5298
  provider: id,
5270
5299
  displayName: SUBSCRIPTION_DISPLAY_NAMES[id] ?? id,
5271
5300
  adapter: ADAPTER_PROVIDER_IDS.has(id),
5272
- ...id === "zai-coding-cn" ? { region: "bigmodel-cn" } : {}
5301
+ ...id === "zai-coding-cn" ? { region: "bigmodel-cn" } : {},
5302
+ ...id === "zai-coding" ? { region: "global" } : {}
5273
5303
  });
5274
5304
  }
5275
5305
  return out;
@@ -5498,13 +5528,28 @@ function parseZai(quotaBody, subscriptionBody) {
5498
5528
  ].filter((hit) => hit !== null)
5499
5529
  };
5500
5530
  }
5531
+ /**
5532
+ * Z.ai 用 HTTP 200 包裹业务错误(实测假 key 回 `{"code":401,"msg":...,"success":false}`),
5533
+ * 只看 HTTP 层会把鉴权失败误报成 invalid-response。这里识别信封错误并归类;
5534
+ * 非 success=false 的信封(含缺字段的旧形态)交回常规解析。
5535
+ */
5536
+ function zaiEnvelopeStatus(body) {
5537
+ if (body === null || typeof body !== "object") return void 0;
5538
+ const record = body;
5539
+ if (record["success"] !== false) return void 0;
5540
+ const code = numberOrNull$1(record["code"]);
5541
+ if (code === 401 || code === 403) return "unauthorized";
5542
+ if (code === 429) return "rate-limited";
5543
+ }
5501
5544
  /** Collect the Z.ai Coding Plan quota. */
5502
5545
  async function collectZai(keys, config, timeoutMs) {
5503
- const apiKey = keys.zaiApiKey.trim();
5504
- const host = (config.region ?? keys.zaiRegion ?? "global") === "bigmodel-cn" ? "https://open.bigmodel.cn" : "https://api.z.ai";
5546
+ const region = config.region ?? keys.zaiRegion ?? "global";
5547
+ const apiKey = (region === "bigmodel-cn" ? keys.zaiCnApiKey : keys.zaiApiKey).trim();
5548
+ const host = region === "bigmodel-cn" ? "https://open.bigmodel.cn" : "https://api.z.ai";
5549
+ const displayName = region === "bigmodel-cn" ? "Z.ai Coding Plan(国内)" : "Z.ai Coding Plan";
5505
5550
  if (apiKey === "") return {
5506
5551
  provider: config.provider,
5507
- displayName: "Z.ai Coding Plan",
5552
+ displayName,
5508
5553
  status: "not-configured",
5509
5554
  windows: []
5510
5555
  };
@@ -5514,14 +5559,22 @@ async function collectZai(keys, config, timeoutMs) {
5514
5559
  accept: "application/json"
5515
5560
  } };
5516
5561
  const quota = await requestJson(`${host}/api/monitor/usage/quota/limit`, init, timeoutMs);
5562
+ const envelope = zaiEnvelopeStatus(quota);
5563
+ if (envelope !== void 0) return {
5564
+ provider: config.provider,
5565
+ displayName,
5566
+ status: envelope,
5567
+ windows: []
5568
+ };
5517
5569
  let subscription = null;
5518
5570
  try {
5519
5571
  subscription = await requestJson(`${host}/api/biz/subscription/list`, init, timeoutMs);
5572
+ if (zaiEnvelopeStatus(subscription) !== void 0) subscription = null;
5520
5573
  } catch {}
5521
5574
  const parsed = parseZai(quota, subscription);
5522
5575
  return {
5523
5576
  provider: config.provider,
5524
- displayName: "Z.ai Coding Plan",
5577
+ displayName,
5525
5578
  plan: parsed.plan,
5526
5579
  status: parsed.windows.length > 0 ? "ok" : "invalid-response",
5527
5580
  windows: parsed.windows
@@ -5529,7 +5582,7 @@ async function collectZai(keys, config, timeoutMs) {
5529
5582
  } catch (error) {
5530
5583
  return {
5531
5584
  provider: config.provider,
5532
- displayName: "Z.ai Coding Plan",
5585
+ displayName,
5533
5586
  status: statusOf$1(error),
5534
5587
  windows: []
5535
5588
  };
@@ -6516,6 +6569,10 @@ const SUBSCRIPTION_KEY_SOURCES = [
6516
6569
  },
6517
6570
  {
6518
6571
  provider: "zai-coding-cn",
6572
+ key: "zaiCnApiKey"
6573
+ },
6574
+ {
6575
+ provider: "zai-coding",
6519
6576
  key: "zaiApiKey"
6520
6577
  },
6521
6578
  {
@@ -6655,7 +6712,7 @@ async function resolveSubscriptionKeys(settings, credentials) {
6655
6712
  if (resolved?.value !== void 0 && resolved.value !== "") keys[key] = resolved.value;
6656
6713
  } catch {}
6657
6714
  }
6658
- if (providers?.["zai-coding-cn"]?.apiKeyEnv !== void 0 && keys.zaiApiKey !== "") keys.zaiRegion = "bigmodel-cn";
6715
+ if (providers?.["zai-coding-cn"]?.apiKeyEnv !== void 0 && keys.zaiCnApiKey !== "") keys.zaiRegion = "bigmodel-cn";
6659
6716
  if (keys.opencodeApiKey === "") keys.opencodeApiKey = await readOpenCodeToken();
6660
6717
  keys.anthropicApiKey = await readClaudeOAuthToken();
6661
6718
  if (keys.anthropicApiKey !== "") {
@@ -6678,13 +6735,7 @@ async function resolveSubscriptionKeys(settings, credentials) {
6678
6735
  /** 读本机 OpenCode 客户端的凭据 token;取不到返回空串(安静退回)。 */
6679
6736
  async function readOpenCodeToken() {
6680
6737
  try {
6681
- const auth = JSON.parse(await readFile(join(homedir(), ".local", "share", "opencode", "auth.json"), "utf8"));
6682
- if (typeof auth === "string" && auth !== "") return auth;
6683
- if (auth !== null && typeof auth === "object") {
6684
- const record = auth;
6685
- const token = record.token ?? record.key ?? record.apiKey;
6686
- if (typeof token === "string" && token !== "") return token;
6687
- }
6738
+ return parseOpenCodeAuthDocument(JSON.parse(await readFile(join(homedir(), ".local", "share", "opencode", "auth.json"), "utf8")));
6688
6739
  } catch {}
6689
6740
  return "";
6690
6741
  }
@@ -16,8 +16,10 @@ import type { SubscriptionPlanConfig, SubscriptionQuota, SubscriptionWindow } fr
16
16
  export interface SubscriptionKeys {
17
17
  /** Kimi For Coding API key。 */
18
18
  kimiApiKey: string;
19
- /** Z.ai API key。 */
19
+ /** Z.ai Coding Plan API key(国际域 api.z.ai)。 */
20
20
  zaiApiKey: string;
21
+ /** Z.ai(智谱国内域)Coding Plan API key——国内 / 国际是两个平台,key 不通用(不同于 MiniMax 的同 key 双域)。 */
22
+ zaiCnApiKey: string;
21
23
  /** OpenCode Go API key。 */
22
24
  opencodeApiKey: string;
23
25
  /** MiniMax Token Plan API key。 */
@@ -33,6 +35,15 @@ export interface SubscriptionKeys {
33
35
  /** Z.ai 区域(global / bigmodel-cn)。 */
34
36
  zaiRegion: 'global' | 'bigmodel-cn';
35
37
  }
38
+ /**
39
+ * 解析 OpenCode 客户端凭据文档(~/.local/share/opencode/auth.json)里的
40
+ * OpenCode Go token。两种形态都收:
41
+ * - 平铺:`{"token": "..."}` / `{"key": "..."}` / `{"apiKey": "..."}` / 裸字符串;
42
+ * - 按 provider 分桶(实测形态):`{"opencode-go": {"type": "api", "key": "..."}}`,
43
+ * 桶序 opencode-go → opencode → zen 即优先序。
44
+ * 识别不出返回空串,绝不抛错——这只是一次便利回退。
45
+ */
46
+ export declare function parseOpenCodeAuthDocument(document: unknown): string;
36
47
  /** 空凭据:全部未配置时的初始值。 */
37
48
  export declare const EMPTY_SUBSCRIPTION_KEYS: SubscriptionKeys;
38
49
  /** 已识别的一个订阅套餐(用户在 llm-pi-ai 里配置了 key 的订阅类 provider)。 */
@@ -43,7 +54,7 @@ export interface IdentifiedSubscriptionPlan {
43
54
  displayName: string;
44
55
  /** 是否有额度查询适配器。 */
45
56
  adapter: boolean;
46
- /** 适配器区域覆盖(zai-coding-cn → bigmodel-cn)。 */
57
+ /** 适配器区域覆盖:两个 Z.ai 路由各自固定区域,互不跟随 keys.zaiRegion。 */
47
58
  region?: 'global' | 'bigmodel-cn';
48
59
  }
49
60
  /** 是否是订阅类 provider id(如 kimi-coding、xiaomi-token-plan-cn)。 */
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.15",
4
+ "version": "1.4.16",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",