clawft_kernel/wasm_runner/
registry.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6#[cfg(feature = "wasm-sandbox")]
7use super::runner::WasmToolRunner;
8use super::types::*;
9#[cfg(feature = "wasm-sandbox")]
10use crate::governance::EffectVector;
11
12pub trait BuiltinTool: Send + Sync {
21 fn name(&self) -> &str;
23 fn spec(&self) -> &BuiltinToolSpec;
25 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError>;
27}
28
29pub struct ToolRegistry {
38 tools: HashMap<String, Arc<dyn BuiltinTool>>,
39 parent: Option<Arc<ToolRegistry>>,
41 require_signatures: bool,
43 trusted_keys: Vec<[u8; 32]>,
45 signatures: HashMap<String, ToolSignature>,
47}
48
49impl ToolRegistry {
50 pub fn new() -> Self {
52 Self {
53 tools: HashMap::new(),
54 parent: None,
55 require_signatures: false,
56 trusted_keys: Vec::new(),
57 signatures: HashMap::new(),
58 }
59 }
60
61 pub fn with_parent(parent: Arc<ToolRegistry>) -> Self {
63 Self {
64 tools: HashMap::new(),
65 parent: Some(parent),
66 require_signatures: false,
67 trusted_keys: Vec::new(),
68 signatures: HashMap::new(),
69 }
70 }
71
72 pub fn register(&mut self, tool: Arc<dyn BuiltinTool>) {
78 if self.require_signatures {
79 tracing::warn!(
80 tool = tool.name(),
81 "unsigned tool registration rejected (require_signatures=true)"
82 );
83 return;
84 }
85 self.tools.insert(tool.name().to_string(), tool);
86 }
87
88 pub fn try_register(&mut self, tool: Arc<dyn BuiltinTool>) -> Result<(), ToolError> {
93 if self.require_signatures {
94 return Err(ToolError::SignatureRequired(tool.name().to_string()));
95 }
96 self.tools.insert(tool.name().to_string(), tool);
97 Ok(())
98 }
99
100 pub fn register_signed(
105 &mut self,
106 tool: Arc<dyn BuiltinTool>,
107 signature: ToolSignature,
108 ) -> Result<(), ToolError> {
109 if !self.verify_tool_signature(&signature) {
111 return Err(ToolError::InvalidSignature(format!(
112 "no trusted key verified signature for tool '{}'",
113 signature.tool_name,
114 )));
115 }
116 let name = tool.name().to_string();
117 self.tools.insert(name.clone(), tool);
118 self.signatures.insert(name, signature);
119 Ok(())
120 }
121
122 pub fn verify_tool_signature(&self, signature: &ToolSignature) -> bool {
124 self.trusted_keys.iter().any(|key| signature.verify(key))
125 }
126
127 pub fn set_require_signatures(&mut self, require: bool) {
129 self.require_signatures = require;
130 }
131
132 pub fn requires_signatures(&self) -> bool {
134 self.require_signatures
135 }
136
137 pub fn add_trusted_key(&mut self, key: [u8; 32]) {
139 self.trusted_keys.push(key);
140 }
141
142 pub fn get_signature(&self, tool_name: &str) -> Option<&ToolSignature> {
144 self.signatures.get(tool_name)
145 }
146
147 pub fn get(&self, name: &str) -> Option<&Arc<dyn BuiltinTool>> {
149 self.tools.get(name).or_else(|| {
150 self.parent.as_ref().and_then(|p| p.get(name))
153 })
154 }
155
156 pub fn execute(
158 &self,
159 name: &str,
160 args: serde_json::Value,
161 ) -> Result<serde_json::Value, ToolError> {
162 let tool = self
163 .get(name)
164 .ok_or_else(|| ToolError::NotFound(name.to_string()))?;
165 tool.execute(args)
166 }
167
168 pub fn list(&self) -> Vec<String> {
170 let mut seen = std::collections::HashSet::new();
171 for name in self.tools.keys() {
173 seen.insert(name.clone());
174 }
175 if let Some(ref parent) = self.parent {
177 for name in parent.list() {
178 seen.insert(name);
179 }
180 }
181 let mut result: Vec<String> = seen.into_iter().collect();
182 result.sort();
183 result
184 }
185
186 pub fn len(&self) -> usize {
188 if self.parent.is_none() {
189 return self.tools.len();
190 }
191 self.list().len()
192 }
193
194 pub fn is_empty(&self) -> bool {
196 self.tools.is_empty() && self.parent.as_ref().is_none_or(|p| p.is_empty())
197 }
198
199 pub fn parent(&self) -> Option<&Arc<ToolRegistry>> {
201 self.parent.as_ref()
202 }
203
204 #[cfg(feature = "wasm-sandbox")]
214 pub fn register_wasm_tool(
215 &mut self,
216 name: &str,
217 description: &str,
218 wasm_bytes: Vec<u8>,
219 runner: Arc<WasmToolRunner>,
220 ) -> Result<(), WasmError> {
221 wasmtime::Module::new(runner.engine(), &wasm_bytes)
224 .map_err(|e| WasmError::InvalidModule(e.to_string()))?;
225
226 let adapter = WasmToolAdapter {
227 tool_name: name.to_owned(),
228 spec: BuiltinToolSpec {
229 name: name.to_owned(),
230 category: ToolCategory::User,
231 description: description.to_owned(),
232 parameters: serde_json::json!({
233 "type": "object",
234 "properties": {
235 "input": {"type": "object", "description": "JSON input passed to WASM stdin"}
236 }
237 }),
238 gate_action: format!("tool.wasm.{name}"),
239 effect: EffectVector {
240 risk: 0.5,
241 ..Default::default()
242 },
243 native: false,
244 },
245 wasm_bytes: Arc::new(wasm_bytes),
246 runner,
247 };
248 self.register(Arc::new(adapter));
249 Ok(())
250 }
251}
252
253#[cfg(feature = "wasm-sandbox")]
263struct WasmToolAdapter {
264 tool_name: String,
265 spec: BuiltinToolSpec,
266 wasm_bytes: Arc<Vec<u8>>,
267 runner: Arc<WasmToolRunner>,
268}
269
270#[cfg(feature = "wasm-sandbox")]
271impl BuiltinTool for WasmToolAdapter {
272 fn name(&self) -> &str {
273 &self.tool_name
274 }
275
276 fn spec(&self) -> &BuiltinToolSpec {
277 &self.spec
278 }
279
280 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
281 let input = args.get("input").cloned().unwrap_or(args.clone());
283
284 let runner = self.runner.clone();
285 let wasm_bytes = self.wasm_bytes.clone();
286 let name = self.tool_name.clone();
287
288 let result = std::thread::spawn(move || {
290 let rt = tokio::runtime::Builder::new_current_thread()
291 .enable_all()
292 .build()
293 .map_err(|e| ToolError::ExecutionFailed(format!("runtime: {e}")))?;
294 rt.block_on(runner.execute_bytes(&name, &wasm_bytes, input))
295 .map_err(ToolError::Wasm)
296 })
297 .join()
298 .map_err(|_| ToolError::ExecutionFailed("WASM execution thread panicked".into()))??;
299
300 Ok(serde_json::json!({
301 "stdout": result.stdout,
302 "stderr": result.stderr,
303 "exit_code": result.exit_code,
304 "fuel_consumed": result.fuel_consumed,
305 "execution_time_ms": result.execution_time.as_millis() as u64,
306 }))
307 }
308}
309
310#[cfg(feature = "wasm-sandbox")]
315unsafe impl Send for WasmToolAdapter {}
316#[cfg(feature = "wasm-sandbox")]
317unsafe impl Sync for WasmToolAdapter {}
318
319unsafe impl Send for ToolRegistry {}
323unsafe impl Sync for ToolRegistry {}
324
325impl Default for ToolRegistry {
326 fn default() -> Self {
327 Self::new()
328 }
329}