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}