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/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
|
-
|
|
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
|
|
42127
|
-
|
|
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(() => {
|
|
@@ -42143,6 +42168,14 @@ var init_score_core = __esm(() => {
|
|
|
42143
42168
|
});
|
|
42144
42169
|
|
|
42145
42170
|
// src/usage-merge.ts
|
|
42171
|
+
function reconcileQuotaCooldown(account, cfg, now) {
|
|
42172
|
+
if (account.cooldownKind === "auth" || account.cooldownKind === "transient")
|
|
42173
|
+
return;
|
|
42174
|
+
if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
42175
|
+
account.cooldownUntil = 0;
|
|
42176
|
+
delete account.cooldownKind;
|
|
42177
|
+
}
|
|
42178
|
+
}
|
|
42146
42179
|
function rollWeeklyAnchorForward(anchor, now) {
|
|
42147
42180
|
if (!Number.isFinite(anchor) || anchor <= 0)
|
|
42148
42181
|
return 0;
|
|
@@ -42159,8 +42192,10 @@ function preserveWeeklyAnchor(incoming, stored, now) {
|
|
|
42159
42192
|
return incoming;
|
|
42160
42193
|
return { utilization: incoming.utilization, resetAt: anchor };
|
|
42161
42194
|
}
|
|
42195
|
+
var MAX_TRANSIENT_COOLDOWN_MS;
|
|
42162
42196
|
var init_usage_merge = __esm(() => {
|
|
42163
42197
|
init_score_core();
|
|
42198
|
+
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
42164
42199
|
});
|
|
42165
42200
|
|
|
42166
42201
|
// src/accounts.ts
|
|
@@ -42207,11 +42242,15 @@ function clearLostLogin(row, login) {
|
|
|
42207
42242
|
function mergeLoginUsage(row, usage) {
|
|
42208
42243
|
if (!usage)
|
|
42209
42244
|
return;
|
|
42245
|
+
if (usage.credits)
|
|
42246
|
+
row.credits = usage.credits;
|
|
42210
42247
|
row.usage = {
|
|
42211
42248
|
hourly: usage.hourly ?? row.usage.hourly,
|
|
42212
42249
|
weekly: usage.weekly ? preserveWeeklyAnchor(usage.weekly, row.usage.weekly, usage.capturedAt) : row.usage.weekly,
|
|
42213
42250
|
capturedAt: usage.weekly ? usage.capturedAt : row.usage.capturedAt
|
|
42214
42251
|
};
|
|
42252
|
+
if (usage.weekly)
|
|
42253
|
+
reconcileQuotaCooldown(row, loadScoreConfig(), usage.capturedAt);
|
|
42215
42254
|
}
|
|
42216
42255
|
function applyTokens(row, tokens) {
|
|
42217
42256
|
if (tokens.refresh) {
|
|
@@ -42305,6 +42344,7 @@ async function bootstrapFromOpencodeAuth(providerID, getAuth, tokensFromApiKey)
|
|
|
42305
42344
|
var init_accounts = __esm(() => {
|
|
42306
42345
|
init_pairing();
|
|
42307
42346
|
init_store();
|
|
42347
|
+
init_score_core();
|
|
42308
42348
|
init_types2();
|
|
42309
42349
|
init_usage_merge();
|
|
42310
42350
|
});
|
|
@@ -42329,7 +42369,8 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
42329
42369
|
continue;
|
|
42330
42370
|
const windows = exhaustedWindows(account, cfg, now);
|
|
42331
42371
|
const cooldownActive = account.cooldownUntil > now;
|
|
42332
|
-
|
|
42372
|
+
const blocking = hasCreditHeadroom(account) ? [] : windows;
|
|
42373
|
+
if (!cooldownActive && blocking.length === 0)
|
|
42333
42374
|
return { state: "available" };
|
|
42334
42375
|
if (cooldownActive && (account.cooldownKind === "auth" || account.cooldownKind === "transient"))
|
|
42335
42376
|
continue;
|
|
@@ -42338,7 +42379,7 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
42338
42379
|
hasQuotaCandidate = true;
|
|
42339
42380
|
const constraints = [];
|
|
42340
42381
|
let accountHasUnknownReset = false;
|
|
42341
|
-
for (const window of
|
|
42382
|
+
for (const window of blocking) {
|
|
42342
42383
|
if (window.resetAt > now)
|
|
42343
42384
|
constraints.push(window.resetAt);
|
|
42344
42385
|
else
|
|
@@ -42651,22 +42692,27 @@ function weeklyUtil(account) {
|
|
|
42651
42692
|
function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude = NO_EXCLUSIONS, incumbentId = null) {
|
|
42652
42693
|
let bestAvail = null;
|
|
42653
42694
|
let bestAvailScore = Number.NEGATIVE_INFINITY;
|
|
42695
|
+
let bestAvailOnCredits = true;
|
|
42654
42696
|
let bestFallback = null;
|
|
42655
42697
|
let lowestWeeklyUtil = Number.POSITIVE_INFINITY;
|
|
42656
42698
|
let incumbentAccount = null;
|
|
42657
42699
|
let incumbentScore = Number.NEGATIVE_INFINITY;
|
|
42700
|
+
let incumbentOnCredits = false;
|
|
42658
42701
|
for (const account of accounts) {
|
|
42659
42702
|
if (account.providerID !== providerID || account.disabledReason || exclude.has(account.id))
|
|
42660
42703
|
continue;
|
|
42661
42704
|
if (isAvailable(account, cfg, now)) {
|
|
42662
42705
|
const score = scoreAccount(account, cfg, now);
|
|
42663
|
-
|
|
42706
|
+
const onCredits = isExhausted(account, cfg, now);
|
|
42707
|
+
if (onCredits === bestAvailOnCredits ? score > bestAvailScore : bestAvailOnCredits) {
|
|
42664
42708
|
bestAvailScore = score;
|
|
42665
42709
|
bestAvail = account;
|
|
42710
|
+
bestAvailOnCredits = onCredits;
|
|
42666
42711
|
}
|
|
42667
42712
|
if (account.id === incumbentId) {
|
|
42668
42713
|
incumbentAccount = account;
|
|
42669
42714
|
incumbentScore = score;
|
|
42715
|
+
incumbentOnCredits = onCredits;
|
|
42670
42716
|
}
|
|
42671
42717
|
} else {
|
|
42672
42718
|
const util = weeklyUtil(account);
|
|
@@ -42677,7 +42723,7 @@ function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude
|
|
|
42677
42723
|
}
|
|
42678
42724
|
}
|
|
42679
42725
|
if (bestAvail) {
|
|
42680
|
-
if (incumbentAccount && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
42726
|
+
if (incumbentAccount && incumbentOnCredits === bestAvailOnCredits && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
42681
42727
|
return { account: incumbentAccount, degraded: false, sticky: false };
|
|
42682
42728
|
return { account: bestAvail, degraded: false, sticky: false };
|
|
42683
42729
|
}
|
|
@@ -42716,7 +42762,7 @@ function selectForSession(pool, providerID, sessionKey, now, cfg = DEFAULT_CONFI
|
|
|
42716
42762
|
const cheap = (!pinnedImminent || cfg.drainMigrate) && isCheapMoment(typeof requestBytes === "function" ? requestBytes() : requestBytes, cfg);
|
|
42717
42763
|
if (pinnedOverSoft && (pinnedImminent || cheap) || cfg.drainMigrate && cheap) {
|
|
42718
42764
|
const alt = selectAccount(pool.accounts, providerID, now, cfg, withExcluded(exclude, pinned.id), null);
|
|
42719
|
-
if (alt && !alt.degraded) {
|
|
42765
|
+
if (alt && !alt.degraded && !isExhausted(alt.account, cfg, now)) {
|
|
42720
42766
|
if (pinnedOverSoft) {
|
|
42721
42767
|
const altUtil = maxUtil(alt.account, now);
|
|
42722
42768
|
const proactiveBetter = pinnedImminent ? altUtil < pinnedUtil : pinnedUtil - altUtil >= PROACTIVE_MIGRATE_MIN_DELTA && weeklyUrgency(alt.account, cfg, now) >= weeklyUrgency(pinned, cfg, now);
|
|
@@ -43246,24 +43292,46 @@ var init_usage_headers = __esm(() => {
|
|
|
43246
43292
|
});
|
|
43247
43293
|
|
|
43248
43294
|
// src/providers/usage-http.ts
|
|
43249
|
-
async function
|
|
43250
|
-
let response;
|
|
43295
|
+
async function request(url, headers, timeoutMs) {
|
|
43251
43296
|
try {
|
|
43252
|
-
|
|
43297
|
+
return await fetch(url, {
|
|
43253
43298
|
headers,
|
|
43254
43299
|
signal: AbortSignal.timeout(timeoutMs)
|
|
43255
43300
|
});
|
|
43256
43301
|
} catch {
|
|
43257
43302
|
return null;
|
|
43258
43303
|
}
|
|
43304
|
+
}
|
|
43305
|
+
async function readJson(response) {
|
|
43306
|
+
if (!response)
|
|
43307
|
+
return null;
|
|
43259
43308
|
if (!response.ok) {
|
|
43260
43309
|
await response.body?.cancel().catch(ignore);
|
|
43261
43310
|
return null;
|
|
43262
43311
|
}
|
|
43263
43312
|
return await response.json().catch(() => null);
|
|
43264
43313
|
}
|
|
43314
|
+
async function fetchJson(url, headers, timeoutMs) {
|
|
43315
|
+
return readJson(await request(url, headers, timeoutMs));
|
|
43316
|
+
}
|
|
43317
|
+
async function fetchUsageJson(url, headers, timeoutMs) {
|
|
43318
|
+
const response = await request(url, headers, timeoutMs);
|
|
43319
|
+
if (response?.status === 401) {
|
|
43320
|
+
await response.body?.cancel().catch(ignore);
|
|
43321
|
+
throw new UsageAuthenticationError;
|
|
43322
|
+
}
|
|
43323
|
+
return readJson(response);
|
|
43324
|
+
}
|
|
43325
|
+
var UsageAuthenticationError;
|
|
43265
43326
|
var init_usage_http = __esm(() => {
|
|
43266
43327
|
init_util4();
|
|
43328
|
+
UsageAuthenticationError = class UsageAuthenticationError extends Error {
|
|
43329
|
+
status = 401;
|
|
43330
|
+
constructor() {
|
|
43331
|
+
super("Usage endpoint rejected the access token (401)");
|
|
43332
|
+
this.name = "UsageAuthenticationError";
|
|
43333
|
+
}
|
|
43334
|
+
};
|
|
43267
43335
|
});
|
|
43268
43336
|
|
|
43269
43337
|
// src/providers/anthropic/version.ts
|
|
@@ -43703,7 +43771,7 @@ async function probeUsage(access, now) {
|
|
|
43703
43771
|
async function fetchUsage(account, now) {
|
|
43704
43772
|
if (!account.refresh)
|
|
43705
43773
|
return probeUsage(account.access, now);
|
|
43706
|
-
const json = await
|
|
43774
|
+
const json = await fetchUsageJson(USAGE_URL, {
|
|
43707
43775
|
authorization: `Bearer ${account.access}`,
|
|
43708
43776
|
"anthropic-beta": "oauth-2025-04-20",
|
|
43709
43777
|
"user-agent": usageUserAgent(),
|
|
@@ -44035,7 +44103,7 @@ function parseUsages(json, now) {
|
|
|
44035
44103
|
return hourly || weekly ? { hourly, weekly, capturedAt: now } : null;
|
|
44036
44104
|
}
|
|
44037
44105
|
function getUsages(baseUrl, key) {
|
|
44038
|
-
return
|
|
44106
|
+
return fetchUsageJson(`${baseUrl}/usages`, { authorization: `Bearer ${key}`, accept: "application/json" }, USAGE_HTTP_TIMEOUT_MS);
|
|
44039
44107
|
}
|
|
44040
44108
|
function usageFetcher(baseUrl) {
|
|
44041
44109
|
return async (account, now) => parseUsages(await getUsages(baseUrl, account.access), now);
|
|
@@ -44292,10 +44360,31 @@ var init_transform2 = __esm(() => {
|
|
|
44292
44360
|
function isWeeklyMinutes(minutes) {
|
|
44293
44361
|
return minutes >= WEEK_MINUTES * 0.95 && minutes <= WEEK_MINUTES * 1.05;
|
|
44294
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
|
+
}
|
|
44295
44382
|
function parseUsageHeaders2(headers) {
|
|
44296
44383
|
const primaryWindowMinutes = Number(headers.get(PRIMARY_WINDOW_MINUTES_HEADER));
|
|
44297
44384
|
const spec = isWeeklyMinutes(primaryWindowMinutes) ? WEEKLY_PRIMARY_HEADER_SPEC : HEADER_SPEC2;
|
|
44298
|
-
|
|
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;
|
|
44299
44388
|
}
|
|
44300
44389
|
function endpointWindow2(w) {
|
|
44301
44390
|
return endpointWindowFrom(w, (win) => win.used_percent, 100, (win) => secondsToMs(Number(win.reset_at ?? 0)));
|
|
@@ -44309,16 +44398,19 @@ async function fetchUsage2(account, now) {
|
|
|
44309
44398
|
const accountId = resolveAccountId(account);
|
|
44310
44399
|
if (accountId)
|
|
44311
44400
|
headers["chatgpt-account-id"] = accountId;
|
|
44312
|
-
const json = await
|
|
44401
|
+
const json = await fetchUsageJson(USAGE_URL2, headers, USAGE_HTTP_TIMEOUT_MS);
|
|
44313
44402
|
const rateLimit = json?.rate_limit;
|
|
44314
44403
|
if (!isPlainObject4(rateLimit))
|
|
44315
44404
|
return null;
|
|
44316
44405
|
const { primary_window: primary, secondary_window: secondary } = rateLimit;
|
|
44317
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;
|
|
44318
44409
|
return {
|
|
44319
44410
|
hourly: endpointWindow2(primaryIsWeekly ? secondary : primary),
|
|
44320
44411
|
weekly: endpointWindow2(primaryIsWeekly ? primary : secondary),
|
|
44321
|
-
capturedAt: now
|
|
44412
|
+
capturedAt: now,
|
|
44413
|
+
...credits ? { credits } : {}
|
|
44322
44414
|
};
|
|
44323
44415
|
}
|
|
44324
44416
|
var WEEK_MINUTES, PRIMARY_WINDOW_MINUTES_HEADER = "x-codex-primary-window-minutes", HEADER_SPEC2, WEEKLY_PRIMARY_HEADER_SPEC;
|
|
@@ -44365,6 +44457,7 @@ var init_adapter3 = __esm(() => {
|
|
|
44365
44457
|
},
|
|
44366
44458
|
parseUsageHeaders: parseUsageHeaders2,
|
|
44367
44459
|
fetchUsage: fetchUsage2,
|
|
44460
|
+
reportsCredits: true,
|
|
44368
44461
|
classifyError: classifyHttpStatus
|
|
44369
44462
|
};
|
|
44370
44463
|
});
|
|
@@ -44398,11 +44491,14 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
44398
44491
|
aliveIds?.add(account.id);
|
|
44399
44492
|
if (account.disabledReason && account.disabledReason !== MANUAL_DISABLED_REASON)
|
|
44400
44493
|
continue;
|
|
44401
|
-
|
|
44494
|
+
const fresh = account.usage.capturedAt !== 0 && now - account.usage.capturedAt < USAGE_REFRESH_TTL_MS;
|
|
44495
|
+
if (fresh && account.credits)
|
|
44402
44496
|
continue;
|
|
44403
44497
|
const adapter = adapterFor(adapters, account.providerID);
|
|
44404
44498
|
if (!adapter)
|
|
44405
44499
|
continue;
|
|
44500
|
+
if (fresh && !adapter.reportsCredits)
|
|
44501
|
+
continue;
|
|
44406
44502
|
if ((lastPoll.get(account.id) ?? 0) > now - USAGE_REFRESH_TTL_MS)
|
|
44407
44503
|
continue;
|
|
44408
44504
|
lastPoll.set(account.id, now);
|
|
@@ -44420,20 +44516,29 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
44420
44516
|
await Promise.all(stale.map(async ({ adapter, account }) => {
|
|
44421
44517
|
try {
|
|
44422
44518
|
await ensureAccessToken(adapter, account, now);
|
|
44423
|
-
const
|
|
44519
|
+
const rejectedAccess = account.access;
|
|
44520
|
+
let snapshot;
|
|
44521
|
+
try {
|
|
44522
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
44523
|
+
} catch (error) {
|
|
44524
|
+
if (!(error instanceof UsageAuthenticationError) || !account.refresh)
|
|
44525
|
+
throw error;
|
|
44526
|
+
await ensureAccessToken(adapter, account, now, rejectedAccess);
|
|
44527
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
44528
|
+
}
|
|
44424
44529
|
if (snapshot) {
|
|
44425
44530
|
await mutatePool((p) => {
|
|
44426
44531
|
const stored = findAccount(p, account.id);
|
|
44427
44532
|
if (stored) {
|
|
44533
|
+
if (snapshot.credits)
|
|
44534
|
+
stored.credits = snapshot.credits;
|
|
44428
44535
|
stored.usage = {
|
|
44429
44536
|
hourly: snapshot.hourly ?? stored.usage.hourly,
|
|
44430
44537
|
weekly: snapshot.weekly === null ? stored.usage.weekly : preserveWeeklyAnchor(snapshot.weekly, stored.usage.weekly, now),
|
|
44431
44538
|
capturedAt: snapshot.weekly === null ? stored.usage.capturedAt : now
|
|
44432
44539
|
};
|
|
44433
|
-
if (snapshot.weekly !== null && stored.cooldownUntil
|
|
44434
|
-
stored
|
|
44435
|
-
delete stored.cooldownKind;
|
|
44436
|
-
}
|
|
44540
|
+
if (snapshot.weekly !== null && stored.cooldownUntil === account.cooldownUntil && stored.cooldownKind === account.cooldownKind)
|
|
44541
|
+
reconcileQuotaCooldown(stored, scoreCfg, now);
|
|
44437
44542
|
}
|
|
44438
44543
|
});
|
|
44439
44544
|
}
|
|
@@ -44446,16 +44551,17 @@ function refreshUsageInBackground(adapter, now, poolSnapshot) {
|
|
|
44446
44551
|
function refreshAllUsageInBackground(now, poolSnapshot) {
|
|
44447
44552
|
return refreshUsage(ADAPTERS, now, poolSnapshot);
|
|
44448
44553
|
}
|
|
44449
|
-
var USAGE_REFRESH_TTL_MS,
|
|
44554
|
+
var USAGE_REFRESH_TTL_MS, lastPoll;
|
|
44450
44555
|
var init_usage_refresh = __esm(() => {
|
|
44451
44556
|
init_store();
|
|
44452
44557
|
init_registry();
|
|
44558
|
+
init_usage_http();
|
|
44453
44559
|
init_refresh();
|
|
44454
44560
|
init_score_core();
|
|
44455
44561
|
init_types2();
|
|
44456
44562
|
init_usage_merge();
|
|
44563
|
+
init_usage_merge();
|
|
44457
44564
|
USAGE_REFRESH_TTL_MS = 5 * 60 * 1000;
|
|
44458
|
-
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
44459
44565
|
lastPoll = new Map;
|
|
44460
44566
|
});
|
|
44461
44567
|
|
|
@@ -44478,6 +44584,8 @@ async function bestEffort(what, op) {
|
|
|
44478
44584
|
function applyUsagePartial(account, partial, now) {
|
|
44479
44585
|
if (partial.hourly !== undefined)
|
|
44480
44586
|
account.usage.hourly = partial.hourly;
|
|
44587
|
+
if (partial.credits !== undefined)
|
|
44588
|
+
account.credits = partial.credits;
|
|
44481
44589
|
if (partial.weekly !== undefined) {
|
|
44482
44590
|
account.usage.weekly = preserveWeeklyAnchor(partial.weekly, account.usage.weekly, now);
|
|
44483
44591
|
account.usage.capturedAt = now;
|
|
@@ -44546,7 +44654,7 @@ async function recordSuccess(adapter, accountId, partial, sessionKey, now, cfg,
|
|
|
44546
44654
|
if (account) {
|
|
44547
44655
|
if (partial)
|
|
44548
44656
|
applyUsagePartial(account, partial, now);
|
|
44549
|
-
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS &&
|
|
44657
|
+
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
44550
44658
|
account.cooldownUntil = 0;
|
|
44551
44659
|
delete account.cooldownKind;
|
|
44552
44660
|
}
|
|
@@ -44890,7 +44998,18 @@ function byProviderId(a, b) {
|
|
|
44890
44998
|
function isExpired(window, now) {
|
|
44891
44999
|
return !!window && window.resetAt > 0 && window.resetAt <= now;
|
|
44892
45000
|
}
|
|
44893
|
-
function
|
|
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) {
|
|
44894
45013
|
const weekly = account.usage.weekly;
|
|
44895
45014
|
const weeklyExpired = isExpired(weekly, now);
|
|
44896
45015
|
return {
|
|
@@ -44906,6 +45025,7 @@ function toStatus(account, now, current, available, rank) {
|
|
|
44906
45025
|
login: credentialTag(account),
|
|
44907
45026
|
warnings: loginWarnings(account, now),
|
|
44908
45027
|
lostNotes: lostLoginNotes(account, now),
|
|
45028
|
+
credits: exhausted ? creditsText(account.credits) : "",
|
|
44909
45029
|
current,
|
|
44910
45030
|
rank
|
|
44911
45031
|
};
|
|
@@ -44924,14 +45044,17 @@ function buildStatus(pool, now, cfg = DEFAULT_CONFIG) {
|
|
|
44924
45044
|
const currentAccountId = pool.lastSelected[providerID] ?? null;
|
|
44925
45045
|
const ranked = providerAccounts.map((a) => {
|
|
44926
45046
|
const available = isAvailable(a, cfg, now);
|
|
45047
|
+
const exhausted = isExhausted(a, cfg, now);
|
|
44927
45048
|
return {
|
|
44928
45049
|
a,
|
|
44929
45050
|
available,
|
|
45051
|
+
exhausted,
|
|
45052
|
+
onCredits: available && exhausted,
|
|
44930
45053
|
score: available ? scoreAccount(a, cfg, now) : Number.NEGATIVE_INFINITY,
|
|
44931
45054
|
weeklyUtil: a.usage.weekly?.utilization ?? 0
|
|
44932
45055
|
};
|
|
44933
45056
|
}).sort(compareRanked);
|
|
44934
|
-
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));
|
|
44935
45058
|
return { providerID, accounts };
|
|
44936
45059
|
});
|
|
44937
45060
|
}
|
|
@@ -44971,12 +45094,10 @@ function stateOf(a, now) {
|
|
|
44971
45094
|
if (a.cooldownUntil > now)
|
|
44972
45095
|
return `cooldown ${relTime(a.cooldownUntil, now)}`;
|
|
44973
45096
|
if (!a.available)
|
|
44974
|
-
return "exhausted";
|
|
45097
|
+
return a.credits ? `exhausted · ${a.credits}` : "exhausted";
|
|
44975
45098
|
const base = a.current ? "in use" : "ready";
|
|
44976
45099
|
const tiers = Object.entries(a.modelCooldownsUntil).filter(([, until]) => until > now).sort(([x], [y]) => compareAscii(x, y)).map(([tier, until]) => `${tier} ${relTime(until, now)}`);
|
|
44977
|
-
|
|
44978
|
-
return `${base} · ${tiers.join(" · ")}`;
|
|
44979
|
-
return base;
|
|
45100
|
+
return [base, a.credits, ...tiers].filter(Boolean).join(" · ");
|
|
44980
45101
|
}
|
|
44981
45102
|
function providerName(providerID) {
|
|
44982
45103
|
return PROVIDER_NAMES[providerID] ?? providerID;
|
|
@@ -45452,7 +45573,7 @@ class PendingCoordinator {
|
|
|
45452
45573
|
await this.notifyPending(ref, recovery);
|
|
45453
45574
|
try {
|
|
45454
45575
|
for (;; ) {
|
|
45455
|
-
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);
|
|
45456
45577
|
const now = this.dependencies.now();
|
|
45457
45578
|
await this.dependencies.refreshUsage(now).catch(ignore);
|
|
45458
45579
|
let next;
|
|
@@ -46087,7 +46208,7 @@ function backoffFrom(res, now) {
|
|
|
46087
46208
|
function isRateLimited(res) {
|
|
46088
46209
|
return res.status === 429 || res.status === 403 && (res.headers.get("x-ratelimit-remaining") === "0" || res.headers.has("retry-after"));
|
|
46089
46210
|
}
|
|
46090
|
-
async function
|
|
46211
|
+
async function request2(url, init, now) {
|
|
46091
46212
|
let res;
|
|
46092
46213
|
try {
|
|
46093
46214
|
res = await globalThis.fetch(url, {
|
|
@@ -46139,7 +46260,7 @@ async function boundedText(res) {
|
|
|
46139
46260
|
}
|
|
46140
46261
|
return Buffer.concat(chunks).toString("utf8");
|
|
46141
46262
|
}
|
|
46142
|
-
async function
|
|
46263
|
+
async function readJson2(res) {
|
|
46143
46264
|
if (!res.ok)
|
|
46144
46265
|
throw new SyncError("http");
|
|
46145
46266
|
const text = await boundedText(res);
|
|
@@ -46154,7 +46275,7 @@ async function readJson(res) {
|
|
|
46154
46275
|
return json;
|
|
46155
46276
|
}
|
|
46156
46277
|
async function createGist(token, content, now = Date.now()) {
|
|
46157
|
-
const res = await
|
|
46278
|
+
const res = await request2(API, {
|
|
46158
46279
|
method: "POST",
|
|
46159
46280
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
46160
46281
|
body: JSON.stringify({
|
|
@@ -46163,7 +46284,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
46163
46284
|
files: { [GIST_FILE]: { content } }
|
|
46164
46285
|
})
|
|
46165
46286
|
}, now);
|
|
46166
|
-
const json = await
|
|
46287
|
+
const json = await readJson2(res);
|
|
46167
46288
|
const owner = isPlainObject4(json.owner) ? json.owner.login : undefined;
|
|
46168
46289
|
if (typeof json.id !== "string" || !GIST_ID_RE.test(json.id))
|
|
46169
46290
|
throw new SyncError("http");
|
|
@@ -46175,7 +46296,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
46175
46296
|
async function updateGist(token, id, content, now = Date.now()) {
|
|
46176
46297
|
let res;
|
|
46177
46298
|
try {
|
|
46178
|
-
res = await
|
|
46299
|
+
res = await request2(`${API}/${id}`, {
|
|
46179
46300
|
method: "PATCH",
|
|
46180
46301
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
46181
46302
|
body: JSON.stringify({ files: { [GIST_FILE]: { content } } })
|
|
@@ -46187,10 +46308,10 @@ async function updateGist(token, id, content, now = Date.now()) {
|
|
|
46187
46308
|
}
|
|
46188
46309
|
if (res.status === 401 || res.status === 403)
|
|
46189
46310
|
throw new SyncError("write-denied");
|
|
46190
|
-
await
|
|
46311
|
+
await readJson2(res);
|
|
46191
46312
|
}
|
|
46192
46313
|
async function readGist(id, etag, now = Date.now(), token) {
|
|
46193
|
-
const ask = (auth) =>
|
|
46314
|
+
const ask = (auth) => request2(`${API}/${id}`, {
|
|
46194
46315
|
headers: {
|
|
46195
46316
|
...headers(auth),
|
|
46196
46317
|
...etag ? { "if-none-match": etag } : {}
|
|
@@ -46201,7 +46322,7 @@ async function readGist(id, etag, now = Date.now(), token) {
|
|
|
46201
46322
|
res = await ask();
|
|
46202
46323
|
if (res.status === 304)
|
|
46203
46324
|
return { changed: false };
|
|
46204
|
-
const json = await
|
|
46325
|
+
const json = await readJson2(res);
|
|
46205
46326
|
const file = isPlainObject4(json.files) ? json.files[GIST_FILE] : undefined;
|
|
46206
46327
|
if (!isPlainObject4(file) || typeof file.content !== "string")
|
|
46207
46328
|
throw new SyncError("bad-blob");
|
|
@@ -47343,6 +47464,71 @@ var init_start = __esm(() => {
|
|
|
47343
47464
|
init_state();
|
|
47344
47465
|
});
|
|
47345
47466
|
|
|
47467
|
+
// src/usage-poll.ts
|
|
47468
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
47469
|
+
async function nextPollAt(path) {
|
|
47470
|
+
try {
|
|
47471
|
+
const value = JSON.parse(await readFile8(path, "utf8"));
|
|
47472
|
+
return isPlainObject4(value) && isFiniteNumber(value.nextPollAt) ? value.nextPollAt : 0;
|
|
47473
|
+
} catch (error) {
|
|
47474
|
+
if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
47475
|
+
return 0;
|
|
47476
|
+
throw error;
|
|
47477
|
+
}
|
|
47478
|
+
}
|
|
47479
|
+
function startUsagePoll(deps = {}) {
|
|
47480
|
+
const now = deps.now ?? Date.now;
|
|
47481
|
+
const refresh = deps.refresh ?? refreshAllUsageInBackground;
|
|
47482
|
+
const timers = deps.timers ?? realTimers;
|
|
47483
|
+
let disposed = false;
|
|
47484
|
+
let inflight;
|
|
47485
|
+
async function poll() {
|
|
47486
|
+
const path = `${poolFilePath()}.usage-poll.json`;
|
|
47487
|
+
try {
|
|
47488
|
+
await withLock(`${path}.lock`, {
|
|
47489
|
+
staleMs: 120000,
|
|
47490
|
+
timeoutMs: 0,
|
|
47491
|
+
retryMs: 25,
|
|
47492
|
+
heartbeatMs: 5000
|
|
47493
|
+
}, async () => {
|
|
47494
|
+
const at = now();
|
|
47495
|
+
if (at < await nextPollAt(path))
|
|
47496
|
+
return;
|
|
47497
|
+
await writeJsonAtomic(path, JSON.stringify({ nextPollAt: at + USAGE_REFRESH_TTL_MS }));
|
|
47498
|
+
await refresh(at);
|
|
47499
|
+
});
|
|
47500
|
+
} catch (error) {
|
|
47501
|
+
if (!(error instanceof LockTimeoutError))
|
|
47502
|
+
throw error;
|
|
47503
|
+
}
|
|
47504
|
+
}
|
|
47505
|
+
function tick() {
|
|
47506
|
+
if (disposed)
|
|
47507
|
+
return Promise.resolve();
|
|
47508
|
+
inflight ??= poll().finally(() => {
|
|
47509
|
+
inflight = undefined;
|
|
47510
|
+
});
|
|
47511
|
+
return inflight;
|
|
47512
|
+
}
|
|
47513
|
+
const timer = timers.set(() => void tick().catch(ignore), USAGE_REFRESH_TTL_MS);
|
|
47514
|
+
return {
|
|
47515
|
+
tick,
|
|
47516
|
+
async dispose() {
|
|
47517
|
+
disposed = true;
|
|
47518
|
+
timers.clear(timer);
|
|
47519
|
+
await inflight;
|
|
47520
|
+
}
|
|
47521
|
+
};
|
|
47522
|
+
}
|
|
47523
|
+
var init_usage_poll = __esm(() => {
|
|
47524
|
+
init_lock();
|
|
47525
|
+
init_paths();
|
|
47526
|
+
init_store();
|
|
47527
|
+
init_loop();
|
|
47528
|
+
init_usage_refresh();
|
|
47529
|
+
init_util4();
|
|
47530
|
+
});
|
|
47531
|
+
|
|
47346
47532
|
// src/legacy.ts
|
|
47347
47533
|
function zeroOutCost(provider) {
|
|
47348
47534
|
for (const model of Object.values(provider.models)) {
|
|
@@ -47497,8 +47683,12 @@ function createProviderPlugin(adapter) {
|
|
|
47497
47683
|
}
|
|
47498
47684
|
var PROVIDER_LABELS, AnthropicLoadBalancerPlugin, OpenAILoadBalancerPlugin, KimiCodeLoadBalancerPlugin, KimiCodeGlobalLoadBalancerPlugin, lbResult = (output) => ({ title: "Auth Load Balancer", output }), AuthLoadBalancerStatusPlugin = async (input) => {
|
|
47499
47685
|
const sync = startSync();
|
|
47686
|
+
const usage = startUsagePoll();
|
|
47500
47687
|
return {
|
|
47501
|
-
dispose: async () =>
|
|
47688
|
+
dispose: async () => {
|
|
47689
|
+
await usage.dispose();
|
|
47690
|
+
await sync?.dispose();
|
|
47691
|
+
},
|
|
47502
47692
|
tool: {
|
|
47503
47693
|
auth_lb_status: tool2({
|
|
47504
47694
|
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.",
|
|
@@ -47587,6 +47777,7 @@ var init_legacy = __esm(() => {
|
|
|
47587
47777
|
init_start();
|
|
47588
47778
|
init_token_mint();
|
|
47589
47779
|
init_types2();
|
|
47780
|
+
init_usage_poll();
|
|
47590
47781
|
init_usage_refresh();
|
|
47591
47782
|
init_util4();
|
|
47592
47783
|
PROVIDER_LABELS = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-auth-load-balancer",
|
|
3
|
-
"version": "0.
|
|
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",
|