Skip to main content

a2a_client/
lib.rs

1//! # a2a-client
2//!
3//! Reusable Rust library for building web-based frontends for A2A (Agent-to-Agent) Protocol agents.
4//!
5//! ## Overview
6//!
7//! This library provides components and utilities for creating web applications that interact
8//! with A2A protocol agents. It wraps the lower-level [`a2a-rs`](https://docs.rs/a2a-rs) clients
9//! with a web-friendly API and includes ready-to-use components for common use cases.
10//!
11//! ## Features
12//!
13//! - **Unified Client API** - Single interface for both HTTP and WebSocket transports
14//! - **SSE Streaming** - Server-Sent Events with automatic fallback to HTTP polling
15//! - **View Models** - Ready-to-use view models for tasks and messages
16//! - **Auto-reconnection** - Resilient WebSocket connections with retry logic
17//! - **Type-safe** - Leverages Rust's type system for protocol correctness
18//!
19//! ## Quick Start
20//!
21//! ### Basic HTTP Client
22//!
23//! ```rust,no_run
24//! use a2a_client::WebA2AClient;
25//! use a2a_rs::domain::Message;
26//! use a2a_rs::services::AsyncA2AClient;
27//!
28//! # #[tokio::main]
29//! # async fn main() -> anyhow::Result<()> {
30//! // Create a client connected to your A2A agent
31//! let client = WebA2AClient::new_http("http://localhost:8080".to_string());
32//!
33//! // Send a message
34//! let message = Message::user_text("Hello, agent!".to_string(), "msg-1".to_string());
35//!
36//! let task = client.http.send_task_message("task-1", &message, None, None).await?;
37//! println!("Task ID: {}", task.id);
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! ### With WebSocket Support
43//!
44//! ```rust
45//! use a2a_client::WebA2AClient;
46//!
47//! // Create client with both HTTP and WebSocket
48//! let client = WebA2AClient::new_with_websocket(
49//!     "http://localhost:8080".to_string(),
50//!     "ws://localhost:8080/ws".to_string()
51//! );
52//!
53//! if client.has_websocket() {
54//!     println!("WebSocket support available!");
55//! }
56//! ```
57//!
58//! ### SSE Streaming with Axum (requires `axum-components` feature)
59//!
60//! ```rust,ignore
61//! # #[cfg(feature = "axum-components")]
62//! # {
63//! use a2a_client::{WebA2AClient, components::create_sse_stream};
64//! use axum::{Router, routing::get, extract::{State, Path}};
65//! use std::sync::Arc;
66//!
67//! # #[tokio::main]
68//! # async fn main() -> anyhow::Result<()> {
69//! let client = Arc::new(WebA2AClient::new_http("http://localhost:8080".to_string()));
70//!
71//! let app = Router::new()
72//!     .route("/stream/:task_id", get(stream_handler))
73//!     .with_state(client);
74//!
75//! // Start your Axum server...
76//! # Ok(())
77//! # }
78//!
79//! async fn stream_handler(
80//!     State(client): State<Arc<WebA2AClient>>,
81//!     Path(task_id): Path<String>,
82//! ) -> axum::response::sse::Sse<impl futures::Stream<Item = Result<axum::response::sse::Event, std::convert::Infallible>>> {
83//!     create_sse_stream(client, task_id)
84//! }
85//! # }
86//! ```
87//!
88//! ## Components
89//!
90//! - [`WebA2AClient`] - Main client wrapper for HTTP and WebSocket transports
91//! - [`components::TaskView`] - View model for displaying tasks in lists
92//! - [`components::MessageView`] - View model for displaying individual messages
93//! - [`components::create_sse_stream`] - SSE stream creation with auto-fallback (requires `axum-components`)
94//! - [`utils::formatters`] - Formatting utilities for A2A types
95//!
96//! ## Feature Flags
97//!
98//! - `axum-components` (default) - Enables Axum-specific SSE streaming components
99//!
100//! ## Integration
101//!
102//! This library integrates with:
103//! - [`a2a-rs`](https://docs.rs/a2a-rs) - Core A2A protocol implementation
104//! - [`a2a-agents`](https://docs.rs/a2a-agents) - Declarative agent framework
105//! - Any agent implementing the A2A Protocol v0.3.0
106//!
107//! ## Examples
108//!
109//! See the `examples/` directory for complete working examples of different use cases.
110
111pub mod components;
112pub mod error;
113pub mod utils;
114
115// Re-export commonly used types
116pub use error::{ClientError, Result};
117
118use a2a_rs::{HttpClient, WebSocketClient};
119use std::sync::Arc;
120
121/// Web-friendly A2A client that wraps both HTTP and WebSocket clients.
122///
123/// This is the main entry point for interacting with A2A agents from web applications.
124/// It provides a unified interface for both HTTP and WebSocket transports, with automatic
125/// fallback and retry logic.
126///
127/// # Examples
128///
129/// ## HTTP-only client
130///
131/// ```rust
132/// use a2a_client::WebA2AClient;
133///
134/// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
135/// ```
136///
137/// ## Client with WebSocket support
138///
139/// ```rust
140/// use a2a_client::WebA2AClient;
141///
142/// let client = WebA2AClient::new_with_websocket(
143///     "http://localhost:8080".to_string(),
144///     "ws://localhost:8080/ws".to_string()
145/// );
146/// ```
147///
148/// ## Auto-detecting transports
149///
150/// ```rust,no_run
151/// use a2a_client::WebA2AClient;
152///
153/// # #[tokio::main]
154/// # async fn main() -> anyhow::Result<()> {
155/// let client = WebA2AClient::auto_connect("http://localhost:8080").await?;
156/// # Ok(())
157/// # }
158/// ```
159pub struct WebA2AClient {
160    /// HTTP client for JSON-RPC requests
161    pub http: HttpClient,
162    /// Optional WebSocket client for streaming updates
163    pub ws: Option<Arc<WebSocketClient>>,
164}
165
166impl WebA2AClient {
167    /// Create a builder for configuring the client.
168    ///
169    /// # Examples
170    ///
171    /// ```rust
172    /// use a2a_client::WebA2AClient;
173    ///
174    /// let client = WebA2AClient::builder()
175    ///     .http_url("http://localhost:8080")
176    ///     .build();
177    /// ```
178    pub fn builder() -> WebA2AClientBuilder {
179        WebA2AClientBuilder::default()
180    }
181
182    /// Create a new client with HTTP transport only.
183    ///
184    /// # Arguments
185    ///
186    /// * `base_url` - Base URL of the A2A agent (e.g., `http://localhost:8080`)
187    ///
188    /// # Examples
189    ///
190    /// ```rust
191    /// use a2a_client::WebA2AClient;
192    ///
193    /// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
194    /// ```
195    pub fn new_http(base_url: String) -> Self {
196        Self {
197            http: HttpClient::new(base_url),
198            ws: None,
199        }
200    }
201
202    /// Create a new client with both HTTP and WebSocket transports.
203    ///
204    /// # Arguments
205    ///
206    /// * `http_url` - HTTP base URL (e.g., `http://localhost:8080`)
207    /// * `ws_url` - WebSocket URL (e.g., `ws://localhost:8080/ws`)
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use a2a_client::WebA2AClient;
213    ///
214    /// let client = WebA2AClient::new_with_websocket(
215    ///     "http://localhost:8080".to_string(),
216    ///     "ws://localhost:8080/ws".to_string()
217    /// );
218    /// ```
219    pub fn new_with_websocket(http_url: String, ws_url: String) -> Self {
220        Self {
221            http: HttpClient::new(http_url),
222            ws: Some(Arc::new(WebSocketClient::new(ws_url))),
223        }
224    }
225
226    /// Auto-connect to an agent, attempting to detect available transports.
227    ///
228    /// Currently defaults to HTTP-only. In the future, this will probe for
229    /// WebSocket support by checking the agent card.
230    ///
231    /// # Arguments
232    ///
233    /// * `base_url` - Base URL of the A2A agent
234    ///
235    /// # Examples
236    ///
237    /// ```rust,no_run
238    /// use a2a_client::WebA2AClient;
239    ///
240    /// # #[tokio::main]
241    /// # async fn main() -> anyhow::Result<()> {
242    /// let client = WebA2AClient::auto_connect("http://localhost:8080").await?;
243    /// # Ok(())
244    /// # }
245    /// ```
246    pub async fn auto_connect(base_url: &str) -> anyhow::Result<Self> {
247        // For now, just use HTTP
248        // TODO: Try to detect WebSocket support by fetching agent card
249        Ok(Self::new_http(base_url.to_string()))
250    }
251
252    /// Check if WebSocket transport is available.
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use a2a_client::WebA2AClient;
258    ///
259    /// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
260    /// assert!(!client.has_websocket());
261    ///
262    /// let client = WebA2AClient::new_with_websocket(
263    ///     "http://localhost:8080".to_string(),
264    ///     "ws://localhost:8080/ws".to_string()
265    /// );
266    /// assert!(client.has_websocket());
267    /// ```
268    pub fn has_websocket(&self) -> bool {
269        self.ws.is_some()
270    }
271
272    /// Get a reference to the WebSocket client if available.
273    ///
274    /// # Examples
275    ///
276    /// ```rust
277    /// use a2a_client::WebA2AClient;
278    ///
279    /// let client = WebA2AClient::new_with_websocket(
280    ///     "http://localhost:8080".to_string(),
281    ///     "ws://localhost:8080/ws".to_string()
282    /// );
283    ///
284    /// if let Some(ws_client) = client.websocket() {
285    ///     // Use WebSocket client
286    /// }
287    /// ```
288    pub fn websocket(&self) -> Option<&Arc<WebSocketClient>> {
289        self.ws.as_ref()
290    }
291}
292
293/// Application state for Axum web applications.
294///
295/// This struct provides a convenient way to share the A2A client and
296/// configuration across Axum route handlers.
297///
298/// # Examples
299///
300/// ```rust
301/// use a2a_client::{WebA2AClient, AppState};
302/// use std::sync::Arc;
303///
304/// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
305/// let state = Arc::new(
306///     AppState::new(client)
307///         .with_webhook_token("secret-token".to_string())
308/// );
309/// ```
310pub struct AppState {
311    /// The A2A client for interacting with agents
312    pub client: WebA2AClient,
313    /// Optional webhook authentication token
314    pub webhook_token: Option<String>,
315}
316
317impl AppState {
318    /// Create new application state with the given client.
319    ///
320    /// # Examples
321    ///
322    /// ```rust
323    /// use a2a_client::{WebA2AClient, AppState};
324    ///
325    /// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
326    /// let state = AppState::new(client);
327    /// ```
328    pub fn new(client: WebA2AClient) -> Self {
329        Self {
330            client,
331            webhook_token: None,
332        }
333    }
334
335    /// Set the webhook authentication token.
336    ///
337    /// # Examples
338    ///
339    /// ```rust
340    /// use a2a_client::{WebA2AClient, AppState};
341    ///
342    /// let client = WebA2AClient::new_http("http://localhost:8080".to_string());
343    /// let state = AppState::new(client)
344    ///     .with_webhook_token("secret-token".to_string());
345    /// ```
346    pub fn with_webhook_token(mut self, token: String) -> Self {
347        self.webhook_token = Some(token);
348        self
349    }
350}
351
352/// Builder for [`WebA2AClient`].
353///
354/// Provides a fluent API for configuring the client with optional WebSocket support.
355///
356/// # Examples
357///
358/// ```rust
359/// use a2a_client::WebA2AClient;
360///
361/// // HTTP-only client
362/// let client = WebA2AClient::builder()
363///     .http_url("http://localhost:8080")
364///     .build();
365///
366/// // Client with WebSocket support
367/// let client = WebA2AClient::builder()
368///     .http_url("http://localhost:8080")
369///     .ws_url("ws://localhost:8080/ws")
370///     .build();
371/// ```
372#[derive(Default)]
373pub struct WebA2AClientBuilder {
374    http_url: Option<String>,
375    ws_url: Option<String>,
376}
377
378impl WebA2AClientBuilder {
379    /// Set the HTTP base URL.
380    ///
381    /// # Examples
382    ///
383    /// ```rust
384    /// use a2a_client::WebA2AClient;
385    ///
386    /// let client = WebA2AClient::builder()
387    ///     .http_url("http://localhost:8080")
388    ///     .build();
389    /// ```
390    pub fn http_url(mut self, url: impl Into<String>) -> Self {
391        self.http_url = Some(url.into());
392        self
393    }
394
395    /// Set the WebSocket URL.
396    ///
397    /// # Examples
398    ///
399    /// ```rust
400    /// use a2a_client::WebA2AClient;
401    ///
402    /// let client = WebA2AClient::builder()
403    ///     .http_url("http://localhost:8080")
404    ///     .ws_url("ws://localhost:8080/ws")
405    ///     .build();
406    /// ```
407    pub fn ws_url(mut self, url: impl Into<String>) -> Self {
408        self.ws_url = Some(url.into());
409        self
410    }
411
412    /// Build the [`WebA2AClient`].
413    ///
414    /// # Panics
415    ///
416    /// Panics if `http_url` was not set.
417    ///
418    /// # Examples
419    ///
420    /// ```rust
421    /// use a2a_client::WebA2AClient;
422    ///
423    /// let client = WebA2AClient::builder()
424    ///     .http_url("http://localhost:8080")
425    ///     .build();
426    /// ```
427    pub fn build(self) -> WebA2AClient {
428        let http_url = self
429            .http_url
430            .expect("http_url is required for WebA2AClient");
431
432        match self.ws_url {
433            Some(ws_url) => WebA2AClient::new_with_websocket(http_url, ws_url),
434            None => WebA2AClient::new_http(http_url),
435        }
436    }
437}