gglib_core/domain/agent/
tool_display.rs1pub fn strip_tool_prefix(name: &str) -> &str {
25 name.find(':').map_or(name, |pos| &name[pos + 1..])
26}
27
28pub fn format_tool_display_name(raw: &str) -> String {
44 raw.split(|c: char| c == '-' || c == '_' || c == '.' || c.is_whitespace())
45 .filter(|w| !w.is_empty())
46 .map(title_case_word)
47 .collect::<Vec<_>>()
48 .join(" ")
49}
50
51pub fn format_tool_args_summary(bare_name: &str, arguments: &serde_json::Value) -> Option<String> {
74 let obj = arguments.as_object()?;
75
76 match bare_name {
77 "read_file" => obj
78 .get("path")
79 .and_then(|v| v.as_str())
80 .map(|s| truncate(s, 60).to_string()),
81
82 "list_directory" => obj
83 .get("path")
84 .and_then(|v| v.as_str())
85 .map(|s| truncate(s, 60).to_string()),
86
87 "grep_search" => {
88 let pattern = obj.get("pattern").and_then(|v| v.as_str())?;
89 let path = obj.get("path").and_then(|v| v.as_str()).unwrap_or(".");
90 Some(format!(
91 "\"{}\" in {}",
92 truncate(pattern, 30),
93 truncate(path, 30)
94 ))
95 }
96
97 "get_current_time" => obj
98 .get("timezone")
99 .and_then(|v| v.as_str())
100 .map(std::string::ToString::to_string),
101
102 _ => obj
104 .values()
105 .find_map(|v| v.as_str())
106 .map(|s| truncate(s, 60).to_string()),
107 }
108}
109
110fn title_case_word(word: &str) -> String {
116 let mut chars = word.chars();
117 chars.next().map_or_else(String::new, |c| {
118 let upper: String = c.to_uppercase().collect();
119 upper + chars.as_str()
120 })
121}
122
123fn truncate(s: &str, max_len: usize) -> &str {
125 if s.len() <= max_len {
126 s
127 } else {
128 let end = s.floor_char_boundary(max_len);
130 &s[..end]
131 }
132}
133
134#[cfg(test)]
139mod tests {
140 use super::*;
141 use serde_json::json;
142
143 #[test]
146 fn strip_builtin_prefix() {
147 assert_eq!(strip_tool_prefix("builtin:read_file"), "read_file");
148 }
149
150 #[test]
151 fn strip_numeric_server_prefix() {
152 assert_eq!(strip_tool_prefix("3:some_tool"), "some_tool");
153 }
154
155 #[test]
156 fn strip_no_prefix() {
157 assert_eq!(strip_tool_prefix("plain_name"), "plain_name");
158 }
159
160 #[test]
163 fn display_name_underscore() {
164 assert_eq!(format_tool_display_name("read_file"), "Read File");
165 }
166
167 #[test]
168 fn display_name_hyphen() {
169 assert_eq!(format_tool_display_name("get-weather"), "Get Weather");
170 }
171
172 #[test]
173 fn display_name_dot() {
174 assert_eq!(format_tool_display_name("file.read"), "File Read");
175 }
176
177 #[test]
178 fn display_name_mixed_separators() {
179 assert_eq!(
180 format_tool_display_name("my-tool_name.here"),
181 "My Tool Name Here"
182 );
183 }
184
185 #[test]
186 fn display_name_consecutive_separators() {
187 assert_eq!(format_tool_display_name("a..b"), "A B");
188 assert_eq!(format_tool_display_name("a--b"), "A B");
189 }
190
191 #[test]
192 fn display_name_single_word() {
193 assert_eq!(format_tool_display_name("weather"), "Weather");
194 }
195
196 #[test]
197 fn display_name_already_title_case() {
198 assert_eq!(format_tool_display_name("Get Weather"), "Get Weather");
199 }
200
201 #[test]
204 fn args_summary_read_file() {
205 let args = json!({"path": "/src/main.rs"});
206 assert_eq!(
207 format_tool_args_summary("read_file", &args),
208 Some("/src/main.rs".into())
209 );
210 }
211
212 #[test]
213 fn args_summary_grep_search() {
214 let args = json!({"pattern": "TODO", "path": "/src"});
215 assert_eq!(
216 format_tool_args_summary("grep_search", &args),
217 Some("\"TODO\" in /src".into())
218 );
219 }
220
221 #[test]
222 fn args_summary_grep_search_no_path() {
223 let args = json!({"pattern": "TODO"});
224 assert_eq!(
225 format_tool_args_summary("grep_search", &args),
226 Some("\"TODO\" in .".into())
227 );
228 }
229
230 #[test]
231 fn args_summary_list_directory() {
232 let args = json!({"path": "/src"});
233 assert_eq!(
234 format_tool_args_summary("list_directory", &args),
235 Some("/src".into())
236 );
237 }
238
239 #[test]
240 fn args_summary_get_current_time() {
241 let args = json!({"timezone": "UTC"});
242 assert_eq!(
243 format_tool_args_summary("get_current_time", &args),
244 Some("UTC".into())
245 );
246 }
247
248 #[test]
249 fn args_summary_generic_fallback() {
250 let args = json!({"query": "rust async"});
251 assert_eq!(
252 format_tool_args_summary("custom_search", &args),
253 Some("rust async".into())
254 );
255 }
256
257 #[test]
258 fn args_summary_no_string_values() {
259 let args = json!({"count": 5});
260 assert_eq!(format_tool_args_summary("some_tool", &args), None);
261 }
262
263 #[test]
264 fn args_summary_null_args() {
265 assert_eq!(
266 format_tool_args_summary("some_tool", &serde_json::Value::Null),
267 None
268 );
269 }
270}