opencode-auth-load-balancer 0.9.1 → 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(() => {
@@ -13388,7 +13413,7 @@ var init_score_core = __esm(() => {
13388
13413
  function reconcileQuotaCooldown(account, cfg, now) {
13389
13414
  if (account.cooldownKind === "auth" || account.cooldownKind === "transient")
13390
13415
  return;
13391
- if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && !isExhausted(account, cfg, now)) {
13416
+ if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
13392
13417
  account.cooldownUntil = 0;
13393
13418
  delete account.cooldownKind;
13394
13419
  }
@@ -13459,6 +13484,8 @@ function clearLostLogin(row, login) {
13459
13484
  function mergeLoginUsage(row, usage) {
13460
13485
  if (!usage)
13461
13486
  return;
13487
+ if (usage.credits)
13488
+ row.credits = usage.credits;
13462
13489
  row.usage = {
13463
13490
  hourly: usage.hourly ?? row.usage.hourly,
13464
13491
  weekly: usage.weekly ? preserveWeeklyAnchor(usage.weekly, row.usage.weekly, usage.capturedAt) : row.usage.weekly,
@@ -13584,7 +13611,8 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
13584
13611
  continue;
13585
13612
  const windows = exhaustedWindows(account, cfg, now);
13586
13613
  const cooldownActive = account.cooldownUntil > now;
13587
- if (!cooldownActive && windows.length === 0)
13614
+ const blocking = hasCreditHeadroom(account) ? [] : windows;
13615
+ if (!cooldownActive && blocking.length === 0)
13588
13616
  return { state: "available" };
13589
13617
  if (cooldownActive && (account.cooldownKind === "auth" || account.cooldownKind === "transient"))
13590
13618
  continue;
@@ -13593,7 +13621,7 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
13593
13621
  hasQuotaCandidate = true;
13594
13622
  const constraints = [];
13595
13623
  let accountHasUnknownReset = false;
13596
- for (const window of windows) {
13624
+ for (const window of blocking) {
13597
13625
  if (window.resetAt > now)
13598
13626
  constraints.push(window.resetAt);
13599
13627
  else
@@ -13906,22 +13934,27 @@ function weeklyUtil(account) {
13906
13934
  function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude = NO_EXCLUSIONS, incumbentId = null) {
13907
13935
  let bestAvail = null;
13908
13936
  let bestAvailScore = Number.NEGATIVE_INFINITY;
13937
+ let bestAvailOnCredits = true;
13909
13938
  let bestFallback = null;
13910
13939
  let lowestWeeklyUtil = Number.POSITIVE_INFINITY;
13911
13940
  let incumbentAccount = null;
13912
13941
  let incumbentScore = Number.NEGATIVE_INFINITY;
13942
+ let incumbentOnCredits = false;
13913
13943
  for (const account of accounts) {
13914
13944
  if (account.providerID !== providerID || account.disabledReason || exclude.has(account.id))
13915
13945
  continue;
13916
13946
  if (isAvailable(account, cfg, now)) {
13917
13947
  const score = scoreAccount(account, cfg, now);
13918
- if (score > bestAvailScore) {
13948
+ const onCredits = isExhausted(account, cfg, now);
13949
+ if (onCredits === bestAvailOnCredits ? score > bestAvailScore : bestAvailOnCredits) {
13919
13950
  bestAvailScore = score;
13920
13951
  bestAvail = account;
13952
+ bestAvailOnCredits = onCredits;
13921
13953
  }
13922
13954
  if (account.id === incumbentId) {
13923
13955
  incumbentAccount = account;
13924
13956
  incumbentScore = score;
13957
+ incumbentOnCredits = onCredits;
13925
13958
  }
13926
13959
  } else {
13927
13960
  const util = weeklyUtil(account);
@@ -13932,7 +13965,7 @@ function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude
13932
13965
  }
13933
13966
  }
13934
13967
  if (bestAvail) {
13935
- if (incumbentAccount && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
13968
+ if (incumbentAccount && incumbentOnCredits === bestAvailOnCredits && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
13936
13969
  return { account: incumbentAccount, degraded: false, sticky: false };
13937
13970
  return { account: bestAvail, degraded: false, sticky: false };
13938
13971
  }
@@ -13971,7 +14004,7 @@ function selectForSession(pool, providerID, sessionKey, now, cfg = DEFAULT_CONFI
13971
14004
  const cheap = (!pinnedImminent || cfg.drainMigrate) && isCheapMoment(typeof requestBytes === "function" ? requestBytes() : requestBytes, cfg);
13972
14005
  if (pinnedOverSoft && (pinnedImminent || cheap) || cfg.drainMigrate && cheap) {
13973
14006
  const alt = selectAccount(pool.accounts, providerID, now, cfg, withExcluded(exclude, pinned.id), null);
13974
- if (alt && !alt.degraded) {
14007
+ if (alt && !alt.degraded && !isExhausted(alt.account, cfg, now)) {
13975
14008
  if (pinnedOverSoft) {
13976
14009
  const altUtil = maxUtil(alt.account, now);
13977
14010
  const proactiveBetter = pinnedImminent ? altUtil < pinnedUtil : pinnedUtil - altUtil >= PROACTIVE_MIGRATE_MIN_DELTA && weeklyUrgency(alt.account, cfg, now) >= weeklyUrgency(pinned, cfg, now);
@@ -15569,10 +15602,31 @@ var init_transform2 = __esm(() => {
15569
15602
  function isWeeklyMinutes(minutes) {
15570
15603
  return minutes >= WEEK_MINUTES * 0.95 && minutes <= WEEK_MINUTES * 1.05;
15571
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
+ }
15572
15624
  function parseUsageHeaders2(headers) {
15573
15625
  const primaryWindowMinutes = Number(headers.get(PRIMARY_WINDOW_MINUTES_HEADER));
15574
15626
  const spec = isWeeklyMinutes(primaryWindowMinutes) ? WEEKLY_PRIMARY_HEADER_SPEC : HEADER_SPEC2;
15575
- 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;
15576
15630
  }
15577
15631
  function endpointWindow2(w) {
15578
15632
  return endpointWindowFrom(w, (win) => win.used_percent, 100, (win) => secondsToMs(Number(win.reset_at ?? 0)));
@@ -15592,10 +15646,13 @@ async function fetchUsage2(account, now) {
15592
15646
  return null;
15593
15647
  const { primary_window: primary, secondary_window: secondary } = rateLimit;
15594
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;
15595
15651
  return {
15596
15652
  hourly: endpointWindow2(primaryIsWeekly ? secondary : primary),
15597
15653
  weekly: endpointWindow2(primaryIsWeekly ? primary : secondary),
15598
- capturedAt: now
15654
+ capturedAt: now,
15655
+ ...credits ? { credits } : {}
15599
15656
  };
15600
15657
  }
15601
15658
  var WEEK_MINUTES, PRIMARY_WINDOW_MINUTES_HEADER = "x-codex-primary-window-minutes", HEADER_SPEC2, WEEKLY_PRIMARY_HEADER_SPEC;
@@ -15642,6 +15699,7 @@ var init_adapter3 = __esm(() => {
15642
15699
  },
15643
15700
  parseUsageHeaders: parseUsageHeaders2,
15644
15701
  fetchUsage: fetchUsage2,
15702
+ reportsCredits: true,
15645
15703
  classifyError: classifyHttpStatus
15646
15704
  };
15647
15705
  });
@@ -15675,11 +15733,14 @@ async function refreshUsage(adapters, now, poolSnapshot) {
15675
15733
  aliveIds?.add(account.id);
15676
15734
  if (account.disabledReason && account.disabledReason !== MANUAL_DISABLED_REASON)
15677
15735
  continue;
15678
- 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)
15679
15738
  continue;
15680
15739
  const adapter = adapterFor(adapters, account.providerID);
15681
15740
  if (!adapter)
15682
15741
  continue;
15742
+ if (fresh && !adapter.reportsCredits)
15743
+ continue;
15683
15744
  if ((lastPoll.get(account.id) ?? 0) > now - USAGE_REFRESH_TTL_MS)
15684
15745
  continue;
15685
15746
  lastPoll.set(account.id, now);
@@ -15711,6 +15772,8 @@ async function refreshUsage(adapters, now, poolSnapshot) {
15711
15772
  await mutatePool((p) => {
15712
15773
  const stored = findAccount(p, account.id);
15713
15774
  if (stored) {
15775
+ if (snapshot.credits)
15776
+ stored.credits = snapshot.credits;
15714
15777
  stored.usage = {
15715
15778
  hourly: snapshot.hourly ?? stored.usage.hourly,
15716
15779
  weekly: snapshot.weekly === null ? stored.usage.weekly : preserveWeeklyAnchor(snapshot.weekly, stored.usage.weekly, now),
@@ -15763,6 +15826,8 @@ async function bestEffort(what, op) {
15763
15826
  function applyUsagePartial(account, partial, now) {
15764
15827
  if (partial.hourly !== undefined)
15765
15828
  account.usage.hourly = partial.hourly;
15829
+ if (partial.credits !== undefined)
15830
+ account.credits = partial.credits;
15766
15831
  if (partial.weekly !== undefined) {
15767
15832
  account.usage.weekly = preserveWeeklyAnchor(partial.weekly, account.usage.weekly, now);
15768
15833
  account.usage.capturedAt = now;
@@ -15831,7 +15896,7 @@ async function recordSuccess(adapter, accountId, partial, sessionKey, now, cfg,
15831
15896
  if (account) {
15832
15897
  if (partial)
15833
15898
  applyUsagePartial(account, partial, now);
15834
- 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)) {
15835
15900
  account.cooldownUntil = 0;
15836
15901
  delete account.cooldownKind;
15837
15902
  }
@@ -16175,7 +16240,18 @@ function byProviderId(a, b) {
16175
16240
  function isExpired(window, now) {
16176
16241
  return !!window && window.resetAt > 0 && window.resetAt <= now;
16177
16242
  }
16178
- 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) {
16179
16255
  const weekly = account.usage.weekly;
16180
16256
  const weeklyExpired = isExpired(weekly, now);
16181
16257
  return {
@@ -16191,6 +16267,7 @@ function toStatus(account, now, current, available, rank) {
16191
16267
  login: credentialTag(account),
16192
16268
  warnings: loginWarnings(account, now),
16193
16269
  lostNotes: lostLoginNotes(account, now),
16270
+ credits: exhausted ? creditsText(account.credits) : "",
16194
16271
  current,
16195
16272
  rank
16196
16273
  };
@@ -16209,14 +16286,17 @@ function buildStatus(pool, now, cfg = DEFAULT_CONFIG) {
16209
16286
  const currentAccountId = pool.lastSelected[providerID] ?? null;
16210
16287
  const ranked = providerAccounts.map((a) => {
16211
16288
  const available = isAvailable(a, cfg, now);
16289
+ const exhausted = isExhausted(a, cfg, now);
16212
16290
  return {
16213
16291
  a,
16214
16292
  available,
16293
+ exhausted,
16294
+ onCredits: available && exhausted,
16215
16295
  score: available ? scoreAccount(a, cfg, now) : Number.NEGATIVE_INFINITY,
16216
16296
  weeklyUtil: a.usage.weekly?.utilization ?? 0
16217
16297
  };
16218
16298
  }).sort(compareRanked);
16219
- 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));
16220
16300
  return { providerID, accounts };
16221
16301
  });
16222
16302
  }
@@ -16256,12 +16336,10 @@ function stateOf(a, now) {
16256
16336
  if (a.cooldownUntil > now)
16257
16337
  return `cooldown ${relTime(a.cooldownUntil, now)}`;
16258
16338
  if (!a.available)
16259
- return "exhausted";
16339
+ return a.credits ? `exhausted · ${a.credits}` : "exhausted";
16260
16340
  const base = a.current ? "in use" : "ready";
16261
16341
  const tiers = Object.entries(a.modelCooldownsUntil).filter(([, until]) => until > now).sort(([x], [y]) => compareAscii(x, y)).map(([tier, until]) => `${tier} ${relTime(until, now)}`);
16262
- if (tiers.length > 0)
16263
- return `${base} · ${tiers.join(" · ")}`;
16264
- return base;
16342
+ return [base, a.credits, ...tiers].filter(Boolean).join(" · ");
16265
16343
  }
16266
16344
  function providerName(providerID) {
16267
16345
  return PROVIDER_NAMES[providerID] ?? providerID;
@@ -16737,7 +16815,7 @@ class PendingCoordinator {
16737
16815
  await this.notifyPending(ref, recovery);
16738
16816
  try {
16739
16817
  for (;; ) {
16740
- 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);
16741
16819
  const now = this.dependencies.now();
16742
16820
  await this.dependencies.refreshUsage(now).catch(ignore);
16743
16821
  let next;
package/dist/v2-entry.js CHANGED
@@ -41812,6 +41812,18 @@ function normalizeWindow(w) {
41812
41812
  win.resetAt = 0;
41813
41813
  return win;
41814
41814
  }
41815
+ function normalizeCredits(c) {
41816
+ if (!isPlainObject4(c))
41817
+ return;
41818
+ const credits = c;
41819
+ if (typeof credits.hasCredits !== "boolean" || typeof credits.unlimited !== "boolean")
41820
+ return;
41821
+ if (!isFiniteNumber(credits.balance))
41822
+ delete credits.balance;
41823
+ if (credits.limitReached !== true)
41824
+ delete credits.limitReached;
41825
+ return credits;
41826
+ }
41815
41827
  function normalizeAccounts(rows) {
41816
41828
  let accounts = null;
41817
41829
  for (let i = 0;i < rows.length; i++) {
@@ -41831,7 +41843,13 @@ function normalizeAccounts(rows) {
41831
41843
  row.usage.capturedAt = 0;
41832
41844
  row.usage.hourly = normalizeWindow(row.usage.hourly);
41833
41845
  row.usage.weekly = normalizeWindow(row.usage.weekly);
41846
+ delete row.usage.credits;
41834
41847
  }
41848
+ const credits = normalizeCredits(row.credits);
41849
+ if (credits)
41850
+ row.credits = credits;
41851
+ else
41852
+ delete row.credits;
41835
41853
  if (!Number.isFinite(row.cooldownUntil))
41836
41854
  row.cooldownUntil = 0;
41837
41855
  if (row.cooldownKind !== undefined && row.cooldownKind !== "quota" && row.cooldownKind !== "auth" && row.cooldownKind !== "transient")
@@ -42084,14 +42102,19 @@ function maxUtil(account, now) {
42084
42102
  function isExhausted(account, cfg, now) {
42085
42103
  return maxUtil(account, now) >= cfg.exhaustedAt;
42086
42104
  }
42105
+ function hasCreditHeadroom(account) {
42106
+ const credits = account.credits;
42107
+ return !!credits && !credits.limitReached && (credits.unlimited || credits.hasCredits);
42108
+ }
42109
+ function hasHeadroom(account, cfg, now) {
42110
+ return !isExhausted(account, cfg, now) || hasCreditHeadroom(account);
42111
+ }
42087
42112
  function isAvailable(account, cfg, now) {
42088
42113
  if (account.disabledReason)
42089
42114
  return false;
42090
42115
  if (account.cooldownUntil > now)
42091
42116
  return false;
42092
- if (isExhausted(account, cfg, now))
42093
- return false;
42094
- return true;
42117
+ return hasHeadroom(account, cfg, now);
42095
42118
  }
42096
42119
  function weeklyUrgency(account, cfg, now) {
42097
42120
  const weekly = account.usage.weekly;
@@ -42122,9 +42145,11 @@ function scoreAccount(account, cfg, now) {
42122
42145
  function compareRanked(x, y) {
42123
42146
  if (x.available !== y.available)
42124
42147
  return x.available ? -1 : 1;
42125
- if (x.available)
42126
- return y.score - x.score;
42127
- return x.weeklyUtil - y.weeklyUtil;
42148
+ if (!x.available)
42149
+ return x.weeklyUtil - y.weeklyUtil;
42150
+ if (x.onCredits !== y.onCredits)
42151
+ return x.onCredits ? 1 : -1;
42152
+ return y.score - x.score;
42128
42153
  }
42129
42154
  var DAY_MS, WEEK_MS2, HOUR_MS, HOURLY_WINDOW_MS, RESET_CUSHION_DAYS = 0.25, SCORE_DEFAULTS;
42130
42155
  var init_score_core = __esm(() => {
@@ -42146,7 +42171,7 @@ var init_score_core = __esm(() => {
42146
42171
  function reconcileQuotaCooldown(account, cfg, now) {
42147
42172
  if (account.cooldownKind === "auth" || account.cooldownKind === "transient")
42148
42173
  return;
42149
- if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && !isExhausted(account, cfg, now)) {
42174
+ if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
42150
42175
  account.cooldownUntil = 0;
42151
42176
  delete account.cooldownKind;
42152
42177
  }
@@ -42217,6 +42242,8 @@ function clearLostLogin(row, login) {
42217
42242
  function mergeLoginUsage(row, usage) {
42218
42243
  if (!usage)
42219
42244
  return;
42245
+ if (usage.credits)
42246
+ row.credits = usage.credits;
42220
42247
  row.usage = {
42221
42248
  hourly: usage.hourly ?? row.usage.hourly,
42222
42249
  weekly: usage.weekly ? preserveWeeklyAnchor(usage.weekly, row.usage.weekly, usage.capturedAt) : row.usage.weekly,
@@ -42342,7 +42369,8 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
42342
42369
  continue;
42343
42370
  const windows = exhaustedWindows(account, cfg, now);
42344
42371
  const cooldownActive = account.cooldownUntil > now;
42345
- if (!cooldownActive && windows.length === 0)
42372
+ const blocking = hasCreditHeadroom(account) ? [] : windows;
42373
+ if (!cooldownActive && blocking.length === 0)
42346
42374
  return { state: "available" };
42347
42375
  if (cooldownActive && (account.cooldownKind === "auth" || account.cooldownKind === "transient"))
42348
42376
  continue;
@@ -42351,7 +42379,7 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
42351
42379
  hasQuotaCandidate = true;
42352
42380
  const constraints = [];
42353
42381
  let accountHasUnknownReset = false;
42354
- for (const window of windows) {
42382
+ for (const window of blocking) {
42355
42383
  if (window.resetAt > now)
42356
42384
  constraints.push(window.resetAt);
42357
42385
  else
@@ -42664,22 +42692,27 @@ function weeklyUtil(account) {
42664
42692
  function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude = NO_EXCLUSIONS, incumbentId = null) {
42665
42693
  let bestAvail = null;
42666
42694
  let bestAvailScore = Number.NEGATIVE_INFINITY;
42695
+ let bestAvailOnCredits = true;
42667
42696
  let bestFallback = null;
42668
42697
  let lowestWeeklyUtil = Number.POSITIVE_INFINITY;
42669
42698
  let incumbentAccount = null;
42670
42699
  let incumbentScore = Number.NEGATIVE_INFINITY;
42700
+ let incumbentOnCredits = false;
42671
42701
  for (const account of accounts) {
42672
42702
  if (account.providerID !== providerID || account.disabledReason || exclude.has(account.id))
42673
42703
  continue;
42674
42704
  if (isAvailable(account, cfg, now)) {
42675
42705
  const score = scoreAccount(account, cfg, now);
42676
- if (score > bestAvailScore) {
42706
+ const onCredits = isExhausted(account, cfg, now);
42707
+ if (onCredits === bestAvailOnCredits ? score > bestAvailScore : bestAvailOnCredits) {
42677
42708
  bestAvailScore = score;
42678
42709
  bestAvail = account;
42710
+ bestAvailOnCredits = onCredits;
42679
42711
  }
42680
42712
  if (account.id === incumbentId) {
42681
42713
  incumbentAccount = account;
42682
42714
  incumbentScore = score;
42715
+ incumbentOnCredits = onCredits;
42683
42716
  }
42684
42717
  } else {
42685
42718
  const util = weeklyUtil(account);
@@ -42690,7 +42723,7 @@ function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude
42690
42723
  }
42691
42724
  }
42692
42725
  if (bestAvail) {
42693
- if (incumbentAccount && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
42726
+ if (incumbentAccount && incumbentOnCredits === bestAvailOnCredits && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
42694
42727
  return { account: incumbentAccount, degraded: false, sticky: false };
42695
42728
  return { account: bestAvail, degraded: false, sticky: false };
42696
42729
  }
@@ -42729,7 +42762,7 @@ function selectForSession(pool, providerID, sessionKey, now, cfg = DEFAULT_CONFI
42729
42762
  const cheap = (!pinnedImminent || cfg.drainMigrate) && isCheapMoment(typeof requestBytes === "function" ? requestBytes() : requestBytes, cfg);
42730
42763
  if (pinnedOverSoft && (pinnedImminent || cheap) || cfg.drainMigrate && cheap) {
42731
42764
  const alt = selectAccount(pool.accounts, providerID, now, cfg, withExcluded(exclude, pinned.id), null);
42732
- if (alt && !alt.degraded) {
42765
+ if (alt && !alt.degraded && !isExhausted(alt.account, cfg, now)) {
42733
42766
  if (pinnedOverSoft) {
42734
42767
  const altUtil = maxUtil(alt.account, now);
42735
42768
  const proactiveBetter = pinnedImminent ? altUtil < pinnedUtil : pinnedUtil - altUtil >= PROACTIVE_MIGRATE_MIN_DELTA && weeklyUrgency(alt.account, cfg, now) >= weeklyUrgency(pinned, cfg, now);
@@ -44327,10 +44360,31 @@ var init_transform2 = __esm(() => {
44327
44360
  function isWeeklyMinutes(minutes) {
44328
44361
  return minutes >= WEEK_MINUTES * 0.95 && minutes <= WEEK_MINUTES * 1.05;
44329
44362
  }
44363
+ function creditsFrom(hasCredits, unlimited, balance, limitReached) {
44364
+ if (typeof hasCredits !== "boolean" || typeof unlimited !== "boolean")
44365
+ return;
44366
+ const amount = typeof balance === "string" && balance.trim() !== "" ? Number(balance) : Number.NaN;
44367
+ return {
44368
+ hasCredits,
44369
+ unlimited,
44370
+ ...Number.isFinite(amount) ? { balance: amount } : {},
44371
+ ...limitReached === true ? { limitReached } : {}
44372
+ };
44373
+ }
44374
+ function headerBool(raw) {
44375
+ const value = raw?.toLowerCase();
44376
+ if (value === "true" || value === "1")
44377
+ return true;
44378
+ if (value === "false" || value === "0")
44379
+ return false;
44380
+ return;
44381
+ }
44330
44382
  function parseUsageHeaders2(headers) {
44331
44383
  const primaryWindowMinutes = Number(headers.get(PRIMARY_WINDOW_MINUTES_HEADER));
44332
44384
  const spec = isWeeklyMinutes(primaryWindowMinutes) ? WEEKLY_PRIMARY_HEADER_SPEC : HEADER_SPEC2;
44333
- return parseWindowPairHeaders(headers, spec);
44385
+ const windows = parseWindowPairHeaders(headers, spec);
44386
+ const credits = creditsFrom(headerBool(headers.get("x-codex-credits-has-credits")), headerBool(headers.get("x-codex-credits-unlimited")), headers.get("x-codex-credits-balance"));
44387
+ return credits ? { ...windows, credits } : windows;
44334
44388
  }
44335
44389
  function endpointWindow2(w) {
44336
44390
  return endpointWindowFrom(w, (win) => win.used_percent, 100, (win) => secondsToMs(Number(win.reset_at ?? 0)));
@@ -44350,10 +44404,13 @@ async function fetchUsage2(account, now) {
44350
44404
  return null;
44351
44405
  const { primary_window: primary, secondary_window: secondary } = rateLimit;
44352
44406
  const primaryIsWeekly = isWeeklyMinutes(Math.ceil(Number(primary?.limit_window_seconds) / 60));
44407
+ const reported = json?.credits;
44408
+ const credits = isPlainObject4(reported) ? creditsFrom(reported.has_credits, reported.unlimited, reported.balance, reported.overage_limit_reached) : undefined;
44353
44409
  return {
44354
44410
  hourly: endpointWindow2(primaryIsWeekly ? secondary : primary),
44355
44411
  weekly: endpointWindow2(primaryIsWeekly ? primary : secondary),
44356
- capturedAt: now
44412
+ capturedAt: now,
44413
+ ...credits ? { credits } : {}
44357
44414
  };
44358
44415
  }
44359
44416
  var WEEK_MINUTES, PRIMARY_WINDOW_MINUTES_HEADER = "x-codex-primary-window-minutes", HEADER_SPEC2, WEEKLY_PRIMARY_HEADER_SPEC;
@@ -44400,6 +44457,7 @@ var init_adapter3 = __esm(() => {
44400
44457
  },
44401
44458
  parseUsageHeaders: parseUsageHeaders2,
44402
44459
  fetchUsage: fetchUsage2,
44460
+ reportsCredits: true,
44403
44461
  classifyError: classifyHttpStatus
44404
44462
  };
44405
44463
  });
@@ -44433,11 +44491,14 @@ async function refreshUsage(adapters, now, poolSnapshot) {
44433
44491
  aliveIds?.add(account.id);
44434
44492
  if (account.disabledReason && account.disabledReason !== MANUAL_DISABLED_REASON)
44435
44493
  continue;
44436
- if (account.usage.capturedAt !== 0 && now - account.usage.capturedAt < USAGE_REFRESH_TTL_MS)
44494
+ const fresh = account.usage.capturedAt !== 0 && now - account.usage.capturedAt < USAGE_REFRESH_TTL_MS;
44495
+ if (fresh && account.credits)
44437
44496
  continue;
44438
44497
  const adapter = adapterFor(adapters, account.providerID);
44439
44498
  if (!adapter)
44440
44499
  continue;
44500
+ if (fresh && !adapter.reportsCredits)
44501
+ continue;
44441
44502
  if ((lastPoll.get(account.id) ?? 0) > now - USAGE_REFRESH_TTL_MS)
44442
44503
  continue;
44443
44504
  lastPoll.set(account.id, now);
@@ -44469,6 +44530,8 @@ async function refreshUsage(adapters, now, poolSnapshot) {
44469
44530
  await mutatePool((p) => {
44470
44531
  const stored = findAccount(p, account.id);
44471
44532
  if (stored) {
44533
+ if (snapshot.credits)
44534
+ stored.credits = snapshot.credits;
44472
44535
  stored.usage = {
44473
44536
  hourly: snapshot.hourly ?? stored.usage.hourly,
44474
44537
  weekly: snapshot.weekly === null ? stored.usage.weekly : preserveWeeklyAnchor(snapshot.weekly, stored.usage.weekly, now),
@@ -44521,6 +44584,8 @@ async function bestEffort(what, op) {
44521
44584
  function applyUsagePartial(account, partial, now) {
44522
44585
  if (partial.hourly !== undefined)
44523
44586
  account.usage.hourly = partial.hourly;
44587
+ if (partial.credits !== undefined)
44588
+ account.credits = partial.credits;
44524
44589
  if (partial.weekly !== undefined) {
44525
44590
  account.usage.weekly = preserveWeeklyAnchor(partial.weekly, account.usage.weekly, now);
44526
44591
  account.usage.capturedAt = now;
@@ -44589,7 +44654,7 @@ async function recordSuccess(adapter, accountId, partial, sessionKey, now, cfg,
44589
44654
  if (account) {
44590
44655
  if (partial)
44591
44656
  applyUsagePartial(account, partial, now);
44592
- if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS && !isExhausted(account, cfg, now)) {
44657
+ if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
44593
44658
  account.cooldownUntil = 0;
44594
44659
  delete account.cooldownKind;
44595
44660
  }
@@ -44933,7 +44998,18 @@ function byProviderId(a, b) {
44933
44998
  function isExpired(window, now) {
44934
44999
  return !!window && window.resetAt > 0 && window.resetAt <= now;
44935
45000
  }
44936
- function toStatus(account, now, current, available, rank) {
45001
+ function creditsText(credits) {
45002
+ if (!credits)
45003
+ return "";
45004
+ if (credits.limitReached)
45005
+ return "credit limit reached";
45006
+ if (credits.unlimited)
45007
+ return "credits unlimited";
45008
+ if (!credits.hasCredits)
45009
+ return "no credits";
45010
+ return credits.balance === undefined ? "credits available" : `credits ${Math.round(credits.balance * 100) / 100}`;
45011
+ }
45012
+ function toStatus(account, now, current, available, exhausted, rank) {
44937
45013
  const weekly = account.usage.weekly;
44938
45014
  const weeklyExpired = isExpired(weekly, now);
44939
45015
  return {
@@ -44949,6 +45025,7 @@ function toStatus(account, now, current, available, rank) {
44949
45025
  login: credentialTag(account),
44950
45026
  warnings: loginWarnings(account, now),
44951
45027
  lostNotes: lostLoginNotes(account, now),
45028
+ credits: exhausted ? creditsText(account.credits) : "",
44952
45029
  current,
44953
45030
  rank
44954
45031
  };
@@ -44967,14 +45044,17 @@ function buildStatus(pool, now, cfg = DEFAULT_CONFIG) {
44967
45044
  const currentAccountId = pool.lastSelected[providerID] ?? null;
44968
45045
  const ranked = providerAccounts.map((a) => {
44969
45046
  const available = isAvailable(a, cfg, now);
45047
+ const exhausted = isExhausted(a, cfg, now);
44970
45048
  return {
44971
45049
  a,
44972
45050
  available,
45051
+ exhausted,
45052
+ onCredits: available && exhausted,
44973
45053
  score: available ? scoreAccount(a, cfg, now) : Number.NEGATIVE_INFINITY,
44974
45054
  weeklyUtil: a.usage.weekly?.utilization ?? 0
44975
45055
  };
44976
45056
  }).sort(compareRanked);
44977
- const accounts = ranked.map(({ a, available }, i) => toStatus(a, now, a.id === currentAccountId, available, i + 1));
45057
+ const accounts = ranked.map(({ a, available, exhausted }, i) => toStatus(a, now, a.id === currentAccountId, available, exhausted, i + 1));
44978
45058
  return { providerID, accounts };
44979
45059
  });
44980
45060
  }
@@ -45014,12 +45094,10 @@ function stateOf(a, now) {
45014
45094
  if (a.cooldownUntil > now)
45015
45095
  return `cooldown ${relTime(a.cooldownUntil, now)}`;
45016
45096
  if (!a.available)
45017
- return "exhausted";
45097
+ return a.credits ? `exhausted · ${a.credits}` : "exhausted";
45018
45098
  const base = a.current ? "in use" : "ready";
45019
45099
  const tiers = Object.entries(a.modelCooldownsUntil).filter(([, until]) => until > now).sort(([x], [y]) => compareAscii(x, y)).map(([tier, until]) => `${tier} ${relTime(until, now)}`);
45020
- if (tiers.length > 0)
45021
- return `${base} · ${tiers.join(" · ")}`;
45022
- return base;
45100
+ return [base, a.credits, ...tiers].filter(Boolean).join(" · ");
45023
45101
  }
45024
45102
  function providerName(providerID) {
45025
45103
  return PROVIDER_NAMES[providerID] ?? providerID;
@@ -45495,7 +45573,7 @@ class PendingCoordinator {
45495
45573
  await this.notifyPending(ref, recovery);
45496
45574
  try {
45497
45575
  for (;; ) {
45498
- await this.dependencies.sleep(Math.max(0, recovery.nextCheckAt - this.dependencies.now()), controller.signal);
45576
+ await this.dependencies.sleep(Math.max(0, Math.min(recovery.nextCheckAt - this.dependencies.now(), USAGE_REFRESH_TTL_MS)), controller.signal);
45499
45577
  const now = this.dependencies.now();
45500
45578
  await this.dependencies.refreshUsage(now).catch(ignore);
45501
45579
  let next;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auth-load-balancer",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Load-balance opencode requests across multiple Anthropic (Claude) and OpenAI (Codex) OAuth accounts and Kimi Code subscriptions, weighted primarily by weekly usage with drain-before-reset scheduling.",
5
5
  "type": "module",
6
6
  "license": "MIT",