#[cfg(test)]
mod tests;
use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use a2a_lf::{JsonRpcError, JsonRpcResponse, StreamResponse};
use futures_util::Stream;
use super::{jsonrpc, rest};
use crate::a2a::config::A2aLimits;
use crate::a2a::error::{A2aError, A2aRpcError};
pub(super) const EVENT_STREAM: &str = "text/event-stream";
const BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
pub struct A2aEventStream {
events: Pin<Box<dyn Stream<Item = Result<StreamResponse, A2aError>> + Send>>,
}
impl A2aEventStream {
pub(super) fn new(
response: reqwest::Response,
decoder: Decoder,
expected_task: Option<String>,
limits: &A2aLimits,
) -> Self {
let reader = Reader {
response,
parser: SseParser::new(limits.max_event_bytes),
ready: VecDeque::new(),
decoder,
expected_task,
idle_timeout: limits.stream_idle_timeout,
failure: None,
ended: false,
finished: false,
};
let events = futures_util::stream::unfold(reader, |mut reader| async move {
let event = reader.next().await?;
Some((event, reader))
});
Self {
events: Box::pin(events),
}
}
}
impl Stream for A2aEventStream {
type Item = Result<StreamResponse, A2aError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.events.as_mut().poll_next(cx)
}
}
impl std::fmt::Debug for A2aEventStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("A2aEventStream").finish_non_exhaustive()
}
}
pub(super) struct Decoder {
pub(super) method: &'static str,
pub(super) format: EventFormat,
}
pub(super) enum EventFormat {
JsonRpc { id: i64 },
HttpJson,
}
impl Decoder {
fn decode(&self, data: &[u8]) -> Result<StreamResponse, A2aError> {
let method = self.method;
match self.format {
EventFormat::JsonRpc { id } => {
let reply: JsonRpcResponse =
serde_json::from_slice(data).map_err(|_| A2aError::Decode { method })?;
let result = jsonrpc::decode_envelope(method, id, reply)?;
serde_json::from_value(result).map_err(|_| A2aError::Decode { method })
}
EventFormat::HttpJson => decode_bare(method, data),
}
}
}
fn decode_bare(method: &'static str, data: &[u8]) -> Result<StreamResponse, A2aError> {
if let Ok(event) = serde_json::from_slice::<StreamResponse>(data) {
return Ok(event);
}
let bare_status = || {
serde_json::from_slice::<rest::Status>(data)
.ok()
.filter(rest::Status::is_status)
};
let error = if let Ok(status) = serde_json::from_slice::<rest::StatusEnvelope>(data) {
status.error.a2a_error()
} else if let Some(status) = bare_status() {
status.a2a_error()
} else if let Ok(error) = serde_json::from_slice::<JsonRpcError>(data) {
Some(A2aRpcError::from_code(error.code))
} else {
return Err(A2aError::Decode { method });
};
Err(match error {
Some(error) => A2aError::Rpc { method, error },
None => A2aError::Transport("the event stream reported an error"),
})
}
struct Reader {
response: reqwest::Response,
parser: SseParser,
ready: VecDeque<Vec<u8>>,
decoder: Decoder,
expected_task: Option<String>,
idle_timeout: Duration,
failure: Option<A2aError>,
ended: bool,
finished: bool,
}
impl Reader {
async fn next(&mut self) -> Option<Result<StreamResponse, A2aError>> {
loop {
if self.finished {
return None;
}
if let Some(data) = self.ready.pop_front() {
if data.iter().all(u8::is_ascii_whitespace) {
continue;
}
let event = self
.decoder
.decode(&data)
.and_then(|event| self.expect_task(event));
return Some(self.finish_on_error(event));
}
if let Some(error) = self.failure.take() {
return Some(self.finish_on_error(Err(error)));
}
if self.ended {
self.finished = true;
return None;
}
let chunk = tokio::time::timeout(self.idle_timeout, self.response.chunk()).await;
match chunk {
Err(_) => self.failure = Some(A2aError::StreamIdle(self.idle_timeout)),
Ok(Err(_)) => {
self.failure = Some(A2aError::Transport("the event stream was interrupted"));
}
Ok(Ok(Some(chunk))) => {
let mut events = Vec::new();
self.failure = self.parser.push(&chunk, &mut events).err();
self.ready.extend(events);
}
Ok(Ok(None)) => {
self.ended = true;
self.ready.extend(self.parser.finish());
}
}
}
}
fn expect_task(&mut self, event: StreamResponse) -> Result<StreamResponse, A2aError> {
let Some(task_id) = event_task_id(&event) else {
return Ok(event);
};
match &self.expected_task {
Some(expected) if expected != task_id => Err(A2aError::MismatchedTask {
method: self.decoder.method,
}),
Some(_) => Ok(event),
None => {
self.expected_task = Some(task_id.to_string());
Ok(event)
}
}
}
fn finish_on_error(
&mut self,
event: Result<StreamResponse, A2aError>,
) -> Result<StreamResponse, A2aError> {
self.finished |= event.is_err();
event
}
}
fn event_task_id(event: &StreamResponse) -> Option<&str> {
match event {
StreamResponse::Task(task) => Some(&task.id),
StreamResponse::Message(message) => message.task_id.as_deref(),
StreamResponse::StatusUpdate(update) => Some(&update.task_id),
StreamResponse::ArtifactUpdate(update) => Some(&update.task_id),
}
}
pub(super) struct SseParser {
bom: Option<usize>,
line: Vec<u8>,
data: Vec<u8>,
has_data: bool,
after_cr: bool,
limit: usize,
}
impl SseParser {
pub(super) fn new(limit: usize) -> Self {
Self {
bom: Some(0),
line: Vec::new(),
data: Vec::new(),
has_data: false,
after_cr: false,
limit,
}
}
pub(super) fn push(&mut self, chunk: &[u8], events: &mut Vec<Vec<u8>>) -> Result<(), A2aError> {
for &byte in chunk {
if let Some(matched) = self.bom {
if byte == BOM[matched] {
self.bom = (matched + 1 < BOM.len()).then_some(matched + 1);
continue;
}
self.bom = None;
for &held in &BOM[..matched] {
self.take(held, events)?;
}
}
self.take(byte, events)?;
}
Ok(())
}
fn take(&mut self, byte: u8, events: &mut Vec<Vec<u8>>) -> Result<(), A2aError> {
let after_cr = std::mem::replace(&mut self.after_cr, byte == b'\r');
match byte {
b'\n' if after_cr => {}
b'\r' | b'\n' => self.end_line(events),
_ => {
self.line.push(byte);
if self.line.len() + self.data.len() > self.limit {
return Err(A2aError::EventTooLarge { limit: self.limit });
}
}
}
Ok(())
}
pub(super) fn finish(&mut self) -> Vec<Vec<u8>> {
let mut events = Vec::new();
if let Some(matched) = self.bom.take() {
self.line.extend_from_slice(&BOM[..matched]);
}
if !self.line.is_empty() {
self.end_line(&mut events);
}
if self.has_data {
self.end_line(&mut events);
}
events
}
fn end_line(&mut self, events: &mut Vec<Vec<u8>>) {
let line = std::mem::take(&mut self.line);
if line.is_empty() {
if std::mem::take(&mut self.has_data) {
events.push(std::mem::take(&mut self.data));
}
return;
}
if line.starts_with(b":") {
return;
}
let (field, value) = match line.iter().position(|&byte| byte == b':') {
Some(colon) => {
let value = &line[colon + 1..];
(&line[..colon], value.strip_prefix(b" ").unwrap_or(value))
}
None => (&line[..], &[][..]),
};
if field == b"data" {
if self.has_data {
self.data.push(b'\n');
}
self.data.extend_from_slice(value);
self.has_data = true;
}
}
}