a2a_protocol_client/methods/
extended_card.rs1use a2a_protocol_types::AuthenticatedExtendedCardResponse;
9
10use crate::client::A2aClient;
11use crate::error::{ClientError, ClientResult};
12use crate::interceptor::{ClientRequest, ClientResponse};
13
14impl A2aClient {
15 pub async fn get_extended_agent_card(&self) -> ClientResult<AuthenticatedExtendedCardResponse> {
29 const METHOD: &str = "GetExtendedAgentCard";
30
31 let params = self.tenant_or_default(None).map_or(
35 serde_json::Value::Null,
36 |tenant| serde_json::json!({ "tenant": tenant }),
37 );
38 let mut req = ClientRequest::new(METHOD, params);
39 self.interceptors.run_before(&mut req).await?;
40
41 let result = self
42 .transport
43 .send_request(METHOD, req.params, &req.extra_headers)
44 .await?;
45
46 let resp = ClientResponse {
47 method: METHOD.to_owned(),
48 result,
49 status_code: 200,
50 };
51 self.interceptors.run_after(&resp).await?;
52
53 serde_json::from_value::<AuthenticatedExtendedCardResponse>(resp.result)
54 .map_err(ClientError::Serialization)
55 }
56}
57
58#[cfg(test)]
61mod tests {
62 use std::collections::HashMap;
63 use std::future::Future;
64 use std::pin::Pin;
65
66 use crate::ClientBuilder;
67 use crate::error::{ClientError, ClientResult};
68 use crate::streaming::EventStream;
69 use crate::transport::Transport;
70
71 struct MockTransport {
72 response: serde_json::Value,
73 }
74
75 impl MockTransport {
76 fn new(response: serde_json::Value) -> Self {
77 Self { response }
78 }
79 }
80
81 impl Transport for MockTransport {
82 fn send_request<'a>(
83 &'a self,
84 _method: &'a str,
85 _params: serde_json::Value,
86 _extra_headers: &'a HashMap<String, String>,
87 ) -> Pin<Box<dyn Future<Output = ClientResult<serde_json::Value>> + Send + 'a>> {
88 let resp = self.response.clone();
89 Box::pin(async move { Ok(resp) })
90 }
91
92 fn send_streaming_request<'a>(
93 &'a self,
94 _method: &'a str,
95 _params: serde_json::Value,
96 _extra_headers: &'a HashMap<String, String>,
97 ) -> Pin<Box<dyn Future<Output = ClientResult<EventStream>> + Send + 'a>> {
98 Box::pin(async move { Err(ClientError::Transport("not supported".into())) })
99 }
100 }
101
102 struct ErrorTransport {
103 error_msg: String,
104 }
105
106 impl Transport for ErrorTransport {
107 fn send_request<'a>(
108 &'a self,
109 _method: &'a str,
110 _params: serde_json::Value,
111 _extra_headers: &'a HashMap<String, String>,
112 ) -> Pin<Box<dyn Future<Output = ClientResult<serde_json::Value>> + Send + 'a>> {
113 let msg = self.error_msg.clone();
114 Box::pin(async move { Err(ClientError::Transport(msg)) })
115 }
116
117 fn send_streaming_request<'a>(
118 &'a self,
119 _method: &'a str,
120 _params: serde_json::Value,
121 _extra_headers: &'a HashMap<String, String>,
122 ) -> Pin<Box<dyn Future<Output = ClientResult<EventStream>> + Send + 'a>> {
123 let msg = self.error_msg.clone();
124 Box::pin(async move { Err(ClientError::Transport(msg)) })
125 }
126 }
127
128 fn make_client(transport: impl Transport) -> crate::A2aClient {
129 ClientBuilder::new("http://localhost:8080")
130 .with_custom_transport(transport)
131 .build()
132 .expect("build client")
133 }
134
135 fn agent_card_json() -> serde_json::Value {
136 serde_json::json!({
137 "name": "test-agent",
138 "description": "A test agent",
139 "version": "1.0.0",
140 "supportedInterfaces": [{
141 "url": "http://localhost:8080",
142 "protocolBinding": "JSONRPC",
143 "protocolVersion": "1.0.0"
144 }],
145 "defaultInputModes": ["text/plain"],
146 "defaultOutputModes": ["text/plain"],
147 "skills": [{
148 "id": "echo",
149 "name": "Echo",
150 "description": "Echoes input",
151 "tags": ["test"]
152 }],
153 "capabilities": {}
154 })
155 }
156
157 #[tokio::test]
158 async fn get_extended_agent_card_success() {
159 let transport = MockTransport::new(agent_card_json());
160 let client = make_client(transport);
161
162 let card = client.get_extended_agent_card().await.unwrap();
163 assert_eq!(card.name, "test-agent");
164 assert_eq!(card.version, "1.0.0");
165 assert_eq!(card.skills.len(), 1);
166 assert_eq!(card.skills[0].id, "echo");
167 }
168
169 #[tokio::test]
170 async fn get_extended_agent_card_transport_error() {
171 let transport = ErrorTransport {
172 error_msg: "connection refused".into(),
173 };
174 let client = make_client(transport);
175
176 let err = client.get_extended_agent_card().await.unwrap_err();
177 assert!(
178 matches!(err, ClientError::Transport(ref msg) if msg.contains("connection refused")),
179 "expected Transport error, got {err:?}"
180 );
181 }
182
183 #[tokio::test]
185 async fn mock_transport_streaming_returns_not_supported() {
186 let transport = MockTransport::new(serde_json::json!({}));
187 let client = make_client(transport);
188
189 let err = client.subscribe_to_task("task-1").await.unwrap_err();
190 assert!(
191 matches!(err, ClientError::Transport(ref msg) if msg.contains("not supported")),
192 "expected Transport error from MockTransport streaming, got {err:?}"
193 );
194 }
195
196 #[tokio::test]
198 async fn error_transport_streaming_returns_error() {
199 let transport = ErrorTransport {
200 error_msg: "stream refused".into(),
201 };
202 let client = make_client(transport);
203
204 let err = client.subscribe_to_task("task-2").await.unwrap_err();
205 assert!(
206 matches!(err, ClientError::Transport(ref msg) if msg.contains("stream refused")),
207 "expected Transport error from ErrorTransport streaming, got {err:?}"
208 );
209 }
210}