pub const BUILTIN_TOOL_NAMES: [&str; 22] = [
"Bash",
"BashInput",
"BashOutput",
"exec_command",
"write_stdin",
"Edit",
"ExitPlanMode",
"GetFileInfo",
"Glob",
"Grep",
"KillShell",
"session_note",
"Read",
"request_permissions",
"Sleep",
"Task",
"update_goal",
"loop_control",
"ViewImage",
"WebFetch",
"Workspace",
"Write",
];
pub const BUILTIN_TOOL_ALIASES: [(&str, &str); 10] = [
("apply_patch", "Edit"),
("FileExists", "GetFileInfo"),
("GetCurrentDir", "Workspace"),
("SetWorkspace", "Workspace"),
("memory_note", "session_note"),
("recall", "session_history"),
("session_inspector", "session_history"),
("schedule_tasks", "scheduler"),
("sub_session_manager", "SubAgent"),
("SubSession", "SubAgent"),
];
pub const LEGACY_TOOL_NAME_ALIASES: [(&str, &str); 34] = [
("execute_command", "Bash"),
("file_exists", "FileExists"),
("fileExists", "FileExists"),
("get_current_dir", "GetCurrentDir"),
("getCurrentDir", "GetCurrentDir"),
("get_file_info", "GetFileInfo"),
("getFileInfo", "GetFileInfo"),
("list_directory", "Glob"),
("memory_note", "memory_note"),
("read_file", "Read"),
("set_workspace", "SetWorkspace"),
("setWorkspace", "SetWorkspace"),
("sleep", "Sleep"),
("applyPatch", "apply_patch"),
("spawn_session", "SubAgent"),
("spawnSession", "SubAgent"),
("sub_session", "SubAgent"),
("subSession", "SubAgent"),
("sub_task", "SubAgent"),
("subTask", "SubAgent"),
("team_agent", "SubAgent"),
("teamAgent", "SubAgent"),
("child_session", "SubAgent"),
("childSession", "SubAgent"),
("sub_session_manager", "SubAgent"),
("sub_agent", "SubAgent"),
("subAgent", "SubAgent"),
("SubSession", "SubAgent"),
("subsession", "SubAgent"),
("view_image", "ViewImage"),
("viewImage", "ViewImage"),
("write_file", "Write"),
("sessionInspector", "session_inspector"),
("scheduleTasks", "schedule_tasks"),
];
pub const SERVER_TOOL_NAMES: [&str; 13] = [
"Plan",
"SubAgent",
"archive_context",
"compact_context",
"scheduler",
"session_history",
"session_history_current",
"session_message",
"session_control",
"memory",
"ledger",
"load_skill",
"read_skill_resource",
];
pub const SERVER_CAPABILITY_NAMES: [&str; 27] = [
"Plan",
"SubAgent",
"Project",
"archive_context",
"ask_agent",
"browser",
"browser_eval",
"cluster",
"compact_context",
"deploy_agent",
"ledger",
"load_skill",
"memory",
"notify",
"read_skill_resource",
"scheduler",
"session_control",
"session_history",
"session_history_current",
"session_message",
"workflow_run",
"work_overview",
"work_search",
"work_inspect",
"work_changes",
"work_update",
"work_dispatch",
];
pub fn canonical_tool_name(value: &str) -> String {
let trimmed = value.trim();
if trimmed.is_empty() {
return String::new();
}
let unqualified = trimmed.rsplit("::").next().unwrap_or(trimmed).trim();
let normalized = normalize_builtin_alias(unqualified);
let canonical = resolve_alias(normalized).unwrap_or(normalized);
BUILTIN_TOOL_NAMES
.iter()
.chain(SERVER_CAPABILITY_NAMES.iter())
.find(|known| known.eq_ignore_ascii_case(canonical))
.map(|known| (*known).to_string())
.unwrap_or_else(|| canonical.to_string())
}
pub fn normalize_tool_ref(value: &str) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
return None;
}
let raw_tool_name = trimmed.split("::").last().unwrap_or(trimmed);
if crate::independent_root_spawn_reference(raw_tool_name) {
return Some("spawn_session".into());
}
let normalized = normalize_builtin_alias(raw_tool_name);
if let Some(name) = BUILTIN_TOOL_NAMES
.iter()
.chain(SERVER_TOOL_NAMES.iter())
.find(|name| name.eq_ignore_ascii_case(normalized))
{
return Some((*name).to_string());
}
BUILTIN_TOOL_ALIASES
.iter()
.find(|(alias, _)| alias.eq_ignore_ascii_case(normalized))
.map(|(alias, _)| (*alias).to_string())
}
pub fn resolve_alias(name: &str) -> Option<&'static str> {
BUILTIN_TOOL_ALIASES
.iter()
.find(|(alias, _)| alias.eq_ignore_ascii_case(name))
.map(|(_, canonical)| *canonical)
}
pub fn normalize_builtin_alias(name: &str) -> &str {
LEGACY_TOOL_NAME_ALIASES
.iter()
.find(|(alias, _)| *alias == name)
.map(|(_, normalized)| *normalized)
.unwrap_or(name)
}
pub fn legacy_shell_replacement(execution_name: &str) -> Option<&'static str> {
match execution_name {
"Bash" => Some("exec_command"),
"BashInput" | "BashOutput" | "KillShell" => Some("write_stdin"),
_ => None,
}
}
pub fn resolve_disabled_tool_reference_name(
reference: &str,
contains_registered_name: impl FnMut(&str) -> bool,
) -> Option<String> {
if let Some(name) = crate::resolve_tool_reference_name(reference, contains_registered_name) {
return Some(name);
}
let reference = reference.trim();
if reference.contains("::") {
return None;
}
let legacy = canonical_tool_name(reference);
legacy_shell_replacement(&legacy).map(|_| legacy)
}
pub fn is_builtin_tool(value: &str) -> bool {
normalize_tool_ref(value).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_shell_disabled_policy_only_expands_exact_builtin_names() {
assert_eq!(legacy_shell_replacement("Bash"), Some("exec_command"));
assert_eq!(legacy_shell_replacement("BashOutput"), Some("write_stdin"));
assert_eq!(legacy_shell_replacement("vendor::Bash"), None);
assert_eq!(legacy_shell_replacement("exec_command"), None);
}
#[test]
fn disabled_shell_fallback_reuses_only_supported_missing_unqualified_aliases() {
for (reference, legacy) in [
("Bash", "Bash"),
(" bash ", "Bash"),
("bAsH", "Bash"),
("execute_command", "Bash"),
("bashinput", "BashInput"),
("bashoutput", "BashOutput"),
("kIlLsHeLl", "KillShell"),
] {
assert_eq!(
crate::resolve_tool_reference_name(reference, |name| name == legacy),
Some(legacy.to_owned()),
);
assert_eq!(
resolve_disabled_tool_reference_name(reference, |_| false),
Some(legacy.to_owned()),
"{reference}",
);
}
for reference in [
"",
"EXECUTE_COMMAND",
"unknown",
"Read",
"exec_command",
"write_stdin",
"vendor::Bash",
"default::Bash",
"vendor::execute_command",
"default::bashoutput",
] {
assert_eq!(
resolve_disabled_tool_reference_name(reference, |_| false),
None,
"{reference}",
);
}
}
#[test]
fn disabled_shell_fallback_keeps_registered_exact_and_alias_resolution_first() {
let registered = [
"Bash",
"bash",
"execute_command",
"vendor::Bash",
"default::Bash",
"default::execute_command",
"Read",
];
for reference in registered {
assert_eq!(
resolve_disabled_tool_reference_name(reference, |name| registered.contains(&name)),
Some(reference.to_owned()),
"{reference}",
);
}
assert_eq!(
resolve_disabled_tool_reference_name("default::execute_command", |name| name == "Bash"),
Some("Bash".to_owned()),
);
assert_eq!(
resolve_disabled_tool_reference_name("read_file", |name| name == "Read"),
Some("Read".to_owned()),
);
}
#[test]
fn test_normalize_tool_ref_accepts_claude_style_names() {
assert_eq!(
normalize_tool_ref("default::Bash"),
Some("Bash".to_string())
);
}
#[test]
fn test_normalize_tool_ref_accepts_legacy_camel_aliases() {
assert_eq!(
normalize_tool_ref("default::fileExists"),
Some("FileExists".to_string())
);
assert_eq!(
normalize_tool_ref("default::getCurrentDir"),
Some("GetCurrentDir".to_string())
);
assert_eq!(
normalize_tool_ref("default::getFileInfo"),
Some("GetFileInfo".to_string())
);
assert_eq!(
normalize_tool_ref("default::setWorkspace"),
Some("SetWorkspace".to_string())
);
assert_eq!(
normalize_tool_ref("default::sleep"),
Some("Sleep".to_string())
);
assert_eq!(
normalize_tool_ref("default::viewImage"),
Some("ViewImage".to_string())
);
}
#[test]
fn retired_conclusion_tool_and_alias_are_not_recognized() {
assert_eq!(normalize_tool_ref("conclusion_with_options"), None);
assert_eq!(normalize_tool_ref("default::conclusionWithOptions"), None);
}
#[test]
fn test_normalize_tool_ref_accepts_legacy_snake_case_aliases() {
assert_eq!(
normalize_tool_ref("default::execute_command"),
Some("Bash".to_string())
);
assert_eq!(
normalize_tool_ref("default::file_exists"),
Some("FileExists".to_string())
);
assert_eq!(
normalize_tool_ref("default::get_current_dir"),
Some("GetCurrentDir".to_string())
);
assert_eq!(
normalize_tool_ref("default::get_file_info"),
Some("GetFileInfo".to_string())
);
assert_eq!(
normalize_tool_ref("default::list_directory"),
Some("Glob".to_string())
);
assert_eq!(
normalize_tool_ref("default::memory_note"),
Some("memory_note".to_string())
);
assert_eq!(
normalize_tool_ref("default::read_file"),
Some("Read".to_string())
);
assert_eq!(
normalize_tool_ref("default::set_workspace"),
Some("SetWorkspace".to_string())
);
assert_eq!(
normalize_tool_ref("default::write_file"),
Some("Write".to_string())
);
}
#[test]
fn test_normalize_tool_ref_accepts_spawn_task_aliases() {
for reference in [
"spawn_session",
"default::spawn_session",
"default::spawnSession",
] {
assert_eq!(normalize_tool_ref(reference), Some("spawn_session".into()));
assert_eq!(canonical_tool_name(reference), "SubAgent");
}
for alias in [
"default::sub_session",
"default::sub_task",
"default::team_agent",
"default::child_session",
"default::sub_agent",
"default::subAgent",
"default::SubSession",
"default::subsession",
] {
assert_eq!(normalize_tool_ref(alias), Some("SubAgent".to_string()));
}
}
#[test]
fn test_normalize_tool_ref_accepts_server_overlay_tools() {
assert_eq!(normalize_tool_ref("compress_context"), None);
assert_eq!(
normalize_tool_ref("default::read_skill_resource"),
Some("read_skill_resource".to_string())
);
}
#[test]
fn test_normalize_tool_ref_rejects_unknown_tool() {
assert_eq!(normalize_tool_ref("default::search"), None);
}
#[test]
fn test_is_builtin_tool() {
assert!(is_builtin_tool("Bash"));
assert!(is_builtin_tool("default::Bash"));
assert!(!is_builtin_tool("unknown_tool"));
assert!(!is_builtin_tool("compress_context"));
}
#[test]
fn test_resolve_alias() {
assert_eq!(resolve_alias("apply_patch"), Some("Edit"));
assert_eq!(resolve_alias("FileExists"), Some("GetFileInfo"));
assert_eq!(resolve_alias("Bash"), None);
}
#[test]
fn canonical_name_resolves_aliases_namespaces_and_known_case() {
assert_eq!(canonical_tool_name(" default::applyPatch "), "Edit");
assert_eq!(canonical_tool_name("DEFAULT::read"), "Read");
assert_eq!(canonical_tool_name("default::set_workspace"), "Workspace");
assert_eq!(canonical_tool_name("DEFAULT::WORKFLOW_RUN"), "workflow_run");
assert_eq!(
canonical_tool_name("DEFAULT::SESSION_HISTORY_CURRENT"),
"session_history_current"
);
assert_eq!(
normalize_tool_ref("session_history_current"),
Some("session_history_current".to_string())
);
assert_eq!(resolve_alias("session_history_current"), None);
}
#[test]
fn canonical_reference_name_unqualifies_dynamic_refs_and_preserves_mcp_aliases() {
assert_eq!(canonical_tool_name("default::custom_tool"), "custom_tool");
assert_eq!(
canonical_tool_name("mcp__alpha__read_file"),
"mcp__alpha__read_file"
);
assert_eq!(
canonical_tool_name("mcp__beta__read_file"),
"mcp__beta__read_file"
);
assert_ne!(
canonical_tool_name("mcp__alpha__read_file"),
canonical_tool_name("mcp__beta__read_file")
);
assert!(canonical_tool_name(" ").is_empty());
}
#[test]
fn legacy_server_names_are_a_subset_of_the_classified_server_catalog() {
for name in SERVER_TOOL_NAMES {
assert!(
SERVER_CAPABILITY_NAMES.contains(&name),
"legacy server tool {name} must remain classifiable"
);
}
}
}