gestura_core_a2a/
client.rs1use super::types::*;
6use gestura_core_foundation::error::AppError;
7use std::time::Duration;
8
9type Result<T> = std::result::Result<T, AppError>;
10
11pub struct A2AClient {
13 client: reqwest::Client,
14 auth_token: Option<String>,
15}
16
17impl Default for A2AClient {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl A2AClient {
24 pub fn new() -> Self {
26 let client = reqwest::Client::builder()
27 .timeout(Duration::from_secs(30))
28 .build()
29 .unwrap_or_default();
30
31 Self {
32 client,
33 auth_token: None,
34 }
35 }
36
37 pub fn with_auth(token: impl Into<String>) -> Self {
39 let mut client = Self::new();
40 client.auth_token = Some(token.into());
41 client
42 }
43
44 pub fn set_auth_token(&mut self, token: impl Into<String>) {
46 self.auth_token = Some(token.into());
47 }
48
49 async fn send_request(&self, url: &str, request: A2ARequest) -> Result<A2AResponse> {
51 let mut req_builder = self.client.post(url).json(&request);
52
53 if let Some(ref token) = self.auth_token {
54 req_builder = req_builder.header("Authorization", format!("Bearer {}", token));
55 }
56
57 let response = req_builder
58 .send()
59 .await
60 .map_err(|e| AppError::Io(std::io::Error::other(format!("A2A request failed: {e}"))))?;
61
62 if !response.status().is_success() {
63 let status = response.status();
64 let text = response.text().await.unwrap_or_default();
65 return Err(AppError::Io(std::io::Error::other(format!(
66 "A2A request failed with status {}: {}",
67 status, text
68 ))));
69 }
70
71 response.json::<A2AResponse>().await.map_err(|e| {
72 AppError::Io(std::io::Error::other(format!(
73 "Failed to parse A2A response: {e}"
74 )))
75 })
76 }
77
78 pub async fn discover(&self, url: &str) -> Result<AgentCard> {
80 let request = A2ARequest::new("agent/discover", serde_json::Value::Null);
81 let response = self.send_request(url, request).await?;
82
83 if let Some(error) = response.error {
84 return Err(AppError::Io(std::io::Error::other(format!(
85 "A2A error {}: {}",
86 error.code, error.message
87 ))));
88 }
89
90 let result = response
91 .result
92 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
93
94 serde_json::from_value(result).map_err(|e| {
95 AppError::Io(std::io::Error::other(format!(
96 "Failed to parse AgentCard: {e}"
97 )))
98 })
99 }
100
101 pub async fn create_task(&self, url: &str, message: &str) -> Result<A2ATask> {
103 let request = CreateTaskRequest::from_message(A2AMessage {
104 role: "user".to_string(),
105 parts: vec![MessagePart::Text {
106 text: message.to_string(),
107 }],
108 });
109
110 self.create_task_with_request(url, request).await
111 }
112
113 pub async fn create_task_with_request(
115 &self,
116 url: &str,
117 task: CreateTaskRequest,
118 ) -> Result<A2ATask> {
119 let params = serde_json::to_value(task).map_err(|e| {
120 AppError::Io(std::io::Error::other(format!(
121 "Failed to serialize A2A task request: {e}"
122 )))
123 })?;
124
125 let request = A2ARequest::new("task/create", params);
126 let response = self.send_request(url, request).await?;
127
128 if let Some(error) = response.error {
129 return Err(AppError::Io(std::io::Error::other(format!(
130 "A2A error {}: {}",
131 error.code, error.message
132 ))));
133 }
134
135 let result = response
136 .result
137 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
138
139 serde_json::from_value(result).map_err(|e| {
140 AppError::Io(std::io::Error::other(format!(
141 "Failed to parse A2ATask: {e}"
142 )))
143 })
144 }
145
146 pub async fn retry_task(&self, url: &str, task_id: &str) -> Result<A2ATask> {
148 let params = serde_json::json!({"taskId": task_id});
149 let request = A2ARequest::new("task/retry", params);
150 let response = self.send_request(url, request).await?;
151
152 if let Some(error) = response.error {
153 return Err(AppError::Io(std::io::Error::other(format!(
154 "A2A error {}: {}",
155 error.code, error.message
156 ))));
157 }
158
159 let result = response
160 .result
161 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
162
163 serde_json::from_value(result).map_err(|e| {
164 AppError::Io(std::io::Error::other(format!(
165 "Failed to parse A2ATask: {e}"
166 )))
167 })
168 }
169
170 pub async fn heartbeat_task(
172 &self,
173 url: &str,
174 heartbeat: TaskHeartbeatRequest,
175 ) -> Result<A2ATask> {
176 let params = serde_json::to_value(heartbeat).map_err(|e| {
177 AppError::Io(std::io::Error::other(format!(
178 "Failed to serialize A2A heartbeat request: {e}"
179 )))
180 })?;
181 let request = A2ARequest::new("task/heartbeat", params);
182 let response = self.send_request(url, request).await?;
183
184 if let Some(error) = response.error {
185 return Err(AppError::Io(std::io::Error::other(format!(
186 "A2A error {}: {}",
187 error.code, error.message
188 ))));
189 }
190
191 let result = response
192 .result
193 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
194
195 serde_json::from_value(result).map_err(|e| {
196 AppError::Io(std::io::Error::other(format!(
197 "Failed to parse A2ATask: {e}"
198 )))
199 })
200 }
201
202 pub async fn list_task_artifacts(
204 &self,
205 url: &str,
206 task_id: &str,
207 ) -> Result<Vec<ArtifactManifestEntry>> {
208 let request = A2ARequest::new("task/artifacts", serde_json::json!({ "taskId": task_id }));
209 let response = self.send_request(url, request).await?;
210 if let Some(error) = response.error {
211 return Err(AppError::Io(std::io::Error::other(format!(
212 "A2A error {}: {}",
213 error.code, error.message
214 ))));
215 }
216 let result = response
217 .result
218 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
219 serde_json::from_value(result).map_err(|e| {
220 AppError::Io(std::io::Error::other(format!(
221 "Failed to parse artifact manifest: {e}"
222 )))
223 })
224 }
225
226 pub async fn fetch_task_artifact(
228 &self,
229 url: &str,
230 task_id: &str,
231 artifact_name: &str,
232 ) -> Result<Artifact> {
233 let request = A2ARequest::new(
234 "task/artifact",
235 serde_json::json!({ "taskId": task_id, "artifactName": artifact_name }),
236 );
237 let response = self.send_request(url, request).await?;
238 if let Some(error) = response.error {
239 return Err(AppError::Io(std::io::Error::other(format!(
240 "A2A error {}: {}",
241 error.code, error.message
242 ))));
243 }
244 let result = response
245 .result
246 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
247 serde_json::from_value(result).map_err(|e| {
248 AppError::Io(std::io::Error::other(format!(
249 "Failed to parse artifact payload: {e}"
250 )))
251 })
252 }
253
254 pub async fn get_task_status(&self, url: &str, task_id: &str) -> Result<A2ATask> {
256 let params = serde_json::json!({"taskId": task_id});
257 let request = A2ARequest::new("task/status", params);
258 let response = self.send_request(url, request).await?;
259
260 if let Some(error) = response.error {
261 return Err(AppError::Io(std::io::Error::other(format!(
262 "A2A error {}: {}",
263 error.code, error.message
264 ))));
265 }
266
267 let result = response
268 .result
269 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
270
271 serde_json::from_value(result).map_err(|e| {
272 AppError::Io(std::io::Error::other(format!(
273 "Failed to parse A2ATask: {e}"
274 )))
275 })
276 }
277
278 pub async fn cancel_task(&self, url: &str, task_id: &str) -> Result<A2ATask> {
280 let params = serde_json::json!({"taskId": task_id});
281 let request = A2ARequest::new("task/cancel", params);
282 let response = self.send_request(url, request).await?;
283
284 if let Some(error) = response.error {
285 return Err(AppError::Io(std::io::Error::other(format!(
286 "A2A error {}: {}",
287 error.code, error.message
288 ))));
289 }
290
291 let result = response
292 .result
293 .ok_or_else(|| AppError::Io(std::io::Error::other("No result in A2A response")))?;
294
295 serde_json::from_value(result).map_err(|e| {
296 AppError::Io(std::io::Error::other(format!(
297 "Failed to parse A2ATask: {e}"
298 )))
299 })
300 }
301}