@narumitw/pi-usage 0.58.0 → 0.59.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 +83 -1
- package/dist/index.ts +813 -197
- package/dist/index.ts.map +4 -4
- package/package.json +7 -1
- package/src/format.ts +161 -7
- package/src/index.ts +13 -0
- 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/query.ts +214 -6
- package/src/types.ts +30 -0
- package/src/usage.ts +19 -13
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { UsageMetric, UsageReport, VercelAIGatewayCreditsPayload } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const DECIMAL_AMOUNT = /^(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
4
|
+
|
|
5
|
+
export function normalizeVercelAIGatewayCreditsPayload(
|
|
6
|
+
payload: VercelAIGatewayCreditsPayload,
|
|
7
|
+
capturedAt: number,
|
|
8
|
+
): UsageReport {
|
|
9
|
+
const balance = decimalAmount(payload.balance, "balance");
|
|
10
|
+
const totalUsed = decimalAmount(payload.total_used, "total used");
|
|
11
|
+
const metrics: UsageMetric[] = [
|
|
12
|
+
{
|
|
13
|
+
id: "credit-balance",
|
|
14
|
+
label: "Credit balance",
|
|
15
|
+
value: balance,
|
|
16
|
+
unit: "currency",
|
|
17
|
+
currency: "USD",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "lifetime-spend",
|
|
21
|
+
label: "Lifetime spend",
|
|
22
|
+
value: totalUsed,
|
|
23
|
+
unit: "currency",
|
|
24
|
+
currency: "USD",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
return {
|
|
28
|
+
providerId: "vercel-ai-gateway",
|
|
29
|
+
providerName: "Vercel AI Gateway",
|
|
30
|
+
capturedAt,
|
|
31
|
+
source: "vercel-ai-gateway-credits",
|
|
32
|
+
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
33
|
+
buckets: [],
|
|
34
|
+
metrics,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function decimalAmount(value: unknown, label: string): string {
|
|
39
|
+
if (typeof value !== "string" || value.length > 64 || !DECIMAL_AMOUNT.test(value)) {
|
|
40
|
+
throw new Error(`Vercel AI Gateway ${label} was not a valid nonnegative amount.`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
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
28
|
import { normalizeZaiQuotaPayload } 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,14 @@ import type {
|
|
|
32
43
|
UsageProviderAdapter,
|
|
33
44
|
UsageQuerySettings,
|
|
34
45
|
UsageReport,
|
|
46
|
+
VercelAIGatewayCreditsPayload,
|
|
35
47
|
XaiBillingPayload,
|
|
36
48
|
XaiUserPayload,
|
|
37
49
|
ZaiQuotaPayload,
|
|
38
50
|
} from "./types.js";
|
|
39
51
|
|
|
52
|
+
const BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
|
|
53
|
+
const BASETEN_USAGE_WINDOW_DAYS = 30;
|
|
40
54
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
41
55
|
const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
42
56
|
const FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
|
|
@@ -44,8 +58,18 @@ const FIREWORKS_SPEND_WINDOW_DAYS = 30;
|
|
|
44
58
|
const FIREWORKS_MAX_ACCOUNT_PAGES = 5;
|
|
45
59
|
const GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
46
60
|
const OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
61
|
+
const VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
|
47
62
|
const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
48
63
|
const KIMI_CODING_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
64
|
+
const MINIMAX_API_ROOTS = Object.freeze({
|
|
65
|
+
minimax: "https://api.minimax.io",
|
|
66
|
+
"minimax-cn": "https://api.minimaxi.com",
|
|
67
|
+
});
|
|
68
|
+
const MOONSHOT_BALANCE_URLS = Object.freeze({
|
|
69
|
+
moonshotai: "https://api.moonshot.ai/v1/users/me/balance",
|
|
70
|
+
"moonshotai-cn": "https://api.moonshot.cn/v1/users/me/balance",
|
|
71
|
+
});
|
|
72
|
+
const SHARED_MOONSHOT_ENV_VAR = "MOONSHOT_API_KEY";
|
|
49
73
|
const XAI_USER_URL = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
|
|
50
74
|
const XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
51
75
|
const XAI_CLIENT_HEADERS = Object.freeze({
|
|
@@ -61,6 +85,27 @@ export const AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
|
61
85
|
export type UsageRequestGuard = () => Promise<void>;
|
|
62
86
|
|
|
63
87
|
export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
88
|
+
{
|
|
89
|
+
id: "baseten",
|
|
90
|
+
displayName: "Baseten",
|
|
91
|
+
semantics: { kind: "api-key", label: "Organization Model APIs spend" },
|
|
92
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
93
|
+
if (!guard) throw new Error("Baseten billing usage requires request-boundary revalidation.");
|
|
94
|
+
const startedAt = Date.now();
|
|
95
|
+
await guard();
|
|
96
|
+
const windowAt = Date.now();
|
|
97
|
+
const payload = (await fetchProviderJson(
|
|
98
|
+
basetenBillingUsageUrl(windowAt),
|
|
99
|
+
auth,
|
|
100
|
+
signal,
|
|
101
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Baseten billing usage"),
|
|
102
|
+
"Baseten billing usage endpoint",
|
|
103
|
+
{ redirect: "error" },
|
|
104
|
+
)) as BasetenBillingUsagePayload;
|
|
105
|
+
await guard();
|
|
106
|
+
return normalizeBasetenBillingUsagePayload(payload, Date.now());
|
|
107
|
+
},
|
|
108
|
+
},
|
|
64
109
|
{
|
|
65
110
|
id: "openai-codex",
|
|
66
111
|
displayName: "OpenAI Codex",
|
|
@@ -133,6 +178,27 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
|
133
178
|
return normalizeOpenRouterKeyPayload(payload as OpenRouterKeyPayload, Date.now());
|
|
134
179
|
},
|
|
135
180
|
},
|
|
181
|
+
{
|
|
182
|
+
id: "vercel-ai-gateway",
|
|
183
|
+
displayName: "Vercel AI Gateway",
|
|
184
|
+
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
185
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
186
|
+
if (!guard)
|
|
187
|
+
throw new Error("Vercel AI Gateway usage requires request-boundary revalidation.");
|
|
188
|
+
const startedAt = Date.now();
|
|
189
|
+
await guard();
|
|
190
|
+
const payload = (await fetchProviderJson(
|
|
191
|
+
VERCEL_AI_GATEWAY_CREDITS_URL,
|
|
192
|
+
auth,
|
|
193
|
+
signal,
|
|
194
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
|
|
195
|
+
"Vercel AI Gateway credits endpoint",
|
|
196
|
+
{ redirect: "error" },
|
|
197
|
+
)) as VercelAIGatewayCreditsPayload;
|
|
198
|
+
await guard();
|
|
199
|
+
return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
|
|
200
|
+
},
|
|
201
|
+
},
|
|
136
202
|
{
|
|
137
203
|
id: "fireworks",
|
|
138
204
|
displayName: "Fireworks",
|
|
@@ -193,6 +259,38 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
|
193
259
|
return normalizeKimiCodingUsagePayload(payload as KimiCodingUsagePayload, Date.now());
|
|
194
260
|
},
|
|
195
261
|
},
|
|
262
|
+
{
|
|
263
|
+
id: "minimax",
|
|
264
|
+
displayName: "MiniMax",
|
|
265
|
+
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
266
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
267
|
+
return queryMiniMaxUsage("minimax", auth, signal, timeoutMs, guard);
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "minimax-cn",
|
|
272
|
+
displayName: "MiniMax CN",
|
|
273
|
+
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
274
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
275
|
+
return queryMiniMaxUsage("minimax-cn", auth, signal, timeoutMs, guard);
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "moonshotai",
|
|
280
|
+
displayName: "Moonshot AI",
|
|
281
|
+
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
282
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
283
|
+
return queryMoonshotBalance("moonshotai", auth, signal, timeoutMs, guard);
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "moonshotai-cn",
|
|
288
|
+
displayName: "Moonshot AI CN",
|
|
289
|
+
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
290
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
291
|
+
return queryMoonshotBalance("moonshotai-cn", auth, signal, timeoutMs, guard);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
196
294
|
{
|
|
197
295
|
id: "zai",
|
|
198
296
|
displayName: "Z.AI",
|
|
@@ -329,11 +427,14 @@ export async function resolveUsageAuth(
|
|
|
329
427
|
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
330
428
|
return authorizationFrom(result) ? result : undefined;
|
|
331
429
|
};
|
|
332
|
-
|
|
430
|
+
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn"].includes(adapter.id);
|
|
431
|
+
if (!resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
333
432
|
if (typeof registry.getProviderAuth !== "function") {
|
|
334
433
|
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
335
434
|
}
|
|
435
|
+
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return undefined;
|
|
336
436
|
const providerResult = await registry.getProviderAuth(adapter.id);
|
|
437
|
+
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return undefined;
|
|
337
438
|
if (
|
|
338
439
|
providerResult?.auth.baseUrl &&
|
|
339
440
|
!hasOfficialUrlOrigin(providerResult.auth.baseUrl, adapter.id)
|
|
@@ -342,9 +443,9 @@ export async function resolveUsageAuth(
|
|
|
342
443
|
`${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`,
|
|
343
444
|
);
|
|
344
445
|
}
|
|
345
|
-
//
|
|
346
|
-
// cannot leave the earlier credential queued for the
|
|
347
|
-
if (
|
|
446
|
+
// Providers with credential-change retries read selected-model auth last so a rotation during
|
|
447
|
+
// provider-origin validation cannot leave the earlier credential queued for the usage request.
|
|
448
|
+
if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
348
449
|
const auth = modelAuth ?? providerResult?.auth;
|
|
349
450
|
if (!auth) return undefined;
|
|
350
451
|
if (adapter.id === "github-copilot") {
|
|
@@ -422,12 +523,53 @@ export async function queryProviderUsage(
|
|
|
422
523
|
|
|
423
524
|
export function providerIsConfigured(ctx: ExtensionContext, providerId: string): boolean {
|
|
424
525
|
try {
|
|
425
|
-
|
|
526
|
+
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
527
|
+
return (
|
|
528
|
+
status.configured &&
|
|
529
|
+
moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label)
|
|
530
|
+
);
|
|
531
|
+
} catch {
|
|
532
|
+
return (
|
|
533
|
+
!isMoonshotSiblingProvider(ctx, providerId) && candidateModels(ctx, providerId).length > 0
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function moonshotProviderAuthIsAllowed(ctx: ExtensionContext, providerId: string): boolean {
|
|
539
|
+
if (!isMoonshotSiblingProvider(ctx, providerId)) return true;
|
|
540
|
+
try {
|
|
541
|
+
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
542
|
+
return moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
426
543
|
} catch {
|
|
427
|
-
return
|
|
544
|
+
return false;
|
|
428
545
|
}
|
|
429
546
|
}
|
|
430
547
|
|
|
548
|
+
function moonshotProviderAuthSourceIsAllowed(
|
|
549
|
+
ctx: ExtensionContext,
|
|
550
|
+
providerId: string,
|
|
551
|
+
source: string | undefined,
|
|
552
|
+
label: string | undefined,
|
|
553
|
+
): boolean {
|
|
554
|
+
if (!isMoonshotSiblingProvider(ctx, providerId)) return true;
|
|
555
|
+
if (source === undefined) return false;
|
|
556
|
+
if (source !== "environment") return true;
|
|
557
|
+
return (
|
|
558
|
+
label !== undefined &&
|
|
559
|
+
!label
|
|
560
|
+
.split(",")
|
|
561
|
+
.map((name) => name.trim())
|
|
562
|
+
.includes(SHARED_MOONSHOT_ENV_VAR)
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function isMoonshotSiblingProvider(ctx: ExtensionContext, providerId: string): boolean {
|
|
567
|
+
return (
|
|
568
|
+
(providerId === "moonshotai" || providerId === "moonshotai-cn") &&
|
|
569
|
+
ctx.model?.provider !== providerId
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
431
573
|
function candidateModels(ctx: ExtensionContext, providerId: string): PiModel[] {
|
|
432
574
|
const candidates: PiModel[] = [];
|
|
433
575
|
const seen = new Set<string>();
|
|
@@ -772,12 +914,20 @@ function hasOfficialOrigin(model: PiModel, providerId: string): boolean {
|
|
|
772
914
|
function hasOfficialUrlOrigin(value: string, providerId: string): boolean {
|
|
773
915
|
try {
|
|
774
916
|
const url = new URL(value);
|
|
917
|
+
if (providerId === "baseten") {
|
|
918
|
+
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
919
|
+
}
|
|
775
920
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
776
921
|
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
777
922
|
if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
|
|
778
923
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
924
|
+
if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
|
|
779
925
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
780
926
|
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
927
|
+
if (providerId === "minimax") return url.origin === "https://api.minimax.io";
|
|
928
|
+
if (providerId === "minimax-cn") return url.origin === "https://api.minimaxi.com";
|
|
929
|
+
if (providerId === "moonshotai") return url.origin === "https://api.moonshot.ai";
|
|
930
|
+
if (providerId === "moonshotai-cn") return url.origin === "https://api.moonshot.cn";
|
|
781
931
|
if (providerId === "xai") return url.origin === "https://api.x.ai";
|
|
782
932
|
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
783
933
|
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
@@ -813,6 +963,64 @@ function validatedXaiUserId(value: unknown): string {
|
|
|
813
963
|
return value;
|
|
814
964
|
}
|
|
815
965
|
|
|
966
|
+
function basetenBillingUsageUrl(windowAt: number): string {
|
|
967
|
+
const url = new URL(BASETEN_BILLING_USAGE_URL);
|
|
968
|
+
url.searchParams.set(
|
|
969
|
+
"start_date",
|
|
970
|
+
new Date(windowAt - BASETEN_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1_000).toISOString(),
|
|
971
|
+
);
|
|
972
|
+
url.searchParams.set("end_date", new Date(windowAt).toISOString());
|
|
973
|
+
return url.toString();
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async function queryMiniMaxUsage(
|
|
977
|
+
providerId: MiniMaxProviderId,
|
|
978
|
+
auth: ResolvedUsageAuth,
|
|
979
|
+
signal: AbortSignal,
|
|
980
|
+
timeoutMs: number,
|
|
981
|
+
guard: UsageRequestGuard | undefined,
|
|
982
|
+
): Promise<UsageReport> {
|
|
983
|
+
if (!guard) throw new Error("MiniMax usage requires request-boundary revalidation.");
|
|
984
|
+
const apiKey = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
985
|
+
if (!apiKey) throw new Error("MiniMax runtime API key was unavailable.");
|
|
986
|
+
const kind = miniMaxUsageKind(apiKey);
|
|
987
|
+
const path = kind === "account-balance" ? "/account/query_balance" : "/v1/token_plan/remains";
|
|
988
|
+
const startedAt = Date.now();
|
|
989
|
+
await guard();
|
|
990
|
+
const payload = (await fetchProviderJson(
|
|
991
|
+
`${MINIMAX_API_ROOTS[providerId]}${path}`,
|
|
992
|
+
auth,
|
|
993
|
+
signal,
|
|
994
|
+
remainingTimeout(timeoutMs, startedAt, "fetching MiniMax usage"),
|
|
995
|
+
"MiniMax usage endpoint",
|
|
996
|
+
{ redirect: "error" },
|
|
997
|
+
)) as MiniMaxUsagePayload;
|
|
998
|
+
await guard();
|
|
999
|
+
return normalizeMiniMaxUsagePayload(providerId, kind, payload, Date.now());
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function queryMoonshotBalance(
|
|
1003
|
+
providerId: "moonshotai" | "moonshotai-cn",
|
|
1004
|
+
auth: ResolvedUsageAuth,
|
|
1005
|
+
signal: AbortSignal,
|
|
1006
|
+
timeoutMs: number,
|
|
1007
|
+
guard: UsageRequestGuard | undefined,
|
|
1008
|
+
): Promise<UsageReport> {
|
|
1009
|
+
if (!guard) throw new Error("Moonshot AI balance requires request-boundary revalidation.");
|
|
1010
|
+
const startedAt = Date.now();
|
|
1011
|
+
await guard();
|
|
1012
|
+
const payload = (await fetchProviderJson(
|
|
1013
|
+
MOONSHOT_BALANCE_URLS[providerId],
|
|
1014
|
+
auth,
|
|
1015
|
+
signal,
|
|
1016
|
+
remainingTimeout(timeoutMs, startedAt, "fetching Moonshot AI balance"),
|
|
1017
|
+
"Moonshot AI balance endpoint",
|
|
1018
|
+
{ redirect: "error" },
|
|
1019
|
+
)) as MoonshotBalancePayload;
|
|
1020
|
+
await guard();
|
|
1021
|
+
return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
|
|
1022
|
+
}
|
|
1023
|
+
|
|
816
1024
|
function remainingTimeout(
|
|
817
1025
|
timeoutMs: number,
|
|
818
1026
|
startedAt: number,
|
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
|
};
|
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,17 @@ 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
|
+
].includes(adapter.id);
|
|
285
289
|
const requestContextChanged = () =>
|
|
286
290
|
expectedSessionGeneration !== sessionGeneration ||
|
|
287
291
|
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
@@ -345,7 +349,7 @@ export default function usageExtension(
|
|
|
345
349
|
const queryId = querySequence;
|
|
346
350
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
347
351
|
|
|
348
|
-
let
|
|
352
|
+
let retryableAuthChanged = false;
|
|
349
353
|
try {
|
|
350
354
|
const remainingMs = Math.max(1, deadlineAt - Date.now());
|
|
351
355
|
const guard = requiresRequestBoundaryGuard
|
|
@@ -359,9 +363,11 @@ export default function usageExtension(
|
|
|
359
363
|
);
|
|
360
364
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
361
365
|
if (revalidated?.fingerprint !== auth.fingerprint) {
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
throw new Error(
|
|
366
|
+
if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
|
|
367
|
+
retryableAuthChanged = true;
|
|
368
|
+
throw new Error(
|
|
369
|
+
`${adapter.displayName} runtime credential changed during the usage query.`,
|
|
370
|
+
);
|
|
365
371
|
}
|
|
366
372
|
throw abortError();
|
|
367
373
|
}
|
|
@@ -393,7 +399,7 @@ export default function usageExtension(
|
|
|
393
399
|
} catch (error) {
|
|
394
400
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
395
401
|
if (
|
|
396
|
-
|
|
402
|
+
retryableAuthChanged &&
|
|
397
403
|
authRetry === 0 &&
|
|
398
404
|
!signal.aborted &&
|
|
399
405
|
!requestContextChanged() &&
|