Skip to main content

polyhook_core/
tools.rs

1use crate::types::CallerKind;
2
3/// Normalize a vendor-specific tool name to the canonical polyhook tool name.
4///
5/// The lookup is case-insensitive. If no mapping exists the original name is
6/// returned unchanged.
7pub fn normalize_tool(vendor: &str, caller: &CallerKind) -> String {
8    let lower = vendor.to_lowercase();
9
10    let canonical = match caller {
11        CallerKind::ClaudeCode => normalize_claude_code(&lower),
12        CallerKind::Cursor => normalize_cursor(&lower),
13        CallerKind::Windsurf => normalize_windsurf(&lower),
14        CallerKind::Cline => normalize_cline(&lower),
15        CallerKind::Amp => normalize_amp(&lower),
16        CallerKind::GeminiCli => normalize_gemini_cli(&lower),
17        CallerKind::Unknown => None,
18    };
19
20    canonical
21        .map(|s| s.to_owned())
22        .unwrap_or_else(|| vendor.to_owned())
23}
24
25fn normalize_claude_code(lower: &str) -> Option<&'static str> {
26    match lower {
27        "bash" => Some("bash"),
28        "read" => Some("read_file"),
29        "write" => Some("write_file"),
30        "edit" => Some("edit_file"),
31        "ls" => Some("list_dir"),
32        "grep" => Some("grep"),
33        "glob" => Some("glob"),
34        "websearch" => Some("web_search"),
35        "webfetch" => Some("web_fetch"),
36        "task" => Some("spawn_agent"),
37        "mcp__ide__getdiagnostics" => Some("diagnostics"),
38        _ => None,
39    }
40}
41
42fn normalize_cursor(lower: &str) -> Option<&'static str> {
43    match lower {
44        "run_terminal_cmd" => Some("bash"),
45        "read_file" => Some("read_file"),
46        "edit_file" => Some("write_file"),
47        "apply_edit" => Some("edit_file"),
48        "list_dir" => Some("list_dir"),
49        "grep_search" => Some("grep"),
50        "file_search" => Some("glob"),
51        "web_search" => Some("web_search"),
52        "fetch_url" => Some("web_fetch"),
53        "spawn_agent" => Some("spawn_agent"),
54        "get_diagnostics" => Some("diagnostics"),
55        "move_file" => Some("move_file"),
56        "delete_file" => Some("delete_file"),
57        "create_dir" => Some("create_dir"),
58        _ => None,
59    }
60}
61
62fn normalize_windsurf(lower: &str) -> Option<&'static str> {
63    match lower {
64        "run_command" => Some("bash"),
65        "read_file" => Some("read_file"),
66        "write_file" => Some("write_file"),
67        "edit_file" => Some("edit_file"),
68        "list_directory" => Some("list_dir"),
69        "search_files" => Some("grep"),
70        "find_files" => Some("glob"),
71        "search_web" => Some("web_search"),
72        "fetch_page" => Some("web_fetch"),
73        "spawn_agent" => Some("spawn_agent"),
74        "get_diagnostics" => Some("diagnostics"),
75        "move_file" => Some("move_file"),
76        "delete_file" => Some("delete_file"),
77        "create_directory" => Some("create_dir"),
78        _ => None,
79    }
80}
81
82fn normalize_cline(lower: &str) -> Option<&'static str> {
83    match lower {
84        "execute_command" => Some("bash"),
85        "read_file" => Some("read_file"),
86        "write_to_file" => Some("write_file"),
87        "apply_diff" => Some("edit_file"),
88        "list_files" => Some("list_dir"),
89        "search_files" => Some("grep"),
90        "search" => Some("web_search"),
91        "fetch" => Some("web_fetch"),
92        "rename_file" => Some("move_file"),
93        "delete_file" => Some("delete_file"),
94        "create_directory" => Some("create_dir"),
95        "get_diagnostics" => Some("diagnostics"),
96        _ => None,
97    }
98}
99
100fn normalize_amp(lower: &str) -> Option<&'static str> {
101    match lower {
102        "shell" => Some("bash"),
103        "file.read" => Some("read_file"),
104        "file.write" => Some("write_file"),
105        "file.edit" => Some("edit_file"),
106        "fs.list" => Some("list_dir"),
107        "search.grep" => Some("grep"),
108        "search.glob" => Some("glob"),
109        "web.search" => Some("web_search"),
110        "web.fetch" => Some("web_fetch"),
111        "agent.spawn" => Some("spawn_agent"),
112        "lsp.diagnostics" => Some("diagnostics"),
113        "fs.move" => Some("move_file"),
114        "fs.delete" => Some("delete_file"),
115        "fs.mkdir" => Some("create_dir"),
116        _ => None,
117    }
118}
119
120fn normalize_gemini_cli(lower: &str) -> Option<&'static str> {
121    match lower {
122        "run_shell_command" => Some("bash"),
123        "read_file" => Some("read_file"),
124        "write_file" => Some("write_file"),
125        "replace" => Some("edit_file"),
126        "list_directory" => Some("list_dir"),
127        "glob" => Some("glob"),
128        "grep" => Some("grep"),
129        "google_web_search" => Some("web_search"),
130        "fetch" => Some("web_fetch"),
131        "move_file" => Some("move_file"),
132        "delete_file" => Some("delete_file"),
133        "make_directory" => Some("create_dir"),
134        _ => None,
135    }
136}
137
138// ---------------------------------------------------------------------------
139// Tests
140// ---------------------------------------------------------------------------
141
142#[cfg(test)]
143#[path = "tools_tests.rs"]
144mod tests;