mars_agents/build/
tool_normalize.rs1pub 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}