maxpool 1.5.1 → 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 +54 -39
- package/src/server.js +22 -6
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -250,48 +250,53 @@ export class AccountManager {
|
|
|
250
250
|
|
|
251
251
|
for (const account of this.accounts) {
|
|
252
252
|
if (excludedIndexes.has(account.index)) continue;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
&& this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
285
|
-
soonestBoundedHold = Math.min(soonestBoundedHold, BOUNDED_REPOLL_HOLD_MS);
|
|
286
|
-
if (!boundedHoldCause) boundedHoldCause = 'queued_behind_fairness';
|
|
287
|
-
} else if (account.type === 'provider' && this._requiresAnthropicThinkingIntegrity(requestInfo)) {
|
|
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) {
|
|
283
|
+
if (account.type === 'provider' && this._requiresAnthropicThinkingIntegrity(requestInfo)) {
|
|
288
284
|
note('provider_fallback_disabled_signed_thinking');
|
|
289
285
|
}
|
|
290
286
|
continue;
|
|
291
287
|
}
|
|
292
288
|
|
|
293
289
|
matchingRoutes++;
|
|
294
|
-
|
|
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 })) {
|
|
295
300
|
return {
|
|
296
301
|
available: true,
|
|
297
302
|
retryAfterMs: 0,
|
|
@@ -300,10 +305,15 @@ export class AccountManager {
|
|
|
300
305
|
matchingRoutes,
|
|
301
306
|
};
|
|
302
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
|
+
}
|
|
303
313
|
|
|
304
314
|
const retry = this._retryInfo(account);
|
|
305
315
|
note(retry.cause);
|
|
306
|
-
if (retry.weeklyCritical && this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
316
|
+
if (!fairnessOnlyBlock && retry.weeklyCritical && this._isAvailable(account, { allowWeeklyReserve: true, allowWeeklyCritical: true })) {
|
|
307
317
|
return {
|
|
308
318
|
available: true,
|
|
309
319
|
retryAfterMs: 0,
|
|
@@ -334,10 +344,15 @@ export class AccountManager {
|
|
|
334
344
|
// hold when no weekly-critical account contributed it.
|
|
335
345
|
if (retry.weeklyCritical) boundedHoldCause = 'weekly_critical';
|
|
336
346
|
else if (!boundedHoldCause) boundedHoldCause = 'concurrency_cap';
|
|
337
|
-
} 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.
|
|
338
353
|
const ms = retry.retryAt - Date.now();
|
|
339
354
|
if (ms < soonestWeekly) soonestWeekly = ms;
|
|
340
|
-
} else if (retry.cause === 'weekly_exhausted' && !retry.retryAt) {
|
|
355
|
+
} else if (!fairnessOnlyBlock && retry.cause === 'weekly_exhausted' && !retry.retryAt) {
|
|
341
356
|
// Weekly-capped but we haven't learned the reset time (cold start /
|
|
342
357
|
// probe failure). We cannot estimate a wait — flag it so the caller
|
|
343
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.
|