Server-Sent Events codec with Last-Event-ID resumption.
Event id scheme: "<task_id>:<seq>". seq is a monotonic integer, so
clients can use Last-Event-ID to request replay from a known point
without ambiguity across tasks.
Wire format per the HTML Living Standard:
id: <task_id>:<seq>
event: <name>
data: <serialized JSON>
<blank line>Multiple data: lines are supported (spec says consumer concatenates
with \n). This encoder emits a single line and JSON-encodes the payload
verbatim, which is the common case and unambiguous.
Comment frames (: keep-alive) are emitted via keep_alive/0 to prevent
intermediary timeouts on long-idle streams.
Summary
Functions
Decode a full SSE event block (lines separated by \n, terminated by
blank line). Returns the parsed {id, event, data} tuple or an error.
Encode one SSE event.
Build the canonical SSE event id for a (task_id, seq) pair.
Emit a keep-alive comment frame to reset intermediary timeouts.
Parse an event id back into {task_id, seq}.
Interpret a Last-Event-ID header value for stream resumption.
Functions
@spec decode_event(String.t()) :: {:ok, %{id: String.t() | nil, event: String.t() | nil, data: String.t()}} | {:error, term()}
Decode a full SSE event block (lines separated by \n, terminated by
blank line). Returns the parsed {id, event, data} tuple or an error.
Does NOT parse the data as JSON — callers decide what to do with it.
Encode one SSE event.
payload may be a map (will be JSON-encoded) or an already-encoded string.
Build the canonical SSE event id for a (task_id, seq) pair.
@spec keep_alive() :: iodata()
Emit a keep-alive comment frame to reset intermediary timeouts.
Parse an event id back into {task_id, seq}.
iex> A2aEngine.Codec.SSE.parse_event_id("abc123:7")
{:ok, {"abc123", 7}}
iex> A2aEngine.Codec.SSE.parse_event_id("bogus")
{:error, :invalid_event_id}
Interpret a Last-Event-ID header value for stream resumption.
Returns {:ok, {task_id, seq}} when the header is a well-formed A2A event
id, or {:error, _} when the client has never connected before (header
absent or malformed).