From 81d60ca30dfdf48e4ec069ac283dc95c5659e5bd Mon Sep 17 00:00:00 2001 From: Peter Lindsey Date: Tue, 30 Jun 2026 23:04:39 +0800 Subject: [PATCH] fix(provider-transport-fetch): raise SSE sanitize buffer cap to 16 MiB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 64 KiB inter-event SSE sanitize buffer added in #96989 rejects a single legitimate event larger than 64 KiB — e.g. a large gpt-5.5 reasoning summary on the openai-chatgpt-responses API — throwing "SSE response exceeded max buffer size (65536 bytes) without event boundary" and failing the whole request. The default ChatGPT-subscription gpt-5.5 path is unusable (present in v2026.6.11-beta.1). Decouple the two bounds: keep the non-OK error-body cap tight at 64 KiB (SSE_NONOK_BODY_MAX_BYTES), and raise the inter-event sanitize buffer to the same 16 MiB ceiling as the JSON-synthesis path. The guard still trips on a genuinely boundary-less (hostile/broken) stream, just not on a real large event. Co-Authored-By: Claude Opus 4.8 --- src/agents/provider-transport-fetch.test.ts | 2 +- src/agents/provider-transport-fetch.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/agents/provider-transport-fetch.test.ts b/src/agents/provider-transport-fetch.test.ts index 402df82e4f53..b2ea7886ab83 100644 --- a/src/agents/provider-transport-fetch.test.ts +++ b/src/agents/provider-transport-fetch.test.ts @@ -1370,7 +1370,7 @@ describe("buildGuardedModelFetch", () => { }); it("errors on oversized SSE body without event boundary in sanitizer", async () => { - const oversized = "x".repeat(65 * 1024); + const oversized = "x".repeat(16 * 1024 * 1024 + 1024); const encoder = new TextEncoder(); fetchWithSsrFGuardMock.mockResolvedValue({ response: new Response( diff --git a/src/agents/provider-transport-fetch.ts b/src/agents/provider-transport-fetch.ts index 5f1c8e8c4e15..071ff243e2ce 100644 --- a/src/agents/provider-transport-fetch.ts +++ b/src/agents/provider-transport-fetch.ts @@ -51,10 +51,16 @@ const log = createSubsystemLogger("provider-transport-fetch"); * without Content-Length. */ const SSE_SYNTHESIZE_JSON_MAX_BYTES = 16 * 1024 * 1024; +/** Max bytes read from a non-OK (error) response body before truncation. Error + * payloads are small, so keep this tight to bound memory on a hostile error stream. */ +const SSE_NONOK_BODY_MAX_BYTES = 64 * 1024; + /** Max bytes for the internal SSE sanitization buffer between event boundaries. - * A response that cannot find a \n\n boundary within this many characters is - * almost certainly hostile or broken — cap the buffer rather than let it grow. */ -const SSE_SANITIZE_BUFFER_MAX_BYTES = 64 * 1024; + * A single legitimate event (e.g. a large reasoning summary on the chatgpt-responses + * API) can far exceed 64 KiB, so bound this at the same 16 MiB ceiling as the + * JSON-synthesis path: only a genuinely boundary-less (hostile/broken) stream trips + * the guard, not a real large event. */ +const SSE_SANITIZE_BUFFER_MAX_BYTES = 16 * 1024 * 1024; const BLOCKED_EXACT_ORIGIN_TRUST_HOSTNAME_LABELS = new Set(["instance-data"]); const PLAIN_DECIMAL_NUMBER_RE = /^\d+(?:\.\d+)?$/; @@ -132,7 +138,7 @@ function sanitizeOpenAISdkSseResponse( return response; } if (!response.ok) { - return capNonOkResponseBodyLazily(response, SSE_SANITIZE_BUFFER_MAX_BYTES); + return capNonOkResponseBodyLazily(response, SSE_NONOK_BODY_MAX_BYTES); } if ( options?.synthesizeJsonAsSse === true &&