Files
aish/ffi
marfrit 2e36381576 ffi/curl: SSE streaming via post_sse — incremental data: events
Phase 1 streaming substrate per PHASE1.md §4.

  curl.post_sse(url, body, headers, on_event, timeout_ms)
    -> true | (nil, errmsg)

Reuses the Phase 0 WRITEFUNCTION hook. Each chunk delivery accumulates
into a per-request buffer; the buffer is drained for complete events
(\n\n-terminated). Each event's `data: ...` field(s) are joined per the
SSE spec and passed to on_event(data_string) synchronously. `:` comment
lines (keepalives) are filtered.

The `[DONE]` sentinel is passed through to on_event as-is (broker.lua
filters it — this module stays HTTP-layer only, no JSON / OpenAI shape
knowledge).

Two robustness items:
  - End-of-stream flush: the final event may lack \n\n if the server
    closes-on-EOF immediately after the last data: line (some llama.cpp
    builds, plain HTTP/1.0 close-on-EOF feeds). Post-perform, any
    remaining buffer is parsed as one last event.
  - FAILONERROR: a non-2xx response surfaces as a CURLcode error rather
    than silently feeding the error body into the SSE parser.

Smoke:
  [1] canned events via nc listener: 3 events parsed in order
  [2] chunk-split mid-event ("Hel" + sleep + "lo..."): correctly
      reassembled across two WRITEFUNCTION deliveries
  [3] LIVE against hossenfelder.fritz.box:8082 fast preset with
      stream:true: response "pong" assembled from incremental deltas;
      4 raw events (role + 1 content + finish_reason + [DONE])

Next: broker.lua chat_stream that decodes the OpenAI delta shape on
top of this and exposes on_delta(content_string) for renderer streaming.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:14:54 +00:00
..