jamjet_worker/executors/
agent_discovery.rs1use crate::executor::{ExecutionResult, NodeExecutor};
8use async_trait::async_trait;
9use jamjet_state::backend::WorkItem;
10use serde_json::{json, Value};
11use tracing::{debug, instrument};
12
13pub struct AgentDiscoveryExecutor;
14
15#[async_trait]
16impl NodeExecutor for AgentDiscoveryExecutor {
17 #[instrument(skip(self, item), fields(node_id = %item.node_id))]
18 async fn execute(&self, item: &WorkItem) -> Result<ExecutionResult, String> {
19 let start = std::time::Instant::now();
20
21 let agent_url = item
22 .payload
23 .get("agent_url")
24 .and_then(|v| v.as_str())
25 .ok_or("AgentDiscovery: missing 'agent_url' in payload")?;
26
27 debug!(agent_url = %agent_url, "Discovering agent");
28
29 let resolved_base = if agent_url.starts_with("did:web:") {
31 let rest = agent_url.trim_start_matches("did:web:");
32 let parts: Vec<&str> = rest.splitn(2, ':').collect();
33 let host = parts[0];
34 let path = if parts.len() > 1 {
35 format!("/{}", parts[1].replace(':', "/"))
36 } else {
37 String::new()
38 };
39 format!("https://{host}{path}")
40 } else {
41 agent_url.trim_end_matches('/').to_string()
42 };
43
44 let card_url = format!("{resolved_base}/.well-known/agent.json");
45 let client = reqwest::Client::builder()
46 .timeout(std::time::Duration::from_secs(10))
47 .build()
48 .map_err(|e| format!("HTTP client: {e}"))?;
49
50 let card: Value = client
51 .get(&card_url)
52 .send()
53 .await
54 .map_err(|e| format!("fetch Agent Card from {card_url}: {e}"))?
55 .json()
56 .await
57 .map_err(|e| format!("parse Agent Card: {e}"))?;
58
59 let duration_ms = start.elapsed().as_millis() as u64;
60
61 Ok(ExecutionResult {
62 output: card.clone(),
63 state_patch: json!({ "discovered_agent": card }),
64 duration_ms,
65 gen_ai_system: None,
66 gen_ai_model: None,
67 input_tokens: None,
68 output_tokens: None,
69 finish_reason: None,
70 })
71 }
72}