open-sse/executors/huggingchat/jsonlStream.ts accumulated the entire upstream
NDJSON body into buffer/fullText with no byte ceiling, and the only exit
condition was a signal.aborted check polled once per loop iteration — so an
already in-flight reader.read() never noticed an abort until the next chunk
arrived. A stalled or hostile HuggingChat backend that never emits a terminal
finalAnswer/status:finished marker could buffer indefinitely and exhaust the
heap.
Fix:
- Track accumulated bytes in both streamJsonlToOpenAi() and
readJsonlResponse() and cancel the reader once HUGGINGCHAT_MAX_BODY_BYTES
(4 MiB) is exceeded, mirroring the readCappedBuffer/readBodyCapped pattern
already used by veoaifree-web.ts and context7-fetch.ts.
- streamJsonlToOpenAi() yields a sanitized upstream_error SSE chunk + [DONE]
instead of throwing mid-stream (the response is already committed as 200);
readJsonlResponse() throws HuggingChatStreamError, which huggingchat.ts's
existing catch already turns into a 502 buildErrorBody() response.
- Bind reader cancellation to the plain signal (not just the cancellation
signal) in both functions, so an in-flight read unblocks the instant a
caller-supplied signal aborts instead of only being noticed on the next
loop iteration.
- huggingchat.ts now passes combinedSignal (signal + AbortSignal.timeout
(FETCH_TIMEOUT_MS)) into both call sites instead of the bare signal, so the
existing fetch timeout actually bounds the body-read phase too.