@narumitw/pi-usage 0.58.0 → 0.60.0
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 +86 -7
- package/dist/index.ts +948 -235
- package/dist/index.ts.map +4 -4
- package/package.json +7 -1
- package/src/format.ts +176 -15
- package/src/index.ts +14 -1
- package/src/providers/baseten.ts +55 -0
- package/src/providers/minimax.ts +264 -0
- package/src/providers/moonshot.ts +64 -0
- package/src/providers/vercel-ai-gateway.ts +43 -0
- package/src/providers/zai.ts +109 -5
- package/src/query.ts +276 -32
- package/src/types.ts +41 -0
- package/src/usage.ts +21 -13
package/src/query.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
fallbackOAuthCredentialCandidates,
|
|
6
6
|
type OAuthCredentialCandidateReader,
|
|
7
7
|
} from "./oauth-credential-source.js";
|
|
8
|
+
import { normalizeBasetenBillingUsagePayload } from "./providers/baseten.js";
|
|
8
9
|
import { normalizeCodexBackendPayload } from "./providers/codex.js";
|
|
9
10
|
import { normalizeDeepSeekBalancePayload } from "./providers/deepseek.js";
|
|
10
11
|
import {
|
|
@@ -14,17 +15,27 @@ import {
|
|
|
14
15
|
} from "./providers/fireworks.js";
|
|
15
16
|
import { normalizeGitHubCopilotUsagePayload } from "./providers/github-copilot.js";
|
|
16
17
|
import { normalizeKimiCodingUsagePayload } from "./providers/kimi-coding.js";
|
|
18
|
+
import {
|
|
19
|
+
type MiniMaxProviderId,
|
|
20
|
+
miniMaxUsageKind,
|
|
21
|
+
normalizeMiniMaxUsagePayload,
|
|
22
|
+
} from "./providers/minimax.js";
|
|
23
|
+
import { normalizeMoonshotBalancePayload } from "./providers/moonshot.js";
|
|
17
24
|
import { normalizeOpenCodeZenPayload } from "./providers/opencode-zen.js";
|
|
18
25
|
import { normalizeOpenRouterKeyPayload } from "./providers/openrouter.js";
|
|
26
|
+
import { normalizeVercelAIGatewayCreditsPayload } from "./providers/vercel-ai-gateway.js";
|
|
19
27
|
import { normalizeXaiBillingPayload } from "./providers/xai.js";
|
|
20
|
-
import { normalizeZaiQuotaPayload } from "./providers/zai.js";
|
|
28
|
+
import { normalizeZaiQuotaPayload, normalizeZaiSubscriptionPayload } from "./providers/zai.js";
|
|
21
29
|
import type {
|
|
30
|
+
BasetenBillingUsagePayload,
|
|
22
31
|
CodexBackendPayload,
|
|
23
32
|
DeepSeekBalancePayload,
|
|
24
33
|
FireworksAccountsPayload,
|
|
25
34
|
FireworksBillingSummaryPayload,
|
|
26
35
|
GitHubCopilotUsagePayload,
|
|
27
36
|
KimiCodingUsagePayload,
|
|
37
|
+
MiniMaxUsagePayload,
|
|
38
|
+
MoonshotBalancePayload,
|
|
28
39
|
OpenCodeZenPayload,
|
|
29
40
|
OpenRouterKeyPayload,
|
|
30
41
|
PiModel,
|
|
@@ -32,11 +43,16 @@ import type {
|
|
|
32
43
|
UsageProviderAdapter,
|
|
33
44
|
UsageQuerySettings,
|
|
34
45
|
UsageReport,
|
|
46
|
+
VercelAIGatewayCreditsPayload,
|
|
35
47
|
XaiBillingPayload,
|
|
36
48
|
XaiUserPayload,
|
|
49
|
+
ZaiPlanInfo,
|
|
37
50
|
ZaiQuotaPayload,
|
|
51
|
+
ZaiSubscriptionPayload,
|
|
38
52
|
} from "./types.js";
|
|
39
53
|
|
|
54
|
+
const BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
|
|
55
|
+
const BASETEN_USAGE_WINDOW_DAYS = 30;
|
|
40
56
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
41
57
|
const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
42
58
|
const FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
|
|
@@ -44,8 +60,18 @@ const FIREWORKS_SPEND_WINDOW_DAYS = 30;
|
|
|
44
60
|
const FIREWORKS_MAX_ACCOUNT_PAGES = 5;
|
|
45
61
|
const GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
46
62
|
const OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
63
|
+
const VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
|
47
64
|
const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
48
65
|
const KIMI_CODING_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
66
|
+
const MINIMAX_API_ROOTS = Object.freeze({
|
|
67
|
+
minimax: "https://api.minimax.io",
|
|
68
|
+
"minimax-cn": "https://api.minimaxi.com",
|
|
69
|
+
});
|
|
70
|
+
const MOONSHOT_BALANCE_URLS = Object.freeze({
|
|
71
|
+
moonshotai: "https://api.moonshot.ai/v1/users/me/balance",
|
|
72
|
+
"moonshotai-cn": "https://api.moonshot.cn/v1/users/me/balance",
|
|
73
|
+
});
|
|
74
|
+
const SHARED_MOONSHOT_ENV_VAR = "MOONSHOT_API_KEY";
|
|
49
75
|
const XAI_USER_URL = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
|
|
50
76
|
const XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
51
77
|
const XAI_CLIENT_HEADERS = Object.freeze({
|
|
@@ -61,6 +87,27 @@ export const AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
|
61
87
|
export type UsageRequestGuard = () => Promise<void>;
|
|
62
88
|
|
|
63
89
|
export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
90
|
+
{
|
|
91
|
+
id: "baseten",
|
|
92
|
+
displayName: "Baseten",
|
|
93
|
+
semantics: { kind: "api-key", label: "Organization Model APIs spend" },
|
|
94
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
95
|
+
if (!guard) throw new Error("Baseten billing usage requires request-boundary revalidation.");
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
await guard();
|
|
98
|
+
const windowAt = Date.now();
|
|
99
|
+
const payload = (await fetchProviderJson(
|
|
100
|
+
basetenBillingUsageUrl(windowAt),
|
|
101
|
+
auth,
|
|
102
|
+
signal,
|
|
103
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Baseten billing usage"),
|
|
104
|
+
"Baseten billing usage endpoint",
|
|
105
|
+
{ redirect: "error" },
|
|
106
|
+
)) as BasetenBillingUsagePayload;
|
|
107
|
+
await guard();
|
|
108
|
+
return normalizeBasetenBillingUsagePayload(payload, Date.now());
|
|
109
|
+
},
|
|
110
|
+
},
|
|
64
111
|
{
|
|
65
112
|
id: "openai-codex",
|
|
66
113
|
displayName: "OpenAI Codex",
|
|
@@ -133,6 +180,27 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
|
133
180
|
return normalizeOpenRouterKeyPayload(payload as OpenRouterKeyPayload, Date.now());
|
|
134
181
|
},
|
|
135
182
|
},
|
|
183
|
+
{
|
|
184
|
+
id: "vercel-ai-gateway",
|
|
185
|
+
displayName: "Vercel AI Gateway",
|
|
186
|
+
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
187
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
188
|
+
if (!guard)
|
|
189
|
+
throw new Error("Vercel AI Gateway usage requires request-boundary revalidation.");
|
|
190
|
+
const startedAt = Date.now();
|
|
191
|
+
await guard();
|
|
192
|
+
const payload = (await fetchProviderJson(
|
|
193
|
+
VERCEL_AI_GATEWAY_CREDITS_URL,
|
|
194
|
+
auth,
|
|
195
|
+
signal,
|
|
196
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
|
|
197
|
+
"Vercel AI Gateway credits endpoint",
|
|
198
|
+
{ redirect: "error" },
|
|
199
|
+
)) as VercelAIGatewayCreditsPayload;
|
|
200
|
+
await guard();
|
|
201
|
+
return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
|
|
202
|
+
},
|
|
203
|
+
},
|
|
136
204
|
{
|
|
137
205
|
id: "fireworks",
|
|
138
206
|
displayName: "Fireworks",
|
|
@@ -193,39 +261,52 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
|
193
261
|
return normalizeKimiCodingUsagePayload(payload as KimiCodingUsagePayload, Date.now());
|
|
194
262
|
},
|
|
195
263
|
},
|
|
264
|
+
{
|
|
265
|
+
id: "minimax",
|
|
266
|
+
displayName: "MiniMax",
|
|
267
|
+
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
268
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
269
|
+
return queryMiniMaxUsage("minimax", auth, signal, timeoutMs, guard);
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "minimax-cn",
|
|
274
|
+
displayName: "MiniMax CN",
|
|
275
|
+
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
276
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
277
|
+
return queryMiniMaxUsage("minimax-cn", auth, signal, timeoutMs, guard);
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "moonshotai",
|
|
282
|
+
displayName: "Moonshot AI",
|
|
283
|
+
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
284
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
285
|
+
return queryMoonshotBalance("moonshotai", auth, signal, timeoutMs, guard);
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "moonshotai-cn",
|
|
290
|
+
displayName: "Moonshot AI CN",
|
|
291
|
+
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
292
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
293
|
+
return queryMoonshotBalance("moonshotai-cn", auth, signal, timeoutMs, guard);
|
|
294
|
+
},
|
|
295
|
+
},
|
|
196
296
|
{
|
|
197
297
|
id: "zai",
|
|
198
298
|
displayName: "Z.AI",
|
|
199
299
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
200
|
-
async query(auth, signal, timeoutMs) {
|
|
201
|
-
|
|
202
|
-
zaiMonitorUrl(auth.model.baseUrl),
|
|
203
|
-
zaiMonitorAuth(auth),
|
|
204
|
-
signal,
|
|
205
|
-
timeoutMs,
|
|
206
|
-
"Z.AI quota endpoint",
|
|
207
|
-
);
|
|
208
|
-
return normalizeZaiQuotaPayload("zai", "Z.AI", payload as ZaiQuotaPayload, Date.now());
|
|
300
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
301
|
+
return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
|
|
209
302
|
},
|
|
210
303
|
},
|
|
211
304
|
{
|
|
212
305
|
id: "zai-coding-cn",
|
|
213
306
|
displayName: "Z.AI Coding CN",
|
|
214
307
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
215
|
-
async query(auth, signal, timeoutMs) {
|
|
216
|
-
|
|
217
|
-
zaiMonitorUrl(auth.model.baseUrl),
|
|
218
|
-
zaiMonitorAuth(auth),
|
|
219
|
-
signal,
|
|
220
|
-
timeoutMs,
|
|
221
|
-
"Z.AI Coding CN quota endpoint",
|
|
222
|
-
);
|
|
223
|
-
return normalizeZaiQuotaPayload(
|
|
224
|
-
"zai-coding-cn",
|
|
225
|
-
"Z.AI Coding CN",
|
|
226
|
-
payload as ZaiQuotaPayload,
|
|
227
|
-
Date.now(),
|
|
228
|
-
);
|
|
308
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
309
|
+
return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
|
|
229
310
|
},
|
|
230
311
|
},
|
|
231
312
|
];
|
|
@@ -329,11 +410,14 @@ export async function resolveUsageAuth(
|
|
|
329
410
|
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
330
411
|
return authorizationFrom(result) ? result : undefined;
|
|
331
412
|
};
|
|
332
|
-
|
|
413
|
+
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn"].includes(adapter.id);
|
|
414
|
+
if (!resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
333
415
|
if (typeof registry.getProviderAuth !== "function") {
|
|
334
416
|
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
335
417
|
}
|
|
418
|
+
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return undefined;
|
|
336
419
|
const providerResult = await registry.getProviderAuth(adapter.id);
|
|
420
|
+
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return undefined;
|
|
337
421
|
if (
|
|
338
422
|
providerResult?.auth.baseUrl &&
|
|
339
423
|
!hasOfficialUrlOrigin(providerResult.auth.baseUrl, adapter.id)
|
|
@@ -342,9 +426,9 @@ export async function resolveUsageAuth(
|
|
|
342
426
|
`${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`,
|
|
343
427
|
);
|
|
344
428
|
}
|
|
345
|
-
//
|
|
346
|
-
// cannot leave the earlier credential queued for the
|
|
347
|
-
if (
|
|
429
|
+
// Providers with credential-change retries read selected-model auth last so a rotation during
|
|
430
|
+
// provider-origin validation cannot leave the earlier credential queued for the usage request.
|
|
431
|
+
if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
348
432
|
const auth = modelAuth ?? providerResult?.auth;
|
|
349
433
|
if (!auth) return undefined;
|
|
350
434
|
if (adapter.id === "github-copilot") {
|
|
@@ -422,12 +506,53 @@ export async function queryProviderUsage(
|
|
|
422
506
|
|
|
423
507
|
export function providerIsConfigured(ctx: ExtensionContext, providerId: string): boolean {
|
|
424
508
|
try {
|
|
425
|
-
|
|
509
|
+
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
510
|
+
return (
|
|
511
|
+
status.configured &&
|
|
512
|
+
moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label)
|
|
513
|
+
);
|
|
514
|
+
} catch {
|
|
515
|
+
return (
|
|
516
|
+
!isMoonshotSiblingProvider(ctx, providerId) && candidateModels(ctx, providerId).length > 0
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function moonshotProviderAuthIsAllowed(ctx: ExtensionContext, providerId: string): boolean {
|
|
522
|
+
if (!isMoonshotSiblingProvider(ctx, providerId)) return true;
|
|
523
|
+
try {
|
|
524
|
+
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
525
|
+
return moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
426
526
|
} catch {
|
|
427
|
-
return
|
|
527
|
+
return false;
|
|
428
528
|
}
|
|
429
529
|
}
|
|
430
530
|
|
|
531
|
+
function moonshotProviderAuthSourceIsAllowed(
|
|
532
|
+
ctx: ExtensionContext,
|
|
533
|
+
providerId: string,
|
|
534
|
+
source: string | undefined,
|
|
535
|
+
label: string | undefined,
|
|
536
|
+
): boolean {
|
|
537
|
+
if (!isMoonshotSiblingProvider(ctx, providerId)) return true;
|
|
538
|
+
if (source === undefined) return false;
|
|
539
|
+
if (source !== "environment") return true;
|
|
540
|
+
return (
|
|
541
|
+
label !== undefined &&
|
|
542
|
+
!label
|
|
543
|
+
.split(",")
|
|
544
|
+
.map((name) => name.trim())
|
|
545
|
+
.includes(SHARED_MOONSHOT_ENV_VAR)
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function isMoonshotSiblingProvider(ctx: ExtensionContext, providerId: string): boolean {
|
|
550
|
+
return (
|
|
551
|
+
(providerId === "moonshotai" || providerId === "moonshotai-cn") &&
|
|
552
|
+
ctx.model?.provider !== providerId
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
431
556
|
function candidateModels(ctx: ExtensionContext, providerId: string): PiModel[] {
|
|
432
557
|
const candidates: PiModel[] = [];
|
|
433
558
|
const seen = new Set<string>();
|
|
@@ -772,12 +897,20 @@ function hasOfficialOrigin(model: PiModel, providerId: string): boolean {
|
|
|
772
897
|
function hasOfficialUrlOrigin(value: string, providerId: string): boolean {
|
|
773
898
|
try {
|
|
774
899
|
const url = new URL(value);
|
|
900
|
+
if (providerId === "baseten") {
|
|
901
|
+
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
902
|
+
}
|
|
775
903
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
776
904
|
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
777
905
|
if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
|
|
778
906
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
907
|
+
if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
|
|
779
908
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
780
909
|
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
910
|
+
if (providerId === "minimax") return url.origin === "https://api.minimax.io";
|
|
911
|
+
if (providerId === "minimax-cn") return url.origin === "https://api.minimaxi.com";
|
|
912
|
+
if (providerId === "moonshotai") return url.origin === "https://api.moonshot.ai";
|
|
913
|
+
if (providerId === "moonshotai-cn") return url.origin === "https://api.moonshot.cn";
|
|
781
914
|
if (providerId === "xai") return url.origin === "https://api.x.ai";
|
|
782
915
|
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
783
916
|
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
@@ -813,6 +946,64 @@ function validatedXaiUserId(value: unknown): string {
|
|
|
813
946
|
return value;
|
|
814
947
|
}
|
|
815
948
|
|
|
949
|
+
function basetenBillingUsageUrl(windowAt: number): string {
|
|
950
|
+
const url = new URL(BASETEN_BILLING_USAGE_URL);
|
|
951
|
+
url.searchParams.set(
|
|
952
|
+
"start_date",
|
|
953
|
+
new Date(windowAt - BASETEN_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1_000).toISOString(),
|
|
954
|
+
);
|
|
955
|
+
url.searchParams.set("end_date", new Date(windowAt).toISOString());
|
|
956
|
+
return url.toString();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async function queryMiniMaxUsage(
|
|
960
|
+
providerId: MiniMaxProviderId,
|
|
961
|
+
auth: ResolvedUsageAuth,
|
|
962
|
+
signal: AbortSignal,
|
|
963
|
+
timeoutMs: number,
|
|
964
|
+
guard: UsageRequestGuard | undefined,
|
|
965
|
+
): Promise<UsageReport> {
|
|
966
|
+
if (!guard) throw new Error("MiniMax usage requires request-boundary revalidation.");
|
|
967
|
+
const apiKey = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
968
|
+
if (!apiKey) throw new Error("MiniMax runtime API key was unavailable.");
|
|
969
|
+
const kind = miniMaxUsageKind(apiKey);
|
|
970
|
+
const path = kind === "account-balance" ? "/account/query_balance" : "/v1/token_plan/remains";
|
|
971
|
+
const startedAt = Date.now();
|
|
972
|
+
await guard();
|
|
973
|
+
const payload = (await fetchProviderJson(
|
|
974
|
+
`${MINIMAX_API_ROOTS[providerId]}${path}`,
|
|
975
|
+
auth,
|
|
976
|
+
signal,
|
|
977
|
+
remainingTimeout(timeoutMs, startedAt, "fetching MiniMax usage"),
|
|
978
|
+
"MiniMax usage endpoint",
|
|
979
|
+
{ redirect: "error" },
|
|
980
|
+
)) as MiniMaxUsagePayload;
|
|
981
|
+
await guard();
|
|
982
|
+
return normalizeMiniMaxUsagePayload(providerId, kind, payload, Date.now());
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async function queryMoonshotBalance(
|
|
986
|
+
providerId: "moonshotai" | "moonshotai-cn",
|
|
987
|
+
auth: ResolvedUsageAuth,
|
|
988
|
+
signal: AbortSignal,
|
|
989
|
+
timeoutMs: number,
|
|
990
|
+
guard: UsageRequestGuard | undefined,
|
|
991
|
+
): Promise<UsageReport> {
|
|
992
|
+
if (!guard) throw new Error("Moonshot AI balance requires request-boundary revalidation.");
|
|
993
|
+
const startedAt = Date.now();
|
|
994
|
+
await guard();
|
|
995
|
+
const payload = (await fetchProviderJson(
|
|
996
|
+
MOONSHOT_BALANCE_URLS[providerId],
|
|
997
|
+
auth,
|
|
998
|
+
signal,
|
|
999
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Moonshot AI balance"),
|
|
1000
|
+
"Moonshot AI balance endpoint",
|
|
1001
|
+
{ redirect: "error" },
|
|
1002
|
+
)) as MoonshotBalancePayload;
|
|
1003
|
+
await guard();
|
|
1004
|
+
return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
|
|
1005
|
+
}
|
|
1006
|
+
|
|
816
1007
|
function remainingTimeout(
|
|
817
1008
|
timeoutMs: number,
|
|
818
1009
|
startedAt: number,
|
|
@@ -915,10 +1106,14 @@ function fireworksBillingSummaryUrl(accountId: string, startedAt: number): strin
|
|
|
915
1106
|
return url.toString();
|
|
916
1107
|
}
|
|
917
1108
|
|
|
918
|
-
function
|
|
1109
|
+
function zaiOrigin(baseUrl: string | undefined): string {
|
|
919
1110
|
const base = baseUrl?.trim();
|
|
920
1111
|
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
921
|
-
return
|
|
1112
|
+
return new URL(base).origin;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function zaiMonitorUrl(baseUrl: string | undefined): string {
|
|
1116
|
+
return `${zaiOrigin(baseUrl)}/api/monitor/usage/quota/limit`;
|
|
922
1117
|
}
|
|
923
1118
|
|
|
924
1119
|
function zaiMonitorAuth(auth: ResolvedUsageAuth): ResolvedUsageAuth {
|
|
@@ -929,6 +1124,55 @@ function zaiMonitorAuth(auth: ResolvedUsageAuth): ResolvedUsageAuth {
|
|
|
929
1124
|
return { ...auth, headers: { ...auth.headers, Authorization: token } };
|
|
930
1125
|
}
|
|
931
1126
|
|
|
1127
|
+
async function queryZaiUsage(
|
|
1128
|
+
providerId: "zai" | "zai-coding-cn",
|
|
1129
|
+
providerName: string,
|
|
1130
|
+
auth: ResolvedUsageAuth,
|
|
1131
|
+
signal: AbortSignal,
|
|
1132
|
+
timeoutMs: number,
|
|
1133
|
+
guard: UsageRequestGuard | undefined,
|
|
1134
|
+
): Promise<UsageReport> {
|
|
1135
|
+
if (!guard) throw new Error("Z.AI usage requires request-boundary revalidation.");
|
|
1136
|
+
const startedAt = Date.now();
|
|
1137
|
+
await guard();
|
|
1138
|
+
const payload = (await fetchProviderJson(
|
|
1139
|
+
zaiMonitorUrl(auth.model.baseUrl),
|
|
1140
|
+
zaiMonitorAuth(auth),
|
|
1141
|
+
signal,
|
|
1142
|
+
remainingTimeout(timeoutMs, startedAt, `fetching ${providerName} quota`),
|
|
1143
|
+
`${providerName} quota endpoint`,
|
|
1144
|
+
)) as ZaiQuotaPayload;
|
|
1145
|
+
await guard();
|
|
1146
|
+
const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
1147
|
+
const plan = await fetchZaiPlan(providerName, auth, signal, planTimeoutMs);
|
|
1148
|
+
return normalizeZaiQuotaPayload(providerId, providerName, payload, Date.now(), plan);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// The subscription endpoint is undocumented and may not exist on every official origin. It only
|
|
1152
|
+
// contributes the plan name and renewal date, so any non-abort failure is swallowed instead of
|
|
1153
|
+
// blanking the required quota report.
|
|
1154
|
+
async function fetchZaiPlan(
|
|
1155
|
+
providerName: string,
|
|
1156
|
+
auth: ResolvedUsageAuth,
|
|
1157
|
+
signal: AbortSignal,
|
|
1158
|
+
timeoutMs: number,
|
|
1159
|
+
): Promise<ZaiPlanInfo | undefined> {
|
|
1160
|
+
if (timeoutMs <= 0 || signal.aborted) return undefined;
|
|
1161
|
+
try {
|
|
1162
|
+
const payload = (await fetchProviderJson(
|
|
1163
|
+
`${zaiOrigin(auth.model.baseUrl)}/api/biz/subscription/list`,
|
|
1164
|
+
zaiMonitorAuth(auth),
|
|
1165
|
+
signal,
|
|
1166
|
+
timeoutMs,
|
|
1167
|
+
`${providerName} plan endpoint`,
|
|
1168
|
+
)) as ZaiSubscriptionPayload;
|
|
1169
|
+
return normalizeZaiSubscriptionPayload(payload);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
if (isAbortError(error)) throw error;
|
|
1172
|
+
return undefined;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
932
1176
|
function isAbortError(error: unknown): boolean {
|
|
933
1177
|
return error instanceof Error && error.name === "AbortError";
|
|
934
1178
|
}
|
package/src/types.ts
CHANGED
|
@@ -89,6 +89,12 @@ export type ProviderUsageState =
|
|
|
89
89
|
message: string;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
export type BasetenBillingUsagePayload = {
|
|
93
|
+
dedicated_usage?: unknown;
|
|
94
|
+
model_apis_usage?: unknown;
|
|
95
|
+
training_usage?: unknown;
|
|
96
|
+
};
|
|
97
|
+
|
|
92
98
|
export type DeepSeekBalancePayload = {
|
|
93
99
|
is_available?: unknown;
|
|
94
100
|
balance_infos?: unknown;
|
|
@@ -119,6 +125,30 @@ export type OpenRouterKeyPayload = {
|
|
|
119
125
|
data?: unknown;
|
|
120
126
|
};
|
|
121
127
|
|
|
128
|
+
export type MiniMaxUsagePayload = {
|
|
129
|
+
available_amount?: unknown;
|
|
130
|
+
balance_alert_switch?: unknown;
|
|
131
|
+
balance_alert_threshold?: unknown;
|
|
132
|
+
base_resp?: unknown;
|
|
133
|
+
cash_balance?: unknown;
|
|
134
|
+
credit_balance?: unknown;
|
|
135
|
+
model_remains?: unknown;
|
|
136
|
+
owed_amount?: unknown;
|
|
137
|
+
voucher_balance?: unknown;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type MoonshotBalancePayload = {
|
|
141
|
+
code?: unknown;
|
|
142
|
+
data?: unknown;
|
|
143
|
+
scode?: unknown;
|
|
144
|
+
status?: unknown;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type VercelAIGatewayCreditsPayload = {
|
|
148
|
+
balance?: unknown;
|
|
149
|
+
total_used?: unknown;
|
|
150
|
+
};
|
|
151
|
+
|
|
122
152
|
export type OpenCodeZenPayload = {
|
|
123
153
|
usage?: unknown;
|
|
124
154
|
};
|
|
@@ -127,6 +157,17 @@ export type ZaiQuotaPayload = {
|
|
|
127
157
|
data?: unknown;
|
|
128
158
|
};
|
|
129
159
|
|
|
160
|
+
export type ZaiSubscriptionPayload = {
|
|
161
|
+
code?: unknown;
|
|
162
|
+
success?: unknown;
|
|
163
|
+
data?: unknown;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export interface ZaiPlanInfo {
|
|
167
|
+
name: string;
|
|
168
|
+
renewsAt?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
130
171
|
export type KimiCodingUsagePayload = {
|
|
131
172
|
usage?: unknown;
|
|
132
173
|
limits?: unknown;
|
package/src/usage.ts
CHANGED
|
@@ -207,13 +207,7 @@ export default function usageExtension(
|
|
|
207
207
|
const generation = statusGeneration;
|
|
208
208
|
statusCountdownTimer = setTimeout(() => {
|
|
209
209
|
statusCountdownTimer = undefined;
|
|
210
|
-
if (
|
|
211
|
-
!sessionActive ||
|
|
212
|
-
generation !== statusGeneration ||
|
|
213
|
-
modelIdentity(ctx.model) !== modelIdentity(model)
|
|
214
|
-
) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
210
|
+
if (!sessionActive || generation !== statusGeneration) return;
|
|
217
211
|
publishStatus(ctx, outcome, model, false);
|
|
218
212
|
}, STATUS_COUNTDOWN_REFRESH_MS);
|
|
219
213
|
statusCountdownTimer.unref?.();
|
|
@@ -281,7 +275,19 @@ export default function usageExtension(
|
|
|
281
275
|
},
|
|
282
276
|
};
|
|
283
277
|
}
|
|
284
|
-
const requiresRequestBoundaryGuard = [
|
|
278
|
+
const requiresRequestBoundaryGuard = [
|
|
279
|
+
"baseten",
|
|
280
|
+
"deepseek",
|
|
281
|
+
"fireworks",
|
|
282
|
+
"minimax",
|
|
283
|
+
"minimax-cn",
|
|
284
|
+
"moonshotai",
|
|
285
|
+
"moonshotai-cn",
|
|
286
|
+
"vercel-ai-gateway",
|
|
287
|
+
"xai",
|
|
288
|
+
"zai",
|
|
289
|
+
"zai-coding-cn",
|
|
290
|
+
].includes(adapter.id);
|
|
285
291
|
const requestContextChanged = () =>
|
|
286
292
|
expectedSessionGeneration !== sessionGeneration ||
|
|
287
293
|
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
@@ -345,7 +351,7 @@ export default function usageExtension(
|
|
|
345
351
|
const queryId = querySequence;
|
|
346
352
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
347
353
|
|
|
348
|
-
let
|
|
354
|
+
let retryableAuthChanged = false;
|
|
349
355
|
try {
|
|
350
356
|
const remainingMs = Math.max(1, deadlineAt - Date.now());
|
|
351
357
|
const guard = requiresRequestBoundaryGuard
|
|
@@ -359,9 +365,11 @@ export default function usageExtension(
|
|
|
359
365
|
);
|
|
360
366
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
361
367
|
if (revalidated?.fingerprint !== auth.fingerprint) {
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
throw new Error(
|
|
368
|
+
if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
|
|
369
|
+
retryableAuthChanged = true;
|
|
370
|
+
throw new Error(
|
|
371
|
+
`${adapter.displayName} runtime credential changed during the usage query.`,
|
|
372
|
+
);
|
|
365
373
|
}
|
|
366
374
|
throw abortError();
|
|
367
375
|
}
|
|
@@ -393,7 +401,7 @@ export default function usageExtension(
|
|
|
393
401
|
} catch (error) {
|
|
394
402
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
395
403
|
if (
|
|
396
|
-
|
|
404
|
+
retryableAuthChanged &&
|
|
397
405
|
authRetry === 0 &&
|
|
398
406
|
!signal.aborted &&
|
|
399
407
|
!requestContextChanged() &&
|