Summary
Session::sendStream in the Python and PHP bindings (and the underlying streaming path) does not stream incrementally. The whole reply is produced, then all deltas are delivered at once after the full generation completes.
Repro (measured)
Python binding, 120-word live generation:
chunks=9 total_chars=784
first chunk at 4.69s, last at 4.69s, spread=0.00s
Nine chunks, all arriving in the same instant after a ~4.7s wait. Same behavior in the PHP binding (10 deltas, 845 chars, all at once). The offline faux path does space its chunks, which masks this in offline testing.
Root cause
Not the bindings — the Rust core buffers the entire HTTP response before parsing SSE. crates/pidgin-ai/src/api/anthropic/driver.rs:
"produces the whole event sequence eagerly from a fully-buffered response body; inter-chunk timing is re-presented at the binding edge."
parse_sse_stream(&response.body, ...) runs only after the full body is in hand, so no binding-level change can make it incremental.
Impact
A chat UI built on sendStream (e.g. the binding demos) shows nothing until the full reply lands, then dumps it — not token-by-token. Purely a UX/latency issue; correctness is fine.
Suggested direction
Make the anthropic driver consume the SSE stream incrementally (feed the parser as bytes arrive) rather than buffering, and surface deltas through the transport seam as they come. Larger than a binding tweak — it's a transport/driver change.
Summary
Session::sendStreamin the Python and PHP bindings (and the underlying streaming path) does not stream incrementally. The whole reply is produced, then all deltas are delivered at once after the full generation completes.Repro (measured)
Python binding, 120-word live generation:
Nine chunks, all arriving in the same instant after a ~4.7s wait. Same behavior in the PHP binding (10 deltas, 845 chars, all at once). The offline faux path does space its chunks, which masks this in offline testing.
Root cause
Not the bindings — the Rust core buffers the entire HTTP response before parsing SSE.
crates/pidgin-ai/src/api/anthropic/driver.rs:parse_sse_stream(&response.body, ...)runs only after the full body is in hand, so no binding-level change can make it incremental.Impact
A chat UI built on
sendStream(e.g. the binding demos) shows nothing until the full reply lands, then dumps it — not token-by-token. Purely a UX/latency issue; correctness is fine.Suggested direction
Make the anthropic driver consume the SSE stream incrementally (feed the parser as bytes arrive) rather than buffering, and surface deltas through the transport seam as they come. Larger than a binding tweak — it's a transport/driver change.