Unified multi-provider LLM SSE stream normalizer and cross-provider message sanitizer (REQ-PBT-04).
Normalizes raw Server-Sent Event (SSE) byte streams from:
:anthropic(anthropic-messages):openai(openai-responses/openai-completions):google(google-generative-ai)
into a canonical sequence of events:
{:start, %{model: String.t(), provider: atom()}}{:text_delta, %{index: non_neg_integer(), delta: String.t()}}{:thinking_delta, %{index: non_neg_integer(), delta: String.t(), signature: String.t() | nil}}{:toolcall_delta, %{index: non_neg_integer(), id: String.t(), name: String.t(), raw_json_delta: String.t(), partial_args: map()}}{:done, %{stop_reason: String.t(), content: list(map()), usage: map()}}{:error, %{reason: term()}}
Guarantees chunk-boundary invariance (REQ-PBT-04): arbitrary byte-level chunk splitting of an
SSE stream produces the exact same final :done message and accumulated tool-call arguments.
Summary
Functions
Feeds an arbitrary binary chunk into the SSE normalizer and returns {normalized_events, next_state}.
Handles arbitrary mid-line and mid-JSON chunk boundaries without losing state.
Flushes any remaining complete SSE data in the buffer and returns {events, final_state}.
Initializes a new stream normalizer state for provider and model.
Best-effort incremental JSON object parser for streaming toolcall_delta arguments.
Automatically closes unclosed strings, arrays, and objects so callers can inspect partial_args
before the tool call block finishes streaming.
Sanitizes conversation history messages when switching across LLM providers or models (spec.md §5)
Types
@type t() :: %SuperPi.AI.StreamNormalizer{ active_block_idx: non_neg_integer() | nil, block_counter: non_neg_integer(), blocks: map(), buffer: String.t(), data_lines: [String.t()], done?: boolean(), last_was_cr?: boolean(), model: String.t(), provider: :anthropic | :openai | :google, started?: boolean(), stop_reason: String.t(), usage: map() }
Functions
Feeds an arbitrary binary chunk into the SSE normalizer and returns {normalized_events, next_state}.
Handles arbitrary mid-line and mid-JSON chunk boundaries without losing state.
Flushes any remaining complete SSE data in the buffer and returns {events, final_state}.
Initializes a new stream normalizer state for provider and model.
Best-effort incremental JSON object parser for streaming toolcall_delta arguments.
Automatically closes unclosed strings, arrays, and objects so callers can inspect partial_args
before the tool call block finishes streaming.
Sanitizes conversation history messages when switching across LLM providers or models (spec.md §5):
- Strips provider-proprietary
thinkingSignature/redacted_thinkingfields or downgrades unrecognized thinking blocks to plain text / omitted metadata when moving between providers (e.g.:anthropic->:openaior:google). - Preserves
thinkingSignatureintact when the target(provider, model)matches the block's origin.