use std::collections::HashMap;
use std::sync::Arc;
use super::types::*;
#[cfg(feature = "wasm-sandbox")]
use super::runner::WasmToolRunner;
#[cfg(feature = "wasm-sandbox")]
use crate::governance::EffectVector;
pub trait BuiltinTool: Send + Sync {
fn name(&self) -> &str;
fn spec(&self) -> &BuiltinToolSpec;
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError>;
}
pub struct ToolRegistry {
tools: HashMap<String, Arc<dyn BuiltinTool>>,
parent: Option<Arc<ToolRegistry>>,
require_signatures: bool,
trusted_keys: Vec<[u8; 32]>,
signatures: HashMap<String, ToolSignature>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
parent: None,
require_signatures: false,
trusted_keys: Vec::new(),
signatures: HashMap::new(),
}
}
pub fn with_parent(parent: Arc<ToolRegistry>) -> Self {
Self {
tools: HashMap::new(),
parent: Some(parent),
require_signatures: false,
trusted_keys: Vec::new(),
signatures: HashMap::new(),
}
}
pub fn register(&mut self, tool: Arc<dyn BuiltinTool>) {
if self.require_signatures {
tracing::warn!(
tool = tool.name(),
"unsigned tool registration rejected (require_signatures=true)"
);
return;
}
self.tools.insert(tool.name().to_string(), tool);
}
pub fn try_register(&mut self, tool: Arc<dyn BuiltinTool>) -> Result<(), ToolError> {
if self.require_signatures {
return Err(ToolError::SignatureRequired(tool.name().to_string()));
}
self.tools.insert(tool.name().to_string(), tool);
Ok(())
}
pub fn register_signed(
&mut self,
tool: Arc<dyn BuiltinTool>,
signature: ToolSignature,
) -> Result<(), ToolError> {
if !self.verify_tool_signature(&signature) {
return Err(ToolError::InvalidSignature(format!(
"no trusted key verified signature for tool '{}'",
signature.tool_name,
)));
}
let name = tool.name().to_string();
self.tools.insert(name.clone(), tool);
self.signatures.insert(name, signature);
Ok(())
}
pub fn verify_tool_signature(&self, signature: &ToolSignature) -> bool {
self.trusted_keys.iter().any(|key| signature.verify(key))
}
pub fn set_require_signatures(&mut self, require: bool) {
self.require_signatures = require;
}
pub fn requires_signatures(&self) -> bool {
self.require_signatures
}
pub fn add_trusted_key(&mut self, key: [u8; 32]) {
self.trusted_keys.push(key);
}
pub fn get_signature(&self, tool_name: &str) -> Option<&ToolSignature> {
self.signatures.get(tool_name)
}
pub fn get(&self, name: &str) -> Option<&Arc<dyn BuiltinTool>> {
self.tools.get(name).or_else(|| {
self.parent.as_ref().and_then(|p| p.get(name))
})
}
pub fn execute(
&self,
name: &str,
args: serde_json::Value,
) -> Result<serde_json::Value, ToolError> {
let tool = self
.get(name)
.ok_or_else(|| ToolError::NotFound(name.to_string()))?;
tool.execute(args)
}
pub fn list(&self) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
for name in self.tools.keys() {
seen.insert(name.clone());
}
if let Some(ref parent) = self.parent {
for name in parent.list() {
seen.insert(name);
}
}
let mut result: Vec<String> = seen.into_iter().collect();
result.sort();
result
}
pub fn len(&self) -> usize {
if self.parent.is_none() {
return self.tools.len();
}
self.list().len()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty() && self.parent.as_ref().is_none_or(|p| p.is_empty())
}
pub fn parent(&self) -> Option<&Arc<ToolRegistry>> {
self.parent.as_ref()
}
#[cfg(feature = "wasm-sandbox")]
pub fn register_wasm_tool(
&mut self,
name: &str,
description: &str,
wasm_bytes: Vec<u8>,
runner: Arc<WasmToolRunner>,
) -> Result<(), WasmError> {
wasmtime::Module::new(runner.engine(), &wasm_bytes)
.map_err(|e| WasmError::InvalidModule(e.to_string()))?;
let adapter = WasmToolAdapter {
tool_name: name.to_owned(),
spec: BuiltinToolSpec {
name: name.to_owned(),
category: ToolCategory::User,
description: description.to_owned(),
parameters: serde_json::json!({
"type": "object",
"properties": {
"input": {"type": "object", "description": "JSON input passed to WASM stdin"}
}
}),
gate_action: format!("tool.wasm.{name}"),
effect: EffectVector {
risk: 0.5,
..Default::default()
},
native: false,
},
wasm_bytes: Arc::new(wasm_bytes),
runner,
};
self.register(Arc::new(adapter));
Ok(())
}
}
#[cfg(feature = "wasm-sandbox")]
struct WasmToolAdapter {
tool_name: String,
spec: BuiltinToolSpec,
wasm_bytes: Arc<Vec<u8>>,
runner: Arc<WasmToolRunner>,
}
#[cfg(feature = "wasm-sandbox")]
impl BuiltinTool for WasmToolAdapter {
fn name(&self) -> &str {
&self.tool_name
}
fn spec(&self) -> &BuiltinToolSpec {
&self.spec
}
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
let input = args
.get("input")
.cloned()
.unwrap_or(args.clone());
let runner = self.runner.clone();
let wasm_bytes = self.wasm_bytes.clone();
let name = self.tool_name.clone();
let result = std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| ToolError::ExecutionFailed(format!("runtime: {e}")))?;
rt.block_on(runner.execute_bytes(&name, &wasm_bytes, input))
.map_err(ToolError::Wasm)
})
.join()
.map_err(|_| ToolError::ExecutionFailed("WASM execution thread panicked".into()))??;
Ok(serde_json::json!({
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.exit_code,
"fuel_consumed": result.fuel_consumed,
"execution_time_ms": result.execution_time.as_millis() as u64,
}))
}
}
#[cfg(feature = "wasm-sandbox")]
unsafe impl Send for WasmToolAdapter {}
#[cfg(feature = "wasm-sandbox")]
unsafe impl Sync for WasmToolAdapter {}
unsafe impl Send for ToolRegistry {}
unsafe impl Sync for ToolRegistry {}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}