maxpool 1.5.10 → 1.5.11
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/package.json +1 -1
- package/src/config.js +1 -1
- package/src/server.js +42 -28
- package/src/tui.js +11 -0
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -111,7 +111,7 @@ export function createDefaultConfig() {
|
|
|
111
111
|
enabled: true,
|
|
112
112
|
maxWaitMs: 24 * 60 * 60 * 1000, // hard ceiling for non-streaming/capacity holds; streaming uses streamHoldMaxMs
|
|
113
113
|
autoMaxWaitMs: null, // 5h/session-cap hold (null = maxWaitMs)
|
|
114
|
-
capacityMaxWaitMs: 15 * 60 * 1000, //
|
|
114
|
+
capacityMaxWaitMs: 15 * 60 * 1000, // short cap for NON-streaming capacity holds + the concurrency-cap clamp. A STREAMING capacity/throttle hold uses maxWaitMs (held on the heartbeat until capacity frees), NOT this.
|
|
115
115
|
weeklyMaxWaitMs: 24 * 60 * 60 * 1000, // legacy bound; streaming holds use streamHoldMaxMs
|
|
116
116
|
// Streaming hold ceiling: how long a streaming session is held ALIVE on the
|
|
117
117
|
// heartbeat waiting for any account to free up. 7d so a session is never
|
package/src/server.js
CHANGED
|
@@ -952,6 +952,38 @@ function formatRetryDuration(seconds) {
|
|
|
952
952
|
return `${s}s`;
|
|
953
953
|
}
|
|
954
954
|
|
|
955
|
+
/**
|
|
956
|
+
* Hold-window ceiling for a queued request. The soonest-recovery oracle
|
|
957
|
+
* (nextRetryForRequest) is the real governor — this is the backstop ceiling that
|
|
958
|
+
* bounds how long a request may WAIT for that recovery.
|
|
959
|
+
*
|
|
960
|
+
* non-streaming (no heartbeat) → short cap (would die on the client's own timeout)
|
|
961
|
+
* streaming + capacity/429/throttle → maxWaitMs (24h): HELD on the SSE heartbeat
|
|
962
|
+
* until an account frees. This is the "hold, don't fail" fix — a throttle clears
|
|
963
|
+
* in seconds and a 5h session-cap in hours, both far under 24h; only a multi-day
|
|
964
|
+
* weekly reset exceeds it and error-fasts with the honest "add an account"
|
|
965
|
+
* message. Previously capped at the short capacityMaxWaitMs (15m), which failed
|
|
966
|
+
* the MOST-clearly-temporary throttle sooner than an ordinary per-account 429.
|
|
967
|
+
* streaming + other (quota) → streamHoldMaxMs (7d)
|
|
968
|
+
* retryPlanCause==='concurrency_cap' → clamped to the short capacity window (a LOCAL
|
|
969
|
+
* transient: a slot frees in seconds; never spin a multi-day hold on it).
|
|
970
|
+
*/
|
|
971
|
+
function computeQueueWindowMs({
|
|
972
|
+
cause, stream, retryPlanCause,
|
|
973
|
+
maxWaitMs, capacityMaxWaitMs, nonStreamMaxWaitMs, streamHoldMaxMs,
|
|
974
|
+
}) {
|
|
975
|
+
let windowMs;
|
|
976
|
+
if (!stream) {
|
|
977
|
+
windowMs = cause === 'capacity' ? Math.min(nonStreamMaxWaitMs, capacityMaxWaitMs) : nonStreamMaxWaitMs;
|
|
978
|
+
} else if (cause === 'capacity') {
|
|
979
|
+
windowMs = maxWaitMs;
|
|
980
|
+
} else {
|
|
981
|
+
windowMs = streamHoldMaxMs;
|
|
982
|
+
}
|
|
983
|
+
if (retryPlanCause === 'concurrency_cap') windowMs = Math.min(windowMs, capacityMaxWaitMs);
|
|
984
|
+
return windowMs;
|
|
985
|
+
}
|
|
986
|
+
|
|
955
987
|
function unavailableMessage(accountManager, requestInfo = {}, retryAfter, willRecoverSoon = true) {
|
|
956
988
|
const thinking = requestInfo.requiresAnthropicThinkingIntegrity
|
|
957
989
|
|| accountManager._requiresAnthropicThinkingIntegrity?.(requestInfo);
|
|
@@ -976,7 +1008,7 @@ function unavailableMessage(accountManager, requestInfo = {}, retryAfter, willRe
|
|
|
976
1008
|
return `All ${n} accounts exhausted. Retry in ${retryAfter}s.`;
|
|
977
1009
|
}
|
|
978
1010
|
|
|
979
|
-
export const __serverTest = { unavailableMessage, isRetriableUpstreamStatus, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, describeRequest };
|
|
1011
|
+
export const __serverTest = { unavailableMessage, computeQueueWindowMs, isRetriableUpstreamStatus, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, describeRequest };
|
|
980
1012
|
|
|
981
1013
|
async function readErrorBody(upstreamRes, limitBytes = 64 * 1024) {
|
|
982
1014
|
if (!upstreamRes.body) return '';
|
|
@@ -1231,33 +1263,15 @@ async function queueAndRetry(
|
|
|
1231
1263
|
const streamHoldMaxMs = queueConfig.streamHoldMaxMs == null
|
|
1232
1264
|
? 7 * 24 * 60 * 60 * 1000
|
|
1233
1265
|
: Math.max(0, Number(queueConfig.streamHoldMaxMs) || 0);
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1243
|
-
queueWindowMs = nonStreamMaxWaitMs;
|
|
1244
|
-
} else {
|
|
1245
|
-
queueWindowMs = streamHoldMaxMs;
|
|
1246
|
-
}
|
|
1247
|
-
// A non-streaming request has no heartbeat regardless of cause, so it must
|
|
1248
|
-
// never outlast nonStreamMaxWaitMs even under capacity (it would occupy a
|
|
1249
|
-
// slot 3x its documented cap with nothing to reap it).
|
|
1250
|
-
if (!requestInfo.stream) queueWindowMs = Math.min(queueWindowMs, nonStreamMaxWaitMs);
|
|
1251
|
-
|
|
1252
|
-
// A pure concurrency-cap block (every account healthy but all in-flight/global
|
|
1253
|
-
// slots busy — NOT a quota/rate-limit reset) is a LOCAL capacity transient. Bound
|
|
1254
|
-
// it by the short capacity window, never the multi-day streaming hold: a slot
|
|
1255
|
-
// frees as active requests finish (seconds–minutes), and if the fleet stays
|
|
1256
|
-
// saturated past the window the request sheds load (error-fast) instead of
|
|
1257
|
-
// spinning a queue slot for up to streamHoldMaxMs (7d) — the soft-deadlock guard.
|
|
1258
|
-
if (retryPlan.cause === 'concurrency_cap') {
|
|
1259
|
-
queueWindowMs = Math.min(queueWindowMs, capacityMaxWaitMs);
|
|
1260
|
-
}
|
|
1266
|
+
const queueWindowMs = computeQueueWindowMs({
|
|
1267
|
+
cause,
|
|
1268
|
+
stream: Boolean(requestInfo.stream),
|
|
1269
|
+
retryPlanCause: retryPlan.cause,
|
|
1270
|
+
maxWaitMs,
|
|
1271
|
+
capacityMaxWaitMs,
|
|
1272
|
+
nonStreamMaxWaitMs,
|
|
1273
|
+
streamHoldMaxMs,
|
|
1274
|
+
});
|
|
1261
1275
|
|
|
1262
1276
|
if (queueWindowMs <= 0) return finishQueuedStreamIfNeeded(res, requestInfo, honestMessage);
|
|
1263
1277
|
|
package/src/tui.js
CHANGED
|
@@ -925,8 +925,19 @@ export class TUI {
|
|
|
925
925
|
if (a.enabled !== false && upstreamBlocking && a.status === 'active') {
|
|
926
926
|
effectiveStatus = a.inFlight > 0 ? 'probing' : 'waiting';
|
|
927
927
|
}
|
|
928
|
+
// Anthropic is actively REJECTING this account right now (e.g. a per-model weekly
|
|
929
|
+
// sub-limit the general utilization % doesn't expose). It's unusable — surface
|
|
930
|
+
// that instead of a benign green "active", so a low weekly % (the bar keeps its
|
|
931
|
+
// true value) is never misread as available headroom.
|
|
932
|
+
// Keys on a.status (not effectiveStatus) so a rejected account reads 'blocked'
|
|
933
|
+
// even inside an upstream-throttle window (where it would otherwise show
|
|
934
|
+
// probing/waiting) — a rejected account is unusable, not part of the recovery.
|
|
935
|
+
if (a.enabled !== false && a.quota?.unifiedStatus === 'rejected' && a.status === 'active') {
|
|
936
|
+
effectiveStatus = 'blocked';
|
|
937
|
+
}
|
|
928
938
|
switch (effectiveStatus) {
|
|
929
939
|
case 'active': status = isCur ? green('active') : 'active'; break;
|
|
940
|
+
case 'blocked': status = red('blocked'); break;
|
|
930
941
|
case 'probing': status = green('probing'); break;
|
|
931
942
|
case 'waiting': status = yellow('waiting'); break;
|
|
932
943
|
case 'paused': status = yellow('paused'); break;
|