Skip to main content

bamboo_domain/
tool_names.rs

1//! Tool name constants and normalization functions.
2//!
3//! These are pure data and functions with zero crate dependencies. They are
4//! used by both `core::config` and `agent::tools::executor`.
5
6/// List of all built-in tool names.
7///
8/// This list intentionally includes only tools that are always registered by
9/// `BuiltinToolExecutor::new()`. Optional tools (for example integrations that
10/// depend on host binaries) should NOT be added here.
11pub const BUILTIN_TOOL_NAMES: [&str; 21] = [
12    "conclusion_with_options",
13    "Bash",
14    "BashOutput",
15    "Edit",
16    "EnterPlanMode",
17    "ExitPlanMode",
18    "GetFileInfo",
19    "Glob",
20    "Grep",
21    "js_repl",
22    "KillShell",
23    "session_note",
24    "NotebookEdit",
25    "Read",
26    "request_permissions",
27    "Sleep",
28    "Task",
29    "WebFetch",
30    "WebSearch",
31    "Workspace",
32    "Write",
33];
34
35/// Tool names that are accepted as aliases for built-in/server tools but are not
36/// independently listed in `BUILTIN_TOOL_NAMES`/`SERVER_TOOL_NAMES`. Calls to these names are
37/// transparently routed to their canonical counterpart.
38pub const BUILTIN_TOOL_ALIASES: [(&str, &str); 10] = [
39    // apply_patch is a patch-only alias for Edit
40    ("apply_patch", "Edit"),
41    // FileExists is subsumed by GetFileInfo (returns {exists: false} for missing paths)
42    ("FileExists", "GetFileInfo"),
43    // GetCurrentDir + SetWorkspace are subsumed by Workspace
44    ("GetCurrentDir", "Workspace"),
45    ("SetWorkspace", "Workspace"),
46    // Session note rename
47    ("memory_note", "session_note"),
48    // Server tool renames: `recall` was ambiguous with durable-memory recall
49    // (RAG-style relevant-memory injection). The canonical name is now
50    // `session_history` — strictly a read-only viewer over local SQLite
51    // session storage. Older names still route to the canonical tool.
52    ("recall", "session_history"),
53    ("session_inspector", "session_history"),
54    ("schedule_tasks", "scheduler"),
55    // SubAgent manager alias
56    ("sub_session_manager", "SubAgent"),
57    ("SubSession", "SubAgent"),
58];
59
60pub const SERVER_TOOL_NAMES: [&str; 7] = [
61    "SubAgent",
62    "compact_context",
63    "scheduler",
64    "session_history",
65    "memory",
66    "load_skill",
67    "read_skill_resource",
68];
69
70/// Normalizes a tool reference to a standard tool name.
71///
72/// Returns None if the tool name is not recognized.
73pub fn normalize_tool_ref(value: &str) -> Option<String> {
74    let trimmed = value.trim();
75    if trimmed.is_empty() {
76        return None;
77    }
78    let raw_tool_name = trimmed.split("::").last().unwrap_or(trimmed);
79    let normalized = normalize_builtin_alias(raw_tool_name);
80
81    // Check canonical tool names first
82    if let Some(name) = BUILTIN_TOOL_NAMES
83        .iter()
84        .chain(SERVER_TOOL_NAMES.iter())
85        .find(|name| name.eq_ignore_ascii_case(normalized))
86    {
87        return Some((*name).to_string());
88    }
89
90    // Check aliases – return the *alias* name so callers see what was typed,
91    // while the executor routes it to the canonical tool.
92    BUILTIN_TOOL_ALIASES
93        .iter()
94        .find(|(alias, _)| alias.eq_ignore_ascii_case(normalized))
95        .map(|(alias, _)| (*alias).to_string())
96}
97
98/// Returns the canonical tool name for an alias, or `None` if the name is
99/// not an alias.
100pub fn resolve_alias(name: &str) -> Option<&'static str> {
101    BUILTIN_TOOL_ALIASES
102        .iter()
103        .find(|(alias, _)| alias.eq_ignore_ascii_case(name))
104        .map(|(_, canonical)| *canonical)
105}
106
107pub fn normalize_builtin_alias(name: &str) -> &str {
108    match name {
109        // Backward compatibility for earlier camelCase and snake_case names.
110        "conclusionWithOptions" => "conclusion_with_options",
111        "execute_command" => "Bash",
112        "file_exists" => "FileExists",
113        "fileExists" => "FileExists",
114        "get_current_dir" => "GetCurrentDir",
115        "getCurrentDir" => "GetCurrentDir",
116        "get_file_info" => "GetFileInfo",
117        "getFileInfo" => "GetFileInfo",
118        "list_directory" => "Glob",
119        "memory_note" => "memory_note",
120        "read_file" => "Read",
121        "set_workspace" => "SetWorkspace",
122        "setWorkspace" => "SetWorkspace",
123        "sleep" => "Sleep",
124        "applyPatch" => "apply_patch",
125        "spawn_session" => "SubAgent",
126        "spawnSession" => "SubAgent",
127        "sub_session" => "SubAgent",
128        "subSession" => "SubAgent",
129        "sub_task" => "SubAgent",
130        "subTask" => "SubAgent",
131        "team_agent" => "SubAgent",
132        "teamAgent" => "SubAgent",
133        "child_session" => "SubAgent",
134        "childSession" => "SubAgent",
135        "sub_session_manager" => "SubAgent",
136        "sub_agent" => "SubAgent",
137        "subAgent" => "SubAgent",
138        "SubSession" => "SubAgent",
139        "subsession" => "SubAgent",
140        "write_file" => "Write",
141        "sessionInspector" => "session_inspector",
142        "scheduleTasks" => "schedule_tasks",
143        _ => name,
144    }
145}
146
147/// Checks if a tool reference is a built-in tool
148pub fn is_builtin_tool(value: &str) -> bool {
149    normalize_tool_ref(value).is_some()
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_normalize_tool_ref_accepts_claude_style_names() {
158        assert_eq!(
159            normalize_tool_ref("default::Bash"),
160            Some("Bash".to_string())
161        );
162    }
163
164    #[test]
165    fn test_normalize_tool_ref_accepts_legacy_camel_aliases() {
166        assert_eq!(
167            normalize_tool_ref("default::fileExists"),
168            Some("FileExists".to_string())
169        );
170        assert_eq!(
171            normalize_tool_ref("default::getCurrentDir"),
172            Some("GetCurrentDir".to_string())
173        );
174        assert_eq!(
175            normalize_tool_ref("default::getFileInfo"),
176            Some("GetFileInfo".to_string())
177        );
178        assert_eq!(
179            normalize_tool_ref("default::setWorkspace"),
180            Some("SetWorkspace".to_string())
181        );
182        assert_eq!(
183            normalize_tool_ref("default::sleep"),
184            Some("Sleep".to_string())
185        );
186    }
187
188    #[test]
189    fn test_normalize_tool_ref_accepts_legacy_snake_case_aliases() {
190        assert_eq!(
191            normalize_tool_ref("default::execute_command"),
192            Some("Bash".to_string())
193        );
194        assert_eq!(
195            normalize_tool_ref("default::file_exists"),
196            Some("FileExists".to_string())
197        );
198        assert_eq!(
199            normalize_tool_ref("default::get_current_dir"),
200            Some("GetCurrentDir".to_string())
201        );
202        assert_eq!(
203            normalize_tool_ref("default::get_file_info"),
204            Some("GetFileInfo".to_string())
205        );
206        assert_eq!(
207            normalize_tool_ref("default::list_directory"),
208            Some("Glob".to_string())
209        );
210        assert_eq!(
211            normalize_tool_ref("default::memory_note"),
212            Some("memory_note".to_string())
213        );
214        assert_eq!(
215            normalize_tool_ref("default::read_file"),
216            Some("Read".to_string())
217        );
218        assert_eq!(
219            normalize_tool_ref("default::set_workspace"),
220            Some("SetWorkspace".to_string())
221        );
222        assert_eq!(
223            normalize_tool_ref("default::write_file"),
224            Some("Write".to_string())
225        );
226    }
227
228    #[test]
229    fn test_normalize_tool_ref_accepts_spawn_task_aliases() {
230        for alias in [
231            "default::spawn_session",
232            "default::sub_session",
233            "default::sub_task",
234            "default::team_agent",
235            "default::child_session",
236            "default::sub_agent",
237            "default::subAgent",
238            "default::SubSession",
239            "default::subsession",
240        ] {
241            assert_eq!(normalize_tool_ref(alias), Some("SubAgent".to_string()));
242        }
243    }
244
245    #[test]
246    fn test_normalize_tool_ref_accepts_server_overlay_tools() {
247        assert_eq!(normalize_tool_ref("compress_context"), None);
248        assert_eq!(
249            normalize_tool_ref("default::read_skill_resource"),
250            Some("read_skill_resource".to_string())
251        );
252    }
253
254    #[test]
255    fn test_normalize_tool_ref_rejects_unknown_tool() {
256        assert_eq!(normalize_tool_ref("default::search"), None);
257    }
258
259    #[test]
260    fn test_is_builtin_tool() {
261        assert!(is_builtin_tool("Bash"));
262        assert!(is_builtin_tool("default::Bash"));
263        assert!(!is_builtin_tool("unknown_tool"));
264        assert!(!is_builtin_tool("compress_context"));
265    }
266
267    #[test]
268    fn test_resolve_alias() {
269        assert_eq!(resolve_alias("apply_patch"), Some("Edit"));
270        assert_eq!(resolve_alias("FileExists"), Some("GetFileInfo"));
271        assert_eq!(resolve_alias("Bash"), None);
272    }
273}