Found during the PR #306 code review (finding 6 of the report-only list). Low priority — mainstream servers newline-terminate — but the failure is silent when it hits.
The behaviour
All three stream buffers require a terminator to yield their last unit, and none of the consumers drain residual bytes on EOF:
NdjsonBuffer::pop_line (src/crates/primer-inference/src/ollama.rs) requires \n; the stream-end arm never drains leftover bytes in buf.
OpenAiSseBuffer::pop_data (src/crates/primer-inference/src/openai_compat/mod.rs) likewise.
- Cloud's
SseBuffer (src/crates/primer-inference/src/cloud.rs) needs the trailing blank line of an SSE event.
If a server flushes its terminal payload — {"done":true,"done_reason":"length"} or data: [DONE] — without a trailing newline before closing the connection, that real terminal chunk is discarded. The synthetic abrupt-EOF flush chunk carries default FinishReason::Stop, so a genuine Length truncation loses its signal and the dialogue manager's context-limit recovery ladder never fires for that turn.
Suggested resolution
On the None (EOF) arm, drain any residual buffered bytes as a final line/payload before feeding the synthetic done chunk. Tests: a terminal NDJSON line without \n, and a data: [DONE] without trailing newline.
Found during the PR #306 code review (finding 6 of the report-only list). Low priority — mainstream servers newline-terminate — but the failure is silent when it hits.
The behaviour
All three stream buffers require a terminator to yield their last unit, and none of the consumers drain residual bytes on EOF:
NdjsonBuffer::pop_line(src/crates/primer-inference/src/ollama.rs) requires\n; the stream-end arm never drains leftover bytes inbuf.OpenAiSseBuffer::pop_data(src/crates/primer-inference/src/openai_compat/mod.rs) likewise.SseBuffer(src/crates/primer-inference/src/cloud.rs) needs the trailing blank line of an SSE event.If a server flushes its terminal payload —
{"done":true,"done_reason":"length"}ordata: [DONE]— without a trailing newline before closing the connection, that real terminal chunk is discarded. The synthetic abrupt-EOF flush chunk carries defaultFinishReason::Stop, so a genuineLengthtruncation loses its signal and the dialogue manager's context-limit recovery ladder never fires for that turn.Suggested resolution
On the
None(EOF) arm, drain any residual buffered bytes as a final line/payload before feeding the synthetic done chunk. Tests: a terminal NDJSON line without\n, and adata: [DONE]without trailing newline.