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/dist/v1.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
- if (isExhausted(account, cfg, now))
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 y.score - x.score;
13369
- return x.weeklyUtil - y.weeklyUtil;
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
- if (!cooldownActive && windows.length === 0)
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 windows) {
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
- if (score > bestAvailScore) {
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 fetchJson(url, headers, timeoutMs) {
14492
- let response;
14537
+ async function request(url, headers, timeoutMs) {
14493
14538
  try {
14494
- response = await fetch(url, {
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 fetchJson(USAGE_URL, {
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 fetchJson(`${baseUrl}/usages`, { authorization: `Bearer ${key}`, accept: "application/json" }, USAGE_HTTP_TIMEOUT_MS);
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
- return parseWindowPairHeaders(headers, spec);
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 fetchJson(USAGE_URL2, headers, USAGE_HTTP_TIMEOUT_MS);
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
- if (account.usage.capturedAt !== 0 && now - account.usage.capturedAt <= USAGE_REFRESH_TTL_MS)
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 snapshot = await adapter.fetchUsage(account, now);
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 > now + MAX_TRANSIENT_COOLDOWN_MS && !isExhausted(stored, scoreCfg, now)) {
15676
- stored.cooldownUntil = 0;
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, MAX_TRANSIENT_COOLDOWN_MS, lastPoll;
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 && !isExhausted(account, cfg, now)) {
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 toStatus(account, now, current, available, rank) {
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
- if (tiers.length > 0)
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 request(url, init, now) {
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 readJson(res) {
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 request(API, {
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 readJson(res);
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 request(`${API}/${id}`, {
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 readJson(res);
17553
+ await readJson2(res);
17433
17554
  }
17434
17555
  async function readGist(id, etag, now = Date.now(), token) {
17435
- const ask = (auth) => request(`${API}/${id}`, {
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 readJson(res);
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 () => sync?.dispose(),
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 = {