fix(openai,proxy): bound streaming 200 success-body SSE reads via shared internal guard#96666
Conversation
… internal guard ClawSweeper review follow-up: revert stream-reader-with-limit from a new public plugin-SDK subpath to an internal helper under src/agents/. The new subpath required maintainer API-boundary acceptance plus regenerated SDK metadata; deferring that to the extension follow-up PRs (extensions/google + extensions/ollama) keeps this PR a clean bounded-refactor with no public SDK surface change. The helper now lives at src/agents/streaming-byte-guard.ts, mirroring the existing readResponseWithLimit / readProviderJsonResponse family in src/agents/provider-http-errors.ts. Both Anthropic SSE parsers (the new src/agents/anthropic-transport-stream.ts parser and the legacy src/llm/providers/anthropic.ts iterateSseMessages) consume the helper via relative imports, no plugin-sdk boundary involved. Removal summary: - DELETE src/plugin-sdk/stream-reader-with-limit.ts - CREATE src/agents/streaming-byte-guard.ts (rename with comment update) - UPDATE src/agents/anthropic-transport-stream.ts (relative import) - UPDATE src/llm/providers/anthropic.ts (relative import) - UPDATE scripts/repro/issue-anthropic-sse-bound-read.mjs (relative path) - REMOVE entry from scripts/lib/plugin-sdk-entrypoints.json (line 314) - REMOVE ./plugin-sdk/stream-reader-with-limit export from package.json - REMOVE row from docs/plugins/sdk-subpaths.md - REMOVE two now-unused @ts-expect-error directives in anthropic-transport-stream.test.ts Re-verification: - vitest: 115/115 (src/agents/anthropic-transport-stream.test.ts + src/llm/providers/anthropic.test.ts) - repro script: 5/5 PASS (real node:http server, 64 MiB content-length-less body) - tsgo: clean - oxlint: clean - plugin-sdk:check-exports: synced - docs/.generated/plugin-sdk-api-baseline.sha256: unchanged (SDK surface restored) Closes ClawSweeper P1 blockers from review on this PR: new public SDK subpath requires maintainer acceptance (deferred), SDK metadata drift (resolved by removal).
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open: current main still has the unbounded OpenAI Codex Responses and proxy SSE success-body reads, and the PR is a plausible internal hardening path with sufficient real behavior proof. It is not merge-ready because the latest head still fails the scripts lint shard, the helper stack depends on open related work, and the new 16 MiB cutoff is a compatibility-sensitive behavior change. Canonical path: Close this PR as superseded by #96632. So I’m closing this here and keeping the remaining discussion on #96632. Review detailsBest possible solution: Close this PR as superseded by #96632. Do we have a high-confidence way to reproduce the issue? Yes. Current-main source shows both target SSE paths append streaming chunks without a total byte cap, and the PR body includes real local HTTP oversized-stream proof showing cancellation after the fix. Is this the best way to solve the issue? Mostly yes. The internal guard matches the existing bounded-response pattern and covers both core call sites, but the lint failure, open stack dependency, and cap acceptance remain before merge. Security review: Security review cleared: The diff reduces an unbounded external-response availability risk and does not add dependency, workflow, lockfile, secret-handling, downloaded-artifact, or public SDK-surface concerns. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 56d95b18f4bc. |
…red internal guard Same OOM family as PR openclaw#96632 (Anthropic SSE success-body bound), applied to the two remaining core streaming-body surfaces that read response.body.getReader() without an accumulation cap: - src/llm/providers/openai-chatgpt-responses.ts (parseSSE, ChatGPT Responses) - src/agents/runtime/proxy.ts (streamProxy, internal proxy route) Both surfaces accumulate decoder-decoded bytes into a string buffer while waiting for the next frame delimiter, with no upper bound. A hostile or malfunctioning endpoint (ChatGPT Responses, custom proxy backend) can return a Content-Length-less SSE body that streams forever, exhausting process memory on every /v1/messages or proxied-stream call. Fix: wrap the existing response.body.getReader() in the shared src/agents/streaming-byte-guard.ts createSseByteGuard helper (introduced in openclaw#96632, now reused). 16 MiB cap, matching readProviderJsonResponse. The existing chunk-by-chunk parsing loop, frame delimiter handling, and signal abort checks are unchanged. For proxy.ts the abort handler also routes through guard.cancel() so the bounded cancel propagates immediately on signal abort (with a fallback to reader.cancel() for the brief window before the guard is constructed). Tests: - src/llm/providers/openai-chatgpt-responses.test.ts: new describe block drives parseSSEForTest (renamed from parseSSE for direct testing) against a hostile 1 MiB-per-pull streaming body, asserts canonical overflow message and bounded pullCount (17-20 pulls), plus a happy-path end-to-end parse. - src/agents/runtime/proxy.test.ts: new integration test stubs fetch with a hostile ReadableStream body, asserts the stream ends with the bounded error and pullCount is bounded. Real-environment proof: scripts/repro/issue-openai-proxy-sse-bound-read.mjs runs against a real node:http server bound to 127.0.0.1 that streams a Content-Length-less 64 MiB body chunk-by-chunk. Five PASS lines: 1. bounded rejection: bytesSent ~19 MiB (<< 64 MiB), server.aborted=true 2. cap-trace: bounded reader cancelled at ~16 MiB 3. negative control: small body fully drained, no cap 4. happy path: small valid OpenAI SSE drained end-to-end 5. legacy-path parity: parseSSEForTest overflow surfaces canonical error Stacked on fix/anthropic-stream-bound-read (openclaw#96632); no new public SDK subpath, no SDK metadata drift, no plugin boundary touched. Stacked-on: fix/anthropic-stream-bound-read
3be9e87 to
7da6061
Compare
|
@clawsweeper re-review Fixed all 10 lint errors from your first review on the new repro script. The strict scripts lint config ( Categories fixed:
Pre-flight now all green:
On the other ClawSweeper points (not requiring code changes, addressed in PR body):
Force-pushed as |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Closing in favor of the Alix-007-style XS split. Why: This PR's diff (L size, 10 files, 1131 LoC) and the "stacked on #96632" structure compound the review risk Alix-007's per-surface pattern avoids. ClawSweeper's gold-shrimp verdict flagged the stacking explicitly: "The PR is stacked on open PR #96632 for the shared internal guard and should land after that prerequisite." What happens to this work:
Structure after split:
The new structure removes the stacking dependency and brings each surface into Alix-007's bounded-read family template. |
… at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body).
… at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body).
… at 16 MiB (#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in #96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR #96666, after PR #3b (#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
Apply createSseByteGuard to src/agents/runtime/proxy.ts (streamProxy) so the runtime proxy SSE parser cannot be exhausted by a hostile or malfunctioning proxy endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/agents/runtime/proxy.ts: wrap response.body.getReader() in createSseByteGuard before the existing streamProxy SSE read loop. The abort handler and finally block use guard.cancel() instead of reader.cancel(). - Inline test added to proxy.test.ts: hostile 1 MiB pull stream through streamProxy, asserts errorMessage matches overflow pattern, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the final per-surface rescue from the previously closed PR openclaw#96666, after PR #3c (openclaw#96762). No SDK surface change. No repro script committed (proof in this body).
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 5880e0a)
What Problem This Solves
src/llm/providers/openai-chatgpt-responses.tsandsrc/agents/runtime/proxy.tsaccumulate an unbounded SSE byte stream into a string buffer while waiting for frame delimiters. Both surfaces are reachable from every ChatGPT Responses model call (openai-chatgpt-responsesAPI, used by the OpenAI Codex Responses plugin and downstream proxies). A hostile or malfunctioning upstream — including a hijacked ChatGPT Responses mirror, a custom proxy backend that does not bound its streams, or a self-hosted LLM mirror serving the OpenAI Codex protocol — can return aContent-Length-less SSE body that streams forever, exhausting process memory on a code path that runs for every/v1/messagesor proxied-stream call.This is the same OOM family as #96632 (Anthropic SSE success-body bound) and Alix-007's bound-stream sweep (
#95218,#95223,#96027,#96035,#96038,#96042): same 16 MiB cap, same helper-driven design, but on the two remaining core streaming-body surfaces that readresponse.body.getReader()without an accumulation cap.Stacks on
fix/anthropic-stream-bound-read(#96632). Reuses the existingsrc/agents/streaming-byte-guard.tshelper that PR #96632 introduced as a core-internal helper. No new public SDK subpath, no SDK metadata drift.Changes
src/llm/providers/openai-chatgpt-responses.ts:719—parseSSEnow wrapsresponse.body.getReader()increateSseByteGuardwithOPENAI_CODEX_RESPONSES_SUCCESS_BODY_MAX_BYTES = 16 * 1024 * 1024. Existing\n\nframe parsing,JSON.parsefailure wrapping inCodexProtocolError, andreader.releaseLock()cleanup are unchanged. Also exports the function asparseSSEForTestfor direct testing.src/agents/runtime/proxy.ts:194—streamProxynow wrapsresponse.body!.getReader()increateSseByteGuardwithPROXY_SUCCESS_BODY_MAX_BYTES = 16 * 1024 * 1024. The abort handler at line 158 also routes throughguard.cancel()so a bounded cancel propagates immediately on signal abort (with a fallback toreader.cancel()for the brief window before the guard is constructed). Existing\nline splitting, terminal-event tracking, and signal-abort checks are unchanged.src/llm/providers/openai-chatgpt-responses.test.ts— newdescribe("parseSSEForTest (streaming 200 success-body bound)")block:bounds streamed OpenAI Codex Responses success bodies without content-lengthdrivesparseSSEForTestagainst a hostile 1 MiB-per-pull streaming body viaonPull/onCancelcallbacks, asserts the canonical overflow message (OpenAI Codex Responses success body exceeded 16777216 bytes), confirmscancel(reason)was invoked with anErrorinstance, and verifies pull count is bounded (17–20 pulls, well below the unconstrained ceiling).parses normal OpenAI Codex Responses SSE responses end-to-endensures small valid SSE frames still parse through the bounded reader unchanged.src/agents/runtime/proxy.test.ts— new integration testbounds streamed proxy success bodies without content-lengthstubsfetchwith a hostile 1 MiB-per-pullReadableStreambody, drivesstreamProxy, asserts the stream ends with a bounded error containingProxy success body exceeded 16777216 bytes, and verifies pull count is bounded (17–20 pulls).Real behavior proof
node:httpserver bound to127.0.0.1returning aContent-Length-less 64 MiB body (4× the cap) chunk-by-chunk with backpressure, plus a small (8 MiB) negative-control body, plus a happy-path body, plus a legacy-path parity check driving the productionparseSSEForTest. Run withpnpm exec tsx scripts/repro/issue-openai-proxy-sse-bound-read.mjs.node scripts/run-vitest.mjs openai-chatgpt-responses proxy.test anthropic-transport-stream anthropic.test --run— 84/84 passed (4 files; 2 new tests inopenai-chatgpt-responses.test.ts, 1 new test inproxy.test.ts, 79 pre-existing tests in the two anthropic files untouched by this PR)pnpm exec tsx scripts/repro/issue-openai-proxy-sse-bound-read.mjs— 5/5 PASSnode scripts/run-tsgo.mjs— cleannode scripts/run-oxlint.mjs— cleannode scripts/sync-plugin-sdk-exports.mjs --check—plugin-sdk exports synced.createSseByteGuardcancelled the upstreamReadableStreamDefaultReaderafter ~17–19 MiB (≪ 64 MiB) for both bounded readers. Server-sidebytesSentobserved at 18,874,368 — bounded, not drained. Negative control confirms the cap is the cause of the overflow, not body structure. Legacy-path parity confirms the productionparseSSErejects with the canonical error.Out of scope (separate PR)
extensions/google/transport-stream.ts+extensions/ollama/src/stream.ts— both extensions consume the sameReadableStreamDefaultReader<Uint8Array>pattern and will need the same 16 MiB cap. This is a separate PR because extensions cannot importsrc/agents/**per the boundary rule insrc/plugin-sdk/CLAUDE.md; the helper will need to be promoted back through the public plugin-SDK seam with full SDK metadata synchronization when that PR is opened.Risk
readProviderJsonResponsefor non-streaming Anthropic success bodies (and now Anthropic SSE success bodies via fix(anthropic): bound streaming 200 success-body SSE reads via shared plugin-sdk guard #96632). The behavior for normal-sized responses is unchanged — only the unbounded case now throws a clear overflow error instead of buffering without limit.createSseByteGuard.cancel(reason)propagates the overflow error to the underlying reader; verified byexpect(cancelReason).toBeInstanceOf(Error)in the test.src/agents/streaming-byte-guard.ts), identical to PR fix(anthropic): bound streaming 200 success-body SSE reads via shared plugin-sdk guard #96632's final state.docs/.generated/plugin-sdk-api-baseline.sha256is unchanged from upstreammain.Diff vs PR #96632 (its base)
The streaming-byte-guard.ts helper and its consumer files in
src/agents/anthropic-transport-stream.ts+src/llm/providers/anthropic.tscome from PR #96632 (stacked, not duplicated).