@ychris12138/dsh-usage-stats 0.2.6
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/LICENSE +21 -0
- package/README.md +362 -0
- package/SECURITY.md +17 -0
- package/cordis.patch.yml +5 -0
- package/docs/images/usage-panel.svg +176 -0
- package/lib/accounts.js +1272 -0
- package/lib/balance.js +126 -0
- package/lib/client.js +1531 -0
- package/lib/index.js +619 -0
- package/lib/subscriptions.js +610 -0
- package/lib/usage.js +276 -0
- package/package.json +72 -0
- package/scripts/install.mjs +142 -0
package/lib/balance.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-usage-stats — provider balance schemes.
|
|
3
|
+
*
|
|
4
|
+
* Pure, testable balance-query registry. Each scheme knows the endpoint path
|
|
5
|
+
* (relative to the provider's configured base URL) and how to parse the
|
|
6
|
+
* response into a normalized `{ isAvailable, currency, total, used, limit,
|
|
7
|
+
* granted, toppedUp }` view. Providers without a public balance API (OpenCode Go,
|
|
8
|
+
* Volcano Ark, OpenAI, Anthropic, …) map to no scheme — the UI shows an
|
|
9
|
+
* explicit "no public balance interface" state instead of guessing.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-usage-stats/balance
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const SCHEMES = {
|
|
15
|
+
/** DeepSeek: GET {origin}/user/balance — CNY balance_infos entry. */
|
|
16
|
+
deepseek: {
|
|
17
|
+
url: (baseURL) => new URL("/user/balance", baseURL).href,
|
|
18
|
+
parse: (json) => {
|
|
19
|
+
const infos = Array.isArray(json?.balance_infos) ? json.balance_infos : [];
|
|
20
|
+
const info = infos.find((entry) => entry?.currency === "CNY") ?? infos[0];
|
|
21
|
+
return {
|
|
22
|
+
isAvailable: json?.is_available === true,
|
|
23
|
+
currency: info?.currency ?? void 0,
|
|
24
|
+
total: info?.total_balance ?? void 0,
|
|
25
|
+
granted: info?.granted_balance ?? void 0,
|
|
26
|
+
toppedUp: info?.topped_up_balance ?? void 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
/** OpenRouter account credits; the endpoint requires a Management Key. */
|
|
31
|
+
openrouter: {
|
|
32
|
+
url: (baseURL) => new URL("/api/v1/credits", baseURL).href,
|
|
33
|
+
parse: (json) => {
|
|
34
|
+
const totalCredits = typeof json?.data?.total_credits === "number" ? json.data.total_credits : void 0;
|
|
35
|
+
const totalUsage = typeof json?.data?.total_usage === "number" ? json.data.total_usage : void 0;
|
|
36
|
+
const remaining = totalCredits !== void 0 && totalUsage !== void 0 ? totalCredits - totalUsage : void 0;
|
|
37
|
+
return {
|
|
38
|
+
isAvailable: remaining !== void 0 ? remaining > 0 : void 0,
|
|
39
|
+
currency: "USD",
|
|
40
|
+
total: remaining,
|
|
41
|
+
used: totalUsage,
|
|
42
|
+
limit: totalCredits,
|
|
43
|
+
granted: void 0,
|
|
44
|
+
toppedUp: void 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
/** Moonshot / Kimi: GET {origin}/v1/users/me/balance — available/cash/voucher. */
|
|
49
|
+
moonshot: {
|
|
50
|
+
url: (baseURL) => new URL("/v1/users/me/balance", baseURL).href,
|
|
51
|
+
parse: (json) => {
|
|
52
|
+
const data = json?.data;
|
|
53
|
+
const available = typeof data?.available_balance === "number" ? data.available_balance : void 0;
|
|
54
|
+
const cash = typeof data?.cash_balance === "number" ? data.cash_balance : void 0;
|
|
55
|
+
const voucher = typeof data?.voucher_balance === "number" ? data.voucher_balance : void 0;
|
|
56
|
+
return {
|
|
57
|
+
isAvailable: available !== void 0 ? available > 0 : void 0,
|
|
58
|
+
currency: typeof data?.currency === "string" ? data.currency : void 0,
|
|
59
|
+
total: available,
|
|
60
|
+
granted: voucher,
|
|
61
|
+
toppedUp: cash
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
/** Z.AI / GLM: GET {origin}/api/paas/v4/balance — total + available. */
|
|
66
|
+
zai: {
|
|
67
|
+
url: (baseURL) => new URL("/api/paas/v4/balance", baseURL).href,
|
|
68
|
+
parse: (json) => {
|
|
69
|
+
const data = json?.data;
|
|
70
|
+
const total = typeof data?.total_balance === "number" ? data.total_balance : typeof data?.available_balance === "number" ? data.available_balance : void 0;
|
|
71
|
+
const available = typeof data?.available_balance === "number" ? data.available_balance : void 0;
|
|
72
|
+
return {
|
|
73
|
+
isAvailable: total !== void 0 ? total > 0 : void 0,
|
|
74
|
+
currency: typeof data?.currency === "string" ? data.currency : void 0,
|
|
75
|
+
total,
|
|
76
|
+
granted: void 0,
|
|
77
|
+
toppedUp: available
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function providerError(status, message, httpStatus) {
|
|
84
|
+
const error = new Error(message);
|
|
85
|
+
error.providerStatus = status;
|
|
86
|
+
if (httpStatus !== void 0) error.httpStatus = httpStatus;
|
|
87
|
+
return error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function responseStatus(status) {
|
|
91
|
+
if (status === 401 || status === 403) return "unauthorized";
|
|
92
|
+
if (status === 429) return "rate-limited";
|
|
93
|
+
return status >= 500 ? "unavailable" : "invalid-response";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Map a provider id (dsh adapter id or pi-ai route) to a balance scheme id. */
|
|
97
|
+
export function balanceSchemeOf(providerId) {
|
|
98
|
+
if (providerId === "deepseek-official" || providerId === "deepseek") return "deepseek";
|
|
99
|
+
if (providerId === "openrouter") return "openrouter";
|
|
100
|
+
if (providerId === "moonshotai" || providerId === "moonshotai-cn" || providerId === "kimi" || providerId === "kimi-coding") return "moonshot";
|
|
101
|
+
if (providerId === "zai" || providerId === "zai-coding-cn") return "zai";
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Query one provider's balance. Throws on transport/HTTP errors. */
|
|
106
|
+
export async function queryBalance(scheme, baseURL, apiKey, timeoutMs = 15000, fetchImpl = fetch) {
|
|
107
|
+
const spec = SCHEMES[scheme];
|
|
108
|
+
if (spec === void 0) throw new Error(`no balance scheme "${scheme}"`);
|
|
109
|
+
const response = await fetchImpl(spec.url(baseURL), {
|
|
110
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
111
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok) throw providerError(responseStatus(response.status), `balance API returned HTTP ${response.status}`, response.status);
|
|
114
|
+
let body;
|
|
115
|
+
try {
|
|
116
|
+
body = await response.json();
|
|
117
|
+
} catch {
|
|
118
|
+
throw providerError("invalid-response", "balance API returned invalid JSON");
|
|
119
|
+
}
|
|
120
|
+
return spec.parse(body);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Scheme ids with built-in support (for docs/tests). */
|
|
124
|
+
export function supportedBalanceSchemes() {
|
|
125
|
+
return Object.keys(SCHEMES);
|
|
126
|
+
}
|