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