Skip to main content

a2a_protocol_client/
client.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! Core [`A2aClient`] struct and constructors.
5//!
6//! [`A2aClient`] is the top-level entry point for all A2A protocol operations.
7//! Construct one via [`ClientBuilder`] or via the convenience
8//! [`A2aClient::from_card`] method.
9//!
10//! All A2A methods are provided by `impl` blocks in the `methods/` modules:
11//!
12//! | Module | Methods |
13//! |---|---|
14//! | [`crate::methods::send_message`] | `send_message`, `stream_message` |
15//! | [`crate::methods::tasks`] | `get_task`, `list_tasks`, `cancel_task`, `resubscribe` |
16//! | [`crate::methods::push_config`] | `set_push_config`, `get_push_config`, `list_push_configs`, `delete_push_config` |
17//! | [`crate::methods::extended_card`] | `get_authenticated_extended_card` |
18//!
19//! [`ClientBuilder`]: crate::ClientBuilder
20
21use a2a_protocol_types::AgentCard;
22
23use crate::builder::ClientBuilder;
24use crate::config::ClientConfig;
25use crate::error::ClientResult;
26use crate::interceptor::InterceptorChain;
27use crate::transport::Transport;
28
29// ── A2aClient ────────────────────────────────────────────────────────────────
30
31/// A client for communicating with A2A-compliant agents.
32///
33/// All A2A protocol methods are available as `async` methods. Create a client
34/// via [`ClientBuilder`] or the [`A2aClient::from_card`] shorthand.
35///
36/// # Example
37///
38/// ```rust,no_run
39/// use a2a_protocol_client::ClientBuilder;
40///
41/// # async fn example() -> Result<(), a2a_protocol_client::error::ClientError> {
42/// let client = ClientBuilder::new("http://localhost:8080").build()?;
43/// # Ok(())
44/// # }
45/// ```
46pub struct A2aClient {
47    /// The underlying transport (JSON-RPC or REST).
48    pub(crate) transport: Box<dyn Transport>,
49    /// Ordered interceptor chain applied to every request/response.
50    pub(crate) interceptors: InterceptorChain,
51    /// Client configuration.
52    pub(crate) config: ClientConfig,
53}
54
55impl A2aClient {
56    /// Creates a client from an [`AgentCard`] using the recommended defaults.
57    ///
58    /// Selects the transport based on the agent's preferred protocol.
59    ///
60    /// # Errors
61    ///
62    /// Returns [`crate::error::ClientError::InvalidEndpoint`] if the agent
63    /// card URL is malformed or the transport cannot be constructed.
64    pub fn from_card(card: &AgentCard) -> ClientResult<Self> {
65        ClientBuilder::from_card(card).build()
66    }
67
68    /// Returns a reference to the active client configuration.
69    #[must_use]
70    pub const fn config(&self) -> &ClientConfig {
71        &self.config
72    }
73
74    /// Creates a new [`A2aClient`] from its constituent parts.
75    ///
76    /// This is the low-level constructor used by [`ClientBuilder`]. Prefer
77    /// [`ClientBuilder`] unless you need precise control over each component.
78    #[must_use]
79    pub(crate) fn new(
80        transport: Box<dyn Transport>,
81        interceptors: InterceptorChain,
82        config: ClientConfig,
83    ) -> Self {
84        Self {
85            transport,
86            interceptors,
87            config,
88        }
89    }
90}
91
92impl std::fmt::Debug for A2aClient {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        f.debug_struct("A2aClient")
95            .field("interceptors", &self.interceptors)
96            .finish_non_exhaustive()
97    }
98}
99
100// ── Tests ─────────────────────────────────────────────────────────────────────
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::transport::JsonRpcTransport;
106
107    #[test]
108    fn client_new_stores_config() {
109        let transport = JsonRpcTransport::new("http://localhost:8080").expect("transport");
110        let client = A2aClient::new(
111            Box::new(transport),
112            InterceptorChain::new(),
113            ClientConfig::default_http(),
114        );
115        assert_eq!(
116            client.config().request_timeout,
117            std::time::Duration::from_secs(30)
118        );
119    }
120
121    #[test]
122    fn client_debug_impl() {
123        let transport = JsonRpcTransport::new("http://localhost:8080").expect("transport");
124        let client = A2aClient::new(
125            Box::new(transport),
126            InterceptorChain::new(),
127            ClientConfig::default_http(),
128        );
129        let dbg = format!("{client:?}");
130        assert!(dbg.contains("A2aClient"));
131    }
132}