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; 7] = [
63    "SubAgent",
64    "compact_context",
65    "scheduler",
66    "session_history",
67    "memory",
68    "load_skill",
69    "read_skill_resource",
70];
71
72/// Normalizes a tool reference to a standard tool name.
73///
74/// Returns None if the tool name is not recognized.
75pub fn normalize_tool_ref(value: &str) -> Option<String> {
76    let trimmed = value.trim();
77    if trimmed.is_empty() {
78        return None;
79    }
80    let raw_tool_name = trimmed.split("::").last().unwrap_or(trimmed);
81    let normalized = normalize_builtin_alias(raw_tool_name);
82
83    // Check canonical tool names first
84    if let Some(name) = BUILTIN_TOOL_NAMES
85        .iter()
86        .chain(SERVER_TOOL_NAMES.iter())
87        .find(|name| name.eq_ignore_ascii_case(normalized))
88    {
89        return Some((*name).to_string());
90    }
91
92    // Check aliases – return the *alias* name so callers see what was typed,
93    // while the executor routes it to the canonical tool.
94    BUILTIN_TOOL_ALIASES
95        .iter()
96        .find(|(alias, _)| alias.eq_ignore_ascii_case(normalized))
97        .map(|(alias, _)| (*alias).to_string())
98}
99
100/// Returns the canonical tool name for an alias, or `None` if the name is
101/// not an alias.
102pub fn resolve_alias(name: &str) -> Option<&'static str> {
103    BUILTIN_TOOL_ALIASES
104        .iter()
105        .find(|(alias, _)| alias.eq_ignore_ascii_case(name))
106        .map(|(_, canonical)| *canonical)
107}
108
109pub fn normalize_builtin_alias(name: &str) -> &str {
110    match name {
111        // Backward compatibility for earlier camelCase and snake_case names.
112        "conclusionWithOptions" => "conclusion_with_options",
113        "execute_command" => "Bash",
114        "file_exists" => "FileExists",
115        "fileExists" => "FileExists",
116        "get_current_dir" => "GetCurrentDir",
117        "getCurrentDir" => "GetCurrentDir",
118        "get_file_info" => "GetFileInfo",
119        "getFileInfo" => "GetFileInfo",
120        "list_directory" => "Glob",
121        "memory_note" => "memory_note",
122        "read_file" => "Read",
123        "set_workspace" => "SetWorkspace",
124        "setWorkspace" => "SetWorkspace",
125        "sleep" => "Sleep",
126        "applyPatch" => "apply_patch",
127        "spawn_session" => "SubAgent",
128        "spawnSession" => "SubAgent",
129        "sub_session" => "SubAgent",
130        "subSession" => "SubAgent",
131        "sub_task" => "SubAgent",
132        "subTask" => "SubAgent",
133        "team_agent" => "SubAgent",
134        "teamAgent" => "SubAgent",
135        "child_session" => "SubAgent",
136        "childSession" => "SubAgent",
137        "sub_session_manager" => "SubAgent",
138        "sub_agent" => "SubAgent",
139        "subAgent" => "SubAgent",
140        "SubSession" => "SubAgent",
141        "subsession" => "SubAgent",
142        "write_file" => "Write",
143        "sessionInspector" => "session_inspector",
144        "scheduleTasks" => "schedule_tasks",
145        _ => name,
146    }
147}
148
149/// Checks if a tool reference is a built-in tool
150pub fn is_builtin_tool(value: &str) -> bool {
151    normalize_tool_ref(value).is_some()
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_normalize_tool_ref_accepts_claude_style_names() {
160        assert_eq!(
161            normalize_tool_ref("default::Bash"),
162            Some("Bash".to_string())
163        );
164    }
165
166    #[test]
167    fn test_normalize_tool_ref_accepts_legacy_camel_aliases() {
168        assert_eq!(
169            normalize_tool_ref("default::fileExists"),
170            Some("FileExists".to_string())
171        );
172        assert_eq!(
173            normalize_tool_ref("default::getCurrentDir"),
174            Some("GetCurrentDir".to_string())
175        );
176        assert_eq!(
177            normalize_tool_ref("default::getFileInfo"),
178            Some("GetFileInfo".to_string())
179        );
180        assert_eq!(
181            normalize_tool_ref("default::setWorkspace"),
182            Some("SetWorkspace".to_string())
183        );
184        assert_eq!(
185            normalize_tool_ref("default::sleep"),
186            Some("Sleep".to_string())
187        );
188    }
189
190    #[test]
191    fn test_normalize_tool_ref_accepts_legacy_snake_case_aliases() {
192        assert_eq!(
193            normalize_tool_ref("default::execute_command"),
194            Some("Bash".to_string())
195        );
196        assert_eq!(
197            normalize_tool_ref("default::file_exists"),
198            Some("FileExists".to_string())
199        );
200        assert_eq!(
201            normalize_tool_ref("default::get_current_dir"),
202            Some("GetCurrentDir".to_string())
203        );
204        assert_eq!(
205            normalize_tool_ref("default::get_file_info"),
206            Some("GetFileInfo".to_string())
207        );
208        assert_eq!(
209            normalize_tool_ref("default::list_directory"),
210            Some("Glob".to_string())
211        );
212        assert_eq!(
213            normalize_tool_ref("default::memory_note"),
214            Some("memory_note".to_string())
215        );
216        assert_eq!(
217            normalize_tool_ref("default::read_file"),
218            Some("Read".to_string())
219        );
220        assert_eq!(
221            normalize_tool_ref("default::set_workspace"),
222            Some("SetWorkspace".to_string())
223        );
224        assert_eq!(
225            normalize_tool_ref("default::write_file"),
226            Some("Write".to_string())
227        );
228    }
229
230    #[test]
231    fn test_normalize_tool_ref_accepts_spawn_task_aliases() {
232        for alias in [
233            "default::spawn_session",
234            "default::sub_session",
235            "default::sub_task",
236            "default::team_agent",
237            "default::child_session",
238            "default::sub_agent",
239            "default::subAgent",
240            "default::SubSession",
241            "default::subsession",
242        ] {
243            assert_eq!(normalize_tool_ref(alias), Some("SubAgent".to_string()));
244        }
245    }
246
247    #[test]
248    fn test_normalize_tool_ref_accepts_server_overlay_tools() {
249        assert_eq!(normalize_tool_ref("compress_context"), None);
250        assert_eq!(
251            normalize_tool_ref("default::read_skill_resource"),
252            Some("read_skill_resource".to_string())
253        );
254    }
255
256    #[test]
257    fn test_normalize_tool_ref_rejects_unknown_tool() {
258        assert_eq!(normalize_tool_ref("default::search"), None);
259    }
260
261    #[test]
262    fn test_is_builtin_tool() {
263        assert!(is_builtin_tool("Bash"));
264        assert!(is_builtin_tool("default::Bash"));
265        assert!(!is_builtin_tool("unknown_tool"));
266        assert!(!is_builtin_tool("compress_context"));
267    }
268
269    #[test]
270    fn test_resolve_alias() {
271        assert_eq!(resolve_alias("apply_patch"), Some("Edit"));
272        assert_eq!(resolve_alias("FileExists"), Some("GetFileInfo"));
273        assert_eq!(resolve_alias("Bash"), None);
274    }
275}