use crate::wire::intel::ToolDef;
use serde_json::Value;
use std::collections::BTreeMap;
use std::sync::{Arc, RwLock};
pub type CodeToolFn = dyn Fn(&Value) -> Result<Value, String> + Send + Sync;
#[derive(Clone)]
pub struct CodeTool {
name: String,
description: String,
input_schema: Value,
handler: Arc<CodeToolFn>,
}
impl CodeTool {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
input_schema: Value,
handler: impl Fn(&Value) -> Result<Value, String> + Send + Sync + 'static,
) -> CodeTool {
CodeTool {
name: name.into(),
description: description.into(),
input_schema,
handler: Arc::new(handler),
}
}
fn def(&self) -> ToolDef {
ToolDef {
name: self.name.clone(),
description: self.description.clone(),
input_schema: self.input_schema.clone(),
}
}
}
fn registry() -> &'static RwLock<BTreeMap<String, CodeTool>> {
static REG: std::sync::OnceLock<RwLock<BTreeMap<String, CodeTool>>> =
std::sync::OnceLock::new();
REG.get_or_init(|| RwLock::new(BTreeMap::new()))
}
pub fn register(tool: CodeTool) -> Result<(), String> {
if tool.name.trim().is_empty() {
return Err("code tool name must be non-empty".into());
}
if crate::agentloop::action::SELF_CONTROL_TOOLS.contains(&tool.name.as_str()) {
return Err(format!(
"code tool {:?} collides with an agentd self/control primitive",
tool.name
));
}
let mut reg = registry().write().unwrap_or_else(|e| e.into_inner());
if reg.contains_key(&tool.name) {
return Err(format!("code tool {:?} is already registered", tool.name));
}
reg.insert(tool.name.clone(), tool);
Ok(())
}
pub fn unregister(name: &str) -> bool {
registry()
.write()
.unwrap_or_else(|e| e.into_inner())
.remove(name)
.is_some()
}
pub fn count() -> usize {
registry().read().unwrap_or_else(|e| e.into_inner()).len()
}
pub(crate) fn is_registered(name: &str) -> bool {
registry()
.read()
.unwrap_or_else(|e| e.into_inner())
.contains_key(name)
}
pub(crate) fn defs() -> Vec<ToolDef> {
registry()
.read()
.unwrap_or_else(|e| e.into_inner())
.values()
.map(CodeTool::def)
.collect()
}
pub(crate) fn dispatch(name: &str, args: &Value) -> Option<(String, bool)> {
let handler = {
let reg = registry().read().unwrap_or_else(|e| e.into_inner());
Arc::clone(®.get(name)?.handler)
};
Some(match handler(args) {
Ok(v) => (
match v {
Value::String(s) => s,
other => other.to_string(),
},
false,
),
Err(e) => (e, true),
})
}
pub fn call(name: &str, args: &Value) -> Option<Result<Value, String>> {
let handler = {
let reg = registry().read().unwrap_or_else(|e| e.into_inner());
Arc::clone(®.get(name)?.handler)
};
Some(handler(args))
}
#[cfg(test)]
pub(crate) fn test_registry_guard() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
#[cfg(test)]
mod tests {
use super::*;
fn call_for_workflow(name: &str, args: &Value) -> (Value, bool) {
match call(name, args) {
None => (Value::String(format!("no such code tool {name:?}")), true),
Some(Ok(v)) => (v, false),
Some(Err(e)) => (Value::String(e), true),
}
}
use serde_json::json;
#[test]
fn register_dispatch_and_unregister_round_trip() {
let _guard = super::test_registry_guard();
register(CodeTool::new(
"t.echo",
"echo",
json!({"type": "object"}),
|args| Ok(json!({ "got": args.clone() })),
))
.expect("fresh name registers");
assert!(is_registered("t.echo"));
assert!(count() >= 1);
assert_eq!(defs().iter().filter(|d| d.name == "t.echo").count(), 1);
let (content, is_err) = dispatch("t.echo", &json!({"x": 1})).expect("registered");
assert!(!is_err);
assert!(content.contains("\"x\":1"), "{content}");
let (v, e) = call_for_workflow("t.echo", &json!({"y": 2}));
assert!(!e);
assert_eq!(v["got"]["y"], json!(2));
assert!(unregister("t.echo"));
assert!(
dispatch("t.echo", &json!({})).is_none(),
"gone after unregister"
);
let (_, e) = call_for_workflow("t.echo", &json!({}));
assert!(
e,
"workflow call of an unregistered tool is an error result"
);
}
#[test]
fn registration_refuses_duplicates_empties_and_self_tool_names() {
let _guard = super::test_registry_guard();
register(CodeTool::new("t.dup", "", json!({}), |_| Ok(json!(1)))).unwrap();
assert!(register(CodeTool::new("t.dup", "", json!({}), |_| Ok(json!(2)))).is_err());
assert!(register(CodeTool::new(" ", "", json!({}), |_| Ok(json!(1)))).is_err());
assert!(
register(CodeTool::new("subagent.spawn", "", json!({}), |_| Ok(
json!(1)
)))
.is_err(),
"self/control primitives are unshadowable"
);
assert!(unregister("t.dup"));
}
#[test]
fn a_handler_error_is_a_tool_error_not_a_panic() {
let _guard = super::test_registry_guard();
register(CodeTool::new("t.fail", "", json!({}), |_| {
Err("deliberate".into())
}))
.unwrap();
let (content, is_err) = dispatch("t.fail", &json!({})).unwrap();
assert!(is_err);
assert_eq!(content, "deliberate");
let (v, e) = call_for_workflow("t.fail", &json!({}));
assert!(e);
assert_eq!(v, json!("deliberate"));
assert!(unregister("t.fail"));
}
}