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