hanzo_zap/executor/
mod.rs1use 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
47pub 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#[derive(Debug, Clone)]
61pub struct ExecutorContext {
62 pub cwd: Option<String>,
64 pub env: HashMap<String, String>,
66 pub session_id: Option<String>,
68 pub approval_policy: AskForApproval,
70 pub sandbox_policy: SandboxPolicy,
72 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 pub fn with_cwd(cwd: impl Into<String>) -> Self {
98 Self {
99 cwd: Some(cwd.into()),
100 ..Default::default()
101 }
102 }
103
104 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 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 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 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#[async_trait]
152pub trait ToolExecutor: Send + Sync {
153 async fn execute(&self, name: &str, args: Value, ctx: &ExecutorContext) -> Result<ToolResult>;
155
156 fn tools(&self) -> Vec<&'static str>;
158
159 fn category(&self) -> ToolCategory;
161}
162
163pub struct ToolDispatcher {
165 executors: HashMap<String, Arc<dyn ToolExecutor>>,
166 category_map: HashMap<ToolCategory, Vec<String>>,
167}
168
169impl ToolDispatcher {
170 pub fn new() -> Self {
172 Self {
173 executors: HashMap::new(),
174 category_map: HashMap::new(),
175 }
176 }
177
178 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 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 pub fn list_tools(&self) -> Vec<String> {
207 self.executors.keys().cloned().collect()
208 }
209
210 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
232pub fn default_dispatcher() -> ToolDispatcher {
234 let mut dispatcher = ToolDispatcher::new();
235
236 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}