- ai: StreamingJsonParser / ToolCallArgumentsStream read each argument delta once (tolerant,
immutable snapshots, linear cost); every provider, pi-messages and the faux provider use it
and keep stream state (partialJson, partialArgs, index, customInput, streamIndex, Bedrock
reasoning bytes) off the content blocks; grammar tool input appends per delta
- agent: streamProxy uses the same parser
- chord: Delta append ops own their text (ownedSlice), so ops and replicas no longer keep the
whole growing string alive
- durable, ai, mcp: bounded error excerpts and summaries own their characters
- coding-agent: tool calls render streamed arguments once per frame, not once per delta
This change makes system prompt text and tool changes part of the transcript rather than silently rewriting its starting conditions. This lets Pi record when instructions changed or tools became available, restore that state after resuming or navigating branches, and preserve cached prompt prefixes where the upstream supports it.
streamProxy dropped a final SSE event that was not newline-terminated
(the residual line buffer was never flushed after the read loop) and
called stream.end() without a result when no done/error event had been
seen, leaving EventStream.result() pending forever. Consumers like
agentLoop awaiting response.result() hung indefinitely when the server
closed the connection mid-response.
Flush the decoder and process the residual buffer after EOF, and push a
synthesized error event when the stream ended without a terminal event.
fixes#8996