@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 +65 -14
- package/lib/types/subscriptions.d.ts +13 -2
- package/package.json +1 -1
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
|
|
5504
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
4
|
+
"version": "1.4.16",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|