@bacnh85/pi-sub 0.1.31 → 0.1.32
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 +14 -0
- package/extensions/index.ts +103 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 0.1.32 (2026-08-22)
|
|
2
|
+
|
|
3
|
+
- **DeepSeek via OmniRoute now shows the real USD balance** (e.g. `M:$18.25`)
|
|
4
|
+
instead of the misleading aggregate windows (R:100%/5H W:0%/2D). Credit-based
|
|
5
|
+
upstreams report "Unavailable" session/weekly in the usage text; pi-sub now
|
|
6
|
+
recognizes that shape and fetches `credits_usd` from the management usage
|
|
7
|
+
API. Needs `ROUTER_MGMT_TOKEN` (an `oma_` CLI token or manage-scope key) in
|
|
8
|
+
env / `~/.pi/agent/.env.local`; without it, the footer degrades to the clean
|
|
9
|
+
endpoint display (no aggregate leak).
|
|
10
|
+
- The plain-retry fallback (drop `?provider=`) now fires only on "No cached
|
|
11
|
+
usage data" (wrong/unknown slug), not on "Unavailable" windows.
|
|
12
|
+
- pi-sub now reads `.env.local`/`.env` (cwd then `~/.pi/agent`) for its own
|
|
13
|
+
env vars — same discovery chain as pi-munin, stdlib parser, no deps.
|
|
14
|
+
|
|
1
15
|
## 0.1.31 (2026-08-22)
|
|
2
16
|
|
|
3
17
|
- **Fixed glm-cn quota via OmniRoute**: OmniRoute's connection slug for the
|
package/extensions/index.ts
CHANGED
|
@@ -4,6 +4,28 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
+
/** Pi config dirs + .env.local/.env discovery (pi-munin convention, stdlib parse). */
|
|
8
|
+
function loadEnvFiles(): void {
|
|
9
|
+
const dirs = process.env.PI_CODING_AGENT_DIR
|
|
10
|
+
? [process.env.PI_CODING_AGENT_DIR]
|
|
11
|
+
: [path.join(os.homedir(), ".pi", "agent"), path.join(os.homedir(), ".pi", "agents")];
|
|
12
|
+
const candidates = [path.resolve(process.cwd(), ".env.local"), path.resolve(process.cwd(), ".env")]
|
|
13
|
+
.concat(dirs.flatMap((d) => [path.join(d, ".env.local"), path.join(d, ".env")]));
|
|
14
|
+
for (const file of candidates) {
|
|
15
|
+
try {
|
|
16
|
+
const text = fs.readFileSync(file, "utf8");
|
|
17
|
+
for (const line of text.split(/\r?\n/)) {
|
|
18
|
+
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
19
|
+
if (!m) continue;
|
|
20
|
+
let v = m[2].trim();
|
|
21
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
|
22
|
+
if (process.env[m[1]] === undefined) process.env[m[1]] = v;
|
|
23
|
+
}
|
|
24
|
+
} catch { /* optional file */ }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
loadEnvFiles();
|
|
28
|
+
|
|
7
29
|
const STATUS_KEY = "pi-sub";
|
|
8
30
|
const MESSAGE_TYPE = "pi-sub-status";
|
|
9
31
|
const USAGE_ENDPOINT = "https://chatgpt.com/backend-api/wham/usage";
|
|
@@ -340,6 +362,13 @@ function routerOrigin(baseUrl: string): string {
|
|
|
340
362
|
return baseUrl.replace(/\/v1\/?$/, "");
|
|
341
363
|
}
|
|
342
364
|
|
|
365
|
+
/** OmniRoute management token (oma_ CLI token or manage-scope key) from env —
|
|
366
|
+
* unlocks /api/usage/<connectionId> which carries the raw USD balance for
|
|
367
|
+
* credit-based upstreams (deepseek) that the key-authable endpoints strip. */
|
|
368
|
+
function readRouterMgmtToken(): string | undefined {
|
|
369
|
+
return process.env.ROUTER_MGMT_TOKEN || process.env.OMNIROUTE_MGMT_TOKEN || undefined;
|
|
370
|
+
}
|
|
371
|
+
|
|
343
372
|
/** Parse OmniRoute's `/api/usage/om-usage` plain-text report into windows.
|
|
344
373
|
* Sections: "Personal quota" (per-key USD budgets: Daily/Weekly) and
|
|
345
374
|
* "Provider quota" (connection session/weekly). Lines: `<Label>`,
|
|
@@ -420,7 +449,7 @@ if (process.env.PI_SUB_SELF_CHECK === "1") {
|
|
|
420
449
|
assert(p.personalWeekly?.remaining === 90, "personal weekly 90");
|
|
421
450
|
assert(p.session?.remaining === 47, "session 47");
|
|
422
451
|
assert(p.providerWeekly?.remaining === 28, "provider weekly 28");
|
|
423
|
-
assert(p.personalDaily?.resetLabel?.includes("15h"), "daily reset label");
|
|
452
|
+
assert(p.personalDaily?.resetLabel?.includes("15h") === true, "daily reset label");
|
|
424
453
|
const disabled = parseOmniUsageText("Usage command is disabled for this API key.");
|
|
425
454
|
assert(Object.keys(disabled).length === 0, "disabled text parses empty");
|
|
426
455
|
// Live-verified: provider without cached data → no windows (endpoint fallback).
|
|
@@ -432,7 +461,7 @@ if (process.env.PI_SUB_SELF_CHECK === "1") {
|
|
|
432
461
|
);
|
|
433
462
|
assert(live.session?.remaining === 90, "session 90");
|
|
434
463
|
assert(live.providerWeekly?.remaining === 0, "weekly 0");
|
|
435
|
-
assert(live.session?.resetLabel?.includes("1h 59m"), "session reset");
|
|
464
|
+
assert(live.session?.resetLabel?.includes("1h 59m") === true, "session reset");
|
|
436
465
|
// Live-verified 2026-08-22: glm-cn scoped quota — Session 99%, Weekly
|
|
437
466
|
// "Unavailable" (skipped, so W stays absent like the direct Z.ai footer).
|
|
438
467
|
const glmCn = parseOmniUsageText(
|
|
@@ -553,9 +582,11 @@ async function fetchRouterUsage(signal?: AbortSignal, provider?: string): Promis
|
|
|
553
582
|
let text = await response.text();
|
|
554
583
|
if (text && !text.includes("disabled")) {
|
|
555
584
|
let w = parseOmniUsageText(text);
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
|
|
585
|
+
// "No cached usage data" = unknown/wrong slug — retry without
|
|
586
|
+
// ?provider= for the best/all snapshot. "Unavailable" windows mean a
|
|
587
|
+
// known credit-based upstream (deepseek) — keep them empty so the
|
|
588
|
+
// USD-balance path below takes over instead of showing the aggregate.
|
|
589
|
+
if (provider && !w.session && !w.providerWeekly && text.includes("No cached usage data")) {
|
|
559
590
|
const plain = await fetch(url.replace(/\?provider=.*$/, ""), {
|
|
560
591
|
headers: { Accept: "text/plain", Authorization: `Bearer ${apiKey}` },
|
|
561
592
|
signal: combined,
|
|
@@ -568,6 +599,23 @@ async function fetchRouterUsage(signal?: AbortSignal, provider?: string): Promis
|
|
|
568
599
|
}
|
|
569
600
|
}
|
|
570
601
|
}
|
|
602
|
+
// Credit-based upstreams (deepseek): the usage text prints "Unavailable"
|
|
603
|
+
// windows — pull the real USD balance from the management API instead.
|
|
604
|
+
const credits = await fetchRouterCredits(provider, w);
|
|
605
|
+
if (credits) {
|
|
606
|
+
const account: SubscriptionAccountSnapshot = {
|
|
607
|
+
...baseAccount,
|
|
608
|
+
plan: `Router · ${provider}`,
|
|
609
|
+
monthlyCredits: credits.balanceUsd,
|
|
610
|
+
usageBreakdown: credits.breakdown,
|
|
611
|
+
};
|
|
612
|
+
return {
|
|
613
|
+
providerDisplayName: "Router",
|
|
614
|
+
accounts: [account],
|
|
615
|
+
activeAccount: account,
|
|
616
|
+
fetchedAt: Date.now(),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
571
619
|
// personalDaily = per-key budget (nearest reset → R slot),
|
|
572
620
|
// provider weekly/session = upstream quota (W slot). Fall back sensibly.
|
|
573
621
|
const account: SubscriptionAccountSnapshot = {
|
|
@@ -612,6 +660,56 @@ async function fetchRouterUsage(signal?: AbortSignal, provider?: string): Promis
|
|
|
612
660
|
};
|
|
613
661
|
}
|
|
614
662
|
|
|
663
|
+
interface RouterCredits {
|
|
664
|
+
balanceUsd: number;
|
|
665
|
+
breakdown: string;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Fetch the raw USD balance for credit-based upstreams (deepseek: `credits_usd`)
|
|
669
|
+
* via OmniRoute's management usage API. Only called when the key-authable
|
|
670
|
+
* om-usage text reports no usable windows — the API-key surface normalizes
|
|
671
|
+
* credits to meaningless percentages, so this needs ROUTER_MGMT_TOKEN.
|
|
672
|
+
* Uses connection discovery from /api/v1/me/status (key-authable) +
|
|
673
|
+
* /api/usage/<id> (management token). */
|
|
674
|
+
async function fetchRouterCredits(provider: string | undefined, w: ReturnType<typeof parseOmniUsageText>): Promise<RouterCredits | undefined> {
|
|
675
|
+
if (!provider || w.session || w.providerWeekly) return undefined;
|
|
676
|
+
const cfg = readRouterConfig();
|
|
677
|
+
const apiKey = readRouterApiKey();
|
|
678
|
+
const mgmtToken = readRouterMgmtToken();
|
|
679
|
+
if (!cfg || !apiKey || !mgmtToken) return undefined;
|
|
680
|
+
const origin = routerOrigin(cfg.baseUrl);
|
|
681
|
+
try {
|
|
682
|
+
const combined = AbortSignal.timeout(7_000);
|
|
683
|
+
// 1. Connection id for this upstream via the key-authable status endpoint.
|
|
684
|
+
const statusRes = await fetch(`${origin}/api/v1/me/status`, {
|
|
685
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
686
|
+
signal: combined,
|
|
687
|
+
});
|
|
688
|
+
if (!statusRes.ok) return undefined;
|
|
689
|
+
const status = (await statusRes.json()) as { accountQuotas?: Array<{ provider?: string; connectionId?: string }> };
|
|
690
|
+
const connectionId = status.accountQuotas?.find((q) => q.provider === provider)?.connectionId;
|
|
691
|
+
if (!connectionId) return undefined;
|
|
692
|
+
// 2. Raw usage (management token) — quotas.credits_usd.remaining is the USD balance.
|
|
693
|
+
const usageRes = await fetch(`${origin}/api/usage/${connectionId}`, {
|
|
694
|
+
headers: { Authorization: `Bearer ${mgmtToken}` },
|
|
695
|
+
signal: combined,
|
|
696
|
+
});
|
|
697
|
+
if (!usageRes.ok) return undefined;
|
|
698
|
+
const usage = (await usageRes.json()) as { quotas?: Record<string, { remaining?: number }> };
|
|
699
|
+
const credits = usage.quotas?.credits_usd ?? usage.quotas?.credits;
|
|
700
|
+
const remaining = credits?.remaining;
|
|
701
|
+
if (typeof remaining !== "number" || !Number.isFinite(remaining)) return undefined;
|
|
702
|
+
const cny = usage.quotas?.credits_cny?.remaining;
|
|
703
|
+
return {
|
|
704
|
+
balanceUsd: remaining,
|
|
705
|
+
breakdown: `🪙 Balance (USD) $${remaining.toFixed(2)}` +
|
|
706
|
+
(typeof cny === "number" ? ` · ¥${cny.toFixed(2)} CNY` : ""),
|
|
707
|
+
};
|
|
708
|
+
} catch {
|
|
709
|
+
return undefined; // no mgmt token / upstream down — fall back to endpoint display
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
615
713
|
// Command Code's /alpha/billing/credits endpoint (auth: same Provider API key
|
|
616
714
|
// as /provider/v1 models) returns live 5-hour and weekly rolling windows plus
|
|
617
715
|
// the monthly credit balance — the same data as the cmd /usage CLI.
|