use anyhow::{anyhow, Result};
use reqwest::Client;
use crate::types::{AgentCard, TaskCreateRequest, TaskCreateResponse};
pub struct A2aClient {
base_url: String,
http: Client,
}
impl A2aClient {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
http: Client::new(),
}
}
pub fn from_card(card: &AgentCard) -> Self {
Self::new(card.url.clone())
}
pub async fn discover(url: &str) -> Result<AgentCard> {
let trimmed = url.trim_end_matches('/');
let full = format!("{}/.well-known/agent.json", trimmed);
let resp = Client::new().get(full).send().await?;
if !resp.status().is_success() {
return Err(anyhow!(
"failed to discover agent: HTTP {}",
resp.status()
));
}
let card = resp.json::<AgentCard>().await?;
Ok(card)
}
pub async fn send_task(&self, text: &str) -> Result<(String, String)> {
let req = TaskCreateRequest {
input: text.to_string(),
};
let url = format!("{}/tasks", self.base_url.trim_end_matches('/'));
let resp = self.http.post(url).json(&req).send().await?;
if !resp.status().is_success() {
return Err(anyhow!(
"failed to send task: HTTP {}",
resp.status()
));
}
let data: TaskCreateResponse = resp.json().await?;
Ok((data.id, data.output))
}
pub async fn subscribe_task(&self, task_id: &str) -> Result<Vec<String>> {
let url = format!(
"{}/tasks/{}/stream",
self.base_url.trim_end_matches('/'),
task_id
);
let resp = self.http.get(url).send().await?;
if !resp.status().is_success() {
return Err(anyhow!(
"failed to subscribe task stream: HTTP {}",
resp.status()
));
}
let body = resp.text().await?;
let mut chunks = Vec::new();
for line in body.lines() {
if let Some(rest) = line.strip_prefix("data:") {
chunks.push(rest.trim().to_string());
}
}
Ok(chunks)
}
}