maxpool 1.5.0 → 1.5.2
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/account-manager.js +53 -5
- package/src/server.js +22 -6
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -250,7 +250,36 @@ export class AccountManager {
|
|
|
250
250
|
|
|
251
251
|
for (const account of this.accounts) {
|
|
252
252
|
if (excludedIndexes.has(account.index)) continue;
|
|
253
|
-
|
|
253
|
+
const matches = this._matchesRequest(account, profile, requestInfo);
|
|
254
|
+
|
|
255
|
+
// A signed-thinking request behind the TRANSIENT FIFO queue-fairness gate
|
|
256
|
+
// (a queue already exists and this newcomer hasn't registered a ticket yet
|
|
257
|
+
// — the `!requestInfo.queueTicket` clause in _matchesRequest) must still be
|
|
258
|
+
// CONSIDERED for retry timing, not skipped. Such a request has NO fallback:
|
|
259
|
+
// providers are barred (signed thinking), so dropping the account here — and
|
|
260
|
+
// losing its REAL cooldown / rate-limit / 5h reset — collapses the oracle to
|
|
261
|
+
// Infinity and KILLS the session. Two reproduced kills (2026-06-27): a queue
|
|
262
|
+
// forms, then either (a) healthy accounts sit idle behind it, or (b) a
|
|
263
|
+
// network blip has cooled EVERY account, and the thinking newcomer dies with
|
|
264
|
+
// the false "all N are at their 5h or weekly limit". The gate lifts the
|
|
265
|
+
// instant the request registers a ticket, so bypass it for retry timing here.
|
|
266
|
+
// Still respected: admissionPaused (restart/shutdown shed → stays terminal),
|
|
267
|
+
// upstream-throttle (handled by the early return above), and structural
|
|
268
|
+
// profile / provider-thinking compatibility. Scoped to THINKING on purpose: a
|
|
269
|
+
// non-thinking newcomer can fall back to a provider or be cheaply retried, so
|
|
270
|
+
// the gate keeps shedding it as backpressure (never grow the queue past its
|
|
271
|
+
// cap under a pure-concurrency burst).
|
|
272
|
+
const fairnessOnlyBlock = !matches
|
|
273
|
+
&& this._requiresAnthropicThinkingIntegrity(requestInfo)
|
|
274
|
+
&& account.type !== 'provider'
|
|
275
|
+
&& this._isRequestCompatible(account, profile, requestInfo)
|
|
276
|
+
&& !this.admissionPaused
|
|
277
|
+
&& !this._isUpstreamThrottleBlocking()
|
|
278
|
+
&& this.queueState.waiting.length > 0
|
|
279
|
+
&& !requestInfo.queueTicket
|
|
280
|
+
&& !requestInfo.queueAdmitted;
|
|
281
|
+
|
|
282
|
+
if (!matches && !fairnessOnlyBlock) {
|
|
254
283
|
if (account.type === 'provider' && this._requiresAnthropicThinkingIntegrity(requestInfo)) {
|
|
255
284
|
note('provider_fallback_disabled_signed_thinking');
|
|
256
285
|
}
|
|
@@ -258,7 +287,16 @@ export class AccountManager {
|
|
|
258
287
|
}
|
|
259
288
|
|
|
260
289
|
matchingRoutes++;
|
|
261
|
-
|
|
290
|
+
|
|
291
|
+
// NEVER claim available:0 for a fairness-gated account — _selectNext still
|
|
292
|
+
// refuses it (the gate), so an available verdict would desync the oracle from
|
|
293
|
+
// selection and spin the caller. A healthy gated account holds a bounded
|
|
294
|
+
// re-poll (queued_behind_fairness); a transiently-blocked one (cooldown /
|
|
295
|
+
// rate-limit / 5h cap) falls through so its REAL short-term reset drives a
|
|
296
|
+
// finite hold. It must NEVER contribute the WEEKLY reset (days) — that is the
|
|
297
|
+
// multi-day-hang the bounded path fences off; see the !fairnessOnlyBlock
|
|
298
|
+
// guards on the weekly branches below.
|
|
299
|
+
if (!fairnessOnlyBlock && this._isAvailable(account, { allowWeeklyReserve: true })) {
|
|
262
300
|
return {
|
|
263
301
|
available: true,
|
|
264
302
|
retryAfterMs: 0,
|
|
@@ -267,10 +305,15 @@ export class AccountManager {
|
|
|
267
305
|
matchingRoutes,
|
|
268
306
|
};
|
|
269
307
|
}
|
|
308
|
+
if (fairnessOnlyBlock && this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
309
|
+
soonestBoundedHold = Math.min(soonestBoundedHold, BOUNDED_REPOLL_HOLD_MS);
|
|
310
|
+
if (!boundedHoldCause) boundedHoldCause = 'queued_behind_fairness';
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
270
313
|
|
|
271
314
|
const retry = this._retryInfo(account);
|
|
272
315
|
note(retry.cause);
|
|
273
|
-
if (retry.weeklyCritical && this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
316
|
+
if (!fairnessOnlyBlock && retry.weeklyCritical && this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
274
317
|
return {
|
|
275
318
|
available: true,
|
|
276
319
|
retryAfterMs: 0,
|
|
@@ -301,10 +344,15 @@ export class AccountManager {
|
|
|
301
344
|
// hold when no weekly-critical account contributed it.
|
|
302
345
|
if (retry.weeklyCritical) boundedHoldCause = 'weekly_critical';
|
|
303
346
|
else if (!boundedHoldCause) boundedHoldCause = 'concurrency_cap';
|
|
304
|
-
} else if (retry.cause === 'weekly_exhausted' && retry.retryAt) {
|
|
347
|
+
} else if (!fairnessOnlyBlock && retry.cause === 'weekly_exhausted' && retry.retryAt) {
|
|
348
|
+
// A fairness-gated account NEVER contributes the weekly reset: holding a
|
|
349
|
+
// thinking session for days behind the queue is the over-correction we
|
|
350
|
+
// avoid (a weekly-exhausted gated fleet stays terminal → honest error,
|
|
351
|
+
// matching the no-newcomer-vs-queue distinction). Only its short-term reset
|
|
352
|
+
// (above) or the bounded hold may fire.
|
|
305
353
|
const ms = retry.retryAt - Date.now();
|
|
306
354
|
if (ms < soonestWeekly) soonestWeekly = ms;
|
|
307
|
-
} else if (retry.cause === 'weekly_exhausted' && !retry.retryAt) {
|
|
355
|
+
} else if (!fairnessOnlyBlock && retry.cause === 'weekly_exhausted' && !retry.retryAt) {
|
|
308
356
|
// Weekly-capped but we haven't learned the reset time (cold start /
|
|
309
357
|
// probe failure). We cannot estimate a wait — flag it so the caller
|
|
310
358
|
// emits an honest "reset time unknown" error instead of waiting forever.
|
package/src/server.js
CHANGED
|
@@ -1158,7 +1158,18 @@ async function queueAndRetry(
|
|
|
1158
1158
|
}
|
|
1159
1159
|
return false;
|
|
1160
1160
|
}
|
|
1161
|
-
|
|
1161
|
+
// A 'proxy' (non-transient upstream) error fails fast. A 'network' error (the
|
|
1162
|
+
// upstream fetch threw — ECONNRESET / ETIMEDOUT / a VPN or internet blip) is the
|
|
1163
|
+
// MOST transient failure there is, and must NOT kill a live session: when the
|
|
1164
|
+
// request is a STREAMING, replayable one (heartbeat keeps it alive, buffered body
|
|
1165
|
+
// can be re-sent) and ≥1 account will recover on a finite schedule, hold it and
|
|
1166
|
+
// resume when connectivity returns — exactly the "a network drop shouldn't break
|
|
1167
|
+
// my session" goal (2026-06-27). The hold-vs-error gate below still error-fasts if
|
|
1168
|
+
// nextRetryForRequest reports no finite recovery (all accounts genuinely gone), so
|
|
1169
|
+
// a real outage doesn't spin. A non-streaming network failure (no keepalive) still
|
|
1170
|
+
// fails fast — it would die on the client's own timeout anyway.
|
|
1171
|
+
if (cause === 'proxy') return false;
|
|
1172
|
+
if (cause === 'network' && (!requestInfo.stream || !canQueueBufferedBody)) return false;
|
|
1162
1173
|
|
|
1163
1174
|
const maxWaitMs = Math.max(0, Number(queueConfig.maxWaitMs) || 0);
|
|
1164
1175
|
const autoMaxWaitMs = queueConfig.autoMaxWaitMs == null
|
|
@@ -1176,11 +1187,16 @@ async function queueAndRetry(
|
|
|
1176
1187
|
};
|
|
1177
1188
|
|
|
1178
1189
|
// Honest, cause-/thinking-aware message used for every give-up path below.
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1190
|
+
// A 'network'-cause hold that ultimately gives up means connectivity never came
|
|
1191
|
+
// back within the window — say THAT (check your internet), not the misleading
|
|
1192
|
+
// "all accounts at their quota limit" (the accounts are fine; the network isn't).
|
|
1193
|
+
const honestMessage = cause === 'network'
|
|
1194
|
+
? 'Could not connect to Claude (network error) and connectivity did not return in time. Check your internet connection and try again. This is not an account quota issue.'
|
|
1195
|
+
: unavailableMessage(
|
|
1196
|
+
accountManager, requestInfo,
|
|
1197
|
+
Math.ceil((Number.isFinite(retryPlan.retryAfterMs) ? retryPlan.retryAfterMs : 0) / 1000),
|
|
1198
|
+
false,
|
|
1199
|
+
);
|
|
1184
1200
|
|
|
1185
1201
|
// Weekly-capped but the reset time is unknown (cold start / probe failure):
|
|
1186
1202
|
// we can't estimate a wait, so don't pretend to — error honestly now.
|