//! Adapter types bridging runtime interfaces use std::pin::Pin; use std::sync::Arc; use async_trait::async_trait; use serde_json::Value; use zclaw_runtime::{LlmDriver, tool::SkillExecutor}; use zclaw_skills::{SkillRegistry, LlmCompleter}; use zclaw_types::Result; /// 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) }) } } /// Skill executor implementation for Kernel pub struct KernelSkillExecutor { pub(crate) skills: Arc, pub(crate) llm: Arc, } 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 } } } #[async_trait] impl SkillExecutor for KernelSkillExecutor { async fn execute_skill( &self, skill_id: &str, agent_id: &str, session_id: &str, input: Value, ) -> Result { let context = zclaw_skills::SkillContext { agent_id: agent_id.to_string(), session_id: session_id.to_string(), llm: Some(self.llm.clone()), ..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); } }