use crate::executor::{ExecutionResult, NodeExecutor};
use async_trait::async_trait;
use jamjet_state::backend::WorkItem;
use serde_json::{json, Value};
use tracing::{debug, instrument};
pub struct AgentDiscoveryExecutor;
#[async_trait]
impl NodeExecutor for AgentDiscoveryExecutor {
#[instrument(skip(self, item), fields(node_id = %item.node_id))]
async fn execute(&self, item: &WorkItem) -> Result<ExecutionResult, String> {
let start = std::time::Instant::now();
let agent_url = item
.payload
.get("agent_url")
.and_then(|v| v.as_str())
.ok_or("AgentDiscovery: missing 'agent_url' in payload")?;
debug!(agent_url = %agent_url, "Discovering agent");
let resolved_base = if agent_url.starts_with("did:web:") {
let rest = agent_url.trim_start_matches("did:web:");
let parts: Vec<&str> = rest.splitn(2, ':').collect();
let host = parts[0];
let path = if parts.len() > 1 {
format!("/{}", parts[1].replace(':', "/"))
} else {
String::new()
};
format!("https://{host}{path}")
} else {
agent_url.trim_end_matches('/').to_string()
};
let card_url = format!("{resolved_base}/.well-known/agent.json");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| format!("HTTP client: {e}"))?;
let card: Value = client
.get(&card_url)
.send()
.await
.map_err(|e| format!("fetch Agent Card from {card_url}: {e}"))?
.json()
.await
.map_err(|e| format!("parse Agent Card: {e}"))?;
let duration_ms = start.elapsed().as_millis() as u64;
Ok(ExecutionResult {
output: card.clone(),
state_patch: json!({ "discovered_agent": card }),
duration_ms,
gen_ai_system: None,
gen_ai_model: None,
input_tokens: None,
output_tokens: None,
finish_reason: None,
})
}
}