codetether_agent/tool/
invalid.rs1use super::{Tool, ToolResult};
4use anyhow::Result;
5use async_trait::async_trait;
6use serde_json::{json, Value};
7
8pub struct InvalidTool {
11 requested_tool: String,
12 available_tools: Vec<String>,
13}
14
15impl Default for InvalidTool {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl InvalidTool {
22 pub fn new() -> Self {
23 Self {
24 requested_tool: String::new(),
25 available_tools: Vec::new(),
26 }
27 }
28
29 pub fn with_context(requested_tool: String, available_tools: Vec<String>) -> Self {
30 Self {
31 requested_tool,
32 available_tools,
33 }
34 }
35}
36
37#[async_trait]
38impl Tool for InvalidTool {
39 fn id(&self) -> &str {
40 "invalid"
41 }
42
43 fn name(&self) -> &str {
44 "Invalid Tool Handler"
45 }
46
47 fn description(&self) -> &str {
48 "Handles invalid or unknown tool calls. Returns helpful error messages with suggestions for valid tools."
49 }
50
51 fn parameters(&self) -> Value {
52 json!({
53 "type": "object",
54 "properties": {
55 "requested_tool": {
56 "type": "string",
57 "description": "The tool that was requested but not found"
58 },
59 "args": {
60 "type": "object",
61 "description": "The arguments that were passed"
62 }
63 },
64 "required": ["requested_tool"]
65 })
66 }
67
68 async fn execute(&self, args: Value) -> Result<ToolResult> {
69 let requested = args["requested_tool"]
70 .as_str()
71 .unwrap_or(&self.requested_tool);
72
73 let passed_args = if let Some(obj) = args.get("args") {
74 serde_json::to_string_pretty(obj).unwrap_or_else(|_| "{}".to_string())
75 } else {
76 "{}".to_string()
77 };
78
79 let suggestions = if !self.available_tools.is_empty() {
80 let similar: Vec<&String> = self.available_tools
82 .iter()
83 .filter(|t| {
84 t.contains(requested) ||
85 requested.contains(t.as_str()) ||
86 Self::levenshtein(t, requested) <= 3
87 })
88 .take(3)
89 .collect();
90
91 if similar.is_empty() {
92 format!("Available tools: {}", self.available_tools.join(", "))
93 } else {
94 format!("Did you mean: {}?\n\nAll available tools: {}",
95 similar.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", "),
96 self.available_tools.join(", ")
97 )
98 }
99 } else {
100 "No tool list provided. Check available tools.".to_string()
101 };
102
103 let message = format!(
104 "Tool '{}' not found.\n\n\
105 Arguments received: {}\n\n\
106 {}",
107 requested, passed_args, suggestions
108 );
109
110 Ok(ToolResult::error(message))
111 }
112}
113
114impl InvalidTool {
115 fn levenshtein(a: &str, b: &str) -> usize {
117 let a: Vec<char> = a.chars().collect();
118 let b: Vec<char> = b.chars().collect();
119 let m = a.len();
120 let n = b.len();
121
122 if m == 0 { return n; }
123 if n == 0 { return m; }
124
125 let mut prev: Vec<usize> = (0..=n).collect();
126 let mut curr = vec![0; n + 1];
127
128 for i in 1..=m {
129 curr[0] = i;
130 for j in 1..=n {
131 let cost = if a[i-1] == b[j-1] { 0 } else { 1 };
132 curr[j] = (prev[j] + 1)
133 .min(curr[j-1] + 1)
134 .min(prev[j-1] + cost);
135 }
136 std::mem::swap(&mut prev, &mut curr);
137 }
138
139 prev[n]
140 }
141}