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}