maxpool 1.19.1 → 1.19.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +31 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.19.1",
3
+ "version": "1.19.2",
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
@@ -1355,8 +1355,16 @@ async function forwardRequest(
1355
1355
  res.end();
1356
1356
  }
1357
1357
  } else {
1358
- if (!res.headersSent) res.writeHead(upstreamRes.status, responseHeaders);
1359
- res.end(buf);
1358
+ // MODEL ECHO: rewrite the provider's model id back to the client's before the
1359
+ // body reaches Claude Code. Computed BEFORE writeHead so a length change lands
1360
+ // in the SAME header block — setting content-length after writeHead is a no-op
1361
+ // and would ship a body/length mismatch (the client then hangs or truncates).
1362
+ const outBuf = normalizeModelEcho(buf, requestInfo.model);
1363
+ const outHeaders = outBuf === buf
1364
+ ? responseHeaders
1365
+ : { ...responseHeaders, 'content-length': String(outBuf.length) };
1366
+ if (!res.headersSent) res.writeHead(upstreamRes.status, outHeaders);
1367
+ res.end(outBuf);
1360
1368
  }
1361
1369
  }
1362
1370
  } catch (err) {
@@ -1657,7 +1665,7 @@ function isContextLengthError(errorBody) {
1657
1665
  return /exceeded model token limit|maximum context length|context length exceeded|context window (?:size )?(?:exceeded|too)|prompt is too long|input is too long|reduce the length of|too many (?:input )?tokens|request too large/i.test(errorBody);
1658
1666
  }
1659
1667
 
1660
- export const __serverTest = { reanchorOrphanedSystemMessages, unavailableMessage, computeQueueWindowMs, isRetriableUpstreamStatus, classifyEffortRejection, repairEffort, isCapacitySignalStatus, isStrippableThinkingBlock, stripForeignThinkingBlocks, parseRejectedBlockPath, stripRejectedBlockClass, peekRejectedBlockType, describeRejectedBlock, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, commitStreamGraceHeartbeat, describeRequest, classifyRateLimit, detectTranscriptOrigin, isAnthropicIncompatBody, isContextLengthError, streamResponse, startIdleRequestReaper };
1668
+ export const __serverTest = { reanchorOrphanedSystemMessages, unavailableMessage, computeQueueWindowMs, isRetriableUpstreamStatus, classifyEffortRejection, repairEffort, isCapacitySignalStatus, isStrippableThinkingBlock, stripForeignThinkingBlocks, parseRejectedBlockPath, stripRejectedBlockClass, peekRejectedBlockType, describeRejectedBlock, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, commitStreamGraceHeartbeat, describeRequest, classifyRateLimit, detectTranscriptOrigin, isAnthropicIncompatBody, isContextLengthError, streamResponse, startIdleRequestReaper, normalizeModelEcho };
1661
1669
 
1662
1670
  async function readErrorBody(upstreamRes, limitBytes = 64 * 1024) {
1663
1671
  if (!upstreamRes.body) return '';
@@ -2857,6 +2865,26 @@ function startIdleRequestReaper(res, reqId, idleMs, { now = Date.now, setInterva
2857
2865
  }
2858
2866
 
2859
2867
 
2868
+ /** Rewrite a non-streaming JSON body's `model` back to the CLIENT'S requested model.
2869
+ * Twin of the SSE model-echo normalization in streamResponse: provider upstreams
2870
+ * (z.ai/GLM, Kimi) echo their own id, Claude Code persists it, and a resumed session
2871
+ * then prints "Session model glm-5.3 could not be restored". The streaming half
2872
+ * shipped in v1.19.1; this covers the buffered path, which is where the remaining
2873
+ * 5,719 contaminated turns came from (measured 2026-09-02). No-op when the ids
2874
+ * already match, when the body is not JSON, or when no client model is known. */
2875
+ function normalizeModelEcho(buf, clientModel) {
2876
+ if (!clientModel || !buf?.length) return buf;
2877
+ try {
2878
+ const json = JSON.parse(buf.toString('utf8'));
2879
+ if (!json || typeof json !== 'object' || typeof json.model !== 'string') return buf;
2880
+ if (json.model === clientModel) return buf;
2881
+ json.model = clientModel;
2882
+ return Buffer.from(JSON.stringify(json), 'utf8');
2883
+ } catch {
2884
+ return buf; // non-JSON (or truncated) body — forward untouched
2885
+ }
2886
+ }
2887
+
2860
2888
  function concatUint8(chunks) {
2861
2889
  let n = 0;
2862
2890
  for (const c of chunks) n += c.length;