opencode-auth-load-balancer 0.9.0 → 0.10.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 +12 -1
- package/dist/index.js +235 -44
- package/dist/providers/anthropic/usage.d.ts +2 -2
- package/dist/providers/kimi/usage.d.ts +1 -1
- package/dist/providers/openai/usage.d.ts +6 -3
- package/dist/providers/types.d.ts +5 -0
- package/dist/providers/usage-http.d.ts +5 -8
- package/dist/scheduler/score-core.d.ts +25 -6
- package/dist/scheduler/select.d.ts +7 -4
- package/dist/status.d.ts +2 -0
- package/dist/types.d.ts +19 -0
- package/dist/usage-merge.d.ts +5 -1
- package/dist/usage-poll.d.ts +11 -0
- package/dist/usage-refresh.d.ts +1 -12
- package/dist/v1.js +235 -44
- package/dist/v2-entry.js +235 -44
- package/package.json +1 -1
- package/tui/auth-load-balancer-scoring.ts +50 -11
- package/tui/auth-load-balancer-tui.logic.ts +49 -1
- package/tui/auth-load-balancer-tui.view.tsx +7 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Selection is **not** round-robin. It is weighted primarily by **weekly** usage,
|
|
|
16
16
|
- **Self-updating Claude Code version** — Anthropic gates new models on the client version the request claims, so a pinned version stops working the day a model ships (`Claude Code 2.1.87 does not support this model; version 2.1.251 or newer is required`). The plugin discovers the current version from npm instead, caches it for a day, and never regresses below a version known to work. See [Claude Code version](#claude-code-version).
|
|
17
17
|
- **Session affinity** — a conversation stays pinned to one account so you keep its prompt cache and don't re-send context on every turn.
|
|
18
18
|
- **Proactive migration** — leaves an account at a configurable soft threshold (~95%) instead of waiting for a hard 100% wall (which can break in-flight subagents).
|
|
19
|
+
- **Codex credits** — a Codex account whose plan usage is exhausted keeps serving on its ChatGPT credits, as the Codex CLI and desktop app do. Credits cost money, so plan quota on any account is spent first, and a session running on credits moves back as soon as an account has plan headroom again. The dashboards show what is left beside the exhausted state.
|
|
19
20
|
- **Single-use refresh-token safety** — per-account singleflight refresh; rotated tokens are persisted immediately.
|
|
20
21
|
- **Visibility** — toasts for account switches, model fallback, pending/recovery, and restart restoration; an on-demand `auth_lb_status` tool; and a `bun run status` CLI dashboard.
|
|
21
22
|
- **Gist sync of credentials and OAuth logins** — machines share their Claude tokens, Kimi API keys and (unless switched off) their Claude / Codex / Kimi OAuth logins, encrypted, through a secret GitHub gist, both ways: every machine that follows the link downloads, and one with a GitHub token also uploads its own. See [Sync credentials and OAuth logins between machines](#sync-credentials-and-oauth-logins-between-machines-github-gist).
|
|
@@ -287,6 +288,16 @@ Pending turns
|
|
|
287
288
|
|
|
288
289
|
`▶` marks the in-use account; `#` is the rank the scheduler would pick next.
|
|
289
290
|
|
|
291
|
+
An exhausted Codex account that still has ChatGPT credits stays selectable, ranked after every account with plan headroom, and its state shows the balance: `ready · credits 42.5` here, `full · credits 42.5` in the TUI sidebar. `credits unlimited` and `credits available` (no amount reported) are usable too; `no credits` means the account waits for its plan to reset. Balances come from the `/wham/usage` endpoint and from the `x-codex-credits-*` headers of every Codex response. A turn waiting for quota rechecks at least every five minutes, so credits that appear meanwhile (reported for the first time, or bought) resume it without waiting for the plan reset.
|
|
292
|
+
|
|
293
|
+
While OpenCode is running, stale usage is checked every five minutes even without inference traffic or a status-tool call. Periodic checks share a machine-wide lock and a next-attempt timestamp in `auth-load-balancer.json.usage-poll.json`, so multiple windows do not multiply periodic requests, including failed attempts. The file contains scheduling metadata only, never credentials.
|
|
294
|
+
|
|
295
|
+
A fresh usage measurement during re-login or a periodic check clears an obsolete, long quota cooldown when both usage windows have headroom. A successful re-login alone is not enough: exhausted or malformed usage, authentication/transient backoffs, and a newer rejection received during a usage check retain their cooldowns. Provider-side resets are picked up on the next successful check; a failed endpoint request never assumes recovery.
|
|
296
|
+
|
|
297
|
+
If a usage endpoint rejects an OAuth access token with HTTP 401, the poll uses the same refresh lock and rejected-token recovery as inference, then retries the usage lookup once, even when the advertised access expiry is still in the future. A concurrently refreshed credential is adopted instead of spending its predecessor again. Static API keys are never sent to OAuth refresh, and 403/429 failures do not trigger authentication retries.
|
|
298
|
+
|
|
299
|
+
A 403 caused by a missing `user:profile` scope requires a fresh OAuth login with browser approval. Restarting OpenCode or refreshing the same grant cannot restore that missing permission; an authoritative usage result is still required before clearing the old quota cooldown.
|
|
300
|
+
|
|
290
301
|
### Persistent bottom status bar (TUI)
|
|
291
302
|
|
|
292
303
|
A SolidJS TUI plugin renders a persistent bottom status bar (opencode's always-visible `app_bottom` slot) showing both the in-use account(s) per provider (polled from the pool, e.g. `Claude anthropic-1 58% · 5h 12%`) **and** the current session usage — tokens, % of the model context window, and `$` cost — computed the same way opencode's own footer does. It is **four files**:
|
|
@@ -335,7 +346,7 @@ All knobs are environment variables with sane defaults.
|
|
|
335
346
|
| `OPENCODE_AUTH_LB_HOURLY_INFLUENCE` | `0.5` | How much 5h headroom modulates the weekly-urgency score (0–1). |
|
|
336
347
|
| `OPENCODE_AUTH_LB_MIN_RESET_MS` | `300000` | Floor on time-to-reset (caps urgency near a reset). |
|
|
337
348
|
| `OPENCODE_AUTH_LB_WEEK_WINDOW_MS` | `604800000` | Baseline horizon used when a weekly reset time is unknown. |
|
|
338
|
-
| `OPENCODE_AUTH_LB_EXHAUSTED_AT` | `0.999` | Hard exhaustion: at/above this utilization an account is excluded. |
|
|
349
|
+
| `OPENCODE_AUTH_LB_EXHAUSTED_AT` | `0.999` | Hard exhaustion: at/above this utilization an account is excluded, unless Codex credits keep it serving. |
|
|
339
350
|
| `OPENCODE_AUTH_LB_MIGRATE_AT` | `0.95` | Soft threshold to proactively leave a pinned account (before 100%). |
|
|
340
351
|
| `OPENCODE_AUTH_LB_WEEKLY_DRAIN_TARGET` | `0.98` | Soft threshold for the WEEKLY window: scoring treats weekly quota as "fully drained" past this utilization, and a pinned session proactively migrates once its weekly util crosses it. The 5h window uses `MIGRATE_AT` (~0.95); the weekly window uses this (~0.98). Must be in (`MIGRATE_AT`, `EXHAUSTED_AT`]. |
|
|
341
352
|
| `OPENCODE_AUTH_LB_CHEAP_SWITCH_MAX_BYTES` | `65536` | For non-forced (proactive/drain) switches, only switch when the request body ≤ this many bytes, so a grown conversation isn't re-sent onto a fresh (uncached) account — a full per-account prompt-cache write. `0` disables the gate (always switch). A proactive switch bypasses this gate when the pinned account is within ~1% of hard exhaustion (a forced switch is imminent anyway and the context only grows); forced switches always ignore it. |
|
package/dist/index.js
CHANGED
|
@@ -13054,6 +13054,18 @@ function normalizeWindow(w) {
|
|
|
13054
13054
|
win.resetAt = 0;
|
|
13055
13055
|
return win;
|
|
13056
13056
|
}
|
|
13057
|
+
function normalizeCredits(c) {
|
|
13058
|
+
if (!isPlainObject2(c))
|
|
13059
|
+
return;
|
|
13060
|
+
const credits = c;
|
|
13061
|
+
if (typeof credits.hasCredits !== "boolean" || typeof credits.unlimited !== "boolean")
|
|
13062
|
+
return;
|
|
13063
|
+
if (!isFiniteNumber(credits.balance))
|
|
13064
|
+
delete credits.balance;
|
|
13065
|
+
if (credits.limitReached !== true)
|
|
13066
|
+
delete credits.limitReached;
|
|
13067
|
+
return credits;
|
|
13068
|
+
}
|
|
13057
13069
|
function normalizeAccounts(rows) {
|
|
13058
13070
|
let accounts = null;
|
|
13059
13071
|
for (let i = 0;i < rows.length; i++) {
|
|
@@ -13073,7 +13085,13 @@ function normalizeAccounts(rows) {
|
|
|
13073
13085
|
row.usage.capturedAt = 0;
|
|
13074
13086
|
row.usage.hourly = normalizeWindow(row.usage.hourly);
|
|
13075
13087
|
row.usage.weekly = normalizeWindow(row.usage.weekly);
|
|
13088
|
+
delete row.usage.credits;
|
|
13076
13089
|
}
|
|
13090
|
+
const credits = normalizeCredits(row.credits);
|
|
13091
|
+
if (credits)
|
|
13092
|
+
row.credits = credits;
|
|
13093
|
+
else
|
|
13094
|
+
delete row.credits;
|
|
13077
13095
|
if (!Number.isFinite(row.cooldownUntil))
|
|
13078
13096
|
row.cooldownUntil = 0;
|
|
13079
13097
|
if (row.cooldownKind !== undefined && row.cooldownKind !== "quota" && row.cooldownKind !== "auth" && row.cooldownKind !== "transient")
|
|
@@ -13326,14 +13344,19 @@ function maxUtil(account, now) {
|
|
|
13326
13344
|
function isExhausted(account, cfg, now) {
|
|
13327
13345
|
return maxUtil(account, now) >= cfg.exhaustedAt;
|
|
13328
13346
|
}
|
|
13347
|
+
function hasCreditHeadroom(account) {
|
|
13348
|
+
const credits = account.credits;
|
|
13349
|
+
return !!credits && !credits.limitReached && (credits.unlimited || credits.hasCredits);
|
|
13350
|
+
}
|
|
13351
|
+
function hasHeadroom(account, cfg, now) {
|
|
13352
|
+
return !isExhausted(account, cfg, now) || hasCreditHeadroom(account);
|
|
13353
|
+
}
|
|
13329
13354
|
function isAvailable(account, cfg, now) {
|
|
13330
13355
|
if (account.disabledReason)
|
|
13331
13356
|
return false;
|
|
13332
13357
|
if (account.cooldownUntil > now)
|
|
13333
13358
|
return false;
|
|
13334
|
-
|
|
13335
|
-
return false;
|
|
13336
|
-
return true;
|
|
13359
|
+
return hasHeadroom(account, cfg, now);
|
|
13337
13360
|
}
|
|
13338
13361
|
function weeklyUrgency(account, cfg, now) {
|
|
13339
13362
|
const weekly = account.usage.weekly;
|
|
@@ -13364,9 +13387,11 @@ function scoreAccount(account, cfg, now) {
|
|
|
13364
13387
|
function compareRanked(x, y) {
|
|
13365
13388
|
if (x.available !== y.available)
|
|
13366
13389
|
return x.available ? -1 : 1;
|
|
13367
|
-
if (x.available)
|
|
13368
|
-
return
|
|
13369
|
-
|
|
13390
|
+
if (!x.available)
|
|
13391
|
+
return x.weeklyUtil - y.weeklyUtil;
|
|
13392
|
+
if (x.onCredits !== y.onCredits)
|
|
13393
|
+
return x.onCredits ? 1 : -1;
|
|
13394
|
+
return y.score - x.score;
|
|
13370
13395
|
}
|
|
13371
13396
|
var DAY_MS, WEEK_MS2, HOUR_MS, HOURLY_WINDOW_MS, RESET_CUSHION_DAYS = 0.25, SCORE_DEFAULTS;
|
|
13372
13397
|
var init_score_core = __esm(() => {
|
|
@@ -13385,6 +13410,14 @@ var init_score_core = __esm(() => {
|
|
|
13385
13410
|
});
|
|
13386
13411
|
|
|
13387
13412
|
// src/usage-merge.ts
|
|
13413
|
+
function reconcileQuotaCooldown(account, cfg, now) {
|
|
13414
|
+
if (account.cooldownKind === "auth" || account.cooldownKind === "transient")
|
|
13415
|
+
return;
|
|
13416
|
+
if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
13417
|
+
account.cooldownUntil = 0;
|
|
13418
|
+
delete account.cooldownKind;
|
|
13419
|
+
}
|
|
13420
|
+
}
|
|
13388
13421
|
function rollWeeklyAnchorForward(anchor, now) {
|
|
13389
13422
|
if (!Number.isFinite(anchor) || anchor <= 0)
|
|
13390
13423
|
return 0;
|
|
@@ -13401,8 +13434,10 @@ function preserveWeeklyAnchor(incoming, stored, now) {
|
|
|
13401
13434
|
return incoming;
|
|
13402
13435
|
return { utilization: incoming.utilization, resetAt: anchor };
|
|
13403
13436
|
}
|
|
13437
|
+
var MAX_TRANSIENT_COOLDOWN_MS;
|
|
13404
13438
|
var init_usage_merge = __esm(() => {
|
|
13405
13439
|
init_score_core();
|
|
13440
|
+
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
13406
13441
|
});
|
|
13407
13442
|
|
|
13408
13443
|
// src/accounts.ts
|
|
@@ -13449,11 +13484,15 @@ function clearLostLogin(row, login) {
|
|
|
13449
13484
|
function mergeLoginUsage(row, usage) {
|
|
13450
13485
|
if (!usage)
|
|
13451
13486
|
return;
|
|
13487
|
+
if (usage.credits)
|
|
13488
|
+
row.credits = usage.credits;
|
|
13452
13489
|
row.usage = {
|
|
13453
13490
|
hourly: usage.hourly ?? row.usage.hourly,
|
|
13454
13491
|
weekly: usage.weekly ? preserveWeeklyAnchor(usage.weekly, row.usage.weekly, usage.capturedAt) : row.usage.weekly,
|
|
13455
13492
|
capturedAt: usage.weekly ? usage.capturedAt : row.usage.capturedAt
|
|
13456
13493
|
};
|
|
13494
|
+
if (usage.weekly)
|
|
13495
|
+
reconcileQuotaCooldown(row, loadScoreConfig(), usage.capturedAt);
|
|
13457
13496
|
}
|
|
13458
13497
|
function applyTokens(row, tokens) {
|
|
13459
13498
|
if (tokens.refresh) {
|
|
@@ -13547,6 +13586,7 @@ async function bootstrapFromOpencodeAuth(providerID, getAuth, tokensFromApiKey)
|
|
|
13547
13586
|
var init_accounts = __esm(() => {
|
|
13548
13587
|
init_pairing();
|
|
13549
13588
|
init_store();
|
|
13589
|
+
init_score_core();
|
|
13550
13590
|
init_types();
|
|
13551
13591
|
init_usage_merge();
|
|
13552
13592
|
});
|
|
@@ -13571,7 +13611,8 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
13571
13611
|
continue;
|
|
13572
13612
|
const windows = exhaustedWindows(account, cfg, now);
|
|
13573
13613
|
const cooldownActive = account.cooldownUntil > now;
|
|
13574
|
-
|
|
13614
|
+
const blocking = hasCreditHeadroom(account) ? [] : windows;
|
|
13615
|
+
if (!cooldownActive && blocking.length === 0)
|
|
13575
13616
|
return { state: "available" };
|
|
13576
13617
|
if (cooldownActive && (account.cooldownKind === "auth" || account.cooldownKind === "transient"))
|
|
13577
13618
|
continue;
|
|
@@ -13580,7 +13621,7 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
13580
13621
|
hasQuotaCandidate = true;
|
|
13581
13622
|
const constraints = [];
|
|
13582
13623
|
let accountHasUnknownReset = false;
|
|
13583
|
-
for (const window of
|
|
13624
|
+
for (const window of blocking) {
|
|
13584
13625
|
if (window.resetAt > now)
|
|
13585
13626
|
constraints.push(window.resetAt);
|
|
13586
13627
|
else
|
|
@@ -13893,22 +13934,27 @@ function weeklyUtil(account) {
|
|
|
13893
13934
|
function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude = NO_EXCLUSIONS, incumbentId = null) {
|
|
13894
13935
|
let bestAvail = null;
|
|
13895
13936
|
let bestAvailScore = Number.NEGATIVE_INFINITY;
|
|
13937
|
+
let bestAvailOnCredits = true;
|
|
13896
13938
|
let bestFallback = null;
|
|
13897
13939
|
let lowestWeeklyUtil = Number.POSITIVE_INFINITY;
|
|
13898
13940
|
let incumbentAccount = null;
|
|
13899
13941
|
let incumbentScore = Number.NEGATIVE_INFINITY;
|
|
13942
|
+
let incumbentOnCredits = false;
|
|
13900
13943
|
for (const account of accounts) {
|
|
13901
13944
|
if (account.providerID !== providerID || account.disabledReason || exclude.has(account.id))
|
|
13902
13945
|
continue;
|
|
13903
13946
|
if (isAvailable(account, cfg, now)) {
|
|
13904
13947
|
const score = scoreAccount(account, cfg, now);
|
|
13905
|
-
|
|
13948
|
+
const onCredits = isExhausted(account, cfg, now);
|
|
13949
|
+
if (onCredits === bestAvailOnCredits ? score > bestAvailScore : bestAvailOnCredits) {
|
|
13906
13950
|
bestAvailScore = score;
|
|
13907
13951
|
bestAvail = account;
|
|
13952
|
+
bestAvailOnCredits = onCredits;
|
|
13908
13953
|
}
|
|
13909
13954
|
if (account.id === incumbentId) {
|
|
13910
13955
|
incumbentAccount = account;
|
|
13911
13956
|
incumbentScore = score;
|
|
13957
|
+
incumbentOnCredits = onCredits;
|
|
13912
13958
|
}
|
|
13913
13959
|
} else {
|
|
13914
13960
|
const util = weeklyUtil(account);
|
|
@@ -13919,7 +13965,7 @@ function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude
|
|
|
13919
13965
|
}
|
|
13920
13966
|
}
|
|
13921
13967
|
if (bestAvail) {
|
|
13922
|
-
if (incumbentAccount && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
13968
|
+
if (incumbentAccount && incumbentOnCredits === bestAvailOnCredits && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
13923
13969
|
return { account: incumbentAccount, degraded: false, sticky: false };
|
|
13924
13970
|
return { account: bestAvail, degraded: false, sticky: false };
|
|
13925
13971
|
}
|
|
@@ -13958,7 +14004,7 @@ function selectForSession(pool, providerID, sessionKey, now, cfg = DEFAULT_CONFI
|
|
|
13958
14004
|
const cheap = (!pinnedImminent || cfg.drainMigrate) && isCheapMoment(typeof requestBytes === "function" ? requestBytes() : requestBytes, cfg);
|
|
13959
14005
|
if (pinnedOverSoft && (pinnedImminent || cheap) || cfg.drainMigrate && cheap) {
|
|
13960
14006
|
const alt = selectAccount(pool.accounts, providerID, now, cfg, withExcluded(exclude, pinned.id), null);
|
|
13961
|
-
if (alt && !alt.degraded) {
|
|
14007
|
+
if (alt && !alt.degraded && !isExhausted(alt.account, cfg, now)) {
|
|
13962
14008
|
if (pinnedOverSoft) {
|
|
13963
14009
|
const altUtil = maxUtil(alt.account, now);
|
|
13964
14010
|
const proactiveBetter = pinnedImminent ? altUtil < pinnedUtil : pinnedUtil - altUtil >= PROACTIVE_MIGRATE_MIN_DELTA && weeklyUrgency(alt.account, cfg, now) >= weeklyUrgency(pinned, cfg, now);
|
|
@@ -14488,24 +14534,46 @@ var init_usage_headers = __esm(() => {
|
|
|
14488
14534
|
});
|
|
14489
14535
|
|
|
14490
14536
|
// src/providers/usage-http.ts
|
|
14491
|
-
async function
|
|
14492
|
-
let response;
|
|
14537
|
+
async function request(url, headers, timeoutMs) {
|
|
14493
14538
|
try {
|
|
14494
|
-
|
|
14539
|
+
return await fetch(url, {
|
|
14495
14540
|
headers,
|
|
14496
14541
|
signal: AbortSignal.timeout(timeoutMs)
|
|
14497
14542
|
});
|
|
14498
14543
|
} catch {
|
|
14499
14544
|
return null;
|
|
14500
14545
|
}
|
|
14546
|
+
}
|
|
14547
|
+
async function readJson(response) {
|
|
14548
|
+
if (!response)
|
|
14549
|
+
return null;
|
|
14501
14550
|
if (!response.ok) {
|
|
14502
14551
|
await response.body?.cancel().catch(ignore);
|
|
14503
14552
|
return null;
|
|
14504
14553
|
}
|
|
14505
14554
|
return await response.json().catch(() => null);
|
|
14506
14555
|
}
|
|
14556
|
+
async function fetchJson(url, headers, timeoutMs) {
|
|
14557
|
+
return readJson(await request(url, headers, timeoutMs));
|
|
14558
|
+
}
|
|
14559
|
+
async function fetchUsageJson(url, headers, timeoutMs) {
|
|
14560
|
+
const response = await request(url, headers, timeoutMs);
|
|
14561
|
+
if (response?.status === 401) {
|
|
14562
|
+
await response.body?.cancel().catch(ignore);
|
|
14563
|
+
throw new UsageAuthenticationError;
|
|
14564
|
+
}
|
|
14565
|
+
return readJson(response);
|
|
14566
|
+
}
|
|
14567
|
+
var UsageAuthenticationError;
|
|
14507
14568
|
var init_usage_http = __esm(() => {
|
|
14508
14569
|
init_util2();
|
|
14570
|
+
UsageAuthenticationError = class UsageAuthenticationError extends Error {
|
|
14571
|
+
status = 401;
|
|
14572
|
+
constructor() {
|
|
14573
|
+
super("Usage endpoint rejected the access token (401)");
|
|
14574
|
+
this.name = "UsageAuthenticationError";
|
|
14575
|
+
}
|
|
14576
|
+
};
|
|
14509
14577
|
});
|
|
14510
14578
|
|
|
14511
14579
|
// src/providers/anthropic/version.ts
|
|
@@ -14945,7 +15013,7 @@ async function probeUsage(access, now) {
|
|
|
14945
15013
|
async function fetchUsage(account, now) {
|
|
14946
15014
|
if (!account.refresh)
|
|
14947
15015
|
return probeUsage(account.access, now);
|
|
14948
|
-
const json = await
|
|
15016
|
+
const json = await fetchUsageJson(USAGE_URL, {
|
|
14949
15017
|
authorization: `Bearer ${account.access}`,
|
|
14950
15018
|
"anthropic-beta": "oauth-2025-04-20",
|
|
14951
15019
|
"user-agent": usageUserAgent(),
|
|
@@ -15277,7 +15345,7 @@ function parseUsages(json, now) {
|
|
|
15277
15345
|
return hourly || weekly ? { hourly, weekly, capturedAt: now } : null;
|
|
15278
15346
|
}
|
|
15279
15347
|
function getUsages(baseUrl, key) {
|
|
15280
|
-
return
|
|
15348
|
+
return fetchUsageJson(`${baseUrl}/usages`, { authorization: `Bearer ${key}`, accept: "application/json" }, USAGE_HTTP_TIMEOUT_MS);
|
|
15281
15349
|
}
|
|
15282
15350
|
function usageFetcher(baseUrl) {
|
|
15283
15351
|
return async (account, now) => parseUsages(await getUsages(baseUrl, account.access), now);
|
|
@@ -15534,10 +15602,31 @@ var init_transform2 = __esm(() => {
|
|
|
15534
15602
|
function isWeeklyMinutes(minutes) {
|
|
15535
15603
|
return minutes >= WEEK_MINUTES * 0.95 && minutes <= WEEK_MINUTES * 1.05;
|
|
15536
15604
|
}
|
|
15605
|
+
function creditsFrom(hasCredits, unlimited, balance, limitReached) {
|
|
15606
|
+
if (typeof hasCredits !== "boolean" || typeof unlimited !== "boolean")
|
|
15607
|
+
return;
|
|
15608
|
+
const amount = typeof balance === "string" && balance.trim() !== "" ? Number(balance) : Number.NaN;
|
|
15609
|
+
return {
|
|
15610
|
+
hasCredits,
|
|
15611
|
+
unlimited,
|
|
15612
|
+
...Number.isFinite(amount) ? { balance: amount } : {},
|
|
15613
|
+
...limitReached === true ? { limitReached } : {}
|
|
15614
|
+
};
|
|
15615
|
+
}
|
|
15616
|
+
function headerBool(raw) {
|
|
15617
|
+
const value = raw?.toLowerCase();
|
|
15618
|
+
if (value === "true" || value === "1")
|
|
15619
|
+
return true;
|
|
15620
|
+
if (value === "false" || value === "0")
|
|
15621
|
+
return false;
|
|
15622
|
+
return;
|
|
15623
|
+
}
|
|
15537
15624
|
function parseUsageHeaders2(headers) {
|
|
15538
15625
|
const primaryWindowMinutes = Number(headers.get(PRIMARY_WINDOW_MINUTES_HEADER));
|
|
15539
15626
|
const spec = isWeeklyMinutes(primaryWindowMinutes) ? WEEKLY_PRIMARY_HEADER_SPEC : HEADER_SPEC2;
|
|
15540
|
-
|
|
15627
|
+
const windows = parseWindowPairHeaders(headers, spec);
|
|
15628
|
+
const credits = creditsFrom(headerBool(headers.get("x-codex-credits-has-credits")), headerBool(headers.get("x-codex-credits-unlimited")), headers.get("x-codex-credits-balance"));
|
|
15629
|
+
return credits ? { ...windows, credits } : windows;
|
|
15541
15630
|
}
|
|
15542
15631
|
function endpointWindow2(w) {
|
|
15543
15632
|
return endpointWindowFrom(w, (win) => win.used_percent, 100, (win) => secondsToMs(Number(win.reset_at ?? 0)));
|
|
@@ -15551,16 +15640,19 @@ async function fetchUsage2(account, now) {
|
|
|
15551
15640
|
const accountId = resolveAccountId(account);
|
|
15552
15641
|
if (accountId)
|
|
15553
15642
|
headers["chatgpt-account-id"] = accountId;
|
|
15554
|
-
const json = await
|
|
15643
|
+
const json = await fetchUsageJson(USAGE_URL2, headers, USAGE_HTTP_TIMEOUT_MS);
|
|
15555
15644
|
const rateLimit = json?.rate_limit;
|
|
15556
15645
|
if (!isPlainObject2(rateLimit))
|
|
15557
15646
|
return null;
|
|
15558
15647
|
const { primary_window: primary, secondary_window: secondary } = rateLimit;
|
|
15559
15648
|
const primaryIsWeekly = isWeeklyMinutes(Math.ceil(Number(primary?.limit_window_seconds) / 60));
|
|
15649
|
+
const reported = json?.credits;
|
|
15650
|
+
const credits = isPlainObject2(reported) ? creditsFrom(reported.has_credits, reported.unlimited, reported.balance, reported.overage_limit_reached) : undefined;
|
|
15560
15651
|
return {
|
|
15561
15652
|
hourly: endpointWindow2(primaryIsWeekly ? secondary : primary),
|
|
15562
15653
|
weekly: endpointWindow2(primaryIsWeekly ? primary : secondary),
|
|
15563
|
-
capturedAt: now
|
|
15654
|
+
capturedAt: now,
|
|
15655
|
+
...credits ? { credits } : {}
|
|
15564
15656
|
};
|
|
15565
15657
|
}
|
|
15566
15658
|
var WEEK_MINUTES, PRIMARY_WINDOW_MINUTES_HEADER = "x-codex-primary-window-minutes", HEADER_SPEC2, WEEKLY_PRIMARY_HEADER_SPEC;
|
|
@@ -15607,6 +15699,7 @@ var init_adapter3 = __esm(() => {
|
|
|
15607
15699
|
},
|
|
15608
15700
|
parseUsageHeaders: parseUsageHeaders2,
|
|
15609
15701
|
fetchUsage: fetchUsage2,
|
|
15702
|
+
reportsCredits: true,
|
|
15610
15703
|
classifyError: classifyHttpStatus
|
|
15611
15704
|
};
|
|
15612
15705
|
});
|
|
@@ -15640,11 +15733,14 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
15640
15733
|
aliveIds?.add(account.id);
|
|
15641
15734
|
if (account.disabledReason && account.disabledReason !== MANUAL_DISABLED_REASON)
|
|
15642
15735
|
continue;
|
|
15643
|
-
|
|
15736
|
+
const fresh = account.usage.capturedAt !== 0 && now - account.usage.capturedAt < USAGE_REFRESH_TTL_MS;
|
|
15737
|
+
if (fresh && account.credits)
|
|
15644
15738
|
continue;
|
|
15645
15739
|
const adapter = adapterFor(adapters, account.providerID);
|
|
15646
15740
|
if (!adapter)
|
|
15647
15741
|
continue;
|
|
15742
|
+
if (fresh && !adapter.reportsCredits)
|
|
15743
|
+
continue;
|
|
15648
15744
|
if ((lastPoll.get(account.id) ?? 0) > now - USAGE_REFRESH_TTL_MS)
|
|
15649
15745
|
continue;
|
|
15650
15746
|
lastPoll.set(account.id, now);
|
|
@@ -15662,20 +15758,29 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
15662
15758
|
await Promise.all(stale.map(async ({ adapter, account }) => {
|
|
15663
15759
|
try {
|
|
15664
15760
|
await ensureAccessToken(adapter, account, now);
|
|
15665
|
-
const
|
|
15761
|
+
const rejectedAccess = account.access;
|
|
15762
|
+
let snapshot;
|
|
15763
|
+
try {
|
|
15764
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
15765
|
+
} catch (error) {
|
|
15766
|
+
if (!(error instanceof UsageAuthenticationError) || !account.refresh)
|
|
15767
|
+
throw error;
|
|
15768
|
+
await ensureAccessToken(adapter, account, now, rejectedAccess);
|
|
15769
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
15770
|
+
}
|
|
15666
15771
|
if (snapshot) {
|
|
15667
15772
|
await mutatePool((p) => {
|
|
15668
15773
|
const stored = findAccount(p, account.id);
|
|
15669
15774
|
if (stored) {
|
|
15775
|
+
if (snapshot.credits)
|
|
15776
|
+
stored.credits = snapshot.credits;
|
|
15670
15777
|
stored.usage = {
|
|
15671
15778
|
hourly: snapshot.hourly ?? stored.usage.hourly,
|
|
15672
15779
|
weekly: snapshot.weekly === null ? stored.usage.weekly : preserveWeeklyAnchor(snapshot.weekly, stored.usage.weekly, now),
|
|
15673
15780
|
capturedAt: snapshot.weekly === null ? stored.usage.capturedAt : now
|
|
15674
15781
|
};
|
|
15675
|
-
if (snapshot.weekly !== null && stored.cooldownUntil
|
|
15676
|
-
stored
|
|
15677
|
-
delete stored.cooldownKind;
|
|
15678
|
-
}
|
|
15782
|
+
if (snapshot.weekly !== null && stored.cooldownUntil === account.cooldownUntil && stored.cooldownKind === account.cooldownKind)
|
|
15783
|
+
reconcileQuotaCooldown(stored, scoreCfg, now);
|
|
15679
15784
|
}
|
|
15680
15785
|
});
|
|
15681
15786
|
}
|
|
@@ -15688,16 +15793,17 @@ function refreshUsageInBackground(adapter, now, poolSnapshot) {
|
|
|
15688
15793
|
function refreshAllUsageInBackground(now, poolSnapshot) {
|
|
15689
15794
|
return refreshUsage(ADAPTERS, now, poolSnapshot);
|
|
15690
15795
|
}
|
|
15691
|
-
var USAGE_REFRESH_TTL_MS,
|
|
15796
|
+
var USAGE_REFRESH_TTL_MS, lastPoll;
|
|
15692
15797
|
var init_usage_refresh = __esm(() => {
|
|
15693
15798
|
init_store();
|
|
15694
15799
|
init_registry();
|
|
15800
|
+
init_usage_http();
|
|
15695
15801
|
init_refresh();
|
|
15696
15802
|
init_score_core();
|
|
15697
15803
|
init_types();
|
|
15698
15804
|
init_usage_merge();
|
|
15805
|
+
init_usage_merge();
|
|
15699
15806
|
USAGE_REFRESH_TTL_MS = 5 * 60 * 1000;
|
|
15700
|
-
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
15701
15807
|
lastPoll = new Map;
|
|
15702
15808
|
});
|
|
15703
15809
|
|
|
@@ -15720,6 +15826,8 @@ async function bestEffort(what, op) {
|
|
|
15720
15826
|
function applyUsagePartial(account, partial, now) {
|
|
15721
15827
|
if (partial.hourly !== undefined)
|
|
15722
15828
|
account.usage.hourly = partial.hourly;
|
|
15829
|
+
if (partial.credits !== undefined)
|
|
15830
|
+
account.credits = partial.credits;
|
|
15723
15831
|
if (partial.weekly !== undefined) {
|
|
15724
15832
|
account.usage.weekly = preserveWeeklyAnchor(partial.weekly, account.usage.weekly, now);
|
|
15725
15833
|
account.usage.capturedAt = now;
|
|
@@ -15788,7 +15896,7 @@ async function recordSuccess(adapter, accountId, partial, sessionKey, now, cfg,
|
|
|
15788
15896
|
if (account) {
|
|
15789
15897
|
if (partial)
|
|
15790
15898
|
applyUsagePartial(account, partial, now);
|
|
15791
|
-
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS &&
|
|
15899
|
+
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
15792
15900
|
account.cooldownUntil = 0;
|
|
15793
15901
|
delete account.cooldownKind;
|
|
15794
15902
|
}
|
|
@@ -16132,7 +16240,18 @@ function byProviderId(a, b) {
|
|
|
16132
16240
|
function isExpired(window, now) {
|
|
16133
16241
|
return !!window && window.resetAt > 0 && window.resetAt <= now;
|
|
16134
16242
|
}
|
|
16135
|
-
function
|
|
16243
|
+
function creditsText(credits) {
|
|
16244
|
+
if (!credits)
|
|
16245
|
+
return "";
|
|
16246
|
+
if (credits.limitReached)
|
|
16247
|
+
return "credit limit reached";
|
|
16248
|
+
if (credits.unlimited)
|
|
16249
|
+
return "credits unlimited";
|
|
16250
|
+
if (!credits.hasCredits)
|
|
16251
|
+
return "no credits";
|
|
16252
|
+
return credits.balance === undefined ? "credits available" : `credits ${Math.round(credits.balance * 100) / 100}`;
|
|
16253
|
+
}
|
|
16254
|
+
function toStatus(account, now, current, available, exhausted, rank) {
|
|
16136
16255
|
const weekly = account.usage.weekly;
|
|
16137
16256
|
const weeklyExpired = isExpired(weekly, now);
|
|
16138
16257
|
return {
|
|
@@ -16148,6 +16267,7 @@ function toStatus(account, now, current, available, rank) {
|
|
|
16148
16267
|
login: credentialTag(account),
|
|
16149
16268
|
warnings: loginWarnings(account, now),
|
|
16150
16269
|
lostNotes: lostLoginNotes(account, now),
|
|
16270
|
+
credits: exhausted ? creditsText(account.credits) : "",
|
|
16151
16271
|
current,
|
|
16152
16272
|
rank
|
|
16153
16273
|
};
|
|
@@ -16166,14 +16286,17 @@ function buildStatus(pool, now, cfg = DEFAULT_CONFIG) {
|
|
|
16166
16286
|
const currentAccountId = pool.lastSelected[providerID] ?? null;
|
|
16167
16287
|
const ranked = providerAccounts.map((a) => {
|
|
16168
16288
|
const available = isAvailable(a, cfg, now);
|
|
16289
|
+
const exhausted = isExhausted(a, cfg, now);
|
|
16169
16290
|
return {
|
|
16170
16291
|
a,
|
|
16171
16292
|
available,
|
|
16293
|
+
exhausted,
|
|
16294
|
+
onCredits: available && exhausted,
|
|
16172
16295
|
score: available ? scoreAccount(a, cfg, now) : Number.NEGATIVE_INFINITY,
|
|
16173
16296
|
weeklyUtil: a.usage.weekly?.utilization ?? 0
|
|
16174
16297
|
};
|
|
16175
16298
|
}).sort(compareRanked);
|
|
16176
|
-
const accounts = ranked.map(({ a, available }, i) => toStatus(a, now, a.id === currentAccountId, available, i + 1));
|
|
16299
|
+
const accounts = ranked.map(({ a, available, exhausted }, i) => toStatus(a, now, a.id === currentAccountId, available, exhausted, i + 1));
|
|
16177
16300
|
return { providerID, accounts };
|
|
16178
16301
|
});
|
|
16179
16302
|
}
|
|
@@ -16213,12 +16336,10 @@ function stateOf(a, now) {
|
|
|
16213
16336
|
if (a.cooldownUntil > now)
|
|
16214
16337
|
return `cooldown ${relTime(a.cooldownUntil, now)}`;
|
|
16215
16338
|
if (!a.available)
|
|
16216
|
-
return "exhausted";
|
|
16339
|
+
return a.credits ? `exhausted · ${a.credits}` : "exhausted";
|
|
16217
16340
|
const base = a.current ? "in use" : "ready";
|
|
16218
16341
|
const tiers = Object.entries(a.modelCooldownsUntil).filter(([, until]) => until > now).sort(([x], [y]) => compareAscii(x, y)).map(([tier, until]) => `${tier} ${relTime(until, now)}`);
|
|
16219
|
-
|
|
16220
|
-
return `${base} · ${tiers.join(" · ")}`;
|
|
16221
|
-
return base;
|
|
16342
|
+
return [base, a.credits, ...tiers].filter(Boolean).join(" · ");
|
|
16222
16343
|
}
|
|
16223
16344
|
function providerName(providerID) {
|
|
16224
16345
|
return PROVIDER_NAMES[providerID] ?? providerID;
|
|
@@ -16694,7 +16815,7 @@ class PendingCoordinator {
|
|
|
16694
16815
|
await this.notifyPending(ref, recovery);
|
|
16695
16816
|
try {
|
|
16696
16817
|
for (;; ) {
|
|
16697
|
-
await this.dependencies.sleep(Math.max(0, recovery.nextCheckAt - this.dependencies.now()), controller.signal);
|
|
16818
|
+
await this.dependencies.sleep(Math.max(0, Math.min(recovery.nextCheckAt - this.dependencies.now(), USAGE_REFRESH_TTL_MS)), controller.signal);
|
|
16698
16819
|
const now = this.dependencies.now();
|
|
16699
16820
|
await this.dependencies.refreshUsage(now).catch(ignore);
|
|
16700
16821
|
let next;
|
|
@@ -17329,7 +17450,7 @@ function backoffFrom(res, now) {
|
|
|
17329
17450
|
function isRateLimited(res) {
|
|
17330
17451
|
return res.status === 429 || res.status === 403 && (res.headers.get("x-ratelimit-remaining") === "0" || res.headers.has("retry-after"));
|
|
17331
17452
|
}
|
|
17332
|
-
async function
|
|
17453
|
+
async function request2(url, init, now) {
|
|
17333
17454
|
let res;
|
|
17334
17455
|
try {
|
|
17335
17456
|
res = await globalThis.fetch(url, {
|
|
@@ -17381,7 +17502,7 @@ async function boundedText(res) {
|
|
|
17381
17502
|
}
|
|
17382
17503
|
return Buffer.concat(chunks).toString("utf8");
|
|
17383
17504
|
}
|
|
17384
|
-
async function
|
|
17505
|
+
async function readJson2(res) {
|
|
17385
17506
|
if (!res.ok)
|
|
17386
17507
|
throw new SyncError("http");
|
|
17387
17508
|
const text = await boundedText(res);
|
|
@@ -17396,7 +17517,7 @@ async function readJson(res) {
|
|
|
17396
17517
|
return json;
|
|
17397
17518
|
}
|
|
17398
17519
|
async function createGist(token, content, now = Date.now()) {
|
|
17399
|
-
const res = await
|
|
17520
|
+
const res = await request2(API, {
|
|
17400
17521
|
method: "POST",
|
|
17401
17522
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
17402
17523
|
body: JSON.stringify({
|
|
@@ -17405,7 +17526,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
17405
17526
|
files: { [GIST_FILE]: { content } }
|
|
17406
17527
|
})
|
|
17407
17528
|
}, now);
|
|
17408
|
-
const json = await
|
|
17529
|
+
const json = await readJson2(res);
|
|
17409
17530
|
const owner = isPlainObject2(json.owner) ? json.owner.login : undefined;
|
|
17410
17531
|
if (typeof json.id !== "string" || !GIST_ID_RE.test(json.id))
|
|
17411
17532
|
throw new SyncError("http");
|
|
@@ -17417,7 +17538,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
17417
17538
|
async function updateGist(token, id, content, now = Date.now()) {
|
|
17418
17539
|
let res;
|
|
17419
17540
|
try {
|
|
17420
|
-
res = await
|
|
17541
|
+
res = await request2(`${API}/${id}`, {
|
|
17421
17542
|
method: "PATCH",
|
|
17422
17543
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
17423
17544
|
body: JSON.stringify({ files: { [GIST_FILE]: { content } } })
|
|
@@ -17429,10 +17550,10 @@ async function updateGist(token, id, content, now = Date.now()) {
|
|
|
17429
17550
|
}
|
|
17430
17551
|
if (res.status === 401 || res.status === 403)
|
|
17431
17552
|
throw new SyncError("write-denied");
|
|
17432
|
-
await
|
|
17553
|
+
await readJson2(res);
|
|
17433
17554
|
}
|
|
17434
17555
|
async function readGist(id, etag, now = Date.now(), token) {
|
|
17435
|
-
const ask = (auth) =>
|
|
17556
|
+
const ask = (auth) => request2(`${API}/${id}`, {
|
|
17436
17557
|
headers: {
|
|
17437
17558
|
...headers(auth),
|
|
17438
17559
|
...etag ? { "if-none-match": etag } : {}
|
|
@@ -17443,7 +17564,7 @@ async function readGist(id, etag, now = Date.now(), token) {
|
|
|
17443
17564
|
res = await ask();
|
|
17444
17565
|
if (res.status === 304)
|
|
17445
17566
|
return { changed: false };
|
|
17446
|
-
const json = await
|
|
17567
|
+
const json = await readJson2(res);
|
|
17447
17568
|
const file = isPlainObject2(json.files) ? json.files[GIST_FILE] : undefined;
|
|
17448
17569
|
if (!isPlainObject2(file) || typeof file.content !== "string")
|
|
17449
17570
|
throw new SyncError("bad-blob");
|
|
@@ -18585,6 +18706,71 @@ var init_start = __esm(() => {
|
|
|
18585
18706
|
init_state();
|
|
18586
18707
|
});
|
|
18587
18708
|
|
|
18709
|
+
// src/usage-poll.ts
|
|
18710
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
18711
|
+
async function nextPollAt(path) {
|
|
18712
|
+
try {
|
|
18713
|
+
const value = JSON.parse(await readFile8(path, "utf8"));
|
|
18714
|
+
return isPlainObject2(value) && isFiniteNumber(value.nextPollAt) ? value.nextPollAt : 0;
|
|
18715
|
+
} catch (error) {
|
|
18716
|
+
if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
18717
|
+
return 0;
|
|
18718
|
+
throw error;
|
|
18719
|
+
}
|
|
18720
|
+
}
|
|
18721
|
+
function startUsagePoll(deps = {}) {
|
|
18722
|
+
const now = deps.now ?? Date.now;
|
|
18723
|
+
const refresh = deps.refresh ?? refreshAllUsageInBackground;
|
|
18724
|
+
const timers = deps.timers ?? realTimers;
|
|
18725
|
+
let disposed = false;
|
|
18726
|
+
let inflight;
|
|
18727
|
+
async function poll() {
|
|
18728
|
+
const path = `${poolFilePath()}.usage-poll.json`;
|
|
18729
|
+
try {
|
|
18730
|
+
await withLock(`${path}.lock`, {
|
|
18731
|
+
staleMs: 120000,
|
|
18732
|
+
timeoutMs: 0,
|
|
18733
|
+
retryMs: 25,
|
|
18734
|
+
heartbeatMs: 5000
|
|
18735
|
+
}, async () => {
|
|
18736
|
+
const at = now();
|
|
18737
|
+
if (at < await nextPollAt(path))
|
|
18738
|
+
return;
|
|
18739
|
+
await writeJsonAtomic(path, JSON.stringify({ nextPollAt: at + USAGE_REFRESH_TTL_MS }));
|
|
18740
|
+
await refresh(at);
|
|
18741
|
+
});
|
|
18742
|
+
} catch (error) {
|
|
18743
|
+
if (!(error instanceof LockTimeoutError))
|
|
18744
|
+
throw error;
|
|
18745
|
+
}
|
|
18746
|
+
}
|
|
18747
|
+
function tick() {
|
|
18748
|
+
if (disposed)
|
|
18749
|
+
return Promise.resolve();
|
|
18750
|
+
inflight ??= poll().finally(() => {
|
|
18751
|
+
inflight = undefined;
|
|
18752
|
+
});
|
|
18753
|
+
return inflight;
|
|
18754
|
+
}
|
|
18755
|
+
const timer = timers.set(() => void tick().catch(ignore), USAGE_REFRESH_TTL_MS);
|
|
18756
|
+
return {
|
|
18757
|
+
tick,
|
|
18758
|
+
async dispose() {
|
|
18759
|
+
disposed = true;
|
|
18760
|
+
timers.clear(timer);
|
|
18761
|
+
await inflight;
|
|
18762
|
+
}
|
|
18763
|
+
};
|
|
18764
|
+
}
|
|
18765
|
+
var init_usage_poll = __esm(() => {
|
|
18766
|
+
init_lock();
|
|
18767
|
+
init_paths();
|
|
18768
|
+
init_store();
|
|
18769
|
+
init_loop();
|
|
18770
|
+
init_usage_refresh();
|
|
18771
|
+
init_util2();
|
|
18772
|
+
});
|
|
18773
|
+
|
|
18588
18774
|
// src/legacy.ts
|
|
18589
18775
|
function zeroOutCost(provider) {
|
|
18590
18776
|
for (const model of Object.values(provider.models)) {
|
|
@@ -18739,8 +18925,12 @@ function createProviderPlugin(adapter) {
|
|
|
18739
18925
|
}
|
|
18740
18926
|
var PROVIDER_LABELS, AnthropicLoadBalancerPlugin, OpenAILoadBalancerPlugin, KimiCodeLoadBalancerPlugin, KimiCodeGlobalLoadBalancerPlugin, lbResult = (output) => ({ title: "Auth Load Balancer", output }), AuthLoadBalancerStatusPlugin = async (input) => {
|
|
18741
18927
|
const sync = startSync();
|
|
18928
|
+
const usage = startUsagePoll();
|
|
18742
18929
|
return {
|
|
18743
|
-
dispose: async () =>
|
|
18930
|
+
dispose: async () => {
|
|
18931
|
+
await usage.dispose();
|
|
18932
|
+
await sync?.dispose();
|
|
18933
|
+
},
|
|
18744
18934
|
tool: {
|
|
18745
18935
|
auth_lb_status: tool({
|
|
18746
18936
|
description: "Show the auth load-balancer pool: the in-use account per provider, each account's weekly and 5h usage, cooldowns, and the ranked next-candidate accounts. Refreshes stale usage from the provider usage endpoints (throttled) before rendering.",
|
|
@@ -18829,6 +19019,7 @@ var init_legacy = __esm(() => {
|
|
|
18829
19019
|
init_start();
|
|
18830
19020
|
init_token_mint();
|
|
18831
19021
|
init_types();
|
|
19022
|
+
init_usage_poll();
|
|
18832
19023
|
init_usage_refresh();
|
|
18833
19024
|
init_util2();
|
|
18834
19025
|
PROVIDER_LABELS = {
|
|
@@ -17,8 +17,8 @@ export declare function sendUsageProbe(access: string): Promise<Response>;
|
|
|
17
17
|
export declare function probeSnapshot(headers: Headers, now: number): UsageSnapshot | null;
|
|
18
18
|
/**
|
|
19
19
|
* Poll the dedicated usage endpoint for authoritative 5h + 7d utilization without
|
|
20
|
-
* consuming inference quota.
|
|
21
|
-
*
|
|
20
|
+
* consuming inference quota. A 401 is surfaced for bounded OAuth recovery;
|
|
21
|
+
* other failures, including an invalid 200 body, keep the last-known snapshot.
|
|
22
22
|
*
|
|
23
23
|
* A row without a refresh token is a `claude setup-token` bearer, scoped to
|
|
24
24
|
* `user:inference` alone; the endpoint demands `user:profile` and answers it
|