maxpool 1.10.0 → 1.10.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/server.js +83 -5
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -1657,7 +1657,7 @@ function isContextLengthError(errorBody) {
|
|
|
1657
1657
|
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
1658
|
}
|
|
1659
1659
|
|
|
1660
|
-
export const __serverTest = { unavailableMessage, computeQueueWindowMs, isRetriableUpstreamStatus, classifyEffortRejection, repairEffort, isCapacitySignalStatus, isStrippableThinkingBlock, stripForeignThinkingBlocks, parseRejectedBlockPath, stripRejectedBlockClass, peekRejectedBlockType, describeRejectedBlock, headerValue, getMaxpoolProfile, ensureQueueHeartbeat, clearQueueHeartbeat, commitStreamGraceHeartbeat, describeRequest, classifyRateLimit, detectTranscriptOrigin, isAnthropicIncompatBody, isContextLengthError, streamResponse, startIdleRequestReaper };
|
|
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 };
|
|
1661
1661
|
|
|
1662
1662
|
async function readErrorBody(upstreamRes, limitBytes = 64 * 1024) {
|
|
1663
1663
|
if (!upstreamRes.body) return '';
|
|
@@ -1905,6 +1905,52 @@ function describeRejectedBlock(body, errorBody) {
|
|
|
1905
1905
|
}
|
|
1906
1906
|
}
|
|
1907
1907
|
|
|
1908
|
+
/** Anthropic (2026-08) accepts a mid-array `system` message under one positional rule,
|
|
1909
|
+
* stated verbatim in its own 400:
|
|
1910
|
+
* "role 'system' must precede an 'assistant' message or end the array; the
|
|
1911
|
+
* directive-only form (content: [] with output_config) is accepted at any position"
|
|
1912
|
+
*
|
|
1913
|
+
* Both transcript repairs DROP a turn whose content strips empty. When that turn is the
|
|
1914
|
+
* assistant anchoring a preceding system, the system is orphaned and the NEXT request
|
|
1915
|
+
* 400s — and because the repair LATCHES the session (markSessionThinkingContaminated →
|
|
1916
|
+
* the pre-strip at retryCount===0), the orphaning recurs on every later turn. The
|
|
1917
|
+
* ordering 400 is not an isSignatureRejection, so it never reaches the recovery branches
|
|
1918
|
+
* or the friendly give-up message: it surfaces raw and the session is bricked. Measured
|
|
1919
|
+
* 2026-08-24: "stripped 21 provider thinking block(s)" at 06:12:13.278Z → that exact 400
|
|
1920
|
+
* at 06:12:13.835Z, 4 occurrences in one day.
|
|
1921
|
+
*
|
|
1922
|
+
* RE-ANCHOR, never drop the system. A system message carries load-bearing directives;
|
|
1923
|
+
* deleting one silently changes the user's session with no signal — a worse defect than
|
|
1924
|
+
* the loud 400. The placeholder reuses the `(content removed)` string this file already
|
|
1925
|
+
* emits for the messages[0] guard, so it is an established shape here, not a new one.
|
|
1926
|
+
*
|
|
1927
|
+
* Runs as a POST-PASS over the rebuilt array so it fires only on real violations:
|
|
1928
|
+
* - a system that ENDS the array is legal ("or end the array") — untouched
|
|
1929
|
+
* - a system already followed by an assistant is legal — untouched
|
|
1930
|
+
* - the directive-only form (content: [] + output_config) is legal ANYWHERE — untouched
|
|
1931
|
+
* Idempotent: a second run finds no violation, so the latched re-strip cannot grow the
|
|
1932
|
+
* transcript turn after turn.
|
|
1933
|
+
*/
|
|
1934
|
+
function reanchorOrphanedSystemMessages(messages) {
|
|
1935
|
+
let inserted = 0;
|
|
1936
|
+
const out = [];
|
|
1937
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1938
|
+
const msg = messages[i];
|
|
1939
|
+
out.push(msg);
|
|
1940
|
+
if (msg?.role !== 'system') continue;
|
|
1941
|
+
const next = messages[i + 1];
|
|
1942
|
+
if (next === undefined) continue; // ends the array — legal
|
|
1943
|
+
if (next?.role === 'assistant') continue; // already anchored — legal
|
|
1944
|
+
// The directive-only form is accepted at any position; re-anchoring it would mutate
|
|
1945
|
+
// a transcript the API already accepts.
|
|
1946
|
+
if (Array.isArray(msg.content) && msg.content.length === 0) continue;
|
|
1947
|
+
out.push({ role: 'assistant', content: [{ type: 'text', text: '(content removed)' }] });
|
|
1948
|
+
inserted++;
|
|
1949
|
+
}
|
|
1950
|
+
return { messages: out, inserted };
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
|
|
1908
1954
|
/**
|
|
1909
1955
|
* Last-resort repair driven by the upstream's own coordinate, for a rejected block no
|
|
1910
1956
|
* shape heuristic here recognised. Removes every block sharing the rejected block's
|
|
@@ -1945,13 +1991,29 @@ function stripRejectedBlockClass(body, errorBody) {
|
|
|
1945
1991
|
// and keeping the original would resend the exact body that just 400'd. Except
|
|
1946
1992
|
// messages[0], which must survive as a `user` turn (see the same guard above).
|
|
1947
1993
|
if (kept.length === 0) {
|
|
1948
|
-
|
|
1994
|
+
// messages[0] must survive AS A USER TURN — Anthropic requires the first message
|
|
1995
|
+
// to be `user`, and preserving the original role (what this did until
|
|
1996
|
+
// 2026-08-24) left a system-first transcript system-first: the guard silently
|
|
1997
|
+
// not doing the one thing it exists for.
|
|
1998
|
+
if (messages.length === 0) {
|
|
1999
|
+
messages.push({ role: 'user', content: [{ type: 'text', text: '(content removed)' }] });
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
// A SYSTEM turn is never dropped. Every other role can go — the turn carried
|
|
2003
|
+
// nothing but a poisoned block, and a gap is harmless. A system is an
|
|
2004
|
+
// INSTRUCTION channel: deleting it changes how the session behaves with no
|
|
2005
|
+
// signal to anyone, which is worse than the loud 400 the repair is fixing.
|
|
2006
|
+
// Keep the turn as a placeholder so its position and presence survive.
|
|
2007
|
+
if (msg?.role === 'system') {
|
|
2008
|
+
messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] });
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
1949
2011
|
continue;
|
|
1950
2012
|
}
|
|
1951
2013
|
messages.push({ ...msg, content: kept });
|
|
1952
2014
|
}
|
|
1953
2015
|
if (!removed) return { body: null, removed: 0, type };
|
|
1954
|
-
json.messages = messages;
|
|
2016
|
+
json.messages = reanchorOrphanedSystemMessages(messages).messages;
|
|
1955
2017
|
return { body: Buffer.from(JSON.stringify(json)), removed, type };
|
|
1956
2018
|
} catch {
|
|
1957
2019
|
return { body: null, removed: 0, type: null };
|
|
@@ -2123,13 +2185,29 @@ function stripForeignThinkingBlocks(body) {
|
|
|
2123
2185
|
// Reachable only since the role gate was removed — before that, non-assistant
|
|
2124
2186
|
// turns were never dropped at all.
|
|
2125
2187
|
if (kept.length === 0) {
|
|
2126
|
-
|
|
2188
|
+
// messages[0] must survive AS A USER TURN — Anthropic requires the first message
|
|
2189
|
+
// to be `user`, and preserving the original role (what this did until
|
|
2190
|
+
// 2026-08-24) left a system-first transcript system-first: the guard silently
|
|
2191
|
+
// not doing the one thing it exists for.
|
|
2192
|
+
if (messages.length === 0) {
|
|
2193
|
+
messages.push({ role: 'user', content: [{ type: 'text', text: '(content removed)' }] });
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
// A SYSTEM turn is never dropped. Every other role can go — the turn carried
|
|
2197
|
+
// nothing but a poisoned block, and a gap is harmless. A system is an
|
|
2198
|
+
// INSTRUCTION channel: deleting it changes how the session behaves with no
|
|
2199
|
+
// signal to anyone, which is worse than the loud 400 the repair is fixing.
|
|
2200
|
+
// Keep the turn as a placeholder so its position and presence survive.
|
|
2201
|
+
if (msg?.role === 'system') {
|
|
2202
|
+
messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] });
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2127
2205
|
continue;
|
|
2128
2206
|
}
|
|
2129
2207
|
messages.push({ ...msg, content: kept });
|
|
2130
2208
|
}
|
|
2131
2209
|
if (!removed && !converted) return { body: null, removed: 0, converted: 0 };
|
|
2132
|
-
json.messages = messages;
|
|
2210
|
+
json.messages = reanchorOrphanedSystemMessages(messages).messages;
|
|
2133
2211
|
return { body: Buffer.from(JSON.stringify(json)), removed, converted };
|
|
2134
2212
|
} catch {
|
|
2135
2213
|
return { body: null, removed: 0, converted: 0 }; // non-JSON / unparseable → no rewrite
|