Skip to main content

a2a_client/components/
streaming.rs

1//! Server-Sent Events (SSE) streaming components
2
3use a2a_rs::services::{AsyncA2AClient, StreamItem};
4use axum::response::sse::{Event, KeepAlive, Sse};
5use futures::StreamExt;
6use std::{convert::Infallible, sync::Arc, time::Duration};
7use tracing::{error, info, warn};
8
9use crate::WebA2AClient;
10
11/// Create an SSE stream for task updates
12///
13/// This function handles:
14/// - WebSocket streaming if available
15/// - Fallback to HTTP polling
16/// - Automatic retry logic
17/// - Serialization to JSON events
18pub fn create_sse_stream(
19    client: Arc<WebA2AClient>,
20    task_id: String,
21) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
22    let stream = async_stream::stream! {
23        info!("Attempting to subscribe to task {} via HTTP stream", task_id);
24
25        let mut retry_count: u32 = 0;
26        let max_retries = 15; // Covers ~2 minutes using exponential backoff
27        let base_delay = Duration::from_millis(500);
28        let max_delay = Duration::from_secs(10);
29        let mut is_terminal = false;
30
31        loop {
32            match client.http.subscribe_to_task(&task_id, Some(50)).await {
33                Ok(mut event_stream) => {
34                    info!("Successfully subscribed to task {} via HTTP stream", task_id);
35                    retry_count = 0; // Reset retries on successful connection
36
37                    while let Some(result) = event_stream.next().await {
38                        match result {
39                            Ok(stream_item) => {
40                                use a2a_rs::domain::TaskStateExt;
41                                let (event_type, event_data) = match &stream_item {
42                                    StreamItem::Task(task) => {
43                                        if let Some(status) = task.status.as_option() {
44                                            if status.state.is_terminal() {
45                                                is_terminal = true;
46                                            }
47                                        }
48                                        match serde_json::to_string(task) {
49                                            Ok(json) => ("task-update", json),
50                                            Err(e) => {
51                                                error!("Failed to serialize task: {}", e);
52                                                continue;
53                                            }
54                                        }
55                                    }
56                                    StreamItem::StatusUpdate(status) => {
57                                        if status.status.state.is_terminal() {
58                                            is_terminal = true;
59                                        }
60                                        match serde_json::to_string(status) {
61                                            Ok(json) => ("task-status", json),
62                                            Err(e) => {
63                                                error!("Failed to serialize status: {}", e);
64                                                continue;
65                                            }
66                                        }
67                                    }
68                                    StreamItem::ArtifactUpdate(artifact) => {
69                                        match serde_json::to_string(artifact) {
70                                            Ok(json) => ("artifact", json),
71                                            Err(e) => {
72                                                error!("Failed to serialize artifact: {}", e);
73                                                continue;
74                                            }
75                                        }
76                                    }
77                                };
78
79                                yield Ok(Event::default()
80                                    .event(event_type)
81                                    .data(event_data));
82                            }
83                            Err(e) => {
84                                warn!("Stream error (continuing): {}", e);
85                                continue;
86                            }
87                        }
88                    }
89
90                    if is_terminal {
91                        info!("Task {} reached terminal state. Ending stream gracefully.", task_id);
92                        break;
93                    } else {
94                        warn!("Stream ended prematurely for task {}. Retrying...", task_id);
95                        // Do not break; it will loop around and reconnect.
96                    }
97                }
98                Err(e) => {
99                    retry_count += 1;
100
101                    if retry_count <= max_retries {
102                        // Exponential delay: base_delay * 2^(retry_count - 1)
103                        let factor = 2u64.saturating_pow(retry_count.saturating_sub(1).min(6));
104                        let delay = base_delay.saturating_mul(factor as u32);
105
106                        // Jitter calculation (0-200ms) based on task_id bytes and system time
107                        let jitter_ms = {
108                            let mut state = 0u64;
109                            for &b in task_id.as_bytes() {
110                                state = state.wrapping_mul(6364136223846793005).wrapping_add(b as u64);
111                            }
112                            let time_ms = std::time::SystemTime::now()
113                                .duration_since(std::time::UNIX_EPOCH)
114                                .unwrap_or_default()
115                                .as_millis() as u64;
116                            state = state.wrapping_mul(6364136223846793005).wrapping_add(time_ms);
117                            state % 200
118                        };
119                        let final_delay = delay.saturating_add(Duration::from_millis(jitter_ms)).min(max_delay);
120
121                        warn!(
122                            "Failed to subscribe to task {} (attempt {}/{}): {}. Retrying in {:?}...",
123                            task_id, retry_count, max_retries, e, final_delay
124                        );
125
126                        tokio::time::sleep(final_delay).await;
127                        continue;
128                    } else {
129                        warn!("Failed to subscribe after {} retries: {}, aborting stream", max_retries, e);
130                        break;
131                    }
132                }
133            }
134        }
135    };
136
137    Sse::new(stream).keep_alive(KeepAlive::default())
138}