use serde_json::{json, Value};
use tokio::sync::OnceCell;
use crate::agent::{AgentInfo, AgentManifest};
use crate::error::{Error, Result};
use crate::identity::normalize_key;
#[derive(Debug)]
pub struct RemoteAgent {
http: reqwest::Client,
base_url: String,
pinned_key: Option<String>,
trusted: OnceCell<()>,
}
impl RemoteAgent {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
http: reqwest::Client::new(),
base_url: base_url.into().trim_end_matches('/').to_string(),
pinned_key: None,
trusted: OnceCell::new(),
}
}
#[must_use]
pub fn with_pinned_key(mut self, key_or_did: impl Into<String>) -> Self {
self.pinned_key = Some(key_or_did.into());
self
}
pub fn base_url(&self) -> &str {
&self.base_url
}
async fn parse_response<T: serde::de::DeserializeOwned>(
response: reqwest::Response,
) -> Result<T> {
let status = response.status();
if !status.is_success() {
let body: Value = response.json().await.unwrap_or(Value::Null);
let detail = body
.get("error")
.and_then(Value::as_str)
.map(String::from)
.unwrap_or_else(|| body.to_string());
return Err(Error::A2a(format!("peer returned {status}: {detail}")));
}
Ok(response.json().await?)
}
pub async fn info(&self) -> Result<AgentInfo> {
let response = self
.http
.get(format!("{}/agent", self.base_url))
.send()
.await?;
Self::parse_response(response).await
}
pub async fn manifest(&self) -> Result<AgentManifest> {
let response = self
.http
.get(format!("{}/agent/manifest", self.base_url))
.send()
.await?;
Self::parse_response(response).await
}
pub async fn verify(&self) -> Result<AgentManifest> {
let manifest = self.manifest().await?;
manifest.verify()?;
if let Some(pinned) = &self.pinned_key {
let expected = normalize_key(pinned)?;
if manifest.public_key.as_deref() != Some(expected.as_str()) {
return Err(Error::A2a(format!(
"peer at {} presented a different identity than the pinned key",
self.base_url
)));
}
}
Ok(manifest)
}
async fn ensure_trusted(&self) -> Result<()> {
if self.pinned_key.is_none() {
return Ok(());
}
self.trusted
.get_or_try_init(|| async {
self.verify().await?;
Ok::<(), Error>(())
})
.await?;
Ok(())
}
pub async fn chat(&self, session_id: &str, message: &str) -> Result<String> {
self.ensure_trusted().await?;
let response = self
.http
.post(format!("{}/chat", self.base_url))
.json(&json!({ "session_id": session_id, "message": message }))
.send()
.await?;
let body: Value = Self::parse_response(response).await?;
body.get("reply")
.and_then(Value::as_str)
.map(String::from)
.ok_or_else(|| Error::A2a("peer chat response had no 'reply'".into()))
}
pub async fn execute_skill(&self, name: &str, input: Value) -> Result<Value> {
self.ensure_trusted().await?;
let response = self
.http
.post(format!("{}/skills/{name}", self.base_url))
.json(&input)
.send()
.await?;
Self::parse_response(response).await
}
}