maxpool 1.5.36 → 1.5.37

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +20 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.5.36",
3
+ "version": "1.5.37",
4
4
  "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/server.js CHANGED
@@ -56,6 +56,11 @@ const DEFAULT_QUEUE = {
56
56
  // Non-streaming requests have no SSE heartbeat to keep them alive, so a long
57
57
  // hold would die on the client timeout anyway. Cap their wait conservatively.
58
58
  nonStreamMaxWaitMs: 5 * 60 * 1000,
59
+ // count_tokens is cheap non-streaming metadata — cap its queue wait VERY low so it
60
+ // fast-fails with a retryable 429 instead of hanging silently past the client's idle
61
+ // window. Kept well under any plausible client idle timeout (observed errors as low
62
+ // as ~23s). Env-overridable for tuning as the "Stream idle timeout" reports resolve.
63
+ countTokensMaxWaitMs: Math.max(1000, Number(process.env.MAXPOOL_COUNT_TOKENS_MAX_WAIT_MS) || 8000),
59
64
  // Backpressure: holds used to be 0ms, now they can be hours. Bound the queue
60
65
  // so 22 retrying agents can't grow the heap without limit.
61
66
  maxConcurrentQueued: 64,
@@ -1095,6 +1100,7 @@ function formatRetryDuration(seconds) {
1095
1100
  function computeQueueWindowMs({
1096
1101
  cause, stream, retryPlanCause,
1097
1102
  maxWaitMs, capacityMaxWaitMs, nonStreamMaxWaitMs, streamHoldMaxMs,
1103
+ isCountTokens, countTokensMaxWaitMs,
1098
1104
  }) {
1099
1105
  let windowMs;
1100
1106
  if (!stream) {
@@ -1105,6 +1111,10 @@ function computeQueueWindowMs({
1105
1111
  windowMs = streamHoldMaxMs;
1106
1112
  }
1107
1113
  if (retryPlanCause === 'concurrency_cap') windowMs = Math.min(windowMs, capacityMaxWaitMs);
1114
+ // count_tokens: cap the QUEUE wait low (bounds only the wait-for-an-account, never
1115
+ // the upstream processing once acquired) so a non-heartbeated metadata call fast-
1116
+ // fails with a retryable 429 instead of hanging past the client's idle window.
1117
+ if (isCountTokens && countTokensMaxWaitMs != null) windowMs = Math.min(windowMs, countTokensMaxWaitMs);
1108
1118
  return windowMs;
1109
1119
  }
1110
1120
 
@@ -1392,6 +1402,9 @@ async function queueAndRetry(
1392
1402
  const nonStreamMaxWaitMs = queueConfig.nonStreamMaxWaitMs == null
1393
1403
  ? 5 * 60_000
1394
1404
  : Math.max(0, Number(queueConfig.nonStreamMaxWaitMs) || 0);
1405
+ const countTokensMaxWaitMs = queueConfig.countTokensMaxWaitMs == null
1406
+ ? 8000
1407
+ : Math.max(0, Number(queueConfig.countTokensMaxWaitMs) || 0);
1395
1408
  const retryPlan = accountManager.nextRetryForRequest?.(requestInfo, new Set()) || {
1396
1409
  retryAfterMs: Infinity,
1397
1410
  cause: 'unavailable',
@@ -1426,6 +1439,8 @@ async function queueAndRetry(
1426
1439
  capacityMaxWaitMs,
1427
1440
  nonStreamMaxWaitMs,
1428
1441
  streamHoldMaxMs,
1442
+ isCountTokens: Boolean(requestInfo.isCountTokens),
1443
+ countTokensMaxWaitMs,
1429
1444
  });
1430
1445
 
1431
1446
  if (queueWindowMs <= 0) return finishQueuedStreamIfNeeded(res, requestInfo, honestMessage);
@@ -1596,6 +1611,11 @@ function describeRequest(req, body) {
1596
1611
  path: req.url,
1597
1612
  bodyBytes: body.length,
1598
1613
  weight,
1614
+ // count_tokens is cheap non-streaming metadata with NO SSE heartbeat, so a long
1615
+ // queue-hold just dies on the client's idle window ("Stream idle timeout - no
1616
+ // chunks received"). Flag it (URL-based, so it's set regardless of body parse) to
1617
+ // cap its queue wait short and fast-fail with a retryable 429 instead of hanging.
1618
+ isCountTokens: /\/v1\/messages\/count_tokens\b/.test(req.url),
1599
1619
  };
1600
1620
  try {
1601
1621
  const json = JSON.parse(body.toString());