switchroom 0.14.80 → 0.14.81
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.
|
@@ -12286,6 +12286,35 @@ async function fetchQuota(opts) {
|
|
|
12286
12286
|
return parsed;
|
|
12287
12287
|
}
|
|
12288
12288
|
|
|
12289
|
+
// src/auth/broker/consumer-quota-sensor.ts
|
|
12290
|
+
var EXHAUSTION_PCT = 99.5;
|
|
12291
|
+
var DEFAULT_CONSUMER_PROBE_INTERVAL_MS = 10 * 60 * 1000;
|
|
12292
|
+
function quotaIndicatesExhaustion(result) {
|
|
12293
|
+
if (!result.ok)
|
|
12294
|
+
return { exhausted: false, until: null };
|
|
12295
|
+
const d = result.data;
|
|
12296
|
+
const fiveBlocked = d.fiveHourUtilizationPct >= EXHAUSTION_PCT;
|
|
12297
|
+
const sevenBlocked = d.sevenDayUtilizationPct >= EXHAUSTION_PCT;
|
|
12298
|
+
if (!fiveBlocked && !sevenBlocked)
|
|
12299
|
+
return { exhausted: false, until: null };
|
|
12300
|
+
const fiveReset = fiveBlocked ? d.fiveHourResetAt?.getTime() ?? null : null;
|
|
12301
|
+
const sevenReset = sevenBlocked ? d.sevenDayResetAt?.getTime() ?? null : null;
|
|
12302
|
+
const candidates = [fiveReset, sevenReset].filter((x) => x != null);
|
|
12303
|
+
const until = candidates.length > 0 ? Math.max(...candidates) : null;
|
|
12304
|
+
return { exhausted: true, until };
|
|
12305
|
+
}
|
|
12306
|
+
function resolveConsumerProbeIntervalMs(env) {
|
|
12307
|
+
if (env.SWITCHROOM_DISABLE_CONSUMER_QUOTA_PROBE === "1")
|
|
12308
|
+
return 0;
|
|
12309
|
+
const raw = env.SWITCHROOM_CONSUMER_QUOTA_PROBE_MS;
|
|
12310
|
+
if (raw !== undefined) {
|
|
12311
|
+
const n = Number(raw);
|
|
12312
|
+
if (Number.isFinite(n) && n >= 0)
|
|
12313
|
+
return n;
|
|
12314
|
+
}
|
|
12315
|
+
return DEFAULT_CONSUMER_PROBE_INTERVAL_MS;
|
|
12316
|
+
}
|
|
12317
|
+
|
|
12289
12318
|
// src/util/atomic.ts
|
|
12290
12319
|
import { randomBytes } from "node:crypto";
|
|
12291
12320
|
import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
|
|
@@ -13483,6 +13512,8 @@ class AuthBroker {
|
|
|
13483
13512
|
config;
|
|
13484
13513
|
listeners = new Map;
|
|
13485
13514
|
refreshTimer = null;
|
|
13515
|
+
consumerProbeTimer = null;
|
|
13516
|
+
fetchQuotaImpl;
|
|
13486
13517
|
stateDir;
|
|
13487
13518
|
socketRoot;
|
|
13488
13519
|
home;
|
|
@@ -13506,6 +13537,7 @@ class AuthBroker {
|
|
|
13506
13537
|
this.now = opts.now ?? nowMs;
|
|
13507
13538
|
this.operatorUid = opts.operatorUid;
|
|
13508
13539
|
this.fetcher = opts.fetcher;
|
|
13540
|
+
this.fetchQuotaImpl = opts._testFetchQuota ?? fetchQuota;
|
|
13509
13541
|
this.stateDir = opts.stateDir ?? resolve7(this.homeRoot(), ".switchroom", "state", "auth-broker");
|
|
13510
13542
|
this.socketRoot = opts.socketRoot ?? AUTH_BROKER_ROOT;
|
|
13511
13543
|
this.providers = new ProviderRegistry;
|
|
@@ -13552,6 +13584,16 @@ class AuthBroker {
|
|
|
13552
13584
|
});
|
|
13553
13585
|
}, REFRESH_TICK_INTERVAL_MS);
|
|
13554
13586
|
this.refreshTimer.unref();
|
|
13587
|
+
const probeMs = resolveConsumerProbeIntervalMs(process.env);
|
|
13588
|
+
const hasConsumers = (this.config.auth?.consumers ?? []).length > 0;
|
|
13589
|
+
if (probeMs > 0 && hasConsumers) {
|
|
13590
|
+
this.consumerProbeTimer = setInterval(() => {
|
|
13591
|
+
this.consumerQuotaProbeTick().catch((err) => {
|
|
13592
|
+
this.logErr(`consumer-quota-probe threw: ${err.message}`);
|
|
13593
|
+
});
|
|
13594
|
+
}, probeMs);
|
|
13595
|
+
this.consumerProbeTimer.unref();
|
|
13596
|
+
}
|
|
13555
13597
|
}
|
|
13556
13598
|
const fanned = this.fanoutAll();
|
|
13557
13599
|
if (fanned.length > 0) {
|
|
@@ -13578,6 +13620,10 @@ class AuthBroker {
|
|
|
13578
13620
|
clearInterval(this.refreshTimer);
|
|
13579
13621
|
this.refreshTimer = null;
|
|
13580
13622
|
}
|
|
13623
|
+
if (this.consumerProbeTimer) {
|
|
13624
|
+
clearInterval(this.consumerProbeTimer);
|
|
13625
|
+
this.consumerProbeTimer = null;
|
|
13626
|
+
}
|
|
13581
13627
|
for (const [sock, lis] of this.listeners) {
|
|
13582
13628
|
try {
|
|
13583
13629
|
lis.server.close();
|
|
@@ -13995,7 +14041,7 @@ class AuthBroker {
|
|
|
13995
14041
|
this.audit({ op: "probe-quota", identity: identity2, account: label, ok: false, error: "missing-credentials" });
|
|
13996
14042
|
return { label, result: result2 };
|
|
13997
14043
|
}
|
|
13998
|
-
const result = await
|
|
14044
|
+
const result = await this.fetchQuotaImpl({ accessToken: token, timeoutMs });
|
|
13999
14045
|
this.audit({
|
|
14000
14046
|
op: "probe-quota",
|
|
14001
14047
|
identity: identity2,
|
|
@@ -14019,6 +14065,34 @@ class AuthBroker {
|
|
|
14019
14065
|
}));
|
|
14020
14066
|
socket.write(encodeSuccess(id, { results }));
|
|
14021
14067
|
}
|
|
14068
|
+
async consumerQuotaProbeTick() {
|
|
14069
|
+
const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account)));
|
|
14070
|
+
for (const label of accounts) {
|
|
14071
|
+
const creds = readAccountCredentials(label, this.home);
|
|
14072
|
+
const token = creds?.claudeAiOauth?.accessToken;
|
|
14073
|
+
if (!token)
|
|
14074
|
+
continue;
|
|
14075
|
+
let result;
|
|
14076
|
+
try {
|
|
14077
|
+
result = await this.fetchQuotaImpl({ accessToken: token });
|
|
14078
|
+
} catch (err) {
|
|
14079
|
+
this.logErr(`consumer-quota-probe ${label}: ${err.message}`);
|
|
14080
|
+
continue;
|
|
14081
|
+
}
|
|
14082
|
+
const decision = quotaIndicatesExhaustion(result);
|
|
14083
|
+
if (!decision.exhausted)
|
|
14084
|
+
continue;
|
|
14085
|
+
const exhaustedUntil = decision.until ?? this.now() + MARK_EXHAUSTED_DEFAULT_MS;
|
|
14086
|
+
const existing = this.quota[label]?.exhausted_until;
|
|
14087
|
+
if (existing !== undefined && existing >= exhaustedUntil)
|
|
14088
|
+
continue;
|
|
14089
|
+
this.quota[label] = { exhausted_until: exhaustedUntil };
|
|
14090
|
+
this.persistQuota();
|
|
14091
|
+
this.audit({ op: "mark-exhausted", identity: { kind: "operator" }, account: label, ok: true });
|
|
14092
|
+
process.stdout.write(`auth-broker: consumer-quota-sensor marked ${label} exhausted until ${new Date(exhaustedUntil).toISOString()} — consumer(s) fail over
|
|
14093
|
+
`);
|
|
14094
|
+
}
|
|
14095
|
+
}
|
|
14022
14096
|
async opSetActive(socket, id, identity2, account) {
|
|
14023
14097
|
if (!this.isAdmin(identity2)) {
|
|
14024
14098
|
this.audit({ op: "set-active", identity: identity2, account, ok: false, error: "FORBIDDEN" });
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -49700,8 +49700,8 @@ var {
|
|
|
49700
49700
|
} = import__.default;
|
|
49701
49701
|
|
|
49702
49702
|
// src/build-info.ts
|
|
49703
|
-
var VERSION = "0.14.
|
|
49704
|
-
var COMMIT_SHA = "
|
|
49703
|
+
var VERSION = "0.14.81";
|
|
49704
|
+
var COMMIT_SHA = "4ac9cc7d";
|
|
49705
49705
|
|
|
49706
49706
|
// src/cli/agent.ts
|
|
49707
49707
|
init_source();
|
package/package.json
CHANGED
|
@@ -52810,11 +52810,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
52810
52810
|
}
|
|
52811
52811
|
|
|
52812
52812
|
// ../src/build-info.ts
|
|
52813
|
-
var VERSION = "0.14.
|
|
52814
|
-
var COMMIT_SHA = "
|
|
52815
|
-
var COMMIT_DATE = "2026-06-
|
|
52813
|
+
var VERSION = "0.14.81";
|
|
52814
|
+
var COMMIT_SHA = "4ac9cc7d";
|
|
52815
|
+
var COMMIT_DATE = "2026-06-07T10:43:55+10:00";
|
|
52816
52816
|
var LATEST_PR = null;
|
|
52817
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
52817
|
+
var COMMITS_AHEAD_OF_TAG = 2;
|
|
52818
52818
|
|
|
52819
52819
|
// gateway/boot-version.ts
|
|
52820
52820
|
function formatRelativeAgo(iso) {
|