use crate::tools::{Tool, ToolExecutor};
use std::collections::HashMap;
pub struct ToolRegistry {
factories: HashMap<String, Box<dyn ToolFactory>>,
}
pub trait ToolFactory: Send + Sync {
fn create(&self) -> Box<dyn Tool>;
fn tool_name(&self) -> &str;
fn tool_description(&self) -> &str;
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
factories: HashMap::new(),
}
}
pub fn register_factory(&mut self, factory: Box<dyn ToolFactory>) {
self.factories.insert(factory.tool_name().to_string(), factory);
}
pub fn create_tool(&self, name: &str) -> Option<Box<dyn Tool>> {
self.factories.get(name).map(|factory| factory.create())
}
pub fn list_tools(&self) -> Vec<&str> {
self.factories.keys().map(|s| s.as_str()).collect()
}
pub fn get_tool_info(&self, name: &str) -> Option<(&str, &str)> {
self.factories.get(name).map(|factory| {
(factory.tool_name(), factory.tool_description())
})
}
pub fn create_executor(&self, tool_names: &[String]) -> ToolExecutor {
let mut executor = ToolExecutor::new();
for name in tool_names {
if let Some(tool) = self.create_tool(name) {
executor.register_tool(tool);
}
}
executor
}
pub fn create_executor_with_all(&self) -> ToolExecutor {
let mut executor = ToolExecutor::new();
for factory in self.factories.values() {
executor.register_tool(factory.create());
}
executor
}
}
impl Default for ToolRegistry {
fn default() -> Self {
let mut registry = Self::new();
registry.register_factory(Box::new(crate::tools::builtin::BashToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::EditToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::ThinkingToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::TaskDoneToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::JsonEditToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::CkgToolFactory));
registry.register_factory(Box::new(crate::tools::builtin::McpToolFactory));
registry
}
}
#[macro_export]
macro_rules! impl_tool_factory {
($factory:ident, $tool:ident, $name:expr, $description:expr) => {
pub struct $factory;
impl $crate::tools::ToolFactory for $factory {
fn create(&self) -> Box<dyn $crate::tools::Tool> {
Box::new($tool::new())
}
fn tool_name(&self) -> &str {
$name
}
fn tool_description(&self) -> &str {
$description
}
}
};
}
#[cfg(test)]
mod tests {
use crate::tools::registry::ToolRegistry;
#[test]
fn test_default_registry_has_all_tools() {
let registry = ToolRegistry::default();
let tools = registry.list_tools();
let expected_tools = vec![
"bash",
"str_replace_based_edit_tool",
"sequentialthinking",
"task_done",
"json_edit_tool",
"ckg_tool",
"mcp_tool",
];
println!("Available tools: {:?}", tools);
for expected_tool in &expected_tools {
assert!(
tools.contains(expected_tool),
"Tool '{}' is not registered in the default registry",
expected_tool
);
}
assert_eq!(
tools.len(),
expected_tools.len(),
"Expected {} tools, but found {}. Tools: {:?}",
expected_tools.len(),
tools.len(),
tools
);
}
#[test]
fn test_tool_creation() {
let registry = ToolRegistry::default();
let tools_to_test = vec![
"bash",
"str_replace_based_edit_tool",
"sequentialthinking",
"task_done",
"json_edit_tool",
"ckg_tool",
"mcp_tool",
];
for tool_name in tools_to_test {
let tool = registry.create_tool(tool_name);
assert!(
tool.is_some(),
"Failed to create tool '{}'",
tool_name
);
let tool = tool.unwrap();
assert_eq!(
tool.name(),
tool_name,
"Tool name mismatch for '{}'",
tool_name
);
assert!(
!tool.description().is_empty(),
"Tool '{}' has empty description",
tool_name
);
let schema = tool.parameters_schema();
assert!(
schema.is_object(),
"Tool '{}' parameters schema is not an object",
tool_name
);
}
}
#[test]
fn test_tool_info() {
let registry = ToolRegistry::default();
for tool_name in registry.list_tools() {
let info = registry.get_tool_info(tool_name);
assert!(
info.is_some(),
"Failed to get info for tool '{}'",
tool_name
);
let (name, description) = info.unwrap();
assert_eq!(name, tool_name);
assert!(!description.is_empty());
}
}
#[test]
fn test_executor_creation() {
let registry = ToolRegistry::default();
let tool_names = vec!["bash".to_string(), "str_replace_based_edit_tool".to_string()];
let _executor = registry.create_executor(&tool_names);
let _all_executor = registry.create_executor_with_all();
}
#[test]
fn test_tool_examples() {
let registry = ToolRegistry::default();
for tool_name in registry.list_tools() {
let tool = registry.create_tool(tool_name).unwrap();
let examples = tool.examples();
assert!(
!examples.is_empty(),
"Tool '{}' has no examples",
tool_name
);
for (i, example) in examples.iter().enumerate() {
assert!(
!example.description.is_empty(),
"Tool '{}' example {} has empty description",
tool_name,
i
);
assert!(
example.parameters.is_object(),
"Tool '{}' example {} parameters is not an object",
tool_name,
i
);
assert!(
!example.expected_result.is_empty(),
"Tool '{}' example {} has empty expected result",
tool_name,
i
);
}
}
}
#[test]
fn test_tool_parameter_schemas() {
let registry = ToolRegistry::default();
for tool_name in registry.list_tools() {
let tool = registry.create_tool(tool_name).unwrap();
let schema = tool.parameters_schema();
assert!(
schema.is_object(),
"Tool '{}' schema is not an object",
tool_name
);
let schema_obj = schema.as_object().unwrap();
if let Some(type_val) = schema_obj.get("type") {
assert_eq!(
type_val.as_str(),
Some("object"),
"Tool '{}' schema type is not 'object'",
tool_name
);
}
if let Some(properties) = schema_obj.get("properties") {
assert!(
properties.is_object(),
"Tool '{}' schema properties is not an object",
tool_name
);
let props = properties.as_object().unwrap();
assert!(
!props.is_empty(),
"Tool '{}' has no properties in schema",
tool_name
);
}
}
}
}