@kenz1117/dsh-ui-usage-billing 1.3.0 → 1.4.0
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.en.md +46 -42
- package/README.md +49 -44
- package/lib/client.js +3 -3
- package/lib/index.js +207 -2
- package/lib/types/pricing-shared.d.ts +2 -0
- package/lib/types/subscriptions.d.ts +26 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const PLAN_KNOWLEDGE = {
|
|
|
60
60
|
"kimi-coding": { type: "code" },
|
|
61
61
|
"zai-coding-cn": { type: "code" },
|
|
62
62
|
"zai-coding": { type: "code" },
|
|
63
|
+
"commandcode": { type: "code" },
|
|
63
64
|
"qwen-token-plan": { type: "code" },
|
|
64
65
|
"qwen-token-plan-cn": { type: "code" },
|
|
65
66
|
"xiaomi-token-plan-ams": { type: "code" },
|
|
@@ -4694,6 +4695,8 @@ const EMPTY_SUBSCRIPTION_KEYS = {
|
|
|
4694
4695
|
opencodeApiKey: "",
|
|
4695
4696
|
minmaxApiKey: "",
|
|
4696
4697
|
openrouterApiKey: "",
|
|
4698
|
+
anthropicApiKey: "",
|
|
4699
|
+
commandcodeApiKey: "",
|
|
4697
4700
|
tencentCloudApi: "",
|
|
4698
4701
|
zaiRegion: "global"
|
|
4699
4702
|
};
|
|
@@ -4720,11 +4723,12 @@ const SUBSCRIPTION_DISPLAY_NAMES = {
|
|
|
4720
4723
|
"minimax-token-plan-cn": "MiniMax Token Plan(国内)",
|
|
4721
4724
|
"minimax-cn": "MiniMax Token Plan(国内)",
|
|
4722
4725
|
"openrouter": "OpenRouter",
|
|
4726
|
+
"commandcode": "CommandCode",
|
|
4723
4727
|
"tencent-token-plan": "腾讯云 Token Plan",
|
|
4724
4728
|
"grok": "Grok(X Premium)"
|
|
4725
4729
|
};
|
|
4726
4730
|
/** 订阅类 provider id 判定:带 coding / agent-plan / token-plan 后缀,或已知订阅通道。 */
|
|
4727
|
-
const SUBSCRIPTION_ID_RE = /* @__PURE__ */ new RegExp("(?:^|-)(?:coding|agent[-_]?plan|token[-_]?plan)(?:$|-|_)|^(?:opencode|opencode-go|kimi-coding|zai-coding|minimax|minimax-cn|minimax-token-plan|minimax-token-plan-cn|openrouter|grok)", "i");
|
|
4731
|
+
const SUBSCRIPTION_ID_RE = /* @__PURE__ */ new RegExp("(?:^|-)(?:coding|agent[-_]?plan|token[-_]?plan)(?:$|-|_)|^(?:opencode|opencode-go|kimi-coding|zai-coding|minimax|minimax-cn|minimax-token-plan|minimax-token-plan-cn|openrouter|grok|commandcode)", "i");
|
|
4728
4732
|
/** 是否是订阅类 provider id(如 kimi-coding、xiaomi-token-plan-cn)。 */
|
|
4729
4733
|
function isSubscriptionProviderId(providerId) {
|
|
4730
4734
|
if (SUBSCRIPTION_ID_RE.test(providerId)) return true;
|
|
@@ -4741,6 +4745,8 @@ const SUBSCRIPTION_ADAPTERS = {
|
|
|
4741
4745
|
"minimax-token-plan": { collect: collectMiniMax },
|
|
4742
4746
|
"minimax-token-plan-cn": { collect: collectMiniMax },
|
|
4743
4747
|
"openrouter": { collect: collectOpenRouter },
|
|
4748
|
+
"anthropic": { collect: collectAnthropic },
|
|
4749
|
+
"commandcode": { collect: collectCommandCode },
|
|
4744
4750
|
"tencent-token-plan": { collect: collectTencentTokenPlan }
|
|
4745
4751
|
};
|
|
4746
4752
|
/** 有额度适配器的 provider id 集合(识别用)。 */
|
|
@@ -5237,6 +5243,131 @@ async function collectOpenRouter(keys, config, timeoutMs) {
|
|
|
5237
5243
|
};
|
|
5238
5244
|
}
|
|
5239
5245
|
}
|
|
5246
|
+
/** Parse one Anthropic usage window (`utilization` is already 0–100). */
|
|
5247
|
+
function anthropicWindow(value, kind) {
|
|
5248
|
+
if (value === null || typeof value !== "object") return null;
|
|
5249
|
+
const record = value;
|
|
5250
|
+
const utilization = numberOrNull$1(record.utilization ?? record.used_percentage);
|
|
5251
|
+
if (utilization === null) return null;
|
|
5252
|
+
const usedPercent = round1$1(clampPercent$1(utilization) ?? 0);
|
|
5253
|
+
const resetsAt = toIso(record.resets_at ?? record.reset_at);
|
|
5254
|
+
return {
|
|
5255
|
+
kind,
|
|
5256
|
+
usedPercent,
|
|
5257
|
+
remainingPercent: round1$1(100 - usedPercent),
|
|
5258
|
+
...resetsAt === null ? {} : { resetsAt }
|
|
5259
|
+
};
|
|
5260
|
+
}
|
|
5261
|
+
/**
|
|
5262
|
+
* 解析 Anthropic OAuth 用量响应(GET https://api.anthropic.com/api/oauth/usage)。
|
|
5263
|
+
* 形如 `{ five_hour: { utilization, resets_at }, seven_day: {...}, seven_day_sonnet: {...} }`:
|
|
5264
|
+
* `utilization` 为 0–100 百分数,`resets_at` 为 unix 秒。子配额窗口
|
|
5265
|
+
* (`seven_day_sonnet` / `five_hour_opus` 等单模型系列限额)只描述一个模型分支,
|
|
5266
|
+
* 与主窗口量纲相同但口径更窄,整体丢弃,避免面板百分比被分支配额覆盖。
|
|
5267
|
+
* 导出供测试:纯函数。
|
|
5268
|
+
* @param body - 接口响应 JSON。
|
|
5269
|
+
* @returns 窗口列表(5 小时 → session、7 天 → weekly);无可用窗口时为 []。
|
|
5270
|
+
*/
|
|
5271
|
+
function parseAnthropicUsage(body) {
|
|
5272
|
+
const doc = body ?? {};
|
|
5273
|
+
return [anthropicWindow(doc.five_hour, "session"), anthropicWindow(doc.seven_day, "weekly")].filter((hit) => hit !== null);
|
|
5274
|
+
}
|
|
5275
|
+
/** Collect the Claude Pro/Max subscription usage via the OAuth usage endpoint. */
|
|
5276
|
+
async function collectAnthropic(keys, config, timeoutMs) {
|
|
5277
|
+
const token = keys.anthropicApiKey.trim();
|
|
5278
|
+
const base = config.baseUrl ?? "https://api.anthropic.com";
|
|
5279
|
+
const displayName = "Claude (Anthropic)";
|
|
5280
|
+
if (token === "") return {
|
|
5281
|
+
provider: config.provider,
|
|
5282
|
+
displayName,
|
|
5283
|
+
status: "not-configured",
|
|
5284
|
+
windows: [],
|
|
5285
|
+
hint: "需 Claude Code 登录态(~/.claude/.credentials.json 自动读取)或 OAuth token;普通 sk-ant- 按量 API key 查不了订阅用量"
|
|
5286
|
+
};
|
|
5287
|
+
try {
|
|
5288
|
+
const windows = parseAnthropicUsage(await requestJson(`${base}/api/oauth/usage`, { headers: {
|
|
5289
|
+
authorization: `Bearer ${token}`,
|
|
5290
|
+
accept: "application/json"
|
|
5291
|
+
} }, timeoutMs));
|
|
5292
|
+
return {
|
|
5293
|
+
provider: config.provider,
|
|
5294
|
+
displayName,
|
|
5295
|
+
status: windows.length > 0 ? "ok" : "invalid-response",
|
|
5296
|
+
windows
|
|
5297
|
+
};
|
|
5298
|
+
} catch (error) {
|
|
5299
|
+
return {
|
|
5300
|
+
provider: config.provider,
|
|
5301
|
+
displayName,
|
|
5302
|
+
status: statusOf$1(error),
|
|
5303
|
+
windows: []
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
/** Parse one CommandCode window row (`used/cap`, `resetAt` is epoch ms). */
|
|
5308
|
+
function commandcodeWindow(value, kind) {
|
|
5309
|
+
if (value === null || typeof value !== "object") return null;
|
|
5310
|
+
const record = value;
|
|
5311
|
+
const used = numberOrNull$1(record.used);
|
|
5312
|
+
const cap = numberOrNull$1(record.cap ?? record.limit ?? record.total);
|
|
5313
|
+
if (used === null || cap === null || cap <= 0 || used < 0) return null;
|
|
5314
|
+
const usedPercent = round1$1(clampPercent$1(used / cap * 100) ?? 0);
|
|
5315
|
+
const resetsAt = toIso(record.resetAt ?? record.reset_at ?? record.resetsAt);
|
|
5316
|
+
return {
|
|
5317
|
+
kind,
|
|
5318
|
+
usedPercent,
|
|
5319
|
+
remainingPercent: round1$1(100 - usedPercent),
|
|
5320
|
+
...resetsAt === null ? {} : { resetsAt }
|
|
5321
|
+
};
|
|
5322
|
+
}
|
|
5323
|
+
/**
|
|
5324
|
+
* 解析 CommandCode(commandcode.ai)额度响应
|
|
5325
|
+
* (GET https://api.commandcode.ai/alpha/billing/credits)。形如
|
|
5326
|
+
* `{ windowLimits: { fiveHour: { used, cap, resetAt }, weekly: {...} }, credits: { monthlyCredits } }`:
|
|
5327
|
+
* 窗口按 used/cap 算已用%(resetAt 为 epoch 毫秒);monthlyCredits 是月度
|
|
5328
|
+
* Credits 余额池(1 credit ≈ $1 用量),无总量字段、算不出百分比,不产出窗口。
|
|
5329
|
+
* 导出供测试:纯函数。
|
|
5330
|
+
* @param body - 接口响应 JSON。
|
|
5331
|
+
* @returns 窗口列表(5 小时 → session、周 → weekly);无可用窗口时为 []。
|
|
5332
|
+
*/
|
|
5333
|
+
function parseCommandCodeCredits(body) {
|
|
5334
|
+
const limits = (body ?? {}).windowLimits;
|
|
5335
|
+
if (limits === null || typeof limits !== "object" || Array.isArray(limits)) return [];
|
|
5336
|
+
const record = limits;
|
|
5337
|
+
return [commandcodeWindow(record.fiveHour ?? record.five_hour, "session"), commandcodeWindow(record.weekly ?? record.week, "weekly")].filter((hit) => hit !== null);
|
|
5338
|
+
}
|
|
5339
|
+
/** Collect the CommandCode quota (5h/weekly windows + monthly credits). */
|
|
5340
|
+
async function collectCommandCode(keys, config, timeoutMs) {
|
|
5341
|
+
const apiKey = keys.commandcodeApiKey.trim();
|
|
5342
|
+
const base = config.baseUrl ?? "https://api.commandcode.ai";
|
|
5343
|
+
const displayName = SUBSCRIPTION_DISPLAY_NAMES["commandcode"] ?? "CommandCode";
|
|
5344
|
+
if (apiKey === "") return {
|
|
5345
|
+
provider: config.provider,
|
|
5346
|
+
displayName,
|
|
5347
|
+
status: "not-configured",
|
|
5348
|
+
windows: [],
|
|
5349
|
+
hint: "需 commandcode.ai 的 API key(user_ 前缀);可在 llm-pi-ai 给 commandcode 路由配 apiKeyEnv"
|
|
5350
|
+
};
|
|
5351
|
+
try {
|
|
5352
|
+
const windows = parseCommandCodeCredits(await requestJson(`${base}/alpha/billing/credits`, { headers: {
|
|
5353
|
+
authorization: `Bearer ${apiKey}`,
|
|
5354
|
+
accept: "application/json"
|
|
5355
|
+
} }, timeoutMs));
|
|
5356
|
+
return {
|
|
5357
|
+
provider: config.provider,
|
|
5358
|
+
displayName,
|
|
5359
|
+
status: windows.length > 0 ? "ok" : "invalid-response",
|
|
5360
|
+
windows
|
|
5361
|
+
};
|
|
5362
|
+
} catch (error) {
|
|
5363
|
+
return {
|
|
5364
|
+
provider: config.provider,
|
|
5365
|
+
displayName,
|
|
5366
|
+
status: statusOf$1(error),
|
|
5367
|
+
windows: []
|
|
5368
|
+
};
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5240
5371
|
/**
|
|
5241
5372
|
* 腾讯云 Token Plan(TokenHub 管控面)订阅额度。凭据是云 API 密钥对
|
|
5242
5373
|
* `<SecretId>:<SecretKey>`(与余额面板同源、同格式),链路与 balance.ts 的
|
|
@@ -5722,6 +5853,9 @@ const usageBillingSettingsNs = validateSettingsNamespace(BILLING_SETTINGS_NAMESP
|
|
|
5722
5853
|
const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false) });
|
|
5723
5854
|
/** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
|
|
5724
5855
|
const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
|
|
5856
|
+
/** 历史回放预热的延迟:等宿主启动高峰(插件加载 / 路由挂载)过去再全量折叠,
|
|
5857
|
+
* 避免抢启动期的 CPU;纯延迟不阻塞任何请求,面板提前打开也只会提前聚合。 */
|
|
5858
|
+
const WARMUP_DELAY_MS = 3e3;
|
|
5725
5859
|
/** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
|
|
5726
5860
|
const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
|
|
5727
5861
|
const BALANCE_CACHE_MS = 300 * 1e3;
|
|
@@ -5874,6 +6008,10 @@ const SUBSCRIPTION_KEY_SOURCES = [
|
|
|
5874
6008
|
provider: "openrouter",
|
|
5875
6009
|
key: "openrouterApiKey"
|
|
5876
6010
|
},
|
|
6011
|
+
{
|
|
6012
|
+
provider: "commandcode",
|
|
6013
|
+
key: "commandcodeApiKey"
|
|
6014
|
+
},
|
|
5877
6015
|
{
|
|
5878
6016
|
provider: "tencent-token-plan",
|
|
5879
6017
|
key: "tencentCloudApi"
|
|
@@ -5981,6 +6119,19 @@ async function resolveSubscriptionKeys(settings, credentials) {
|
|
|
5981
6119
|
}
|
|
5982
6120
|
if (providers?.["zai-coding-cn"]?.apiKeyEnv !== void 0 && keys.zaiApiKey !== "") keys.zaiRegion = "bigmodel-cn";
|
|
5983
6121
|
if (keys.opencodeApiKey === "") keys.opencodeApiKey = await readOpenCodeToken();
|
|
6122
|
+
keys.anthropicApiKey = await readClaudeOAuthToken();
|
|
6123
|
+
if (keys.anthropicApiKey !== "") {
|
|
6124
|
+
const identified = identifySubscriptionPlans(providers);
|
|
6125
|
+
if (!identified.some((plan) => plan.provider === "anthropic")) identified.push({
|
|
6126
|
+
provider: "anthropic",
|
|
6127
|
+
displayName: "Claude (Anthropic)",
|
|
6128
|
+
adapter: true
|
|
6129
|
+
});
|
|
6130
|
+
return {
|
|
6131
|
+
keys,
|
|
6132
|
+
identified
|
|
6133
|
+
};
|
|
6134
|
+
}
|
|
5984
6135
|
return {
|
|
5985
6136
|
keys,
|
|
5986
6137
|
identified: identifySubscriptionPlans(providers)
|
|
@@ -6000,6 +6151,17 @@ async function readOpenCodeToken() {
|
|
|
6000
6151
|
return "";
|
|
6001
6152
|
}
|
|
6002
6153
|
/**
|
|
6154
|
+
* 从本机 Claude Code 登录态自动发现 Anthropic OAuth access token;取不到返回
|
|
6155
|
+
* 空串(安静退回)。与 OpenCode auth.json 兜底同款姿态:只是一次便利,绝不报错。
|
|
6156
|
+
*/
|
|
6157
|
+
async function readClaudeOAuthToken() {
|
|
6158
|
+
try {
|
|
6159
|
+
const token = JSON.parse(await readFile(join(homedir(), ".claude", ".credentials.json"), "utf8"))?.claudeAiOauth?.accessToken;
|
|
6160
|
+
if (typeof token === "string" && token !== "") return token;
|
|
6161
|
+
} catch {}
|
|
6162
|
+
return "";
|
|
6163
|
+
}
|
|
6164
|
+
/**
|
|
6003
6165
|
* 宿主 persistence 形状适配。宿主 0.1.3 起 SessionPersistence 改为
|
|
6004
6166
|
* SessionHandle 模型(open(id,'read') 后经 handle.read(offset) 读,fork 边界
|
|
6005
6167
|
* 挂在 handle.inheritedEventCount,list 返回 {header, revision} 快照行,
|
|
@@ -6080,6 +6242,16 @@ function apply(ctx, config = {}) {
|
|
|
6080
6242
|
ctx.effect(() => () => {
|
|
6081
6243
|
aggregator.flush();
|
|
6082
6244
|
}, "usage-billing: ledger flush on dispose");
|
|
6245
|
+
const warmupTimer = setTimeout(() => {
|
|
6246
|
+
aggregator.aggregate().then(() => {
|
|
6247
|
+
console.info("[usage-billing] historical replay warmed up; ledger ready");
|
|
6248
|
+
}).catch((error) => {
|
|
6249
|
+
console.warn("[usage-billing] historical replay warmup failed; will fold on first dashboard request:", error);
|
|
6250
|
+
});
|
|
6251
|
+
}, WARMUP_DELAY_MS);
|
|
6252
|
+
ctx.effect(() => () => {
|
|
6253
|
+
clearTimeout(warmupTimer);
|
|
6254
|
+
}, "usage-billing: historical replay warmup timer");
|
|
6083
6255
|
const candidates = [
|
|
6084
6256
|
config.statsPath,
|
|
6085
6257
|
process.env.DSH_USAGE_STATS,
|
|
@@ -6328,8 +6500,10 @@ function apply(ctx, config = {}) {
|
|
|
6328
6500
|
});
|
|
6329
6501
|
});
|
|
6330
6502
|
let live = { source: "builtin" };
|
|
6503
|
+
let pricingSyncedAt = 0;
|
|
6331
6504
|
const refreshPricing = async () => {
|
|
6332
6505
|
live = await fetchLivePricing();
|
|
6506
|
+
pricingSyncedAt = Date.now();
|
|
6333
6507
|
applyLivePricing(live);
|
|
6334
6508
|
};
|
|
6335
6509
|
refreshPricing();
|
|
@@ -6347,9 +6521,40 @@ function apply(ctx, config = {}) {
|
|
|
6347
6521
|
handler: async (req, res) => {
|
|
6348
6522
|
if (!guardLoopback(req, res)) return;
|
|
6349
6523
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
6350
|
-
res.end(JSON.stringify(
|
|
6524
|
+
res.end(JSON.stringify({
|
|
6525
|
+
...live,
|
|
6526
|
+
syncedAt: pricingSyncedAt
|
|
6527
|
+
}));
|
|
6351
6528
|
}
|
|
6352
6529
|
}), "usage-billing: pricing route");
|
|
6530
|
+
ctx.effect(() => ctx.webServer.register({
|
|
6531
|
+
kind: "exact",
|
|
6532
|
+
path: "/api/billing/pricing/refresh",
|
|
6533
|
+
handler: async (req, res) => {
|
|
6534
|
+
if (!guardLoopback(req, res)) return;
|
|
6535
|
+
if (req.method !== "POST") {
|
|
6536
|
+
res.writeHead(405, { "content-type": "application/json; charset=utf-8" });
|
|
6537
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
if (!isLoopbackOrigin(req.headers.origin)) {
|
|
6541
|
+
res.writeHead(403, { "content-type": "application/json; charset=utf-8" });
|
|
6542
|
+
res.end(JSON.stringify({ error: "forbidden: loopback only" }));
|
|
6543
|
+
return;
|
|
6544
|
+
}
|
|
6545
|
+
if (!(req.headers["content-type"] ?? "").toLowerCase().includes("application/json")) {
|
|
6546
|
+
res.writeHead(415, { "content-type": "application/json; charset=utf-8" });
|
|
6547
|
+
res.end(JSON.stringify({ error: "unsupported content-type" }));
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6550
|
+
await refreshPricing();
|
|
6551
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
6552
|
+
res.end(JSON.stringify({
|
|
6553
|
+
...live,
|
|
6554
|
+
syncedAt: pricingSyncedAt
|
|
6555
|
+
}));
|
|
6556
|
+
}
|
|
6557
|
+
}), "usage-billing: pricing refresh route");
|
|
6353
6558
|
let balanceCache = {
|
|
6354
6559
|
at: 0,
|
|
6355
6560
|
doc: { balances: [] }
|
|
@@ -27,6 +27,8 @@ export interface LivePricing {
|
|
|
27
27
|
* 内置目录未收录」的模型也能计价并出现在费率表。
|
|
28
28
|
*/
|
|
29
29
|
extraModels?: readonly ExtraModelPrice[];
|
|
30
|
+
/** 上次价格目录同步完成的本机时间戳(毫秒);0 = 尚未完成过任何一次同步。 */
|
|
31
|
+
syncedAt?: number;
|
|
30
32
|
}
|
|
31
33
|
/** models.dev 补充的目录外模型价(USD / 1M tokens)。 */
|
|
32
34
|
export interface ExtraModelPrice {
|
|
@@ -24,6 +24,10 @@ export interface SubscriptionKeys {
|
|
|
24
24
|
minmaxApiKey: string;
|
|
25
25
|
/** OpenRouter API key(credits 已用%)。 */
|
|
26
26
|
openrouterApiKey: string;
|
|
27
|
+
/** Anthropic Claude Pro/Max OAuth access token。 */
|
|
28
|
+
anthropicApiKey: string;
|
|
29
|
+
/** CommandCode API key(user_* 前缀)。 */
|
|
30
|
+
commandcodeApiKey: string;
|
|
27
31
|
/** 腾讯云云 API 密钥对(`<SecretId>:<SecretKey>`,管控面用,非 TokenHub 推理 key)。 */
|
|
28
32
|
tencentCloudApi: string;
|
|
29
33
|
/** Z.ai 区域(global / bigmodel-cn)。 */
|
|
@@ -67,6 +71,28 @@ export declare function parseMiniMaxRemains(body: unknown): SubscriptionWindow[]
|
|
|
67
71
|
* @returns 窗口列表;无有效额度时为 []。
|
|
68
72
|
*/
|
|
69
73
|
export declare function parseOpenRouterCredits(body: unknown): SubscriptionWindow[];
|
|
74
|
+
/**
|
|
75
|
+
* 解析 Anthropic OAuth 用量响应(GET https://api.anthropic.com/api/oauth/usage)。
|
|
76
|
+
* 形如 `{ five_hour: { utilization, resets_at }, seven_day: {...}, seven_day_sonnet: {...} }`:
|
|
77
|
+
* `utilization` 为 0–100 百分数,`resets_at` 为 unix 秒。子配额窗口
|
|
78
|
+
* (`seven_day_sonnet` / `five_hour_opus` 等单模型系列限额)只描述一个模型分支,
|
|
79
|
+
* 与主窗口量纲相同但口径更窄,整体丢弃,避免面板百分比被分支配额覆盖。
|
|
80
|
+
* 导出供测试:纯函数。
|
|
81
|
+
* @param body - 接口响应 JSON。
|
|
82
|
+
* @returns 窗口列表(5 小时 → session、7 天 → weekly);无可用窗口时为 []。
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseAnthropicUsage(body: unknown): SubscriptionWindow[];
|
|
85
|
+
/**
|
|
86
|
+
* 解析 CommandCode(commandcode.ai)额度响应
|
|
87
|
+
* (GET https://api.commandcode.ai/alpha/billing/credits)。形如
|
|
88
|
+
* `{ windowLimits: { fiveHour: { used, cap, resetAt }, weekly: {...} }, credits: { monthlyCredits } }`:
|
|
89
|
+
* 窗口按 used/cap 算已用%(resetAt 为 epoch 毫秒);monthlyCredits 是月度
|
|
90
|
+
* Credits 余额池(1 credit ≈ $1 用量),无总量字段、算不出百分比,不产出窗口。
|
|
91
|
+
* 导出供测试:纯函数。
|
|
92
|
+
* @param body - 接口响应 JSON。
|
|
93
|
+
* @returns 窗口列表(5 小时 → session、周 → weekly);无可用窗口时为 []。
|
|
94
|
+
*/
|
|
95
|
+
export declare function parseCommandCodeCredits(body: unknown): SubscriptionWindow[];
|
|
70
96
|
/**
|
|
71
97
|
* Collect quota for the given plans concurrently (adapter-backed plans only;
|
|
72
98
|
* identified plans without an adapter are surfaced by the caller as "no
|
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
|
+
"version": "1.4.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|