use bytes::Bytes;
use futures::Stream;
use crate::error::A2aError;
use crate::jsonrpc::JsonRpcResponse;
use crate::streaming::StreamResponse;
const MAX_SSE_BUFFER_SIZE: usize = 16 * 1024 * 1024;
pub fn parse_sse_stream(body: String) -> impl Stream<Item = Result<StreamResponse, A2aError>> {
async_stream::stream! {
for line in body.lines() {
let line = line.trim();
if let Some(data) = line.strip_prefix("data: ") {
match serde_json::from_str::<JsonRpcResponse>(data) {
Ok(resp) => {
if let Some(err) = resp.error {
yield Err(err);
} else if let Some(result) = resp.result {
match serde_json::from_value::<StreamResponse>(result) {
Ok(event) => yield Ok(event),
Err(e) => yield Err(A2aError::from(e)),
}
}
}
Err(e) => {
yield Err(A2aError::parse_error(e.to_string()));
}
}
}
}
}
}
pub fn parse_sse_bytes(data: Bytes) -> impl Stream<Item = Result<StreamResponse, A2aError>> {
let text = String::from_utf8_lossy(&data).to_string();
parse_sse_stream(text)
}
pub fn parse_sse_byte_stream(
stream: impl Stream<Item = Result<Bytes, reqwest::Error>> + Send + 'static,
) -> impl Stream<Item = Result<StreamResponse, A2aError>> + Send {
async_stream::stream! {
use futures::StreamExt;
let mut pinned = std::pin::pin!(stream);
let mut buffer = String::new();
while let Some(chunk) = pinned.next().await {
let chunk = match chunk {
Ok(c) => c,
Err(e) => {
yield Err(A2aError::internal(format!("Stream read error: {e}")));
return;
}
};
buffer.push_str(&String::from_utf8_lossy(&chunk));
if buffer.len() > MAX_SSE_BUFFER_SIZE {
yield Err(A2aError::internal(
"SSE stream buffer exceeded maximum size"
));
return;
}
while let Some(boundary) = buffer.find("\n\n") {
let frame = buffer[..boundary].to_string();
buffer = buffer[boundary + 2..].to_string();
yield parse_sse_frame_jsonrpc_or_error(&frame);
}
}
if !buffer.trim().is_empty() {
yield parse_sse_frame_jsonrpc_or_error(&buffer);
}
}
}
pub fn parse_sse_rest_byte_stream(
stream: impl Stream<Item = Result<Bytes, reqwest::Error>> + Send + 'static,
) -> impl Stream<Item = Result<StreamResponse, A2aError>> + Send {
async_stream::stream! {
use futures::StreamExt;
let mut pinned = std::pin::pin!(stream);
let mut buffer = String::new();
while let Some(chunk) = pinned.next().await {
let chunk = match chunk {
Ok(c) => c,
Err(e) => {
yield Err(A2aError::internal(format!("Stream read error: {e}")));
return;
}
};
buffer.push_str(&String::from_utf8_lossy(&chunk));
if buffer.len() > MAX_SSE_BUFFER_SIZE {
yield Err(A2aError::internal(
"SSE stream buffer exceeded maximum size"
));
return;
}
while let Some(boundary) = buffer.find("\n\n") {
let frame = buffer[..boundary].to_string();
buffer = buffer[boundary + 2..].to_string();
yield parse_sse_frame_rest_or_error(&frame);
}
}
if !buffer.trim().is_empty() {
yield parse_sse_frame_rest_or_error(&buffer);
}
}
}
fn extract_sse_data(frame: &str) -> Option<String> {
let mut data_parts: Vec<&str> = Vec::new();
for line in frame.lines() {
let line = line.trim_end_matches('\r');
if let Some(value) = line.strip_prefix("data:") {
data_parts.push(value.strip_prefix(' ').unwrap_or(value));
}
}
if data_parts.is_empty() {
return None;
}
let payload = data_parts.join("\n");
if payload.is_empty() {
None
} else {
Some(payload)
}
}
fn parse_sse_frame_jsonrpc_or_error(frame: &str) -> Result<StreamResponse, A2aError> {
let data = match extract_sse_data(frame) {
Some(d) => d,
None => return Err(A2aError::parse_error("SSE frame contains no data field")),
};
match serde_json::from_str::<JsonRpcResponse>(&data) {
Ok(resp) => {
if let Some(err) = resp.error {
Err(err)
} else if let Some(result) = resp.result {
serde_json::from_value::<StreamResponse>(result).map_err(A2aError::from)
} else {
Err(A2aError::parse_error(
"JSON-RPC response has neither result nor error",
))
}
}
Err(e) => Err(A2aError::parse_error(e.to_string())),
}
}
fn parse_sse_frame_rest_or_error(frame: &str) -> Result<StreamResponse, A2aError> {
let data = match extract_sse_data(frame) {
Some(d) => d,
None => return Err(A2aError::parse_error("SSE frame contains no data field")),
};
serde_json::from_str::<StreamResponse>(&data).map_err(|e| A2aError::parse_error(e.to_string()))
}