@bacnh85/pi-sub 0.1.20 → 0.1.22
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/CHANGELOG.md +35 -0
- package/README.md +13 -11
- package/extensions/index.ts +48 -0
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `pi-sub` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.22 (2026-07-30)
|
|
6
|
+
|
|
7
|
+
### Improvements
|
|
8
|
+
|
|
9
|
+
- Patch version bump for release sync and package documentation update.
|
|
10
|
+
|
|
11
|
+
## 0.1.21 (2026-07-24)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Added 9router adapter for tokens per second (tok/s) and endpoint display.
|
|
16
|
+
- Cleared stale context on session shutdown to prevent crashes on `/new`.
|
|
17
|
+
|
|
18
|
+
## 0.1.15 (2026-07-16)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Added `zai-coding-cn` (Z.ai China / open.bigmodel.cn) usage monitoring.
|
|
23
|
+
- Surfaced Z.ai MCP/month + per-model usage in `/sub` detail view.
|
|
24
|
+
|
|
25
|
+
## 0.1.10 (2026-07-10)
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- Added tok/s (tokens per second) display for response speed tracking.
|
|
30
|
+
|
|
31
|
+
## 0.1.0 (2026-07-05)
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
- Initial release of `pi-sub` subscription usage footer extension.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pi-sub
|
|
1
|
+
# @bacnh85/pi-sub
|
|
2
2
|
|
|
3
3
|
Pi extension that shows subscription usage for the currently selected supported model provider.
|
|
4
4
|
|
|
@@ -6,20 +6,10 @@ Supports OpenAI Codex (`openai-codex`) with live usage windows from ChatGPT's us
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
From npm after the package is published:
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
10
|
pi install npm:@bacnh85/pi-sub
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
From this repository checkout:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pi install ./pi-sub
|
|
19
|
-
# or test directly
|
|
20
|
-
pi -e ./pi-sub
|
|
21
|
-
```
|
|
22
|
-
|
|
23
13
|
## What it shows
|
|
24
14
|
|
|
25
15
|
### OpenAI Codex
|
|
@@ -61,6 +51,14 @@ The built-in `zai-coding-cn` provider targets the domestic BigModel endpoint (`o
|
|
|
61
51
|
(Z.ai (CN) key#1a2b3c4d) R:55%/2H W:80%/3D 42 tok/s
|
|
62
52
|
```
|
|
63
53
|
|
|
54
|
+
### 9router
|
|
55
|
+
|
|
56
|
+
9router is a proxy/router with no upstream usage API, so the footer shows the configured endpoint and last response speed:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
9router (172.30.55.22:20128) 145 tok/s
|
|
60
|
+
```
|
|
61
|
+
|
|
64
62
|
### Tokens per second
|
|
65
63
|
|
|
66
64
|
`pi-sub` tracks each response's tokens-per-second (tok/s) speed by measuring the time from provider request to message completion against the response's output token count. The last response's speed is shown in the footer next to usage data. The `/sub` detail view shows both the last response speed and the session-wide average.
|
|
@@ -137,6 +135,10 @@ Refreshes are cached briefly to avoid excessive usage endpoint calls.
|
|
|
137
135
|
- For API-key-only providers, account labels come from stored auth metadata (`email`, `label`, `name`, or `accountId`) when available; otherwise `pi-sub` displays a non-secret SHA-256 key fingerprint such as `Z.ai key#1a2b3c4d`.
|
|
138
136
|
- `pi-sub` redacts auth/token-related errors and never prints credentials.
|
|
139
137
|
|
|
138
|
+
## Changelog
|
|
139
|
+
|
|
140
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
141
|
+
|
|
140
142
|
## Design notes
|
|
141
143
|
|
|
142
144
|
The extension is named `pi-sub` rather than `pi-codex-usage` so future subscription providers can be added as separate adapters. Supports OpenAI Codex (live usage API), OpenCode Go (session cost only), and the Z.ai GLM Coding Plan (international `zai` and China `zai-coding-cn`, which share a quota response format and are served by one parameterized adapter).
|
package/extensions/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readStoredCredential, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
|
|
@@ -15,6 +16,8 @@ const ZAI_PROVIDER = "zai";
|
|
|
15
16
|
const ZAI_USAGE_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
16
17
|
const ZAI_CODING_CN_PROVIDER = "zai-coding-cn";
|
|
17
18
|
const ZAI_CODING_CN_USAGE_URL = "https://open.bigmodel.cn/api/monitor/usage/quota/limit";
|
|
19
|
+
const NINE_ROUTER_PROVIDER = "9router";
|
|
20
|
+
const NINE_ROUTER_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "9router-config.json");
|
|
18
21
|
|
|
19
22
|
type ModelLike = { provider?: string; id?: string } | undefined;
|
|
20
23
|
|
|
@@ -110,6 +113,10 @@ function isZaiCodingCnModel(model: ModelLike): boolean {
|
|
|
110
113
|
return (model?.provider?.toLowerCase() ?? "") === ZAI_CODING_CN_PROVIDER;
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
function isNineRouterModel(model: ModelLike): boolean {
|
|
117
|
+
return (model?.provider?.toLowerCase() ?? "") === NINE_ROUTER_PROVIDER;
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
function piAuthPath(): string {
|
|
114
121
|
const configDir = process.env.PI_CODING_AGENT_DIR?.trim() || path.join(os.homedir(), ".pi", "agent");
|
|
115
122
|
return path.join(configDir, "auth.json");
|
|
@@ -294,6 +301,21 @@ async function readZaiAuth(providerId: string, label: string): Promise<{ key: st
|
|
|
294
301
|
return { key: entry.key, account: authAccountSnapshot(label, entry) };
|
|
295
302
|
}
|
|
296
303
|
|
|
304
|
+
// ponytail: duplicated from pi-9router — no shared config lib, 8 lines, acceptable
|
|
305
|
+
function readNineRouterConfig(): { baseUrl: string; apiKey?: string } | null {
|
|
306
|
+
try {
|
|
307
|
+
const exists = (p: string) => { try { return fs.statSync(p).isFile(); } catch { return false; } };
|
|
308
|
+
if (!exists(NINE_ROUTER_CONFIG_PATH)) return null;
|
|
309
|
+
const data = JSON.parse(fs.readFileSync(NINE_ROUTER_CONFIG_PATH, "utf8")) as Record<string, unknown>;
|
|
310
|
+
const baseUrl = process.env.NINE_ROUTER_BASE_URL || (typeof data.baseUrl === "string" ? data.baseUrl : undefined);
|
|
311
|
+
const apiKey = process.env.NINE_ROUTER_API_KEY || (typeof data.apiKey === "string" ? data.apiKey : undefined);
|
|
312
|
+
if (!baseUrl || typeof baseUrl !== "string") return null;
|
|
313
|
+
return { baseUrl, apiKey };
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
297
319
|
async function fetchUsageFromPiAuth(entry: PiAuthEntry, signal?: AbortSignal): Promise<UsageApiSnapshot | undefined> {
|
|
298
320
|
const accountId = getCodexAccountId(entry) ?? entry.accountId;
|
|
299
321
|
if (!accountId) throw new Error("Missing openai-codex OAuth entry in Pi auth");
|
|
@@ -364,6 +386,31 @@ async function fetchOpenCodeGoUsage(_signal?: AbortSignal): Promise<Subscription
|
|
|
364
386
|
}
|
|
365
387
|
}
|
|
366
388
|
|
|
389
|
+
async function fetchNineRouterUsage(_signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
|
|
390
|
+
const cfg = readNineRouterConfig();
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
if (!cfg) {
|
|
393
|
+
return {
|
|
394
|
+
providerDisplayName: "9router",
|
|
395
|
+
accounts: [],
|
|
396
|
+
fetchedAt: now,
|
|
397
|
+
error: "9router not configured — run /login-9router",
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const account: SubscriptionAccountSnapshot = {
|
|
401
|
+
id: cfg.baseUrl,
|
|
402
|
+
isActive: true,
|
|
403
|
+
accountLabel: cfg.baseUrl.replace(/^https?:\/\//, ""),
|
|
404
|
+
lastActivity: "Now",
|
|
405
|
+
};
|
|
406
|
+
return {
|
|
407
|
+
providerDisplayName: "9router",
|
|
408
|
+
accounts: [account],
|
|
409
|
+
activeAccount: account,
|
|
410
|
+
fetchedAt: now,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
367
414
|
// ---------------------------------------------------------------------------
|
|
368
415
|
// Z.ai adapter
|
|
369
416
|
// ---------------------------------------------------------------------------
|
|
@@ -552,6 +599,7 @@ function supportedAdapter(model: ModelLike): SubscriptionProviderAdapter | undef
|
|
|
552
599
|
if (isOpenCodeGoModel(model)) return { id: OPC_PROVIDER, displayName: "OpenCode Go", fetchUsage: fetchOpenCodeGoUsage };
|
|
553
600
|
if (isZaiModel(model)) return { id: ZAI_PROVIDER, displayName: "Z.ai", ...zaiUsageAdapter(ZAI_PROVIDER, ZAI_USAGE_URL, "Z.ai") };
|
|
554
601
|
if (isZaiCodingCnModel(model)) return { id: ZAI_CODING_CN_PROVIDER, displayName: "Z.ai (CN)", ...zaiUsageAdapter(ZAI_CODING_CN_PROVIDER, ZAI_CODING_CN_USAGE_URL, "Z.ai (CN)") };
|
|
602
|
+
if (isNineRouterModel(model)) return { id: NINE_ROUTER_PROVIDER, displayName: "9router", fetchUsage: fetchNineRouterUsage };
|
|
555
603
|
return undefined;
|
|
556
604
|
}
|
|
557
605
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-sub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Pi extension showing subscription usage for supported model providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"files": [
|
|
28
28
|
"README.md",
|
|
29
|
+
"CHANGELOG.md",
|
|
29
30
|
"extensions/index.ts",
|
|
30
31
|
"extensions/package.json"
|
|
31
32
|
],
|