Skip to main content

hanzo_zap/executor/
mod.rs

1//! Tool executor trait and dispatch logic.
2//!
3//! This module provides the infrastructure to execute tools with typed interfaces.
4//! Each tool category has an executor that implements the actual functionality.
5//!
6//! ## Cross-Language Support
7//!
8//! ZAP provides a language-agnostic protocol for tool execution. Agents can be
9//! implemented in any supported language while tools maintain consistent behavior:
10//!
11//! - **Rust**: Native executors in this module
12//! - **Python**: hanzo-tools-* packages via MCP bridge
13//! - **Node.js**: @hanzo/tools via MCP bridge
14//! - **Go**: hanzo-go-tools via MCP bridge
15//! - **C/C++**: libzap FFI bindings
16//! - **Ruby**: hanzo-ruby-tools gem
17//! - **Elixir**: hanzo_tools hex package
18//!
19//! All implementations share the same tool schemas defined in `crate::tools`.
20
21use crate::error::Error;
22use crate::error::Result;
23use crate::message::ToolResult;
24use crate::tools::ToolCategory;
25use crate::tools::ToolDef;
26use async_trait::async_trait;
27use serde_json::Value;
28use std::collections::HashMap;
29use std::path::PathBuf;
30use std::sync::Arc;
31
32mod build;
33mod computer;
34mod filesystem;
35mod network;
36pub mod permissions;
37mod plan;
38mod vcs;
39
40pub use build::BuildExecutor;
41pub use computer::ComputerExecutor;
42pub use filesystem::FilesystemExecutor;
43pub use network::NetworkExecutor;
44pub use plan::PlanExecutor;
45pub use vcs::VcsExecutor;
46
47// Re-export permission types from hanzo-protocol
48pub use permissions::AskForApproval;
49pub use permissions::PermissionLevel;
50pub use permissions::PermissionResult;
51pub use permissions::SandboxPolicy;
52pub use permissions::check_approval;
53pub use permissions::is_path_writable;
54pub use permissions::operation_level;
55
56/// Context passed to tool executors.
57///
58/// Contains all state needed for permission checks and execution.
59/// Bridges with hanzo-protocol's `AskForApproval` and `SandboxPolicy`.
60#[derive(Debug, Clone)]
61pub struct ExecutorContext {
62    /// Working directory for filesystem operations
63    pub cwd: Option<String>,
64    /// Environment variables
65    pub env: HashMap<String, String>,
66    /// User/session ID for audit
67    pub session_id: Option<String>,
68    /// When to ask for approval (from hanzo-protocol)
69    pub approval_policy: AskForApproval,
70    /// How to sandbox execution (from hanzo-protocol)
71    pub sandbox_policy: SandboxPolicy,
72    /// Timeout in milliseconds
73    pub timeout_ms: Option<u64>,
74}
75
76impl Default for ExecutorContext {
77    fn default() -> Self {
78        Self {
79            cwd: None,
80            env: HashMap::new(),
81            session_id: None,
82            approval_policy: AskForApproval::OnRequest,
83            sandbox_policy: SandboxPolicy::WorkspaceWrite {
84                writable_roots: vec![],
85                network_access: false,
86                exclude_tmpdir_env_var: false,
87                exclude_slash_tmp: false,
88                allow_git_writes: true,
89            },
90            timeout_ms: None,
91        }
92    }
93}
94
95impl ExecutorContext {
96    /// Create a new context with specified working directory
97    pub fn with_cwd(cwd: impl Into<String>) -> Self {
98        Self {
99            cwd: Some(cwd.into()),
100            ..Default::default()
101        }
102    }
103
104    /// Check if an operation should be approved
105    pub fn check_approval(&self, operation: &str) -> PermissionResult {
106        let level = operation_level(operation);
107        check_approval(
108            operation,
109            level,
110            &self.approval_policy,
111            &self.sandbox_policy,
112        )
113    }
114
115    /// Check if a path is writable under current sandbox policy
116    pub fn is_path_writable(&self, path: &std::path::Path) -> bool {
117        let cwd = self
118            .cwd
119            .as_ref()
120            .map(PathBuf::from)
121            .unwrap_or_else(|| PathBuf::from("."));
122        is_path_writable(path, &self.sandbox_policy, &cwd)
123    }
124
125    /// Create a permissive context (for trusted environments)
126    pub fn permissive() -> Self {
127        Self {
128            cwd: None,
129            env: HashMap::new(),
130            session_id: None,
131            approval_policy: AskForApproval::Never,
132            sandbox_policy: SandboxPolicy::DangerFullAccess,
133            timeout_ms: None,
134        }
135    }
136
137    /// Create a read-only context (for sandboxed agents)
138    pub fn read_only() -> Self {
139        Self {
140            cwd: None,
141            env: HashMap::new(),
142            session_id: None,
143            approval_policy: AskForApproval::UnlessTrusted,
144            sandbox_policy: SandboxPolicy::ReadOnly,
145            timeout_ms: None,
146        }
147    }
148}
149
150/// Trait for tool executors
151#[async_trait]
152pub trait ToolExecutor: Send + Sync {
153    /// Execute a tool by name with JSON arguments
154    async fn execute(&self, name: &str, args: Value, ctx: &ExecutorContext) -> Result<ToolResult>;
155
156    /// List tools provided by this executor
157    fn tools(&self) -> Vec<&'static str>;
158
159    /// Get the category this executor handles
160    fn category(&self) -> ToolCategory;
161}
162
163/// Tool dispatcher that routes calls to appropriate executors
164pub struct ToolDispatcher {
165    executors: HashMap<String, Arc<dyn ToolExecutor>>,
166    category_map: HashMap<ToolCategory, Vec<String>>,
167}
168
169impl ToolDispatcher {
170    /// Create a new dispatcher
171    pub fn new() -> Self {
172        Self {
173            executors: HashMap::new(),
174            category_map: HashMap::new(),
175        }
176    }
177
178    /// Register an executor
179    pub fn register(&mut self, executor: Arc<dyn ToolExecutor>) {
180        let category = executor.category();
181        for tool in executor.tools() {
182            self.executors.insert(tool.to_string(), executor.clone());
183            self.category_map
184                .entry(category.clone())
185                .or_default()
186                .push(tool.to_string());
187        }
188    }
189
190    /// Execute a tool by name
191    pub async fn execute(
192        &self,
193        name: &str,
194        args: Value,
195        ctx: &ExecutorContext,
196    ) -> Result<ToolResult> {
197        let executor = self
198            .executors
199            .get(name)
200            .ok_or_else(|| Error::ToolNotFound(name.to_string()))?;
201
202        executor.execute(name, args, ctx).await
203    }
204
205    /// List all available tools
206    pub fn list_tools(&self) -> Vec<String> {
207        self.executors.keys().cloned().collect()
208    }
209
210    /// List tools by category
211    pub fn tools_by_category(&self, category: &ToolCategory) -> Vec<String> {
212        self.category_map.get(category).cloned().unwrap_or_default()
213    }
214
215    pub fn all_tool_schemas(&self) -> Vec<ToolDef> {
216        let all_defs = crate::tools::default_tools();
217        let registered: std::collections::HashSet<&str> =
218            self.executors.keys().map(String::as_str).collect();
219        all_defs
220            .into_iter()
221            .filter(|def| registered.contains(def.name.as_str()))
222            .collect()
223    }
224}
225
226impl Default for ToolDispatcher {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232/// Create a dispatcher with all default executors registered
233pub fn default_dispatcher() -> ToolDispatcher {
234    let mut dispatcher = ToolDispatcher::new();
235
236    // Register all executors
237    dispatcher.register(Arc::new(FilesystemExecutor::new()));
238    dispatcher.register(Arc::new(ComputerExecutor::new()));
239    dispatcher.register(Arc::new(VcsExecutor::new()));
240    dispatcher.register(Arc::new(BuildExecutor::new()));
241    dispatcher.register(Arc::new(NetworkExecutor::new()));
242    dispatcher.register(Arc::new(PlanExecutor::new()));
243
244    dispatcher
245}