use std::collections::HashMap;
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::Bytes;
use futures_core::Stream;
use futures_util::StreamExt;
use serde::de::{self, Deserializer};
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use crate::error::{self, Error, Result};
use crate::types::{ContentBlock, Message, StopDetails, StopReason, Usage};
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum StreamEvent {
MessageStart {
message: Message,
},
ContentBlockStart {
index: usize,
content_block: ContentBlock,
},
ContentBlockDelta {
index: usize,
delta: ContentDelta,
},
ContentBlockStop {
index: usize,
},
MessageDelta {
delta: MessageDeltaBody,
usage: Option<UsageDelta>,
},
MessageStop,
Ping,
Error {
error: StreamErrorBody,
},
Unknown(serde_json::Value),
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ContentDelta {
TextDelta {
text: String,
},
InputJsonDelta {
partial_json: String,
},
ThinkingDelta {
thinking: String,
},
SignatureDelta {
signature: String,
},
CitationsDelta {
citation: serde_json::Value,
},
Unknown(serde_json::Value),
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MessageDeltaBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<StopReason>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_sequence: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_details: Option<StopDetails>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct UsageDelta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_creation_input_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_read_input_tokens: Option<u64>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StreamErrorBody {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl Serialize for ContentDelta {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ContentDelta::TextDelta { text } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "text_delta")?;
map.serialize_entry("text", text)?;
map.end()
}
ContentDelta::InputJsonDelta { partial_json } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "input_json_delta")?;
map.serialize_entry("partial_json", partial_json)?;
map.end()
}
ContentDelta::ThinkingDelta { thinking } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "thinking_delta")?;
map.serialize_entry("thinking", thinking)?;
map.end()
}
ContentDelta::SignatureDelta { signature } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "signature_delta")?;
map.serialize_entry("signature", signature)?;
map.end()
}
ContentDelta::CitationsDelta { citation } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "citations_delta")?;
map.serialize_entry("citation", citation)?;
map.end()
}
ContentDelta::Unknown(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for ContentDelta {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let tag = value
.get("type")
.and_then(|t| t.as_str())
.map(str::to_owned);
let delta = match tag.as_deref() {
Some("text_delta") => {
#[derive(Deserialize)]
struct R {
#[serde(default)]
text: String,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
ContentDelta::TextDelta { text: r.text }
}
Some("input_json_delta") => {
#[derive(Deserialize)]
struct R {
#[serde(default)]
partial_json: String,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
ContentDelta::InputJsonDelta {
partial_json: r.partial_json,
}
}
Some("thinking_delta") => {
#[derive(Deserialize)]
struct R {
#[serde(default)]
thinking: String,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
ContentDelta::ThinkingDelta {
thinking: r.thinking,
}
}
Some("signature_delta") => {
#[derive(Deserialize)]
struct R {
#[serde(default)]
signature: String,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
ContentDelta::SignatureDelta {
signature: r.signature,
}
}
Some("citations_delta") => {
#[derive(Deserialize)]
struct R {
#[serde(default)]
citation: serde_json::Value,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
ContentDelta::CitationsDelta {
citation: r.citation,
}
}
_ => ContentDelta::Unknown(value),
};
Ok(delta)
}
}
impl Serialize for StreamEvent {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
StreamEvent::MessageStart { message } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "message_start")?;
map.serialize_entry("message", message)?;
map.end()
}
StreamEvent::ContentBlockStart {
index,
content_block,
} => {
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("type", "content_block_start")?;
map.serialize_entry("index", index)?;
map.serialize_entry("content_block", content_block)?;
map.end()
}
StreamEvent::ContentBlockDelta { index, delta } => {
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("type", "content_block_delta")?;
map.serialize_entry("index", index)?;
map.serialize_entry("delta", delta)?;
map.end()
}
StreamEvent::ContentBlockStop { index } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "content_block_stop")?;
map.serialize_entry("index", index)?;
map.end()
}
StreamEvent::MessageDelta { delta, usage } => {
let len = if usage.is_some() { 3 } else { 2 };
let mut map = serializer.serialize_map(Some(len))?;
map.serialize_entry("type", "message_delta")?;
map.serialize_entry("delta", delta)?;
if let Some(usage) = usage {
map.serialize_entry("usage", usage)?;
}
map.end()
}
StreamEvent::MessageStop => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("type", "message_stop")?;
map.end()
}
StreamEvent::Ping => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("type", "ping")?;
map.end()
}
StreamEvent::Error { error } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "error")?;
map.serialize_entry("error", error)?;
map.end()
}
StreamEvent::Unknown(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for StreamEvent {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let tag = value
.get("type")
.and_then(|t| t.as_str())
.map(str::to_owned);
let event = match tag.as_deref() {
Some("message_start") => {
#[derive(Deserialize)]
struct R {
message: Message,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::MessageStart { message: r.message }
}
Some("content_block_start") => {
#[derive(Deserialize)]
struct R {
index: usize,
content_block: ContentBlock,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::ContentBlockStart {
index: r.index,
content_block: r.content_block,
}
}
Some("content_block_delta") => {
#[derive(Deserialize)]
struct R {
index: usize,
delta: ContentDelta,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::ContentBlockDelta {
index: r.index,
delta: r.delta,
}
}
Some("content_block_stop") => {
#[derive(Deserialize)]
struct R {
index: usize,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::ContentBlockStop { index: r.index }
}
Some("message_delta") => {
#[derive(Deserialize)]
struct R {
delta: MessageDeltaBody,
#[serde(default)]
usage: Option<UsageDelta>,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::MessageDelta {
delta: r.delta,
usage: r.usage,
}
}
Some("message_stop") => StreamEvent::MessageStop,
Some("ping") => StreamEvent::Ping,
Some("error") => {
#[derive(Deserialize)]
struct R {
error: StreamErrorBody,
}
let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
StreamEvent::Error { error: r.error }
}
_ => StreamEvent::Unknown(value),
};
Ok(event)
}
}
fn parse_field(line: &str) -> (&str, &str) {
match line.find(':') {
Some(idx) => {
let field = &line[..idx];
let value = &line[idx + 1..];
let value = value.strip_prefix(' ').unwrap_or(value);
(field, value)
}
None => (line, ""),
}
}
#[derive(Default)]
struct SseDecoder {
buffer: Vec<u8>,
data: String,
saw_data: bool,
}
impl SseDecoder {
fn push(&mut self, bytes: &[u8]) {
self.buffer.extend_from_slice(bytes);
}
fn next_event_data(&mut self) -> Option<String> {
loop {
let newline = self.buffer.iter().position(|&byte| byte == b'\n')?;
let line_bytes: Vec<u8> = self.buffer.drain(..=newline).collect();
let mut end = line_bytes.len() - 1; if end > 0 && line_bytes[end - 1] == b'\r' {
end -= 1; }
let line = String::from_utf8_lossy(&line_bytes[..end]);
if line.is_empty() {
if self.saw_data {
self.saw_data = false;
return Some(std::mem::take(&mut self.data));
}
self.data.clear();
continue;
}
if line.starts_with(':') {
continue; }
let (field, value) = parse_field(line.as_ref());
if field == "data" {
if self.saw_data {
self.data.push('\n');
}
self.data.push_str(value);
self.saw_data = true;
}
}
}
fn flush(&mut self) -> Option<String> {
if !self.buffer.is_empty() {
let line_bytes = std::mem::take(&mut self.buffer);
let mut end = line_bytes.len();
if end > 0 && line_bytes[end - 1] == b'\n' {
end -= 1;
}
if end > 0 && line_bytes[end - 1] == b'\r' {
end -= 1;
}
let line = String::from_utf8_lossy(&line_bytes[..end]);
if !line.is_empty() && !line.starts_with(':') {
let (field, value) = parse_field(line.as_ref());
if field == "data" {
if self.saw_data {
self.data.push('\n');
}
self.data.push_str(value);
self.saw_data = true;
}
}
}
if self.saw_data {
self.saw_data = false;
Some(std::mem::take(&mut self.data))
} else {
None
}
}
}
const MAX_STREAM_CONTENT_BLOCKS: usize = 1 << 16;
fn set_block(content: &mut Vec<ContentBlock>, index: usize, block: ContentBlock) {
if index < content.len() {
content[index] = block;
} else if index < MAX_STREAM_CONTENT_BLOCKS {
while content.len() < index {
content.push(ContentBlock::Unknown(serde_json::Value::Null));
}
content.push(block);
}
}
fn merge_usage(target: &mut Usage, delta: &UsageDelta) {
if let Some(value) = delta.input_tokens {
target.input_tokens = value;
}
if let Some(value) = delta.output_tokens {
target.output_tokens = value;
}
if let Some(value) = delta.cache_creation_input_tokens {
target.cache_creation_input_tokens = Some(value);
}
if let Some(value) = delta.cache_read_input_tokens {
target.cache_read_input_tokens = Some(value);
}
for (key, value) in &delta.extra {
target.extra.insert(key.clone(), value.clone());
}
}
#[derive(Default)]
struct Accumulator {
message: Option<Message>,
json_buffers: HashMap<usize, String>,
}
impl Accumulator {
fn apply(&mut self, event: &StreamEvent) {
match event {
StreamEvent::MessageStart { message } => {
self.message = Some(message.clone());
self.json_buffers.clear();
}
StreamEvent::ContentBlockStart {
index,
content_block,
} => {
if let Some(message) = self.message.as_mut() {
set_block(&mut message.content, *index, content_block.clone());
}
if matches!(
content_block,
ContentBlock::ToolUse(_) | ContentBlock::ServerToolUse(_)
) {
self.json_buffers.insert(*index, String::new());
}
}
StreamEvent::ContentBlockDelta { index, delta } => {
self.apply_delta(*index, delta);
}
StreamEvent::ContentBlockStop { index } => {
self.finish_block(*index);
}
StreamEvent::MessageDelta { delta, usage } => {
if let Some(message) = self.message.as_mut() {
if delta.stop_reason.is_some() {
message.stop_reason = delta.stop_reason.clone();
}
if delta.stop_sequence.is_some() {
message.stop_sequence = delta.stop_sequence.clone();
}
if delta.stop_details.is_some() {
message.stop_details = delta.stop_details.clone();
}
if let Some(usage) = usage {
merge_usage(&mut message.usage, usage);
}
}
}
StreamEvent::MessageStop
| StreamEvent::Ping
| StreamEvent::Error { .. }
| StreamEvent::Unknown(_) => {}
}
}
fn apply_delta(&mut self, index: usize, delta: &ContentDelta) {
if let ContentDelta::InputJsonDelta { partial_json } = delta {
self.json_buffers
.entry(index)
.or_default()
.push_str(partial_json);
return;
}
let Some(message) = self.message.as_mut() else {
return;
};
let Some(block) = message.content.get_mut(index) else {
return;
};
match delta {
ContentDelta::TextDelta { text } => {
if let ContentBlock::Text(inner) = block {
inner.text.push_str(text);
}
}
ContentDelta::ThinkingDelta { thinking } => {
if let ContentBlock::Thinking(inner) = block {
inner.thinking.push_str(thinking);
}
}
ContentDelta::SignatureDelta { signature } => {
if let ContentBlock::Thinking(inner) = block {
inner.signature.push_str(signature);
}
}
ContentDelta::CitationsDelta { citation } => {
if let ContentBlock::Text(inner) = block {
inner
.citations
.get_or_insert_with(Vec::new)
.push(citation.clone());
}
}
ContentDelta::InputJsonDelta { .. } | ContentDelta::Unknown(_) => {}
}
}
fn finish_block(&mut self, index: usize) {
let Some(json) = self.json_buffers.remove(&index) else {
return;
};
let parsed = if json.trim().is_empty() {
serde_json::Value::Object(serde_json::Map::new())
} else {
match serde_json::from_str(&json) {
Ok(value) => value,
Err(_) => serde_json::Value::String(json),
}
};
if let Some(message) = self.message.as_mut() {
if let Some(ContentBlock::ToolUse(inner) | ContentBlock::ServerToolUse(inner)) =
message.content.get_mut(index)
{
inner.input = parsed;
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub trait MaybeSend: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send> MaybeSend for T {}
#[cfg(target_arch = "wasm32")]
pub trait MaybeSend {}
#[cfg(target_arch = "wasm32")]
impl<T> MaybeSend for T {}
#[cfg(not(target_arch = "wasm32"))]
type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes>> + Send>>;
#[cfg(target_arch = "wasm32")]
type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes>>>>;
pub struct MessageStream {
source: ByteStream,
decoder: SseDecoder,
accumulator: Accumulator,
source_done: bool,
done: bool,
}
impl MessageStream {
pub(crate) fn new(response: reqwest::Response) -> Self {
let source = response
.bytes_stream()
.map(|chunk| chunk.map_err(Error::from));
Self::from_pinned(Box::pin(source))
}
#[doc(hidden)]
pub fn from_byte_stream<S>(stream: S) -> Self
where
S: Stream<Item = Result<Bytes>> + MaybeSend + 'static,
{
Self::from_pinned(Box::pin(stream))
}
fn from_pinned(source: ByteStream) -> Self {
Self {
source,
decoder: SseDecoder::default(),
accumulator: Accumulator::default(),
source_done: false,
done: false,
}
}
pub fn final_message(&self) -> Option<&Message> {
self.accumulator.message.as_ref()
}
pub async fn collect_final(mut self) -> Result<Message> {
while let Some(item) = self.next().await {
item?;
}
self.accumulator
.message
.ok_or_else(|| Error::Stream("stream ended without a message_start event".to_string()))
}
fn decode(&mut self, data: &str) -> Option<Result<StreamEvent>> {
if data.trim().is_empty() {
return None;
}
match serde_json::from_str::<StreamEvent>(data) {
Ok(StreamEvent::Error { error }) => {
self.done = true;
Some(Err(error::from_error_body(error.error_type, error.message)))
}
Ok(event) => {
self.accumulator.apply(&event);
Some(Ok(event))
}
Err(source) => {
self.done = true;
Some(Err(Error::from(source)))
}
}
}
}
impl Stream for MessageStream {
type Item = Result<StreamEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.done {
return Poll::Ready(None);
}
loop {
if let Some(data) = this.decoder.next_event_data() {
if let Some(item) = this.decode(&data) {
return Poll::Ready(Some(item));
}
continue;
}
if this.source_done {
if let Some(data) = this.decoder.flush() {
if let Some(item) = this.decode(&data) {
return Poll::Ready(Some(item));
}
}
this.done = true;
return Poll::Ready(None);
}
match this.source.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(bytes))) => {
this.decoder.push(&bytes);
continue;
}
Poll::Ready(Some(Err(err))) => {
this.done = true;
return Poll::Ready(Some(Err(err)));
}
Poll::Ready(None) => {
this.source_done = true;
continue;
}
Poll::Pending => return Poll::Pending,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sse_decoder_tolerates_crlf_and_comments() {
let mut decoder = SseDecoder::default();
decoder.push(b": heartbeat comment\r\n");
decoder.push(b"event: ping\r\ndata: {\"type\":\"ping\"}\r\n\r\n");
let data = decoder.next_event_data().expect("one record");
assert_eq!(data, "{\"type\":\"ping\"}");
assert!(decoder.next_event_data().is_none());
}
#[test]
fn sse_decoder_joins_multiline_data() {
let mut decoder = SseDecoder::default();
decoder.push(b"data: line one\ndata: line two\n\n");
let data = decoder.next_event_data().expect("one record");
assert_eq!(data, "line one\nline two");
}
#[test]
fn content_delta_round_trips() {
for raw in [
r#"{"type":"text_delta","text":"Hi"}"#,
r#"{"type":"input_json_delta","partial_json":"{\"a\":1}"}"#,
r#"{"type":"thinking_delta","thinking":"hmm"}"#,
r#"{"type":"signature_delta","signature":"sig"}"#,
] {
let delta: ContentDelta = serde_json::from_str(raw).expect("parse");
let out = serde_json::to_string(&delta).expect("serialize");
let reparsed: ContentDelta = serde_json::from_str(&out).expect("reparse");
assert_eq!(delta, reparsed);
}
}
#[test]
fn empty_input_json_becomes_empty_object() {
let mut acc = Accumulator::default();
let message_start: StreamEvent = serde_json::from_str(
r#"{"type":"message_start","message":{"id":"m1","type":"message","role":"assistant","model":"claude-opus-4-8","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":1}}}"#,
)
.expect("parse message_start");
acc.apply(&message_start);
let start: StreamEvent = serde_json::from_str(
r#"{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"t1","name":"noop","input":{}}}"#,
)
.expect("parse start");
acc.apply(&start);
let stop: StreamEvent =
serde_json::from_str(r#"{"type":"content_block_stop","index":0}"#).expect("parse stop");
acc.apply(&stop);
let message = acc.message.expect("no message");
match &message.content[0] {
ContentBlock::ToolUse(inner) => {
assert_eq!(inner.input, serde_json::json!({}));
}
other => panic!("expected tool_use, got {other:?}"),
}
}
}