@hk_net/pi-usage-bars 0.3.0 → 0.4.1
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 +41 -5
- package/docs/financial-metrics-plan.md +75 -0
- package/docs/releasing.md +84 -0
- package/extensions/usage-bars/core.ts +628 -13
- package/extensions/usage-bars/index.ts +170 -22
- package/package.json +12 -7
|
@@ -2,18 +2,63 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
|
|
5
|
-
export type ProviderKey =
|
|
6
|
-
|
|
5
|
+
export type ProviderKey =
|
|
6
|
+
| "codex"
|
|
7
|
+
| "claude"
|
|
8
|
+
| "zai"
|
|
9
|
+
| "zai-cn"
|
|
10
|
+
| "kimi"
|
|
11
|
+
| "minimax"
|
|
12
|
+
| "minimax-cn"
|
|
13
|
+
| "openrouter"
|
|
14
|
+
| "deepseek"
|
|
15
|
+
| "moonshot"
|
|
16
|
+
| "moonshot-cn";
|
|
17
|
+
export type PiProviderId =
|
|
18
|
+
| "openai-codex"
|
|
19
|
+
| "anthropic"
|
|
20
|
+
| "zai"
|
|
21
|
+
| "zai-coding-cn"
|
|
22
|
+
| "kimi-coding"
|
|
23
|
+
| "minimax"
|
|
24
|
+
| "minimax-cn"
|
|
25
|
+
| "openrouter"
|
|
26
|
+
| "deepseek"
|
|
27
|
+
| "moonshotai"
|
|
28
|
+
| "moonshotai-cn";
|
|
29
|
+
|
|
30
|
+
export interface AccountBalance {
|
|
31
|
+
amount: number;
|
|
32
|
+
unit: string;
|
|
33
|
+
label: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AccountSpend {
|
|
37
|
+
unit: string;
|
|
38
|
+
daily?: number;
|
|
39
|
+
weekly?: number;
|
|
40
|
+
monthly?: number;
|
|
41
|
+
lifetime?: number;
|
|
42
|
+
}
|
|
7
43
|
|
|
8
44
|
export interface UsageData {
|
|
9
45
|
session: number;
|
|
10
46
|
weekly: number;
|
|
47
|
+
quotaHidden?: boolean;
|
|
48
|
+
accountBalance?: AccountBalance;
|
|
49
|
+
accountBalanceDetails?: AccountBalance[];
|
|
50
|
+
accountSpend?: AccountSpend;
|
|
11
51
|
sessionResetsIn?: string;
|
|
12
52
|
weeklyResetsIn?: string;
|
|
13
53
|
sessionResetsAt?: string;
|
|
14
54
|
weeklyResetsAt?: string;
|
|
15
55
|
extraSpend?: number;
|
|
16
56
|
extraLimit?: number;
|
|
57
|
+
sessionLabel?: string;
|
|
58
|
+
weeklyLabel?: string;
|
|
59
|
+
sessionHidden?: boolean;
|
|
60
|
+
weeklyHidden?: boolean;
|
|
61
|
+
notice?: string;
|
|
17
62
|
warning?: string;
|
|
18
63
|
stale?: boolean;
|
|
19
64
|
fetchedAt?: number;
|
|
@@ -26,6 +71,16 @@ export type UsageTokens = Partial<Record<ProviderKey, string>>;
|
|
|
26
71
|
export interface UsageEndpoints {
|
|
27
72
|
zai: string;
|
|
28
73
|
zaiCn: string;
|
|
74
|
+
kimi: string;
|
|
75
|
+
minimax: string;
|
|
76
|
+
minimaxLegacy: string;
|
|
77
|
+
minimaxCn: string;
|
|
78
|
+
minimaxCnLegacy: string;
|
|
79
|
+
openRouterCredits: string;
|
|
80
|
+
openRouterKey: string;
|
|
81
|
+
deepSeekBalance: string;
|
|
82
|
+
moonshotBalance: string;
|
|
83
|
+
moonshotCnBalance: string;
|
|
29
84
|
}
|
|
30
85
|
|
|
31
86
|
export interface HeadersLike {
|
|
@@ -108,6 +163,16 @@ const CLAUDE_LOCK_STALE_MS = 20_000;
|
|
|
108
163
|
export const DEFAULT_USAGE_CACHE_FILE = path.join(os.tmpdir(), "pi", "usage-bars-cache.json");
|
|
109
164
|
export const DEFAULT_ZAI_USAGE_ENDPOINT = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
110
165
|
export const DEFAULT_ZAI_CN_USAGE_ENDPOINT = "https://open.bigmodel.cn/api/monitor/usage/quota/limit";
|
|
166
|
+
export const DEFAULT_KIMI_USAGE_ENDPOINT = "https://api.kimi.com/coding/v1/usages";
|
|
167
|
+
export const DEFAULT_MINIMAX_USAGE_ENDPOINT = "https://api.minimax.io/v1/token_plan/remains";
|
|
168
|
+
export const DEFAULT_MINIMAX_LEGACY_USAGE_ENDPOINT = "https://api.minimax.io/v1/api/openplatform/coding_plan/remains";
|
|
169
|
+
export const DEFAULT_MINIMAX_CN_USAGE_ENDPOINT = "https://api.minimaxi.com/v1/token_plan/remains";
|
|
170
|
+
export const DEFAULT_MINIMAX_CN_LEGACY_USAGE_ENDPOINT = "https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains";
|
|
171
|
+
export const DEFAULT_OPENROUTER_CREDITS_ENDPOINT = "https://openrouter.ai/api/v1/credits";
|
|
172
|
+
export const DEFAULT_OPENROUTER_KEY_ENDPOINT = "https://openrouter.ai/api/v1/key";
|
|
173
|
+
export const DEFAULT_DEEPSEEK_BALANCE_ENDPOINT = "https://api.deepseek.com/user/balance";
|
|
174
|
+
export const DEFAULT_MOONSHOT_BALANCE_ENDPOINT = "https://api.moonshot.ai/v1/users/me/balance";
|
|
175
|
+
export const DEFAULT_MOONSHOT_CN_BALANCE_ENDPOINT = "https://api.moonshot.cn/v1/users/me/balance";
|
|
111
176
|
|
|
112
177
|
export function resolveUsageEndpoints(env: NodeJS.ProcessEnv = process.env): UsageEndpoints {
|
|
113
178
|
const configured = (value: string | undefined, fallback: string) => {
|
|
@@ -118,6 +183,16 @@ export function resolveUsageEndpoints(env: NodeJS.ProcessEnv = process.env): Usa
|
|
|
118
183
|
return {
|
|
119
184
|
zai: configured(env.PI_ZAI_USAGE_ENDPOINT, DEFAULT_ZAI_USAGE_ENDPOINT),
|
|
120
185
|
zaiCn: configured(env.PI_ZAI_CODING_CN_USAGE_ENDPOINT, DEFAULT_ZAI_CN_USAGE_ENDPOINT),
|
|
186
|
+
kimi: configured(env.PI_KIMI_USAGE_ENDPOINT, DEFAULT_KIMI_USAGE_ENDPOINT),
|
|
187
|
+
minimax: configured(env.PI_MINIMAX_USAGE_ENDPOINT, DEFAULT_MINIMAX_USAGE_ENDPOINT),
|
|
188
|
+
minimaxLegacy: configured(env.PI_MINIMAX_LEGACY_USAGE_ENDPOINT, DEFAULT_MINIMAX_LEGACY_USAGE_ENDPOINT),
|
|
189
|
+
minimaxCn: configured(env.PI_MINIMAX_CN_USAGE_ENDPOINT, DEFAULT_MINIMAX_CN_USAGE_ENDPOINT),
|
|
190
|
+
minimaxCnLegacy: configured(env.PI_MINIMAX_CN_LEGACY_USAGE_ENDPOINT, DEFAULT_MINIMAX_CN_LEGACY_USAGE_ENDPOINT),
|
|
191
|
+
openRouterCredits: configured(env.PI_OPENROUTER_CREDITS_ENDPOINT, DEFAULT_OPENROUTER_CREDITS_ENDPOINT),
|
|
192
|
+
openRouterKey: configured(env.PI_OPENROUTER_KEY_ENDPOINT, DEFAULT_OPENROUTER_KEY_ENDPOINT),
|
|
193
|
+
deepSeekBalance: configured(env.PI_DEEPSEEK_BALANCE_ENDPOINT, DEFAULT_DEEPSEEK_BALANCE_ENDPOINT),
|
|
194
|
+
moonshotBalance: configured(env.PI_MOONSHOT_BALANCE_ENDPOINT, DEFAULT_MOONSHOT_BALANCE_ENDPOINT),
|
|
195
|
+
moonshotCnBalance: configured(env.PI_MOONSHOT_CN_BALANCE_ENDPOINT, DEFAULT_MOONSHOT_CN_BALANCE_ENDPOINT),
|
|
121
196
|
};
|
|
122
197
|
}
|
|
123
198
|
|
|
@@ -384,12 +459,21 @@ function snapshotUsage(usage: UsageData, nowMs = Date.now()): UsageData {
|
|
|
384
459
|
return {
|
|
385
460
|
session: usage.session,
|
|
386
461
|
weekly: usage.weekly,
|
|
462
|
+
quotaHidden: usage.quotaHidden,
|
|
463
|
+
accountBalance: usage.accountBalance,
|
|
464
|
+
accountBalanceDetails: usage.accountBalanceDetails,
|
|
465
|
+
accountSpend: usage.accountSpend,
|
|
387
466
|
sessionResetsAt: usage.sessionResetsAt,
|
|
388
467
|
weeklyResetsAt: usage.weeklyResetsAt,
|
|
389
468
|
sessionResetsIn: usage.sessionResetsIn,
|
|
390
469
|
weeklyResetsIn: usage.weeklyResetsIn,
|
|
391
470
|
extraSpend: usage.extraSpend,
|
|
392
471
|
extraLimit: usage.extraLimit,
|
|
472
|
+
sessionLabel: usage.sessionLabel,
|
|
473
|
+
weeklyLabel: usage.weeklyLabel,
|
|
474
|
+
sessionHidden: usage.sessionHidden,
|
|
475
|
+
weeklyHidden: usage.weeklyHidden,
|
|
476
|
+
notice: usage.notice,
|
|
393
477
|
fetchedAt: usage.fetchedAt ?? nowMs,
|
|
394
478
|
};
|
|
395
479
|
}
|
|
@@ -438,6 +522,41 @@ function readClaudeCacheOutcome(cacheFile = DEFAULT_USAGE_CACHE_FILE, nowMs = Da
|
|
|
438
522
|
return null;
|
|
439
523
|
}
|
|
440
524
|
|
|
525
|
+
export function parseCodexRateLimit(data: any): UsageData {
|
|
526
|
+
const rateLimit = data?.rate_limit ?? data?.rate_limits;
|
|
527
|
+
const primary = rateLimit?.primary_window ?? rateLimit?.primary ?? rateLimit?.five_hour;
|
|
528
|
+
const secondary = rateLimit?.secondary_window ?? rateLimit?.secondary ?? rateLimit?.weekly;
|
|
529
|
+
|
|
530
|
+
let sessionWindow: any = null;
|
|
531
|
+
let weeklyWindow: any = null;
|
|
532
|
+
for (const [position, window] of [["primary", primary], ["secondary", secondary]] as const) {
|
|
533
|
+
if (!window || typeof window !== "object") continue;
|
|
534
|
+
const duration = window.limit_window_seconds;
|
|
535
|
+
if (typeof duration === "number" && Number.isFinite(duration)) {
|
|
536
|
+
// Some Codex accounts return their seven-day quota as primary_window
|
|
537
|
+
// and omit secondary_window, so position alone does not identify it.
|
|
538
|
+
if (duration >= 2 * 24 * 60 * 60) weeklyWindow ??= window;
|
|
539
|
+
else sessionWindow ??= window;
|
|
540
|
+
} else if (position === "primary") {
|
|
541
|
+
sessionWindow ??= window;
|
|
542
|
+
} else {
|
|
543
|
+
weeklyWindow ??= window;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const reset = (window: any) =>
|
|
548
|
+
typeof window?.reset_after_seconds === "number" ? formatDuration(window.reset_after_seconds) : undefined;
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
session: readPercentCandidate(sessionWindow?.used_percent) ?? 0,
|
|
552
|
+
weekly: readPercentCandidate(weeklyWindow?.used_percent) ?? 0,
|
|
553
|
+
...(!sessionWindow ? { sessionHidden: true } : {}),
|
|
554
|
+
...(!weeklyWindow ? { weeklyHidden: true } : {}),
|
|
555
|
+
sessionResetsIn: reset(sessionWindow),
|
|
556
|
+
weeklyResetsIn: reset(weeklyWindow),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
441
560
|
export async function fetchCodexUsage(token: string, config: RequestConfig = {}): Promise<UsageData> {
|
|
442
561
|
const result = await requestJson(
|
|
443
562
|
"https://chatgpt.com/backend-api/wham/usage",
|
|
@@ -445,16 +564,7 @@ export async function fetchCodexUsage(token: string, config: RequestConfig = {})
|
|
|
445
564
|
config,
|
|
446
565
|
);
|
|
447
566
|
if (!result.ok) return { session: 0, weekly: 0, error: result.error };
|
|
448
|
-
|
|
449
|
-
const data = result.data as any;
|
|
450
|
-
const primary = data?.rate_limit?.primary_window;
|
|
451
|
-
const secondary = data?.rate_limit?.secondary_window;
|
|
452
|
-
return {
|
|
453
|
-
session: readPercentCandidate(primary?.used_percent) ?? 0,
|
|
454
|
-
weekly: readPercentCandidate(secondary?.used_percent) ?? 0,
|
|
455
|
-
sessionResetsIn: typeof primary?.reset_after_seconds === "number" ? formatDuration(primary.reset_after_seconds) : undefined,
|
|
456
|
-
weeklyResetsIn: typeof secondary?.reset_after_seconds === "number" ? formatDuration(secondary.reset_after_seconds) : undefined,
|
|
457
|
-
};
|
|
567
|
+
return parseCodexRateLimit(result.data);
|
|
458
568
|
}
|
|
459
569
|
|
|
460
570
|
async function fetchClaudeUsageAttempt(
|
|
@@ -545,6 +655,463 @@ export async function fetchClaudeUsageWithFallback(
|
|
|
545
655
|
}
|
|
546
656
|
}
|
|
547
657
|
|
|
658
|
+
function readNumber(value: unknown): number | null {
|
|
659
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
660
|
+
if (typeof value === "string" && value.trim()) {
|
|
661
|
+
const parsed = Number(value);
|
|
662
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function usedPercentFromCounts(
|
|
668
|
+
value: Record<string, unknown> | null | undefined,
|
|
669
|
+
options: { remainingPercent?: string; used?: string; total?: string; remaining?: string } = {},
|
|
670
|
+
): number | null {
|
|
671
|
+
if (!value) return null;
|
|
672
|
+
const remainingPercent = readNumber(value[options.remainingPercent ?? "remaining_percent"]);
|
|
673
|
+
if (remainingPercent !== null) return Math.max(0, Math.min(100, 100 - remainingPercent));
|
|
674
|
+
|
|
675
|
+
const total = readNumber(value[options.total ?? "limit"]);
|
|
676
|
+
const used = readNumber(value[options.used ?? "used"]);
|
|
677
|
+
const remaining = readNumber(value[options.remaining ?? "remaining"]);
|
|
678
|
+
if (total === null || total <= 0) return null;
|
|
679
|
+
if (used !== null) return Math.max(0, Math.min(100, used / total * 100));
|
|
680
|
+
if (remaining !== null) return Math.max(0, Math.min(100, (total - remaining) / total * 100));
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function normalizeIsoDate(value: unknown): string | undefined {
|
|
685
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
686
|
+
const normalized = value.trim().replace(/(\.\d{3})\d+(?=Z|[+-]\d\d:\d\d$)/, "$1");
|
|
687
|
+
return Number.isFinite(new Date(normalized).getTime()) ? normalized : undefined;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function isoFromEpoch(value: unknown): string | undefined {
|
|
691
|
+
const raw = readNumber(value);
|
|
692
|
+
if (raw === null || raw <= 0) return undefined;
|
|
693
|
+
const milliseconds = raw > 1_000_000_000_000 ? raw : raw * 1000;
|
|
694
|
+
const date = new Date(milliseconds);
|
|
695
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function resetFromRemains(value: unknown, nowMs: number): string | undefined {
|
|
699
|
+
const raw = readNumber(value);
|
|
700
|
+
if (raw === null || raw <= 0) return undefined;
|
|
701
|
+
const milliseconds = raw > 1_000_000 ? raw : raw * 1000;
|
|
702
|
+
return new Date(nowMs + milliseconds).toISOString();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function extractKimiUsageFromPayload(payload: unknown, nowMs = Date.now()): UsageData | null {
|
|
706
|
+
const root = asObject(payload);
|
|
707
|
+
if (!root) return null;
|
|
708
|
+
const webUsages = Array.isArray(root.usages) ? root.usages : undefined;
|
|
709
|
+
const codingUsage = webUsages?.map(asObject).find((entry) =>
|
|
710
|
+
String(entry?.scope ?? "").toUpperCase() === "FEATURE_CODING") ?? root;
|
|
711
|
+
const dataRows = Array.isArray(codingUsage.data) ? codingUsage.data.map(asObject).filter(Boolean) : [];
|
|
712
|
+
const usage = asObject(codingUsage.usage) ?? asObject(codingUsage.detail) ??
|
|
713
|
+
dataRows.find((entry) => String(entry?.model_name ?? entry?.modelName ?? "").toLowerCase() === "all");
|
|
714
|
+
const limits = Array.isArray(codingUsage.limits)
|
|
715
|
+
? codingUsage.limits
|
|
716
|
+
: dataRows.filter((entry) => entry !== usage);
|
|
717
|
+
const sessionLimit = limits.map(asObject).find((entry) => {
|
|
718
|
+
const window = asObject(entry?.window);
|
|
719
|
+
const duration = readNumber(window?.duration);
|
|
720
|
+
const unit = String(window?.timeUnit ?? window?.time_unit ?? "").toUpperCase();
|
|
721
|
+
return duration === 300 && unit.includes("MINUTE");
|
|
722
|
+
}) ?? limits.map(asObject).find((entry) => entry !== null);
|
|
723
|
+
const sessionDetail = asObject(sessionLimit?.detail) ?? sessionLimit;
|
|
724
|
+
|
|
725
|
+
const session = usedPercentFromCounts(sessionDetail);
|
|
726
|
+
const weekly = usedPercentFromCounts(usage);
|
|
727
|
+
if (session === null || weekly === null) return null;
|
|
728
|
+
|
|
729
|
+
const sessionReset = normalizeIsoDate(sessionDetail?.resetTime ?? sessionDetail?.reset_at ?? sessionDetail?.reset_time);
|
|
730
|
+
const weeklyReset = normalizeIsoDate(usage?.resetTime ?? usage?.reset_at ?? usage?.reset_time);
|
|
731
|
+
return hydrateUsageResets({
|
|
732
|
+
...normalizeUsagePair(session, weekly),
|
|
733
|
+
sessionLabel: "5-hour",
|
|
734
|
+
weeklyLabel: "Weekly",
|
|
735
|
+
sessionResetsAt: sessionReset,
|
|
736
|
+
weeklyResetsAt: weeklyReset,
|
|
737
|
+
}, nowMs);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export async function fetchKimiUsage(token: string, config: FetchConfig = {}): Promise<UsageData> {
|
|
741
|
+
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
742
|
+
const result = await requestJson(endpoints.kimi, {
|
|
743
|
+
headers: {
|
|
744
|
+
Authorization: `Bearer ${token}`,
|
|
745
|
+
"User-Agent": "KimiCLI/1.5",
|
|
746
|
+
},
|
|
747
|
+
}, config);
|
|
748
|
+
if (!result.ok) return { session: 0, weekly: 0, error: result.error };
|
|
749
|
+
return extractKimiUsageFromPayload(result.data) ?? {
|
|
750
|
+
session: 0,
|
|
751
|
+
weekly: 0,
|
|
752
|
+
error: "unrecognized response shape",
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
interface MiniMaxWindow {
|
|
757
|
+
percent: number;
|
|
758
|
+
resetsAt?: string;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function pickHighestWindow(windows: MiniMaxWindow[]): MiniMaxWindow | undefined {
|
|
762
|
+
return windows.reduce<MiniMaxWindow | undefined>((highest, window) =>
|
|
763
|
+
!highest || window.percent > highest.percent ? window : highest, undefined);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function miniMaxResetAt(value: Record<string, unknown>, prefix: "current" | "weekly", nowMs: number): string | undefined {
|
|
767
|
+
const end = prefix === "current"
|
|
768
|
+
? value.end_time ?? value.endTime
|
|
769
|
+
: value.weekly_end_time ?? value.weeklyEndTime;
|
|
770
|
+
const remains = prefix === "current"
|
|
771
|
+
? value.remains_time ?? value.remainsTime
|
|
772
|
+
: value.weekly_remains_time ?? value.weeklyRemainsTime;
|
|
773
|
+
const resetsAt = prefix === "current"
|
|
774
|
+
? value.current_resets_at ?? value.currentResetsAt
|
|
775
|
+
: value.weekly_resets_at ?? value.weeklyResetsAt;
|
|
776
|
+
return normalizeIsoDate(resetsAt) ?? isoFromEpoch(end) ?? resetFromRemains(remains, nowMs);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function extractMiniMaxCreditBalance(payload: unknown): AccountBalance | undefined {
|
|
780
|
+
const root = asObject(payload);
|
|
781
|
+
const data = asObject(root?.data) ?? root;
|
|
782
|
+
if (!data) return undefined;
|
|
783
|
+
const amount = readNumber(
|
|
784
|
+
data.points_balance ?? data.pointsBalance ??
|
|
785
|
+
data.point_balance ?? data.pointBalance ??
|
|
786
|
+
data.credits_balance ?? data.creditsBalance ??
|
|
787
|
+
data.credit_balance ?? data.creditBalance,
|
|
788
|
+
);
|
|
789
|
+
return amount === null ? undefined : { amount, unit: "credits", label: "Credit balance" };
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export function extractMiniMaxUsageFromPayload(payload: unknown, nowMs = Date.now()): UsageData | null {
|
|
793
|
+
const root = asObject(payload);
|
|
794
|
+
const data = asObject(root?.data) ?? root;
|
|
795
|
+
if (!data) return null;
|
|
796
|
+
const accountBalance = extractMiniMaxCreditBalance(payload);
|
|
797
|
+
|
|
798
|
+
const intervalWindows: MiniMaxWindow[] = [];
|
|
799
|
+
const weeklyWindows: MiniMaxWindow[] = [];
|
|
800
|
+
if (Array.isArray(data.services)) {
|
|
801
|
+
for (const rawService of data.services) {
|
|
802
|
+
const service = asObject(rawService);
|
|
803
|
+
if (!service) continue;
|
|
804
|
+
const directPercent = readPercentCandidate(readNumber(service.percent));
|
|
805
|
+
const percent = directPercent ?? usedPercentFromCounts(service, { total: "limit", used: "usage" });
|
|
806
|
+
if (percent === null) continue;
|
|
807
|
+
const windowType = String(service.window_type ?? service.windowType ?? "").toLowerCase();
|
|
808
|
+
const resetsAt = normalizeIsoDate(service.resets_at ?? service.reset_time ?? service.end_time);
|
|
809
|
+
(windowType.includes("week") ? weeklyWindows : intervalWindows).push({ percent, resetsAt });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (Array.isArray(data.model_remains ?? data.modelRemains)) {
|
|
814
|
+
for (const rawModel of (data.model_remains ?? data.modelRemains) as unknown[]) {
|
|
815
|
+
const raw = asObject(rawModel);
|
|
816
|
+
if (!raw) continue;
|
|
817
|
+
const model: Record<string, unknown> = {
|
|
818
|
+
...raw,
|
|
819
|
+
current_interval_remaining_percent:
|
|
820
|
+
raw.current_interval_remaining_percent ?? raw.currentIntervalRemainingPercent,
|
|
821
|
+
current_interval_total_count: raw.current_interval_total_count ?? raw.currentIntervalTotalCount,
|
|
822
|
+
current_interval_usage_count: raw.current_interval_usage_count ?? raw.currentIntervalUsageCount,
|
|
823
|
+
current_interval_status: raw.current_interval_status ?? raw.currentIntervalStatus,
|
|
824
|
+
current_weekly_remaining_percent:
|
|
825
|
+
raw.current_weekly_remaining_percent ?? raw.currentWeeklyRemainingPercent,
|
|
826
|
+
current_weekly_total_count: raw.current_weekly_total_count ?? raw.currentWeeklyTotalCount,
|
|
827
|
+
current_weekly_usage_count: raw.current_weekly_usage_count ?? raw.currentWeeklyUsageCount,
|
|
828
|
+
current_weekly_status: raw.current_weekly_status ?? raw.currentWeeklyStatus,
|
|
829
|
+
};
|
|
830
|
+
const unavailable = (prefix: "interval" | "weekly") =>
|
|
831
|
+
readNumber(model[`current_${prefix}_status`]) === 3 &&
|
|
832
|
+
(readNumber(model[`current_${prefix}_remaining_percent`]) ?? 0) >= 100 &&
|
|
833
|
+
(readNumber(model[`current_${prefix}_total_count`]) ?? 0) === 0 &&
|
|
834
|
+
(readNumber(model[`current_${prefix}_usage_count`]) ?? 0) === 0;
|
|
835
|
+
const interval = unavailable("interval") ? null : usedPercentFromCounts(model, {
|
|
836
|
+
remainingPercent: "current_interval_remaining_percent",
|
|
837
|
+
total: "current_interval_total_count",
|
|
838
|
+
remaining: "current_interval_usage_count",
|
|
839
|
+
});
|
|
840
|
+
if (interval !== null) {
|
|
841
|
+
intervalWindows.push({ percent: interval, resetsAt: miniMaxResetAt(model, "current", nowMs) });
|
|
842
|
+
}
|
|
843
|
+
const weekly = unavailable("weekly") ? null : usedPercentFromCounts(model, {
|
|
844
|
+
remainingPercent: "current_weekly_remaining_percent",
|
|
845
|
+
total: "current_weekly_total_count",
|
|
846
|
+
remaining: "current_weekly_usage_count",
|
|
847
|
+
});
|
|
848
|
+
if (weekly !== null) {
|
|
849
|
+
weeklyWindows.push({ percent: weekly, resetsAt: miniMaxResetAt(model, "weekly", nowMs) });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const session = pickHighestWindow(intervalWindows);
|
|
855
|
+
const weekly = pickHighestWindow(weeklyWindows);
|
|
856
|
+
if (!session) {
|
|
857
|
+
return accountBalance
|
|
858
|
+
? { session: 0, weekly: 0, quotaHidden: true, accountBalance }
|
|
859
|
+
: null;
|
|
860
|
+
}
|
|
861
|
+
return hydrateUsageResets({
|
|
862
|
+
session: Number(session.percent.toFixed(2)),
|
|
863
|
+
accountBalance,
|
|
864
|
+
weekly: Number((weekly?.percent ?? 0).toFixed(2)),
|
|
865
|
+
sessionLabel: "Interval",
|
|
866
|
+
weeklyLabel: "Weekly",
|
|
867
|
+
weeklyHidden: !weekly,
|
|
868
|
+
sessionResetsAt: session.resetsAt,
|
|
869
|
+
weeklyResetsAt: weekly?.resetsAt,
|
|
870
|
+
}, nowMs);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function miniMaxPayloadStatus(payload: unknown): number | null {
|
|
874
|
+
const root = asObject(payload);
|
|
875
|
+
const data = asObject(root?.data);
|
|
876
|
+
const baseResponse = asObject(data?.base_resp ?? data?.baseResp ?? root?.base_resp ?? root?.baseResp);
|
|
877
|
+
return readNumber(baseResponse?.status_code ?? baseResponse?.statusCode);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function miniMaxPayloadError(payload: unknown): string | null {
|
|
881
|
+
const root = asObject(payload);
|
|
882
|
+
const data = asObject(root?.data);
|
|
883
|
+
const baseResponse = asObject(data?.base_resp ?? data?.baseResp ?? root?.base_resp ?? root?.baseResp);
|
|
884
|
+
const status = miniMaxPayloadStatus(payload);
|
|
885
|
+
if (status === null || status === 0) return null;
|
|
886
|
+
const message = baseResponse?.status_msg ?? baseResponse?.statusMessage;
|
|
887
|
+
return typeof message === "string" && message.trim()
|
|
888
|
+
? `API ${status}: ${message.trim()}`
|
|
889
|
+
: `API ${status}`;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export async function fetchMiniMaxUsage(
|
|
893
|
+
token: string,
|
|
894
|
+
provider: "minimax" | "minimax-cn" = "minimax",
|
|
895
|
+
config: FetchConfig = {},
|
|
896
|
+
): Promise<UsageData> {
|
|
897
|
+
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
898
|
+
const candidates = provider === "minimax-cn"
|
|
899
|
+
? [endpoints.minimaxCn, endpoints.minimaxCnLegacy]
|
|
900
|
+
: [endpoints.minimax, endpoints.minimaxLegacy];
|
|
901
|
+
let lastError = "usage request failed";
|
|
902
|
+
let credentialError: string | undefined;
|
|
903
|
+
let noActiveTokenPlan = false;
|
|
904
|
+
|
|
905
|
+
for (const endpoint of [...new Set(candidates)]) {
|
|
906
|
+
const result = await requestJson(endpoint, {
|
|
907
|
+
headers: {
|
|
908
|
+
Authorization: `Bearer ${token}`,
|
|
909
|
+
Accept: "application/json",
|
|
910
|
+
},
|
|
911
|
+
}, config);
|
|
912
|
+
if (!result.ok) {
|
|
913
|
+
lastError = result.error;
|
|
914
|
+
if (result.status === 401 || result.status === 403) credentialError ??= result.error;
|
|
915
|
+
if (config.signal?.aborted) break;
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
const payloadStatus = miniMaxPayloadStatus(result.data);
|
|
919
|
+
const payloadError = miniMaxPayloadError(result.data);
|
|
920
|
+
const usage = extractMiniMaxUsageFromPayload(result.data);
|
|
921
|
+
if (usage && (!payloadError || usage.quotaHidden)) return usage;
|
|
922
|
+
if (payloadStatus === 2062) {
|
|
923
|
+
noActiveTokenPlan = true;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (payloadError) {
|
|
927
|
+
lastError = payloadError;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
lastError = "unrecognized response shape";
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (noActiveTokenPlan) {
|
|
934
|
+
return {
|
|
935
|
+
session: 0,
|
|
936
|
+
weekly: 0,
|
|
937
|
+
quotaHidden: true,
|
|
938
|
+
notice: "No active Token Plan · check Credit balance in the MiniMax console",
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
return { session: 0, weekly: 0, error: credentialError ?? lastError };
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export function extractOpenRouterUsageFromPayloads(
|
|
945
|
+
creditsPayload: unknown,
|
|
946
|
+
keyPayload: unknown,
|
|
947
|
+
): UsageData | null {
|
|
948
|
+
const credits = asObject(asObject(creditsPayload)?.data) ?? asObject(creditsPayload);
|
|
949
|
+
const key = asObject(asObject(keyPayload)?.data) ?? asObject(keyPayload);
|
|
950
|
+
|
|
951
|
+
const totalCredits = readNumber(credits?.total_credits ?? credits?.totalCredits);
|
|
952
|
+
const totalUsage = readNumber(credits?.total_usage ?? credits?.totalUsage);
|
|
953
|
+
const accountBalance = totalCredits !== null && totalUsage !== null
|
|
954
|
+
? {
|
|
955
|
+
amount: Number((totalCredits - totalUsage).toFixed(6)),
|
|
956
|
+
unit: "USD",
|
|
957
|
+
label: "Balance",
|
|
958
|
+
}
|
|
959
|
+
: undefined;
|
|
960
|
+
|
|
961
|
+
const spendValues = {
|
|
962
|
+
daily: readNumber(key?.usage_daily ?? key?.usageDaily),
|
|
963
|
+
weekly: readNumber(key?.usage_weekly ?? key?.usageWeekly),
|
|
964
|
+
monthly: readNumber(key?.usage_monthly ?? key?.usageMonthly),
|
|
965
|
+
lifetime: readNumber(key?.usage),
|
|
966
|
+
};
|
|
967
|
+
const accountSpend = Object.values(spendValues).some((value) => value !== null)
|
|
968
|
+
? {
|
|
969
|
+
unit: "USD",
|
|
970
|
+
daily: spendValues.daily ?? undefined,
|
|
971
|
+
weekly: spendValues.weekly ?? undefined,
|
|
972
|
+
monthly: spendValues.monthly ?? undefined,
|
|
973
|
+
lifetime: spendValues.lifetime ?? undefined,
|
|
974
|
+
}
|
|
975
|
+
: undefined;
|
|
976
|
+
|
|
977
|
+
const limit = readNumber(key?.limit);
|
|
978
|
+
const remaining = readNumber(key?.limit_remaining ?? key?.limitRemaining);
|
|
979
|
+
const limitUsed = limit !== null && limit > 0 && remaining !== null
|
|
980
|
+
? Math.max(0, Math.min(limit, limit - remaining))
|
|
981
|
+
: null;
|
|
982
|
+
const limitPercent = limitUsed !== null && limit !== null ? limitUsed / limit * 100 : null;
|
|
983
|
+
if (!accountBalance && !accountSpend && limitPercent === null) return null;
|
|
984
|
+
|
|
985
|
+
return {
|
|
986
|
+
session: limitPercent === null ? 0 : Number(limitPercent.toFixed(2)),
|
|
987
|
+
weekly: 0,
|
|
988
|
+
quotaHidden: limitPercent === null,
|
|
989
|
+
weeklyHidden: true,
|
|
990
|
+
sessionLabel: "Key limit",
|
|
991
|
+
accountBalance,
|
|
992
|
+
accountSpend,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export async function fetchOpenRouterUsage(token: string, config: FetchConfig = {}): Promise<UsageData> {
|
|
997
|
+
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
998
|
+
const headers = { Authorization: `Bearer ${token}`, Accept: "application/json" };
|
|
999
|
+
const [creditsResult, keyResult] = await Promise.all([
|
|
1000
|
+
requestJson(endpoints.openRouterCredits, { headers }, config),
|
|
1001
|
+
requestJson(endpoints.openRouterKey, { headers }, config),
|
|
1002
|
+
]);
|
|
1003
|
+
const usage = extractOpenRouterUsageFromPayloads(
|
|
1004
|
+
creditsResult.ok ? creditsResult.data : undefined,
|
|
1005
|
+
keyResult.ok ? keyResult.data : undefined,
|
|
1006
|
+
);
|
|
1007
|
+
if (usage) return usage;
|
|
1008
|
+
|
|
1009
|
+
const errors = [
|
|
1010
|
+
creditsResult.ok ? undefined : `credits: ${creditsResult.error}`,
|
|
1011
|
+
keyResult.ok ? undefined : `key: ${keyResult.error}`,
|
|
1012
|
+
].filter((value): value is string => Boolean(value));
|
|
1013
|
+
return {
|
|
1014
|
+
session: 0,
|
|
1015
|
+
weekly: 0,
|
|
1016
|
+
error: errors.length > 0 ? errors.join("; ") : "unrecognized response shape",
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
export function extractDeepSeekBalanceFromPayload(payload: unknown): UsageData | null {
|
|
1021
|
+
const root = asObject(payload);
|
|
1022
|
+
const rawBalances = Array.isArray(root?.balance_infos) ? root.balance_infos : [];
|
|
1023
|
+
const balances = rawBalances.map(asObject).filter((value): value is Record<string, unknown> => value !== null);
|
|
1024
|
+
if (balances.length === 0) return null;
|
|
1025
|
+
|
|
1026
|
+
const parsed = balances.flatMap((balance) => {
|
|
1027
|
+
const unit = typeof balance.currency === "string" ? balance.currency.toUpperCase() : "USD";
|
|
1028
|
+
const total = readNumber(balance.total_balance ?? balance.totalBalance);
|
|
1029
|
+
if (total === null) return [];
|
|
1030
|
+
return [{
|
|
1031
|
+
total: { amount: total, unit, label: "Total balance" } satisfies AccountBalance,
|
|
1032
|
+
toppedUp: readNumber(balance.topped_up_balance ?? balance.toppedUpBalance),
|
|
1033
|
+
granted: readNumber(balance.granted_balance ?? balance.grantedBalance),
|
|
1034
|
+
}];
|
|
1035
|
+
});
|
|
1036
|
+
const primary = parsed[0];
|
|
1037
|
+
if (!primary) return null;
|
|
1038
|
+
|
|
1039
|
+
const details: AccountBalance[] = [];
|
|
1040
|
+
if (primary.toppedUp !== null) {
|
|
1041
|
+
details.push({ amount: primary.toppedUp, unit: primary.total.unit, label: "Topped up" });
|
|
1042
|
+
}
|
|
1043
|
+
if (primary.granted !== null) {
|
|
1044
|
+
details.push({ amount: primary.granted, unit: primary.total.unit, label: "Granted" });
|
|
1045
|
+
}
|
|
1046
|
+
for (const additional of parsed.slice(1)) details.push(additional.total);
|
|
1047
|
+
|
|
1048
|
+
return {
|
|
1049
|
+
session: 0,
|
|
1050
|
+
weekly: 0,
|
|
1051
|
+
quotaHidden: true,
|
|
1052
|
+
accountBalance: primary.total,
|
|
1053
|
+
accountBalanceDetails: details,
|
|
1054
|
+
warning: root?.is_available === false ? "Balance is not currently available for API use" : undefined,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export async function fetchDeepSeekBalance(token: string, config: FetchConfig = {}): Promise<UsageData> {
|
|
1059
|
+
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
1060
|
+
const result = await requestJson(endpoints.deepSeekBalance, {
|
|
1061
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
1062
|
+
}, config);
|
|
1063
|
+
if (!result.ok) return { session: 0, weekly: 0, error: result.error };
|
|
1064
|
+
return extractDeepSeekBalanceFromPayload(result.data) ?? {
|
|
1065
|
+
session: 0,
|
|
1066
|
+
weekly: 0,
|
|
1067
|
+
error: "unrecognized response shape",
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
export function extractMoonshotBalanceFromPayload(
|
|
1072
|
+
payload: unknown,
|
|
1073
|
+
provider: "moonshot" | "moonshot-cn" = "moonshot",
|
|
1074
|
+
): UsageData | null {
|
|
1075
|
+
const root = asObject(payload);
|
|
1076
|
+
const data = asObject(root?.data) ?? root;
|
|
1077
|
+
if (!data) return null;
|
|
1078
|
+
const available = readNumber(data.available_balance ?? data.availableBalance);
|
|
1079
|
+
if (available === null) return null;
|
|
1080
|
+
const cash = readNumber(data.cash_balance ?? data.cashBalance);
|
|
1081
|
+
const voucher = readNumber(data.voucher_balance ?? data.voucherBalance);
|
|
1082
|
+
const unit = provider === "moonshot-cn" ? "CNY" : "USD";
|
|
1083
|
+
const details: AccountBalance[] = [];
|
|
1084
|
+
if (cash !== null) details.push({ amount: cash, unit, label: "Cash" });
|
|
1085
|
+
if (voucher !== null) details.push({ amount: voucher, unit, label: "Voucher" });
|
|
1086
|
+
|
|
1087
|
+
return {
|
|
1088
|
+
session: 0,
|
|
1089
|
+
weekly: 0,
|
|
1090
|
+
quotaHidden: true,
|
|
1091
|
+
accountBalance: { amount: available, unit, label: "Available balance" },
|
|
1092
|
+
accountBalanceDetails: details,
|
|
1093
|
+
warning: available <= 0 ? "Balance exhausted; inference requests may be rejected" : undefined,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export async function fetchMoonshotBalance(
|
|
1098
|
+
token: string,
|
|
1099
|
+
provider: "moonshot" | "moonshot-cn" = "moonshot",
|
|
1100
|
+
config: FetchConfig = {},
|
|
1101
|
+
): Promise<UsageData> {
|
|
1102
|
+
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
1103
|
+
const endpoint = provider === "moonshot-cn" ? endpoints.moonshotCnBalance : endpoints.moonshotBalance;
|
|
1104
|
+
const result = await requestJson(endpoint, {
|
|
1105
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
1106
|
+
}, config);
|
|
1107
|
+
if (!result.ok) return { session: 0, weekly: 0, error: result.error };
|
|
1108
|
+
return extractMoonshotBalanceFromPayload(result.data, provider) ?? {
|
|
1109
|
+
session: 0,
|
|
1110
|
+
weekly: 0,
|
|
1111
|
+
error: "unrecognized response shape",
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
548
1115
|
/** Parse ZAI limits where unit 3 is the five-hour window and unit 6 the weekly window. */
|
|
549
1116
|
export function extractZaiUsageFromPayload(payload: unknown, nowMs = Date.now()): UsageData | null {
|
|
550
1117
|
const data = payload as any;
|
|
@@ -597,6 +1164,13 @@ export function detectProvider(
|
|
|
597
1164
|
case "anthropic": return "claude";
|
|
598
1165
|
case "zai": return "zai";
|
|
599
1166
|
case "zai-coding-cn": return "zai-cn";
|
|
1167
|
+
case "kimi-coding": return "kimi";
|
|
1168
|
+
case "minimax": return "minimax";
|
|
1169
|
+
case "minimax-cn": return "minimax-cn";
|
|
1170
|
+
case "openrouter": return "openrouter";
|
|
1171
|
+
case "deepseek": return "deepseek";
|
|
1172
|
+
case "moonshotai": return "moonshot";
|
|
1173
|
+
case "moonshotai-cn": return "moonshot-cn";
|
|
600
1174
|
default: return null;
|
|
601
1175
|
}
|
|
602
1176
|
}
|
|
@@ -607,6 +1181,13 @@ export function providerToPiProviderId(provider: ProviderKey): PiProviderId {
|
|
|
607
1181
|
case "claude": return "anthropic";
|
|
608
1182
|
case "zai": return "zai";
|
|
609
1183
|
case "zai-cn": return "zai-coding-cn";
|
|
1184
|
+
case "kimi": return "kimi-coding";
|
|
1185
|
+
case "minimax": return "minimax";
|
|
1186
|
+
case "minimax-cn": return "minimax-cn";
|
|
1187
|
+
case "openrouter": return "openrouter";
|
|
1188
|
+
case "deepseek": return "deepseek";
|
|
1189
|
+
case "moonshot": return "moonshotai";
|
|
1190
|
+
case "moonshot-cn": return "moonshotai-cn";
|
|
610
1191
|
}
|
|
611
1192
|
}
|
|
612
1193
|
|
|
@@ -626,7 +1207,19 @@ export async function fetchAllUsages(
|
|
|
626
1207
|
config: FetchAllUsagesConfig = {},
|
|
627
1208
|
): Promise<UsageByProvider> {
|
|
628
1209
|
const endpoints = config.endpoints ?? resolveUsageEndpoints(config.env);
|
|
629
|
-
const results: UsageByProvider = {
|
|
1210
|
+
const results: UsageByProvider = {
|
|
1211
|
+
codex: null,
|
|
1212
|
+
claude: null,
|
|
1213
|
+
zai: null,
|
|
1214
|
+
"zai-cn": null,
|
|
1215
|
+
kimi: null,
|
|
1216
|
+
minimax: null,
|
|
1217
|
+
"minimax-cn": null,
|
|
1218
|
+
openrouter: null,
|
|
1219
|
+
deepseek: null,
|
|
1220
|
+
moonshot: null,
|
|
1221
|
+
"moonshot-cn": null,
|
|
1222
|
+
};
|
|
630
1223
|
const tasks: Promise<void>[] = [];
|
|
631
1224
|
|
|
632
1225
|
const assign = (provider: ProviderKey, request: Promise<UsageData>) => {
|
|
@@ -647,7 +1240,29 @@ export async function fetchAllUsages(
|
|
|
647
1240
|
}
|
|
648
1241
|
if (tokens.zai) assign("zai", fetchZaiUsage(tokens.zai, "zai", { ...config, endpoints }));
|
|
649
1242
|
if (tokens["zai-cn"]) assign("zai-cn", fetchZaiUsage(tokens["zai-cn"], "zai-cn", { ...config, endpoints }));
|
|
1243
|
+
if (tokens.kimi) assign("kimi", fetchKimiUsage(tokens.kimi, { ...config, endpoints }));
|
|
1244
|
+
if (tokens.minimax) assign("minimax", fetchMiniMaxUsage(tokens.minimax, "minimax", { ...config, endpoints }));
|
|
1245
|
+
if (tokens["minimax-cn"]) {
|
|
1246
|
+
assign("minimax-cn", fetchMiniMaxUsage(tokens["minimax-cn"], "minimax-cn", { ...config, endpoints }));
|
|
1247
|
+
}
|
|
1248
|
+
if (tokens.openrouter) assign("openrouter", fetchOpenRouterUsage(tokens.openrouter, { ...config, endpoints }));
|
|
1249
|
+
if (tokens.deepseek) assign("deepseek", fetchDeepSeekBalance(tokens.deepseek, { ...config, endpoints }));
|
|
1250
|
+
if (tokens.moonshot) assign("moonshot", fetchMoonshotBalance(tokens.moonshot, "moonshot", { ...config, endpoints }));
|
|
1251
|
+
if (tokens["moonshot-cn"]) {
|
|
1252
|
+
assign("moonshot-cn", fetchMoonshotBalance(tokens["moonshot-cn"], "moonshot-cn", { ...config, endpoints }));
|
|
1253
|
+
}
|
|
650
1254
|
|
|
651
1255
|
await Promise.all(tasks);
|
|
1256
|
+
|
|
1257
|
+
// Pi intentionally uses MOONSHOT_API_KEY for both regional providers. When one
|
|
1258
|
+
// key works in only one region, hide the expected regional auth failure from
|
|
1259
|
+
// the all-provider view while preserving active-provider polling behavior.
|
|
1260
|
+
if (tokens.moonshot && tokens.moonshot === tokens["moonshot-cn"]) {
|
|
1261
|
+
if (results.moonshot && !results.moonshot.error && results["moonshot-cn"]?.error) {
|
|
1262
|
+
results["moonshot-cn"] = null;
|
|
1263
|
+
} else if (results["moonshot-cn"] && !results["moonshot-cn"].error && results.moonshot?.error) {
|
|
1264
|
+
results.moonshot = null;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
652
1267
|
return results;
|
|
653
1268
|
}
|