//! Tool system for agent capabilities use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; use serde_json::Value; use tokio::sync::mpsc; use zclaw_types::{AgentId, Result}; use crate::driver::ToolDefinition; use crate::loop_runner::LoopEvent; use crate::tool::builtin::PathValidator; /// Tool trait for implementing agent tools #[async_trait] pub trait Tool: Send + Sync { /// Get the tool name fn name(&self) -> &str; /// Get the tool description fn description(&self) -> &str; /// Get the JSON schema for input parameters fn input_schema(&self) -> Value; /// Execute the tool async fn execute(&self, input: Value, context: &ToolContext) -> Result; } /// Skill executor trait for runtime skill execution /// This allows tools to execute skills without direct dependency on zclaw-skills #[async_trait] pub trait SkillExecutor: Send + Sync { /// Execute a skill by ID async fn execute_skill( &self, skill_id: &str, agent_id: &str, session_id: &str, input: Value, ) -> Result; /// Return metadata for on-demand skill loading. /// Default returns `None` (skill detail not available). fn get_skill_detail(&self, skill_id: &str) -> Option { let _ = skill_id; None } /// Return lightweight index of all available skills. /// Default returns empty (no index available). fn list_skill_index(&self) -> Vec { Vec::new() } } /// Lightweight skill index entry for system prompt injection. #[derive(Debug, Clone, serde::Serialize)] pub struct SkillIndexEntry { pub id: String, pub description: String, pub triggers: Vec, } /// Full skill detail returned by `skill_load` tool. #[derive(Debug, Clone, serde::Serialize)] pub struct SkillDetail { pub id: String, pub name: String, pub description: String, pub category: Option, pub input_schema: Option, pub triggers: Vec, pub capabilities: Vec, } /// Context provided to tool execution pub struct ToolContext { pub agent_id: AgentId, pub working_directory: Option, pub session_id: Option, pub skill_executor: Option>, /// Path validator for file system operations pub path_validator: Option, /// Optional event sender for streaming tool progress to the frontend. /// Tools like TaskTool use this to emit sub-agent status events. pub event_sender: Option>, } impl std::fmt::Debug for ToolContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ToolContext") .field("agent_id", &self.agent_id) .field("working_directory", &self.working_directory) .field("session_id", &self.session_id) .field("skill_executor", &self.skill_executor.as_ref().map(|_| "SkillExecutor")) .field("path_validator", &self.path_validator.as_ref().map(|_| "PathValidator")) .field("event_sender", &self.event_sender.as_ref().map(|_| "Sender")) .finish() } } impl Clone for ToolContext { fn clone(&self) -> Self { Self { agent_id: self.agent_id.clone(), working_directory: self.working_directory.clone(), session_id: self.session_id.clone(), skill_executor: self.skill_executor.clone(), path_validator: self.path_validator.clone(), event_sender: self.event_sender.clone(), } } } /// Tool registry for managing available tools /// Uses HashMap for O(1) lookup performance #[derive(Clone)] pub struct ToolRegistry { /// Tool lookup by name (O(1)) tools: HashMap>, /// Registration order for consistent iteration tool_order: Vec, } impl ToolRegistry { pub fn new() -> Self { Self { tools: HashMap::new(), tool_order: Vec::new(), } } pub fn register(&mut self, tool: Box) { let tool: Arc = Arc::from(tool); let name = tool.name().to_string(); // Track order for new tools if !self.tools.contains_key(&name) { self.tool_order.push(name.clone()); } self.tools.insert(name, tool); } /// Get tool by name - O(1) lookup pub fn get(&self, name: &str) -> Option> { self.tools.get(name).cloned() } /// List all tools in registration order pub fn list(&self) -> Vec<&dyn Tool> { self.tool_order .iter() .filter_map(|name| self.tools.get(name).map(|t| t.as_ref())) .collect() } /// Get tool definitions in registration order pub fn definitions(&self) -> Vec { self.tool_order .iter() .filter_map(|name| { self.tools.get(name).map(|t| { ToolDefinition::new( t.name(), t.description(), t.input_schema(), ) }) }) .collect() } /// Get number of registered tools pub fn len(&self) -> usize { self.tools.len() } /// Check if registry is empty pub fn is_empty(&self) -> bool { self.tools.is_empty() } } impl Default for ToolRegistry { fn default() -> Self { Self::new() } } // Built-in tools module pub mod builtin;