Skip to main content

mars_agents/build/
tool_normalize.rs

1pub struct NormalizedTool {
2    pub name: String,
3    pub status: ToolProjectionStatus,
4}
5
6#[derive(Clone, Copy, PartialEq, Eq)]
7pub enum ToolProjectionStatus {
8    Canonical,
9    Normalized,
10    Unknown,
11}
12
13pub fn normalize_tool_for_harness(raw: &str, target_harness: &str) -> NormalizedTool {
14    let trimmed = raw.trim();
15    if trimmed.is_empty() {
16        return NormalizedTool {
17            name: String::new(),
18            status: ToolProjectionStatus::Unknown,
19        };
20    }
21
22    let (head, payload) = match trimmed.find('(') {
23        Some(index) => (&trimmed[..index], &trimmed[index..]),
24        None => (trimmed, ""),
25    };
26
27    let canonical = canonical_tool_name(head, target_harness);
28    match canonical {
29        Some(value) => {
30            let status = if head == value {
31                ToolProjectionStatus::Canonical
32            } else {
33                ToolProjectionStatus::Normalized
34            };
35            NormalizedTool {
36                name: format!("{value}{payload}"),
37                status,
38            }
39        }
40        None => NormalizedTool {
41            name: trimmed.to_string(),
42            status: ToolProjectionStatus::Unknown,
43        },
44    }
45}
46
47pub fn is_first_class_harness(harness: &str) -> bool {
48    matches!(
49        harness.trim().to_ascii_lowercase().as_str(),
50        "claude" | "codex" | "opencode"
51    )
52}
53
54fn canonical_tool_name(head: &str, target_harness: &str) -> Option<&'static str> {
55    let key = head.trim().to_ascii_lowercase();
56    if key.is_empty() {
57        return None;
58    }
59
60    match target_harness.trim().to_ascii_lowercase().as_str() {
61        "claude" => canonical_claude_tool(&key),
62        "codex" => canonical_codex_tool(&key),
63        "opencode" => canonical_opencode_tool(&key),
64        "cursor" | "pi" => canonical_generic_tool(&key),
65        _ => canonical_generic_tool(&key),
66    }
67}
68
69fn canonical_claude_tool(key: &str) -> Option<&'static str> {
70    match key {
71        "bash" | "shell" | "terminal" => Some("Bash"),
72        "read" | "cat" | "view" => Some("Read"),
73        "write" => Some("Write"),
74        "edit" | "sed" => Some("Edit"),
75        "agent" | "subagent" => Some("Agent"),
76        "glob" | "find" => Some("Glob"),
77        "grep" | "search" | "rg" => Some("Grep"),
78        "notebook" | "jupyter" => Some("Notebook"),
79        "task" | "task_tool" | "taskcreate" | "task_create" | "taskget" | "task_get"
80        | "tasklist" | "task_list" | "taskoutput" | "task_output" | "taskstop" | "task_stop"
81        | "taskupdate" | "task_update" => Some("Task"),
82        "web_search" | "websearch" => Some("WebSearch"),
83        "web_fetch" | "webfetch" => Some("WebFetch"),
84        "todo_read" | "todoread" => Some("TodoRead"),
85        "todo_write" | "todowrite" => Some("TodoWrite"),
86        "cron" | "croncreate" | "cron_create" | "crondelete" | "cron_delete" | "cronlist"
87        | "cron_list" => Some("Cron"),
88        "ask_user" | "askuser" | "askuserquestion" | "ask_user_question" => Some("AskUser"),
89        "notifications" | "pushnotification" | "push_notification" => Some("Notifications"),
90        "plan_mode" | "planmode" | "enterplanmode" | "enter_plan_mode" | "exitplanmode"
91        | "exit_plan_mode" => Some("PlanMode"),
92        "worktree" | "enterworktree" | "enter_worktree" | "exitworktree" | "exit_worktree" => {
93            Some("Worktree")
94        }
95        "lsp" => Some("LSP"),
96        "monitor" => Some("Monitor"),
97        "send_user_file" | "senduserfile" => Some("SendUserFile"),
98        "schedule_wakeup" | "schedulewakeup" => Some("ScheduleWakeup"),
99        "remote_trigger" | "remotetrigger" => Some("RemoteTrigger"),
100        "tool_search" | "toolsearch" => Some("ToolSearch"),
101        _ => None,
102    }
103}
104
105fn canonical_codex_tool(key: &str) -> Option<&'static str> {
106    match key {
107        "shell" | "bash" | "terminal" => Some("shell"),
108        "file_read" | "read" | "cat" => Some("file_read"),
109        "file_write" | "write" | "edit" => Some("file_write"),
110        "agent" | "subagent" => Some("agent"),
111        _ => None,
112    }
113}
114
115fn canonical_opencode_tool(key: &str) -> Option<&'static str> {
116    match key {
117        "bash" | "shell" | "terminal" => Some("bash"),
118        "read" | "cat" => Some("read"),
119        "write" => Some("write"),
120        "edit" => Some("edit"),
121        "agent" | "subagent" => Some("agent"),
122        "browser" | "web_search" | "websearch" => Some("browser"),
123        "fetch" | "web_fetch" | "webfetch" => Some("fetch"),
124        _ => None,
125    }
126}
127
128fn canonical_generic_tool(key: &str) -> Option<&'static str> {
129    match key {
130        "bash" => Some("Bash"),
131        "read" => Some("Read"),
132        "write" => Some("Write"),
133        "edit" => Some("Edit"),
134        "agent" => Some("Agent"),
135        _ => None,
136    }
137}