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/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
|
-
|
|
13335
|
-
return false;
|
|
13336
|
-
return true;
|
|
13359
|
+
return hasHeadroom(account, cfg, now);
|
|
13337
13360
|
}
|
|
13338
13361
|
function weeklyUrgency(account, cfg, now) {
|
|
13339
13362
|
const weekly = account.usage.weekly;
|
|
@@ -13364,9 +13387,11 @@ function scoreAccount(account, cfg, now) {
|
|
|
13364
13387
|
function compareRanked(x, y) {
|
|
13365
13388
|
if (x.available !== y.available)
|
|
13366
13389
|
return x.available ? -1 : 1;
|
|
13367
|
-
if (x.available)
|
|
13368
|
-
return
|
|
13369
|
-
|
|
13390
|
+
if (!x.available)
|
|
13391
|
+
return x.weeklyUtil - y.weeklyUtil;
|
|
13392
|
+
if (x.onCredits !== y.onCredits)
|
|
13393
|
+
return x.onCredits ? 1 : -1;
|
|
13394
|
+
return y.score - x.score;
|
|
13370
13395
|
}
|
|
13371
13396
|
var DAY_MS, WEEK_MS2, HOUR_MS, HOURLY_WINDOW_MS, RESET_CUSHION_DAYS = 0.25, SCORE_DEFAULTS;
|
|
13372
13397
|
var init_score_core = __esm(() => {
|
|
@@ -13385,6 +13410,14 @@ var init_score_core = __esm(() => {
|
|
|
13385
13410
|
});
|
|
13386
13411
|
|
|
13387
13412
|
// src/usage-merge.ts
|
|
13413
|
+
function reconcileQuotaCooldown(account, cfg, now) {
|
|
13414
|
+
if (account.cooldownKind === "auth" || account.cooldownKind === "transient")
|
|
13415
|
+
return;
|
|
13416
|
+
if (account.cooldownUntil > now + MAX_TRANSIENT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
13417
|
+
account.cooldownUntil = 0;
|
|
13418
|
+
delete account.cooldownKind;
|
|
13419
|
+
}
|
|
13420
|
+
}
|
|
13388
13421
|
function rollWeeklyAnchorForward(anchor, now) {
|
|
13389
13422
|
if (!Number.isFinite(anchor) || anchor <= 0)
|
|
13390
13423
|
return 0;
|
|
@@ -13401,8 +13434,10 @@ function preserveWeeklyAnchor(incoming, stored, now) {
|
|
|
13401
13434
|
return incoming;
|
|
13402
13435
|
return { utilization: incoming.utilization, resetAt: anchor };
|
|
13403
13436
|
}
|
|
13437
|
+
var MAX_TRANSIENT_COOLDOWN_MS;
|
|
13404
13438
|
var init_usage_merge = __esm(() => {
|
|
13405
13439
|
init_score_core();
|
|
13440
|
+
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
13406
13441
|
});
|
|
13407
13442
|
|
|
13408
13443
|
// src/accounts.ts
|
|
@@ -13449,11 +13484,15 @@ function clearLostLogin(row, login) {
|
|
|
13449
13484
|
function mergeLoginUsage(row, usage) {
|
|
13450
13485
|
if (!usage)
|
|
13451
13486
|
return;
|
|
13487
|
+
if (usage.credits)
|
|
13488
|
+
row.credits = usage.credits;
|
|
13452
13489
|
row.usage = {
|
|
13453
13490
|
hourly: usage.hourly ?? row.usage.hourly,
|
|
13454
13491
|
weekly: usage.weekly ? preserveWeeklyAnchor(usage.weekly, row.usage.weekly, usage.capturedAt) : row.usage.weekly,
|
|
13455
13492
|
capturedAt: usage.weekly ? usage.capturedAt : row.usage.capturedAt
|
|
13456
13493
|
};
|
|
13494
|
+
if (usage.weekly)
|
|
13495
|
+
reconcileQuotaCooldown(row, loadScoreConfig(), usage.capturedAt);
|
|
13457
13496
|
}
|
|
13458
13497
|
function applyTokens(row, tokens) {
|
|
13459
13498
|
if (tokens.refresh) {
|
|
@@ -13547,6 +13586,7 @@ async function bootstrapFromOpencodeAuth(providerID, getAuth, tokensFromApiKey)
|
|
|
13547
13586
|
var init_accounts = __esm(() => {
|
|
13548
13587
|
init_pairing();
|
|
13549
13588
|
init_store();
|
|
13589
|
+
init_score_core();
|
|
13550
13590
|
init_types();
|
|
13551
13591
|
init_usage_merge();
|
|
13552
13592
|
});
|
|
@@ -13571,7 +13611,8 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
13571
13611
|
continue;
|
|
13572
13612
|
const windows = exhaustedWindows(account, cfg, now);
|
|
13573
13613
|
const cooldownActive = account.cooldownUntil > now;
|
|
13574
|
-
|
|
13614
|
+
const blocking = hasCreditHeadroom(account) ? [] : windows;
|
|
13615
|
+
if (!cooldownActive && blocking.length === 0)
|
|
13575
13616
|
return { state: "available" };
|
|
13576
13617
|
if (cooldownActive && (account.cooldownKind === "auth" || account.cooldownKind === "transient"))
|
|
13577
13618
|
continue;
|
|
@@ -13580,7 +13621,7 @@ function classifyProviderRecovery(accounts, providerID, cfg, now, pollMs) {
|
|
|
13580
13621
|
hasQuotaCandidate = true;
|
|
13581
13622
|
const constraints = [];
|
|
13582
13623
|
let accountHasUnknownReset = false;
|
|
13583
|
-
for (const window of
|
|
13624
|
+
for (const window of blocking) {
|
|
13584
13625
|
if (window.resetAt > now)
|
|
13585
13626
|
constraints.push(window.resetAt);
|
|
13586
13627
|
else
|
|
@@ -13893,22 +13934,27 @@ function weeklyUtil(account) {
|
|
|
13893
13934
|
function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude = NO_EXCLUSIONS, incumbentId = null) {
|
|
13894
13935
|
let bestAvail = null;
|
|
13895
13936
|
let bestAvailScore = Number.NEGATIVE_INFINITY;
|
|
13937
|
+
let bestAvailOnCredits = true;
|
|
13896
13938
|
let bestFallback = null;
|
|
13897
13939
|
let lowestWeeklyUtil = Number.POSITIVE_INFINITY;
|
|
13898
13940
|
let incumbentAccount = null;
|
|
13899
13941
|
let incumbentScore = Number.NEGATIVE_INFINITY;
|
|
13942
|
+
let incumbentOnCredits = false;
|
|
13900
13943
|
for (const account of accounts) {
|
|
13901
13944
|
if (account.providerID !== providerID || account.disabledReason || exclude.has(account.id))
|
|
13902
13945
|
continue;
|
|
13903
13946
|
if (isAvailable(account, cfg, now)) {
|
|
13904
13947
|
const score = scoreAccount(account, cfg, now);
|
|
13905
|
-
|
|
13948
|
+
const onCredits = isExhausted(account, cfg, now);
|
|
13949
|
+
if (onCredits === bestAvailOnCredits ? score > bestAvailScore : bestAvailOnCredits) {
|
|
13906
13950
|
bestAvailScore = score;
|
|
13907
13951
|
bestAvail = account;
|
|
13952
|
+
bestAvailOnCredits = onCredits;
|
|
13908
13953
|
}
|
|
13909
13954
|
if (account.id === incumbentId) {
|
|
13910
13955
|
incumbentAccount = account;
|
|
13911
13956
|
incumbentScore = score;
|
|
13957
|
+
incumbentOnCredits = onCredits;
|
|
13912
13958
|
}
|
|
13913
13959
|
} else {
|
|
13914
13960
|
const util = weeklyUtil(account);
|
|
@@ -13919,7 +13965,7 @@ function selectAccount(accounts, providerID, now, cfg = DEFAULT_CONFIG, exclude
|
|
|
13919
13965
|
}
|
|
13920
13966
|
}
|
|
13921
13967
|
if (bestAvail) {
|
|
13922
|
-
if (incumbentAccount && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
13968
|
+
if (incumbentAccount && incumbentOnCredits === bestAvailOnCredits && incumbentScore >= bestAvailScore * (1 - INCUMBENT_HYSTERESIS))
|
|
13923
13969
|
return { account: incumbentAccount, degraded: false, sticky: false };
|
|
13924
13970
|
return { account: bestAvail, degraded: false, sticky: false };
|
|
13925
13971
|
}
|
|
@@ -13958,7 +14004,7 @@ function selectForSession(pool, providerID, sessionKey, now, cfg = DEFAULT_CONFI
|
|
|
13958
14004
|
const cheap = (!pinnedImminent || cfg.drainMigrate) && isCheapMoment(typeof requestBytes === "function" ? requestBytes() : requestBytes, cfg);
|
|
13959
14005
|
if (pinnedOverSoft && (pinnedImminent || cheap) || cfg.drainMigrate && cheap) {
|
|
13960
14006
|
const alt = selectAccount(pool.accounts, providerID, now, cfg, withExcluded(exclude, pinned.id), null);
|
|
13961
|
-
if (alt && !alt.degraded) {
|
|
14007
|
+
if (alt && !alt.degraded && !isExhausted(alt.account, cfg, now)) {
|
|
13962
14008
|
if (pinnedOverSoft) {
|
|
13963
14009
|
const altUtil = maxUtil(alt.account, now);
|
|
13964
14010
|
const proactiveBetter = pinnedImminent ? altUtil < pinnedUtil : pinnedUtil - altUtil >= PROACTIVE_MIGRATE_MIN_DELTA && weeklyUrgency(alt.account, cfg, now) >= weeklyUrgency(pinned, cfg, now);
|
|
@@ -14488,24 +14534,46 @@ var init_usage_headers = __esm(() => {
|
|
|
14488
14534
|
});
|
|
14489
14535
|
|
|
14490
14536
|
// src/providers/usage-http.ts
|
|
14491
|
-
async function
|
|
14492
|
-
let response;
|
|
14537
|
+
async function request(url, headers, timeoutMs) {
|
|
14493
14538
|
try {
|
|
14494
|
-
|
|
14539
|
+
return await fetch(url, {
|
|
14495
14540
|
headers,
|
|
14496
14541
|
signal: AbortSignal.timeout(timeoutMs)
|
|
14497
14542
|
});
|
|
14498
14543
|
} catch {
|
|
14499
14544
|
return null;
|
|
14500
14545
|
}
|
|
14546
|
+
}
|
|
14547
|
+
async function readJson(response) {
|
|
14548
|
+
if (!response)
|
|
14549
|
+
return null;
|
|
14501
14550
|
if (!response.ok) {
|
|
14502
14551
|
await response.body?.cancel().catch(ignore);
|
|
14503
14552
|
return null;
|
|
14504
14553
|
}
|
|
14505
14554
|
return await response.json().catch(() => null);
|
|
14506
14555
|
}
|
|
14556
|
+
async function fetchJson(url, headers, timeoutMs) {
|
|
14557
|
+
return readJson(await request(url, headers, timeoutMs));
|
|
14558
|
+
}
|
|
14559
|
+
async function fetchUsageJson(url, headers, timeoutMs) {
|
|
14560
|
+
const response = await request(url, headers, timeoutMs);
|
|
14561
|
+
if (response?.status === 401) {
|
|
14562
|
+
await response.body?.cancel().catch(ignore);
|
|
14563
|
+
throw new UsageAuthenticationError;
|
|
14564
|
+
}
|
|
14565
|
+
return readJson(response);
|
|
14566
|
+
}
|
|
14567
|
+
var UsageAuthenticationError;
|
|
14507
14568
|
var init_usage_http = __esm(() => {
|
|
14508
14569
|
init_util2();
|
|
14570
|
+
UsageAuthenticationError = class UsageAuthenticationError extends Error {
|
|
14571
|
+
status = 401;
|
|
14572
|
+
constructor() {
|
|
14573
|
+
super("Usage endpoint rejected the access token (401)");
|
|
14574
|
+
this.name = "UsageAuthenticationError";
|
|
14575
|
+
}
|
|
14576
|
+
};
|
|
14509
14577
|
});
|
|
14510
14578
|
|
|
14511
14579
|
// src/providers/anthropic/version.ts
|
|
@@ -14945,7 +15013,7 @@ async function probeUsage(access, now) {
|
|
|
14945
15013
|
async function fetchUsage(account, now) {
|
|
14946
15014
|
if (!account.refresh)
|
|
14947
15015
|
return probeUsage(account.access, now);
|
|
14948
|
-
const json = await
|
|
15016
|
+
const json = await fetchUsageJson(USAGE_URL, {
|
|
14949
15017
|
authorization: `Bearer ${account.access}`,
|
|
14950
15018
|
"anthropic-beta": "oauth-2025-04-20",
|
|
14951
15019
|
"user-agent": usageUserAgent(),
|
|
@@ -15277,7 +15345,7 @@ function parseUsages(json, now) {
|
|
|
15277
15345
|
return hourly || weekly ? { hourly, weekly, capturedAt: now } : null;
|
|
15278
15346
|
}
|
|
15279
15347
|
function getUsages(baseUrl, key) {
|
|
15280
|
-
return
|
|
15348
|
+
return fetchUsageJson(`${baseUrl}/usages`, { authorization: `Bearer ${key}`, accept: "application/json" }, USAGE_HTTP_TIMEOUT_MS);
|
|
15281
15349
|
}
|
|
15282
15350
|
function usageFetcher(baseUrl) {
|
|
15283
15351
|
return async (account, now) => parseUsages(await getUsages(baseUrl, account.access), now);
|
|
@@ -15534,10 +15602,31 @@ var init_transform2 = __esm(() => {
|
|
|
15534
15602
|
function isWeeklyMinutes(minutes) {
|
|
15535
15603
|
return minutes >= WEEK_MINUTES * 0.95 && minutes <= WEEK_MINUTES * 1.05;
|
|
15536
15604
|
}
|
|
15605
|
+
function creditsFrom(hasCredits, unlimited, balance, limitReached) {
|
|
15606
|
+
if (typeof hasCredits !== "boolean" || typeof unlimited !== "boolean")
|
|
15607
|
+
return;
|
|
15608
|
+
const amount = typeof balance === "string" && balance.trim() !== "" ? Number(balance) : Number.NaN;
|
|
15609
|
+
return {
|
|
15610
|
+
hasCredits,
|
|
15611
|
+
unlimited,
|
|
15612
|
+
...Number.isFinite(amount) ? { balance: amount } : {},
|
|
15613
|
+
...limitReached === true ? { limitReached } : {}
|
|
15614
|
+
};
|
|
15615
|
+
}
|
|
15616
|
+
function headerBool(raw) {
|
|
15617
|
+
const value = raw?.toLowerCase();
|
|
15618
|
+
if (value === "true" || value === "1")
|
|
15619
|
+
return true;
|
|
15620
|
+
if (value === "false" || value === "0")
|
|
15621
|
+
return false;
|
|
15622
|
+
return;
|
|
15623
|
+
}
|
|
15537
15624
|
function parseUsageHeaders2(headers) {
|
|
15538
15625
|
const primaryWindowMinutes = Number(headers.get(PRIMARY_WINDOW_MINUTES_HEADER));
|
|
15539
15626
|
const spec = isWeeklyMinutes(primaryWindowMinutes) ? WEEKLY_PRIMARY_HEADER_SPEC : HEADER_SPEC2;
|
|
15540
|
-
|
|
15627
|
+
const windows = parseWindowPairHeaders(headers, spec);
|
|
15628
|
+
const credits = creditsFrom(headerBool(headers.get("x-codex-credits-has-credits")), headerBool(headers.get("x-codex-credits-unlimited")), headers.get("x-codex-credits-balance"));
|
|
15629
|
+
return credits ? { ...windows, credits } : windows;
|
|
15541
15630
|
}
|
|
15542
15631
|
function endpointWindow2(w) {
|
|
15543
15632
|
return endpointWindowFrom(w, (win) => win.used_percent, 100, (win) => secondsToMs(Number(win.reset_at ?? 0)));
|
|
@@ -15551,16 +15640,19 @@ async function fetchUsage2(account, now) {
|
|
|
15551
15640
|
const accountId = resolveAccountId(account);
|
|
15552
15641
|
if (accountId)
|
|
15553
15642
|
headers["chatgpt-account-id"] = accountId;
|
|
15554
|
-
const json = await
|
|
15643
|
+
const json = await fetchUsageJson(USAGE_URL2, headers, USAGE_HTTP_TIMEOUT_MS);
|
|
15555
15644
|
const rateLimit = json?.rate_limit;
|
|
15556
15645
|
if (!isPlainObject2(rateLimit))
|
|
15557
15646
|
return null;
|
|
15558
15647
|
const { primary_window: primary, secondary_window: secondary } = rateLimit;
|
|
15559
15648
|
const primaryIsWeekly = isWeeklyMinutes(Math.ceil(Number(primary?.limit_window_seconds) / 60));
|
|
15649
|
+
const reported = json?.credits;
|
|
15650
|
+
const credits = isPlainObject2(reported) ? creditsFrom(reported.has_credits, reported.unlimited, reported.balance, reported.overage_limit_reached) : undefined;
|
|
15560
15651
|
return {
|
|
15561
15652
|
hourly: endpointWindow2(primaryIsWeekly ? secondary : primary),
|
|
15562
15653
|
weekly: endpointWindow2(primaryIsWeekly ? primary : secondary),
|
|
15563
|
-
capturedAt: now
|
|
15654
|
+
capturedAt: now,
|
|
15655
|
+
...credits ? { credits } : {}
|
|
15564
15656
|
};
|
|
15565
15657
|
}
|
|
15566
15658
|
var WEEK_MINUTES, PRIMARY_WINDOW_MINUTES_HEADER = "x-codex-primary-window-minutes", HEADER_SPEC2, WEEKLY_PRIMARY_HEADER_SPEC;
|
|
@@ -15607,6 +15699,7 @@ var init_adapter3 = __esm(() => {
|
|
|
15607
15699
|
},
|
|
15608
15700
|
parseUsageHeaders: parseUsageHeaders2,
|
|
15609
15701
|
fetchUsage: fetchUsage2,
|
|
15702
|
+
reportsCredits: true,
|
|
15610
15703
|
classifyError: classifyHttpStatus
|
|
15611
15704
|
};
|
|
15612
15705
|
});
|
|
@@ -15640,11 +15733,14 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
15640
15733
|
aliveIds?.add(account.id);
|
|
15641
15734
|
if (account.disabledReason && account.disabledReason !== MANUAL_DISABLED_REASON)
|
|
15642
15735
|
continue;
|
|
15643
|
-
|
|
15736
|
+
const fresh = account.usage.capturedAt !== 0 && now - account.usage.capturedAt < USAGE_REFRESH_TTL_MS;
|
|
15737
|
+
if (fresh && account.credits)
|
|
15644
15738
|
continue;
|
|
15645
15739
|
const adapter = adapterFor(adapters, account.providerID);
|
|
15646
15740
|
if (!adapter)
|
|
15647
15741
|
continue;
|
|
15742
|
+
if (fresh && !adapter.reportsCredits)
|
|
15743
|
+
continue;
|
|
15648
15744
|
if ((lastPoll.get(account.id) ?? 0) > now - USAGE_REFRESH_TTL_MS)
|
|
15649
15745
|
continue;
|
|
15650
15746
|
lastPoll.set(account.id, now);
|
|
@@ -15662,20 +15758,29 @@ async function refreshUsage(adapters, now, poolSnapshot) {
|
|
|
15662
15758
|
await Promise.all(stale.map(async ({ adapter, account }) => {
|
|
15663
15759
|
try {
|
|
15664
15760
|
await ensureAccessToken(adapter, account, now);
|
|
15665
|
-
const
|
|
15761
|
+
const rejectedAccess = account.access;
|
|
15762
|
+
let snapshot;
|
|
15763
|
+
try {
|
|
15764
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
15765
|
+
} catch (error) {
|
|
15766
|
+
if (!(error instanceof UsageAuthenticationError) || !account.refresh)
|
|
15767
|
+
throw error;
|
|
15768
|
+
await ensureAccessToken(adapter, account, now, rejectedAccess);
|
|
15769
|
+
snapshot = await adapter.fetchUsage(account, now);
|
|
15770
|
+
}
|
|
15666
15771
|
if (snapshot) {
|
|
15667
15772
|
await mutatePool((p) => {
|
|
15668
15773
|
const stored = findAccount(p, account.id);
|
|
15669
15774
|
if (stored) {
|
|
15775
|
+
if (snapshot.credits)
|
|
15776
|
+
stored.credits = snapshot.credits;
|
|
15670
15777
|
stored.usage = {
|
|
15671
15778
|
hourly: snapshot.hourly ?? stored.usage.hourly,
|
|
15672
15779
|
weekly: snapshot.weekly === null ? stored.usage.weekly : preserveWeeklyAnchor(snapshot.weekly, stored.usage.weekly, now),
|
|
15673
15780
|
capturedAt: snapshot.weekly === null ? stored.usage.capturedAt : now
|
|
15674
15781
|
};
|
|
15675
|
-
if (snapshot.weekly !== null && stored.cooldownUntil
|
|
15676
|
-
stored
|
|
15677
|
-
delete stored.cooldownKind;
|
|
15678
|
-
}
|
|
15782
|
+
if (snapshot.weekly !== null && stored.cooldownUntil === account.cooldownUntil && stored.cooldownKind === account.cooldownKind)
|
|
15783
|
+
reconcileQuotaCooldown(stored, scoreCfg, now);
|
|
15679
15784
|
}
|
|
15680
15785
|
});
|
|
15681
15786
|
}
|
|
@@ -15688,16 +15793,17 @@ function refreshUsageInBackground(adapter, now, poolSnapshot) {
|
|
|
15688
15793
|
function refreshAllUsageInBackground(now, poolSnapshot) {
|
|
15689
15794
|
return refreshUsage(ADAPTERS, now, poolSnapshot);
|
|
15690
15795
|
}
|
|
15691
|
-
var USAGE_REFRESH_TTL_MS,
|
|
15796
|
+
var USAGE_REFRESH_TTL_MS, lastPoll;
|
|
15692
15797
|
var init_usage_refresh = __esm(() => {
|
|
15693
15798
|
init_store();
|
|
15694
15799
|
init_registry();
|
|
15800
|
+
init_usage_http();
|
|
15695
15801
|
init_refresh();
|
|
15696
15802
|
init_score_core();
|
|
15697
15803
|
init_types();
|
|
15698
15804
|
init_usage_merge();
|
|
15805
|
+
init_usage_merge();
|
|
15699
15806
|
USAGE_REFRESH_TTL_MS = 5 * 60 * 1000;
|
|
15700
|
-
MAX_TRANSIENT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
15701
15807
|
lastPoll = new Map;
|
|
15702
15808
|
});
|
|
15703
15809
|
|
|
@@ -15720,6 +15826,8 @@ async function bestEffort(what, op) {
|
|
|
15720
15826
|
function applyUsagePartial(account, partial, now) {
|
|
15721
15827
|
if (partial.hourly !== undefined)
|
|
15722
15828
|
account.usage.hourly = partial.hourly;
|
|
15829
|
+
if (partial.credits !== undefined)
|
|
15830
|
+
account.credits = partial.credits;
|
|
15723
15831
|
if (partial.weekly !== undefined) {
|
|
15724
15832
|
account.usage.weekly = preserveWeeklyAnchor(partial.weekly, account.usage.weekly, now);
|
|
15725
15833
|
account.usage.capturedAt = now;
|
|
@@ -15788,7 +15896,7 @@ async function recordSuccess(adapter, accountId, partial, sessionKey, now, cfg,
|
|
|
15788
15896
|
if (account) {
|
|
15789
15897
|
if (partial)
|
|
15790
15898
|
applyUsagePartial(account, partial, now);
|
|
15791
|
-
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS &&
|
|
15899
|
+
if (partial && account.cooldownUntil > now + ACCOUNT_COOLDOWN_MS && hasHeadroom(account, cfg, now)) {
|
|
15792
15900
|
account.cooldownUntil = 0;
|
|
15793
15901
|
delete account.cooldownKind;
|
|
15794
15902
|
}
|
|
@@ -16132,7 +16240,18 @@ function byProviderId(a, b) {
|
|
|
16132
16240
|
function isExpired(window, now) {
|
|
16133
16241
|
return !!window && window.resetAt > 0 && window.resetAt <= now;
|
|
16134
16242
|
}
|
|
16135
|
-
function
|
|
16243
|
+
function creditsText(credits) {
|
|
16244
|
+
if (!credits)
|
|
16245
|
+
return "";
|
|
16246
|
+
if (credits.limitReached)
|
|
16247
|
+
return "credit limit reached";
|
|
16248
|
+
if (credits.unlimited)
|
|
16249
|
+
return "credits unlimited";
|
|
16250
|
+
if (!credits.hasCredits)
|
|
16251
|
+
return "no credits";
|
|
16252
|
+
return credits.balance === undefined ? "credits available" : `credits ${Math.round(credits.balance * 100) / 100}`;
|
|
16253
|
+
}
|
|
16254
|
+
function toStatus(account, now, current, available, exhausted, rank) {
|
|
16136
16255
|
const weekly = account.usage.weekly;
|
|
16137
16256
|
const weeklyExpired = isExpired(weekly, now);
|
|
16138
16257
|
return {
|
|
@@ -16148,6 +16267,7 @@ function toStatus(account, now, current, available, rank) {
|
|
|
16148
16267
|
login: credentialTag(account),
|
|
16149
16268
|
warnings: loginWarnings(account, now),
|
|
16150
16269
|
lostNotes: lostLoginNotes(account, now),
|
|
16270
|
+
credits: exhausted ? creditsText(account.credits) : "",
|
|
16151
16271
|
current,
|
|
16152
16272
|
rank
|
|
16153
16273
|
};
|
|
@@ -16166,14 +16286,17 @@ function buildStatus(pool, now, cfg = DEFAULT_CONFIG) {
|
|
|
16166
16286
|
const currentAccountId = pool.lastSelected[providerID] ?? null;
|
|
16167
16287
|
const ranked = providerAccounts.map((a) => {
|
|
16168
16288
|
const available = isAvailable(a, cfg, now);
|
|
16289
|
+
const exhausted = isExhausted(a, cfg, now);
|
|
16169
16290
|
return {
|
|
16170
16291
|
a,
|
|
16171
16292
|
available,
|
|
16293
|
+
exhausted,
|
|
16294
|
+
onCredits: available && exhausted,
|
|
16172
16295
|
score: available ? scoreAccount(a, cfg, now) : Number.NEGATIVE_INFINITY,
|
|
16173
16296
|
weeklyUtil: a.usage.weekly?.utilization ?? 0
|
|
16174
16297
|
};
|
|
16175
16298
|
}).sort(compareRanked);
|
|
16176
|
-
const accounts = ranked.map(({ a, available }, i) => toStatus(a, now, a.id === currentAccountId, available, i + 1));
|
|
16299
|
+
const accounts = ranked.map(({ a, available, exhausted }, i) => toStatus(a, now, a.id === currentAccountId, available, exhausted, i + 1));
|
|
16177
16300
|
return { providerID, accounts };
|
|
16178
16301
|
});
|
|
16179
16302
|
}
|
|
@@ -16213,12 +16336,10 @@ function stateOf(a, now) {
|
|
|
16213
16336
|
if (a.cooldownUntil > now)
|
|
16214
16337
|
return `cooldown ${relTime(a.cooldownUntil, now)}`;
|
|
16215
16338
|
if (!a.available)
|
|
16216
|
-
return "exhausted";
|
|
16339
|
+
return a.credits ? `exhausted · ${a.credits}` : "exhausted";
|
|
16217
16340
|
const base = a.current ? "in use" : "ready";
|
|
16218
16341
|
const tiers = Object.entries(a.modelCooldownsUntil).filter(([, until]) => until > now).sort(([x], [y]) => compareAscii(x, y)).map(([tier, until]) => `${tier} ${relTime(until, now)}`);
|
|
16219
|
-
|
|
16220
|
-
return `${base} · ${tiers.join(" · ")}`;
|
|
16221
|
-
return base;
|
|
16342
|
+
return [base, a.credits, ...tiers].filter(Boolean).join(" · ");
|
|
16222
16343
|
}
|
|
16223
16344
|
function providerName(providerID) {
|
|
16224
16345
|
return PROVIDER_NAMES[providerID] ?? providerID;
|
|
@@ -16694,7 +16815,7 @@ class PendingCoordinator {
|
|
|
16694
16815
|
await this.notifyPending(ref, recovery);
|
|
16695
16816
|
try {
|
|
16696
16817
|
for (;; ) {
|
|
16697
|
-
await this.dependencies.sleep(Math.max(0, recovery.nextCheckAt - this.dependencies.now()), controller.signal);
|
|
16818
|
+
await this.dependencies.sleep(Math.max(0, Math.min(recovery.nextCheckAt - this.dependencies.now(), USAGE_REFRESH_TTL_MS)), controller.signal);
|
|
16698
16819
|
const now = this.dependencies.now();
|
|
16699
16820
|
await this.dependencies.refreshUsage(now).catch(ignore);
|
|
16700
16821
|
let next;
|
|
@@ -17329,7 +17450,7 @@ function backoffFrom(res, now) {
|
|
|
17329
17450
|
function isRateLimited(res) {
|
|
17330
17451
|
return res.status === 429 || res.status === 403 && (res.headers.get("x-ratelimit-remaining") === "0" || res.headers.has("retry-after"));
|
|
17331
17452
|
}
|
|
17332
|
-
async function
|
|
17453
|
+
async function request2(url, init, now) {
|
|
17333
17454
|
let res;
|
|
17334
17455
|
try {
|
|
17335
17456
|
res = await globalThis.fetch(url, {
|
|
@@ -17381,7 +17502,7 @@ async function boundedText(res) {
|
|
|
17381
17502
|
}
|
|
17382
17503
|
return Buffer.concat(chunks).toString("utf8");
|
|
17383
17504
|
}
|
|
17384
|
-
async function
|
|
17505
|
+
async function readJson2(res) {
|
|
17385
17506
|
if (!res.ok)
|
|
17386
17507
|
throw new SyncError("http");
|
|
17387
17508
|
const text = await boundedText(res);
|
|
@@ -17396,7 +17517,7 @@ async function readJson(res) {
|
|
|
17396
17517
|
return json;
|
|
17397
17518
|
}
|
|
17398
17519
|
async function createGist(token, content, now = Date.now()) {
|
|
17399
|
-
const res = await
|
|
17520
|
+
const res = await request2(API, {
|
|
17400
17521
|
method: "POST",
|
|
17401
17522
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
17402
17523
|
body: JSON.stringify({
|
|
@@ -17405,7 +17526,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
17405
17526
|
files: { [GIST_FILE]: { content } }
|
|
17406
17527
|
})
|
|
17407
17528
|
}, now);
|
|
17408
|
-
const json = await
|
|
17529
|
+
const json = await readJson2(res);
|
|
17409
17530
|
const owner = isPlainObject2(json.owner) ? json.owner.login : undefined;
|
|
17410
17531
|
if (typeof json.id !== "string" || !GIST_ID_RE.test(json.id))
|
|
17411
17532
|
throw new SyncError("http");
|
|
@@ -17417,7 +17538,7 @@ async function createGist(token, content, now = Date.now()) {
|
|
|
17417
17538
|
async function updateGist(token, id, content, now = Date.now()) {
|
|
17418
17539
|
let res;
|
|
17419
17540
|
try {
|
|
17420
|
-
res = await
|
|
17541
|
+
res = await request2(`${API}/${id}`, {
|
|
17421
17542
|
method: "PATCH",
|
|
17422
17543
|
headers: { ...headers(token), "content-type": "application/json" },
|
|
17423
17544
|
body: JSON.stringify({ files: { [GIST_FILE]: { content } } })
|
|
@@ -17429,10 +17550,10 @@ async function updateGist(token, id, content, now = Date.now()) {
|
|
|
17429
17550
|
}
|
|
17430
17551
|
if (res.status === 401 || res.status === 403)
|
|
17431
17552
|
throw new SyncError("write-denied");
|
|
17432
|
-
await
|
|
17553
|
+
await readJson2(res);
|
|
17433
17554
|
}
|
|
17434
17555
|
async function readGist(id, etag, now = Date.now(), token) {
|
|
17435
|
-
const ask = (auth) =>
|
|
17556
|
+
const ask = (auth) => request2(`${API}/${id}`, {
|
|
17436
17557
|
headers: {
|
|
17437
17558
|
...headers(auth),
|
|
17438
17559
|
...etag ? { "if-none-match": etag } : {}
|
|
@@ -17443,7 +17564,7 @@ async function readGist(id, etag, now = Date.now(), token) {
|
|
|
17443
17564
|
res = await ask();
|
|
17444
17565
|
if (res.status === 304)
|
|
17445
17566
|
return { changed: false };
|
|
17446
|
-
const json = await
|
|
17567
|
+
const json = await readJson2(res);
|
|
17447
17568
|
const file = isPlainObject2(json.files) ? json.files[GIST_FILE] : undefined;
|
|
17448
17569
|
if (!isPlainObject2(file) || typeof file.content !== "string")
|
|
17449
17570
|
throw new SyncError("bad-blob");
|
|
@@ -18585,6 +18706,71 @@ var init_start = __esm(() => {
|
|
|
18585
18706
|
init_state();
|
|
18586
18707
|
});
|
|
18587
18708
|
|
|
18709
|
+
// src/usage-poll.ts
|
|
18710
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
18711
|
+
async function nextPollAt(path) {
|
|
18712
|
+
try {
|
|
18713
|
+
const value = JSON.parse(await readFile8(path, "utf8"));
|
|
18714
|
+
return isPlainObject2(value) && isFiniteNumber(value.nextPollAt) ? value.nextPollAt : 0;
|
|
18715
|
+
} catch (error) {
|
|
18716
|
+
if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
18717
|
+
return 0;
|
|
18718
|
+
throw error;
|
|
18719
|
+
}
|
|
18720
|
+
}
|
|
18721
|
+
function startUsagePoll(deps = {}) {
|
|
18722
|
+
const now = deps.now ?? Date.now;
|
|
18723
|
+
const refresh = deps.refresh ?? refreshAllUsageInBackground;
|
|
18724
|
+
const timers = deps.timers ?? realTimers;
|
|
18725
|
+
let disposed = false;
|
|
18726
|
+
let inflight;
|
|
18727
|
+
async function poll() {
|
|
18728
|
+
const path = `${poolFilePath()}.usage-poll.json`;
|
|
18729
|
+
try {
|
|
18730
|
+
await withLock(`${path}.lock`, {
|
|
18731
|
+
staleMs: 120000,
|
|
18732
|
+
timeoutMs: 0,
|
|
18733
|
+
retryMs: 25,
|
|
18734
|
+
heartbeatMs: 5000
|
|
18735
|
+
}, async () => {
|
|
18736
|
+
const at = now();
|
|
18737
|
+
if (at < await nextPollAt(path))
|
|
18738
|
+
return;
|
|
18739
|
+
await writeJsonAtomic(path, JSON.stringify({ nextPollAt: at + USAGE_REFRESH_TTL_MS }));
|
|
18740
|
+
await refresh(at);
|
|
18741
|
+
});
|
|
18742
|
+
} catch (error) {
|
|
18743
|
+
if (!(error instanceof LockTimeoutError))
|
|
18744
|
+
throw error;
|
|
18745
|
+
}
|
|
18746
|
+
}
|
|
18747
|
+
function tick() {
|
|
18748
|
+
if (disposed)
|
|
18749
|
+
return Promise.resolve();
|
|
18750
|
+
inflight ??= poll().finally(() => {
|
|
18751
|
+
inflight = undefined;
|
|
18752
|
+
});
|
|
18753
|
+
return inflight;
|
|
18754
|
+
}
|
|
18755
|
+
const timer = timers.set(() => void tick().catch(ignore), USAGE_REFRESH_TTL_MS);
|
|
18756
|
+
return {
|
|
18757
|
+
tick,
|
|
18758
|
+
async dispose() {
|
|
18759
|
+
disposed = true;
|
|
18760
|
+
timers.clear(timer);
|
|
18761
|
+
await inflight;
|
|
18762
|
+
}
|
|
18763
|
+
};
|
|
18764
|
+
}
|
|
18765
|
+
var init_usage_poll = __esm(() => {
|
|
18766
|
+
init_lock();
|
|
18767
|
+
init_paths();
|
|
18768
|
+
init_store();
|
|
18769
|
+
init_loop();
|
|
18770
|
+
init_usage_refresh();
|
|
18771
|
+
init_util2();
|
|
18772
|
+
});
|
|
18773
|
+
|
|
18588
18774
|
// src/legacy.ts
|
|
18589
18775
|
function zeroOutCost(provider) {
|
|
18590
18776
|
for (const model of Object.values(provider.models)) {
|
|
@@ -18739,8 +18925,12 @@ function createProviderPlugin(adapter) {
|
|
|
18739
18925
|
}
|
|
18740
18926
|
var PROVIDER_LABELS, AnthropicLoadBalancerPlugin, OpenAILoadBalancerPlugin, KimiCodeLoadBalancerPlugin, KimiCodeGlobalLoadBalancerPlugin, lbResult = (output) => ({ title: "Auth Load Balancer", output }), AuthLoadBalancerStatusPlugin = async (input) => {
|
|
18741
18927
|
const sync = startSync();
|
|
18928
|
+
const usage = startUsagePoll();
|
|
18742
18929
|
return {
|
|
18743
|
-
dispose: async () =>
|
|
18930
|
+
dispose: async () => {
|
|
18931
|
+
await usage.dispose();
|
|
18932
|
+
await sync?.dispose();
|
|
18933
|
+
},
|
|
18744
18934
|
tool: {
|
|
18745
18935
|
auth_lb_status: tool({
|
|
18746
18936
|
description: "Show the auth load-balancer pool: the in-use account per provider, each account's weekly and 5h usage, cooldowns, and the ranked next-candidate accounts. Refreshes stale usage from the provider usage endpoints (throttled) before rendering.",
|
|
@@ -18829,6 +19019,7 @@ var init_legacy = __esm(() => {
|
|
|
18829
19019
|
init_start();
|
|
18830
19020
|
init_token_mint();
|
|
18831
19021
|
init_types();
|
|
19022
|
+
init_usage_poll();
|
|
18832
19023
|
init_usage_refresh();
|
|
18833
19024
|
init_util2();
|
|
18834
19025
|
PROVIDER_LABELS = {
|