@bitkyc08/opencodex 2.10.2 → 2.11.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 +31 -0
- package/bin/ocx.mjs +10 -0
- package/gui/dist/assets/index-Bk-PN-70.css +1 -0
- package/gui/dist/assets/index-BynIEIV-.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -2
- package/src/adapters/cursor/effort-map.ts +11 -0
- package/src/adapters/cursor/live-transport.ts +11 -0
- package/src/adapters/cursor/native-exec-fs.ts +9 -6
- package/src/adapters/cursor/native-exec.ts +4 -2
- package/src/adapters/cursor/protobuf-events.ts +176 -4
- package/src/adapters/cursor/request-builder.ts +15 -4
- package/src/adapters/cursor/tool-definitions.ts +118 -2
- package/src/adapters/google.ts +15 -5
- package/src/adapters/openai-chat.ts +24 -2
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +9 -5
- package/src/chat/outbound.ts +4 -3
- package/src/claude/desktop-3p.ts +222 -2
- package/src/claude/outbound.ts +15 -6
- package/src/cli/account-api.ts +4 -0
- package/src/cli/account-extended.ts +112 -0
- package/src/cli/account.ts +23 -6
- package/src/cli/claude-desktop.ts +26 -3
- package/src/cli/config-command.ts +9 -0
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +277 -55
- package/src/cli/models.ts +5 -1
- package/src/cli/provider.ts +8 -2
- package/src/cli/ready.ts +301 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/clients/config-export.ts +12 -19
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/auth-api.ts +83 -0
- package/src/codex/auth-context.ts +5 -2
- package/src/codex/catalog/provider-fetch.ts +11 -0
- package/src/codex/catalog/sync.ts +23 -1
- package/src/codex/codex-write-lock.ts +16 -4
- package/src/codex/desired-state.ts +37 -4
- package/src/codex/history-job.ts +15 -5
- package/src/codex/history-provider.ts +31 -14
- package/src/codex/history-worker.ts +28 -4
- package/src/codex/inject-coordination.ts +13 -1
- package/src/codex/inject.ts +360 -66
- package/src/codex/internal/history-writer.ts +1 -1
- package/src/codex/native-main-lock-file.ts +5 -1
- package/src/codex/native-main-owner.ts +17 -3
- package/src/codex/native-profile-manager.ts +19 -0
- package/src/codex/native-profile-startup.ts +8 -0
- package/src/codex/native-residue.ts +140 -27
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/refresh.ts +7 -0
- package/src/codex/routing.ts +177 -36
- package/src/codex/subagent-model-fallback.ts +34 -4
- package/src/codex/sync.ts +61 -0
- package/src/codex/upstream-host-health.ts +329 -31
- package/src/combos/request.ts +2 -0
- package/src/config.ts +221 -2
- package/src/images/loop.ts +1 -1
- package/src/integrations/native/ownership-preflight.ts +39 -2
- package/src/lib/bun-stream-caps.ts +3 -3
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/windows-secret-acl.ts +141 -39
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +18 -2
- package/src/oauth/key-providers.ts +12 -0
- package/src/providers/derive.ts +54 -2
- package/src/providers/free-directory.ts +6 -5
- package/src/providers/model-discovery.ts +9 -3
- package/src/providers/quota.ts +592 -0
- package/src/providers/registry.ts +316 -13
- package/src/responses/parser.ts +26 -10
- package/src/responses/reasoning-replay-cache.ts +1 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +2 -1
- package/src/server/auth-cors.ts +44 -13
- package/src/server/chat-completions.ts +0 -4
- package/src/server/claude-messages.ts +73 -15
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/index.ts +328 -111
- package/src/server/lifecycle.ts +36 -0
- package/src/server/management/agent-settings-routes.ts +147 -56
- package/src/server/management/config-routes.ts +7 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/native-integration-routes.ts +199 -20
- package/src/server/management/provider-routes.ts +41 -0
- package/src/server/management/routing-profile-routes.ts +234 -5
- package/src/server/management/system-restart.ts +12 -10
- package/src/server/management/system-routes.ts +20 -0
- package/src/server/management-auth.ts +51 -3
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +129 -4
- package/src/server/readiness.ts +99 -0
- package/src/server/relay.ts +113 -97
- package/src/server/request-log.ts +10 -4
- package/src/server/responses/compact.ts +107 -12
- package/src/server/responses/core.ts +220 -39
- package/src/server/responses-item-id-repair.ts +22 -3
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +25 -14
- package/src/server/ws-bridge.ts +27 -22
- package/src/service-manager-probe.ts +520 -10
- package/src/service.ts +134 -2
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +74 -9
- package/src/types.ts +68 -2
- package/src/update/index.ts +12 -0
- package/src/update/job.ts +392 -18
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/usage/log.ts +1 -1
- package/src/vision/index.ts +77 -2
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/parse.ts +4 -1
- package/gui/dist/assets/index-BKVqyYqT.js +0 -70
- package/gui/dist/assets/index-Ca_3269W.css +0 -1
package/src/providers/quota.ts
CHANGED
|
@@ -34,6 +34,16 @@ const REQUEST_TIMEOUT_MS = 8_000;
|
|
|
34
34
|
const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
35
35
|
const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
|
|
36
36
|
const A6API_BASE_URL = "https://api.a6api.com";
|
|
37
|
+
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
38
|
+
const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
39
|
+
const CLINE_BASE_URL = "https://api.cline.bot";
|
|
40
|
+
const ZAI_BASE_URL = "https://api.z.ai";
|
|
41
|
+
const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
|
|
42
|
+
const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
|
|
43
|
+
const VENICE_BASE_URL = "https://api.venice.ai/api/v1";
|
|
44
|
+
const SYNTHETIC_BASE_URL = "https://api.synthetic.new/v2";
|
|
45
|
+
const DEEPINFRA_BASE_URL = "https://api.deepinfra.com";
|
|
46
|
+
const NEURALWATT_BASE_URL = "https://api.neuralwatt.com/v1";
|
|
37
47
|
/** Keep a failed probe's previous row at most this long before dropping it. */
|
|
38
48
|
const LAST_GOOD_MAX_AGE_MS = CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
|
|
39
49
|
const nativeMainReportGenerations = new WeakMap<ProviderQuotaReport, number>();
|
|
@@ -54,6 +64,15 @@ export interface ProviderQuotaWindow {
|
|
|
54
64
|
resetAt?: number;
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
export interface ProviderQuotaCreditsUsd {
|
|
68
|
+
used: number;
|
|
69
|
+
limit: number;
|
|
70
|
+
remaining: number;
|
|
71
|
+
percent: number;
|
|
72
|
+
expiresAt?: number;
|
|
73
|
+
unlimited?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
export interface ProviderQuota {
|
|
58
77
|
fiveHourPercent?: number;
|
|
59
78
|
fiveHourResetAt?: number;
|
|
@@ -62,6 +81,7 @@ export interface ProviderQuota {
|
|
|
62
81
|
monthlyPercent?: number;
|
|
63
82
|
monthlyResetAt?: number;
|
|
64
83
|
customWindows?: ProviderQuotaWindow[];
|
|
84
|
+
creditsUsd?: ProviderQuotaCreditsUsd;
|
|
65
85
|
updatedAt: number;
|
|
66
86
|
}
|
|
67
87
|
|
|
@@ -193,6 +213,8 @@ function hasQuotaRows(quota: ProviderQuota | null | undefined): quota is Provide
|
|
|
193
213
|
return typeof quota.fiveHourPercent === "number"
|
|
194
214
|
|| typeof quota.weeklyPercent === "number"
|
|
195
215
|
|| typeof quota.monthlyPercent === "number"
|
|
216
|
+
|| quota.creditsUsd?.unlimited === true
|
|
217
|
+
|| typeof quota.creditsUsd?.percent === "number"
|
|
196
218
|
|| !!quota.customWindows?.some(window => typeof window.percent === "number");
|
|
197
219
|
}
|
|
198
220
|
|
|
@@ -243,6 +265,54 @@ function isCanonicalA6apiBaseUrl(baseUrl: string): boolean {
|
|
|
243
265
|
return normalized === A6API_BASE_URL || normalized === `${A6API_BASE_URL}/v1`;
|
|
244
266
|
}
|
|
245
267
|
|
|
268
|
+
function isCanonicalOpenRouterBaseUrl(baseUrl: string): boolean {
|
|
269
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
270
|
+
return normalized === OPENROUTER_BASE_URL;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isCanonicalDeepSeekBaseUrl(baseUrl: string): boolean {
|
|
274
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
275
|
+
return normalized === DEEPSEEK_BASE_URL || normalized === `${DEEPSEEK_BASE_URL}/v1`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isCanonicalClineBaseUrl(baseUrl: string): boolean {
|
|
279
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
280
|
+
return normalized === CLINE_BASE_URL || normalized === `${CLINE_BASE_URL}/api/v1`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isCanonicalZaiBaseUrl(baseUrl: string): boolean {
|
|
284
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
285
|
+
return normalized === ZAI_BASE_URL || normalized === `${ZAI_BASE_URL}/api/coding/paas/v4`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function isCanonicalMinimaxBaseUrl(baseUrl: string): boolean {
|
|
289
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
290
|
+
return normalized === "https://api.minimax.io/v1" || normalized === "https://api.minimaxi.com/v1";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isCanonicalMoonshotBaseUrl(baseUrl: string): boolean {
|
|
294
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
295
|
+
return normalized === MOONSHOT_BASE_URL || normalized === "https://api.moonshot.cn/v1";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function isCanonicalVeniceBaseUrl(baseUrl: string): boolean {
|
|
299
|
+
return normalizedBaseUrl(baseUrl) === VENICE_BASE_URL;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function isCanonicalSyntheticBaseUrl(baseUrl: string): boolean {
|
|
303
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
304
|
+
return normalized === SYNTHETIC_BASE_URL || normalized === "https://api.synthetic.new/openai/v1";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isCanonicalDeepInfraBaseUrl(baseUrl: string): boolean {
|
|
308
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
309
|
+
return normalized === DEEPINFRA_BASE_URL || normalized === `${DEEPINFRA_BASE_URL}/v1/openai`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isCanonicalNeuralwattBaseUrl(baseUrl: string): boolean {
|
|
313
|
+
return normalizedBaseUrl(baseUrl) === NEURALWATT_BASE_URL;
|
|
314
|
+
}
|
|
315
|
+
|
|
246
316
|
function a6apiPayload(value: unknown): Record<string, unknown> | null {
|
|
247
317
|
const body = asRecord(value);
|
|
248
318
|
return asRecord(body?.data) ?? body;
|
|
@@ -282,6 +352,27 @@ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Pro
|
|
|
282
352
|
}
|
|
283
353
|
const subscription = a6apiPayload(await subscriptionResponse.json().catch(() => null));
|
|
284
354
|
const token = a6apiPayload(await tokenResponse.json().catch(() => null));
|
|
355
|
+
const unlimited = token?.unlimited_quota === true
|
|
356
|
+
|| token?.unlimited_quota === 1
|
|
357
|
+
|| token?.unlimited_quota === "true";
|
|
358
|
+
const normalizedExpiry = normalizeResetAt(token?.expires_at);
|
|
359
|
+
const expiry = normalizedExpiry && normalizedExpiry > 0
|
|
360
|
+
? { expiresAt: normalizedExpiry }
|
|
361
|
+
: {};
|
|
362
|
+
if (unlimited) {
|
|
363
|
+
return report(provider, "a6api:billing", {
|
|
364
|
+
creditsUsd: {
|
|
365
|
+
used: 0,
|
|
366
|
+
limit: 0,
|
|
367
|
+
remaining: 0,
|
|
368
|
+
percent: 0,
|
|
369
|
+
unlimited: true,
|
|
370
|
+
...expiry,
|
|
371
|
+
},
|
|
372
|
+
customWindows: [{ label: "Unlimited API credits", percent: 0 }],
|
|
373
|
+
updatedAt: Date.now(),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
285
376
|
const limitUsd = firstFinite(subscription, ["hard_limit_usd"]);
|
|
286
377
|
const grantedUnits = firstFinite(token, ["total_granted"]);
|
|
287
378
|
const usedUnits = firstFinite(token, ["total_used"]);
|
|
@@ -304,11 +395,482 @@ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Pro
|
|
|
304
395
|
if (percent === undefined) return TERMINAL_QUOTA_FAILURE;
|
|
305
396
|
const label = `API credits ($${remainingUsd.toFixed(2)} of $${limitUsd.toFixed(2)} remaining)`;
|
|
306
397
|
return report(provider, "a6api:billing", {
|
|
398
|
+
creditsUsd: {
|
|
399
|
+
used: usedUsd,
|
|
400
|
+
limit: limitUsd,
|
|
401
|
+
remaining: remainingUsd,
|
|
402
|
+
percent,
|
|
403
|
+
...expiry,
|
|
404
|
+
},
|
|
405
|
+
customWindows: [{ label, percent }],
|
|
406
|
+
updatedAt: Date.now(),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* OpenRouter `GET /api/v1/key` — the key's own credit balance and optional
|
|
412
|
+
* per-key spending cap. `limit` is the configured cap (absent = uncapped);
|
|
413
|
+
* `usage` is lifetime spend; `limit_remaining` is what is left of the cap.
|
|
414
|
+
* When no cap is set there is no hard limit to meter against, so no bar is
|
|
415
|
+
* produced — the provider falls back to its documented reference.
|
|
416
|
+
*/
|
|
417
|
+
async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
418
|
+
// Never send a configured API key to a lookalike host or through a redirect.
|
|
419
|
+
if (!isCanonicalOpenRouterBaseUrl(config.baseUrl)) return null;
|
|
420
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
421
|
+
if (!apiKey) return null;
|
|
422
|
+
const response = await fetch(`${OPENROUTER_BASE_URL}/key`, {
|
|
423
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
424
|
+
redirect: "error",
|
|
425
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
426
|
+
});
|
|
427
|
+
if (!response.ok) {
|
|
428
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
429
|
+
? TERMINAL_QUOTA_FAILURE
|
|
430
|
+
: null;
|
|
431
|
+
}
|
|
432
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
433
|
+
const data = asRecord(body?.data) ?? body;
|
|
434
|
+
if (!data) return null;
|
|
435
|
+
const limit = toFiniteNumber(data.limit);
|
|
436
|
+
const limitRemaining = toFiniteNumber(data.limit_remaining);
|
|
437
|
+
const usage = toFiniteNumber(data.usage);
|
|
438
|
+
// A successful no-cap response is a DELIBERATE change, not a transient
|
|
439
|
+
// failure: the old capped row must be dropped, not preserved as last-good.
|
|
440
|
+
if (limit === undefined || limit <= 0) return TERMINAL_QUOTA_FAILURE;
|
|
441
|
+
// Prefer the authoritative remaining-cap value when present: `usage` is
|
|
442
|
+
// lifetime accumulated spend and overstates a reset or re-capped key.
|
|
443
|
+
const used = limitRemaining !== undefined
|
|
444
|
+
? Math.max(0, limit - limitRemaining)
|
|
445
|
+
: usage !== undefined && usage >= 0 ? usage : undefined;
|
|
446
|
+
if (used === undefined) return null;
|
|
447
|
+
const percent = normalizePercent((used / limit) * 100);
|
|
448
|
+
if (percent === undefined) return null;
|
|
449
|
+
const remaining = Math.max(0, limit - used);
|
|
450
|
+
const label = `API credits ($${remaining.toFixed(2)} of $${limit.toFixed(2)} remaining)`;
|
|
451
|
+
return report(provider, "openrouter:key-info", {
|
|
452
|
+
customWindows: [{ label, percent }],
|
|
453
|
+
updatedAt: Date.now(),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* DeepSeek `GET /user/balance` — the account's granted + topped-up credit
|
|
459
|
+
* balance. The payload places `total_balance` / `granted_balance` inside
|
|
460
|
+
* entries of `balance_infos` (one row per currency); the row for the account's
|
|
461
|
+
* currency is selected by preference. `granted_balance` is a CURRENT balance
|
|
462
|
+
* component, not the original grant ceiling, so no consumed percentage is
|
|
463
|
+
* fabricated — the balance is reported as a balance-only window.
|
|
464
|
+
*/
|
|
465
|
+
async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
466
|
+
if (!isCanonicalDeepSeekBaseUrl(config.baseUrl)) return null;
|
|
467
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
468
|
+
if (!apiKey) return null;
|
|
469
|
+
const response = await fetch(`${DEEPSEEK_BASE_URL}/user/balance`, {
|
|
470
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
471
|
+
redirect: "error",
|
|
472
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
473
|
+
});
|
|
474
|
+
if (!response.ok) {
|
|
475
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
476
|
+
? TERMINAL_QUOTA_FAILURE
|
|
477
|
+
: null;
|
|
478
|
+
}
|
|
479
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
480
|
+
// The payload nests balances under `balance_infos` rows keyed by currency;
|
|
481
|
+
// prefer a USD row, then CNY, then the first row that parses.
|
|
482
|
+
const infos = Array.isArray(body?.balance_infos) ? body.balance_infos as unknown[] : null;
|
|
483
|
+
const rows = infos
|
|
484
|
+
? infos.map((raw): Record<string, unknown> | null => asRecord(raw)).filter((r): r is Record<string, unknown> => r !== null)
|
|
485
|
+
: [];
|
|
486
|
+
const pick = (currency: string): Record<string, unknown> | null =>
|
|
487
|
+
rows.find(row => String(row.currency ?? "").toUpperCase() === currency) ?? null;
|
|
488
|
+
const preferred = pick("USD") ?? pick("CNY") ?? rows[0] ?? null;
|
|
489
|
+
if (!preferred) return null;
|
|
490
|
+
const totalBalance = toFiniteNumber(preferred.total_balance);
|
|
491
|
+
const grantedBalance = toFiniteNumber(preferred.granted_balance);
|
|
492
|
+
const toppedUp = toFiniteNumber(preferred.topped_up_balance);
|
|
493
|
+
const balance = totalBalance ?? grantedBalance ?? toppedUp;
|
|
494
|
+
if (balance === undefined || balance < 0) return null;
|
|
495
|
+
const label = grantedBalance !== undefined && grantedBalance > 0
|
|
496
|
+
? `API balance ($${balance.toFixed(2)} total, $${grantedBalance.toFixed(2)} granted)`
|
|
497
|
+
: `API balance ($${balance.toFixed(2)})`;
|
|
498
|
+
return report(provider, "deepseek:balance", {
|
|
499
|
+
customWindows: [{ label, percent: 0 }],
|
|
500
|
+
updatedAt: Date.now(),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* ClinePass `GET /api/v1/users/me/plan/usage-limits` — the subscription's
|
|
506
|
+
* rolling five-hour, weekly, and monthly utilization, matching the existing
|
|
507
|
+
* ProviderQuota windows directly. The endpoint 404s (or returns a null plan)
|
|
508
|
+
* for accounts without an active ClinePass, which is a no-report, not an error.
|
|
509
|
+
*/
|
|
510
|
+
async function fetchClineQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
511
|
+
if (!isCanonicalClineBaseUrl(config.baseUrl)) return null;
|
|
512
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
513
|
+
if (!apiKey) return null;
|
|
514
|
+
const response = await fetch(`${CLINE_BASE_URL}/api/v1/users/me/plan/usage-limits`, {
|
|
515
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
516
|
+
redirect: "error",
|
|
517
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
518
|
+
});
|
|
519
|
+
if (!response.ok) {
|
|
520
|
+
// 404 = no active plan; a plain "no plan" is a no-report, everything else
|
|
521
|
+
// 4xx (except 408/429) is a credential/contract problem.
|
|
522
|
+
if (response.status === 404) return null;
|
|
523
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
524
|
+
? TERMINAL_QUOTA_FAILURE
|
|
525
|
+
: null;
|
|
526
|
+
}
|
|
527
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
528
|
+
const data = asRecord(body?.data) ?? body;
|
|
529
|
+
const limits = Array.isArray(data?.limits) ? data.limits : null;
|
|
530
|
+
if (!limits) return null;
|
|
531
|
+
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
532
|
+
let windows = 0;
|
|
533
|
+
for (const raw of limits) {
|
|
534
|
+
const row = asRecord(raw);
|
|
535
|
+
if (!row) continue;
|
|
536
|
+
const percent = normalizePercent(row.percentUsed);
|
|
537
|
+
if (percent === undefined) continue;
|
|
538
|
+
const resetAt = normalizeResetAt(row.resetsAt);
|
|
539
|
+
if (row.type === "five_hour") {
|
|
540
|
+
quota.fiveHourPercent = percent;
|
|
541
|
+
if (resetAt !== undefined) quota.fiveHourResetAt = resetAt;
|
|
542
|
+
windows += 1;
|
|
543
|
+
} else if (row.type === "weekly") {
|
|
544
|
+
quota.weeklyPercent = percent;
|
|
545
|
+
if (resetAt !== undefined) quota.weeklyResetAt = resetAt;
|
|
546
|
+
windows += 1;
|
|
547
|
+
} else if (row.type === "monthly") {
|
|
548
|
+
quota.monthlyPercent = percent;
|
|
549
|
+
if (resetAt !== undefined) quota.monthlyResetAt = resetAt;
|
|
550
|
+
windows += 1;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return windows > 0 ? report(provider, "cline:plan-usage-limits", quota) : null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Z.AI GLM Coding Plan `GET /api/monitor/usage/quota/limit` — the coding-plan
|
|
558
|
+
* subscription's 5-hour token cycle, weekly quota, and monthly MCP usage.
|
|
559
|
+
* Authenticates with the API key as a Bearer token per Z.AI's API reference.
|
|
560
|
+
*/
|
|
561
|
+
async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
562
|
+
if (!isCanonicalZaiBaseUrl(config.baseUrl)) return null;
|
|
563
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
564
|
+
if (!apiKey) return null;
|
|
565
|
+
const response = await fetch(`${ZAI_BASE_URL}/api/monitor/usage/quota/limit`, {
|
|
566
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
567
|
+
redirect: "error",
|
|
568
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
569
|
+
});
|
|
570
|
+
if (!response.ok) {
|
|
571
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
572
|
+
? TERMINAL_QUOTA_FAILURE
|
|
573
|
+
: null;
|
|
574
|
+
}
|
|
575
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
576
|
+
if (!body || body.success === false) return null;
|
|
577
|
+
const data = asRecord(body.data) ?? body;
|
|
578
|
+
// The plugin renders a 5h token window, a weekly window, and a monthly MCP
|
|
579
|
+
// window. Look for percent fields with window identifiers.
|
|
580
|
+
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
581
|
+
let windows = 0;
|
|
582
|
+
const percentAt = (key: string): number | undefined => {
|
|
583
|
+
const value = normalizePercent(data?.[key]);
|
|
584
|
+
if (value !== undefined) return value;
|
|
585
|
+
const nested = asRecord(data?.quota);
|
|
586
|
+
return nested ? normalizePercent(nested[key]) : undefined;
|
|
587
|
+
};
|
|
588
|
+
const fiveHour = percentAt("fiveHourPercent") ?? percentAt("fiveHourUsage") ?? percentAt("fiveHourUsed");
|
|
589
|
+
const weekly = percentAt("weeklyPercent") ?? percentAt("weeklyUsage") ?? percentAt("weeklyUsed");
|
|
590
|
+
const monthly = percentAt("monthlyPercent") ?? percentAt("mcpPercent") ?? percentAt("monthlyMCPUsage");
|
|
591
|
+
if (fiveHour !== undefined) {
|
|
592
|
+
quota.fiveHourPercent = fiveHour;
|
|
593
|
+
windows += 1;
|
|
594
|
+
}
|
|
595
|
+
if (weekly !== undefined) {
|
|
596
|
+
quota.weeklyPercent = weekly;
|
|
597
|
+
windows += 1;
|
|
598
|
+
}
|
|
599
|
+
if (monthly !== undefined) {
|
|
600
|
+
quota.monthlyPercent = monthly;
|
|
601
|
+
windows += 1;
|
|
602
|
+
}
|
|
603
|
+
return windows > 0 ? report(provider, "zai:quota-limit", quota) : null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* MiniMax Token Plan `GET /v1/token_plan/remains` — the subscription's
|
|
608
|
+
* remaining quota as a countdown-time value (ms). The endpoint does not expose
|
|
609
|
+
* the plan's total duration, so no percentage is fabricated from a presumed
|
|
610
|
+
* window: the remaining time is reported as a duration-only window. When the
|
|
611
|
+
* API supplies a total (`total_time` / `plan_duration_ms`), a consumed share
|
|
612
|
+
* is derived from it. Region selects the host: `minimax` → www.minimax.io,
|
|
613
|
+
* `minimax-cn` → api.minimaxi.com.
|
|
614
|
+
*/
|
|
615
|
+
async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
616
|
+
if (!isCanonicalMinimaxBaseUrl(config.baseUrl)) return null;
|
|
617
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
618
|
+
if (!apiKey) return null;
|
|
619
|
+
const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
|
|
620
|
+
const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
|
|
621
|
+
const response = await fetch(remainsUrl, {
|
|
622
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
623
|
+
redirect: "error",
|
|
624
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
625
|
+
});
|
|
626
|
+
if (!response.ok) {
|
|
627
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
628
|
+
? TERMINAL_QUOTA_FAILURE
|
|
629
|
+
: null;
|
|
630
|
+
}
|
|
631
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
632
|
+
if (!body || body.success === false) return null;
|
|
633
|
+
const data = asRecord(body.data) ?? body;
|
|
634
|
+
const remainsMs = toFiniteNumber(data.remains_time ?? data.remainsTime);
|
|
635
|
+
if (remainsMs === undefined || remainsMs < 0) return null;
|
|
636
|
+
const hours = Math.floor(remainsMs / 3_600_000);
|
|
637
|
+
const label = `Token Plan remaining (${hours}h)`;
|
|
638
|
+
// Only derive a consumed share when the API actually reports the plan total;
|
|
639
|
+
// a presumed window (e.g. 30 days) would fabricate utilization. A valid
|
|
640
|
+
// response that omits the total after a prior refresh had it is a DELIBERATE
|
|
641
|
+
// contract change — the old row must be dropped (terminal), not preserved as
|
|
642
|
+
// a transient last-good.
|
|
643
|
+
const totalMs = toFiniteNumber(data.total_time ?? data.plan_duration_ms ?? data.total_duration_ms);
|
|
644
|
+
if (totalMs === undefined || totalMs <= 0) return TERMINAL_QUOTA_FAILURE;
|
|
645
|
+
const consumed = Math.max(0, totalMs - remainsMs);
|
|
646
|
+
const percent = normalizePercent((consumed / totalMs) * 100);
|
|
647
|
+
if (percent === undefined) return null;
|
|
648
|
+
return report(provider, "minimax:token-plan-remains", {
|
|
307
649
|
customWindows: [{ label, percent }],
|
|
308
650
|
updatedAt: Date.now(),
|
|
309
651
|
});
|
|
310
652
|
}
|
|
311
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Moonshot/Kimi `GET /v1/users/me/balance` — the account's available balance
|
|
656
|
+
* (voucher + cash). Renders a single balance window against the sum of
|
|
657
|
+
* voucher + cash when positive (there is no per-window rate limit to meter).
|
|
658
|
+
*/
|
|
659
|
+
async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
660
|
+
if (!isCanonicalMoonshotBaseUrl(config.baseUrl)) return null;
|
|
661
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
662
|
+
if (!apiKey) return null;
|
|
663
|
+
const host = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.moonshot.cn") ? "https://api.moonshot.cn/v1" : MOONSHOT_BASE_URL;
|
|
664
|
+
const response = await fetch(`${host}/users/me/balance`, {
|
|
665
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
666
|
+
redirect: "error",
|
|
667
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
668
|
+
});
|
|
669
|
+
if (!response.ok) {
|
|
670
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
671
|
+
? TERMINAL_QUOTA_FAILURE
|
|
672
|
+
: null;
|
|
673
|
+
}
|
|
674
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
675
|
+
const data = asRecord(body?.data) ?? body;
|
|
676
|
+
if (!data) return null;
|
|
677
|
+
const available = toFiniteNumber(data.available_balance);
|
|
678
|
+
const voucher = toFiniteNumber(data.voucher_balance);
|
|
679
|
+
const cash = toFiniteNumber(data.cash_balance);
|
|
680
|
+
if (available === undefined || available < 0) return null;
|
|
681
|
+
// Moonshot exposes no per-window quota ceiling, only a balance — report it
|
|
682
|
+
// as a balance-only window (percent 0) rather than a fabricated utilization.
|
|
683
|
+
const label = voucher !== undefined && cash !== undefined
|
|
684
|
+
? `Balance ($${available.toFixed(2)} available, $${voucher.toFixed(2)} voucher)`
|
|
685
|
+
: `Balance ($${available.toFixed(2)} available)`;
|
|
686
|
+
return report(provider, "moonshot:balance", {
|
|
687
|
+
customWindows: [{ label, percent: 0 }],
|
|
688
|
+
updatedAt: Date.now(),
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Venice `GET /api/v1/billing/balance` — DIEM (native credits) or USD balance.
|
|
694
|
+
* Shows the remaining balance; epoch allocation progress when present.
|
|
695
|
+
*/
|
|
696
|
+
async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
697
|
+
if (!isCanonicalVeniceBaseUrl(config.baseUrl)) return null;
|
|
698
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
699
|
+
if (!apiKey) return null;
|
|
700
|
+
const response = await fetch(`${VENICE_BASE_URL}/billing/balance`, {
|
|
701
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
702
|
+
redirect: "error",
|
|
703
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
704
|
+
});
|
|
705
|
+
if (!response.ok) {
|
|
706
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
707
|
+
? TERMINAL_QUOTA_FAILURE
|
|
708
|
+
: null;
|
|
709
|
+
}
|
|
710
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
711
|
+
const data = asRecord(body?.data) ?? body;
|
|
712
|
+
if (!data) return null;
|
|
713
|
+
const diemBalance = toFiniteNumber(data.balance);
|
|
714
|
+
const usdBalance = toFiniteNumber(data.balance_usd);
|
|
715
|
+
const epochUsed = toFiniteNumber(data.diem_epoch_used);
|
|
716
|
+
const epochAllocated = toFiniteNumber(data.diem_epoch_allocated);
|
|
717
|
+
if (diemBalance === undefined && usdBalance === undefined) return null;
|
|
718
|
+
const label = diemBalance !== undefined
|
|
719
|
+
? `DIEM balance (${Math.round(diemBalance)})`
|
|
720
|
+
: `USD balance ($${usdBalance?.toFixed(2) ?? "?"})`;
|
|
721
|
+
if (epochAllocated !== undefined && epochAllocated > 0 && epochUsed !== undefined) {
|
|
722
|
+
const percent = normalizePercent((epochUsed / epochAllocated) * 100);
|
|
723
|
+
if (percent === undefined) return null;
|
|
724
|
+
return report(provider, "venice:billing-balance", {
|
|
725
|
+
customWindows: [{ label, percent }],
|
|
726
|
+
updatedAt: Date.now(),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
return report(provider, "venice:billing-balance", {
|
|
730
|
+
customWindows: [{ label, percent: 0 }],
|
|
731
|
+
updatedAt: Date.now(),
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Synthetic `GET /v2/quotas` — the known quota lanes (rolling 5-hour,
|
|
737
|
+
* weekly token, search-hourly) mapped onto the quota windows.
|
|
738
|
+
*/
|
|
739
|
+
async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
740
|
+
if (!isCanonicalSyntheticBaseUrl(config.baseUrl)) return null;
|
|
741
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
742
|
+
if (!apiKey) return null;
|
|
743
|
+
const response = await fetch(`${SYNTHETIC_BASE_URL}/quotas`, {
|
|
744
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
745
|
+
redirect: "error",
|
|
746
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
747
|
+
});
|
|
748
|
+
if (!response.ok) {
|
|
749
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
750
|
+
? TERMINAL_QUOTA_FAILURE
|
|
751
|
+
: null;
|
|
752
|
+
}
|
|
753
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
754
|
+
const data = asRecord(body?.data) ?? body;
|
|
755
|
+
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
756
|
+
let windows = 0;
|
|
757
|
+
const percentAt = (key: string): number | undefined => {
|
|
758
|
+
const value = normalizePercent(data?.[key]);
|
|
759
|
+
if (value !== undefined) return value;
|
|
760
|
+
const nested = asRecord(data?.quota) ?? asRecord(data?.quotas);
|
|
761
|
+
return nested ? normalizePercent(nested[key]) : undefined;
|
|
762
|
+
};
|
|
763
|
+
const fiveHour = percentAt("rollingFiveHourLimit");
|
|
764
|
+
const weekly = percentAt("weeklyTokenLimit");
|
|
765
|
+
if (fiveHour !== undefined) {
|
|
766
|
+
quota.fiveHourPercent = fiveHour;
|
|
767
|
+
windows += 1;
|
|
768
|
+
}
|
|
769
|
+
if (weekly !== undefined) {
|
|
770
|
+
quota.weeklyPercent = weekly;
|
|
771
|
+
windows += 1;
|
|
772
|
+
}
|
|
773
|
+
const search = asRecord(data?.search);
|
|
774
|
+
const searchHourly = search ? normalizePercent(search.hourly) : undefined;
|
|
775
|
+
if (searchHourly !== undefined) {
|
|
776
|
+
quota.customWindows = [...(quota.customWindows ?? []), { label: "Search hourly", percent: searchHourly }];
|
|
777
|
+
windows += 1;
|
|
778
|
+
}
|
|
779
|
+
return windows > 0 ? report(provider, "synthetic:quotas", quota) : null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* DeepInfra `GET /payment/checklist?compute_owed=true` — prepaid balance,
|
|
784
|
+
* recent spend, spending limit, and suspension state. Renders a balance
|
|
785
|
+
* window (prepaid funds are a negative `stripe_balance` → positive available).
|
|
786
|
+
*/
|
|
787
|
+
async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
788
|
+
if (!isCanonicalDeepInfraBaseUrl(config.baseUrl)) return null;
|
|
789
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
790
|
+
if (!apiKey) return null;
|
|
791
|
+
const response = await fetch(`${DEEPINFRA_BASE_URL}/payment/checklist?compute_owed=true`, {
|
|
792
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
793
|
+
redirect: "error",
|
|
794
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
795
|
+
});
|
|
796
|
+
if (!response.ok) {
|
|
797
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
798
|
+
? TERMINAL_QUOTA_FAILURE
|
|
799
|
+
: null;
|
|
800
|
+
}
|
|
801
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
802
|
+
const data = asRecord(body?.data) ?? body;
|
|
803
|
+
if (!data) return null;
|
|
804
|
+
const stripeBalance = toFiniteNumber(data.stripe_balance);
|
|
805
|
+
const spendLimit = toFiniteNumber(data.spending_limit);
|
|
806
|
+
const total = toFiniteNumber(data.total_amount_due);
|
|
807
|
+
if (stripeBalance === undefined) return null;
|
|
808
|
+
// Prepaid funds are negative; a positive value is money owed.
|
|
809
|
+
const available = stripeBalance < 0 ? -stripeBalance : 0;
|
|
810
|
+
if (spendLimit !== undefined && spendLimit > 0) {
|
|
811
|
+
const spent = total !== undefined && total > 0 ? total : Math.max(0, spendLimit - available);
|
|
812
|
+
const percent = normalizePercent((spent / spendLimit) * 100);
|
|
813
|
+
if (percent === undefined) return null;
|
|
814
|
+
return report(provider, "deepinfra:billing-checklist", {
|
|
815
|
+
customWindows: [{ label: `Billing cycle spend ($${spent.toFixed(2)} of $${spendLimit.toFixed(2)})`, percent }],
|
|
816
|
+
updatedAt: Date.now(),
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return report(provider, "deepinfra:billing-checklist", {
|
|
820
|
+
customWindows: [{ label: `Prepaid balance ($${available.toFixed(2)})`, percent: 0 }],
|
|
821
|
+
updatedAt: Date.now(),
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Neuralwatt `GET /v1/quota` — subscription kWh usage (primary window) and
|
|
827
|
+
* prepaid USD credit balance (secondary).
|
|
828
|
+
*/
|
|
829
|
+
async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
830
|
+
if (!isCanonicalNeuralwattBaseUrl(config.baseUrl)) return null;
|
|
831
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
832
|
+
if (!apiKey) return null;
|
|
833
|
+
const response = await fetch(`${NEURALWATT_BASE_URL}/quota`, {
|
|
834
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
835
|
+
redirect: "error",
|
|
836
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
837
|
+
});
|
|
838
|
+
if (!response.ok) {
|
|
839
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
840
|
+
? TERMINAL_QUOTA_FAILURE
|
|
841
|
+
: null;
|
|
842
|
+
}
|
|
843
|
+
const body = asRecord(await response.json().catch(() => null));
|
|
844
|
+
const data = asRecord(body?.data) ?? body;
|
|
845
|
+
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
846
|
+
let windows = 0;
|
|
847
|
+
const subscription = asRecord(data?.subscription);
|
|
848
|
+
const kwhUsed = subscription ? toFiniteNumber(subscription.kwh_used) : undefined;
|
|
849
|
+
const kwhIncluded = subscription ? toFiniteNumber(subscription.kwh_included) : undefined;
|
|
850
|
+
if (kwhUsed !== undefined && kwhIncluded !== undefined && kwhIncluded > 0) {
|
|
851
|
+
const percent = normalizePercent((kwhUsed / kwhIncluded) * 100);
|
|
852
|
+
if (percent !== undefined) {
|
|
853
|
+
quota.fiveHourPercent = percent;
|
|
854
|
+
const periodEnd = subscription ? normalizeResetAt(subscription.current_period_end) : undefined;
|
|
855
|
+
if (periodEnd !== undefined) quota.fiveHourResetAt = periodEnd;
|
|
856
|
+
windows += 1;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const balance = asRecord(data?.balance);
|
|
860
|
+
const totalCredits = balance ? toFiniteNumber(balance.total_credits_usd) : undefined;
|
|
861
|
+
const remainingCredits = balance ? toFiniteNumber(balance.credits_remaining_usd) : undefined;
|
|
862
|
+
if (totalCredits !== undefined && totalCredits > 0 && remainingCredits !== undefined) {
|
|
863
|
+
// Utilization is CONSUMED credits, not the remaining share.
|
|
864
|
+
const used = Math.max(0, totalCredits - remainingCredits);
|
|
865
|
+
const percent = normalizePercent((used / totalCredits) * 100);
|
|
866
|
+
if (percent !== undefined) {
|
|
867
|
+
quota.customWindows = [...(quota.customWindows ?? []), { label: "Prepaid credits", percent }];
|
|
868
|
+
windows += 1;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return windows > 0 ? report(provider, "neuralwatt:quota", quota) : null;
|
|
872
|
+
}
|
|
873
|
+
|
|
312
874
|
function report(
|
|
313
875
|
provider: string,
|
|
314
876
|
source: string,
|
|
@@ -1179,6 +1741,36 @@ async function maybeFetchProviderQuota(
|
|
|
1179
1741
|
if ((provider.authMode ?? "key") === "key" && isCanonicalA6apiBaseUrl(provider.baseUrl)) {
|
|
1180
1742
|
return fetchA6apiQuota(name, provider);
|
|
1181
1743
|
}
|
|
1744
|
+
if ((provider.authMode ?? "key") === "key" && name === "openrouter") {
|
|
1745
|
+
return fetchOpenRouterQuota(name, provider);
|
|
1746
|
+
}
|
|
1747
|
+
if ((provider.authMode ?? "key") === "key" && name === "deepseek") {
|
|
1748
|
+
return fetchDeepSeekQuota(name, provider);
|
|
1749
|
+
}
|
|
1750
|
+
if ((provider.authMode ?? "key") === "key" && name === "cline-pass") {
|
|
1751
|
+
return fetchClineQuota(name, provider);
|
|
1752
|
+
}
|
|
1753
|
+
if ((provider.authMode ?? "key") === "key" && name === "zai") {
|
|
1754
|
+
return fetchZaiQuota(name, provider);
|
|
1755
|
+
}
|
|
1756
|
+
if ((provider.authMode ?? "key") === "key" && (name === "minimax" || name === "minimax-cn")) {
|
|
1757
|
+
return fetchMinimaxQuota(name, provider);
|
|
1758
|
+
}
|
|
1759
|
+
if ((provider.authMode ?? "key") === "key" && name === "moonshot") {
|
|
1760
|
+
return fetchMoonshotQuota(name, provider);
|
|
1761
|
+
}
|
|
1762
|
+
if ((provider.authMode ?? "key") === "key" && name === "venice") {
|
|
1763
|
+
return fetchVeniceQuota(name, provider);
|
|
1764
|
+
}
|
|
1765
|
+
if ((provider.authMode ?? "key") === "key" && name === "synthetic") {
|
|
1766
|
+
return fetchSyntheticQuota(name, provider);
|
|
1767
|
+
}
|
|
1768
|
+
if ((provider.authMode ?? "key") === "key" && name === "deepinfra") {
|
|
1769
|
+
return fetchDeepInfraQuota(name, provider);
|
|
1770
|
+
}
|
|
1771
|
+
if ((provider.authMode ?? "key") === "key" && name === "neuralwatt") {
|
|
1772
|
+
return fetchNeuralwattQuota(name, provider);
|
|
1773
|
+
}
|
|
1182
1774
|
return null;
|
|
1183
1775
|
} catch {
|
|
1184
1776
|
return null;
|