@ychris12138/dsh-usage-stats 0.2.9 → 0.2.10
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.md +19 -0
- package/lib/accounts.js +13 -2
- package/lib/client.js +4 -1
- package/lib/subscriptions.js +59 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,7 @@ npx --yes github:Ychris12138/dsh-usage-stats --no-enable
|
|
|
98
98
|
| Z.ai / 智谱 | 订阅 | `ZAI_API_KEY` | Coding Plan quota/subscription |
|
|
99
99
|
| Kimi For Coding | 订阅 | `KIMI_API_KEY` | `/coding/v1/usages` |
|
|
100
100
|
| MiniMax Coding Plan | 订阅 | `MINIMAX_API_KEY` | `/v1/token_plan/remains` |
|
|
101
|
+
| Ollama 云 | 订阅 | `OLLAMA_API_KEY` | `/api/usage`(5小时 + 周窗口) |
|
|
101
102
|
| New API | 余额 | provider 推理 Token | `/api/usage/token/` |
|
|
102
103
|
| Sub2API / Passion | 自动判别 | provider `apiKeyEnv` | `/v1/usage` |
|
|
103
104
|
| Sub2API 面板(真实) | 余额 | provider 推理 Token | `/user/balance`(复用 apiKey) |
|
|
@@ -139,10 +140,28 @@ KIMI_API_KEY: your-kimi-key
|
|
|
139
140
|
MINIMAX_API_KEY: your-minimax-key
|
|
140
141
|
# 中国区 MiniMax 用户可选;默认 global
|
|
141
142
|
MINIMAX_API_REGION: cn
|
|
143
|
+
OLLAMA_API_KEY: sk-ollama-your-key
|
|
142
144
|
```
|
|
143
145
|
|
|
144
146
|
OpenCode Go 依次尝试 Harness credential、`~/.local/share/opencode/auth.json`,最后才使用显式 `OPENCODE_GO_AUTH_COOKIE + OPENCODE_GO_WORKSPACE_ID` 兼容回退。Bearer usage endpoint 目前不是公开 API,可能随上游变化;Cookie 等同登录凭据,不应进入日志或 issue。
|
|
145
147
|
|
|
148
|
+
Ollama 云读取 `OLLAMA_API_KEY`,调用 `/api/usage` 展示两个订阅窗口(5 小时会话 + 每周),无余额;`usage` 按 0..1 比例换算为进度条。
|
|
149
|
+
|
|
150
|
+
Ollama 适配器只对**已配置的 provider** 生效,不会自动添加账户:当 provider 的 id 为 `ollama`,或其 baseURL 主机为 `ollama.com`(含子域)时自动选用;本地 Ollama(`localhost:11434`)不会被当作云配额账户。特殊代理/自定义端点可用显式 monitor 绑定:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# ~/.dsh/profiles/web/cordis.patch.yml
|
|
154
|
+
- insert:
|
|
155
|
+
- id: usage-stats
|
|
156
|
+
name: dsh-usage-stats
|
|
157
|
+
config:
|
|
158
|
+
monitors:
|
|
159
|
+
relay-ollama: # 你配置的 provider id
|
|
160
|
+
adapter: ollama
|
|
161
|
+
usageBaseURL: https://ollama.example.com
|
|
162
|
+
credentialRef: OLLAMA_API_KEY # 非已配置 provider 时必填
|
|
163
|
+
```
|
|
164
|
+
|
|
146
165
|
Z.ai 全球区使用 `api.z.ai`,中国区使用 `open.bigmodel.cn`。MiniMax 优先使用官方 `www.minimax.io` / `www.minimaxi.com` Token Plan 地址,并解析 5 小时与周窗口的剩余比例和重置时间。
|
|
147
166
|
|
|
148
167
|
### New API、Sub2API 与自定义 monitor
|
package/lib/accounts.js
CHANGED
|
@@ -52,6 +52,7 @@ const ADAPTERS = new Set([
|
|
|
52
52
|
"zai-token-plan",
|
|
53
53
|
"kimi-token-plan",
|
|
54
54
|
"minimax-token-plan",
|
|
55
|
+
"ollama",
|
|
55
56
|
"declarative"
|
|
56
57
|
]);
|
|
57
58
|
const SENSITIVE_HEADERS = new Set([
|
|
@@ -189,6 +190,15 @@ function defaultAdapter(provider) {
|
|
|
189
190
|
try {
|
|
190
191
|
const hostname = new URL(provider.baseURL).hostname.toLowerCase();
|
|
191
192
|
if (hostname === "passionapi.com" || hostname.endsWith(".passionapi.com")) return "sub2api";
|
|
193
|
+
// Ollama Cloud is detected by its canonical host so a user-configured
|
|
194
|
+
// provider with a custom id still gets the quota adapter; local Ollama
|
|
195
|
+
// (localhost:11434) never matches and stays without an account adapter.
|
|
196
|
+
// The id check is gated on the hostname so a local install that happens
|
|
197
|
+
// to use the canonical "ollama" id is not misread as a cloud account.
|
|
198
|
+
// Subdomains (e.g. api.ollama.com) are recognized for identification
|
|
199
|
+
// only: the usage query always targets the canonical ollama.com host,
|
|
200
|
+
// which is where the /api/usage endpoint is served.
|
|
201
|
+
if ((providerId === "ollama" || hostname === "ollama.com" || hostname.endsWith(".ollama.com")) && !privateHostname(hostname)) return "ollama";
|
|
192
202
|
} catch {
|
|
193
203
|
// A malformed provider URL is handled by the adapter when it is queried.
|
|
194
204
|
}
|
|
@@ -198,7 +208,7 @@ function defaultAdapter(provider) {
|
|
|
198
208
|
|
|
199
209
|
function adapterMode(adapter, monitor) {
|
|
200
210
|
if (adapter === "declarative") return monitor.mode;
|
|
201
|
-
if (["opencode-go", "zai-token-plan", "kimi-token-plan", "minimax-token-plan"].includes(adapter)) return "subscription";
|
|
211
|
+
if (["opencode-go", "zai-token-plan", "kimi-token-plan", "minimax-token-plan", "ollama"].includes(adapter)) return "subscription";
|
|
202
212
|
return "balance";
|
|
203
213
|
}
|
|
204
214
|
|
|
@@ -1118,7 +1128,8 @@ export async function queryAccount(spec, credentials, deps = {}) {
|
|
|
1118
1128
|
const subscriptionId = spec.adapter === "zai-token-plan" ? "zai"
|
|
1119
1129
|
: spec.adapter === "kimi-token-plan" ? "kimi"
|
|
1120
1130
|
: spec.adapter === "minimax-token-plan" ? "minimax"
|
|
1121
|
-
: "
|
|
1131
|
+
: spec.adapter === "ollama" ? "ollama"
|
|
1132
|
+
: "opencode-go";
|
|
1122
1133
|
const provider = await collectSubscription(subscriptionId, credentials, {
|
|
1123
1134
|
apiKeyRef: spec.apiKeyRef,
|
|
1124
1135
|
region: spec.monitor.region
|
package/lib/client.js
CHANGED
|
@@ -62,6 +62,7 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
".usg_accountCard{--usg-providerAccent:#1f6feb;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:linear-gradient(135deg,color-mix(in srgb,var(--usg-providerAccent) 8%,transparent),transparent 42%);border-radius:12px;padding:10px 11px;display:flex;flex-direction:column;gap:9px}",
|
|
63
63
|
".usg_accountCard[data-provider=deepseek],.usg_accountCard[data-provider=deepseek-official]{--usg-providerAccent:#1f6feb}",
|
|
64
64
|
".usg_accountCard[data-provider=opencode-go]{--usg-providerAccent:#00a67d}",
|
|
65
|
+
".usg_accountCard[data-provider=ollama],.usg_accountCard[data-adapter=ollama]{--usg-providerAccent:#d97706}",
|
|
65
66
|
".usg_accountCard[data-provider=zai],.usg_accountCard[data-provider=zai-coding-cn]{--usg-providerAccent:#7656e8}",
|
|
66
67
|
".usg_accountCard[data-provider=openrouter]{--usg-providerAccent:#6366f1}",
|
|
67
68
|
".usg_accountCard[data-provider=moonshotai],.usg_accountCard[data-provider=moonshotai-cn],.usg_accountCard[data-provider=kimi],.usg_accountCard[data-provider=kimi-coding]{--usg-providerAccent:#e07a1f}",
|
|
@@ -960,6 +961,7 @@ window.__ModuleLoader__.load({
|
|
|
960
961
|
"deepseek-official": "DS",
|
|
961
962
|
deepseek: "DS",
|
|
962
963
|
"opencode-go": "GO",
|
|
964
|
+
ollama: "OL",
|
|
963
965
|
openrouter: "OR",
|
|
964
966
|
moonshotai: "K",
|
|
965
967
|
"moonshotai-cn": "K",
|
|
@@ -968,7 +970,7 @@ window.__ModuleLoader__.load({
|
|
|
968
970
|
zai: "Z",
|
|
969
971
|
"zai-coding-cn": "Z"
|
|
970
972
|
};
|
|
971
|
-
return known[provider.id] ?? String(provider.displayName ?? provider.id).replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase();
|
|
973
|
+
return known[provider.id] ?? known[provider.adapter] ?? String(provider.displayName ?? provider.id).replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase();
|
|
972
974
|
}
|
|
973
975
|
|
|
974
976
|
/** Balance-mode body rendered inside the shared provider account frame. */
|
|
@@ -1129,6 +1131,7 @@ window.__ModuleLoader__.load({
|
|
|
1129
1131
|
return react_jsx_runtime.jsxs("article", {
|
|
1130
1132
|
className: S.accountCard,
|
|
1131
1133
|
"data-provider": provider.id,
|
|
1134
|
+
"data-adapter": provider.adapter ?? provider.accountMode ?? "",
|
|
1132
1135
|
"data-account-mode": mode,
|
|
1133
1136
|
children: [
|
|
1134
1137
|
react_jsx_runtime.jsxs("div", {
|
package/lib/subscriptions.js
CHANGED
|
@@ -38,6 +38,7 @@ const MINIMAX_LEGACY_HOSTS = {
|
|
|
38
38
|
};
|
|
39
39
|
const MINIMAX_USAGE_PATH = "/v1/api/openplatform/coding_plan/remains";
|
|
40
40
|
const MINIMAX_TOKEN_PLAN_PATH = "/v1/token_plan/remains";
|
|
41
|
+
const OLLAMA_USAGE_URL = "https://ollama.com/api/usage";
|
|
41
42
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
42
43
|
|
|
43
44
|
const REFS = {
|
|
@@ -48,7 +49,8 @@ const REFS = {
|
|
|
48
49
|
zaiRegion: "ZAI_API_REGION",
|
|
49
50
|
kimiApiKey: "KIMI_API_KEY",
|
|
50
51
|
minimaxApiKey: "MINIMAX_API_KEY",
|
|
51
|
-
minimaxRegion: "MINIMAX_API_REGION"
|
|
52
|
+
minimaxRegion: "MINIMAX_API_REGION",
|
|
53
|
+
ollamaApiKey: "OLLAMA_API_KEY"
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
function numberOrNull(value) {
|
|
@@ -576,6 +578,61 @@ async function collectMiniMax(credentials, deps) {
|
|
|
576
578
|
}
|
|
577
579
|
}
|
|
578
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Ollama Cloud usage monitor.
|
|
583
|
+
*
|
|
584
|
+
* Ollama's cloud /api/usage endpoint reports consumed usage ratios for two
|
|
585
|
+
* limit windows (a 5-hour session window and a weekly window), plus an
|
|
586
|
+
* activity cost. There is no monetary balance, so this adapter presents the
|
|
587
|
+
* two windows as subscription-style progress bars, mirroring OpenCode Go.
|
|
588
|
+
*/
|
|
589
|
+
function ollamaWindowFromObject(limit, kind) {
|
|
590
|
+
if (limit === null || typeof limit !== "object") return null;
|
|
591
|
+
// limits.session.usage / limits.weekly.usage are 0..1 consumed ratios
|
|
592
|
+
// (observed 0.0x..0.3x on live data). clampPercent maps any numeric ratio
|
|
593
|
+
// onto 0..100, so an over-quota window renders as a fully-used bar rather
|
|
594
|
+
// than silently disappearing, mirroring the OpenCode Go adapter.
|
|
595
|
+
const ratio = numberOrNull(limit.usage);
|
|
596
|
+
if (ratio === null) return null;
|
|
597
|
+
const usedPercent = round1(clampPercent(ratio * 100));
|
|
598
|
+
return {
|
|
599
|
+
kind,
|
|
600
|
+
usedPercent,
|
|
601
|
+
remainingPercent: round1(100 - usedPercent)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function parseOllama(body) {
|
|
606
|
+
const limits = body?.limits;
|
|
607
|
+
if (limits === null || typeof limits !== "object") return [];
|
|
608
|
+
return [
|
|
609
|
+
ollamaWindowFromObject(limits.session, "session"),
|
|
610
|
+
ollamaWindowFromObject(limits.weekly, "weekly")
|
|
611
|
+
].filter(Boolean);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function collectOllama(credentials, deps) {
|
|
615
|
+
const apiKeyRef = deps.apiKeyRef ?? REFS.ollamaApiKey;
|
|
616
|
+
const apiKey = await resolveCredential(credentials, apiKeyRef);
|
|
617
|
+
if (apiKey === "") return { id: "ollama", displayName: "Ollama", mode: "subscription", status: "not-configured", plan: "Ollama", missingCredentials: [apiKeyRef], windows: [] };
|
|
618
|
+
try {
|
|
619
|
+
const body = await request(nonEmptyUrl(deps.baseURL, "/api/usage") ?? OLLAMA_USAGE_URL, {
|
|
620
|
+
headers: { authorization: `Bearer ${apiKey}`, accept: "application/json" }
|
|
621
|
+
}, deps, "json");
|
|
622
|
+
const windows = parseOllama(body);
|
|
623
|
+
return {
|
|
624
|
+
id: "ollama",
|
|
625
|
+
displayName: "Ollama",
|
|
626
|
+
mode: "subscription",
|
|
627
|
+
status: windows.length > 0 ? "ok" : "invalid-response",
|
|
628
|
+
plan: "Ollama",
|
|
629
|
+
windows
|
|
630
|
+
};
|
|
631
|
+
} catch (error) {
|
|
632
|
+
return { id: "ollama", displayName: "Ollama", mode: "subscription", status: normalizedStatus(error), plan: "Ollama", windows: [] };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
579
636
|
/** Query one subscription/token-plan adapter. */
|
|
580
637
|
export async function collectSubscription(providerId, credentials, options = {}, deps = {}) {
|
|
581
638
|
const shared = {
|
|
@@ -596,6 +653,7 @@ export async function collectSubscription(providerId, credentials, options = {},
|
|
|
596
653
|
});
|
|
597
654
|
if (providerId === "kimi") return collectKimi(credentials, shared);
|
|
598
655
|
if (providerId === "minimax") return collectMiniMax(credentials, shared);
|
|
656
|
+
if (providerId === "ollama") return collectOllama(credentials, shared);
|
|
599
657
|
return { id: providerId, displayName: providerId, mode: "subscription", status: "unavailable", windows: [] };
|
|
600
658
|
}
|
|
601
659
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ychris12138/dsh-usage-stats",
|
|
3
3
|
"description": "Token usage heatmap, provider balances, and subscription quotas for the dsh web GUI",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.10",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Ychris12138/dsh-usage-stats.git"
|