Skip to main content

codetether_agent/tool/
invalid.rs

1//! Invalid tool: Handle invalid or unknown tool calls gracefully
2
3use super::{Tool, ToolResult};
4use anyhow::Result;
5use async_trait::async_trait;
6use serde_json::{Value, json};
7
8/// Invalid Tool - A fallback tool that is returned when a model tries to call
9/// a tool that doesn't exist. This provides helpful error messages and suggestions.
10pub 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            // Find similar tool names
81            let similar: Vec<&String> = self
82                .available_tools
83                .iter()
84                .filter(|t| {
85                    t.contains(requested)
86                        || requested.contains(t.as_str())
87                        || Self::levenshtein(t, requested) <= 3
88                })
89                .take(3)
90                .collect();
91
92            if similar.is_empty() {
93                format!("Available tools: {}", self.available_tools.join(", "))
94            } else {
95                format!(
96                    "Did you mean: {}?\n\nAll available tools: {}",
97                    similar
98                        .iter()
99                        .map(|s| s.as_str())
100                        .collect::<Vec<_>>()
101                        .join(", "),
102                    self.available_tools.join(", ")
103                )
104            }
105        } else {
106            "No tool list provided. Check available tools.".to_string()
107        };
108
109        let message = format!(
110            "Tool '{}' not found.\n\n\
111            Arguments received: {}\n\n\
112            {}",
113            requested, passed_args, suggestions
114        );
115
116        Ok(ToolResult::error(message))
117    }
118}
119
120impl InvalidTool {
121    /// Simple Levenshtein distance for finding similar tool names
122    fn levenshtein(a: &str, b: &str) -> usize {
123        let a: Vec<char> = a.chars().collect();
124        let b: Vec<char> = b.chars().collect();
125        let m = a.len();
126        let n = b.len();
127
128        if m == 0 {
129            return n;
130        }
131        if n == 0 {
132            return m;
133        }
134
135        let mut prev: Vec<usize> = (0..=n).collect();
136        let mut curr = vec![0; n + 1];
137
138        for i in 1..=m {
139            curr[0] = i;
140            for j in 1..=n {
141                let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
142                curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
143            }
144            std::mem::swap(&mut prev, &mut curr);
145        }
146
147        prev[n]
148    }
149}