maxpool 1.19.6 → 1.19.7

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 +39 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.19.6",
3
+ "version": "1.19.7",
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
@@ -1757,7 +1757,7 @@ function isContextLengthError(errorBody) {
1757
1757
  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);
1758
1758
  }
1759
1759
 
1760
- 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, isProviderParamRejection, describeBodyShape, streamResponse, startIdleRequestReaper, normalizeModelEcho };
1760
+ export const __serverTest = { rewriteBodyForAccount, sanitizeBlocksForProvider, reanchorOrphanedSystemMessages, unavailableMessage, computeQueueWindowMs, isRetriableUpstreamStatus, classifyEffortRejection, repairEffort, isCapacitySignalStatus, isStrippableThinkingBlock, stripForeignThinkingBlocks, parseRejectedBlockPath, stripRejectedBlockClass, peekRejectedBlockType, describeRejectedBlock, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, commitStreamGraceHeartbeat, describeRequest, classifyRateLimit, detectTranscriptOrigin, isAnthropicIncompatBody, isContextLengthError, isProviderParamRejection, describeBodyShape, streamResponse, startIdleRequestReaper, normalizeModelEcho };
1761
1761
 
1762
1762
  async function readErrorBody(upstreamRes, limitBytes = 64 * 1024) {
1763
1763
  if (!upstreamRes.body) return '';
@@ -2957,18 +2957,54 @@ const threadOwners = new ThreadOwners();
2957
2957
  const THREAD_GATE_ENABLED = process.env.MAXPOOL_THREAD_GATE !== '0';
2958
2958
 
2959
2959
  function rewriteBodyForAccount(body, account) {
2960
- if (!body.length || (!account.model && !account.modelMap)) return body;
2960
+ const needsProviderSanitize = account.type === 'provider';
2961
+ if (!body.length || (!account.model && !account.modelMap && !needsProviderSanitize)) return body;
2961
2962
 
2962
2963
  try {
2963
2964
  const json = JSON.parse(body.toString());
2964
2965
  if (!json || typeof json !== 'object' || !json.model) return body;
2965
- json.model = mappedModel(json.model, account);
2966
+ if (account.model || account.modelMap) json.model = mappedModel(json.model, account);
2967
+ if (needsProviderSanitize) sanitizeBlocksForProvider(json);
2966
2968
  return Buffer.from(JSON.stringify(json));
2967
2969
  } catch {
2968
2970
  return body;
2969
2971
  }
2970
2972
  }
2971
2973
 
2974
+ // Content-block types Anthropic's own client emits that a provider's validator rejects
2975
+ // outright, taking the WHOLE request with it. Measured 2026-09-13 against z.ai: a body
2976
+ // carrying one `tool_reference` block (Claude Code writes these into `tool_result` when
2977
+ // ToolSearch loads a deferred tool) returns `[1210] Invalid API parameter` — the entire
2978
+ // 5.5MB transcript refused over 12 blocks. Counterfactual on the owner's real failing
2979
+ // session: identical body with ONLY these blocks rewritten to text returned 200 OK.
2980
+ //
2981
+ // It is not a size or token limit — z.ai has a distinct code for that (`[1261] Prompt
2982
+ // too long`), and a 34KB body with the block fails while 5.5MB without it passes.
2983
+ //
2984
+ // We rewrite rather than drop: the block carries a tool NAME the conversation refers to,
2985
+ // so replacing it with the equivalent sentence keeps the transcript truthful. Anthropic
2986
+ // accounts are untouched — they understand the block natively.
2987
+ const PROVIDER_UNSUPPORTED_BLOCKS = new Set(['tool_reference']);
2988
+
2989
+ function sanitizeBlocksForProvider(json) {
2990
+ const messages = json?.messages;
2991
+ if (!Array.isArray(messages)) return;
2992
+ for (const message of messages) {
2993
+ const content = message?.content;
2994
+ if (!Array.isArray(content)) continue;
2995
+ for (const block of content) {
2996
+ // The blocks live INSIDE tool_result content, not at the message top level.
2997
+ if (!block || typeof block !== 'object' || !Array.isArray(block.content)) continue;
2998
+ for (let i = 0; i < block.content.length; i++) {
2999
+ const inner = block.content[i];
3000
+ if (!inner || typeof inner !== 'object') continue;
3001
+ if (!PROVIDER_UNSUPPORTED_BLOCKS.has(inner.type)) continue;
3002
+ block.content[i] = { type: 'text', text: `Tool loaded: ${inner.tool_name || 'unknown'}` };
3003
+ }
3004
+ }
3005
+ }
3006
+ }
3007
+
2972
3008
  function mappedModel(originalModel, account) {
2973
3009
  if (account.model) return account.model;
2974
3010
  const map = account.modelMap || {};