claude_rust_tools/infrastructure/
tool_search_tool.rs1use claude_rust_errors::AppResult;
2use claude_rust_types::{PermissionLevel, SearchReadInfo, Tool};
3use serde_json::{Value, json};
4
5pub struct ToolSearchTool {
6 tool_catalog: Vec<(String, String)>,
7}
8
9impl ToolSearchTool {
10 pub fn new(tool_catalog: Vec<(String, String)>) -> Self {
11 Self { tool_catalog }
12 }
13}
14
15#[async_trait::async_trait]
16impl Tool for ToolSearchTool {
17 fn name(&self) -> &str {
18 "tool_search"
19 }
20
21 fn description(&self) -> &str {
22 "Search available tools by name or description. Returns matching tool names with their descriptions."
23 }
24
25 fn input_schema(&self) -> Value {
26 json!({
27 "type": "object",
28 "properties": {
29 "query": {
30 "type": "string",
31 "description": "Search query to match against tool names and descriptions"
32 },
33 "max_results": {
34 "type": "integer",
35 "description": "Maximum number of results to return (default 5)"
36 }
37 },
38 "required": ["query"]
39 })
40 }
41
42 fn permission_level(&self) -> PermissionLevel {
43 PermissionLevel::ReadOnly
44 }
45
46 fn is_read_only(&self, _input: &Value) -> bool { true }
47 fn is_concurrent_safe(&self, _input: &Value) -> bool { true }
48 fn always_load(&self) -> bool { true }
49
50 fn is_search_or_read_command(&self, _input: &Value) -> SearchReadInfo {
51 SearchReadInfo { is_search: true, is_read: false, is_list: false }
52 }
53
54 async fn execute(&self, input: Value) -> AppResult<String> {
55 let query = input
56 .get("query")
57 .and_then(|v| v.as_str())
58 .ok_or_else(|| claude_rust_errors::AppError::Tool("missing 'query' field".into()))?;
59
60 let max_results = input
61 .get("max_results")
62 .and_then(|v| v.as_u64())
63 .unwrap_or(5) as usize;
64
65 let query_lower = query.to_lowercase();
66
67 tracing::info!(query, max_results, "searching tools");
68
69 let matches: Vec<&(String, String)> = self
70 .tool_catalog
71 .iter()
72 .filter(|(name, desc)| {
73 name.to_lowercase().contains(&query_lower)
74 || desc.to_lowercase().contains(&query_lower)
75 })
76 .take(max_results)
77 .collect();
78
79 if matches.is_empty() {
80 return Ok(format!("No tools matched query '{query}'."));
81 }
82
83 let mut result = format!("{} tool(s) matched:\n", matches.len());
84 for (name, desc) in matches {
85 result.push_str(&format!("\n- **{name}**: {desc}"));
86 }
87
88 Ok(result)
89 }
90}