pub const CLAUDE_CODE: &str = "claude-code";
pub const CODEX: &str = "codex";
const MCP_PREFIX: &str = "mcp__";
const PATCH_ADD: &str = "*** Add File";
const PATCH_UPDATE: &str = "*** Update File";
const PATCH_DELETE: &str = "*** Delete File";
pub fn canonical_tool(
family: &str,
namespace: Option<&str>,
name: &str,
payload: Option<&str>,
) -> String {
if let Some(mcp) = canonical_mcp(namespace, name) {
return mcp;
}
match family {
CODEX => canonical_codex(namespace, name, payload),
CLAUDE_CODE | "" => name.to_string(),
other => qualify(other, namespace, name),
}
}
fn canonical_mcp(namespace: Option<&str>, name: &str) -> Option<String> {
if name.starts_with(MCP_PREFIX) {
return Some(name.to_string());
}
let ns = namespace?;
if ns.starts_with(MCP_PREFIX) {
return Some(format!("{ns}__{name}"));
}
None
}
fn canonical_codex(namespace: Option<&str>, name: &str, payload: Option<&str>) -> String {
if namespace.is_none() {
if let Some((ns, rest)) = name.split_once('/') {
return canonical_codex(Some(ns), rest, payload);
}
}
if let Some(ns) = namespace {
return match (ns, name) {
("collaboration", "spawn_agent" | "followup_task") => "Task".to_string(),
("web", "run") => "WebSearch".to_string(),
_ => qualify(CODEX, Some(ns), name),
};
}
match name {
"exec_command" | "shell_command" => "Bash".to_string(),
"apply_patch" => canonical_apply_patch(payload),
"update_plan" => "TodoWrite".to_string(),
"spawn_agent" => "Task".to_string(),
"request_user_input_async" => "AskUserQuestion".to_string(),
_ => qualify(CODEX, None, name),
}
}
fn canonical_apply_patch(payload: Option<&str>) -> String {
let Some(blob) = payload else {
return "Edit".to_string();
};
if blob.contains(PATCH_ADD) {
return "Write".to_string();
}
if blob.contains(PATCH_UPDATE) {
return "Edit".to_string();
}
if blob.contains(PATCH_DELETE) {
return format!("{CODEX}:apply_patch_delete");
}
"Edit".to_string()
}
fn qualify(family: &str, namespace: Option<&str>, name: &str) -> String {
match namespace {
Some(ns) => format!("{family}:{ns}/{name}"),
None => format!("{family}:{name}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn claude_code_names_are_unchanged() {
for name in ["Bash", "Write", "Edit", "Read", "Task", "TodoWrite", "*"] {
assert_eq!(
canonical_tool(CLAUDE_CODE, None, name, None),
name,
"Claude Code is the canonical vocabulary; '{name}' must not be rewritten"
);
}
}
#[test]
fn absent_family_passes_through() {
assert_eq!(canonical_tool("", None, "Bash", None), "Bash");
assert_eq!(canonical_tool("", None, "Edit", None), "Edit");
}
#[test]
fn unknown_named_family_is_qualified() {
assert_eq!(canonical_tool("aider", None, "Bash", None), "aider:Bash");
}
#[test]
fn codex_shell_tools_become_bash() {
assert_eq!(canonical_tool(CODEX, None, "exec_command", None), "Bash");
assert_eq!(canonical_tool(CODEX, None, "shell_command", None), "Bash");
}
#[test]
fn codex_shell_call_agrees_with_claude_bash() {
assert_eq!(
canonical_tool(CODEX, None, "exec_command", Some(r#"{"cmd":"cargo test"}"#)),
canonical_tool(CLAUDE_CODE, None, "Bash", Some("cargo test")),
);
}
#[test]
fn codex_exec_is_not_bash() {
assert_eq!(canonical_tool(CODEX, None, "exec", None), "codex:exec");
}
#[test]
fn codex_write_stdin_is_not_bash() {
assert_eq!(
canonical_tool(CODEX, None, "write_stdin", None),
"codex:write_stdin"
);
}
#[test]
fn apply_patch_add_file_is_write() {
let patch = "*** Begin Patch\n*** Add File: AGENTS.md\n+# Repository Guidelines\n";
assert_eq!(
canonical_tool(CODEX, None, "apply_patch", Some(patch)),
"Write"
);
}
#[test]
fn apply_patch_update_file_is_edit() {
let patch = "*** Begin Patch\n*** Update File: src/main.rs\n@@\n-old\n+new\n";
assert_eq!(
canonical_tool(CODEX, None, "apply_patch", Some(patch)),
"Edit"
);
}
#[test]
fn apply_patch_delete_file_merges_with_nothing() {
let patch = "*** Begin Patch\n*** Delete File: stale.rs\n";
assert_eq!(
canonical_tool(CODEX, None, "apply_patch", Some(patch)),
"codex:apply_patch_delete"
);
}
#[test]
fn apply_patch_mixed_markers_prefer_write() {
let patch = "*** Begin Patch\n*** Update File: a.rs\n@@\n*** Add File: b.rs\n+new\n";
assert_eq!(
canonical_tool(CODEX, None, "apply_patch", Some(patch)),
"Write"
);
}
#[test]
fn apply_patch_without_marker_is_edit() {
assert_eq!(canonical_tool(CODEX, None, "apply_patch", None), "Edit");
assert_eq!(
canonical_tool(CODEX, None, "apply_patch", Some("garbage")),
"Edit"
);
}
#[test]
fn mcp_name_already_canonical_passes_through() {
assert_eq!(
canonical_tool(CODEX, None, "mcp__chrome-devtools__click", None),
"mcp__chrome-devtools__click"
);
}
#[test]
fn mcp_split_shape_is_joined() {
assert_eq!(
canonical_tool(CODEX, Some("mcp__cua_repl"), "js", None),
"mcp__cua_repl__js"
);
}
#[test]
fn mcp_call_agrees_across_agents() {
assert_eq!(
canonical_tool(CODEX, Some("mcp__context7"), "query_docs", None),
canonical_tool(CLAUDE_CODE, None, "mcp__context7__query_docs", None),
);
}
#[test]
fn mcp_server_with_mixed_separators_survives_renormalization() {
for name in [
"mcp__chrome-devtools__new_page",
"mcp__claude_ai_Claude_Docs__batch",
"mcp__Parallel-search-mcp__web_fetch",
] {
let once = canonical_tool(CODEX, None, name, None);
assert_eq!(once, name);
assert_eq!(
canonical_tool(CLAUDE_CODE, None, &once, None),
name,
"normalization must be idempotent and never attempt the inverse"
);
}
}
#[test]
fn mcp_resolves_before_the_family_table() {
assert_eq!(
canonical_tool("some-future-agent", None, "mcp__context7__query_docs", None),
"mcp__context7__query_docs"
);
}
#[test]
fn codex_update_plan_is_todowrite() {
assert_eq!(
canonical_tool(CODEX, None, "update_plan", None),
"TodoWrite"
);
}
#[test]
fn codex_delegation_becomes_task() {
assert_eq!(canonical_tool(CODEX, None, "spawn_agent", None), "Task");
assert_eq!(
canonical_tool(CODEX, Some("collaboration"), "spawn_agent", None),
"Task"
);
assert_eq!(
canonical_tool(CODEX, Some("collaboration"), "followup_task", None),
"Task"
);
}
#[test]
fn codex_collaboration_messaging_is_not_task() {
assert_eq!(
canonical_tool(CODEX, Some("collaboration"), "send_message", None),
"codex:collaboration/send_message"
);
assert_eq!(
canonical_tool(CODEX, Some("collaboration"), "wait_agent", None),
"codex:collaboration/wait_agent"
);
}
#[test]
fn codex_user_input_is_askuserquestion() {
assert_eq!(
canonical_tool(CODEX, None, "request_user_input_async", None),
"AskUserQuestion"
);
}
#[test]
fn codex_web_run_is_websearch() {
assert_eq!(canonical_tool(CODEX, Some("web"), "run", None), "WebSearch");
}
#[test]
fn codex_wait_primitives_merge_with_nothing() {
assert_eq!(canonical_tool(CODEX, None, "wait", None), "codex:wait");
assert_eq!(
canonical_tool(CODEX, Some("clock"), "sleep", None),
"codex:clock/sleep"
);
}
#[test]
fn namespace_is_part_of_the_identity() {
assert_ne!(
canonical_tool(CODEX, Some("somens"), "thing", None),
canonical_tool(CODEX, None, "thing", None),
);
}
#[test]
fn prejoined_namespace_matches_the_split_form() {
for (ns, name) in [
("collaboration", "spawn_agent"),
("collaboration", "send_message"),
("web", "run"),
("clock", "sleep"),
] {
assert_eq!(
canonical_tool(CODEX, None, &format!("{ns}/{name}"), None),
canonical_tool(CODEX, Some(ns), name, None),
"pre-joined '{ns}/{name}' must match the split form"
);
}
}
#[test]
fn normalization_is_idempotent() {
for (ns, name) in [
(None, "exec_command"),
(None, "apply_patch"),
(None, "update_plan"),
(Some("mcp__cua_repl"), "js"),
(Some("collaboration"), "spawn_agent"),
] {
let once = canonical_tool(CODEX, ns, name, Some("*** Update File: x"));
let twice = canonical_tool(CLAUDE_CODE, None, &once, None);
assert_eq!(once, twice, "normalizing {name} twice changed the result");
}
}
}