//! Adapter types bridging runtime interfaces use std::pin::Pin; use std::sync::Arc; use async_trait::async_trait; use serde_json::{json, Value}; use zclaw_runtime::{LlmDriver, tool::{SkillExecutor, HandExecutor}}; use zclaw_skills::{SkillRegistry, LlmCompleter, SkillCompletion, SkillToolCall}; use zclaw_hands::HandRegistry; use zclaw_types::{AgentId, Result, ToolDefinition}; /// Adapter that bridges `zclaw_runtime::LlmDriver` -> `zclaw_skills::LlmCompleter` pub(crate) struct LlmDriverAdapter { pub(crate) driver: Arc, pub(crate) max_tokens: u32, pub(crate) temperature: f32, } impl LlmCompleter for LlmDriverAdapter { fn complete( &self, prompt: &str, ) -> Pin> + Send + '_>> { let driver = self.driver.clone(); let prompt = prompt.to_string(); Box::pin(async move { let request = zclaw_runtime::CompletionRequest { messages: vec![zclaw_types::Message::user(prompt)], max_tokens: Some(self.max_tokens), temperature: Some(self.temperature), ..Default::default() }; let response = driver.complete(request).await .map_err(|e| format!("LLM completion error: {}", e))?; // Extract text from content blocks let text: String = response.content.iter() .filter_map(|block| match block { zclaw_runtime::ContentBlock::Text { text } => Some(text.as_str()), _ => None, }) .collect::>() .join(""); Ok(text) }) } fn complete_with_tools( &self, prompt: &str, system_prompt: Option<&str>, tools: Vec, ) -> Pin> + Send + '_>> { let driver = self.driver.clone(); let prompt = prompt.to_string(); let system = system_prompt.map(|s| s.to_string()); let max_tokens = self.max_tokens; let temperature = self.temperature; Box::pin(async move { let mut messages = Vec::new(); messages.push(zclaw_types::Message::user(prompt)); let request = zclaw_runtime::CompletionRequest { model: String::new(), system, messages, tools, max_tokens: Some(max_tokens), temperature: Some(temperature), stop: Vec::new(), stream: false, thinking_enabled: false, reasoning_effort: None, plan_mode: false, }; let response = driver.complete(request).await .map_err(|e| format!("LLM completion error: {}", e))?; let mut text_parts = Vec::new(); let mut tool_calls = Vec::new(); for block in &response.content { match block { zclaw_runtime::ContentBlock::Text { text } => { text_parts.push(text.clone()); } zclaw_runtime::ContentBlock::ToolUse { id, name, input } => { tool_calls.push(SkillToolCall { id: id.clone(), name: name.clone(), input: input.clone(), }); } _ => {} } } Ok(SkillCompletion { text: text_parts.join(""), tool_calls, }) }) } } /// Skill executor implementation for Kernel pub struct KernelSkillExecutor { pub(crate) skills: Arc, pub(crate) llm: Arc, /// Shared tool registry, updated before each skill execution from the /// agent loop's freshly-built registry. Uses std::sync because reads /// happen from async code but writes are brief and infrequent. pub(crate) tool_registry: std::sync::RwLock>, } impl KernelSkillExecutor { pub fn new(skills: Arc, driver: Arc) -> Self { let llm: Arc = Arc::new(LlmDriverAdapter { driver, max_tokens: 4096, temperature: 0.7 }); Self { skills, llm, tool_registry: std::sync::RwLock::new(None) } } /// Update the tool registry snapshot. Called by the kernel before each /// agent loop iteration so skill execution sees the latest tool set. pub fn set_tool_registry(&self, registry: zclaw_runtime::ToolRegistry) { if let Ok(mut guard) = self.tool_registry.write() { *guard = Some(registry); } } /// Resolve the tool definitions declared by a skill manifest against /// the currently active tool registry. fn resolve_tool_definitions(&self, skill_id: &str) -> Vec { let manifests = self.skills.manifests_snapshot(); let manifest = match manifests.get(&zclaw_types::SkillId::new(skill_id)) { Some(m) => m, None => return vec![], }; if manifest.tools.is_empty() { return vec![]; } let guard = match self.tool_registry.read() { Ok(g) => g, Err(_) => return vec![], }; let registry = match guard.as_ref() { Some(r) => r, None => return vec![], }; // Only include definitions for tools declared in the skill manifest. registry.definitions().into_iter() .filter(|def| manifest.tools.iter().any(|t| t == &def.name)) .collect() } } #[async_trait] impl SkillExecutor for KernelSkillExecutor { async fn execute_skill( &self, skill_id: &str, agent_id: &str, session_id: &str, input: Value, ) -> Result { let tool_definitions = self.resolve_tool_definitions(skill_id); let context = zclaw_skills::SkillContext { agent_id: agent_id.to_string(), session_id: session_id.to_string(), llm: Some(self.llm.clone()), tool_definitions, ..Default::default() }; let result = self.skills.execute(&zclaw_types::SkillId::new(skill_id), &context, input).await?; Ok(result.output) } fn get_skill_detail(&self, skill_id: &str) -> Option { let manifests = self.skills.manifests_snapshot(); let manifest = manifests.get(&zclaw_types::SkillId::new(skill_id))?; Some(zclaw_runtime::tool::SkillDetail { id: manifest.id.as_str().to_string(), name: manifest.name.clone(), description: manifest.description.clone(), category: manifest.category.clone(), input_schema: manifest.input_schema.clone(), triggers: manifest.triggers.clone(), capabilities: manifest.capabilities.clone(), }) } fn list_skill_index(&self) -> Vec { let manifests = self.skills.manifests_snapshot(); manifests.values() .filter(|m| m.enabled) .map(|m| zclaw_runtime::tool::SkillIndexEntry { id: m.id.as_str().to_string(), description: m.description.clone(), triggers: m.triggers.clone(), }) .collect() } } /// Inbox wrapper for A2A message receivers that supports re-queuing /// non-matching messages instead of dropping them. pub(crate) struct AgentInbox { pub(crate) rx: tokio::sync::mpsc::Receiver, pub(crate) pending: std::collections::VecDeque, } impl AgentInbox { pub(crate) fn new(rx: tokio::sync::mpsc::Receiver) -> Self { Self { rx, pending: std::collections::VecDeque::new() } } pub(crate) fn try_recv(&mut self) -> std::result::Result { if let Some(msg) = self.pending.pop_front() { return Ok(msg); } self.rx.try_recv() } pub(crate) async fn recv(&mut self) -> Option { if let Some(msg) = self.pending.pop_front() { return Some(msg); } self.rx.recv().await } pub(crate) fn requeue(&mut self, envelope: zclaw_protocols::A2aEnvelope) { self.pending.push_back(envelope); } } /// Hand executor implementation for Kernel /// /// Bridges `zclaw_runtime::tool::HandExecutor` → `zclaw_hands::HandRegistry`, /// allowing `HandTool::execute()` to dispatch to the real Hand implementations. pub struct KernelHandExecutor { hands: Arc, } impl KernelHandExecutor { pub fn new(hands: Arc) -> Self { Self { hands } } } #[async_trait] impl HandExecutor for KernelHandExecutor { async fn execute_hand( &self, hand_id: &str, agent_id: &AgentId, input: Value, ) -> Result { let context = zclaw_hands::HandContext { agent_id: agent_id.clone(), working_dir: None, env: std::collections::HashMap::new(), timeout_secs: 300, callback_url: None, }; let result = self.hands.execute(hand_id, &context, input).await?; if result.success { Ok(result.output) } else { Ok(json!({ "hand_id": hand_id, "status": "failed", "error": result.error.unwrap_or_else(|| "Unknown hand execution error".to_string()), "output": result.output, "duration_ms": result.duration_ms, })) } } }