tycode_core/tools/
registry.rs1use crate::ai::{ToolDefinition, ToolUseData};
2use crate::tools::r#trait::{ToolCallHandle, ToolCategory, ToolExecutor, ToolRequest};
3use crate::tools::ToolName;
4use std::collections::BTreeMap;
5use std::sync::Arc;
6use tracing::{debug, error};
7
8pub struct ToolRegistry {
9 tools: BTreeMap<String, Arc<dyn ToolExecutor>>,
10}
11
12impl ToolRegistry {
13 pub fn new(tools: Vec<Arc<dyn ToolExecutor>>) -> Self {
14 let mut registry = Self {
15 tools: BTreeMap::new(),
16 };
17
18 for tool in tools {
19 registry.register_tool(tool);
20 }
21
22 registry
23 }
24
25 pub fn register_tool(&mut self, tool: Arc<dyn ToolExecutor>) {
26 let name = tool.name().to_string();
27 debug!(tool_name = %name, "Registering tool");
28 self.tools.insert(name, tool);
29 }
30
31 pub fn get_tool_definitions(&self, tool_names: &[ToolName]) -> Vec<ToolDefinition> {
32 let allowed_names: Vec<String> = tool_names.iter().map(|name| name.to_string()).collect();
33
34 self.tools
35 .iter()
36 .filter(|(name, _)| allowed_names.contains(name) || name.starts_with("mcp_"))
37 .map(|(_, tool)| ToolDefinition {
38 name: tool.name().to_string(),
39 description: tool.description().to_string(),
40 input_schema: tool.input_schema(),
41 })
42 .collect()
43 }
44
45 pub async fn process_tools(
46 &self,
47 tool_use: &ToolUseData,
48 allowed_tools: &[ToolName],
49 ) -> Result<Box<dyn ToolCallHandle>, String> {
50 let mut allowed_names: Vec<&str> = allowed_tools
51 .iter()
52 .map(|tool| tool.as_str())
53 .filter(|name| self.tools.contains_key(*name))
54 .collect();
55
56 for name in self.tools.keys() {
58 if name.starts_with("mcp_") && !allowed_names.contains(&name.as_str()) {
59 allowed_names.push(name.as_str());
60 }
61 }
62
63 let tool = match self.tools.get(&tool_use.name) {
64 Some(tool) => tool,
65 None => {
66 let available = allowed_names.join(", ");
67 error!(tool_name = %tool_use.name, "Unknown tool");
68 return Err(format!(
69 "Unknown tool: {}. Available tools: {}",
70 tool_use.name, available
71 ));
72 }
73 };
74
75 if !allowed_names.contains(&tool_use.name.as_str()) {
76 debug!(
77 tool_name = %tool_use.name,
78 allowed_tools = ?allowed_names,
79 "Tool not in allowed list for current agent"
80 );
81 return Err(format!(
82 "Tool not available for current agent: {}",
83 tool_use.name
84 ));
85 }
86
87 let schema = tool.input_schema();
88 let coerced_arguments =
89 match crate::tools::fuzzy_json::coerce_to_schema(&tool_use.arguments, &schema) {
90 Ok(args) => args,
91 Err(e) => {
92 error!(?e, tool_name = %tool_use.name, "Failed to coerce tool arguments");
93 return Err(format!("Failed to coerce arguments: {e:?}"));
94 }
95 };
96
97 let request = ToolRequest::new(coerced_arguments, tool_use.id.clone());
98 tool.process(&request).await.map_err(|e| {
99 error!(?e, tool_name = %tool_use.name, "Tool processing failed");
100 format!("Error: {e:?}")
101 })
102 }
103
104 pub fn list_tools(&self) -> Vec<&str> {
105 self.tools.keys().map(|s| s.as_str()).collect()
106 }
107
108 pub fn get_tool_executor_by_name(&self, name: &str) -> Option<&Arc<dyn ToolExecutor>> {
110 self.tools.get(name)
111 }
112
113 pub fn get_tool_category_by_name(&self, name: &str) -> Option<ToolCategory> {
115 self.tools.get(name).map(|executor| executor.category())
116 }
117}