1pub mod agent_card;
4pub mod auth;
5pub mod client;
6pub mod factory;
7pub mod jsonrpc;
8pub mod middleware;
9mod push_config_compat;
10pub mod rest;
11pub mod transport;
12
13pub use client::A2AClient;
14pub use factory::A2AClientFactory;
15pub use futures::stream::BoxStream;
16pub use transport::{ServiceParams, Transport, TransportFactory};
17
18pub fn default_reqwest_client(
35 extra_root_pem: Option<&[u8]>,
36) -> Result<reqwest::Client, a2a::A2AError> {
37 let builder = reqwest::Client::builder();
38
39 #[cfg(any(feature = "rustls-tls-aws-lc-rs", feature = "rustls-tls-ring"))]
40 let builder = builder.use_preconfigured_tls(rustls_client_config(extra_root_pem)?);
41
42 #[cfg(not(any(feature = "rustls-tls-aws-lc-rs", feature = "rustls-tls-ring")))]
43 let builder = match extra_root_pem {
44 Some(pem) => {
45 let cert = reqwest::Certificate::from_pem(pem)
46 .map_err(|e| a2a::A2AError::internal(format!("invalid PEM certificate: {e}")))?;
47 builder.add_root_certificate(cert)
48 }
49 None => builder,
50 };
51
52 builder
53 .build()
54 .map_err(|e| a2a::A2AError::internal(format!("failed to build HTTP client: {e}")))
55}
56
57#[cfg(any(feature = "rustls-tls-aws-lc-rs", feature = "rustls-tls-ring"))]
58fn rustls_client_config(
59 extra_root_pem: Option<&[u8]>,
60) -> Result<rustls::ClientConfig, a2a::A2AError> {
61 let provider = std::sync::Arc::new(selected_crypto_provider());
62 let mut roots = rustls::RootCertStore::empty();
63 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
64 if let Some(pem) = extra_root_pem {
65 for cert in rustls_pemfile::certs(&mut std::io::Cursor::new(pem)) {
66 let der =
67 cert.map_err(|e| a2a::A2AError::internal(format!("invalid PEM certificate: {e}")))?;
68 roots
69 .add(der)
70 .map_err(|e| a2a::A2AError::internal(format!("failed to add CA: {e}")))?;
71 }
72 }
73 Ok(rustls::ClientConfig::builder_with_provider(provider)
74 .with_safe_default_protocol_versions()
75 .expect("safe default protocol versions are supported")
76 .with_root_certificates(roots)
77 .with_no_client_auth())
78}
79
80#[cfg(feature = "rustls-tls-aws-lc-rs")]
81fn selected_crypto_provider() -> rustls::crypto::CryptoProvider {
82 rustls::crypto::aws_lc_rs::default_provider()
83}
84
85#[cfg(all(feature = "rustls-tls-ring", not(feature = "rustls-tls-aws-lc-rs")))]
86fn selected_crypto_provider() -> rustls::crypto::CryptoProvider {
87 rustls::crypto::ring::default_provider()
88}
89
90pub(crate) fn a2a_error_from_details(
91 code: i32,
92 message: String,
93 details: Vec<a2a::TypedDetail>,
94) -> a2a::A2AError {
95 use a2a::{error_code, errordetails, reason_to_error_code};
96 use serde_json::Value;
97
98 let mut code = code;
99 let mut message = message;
100
101 for detail in &details {
102 match detail.type_url.as_str() {
103 errordetails::BAD_REQUEST_TYPE => {
104 if let Some(Value::Array(violations)) = detail.value.get("fieldViolations") {
105 let violation_strs: Vec<String> = violations
106 .iter()
107 .filter_map(|v| {
108 let field = v.get("field")?.as_str()?;
109 let desc = v.get("description")?.as_str()?;
110 if field.is_empty() {
111 Some(desc.to_string())
112 } else {
113 Some(format!("{field}: {desc}"))
114 }
115 })
116 .collect();
117 if !violation_strs.is_empty() {
118 message = format!("{}: {}", message, violation_strs.join("; "));
119 }
120 }
121 if code == error_code::INTERNAL_ERROR {
122 code = error_code::INVALID_PARAMS;
123 }
124 }
125 errordetails::ERROR_INFO_TYPE => {
126 if let Some(Value::String(domain)) = detail.value.get("domain") {
127 if domain == errordetails::PROTOCOL_DOMAIN {
128 if let Some(Value::String(reason)) = detail.value.get("reason") {
129 if let Some(c) = reason_to_error_code(reason) {
130 code = c;
131 }
132 }
133 }
134 }
135 }
136 _ => {}
137 }
138 }
139
140 a2a::A2AError {
141 code,
142 message,
143 details: (!details.is_empty()).then_some(details),
144 }
145}
146
147#[cfg(test)]
148pub(crate) mod test_utils {
149 pub fn rcgen_self_signed_ca_pem() -> Vec<u8> {
150 let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
151 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
152 params
153 .distinguished_name
154 .push(rcgen::DnType::CommonName, "Test CA");
155 let key = rcgen::KeyPair::generate().unwrap();
156 let cert = params.self_signed(&key).unwrap();
157 cert.pem().into_bytes()
158 }
159}