@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/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
- const payload = await fetchProviderJson(
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
- const payload = await fetchProviderJson(
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
- if (adapter.id !== "deepseek") modelAuth = await resolveCurrentModelAuth();
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
- // DeepSeek reads selected-model auth last so a rotation during provider-origin validation
346
- // cannot leave the earlier credential queued for the balance request.
347
- if (adapter.id === "deepseek") modelAuth = await resolveCurrentModelAuth();
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
- return ctx.modelRegistry.getProviderAuthStatus(providerId).configured;
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 candidateModels(ctx, providerId).length > 0;
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 zaiMonitorUrl(baseUrl: string | undefined): string {
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 `${new URL(base).origin}/api/monitor/usage/quota/limit`;
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 = ["deepseek", "fireworks", "xai"].includes(adapter.id);
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 deepSeekAuthChanged = false;
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 (adapter.id === "deepseek") {
363
- deepSeekAuthChanged = true;
364
- throw new Error("DeepSeek runtime credential changed during the balance query.");
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
- deepSeekAuthChanged &&
404
+ retryableAuthChanged &&
397
405
  authRetry === 0 &&
398
406
  !signal.aborted &&
399
407
  !requestContextChanged() &&