///|
/// A server-sent-events reader that takes the stream in whatever pieces the network
/// delivers. A frame's fields may be split across two reads, and one read may carry
/// several frames; the reader only dispatches at the blank line that ends a frame, so
/// the boundaries of the transport never leak into the dialect.
pub struct Sse {
mut buf : String
mut event : String?
mut data : Array[String]
mut started : Bool
}
///|
/// A reader with nothing buffered.
pub fn Sse::new() -> Sse {
{ buf: "", event: None, data: [], started: false, }
}
///|
/// Take one piece of the stream and return the frames it completed.
pub fn Sse::feed(self : Sse, chunk : StringView) -> Array[Frame] {
self.buf = self.buf + chunk.to_owned()
let frames = []
while true {
match self.buf.find("\n") {
None => break
Some(at) => {
let line = self.buf.exact_view(start=0, end=at).to_owned()
self.buf = self.buf.exact_view(start=at + 1).to_owned()
let line = strip_cr(line)
self.line(line, frames)
}
}
}
frames
}
///|
/// Dispatch whatever a truncated stream left behind. A server that closes without the
/// final blank line has still sent a complete frame.
pub fn Sse::finish(self : Sse) -> Array[Frame] {
let frames = []
if self.buf != "" {
let line = strip_cr(self.buf)
self.buf = ""
self.line(line, frames)
}
self.dispatch(frames)
frames
}
///|
/// Whether a frame is in progress.
pub fn Sse::is_idle(self : Sse) -> Bool {
!self.started && self.event is None && self.data.is_empty() && self.buf == ""
}
///|
/// One line of the stream.
fn Sse::line(self : Sse, line : String, frames : Array[Frame]) -> Unit {
if line == "" {
self.dispatch(frames)
return
}
if line.has_prefix(":") {
// A comment, which is also the keep-alive most servers send.
return
}
let (field, value) = match line.find(":") {
None => (line, "")
Some(at) => {
let rest = line.exact_view(start=at + 1).to_owned()
let value = if rest.has_prefix(" ") {
rest.exact_view(start=1).to_owned()
} else {
rest
}
(line.exact_view(start=0, end=at).to_owned(), value)
}
}
match field {
"event" => {
self.event = Some(value)
self.started = true
}
"data" => {
self.data.push(value)
self.started = true
}
_ =>
// `id` and `retry` do not change what a model stream means.
self.started = true
}
}
///|
/// Emit the frame the accumulated fields describe, if any.
fn Sse::dispatch(self : Sse, frames : Array[Frame]) -> Unit {
if self.event is None && self.data.is_empty() {
self.started = false
return
}
let data = self.data.join("\n")
frames.push({ event: self.event, data, })
self.event = None
self.data = []
self.started = false
}
///|
/// A carriage return left by a CRLF stream is not part of the line.
fn strip_cr(line : String) -> String {
if line.has_suffix("\r") {
line.exact_view(start=0, end=line.length() - 1).to_owned()
} else {
line
}
}
///|
/// Read a whole server-sent-events body at once.
pub fn parse_sse(text : StringView) -> Array[Frame] {
let reader = Sse::new()
let frames = reader.feed(text)
let tail = reader.finish()
frames + tail
}
///|
/// Read a whole newline-delimited JSON body — Ollama's streaming shape and the
/// fallback for endpoints that stream bare objects.
pub fn parse_ndjson(text : StringView) -> Array[Frame] {
let frames = []
for line in text.split("\n") {
let line = strip_cr(line.to_owned()).trim()
if line != "" {
frames.push({ event: None, data: line.to_owned(), })
}
}
frames
}
///|
/// Encode frames as a server-sent-events body, with the event name when there is one.
pub fn encode_sse(frames : Array[Frame]) -> String {
let out = StringBuilder()
for frame in frames {
match frame.event {
Some(event) => {
out.write_string("event: ")
out.write_string(event)
out.write_string("\n")
}
None => ()
}
for line in frame.data.split("\n") {
out.write_string("data: ")
out.write_string(line.to_owned())
out.write_string("\n")
}
out.write_string("\n")
}
out.to_string()
}