Skip to main content

j_agent/tools/
classification.rs

1use crate::constants::{
2    CLASSIFY_SIZE_THRESHOLD_BYTES, CLASSIFY_SIZE_THRESHOLD_CHARS, CLASSIFY_TITLE_TRUNCATE_LEN,
3    CLASSIFY_TRUNCATE_LEN, HOOK_LOG_DESC_MAX_LEN,
4};
5
6use super::tool_names;
7
8/// 工具类型分类
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum ToolCategory {
11    /// 文件操作类 (Read, Write, Edit, Glob)
12    File,
13    /// 搜索类 (Grep)
14    Search,
15    /// 执行类 (Bash, Task, TaskOutput)
16    Execute,
17    /// 网络类 (WebFetch, WebSearch)
18    Network,
19    /// 计划类 (EnterPlanMode, ExitPlanMode)
20    Plan,
21    /// 代理类 (Agent)
22    Agent,
23    /// 协作者类 (Teammate)
24    Teammate,
25    /// 压缩类 (Compact)
26    Compact,
27    /// 发送消息 (SendMessage)
28    SendMessage,
29    /// 忽略消息 (IgnoreMessage)
30    IgnoreMessage,
31    /// 工作完成 (WorkDone)
32    WorkDone,
33    /// 其他类
34    Other,
35}
36
37impl ToolCategory {
38    /// 根据工具名称判断分类
39    pub fn from_name(name: &str) -> Self {
40        match name {
41            tool_names::READ | tool_names::WRITE | tool_names::EDIT | tool_names::GLOB => {
42                Self::File
43            }
44            tool_names::GREP => Self::Search,
45            tool_names::SHELL | tool_names::TASK | tool_names::TASK_OUTPUT => Self::Execute,
46            tool_names::WEB_FETCH | tool_names::WEB_SEARCH | tool_names::BROWSER => Self::Network,
47            tool_names::ENTER_PLAN_MODE | tool_names::EXIT_PLAN_MODE => Self::Plan,
48            tool_names::AGENT => Self::Agent,
49            tool_names::TEAMMATE => Self::Teammate,
50            tool_names::COMPACT => Self::Compact,
51            tool_names::SEND_MESSAGE => Self::SendMessage,
52            tool_names::IGNORE_MESSAGE => Self::IgnoreMessage,
53            tool_names::WORK_DONE => Self::WorkDone,
54            _ => Self::Other,
55        }
56    }
57
58    /// 获取工具图标
59    pub fn icon(&self) -> &'static str {
60        match self {
61            Self::File => "📄",
62            Self::Search => "🔍",
63            Self::Execute => "⚡",
64            Self::Network => "🌐",
65            Self::Plan => "📋",
66            Self::Agent => "🤖",
67            Self::Teammate => "👥",
68            Self::Compact => "📦",
69            Self::SendMessage => "✉️",
70            Self::IgnoreMessage => "💤",
71            Self::WorkDone => "🚩",
72            Self::Other => "🔧",
73        }
74    }
75}
76
77/// 工具执行状态
78#[derive(Debug, Clone, Copy, PartialEq)]
79pub enum ToolStatus {
80    /// 成功完成
81    Success,
82    /// 失败
83    Failed,
84}
85
86impl ToolStatus {
87    /// 状态图标
88    pub fn icon(&self) -> &'static str {
89        match self {
90            Self::Success => "✓",
91            Self::Failed => "✗",
92        }
93    }
94}
95
96/// 格式化 JSON 值为简短显示
97pub fn format_json_value(value: &serde_json::Value) -> String {
98    match value {
99        serde_json::Value::String(s) => {
100            // 使用字符数而不是字节数来截断,避免 UTF-8 边界问题
101            let char_count = s.chars().count();
102            if char_count > CLASSIFY_TRUNCATE_LEN {
103                let truncated: String = s.chars().take(CLASSIFY_TRUNCATE_LEN - 3).collect();
104                format!("\"{}...\"", truncated)
105            } else {
106                format!("\"{}\"", s)
107            }
108        }
109        serde_json::Value::Number(n) => n.to_string(),
110        serde_json::Value::Bool(b) => b.to_string(),
111        serde_json::Value::Null => "null".to_string(),
112        serde_json::Value::Array(arr) => {
113            if arr.is_empty() {
114                "[]".to_string()
115            } else {
116                format!("[{} items]", arr.len())
117            }
118        }
119        serde_json::Value::Object(obj) => {
120            if obj.is_empty() {
121                "{}".to_string()
122            } else {
123                let keys: Vec<&str> = obj.keys().take(3).map(|s| s.as_str()).collect();
124                format!("{{{}}}", keys.join(", "))
125            }
126        }
127    }
128}
129
130/// 获取工具特性化结果摘要
131pub fn get_result_summary_for_tool(
132    content: &str,
133    is_error: bool,
134    tool_name: &str,
135    tool_args: Option<&str>,
136) -> String {
137    if is_error {
138        return "失败".to_string();
139    }
140
141    if content.is_empty() {
142        return "无输出".to_string();
143    }
144
145    // 工具特性化摘要
146    match tool_name {
147        tool_names::READ => get_read_summary(content, tool_args),
148        tool_names::WRITE => get_write_summary(content, tool_args),
149        tool_names::EDIT => get_edit_summary(content, tool_args),
150        tool_names::SHELL => get_bash_summary(content, tool_args),
151        tool_names::GLOB => get_glob_summary(content, tool_args),
152        tool_names::GREP => get_grep_summary(content, tool_args),
153        tool_names::WEB_FETCH => get_web_fetch_summary(content, tool_args),
154        tool_names::WEB_SEARCH => get_web_search_summary(content, tool_args),
155        tool_names::BROWSER => get_browser_summary(content, tool_args),
156        tool_names::ASK => "用户已回答".to_string(),
157        tool_names::TASK_OUTPUT => get_task_output_result_summary(content, tool_args),
158        tool_names::TODO_WRITE => get_todo_write_summary(content, tool_args),
159        tool_names::TODO_READ => get_todo_read_summary(content),
160        tool_names::TASK => get_task_summary(content, tool_args),
161        tool_names::AGENT => get_agent_summary(content, tool_args),
162        tool_names::TEAMMATE => get_teammate_summary(content, tool_args),
163        tool_names::COMPACT => get_compact_summary(content),
164        tool_names::REGISTER_HOOK => "钩子已注册".to_string(),
165        tool_names::LOAD_SKILL => get_load_skill_result_summary(tool_args),
166        tool_names::SEND_MESSAGE => "消息已发送".to_string(),
167        tool_names::WORK_DONE => get_work_done_result_summary(tool_args),
168        tool_names::ENTER_PLAN_MODE | tool_names::EXIT_PLAN_MODE => {
169            get_plan_result_summary(tool_name)
170        }
171        tool_names::ENTER_WORKTREE | tool_names::EXIT_WORKTREE => {
172            get_worktree_result_summary(tool_name)
173        }
174        tool_names::LOAD_TOOL => get_load_tool_result_summary(tool_args),
175        tool_names::SESSION => "会话操作完成".to_string(),
176        tool_names::IGNORE_MESSAGE => "消息已忽略".to_string(),
177        #[cfg(target_os = "macos")]
178        tool_names::COMPUTER_USE => get_computer_use_result_summary(tool_args),
179        _ => get_generic_summary(content),
180    }
181}
182
183/// Read 工具摘要:显示文件路径和行数
184fn get_read_summary(content: &str, tool_args: Option<&str>) -> String {
185    let lines = content.lines().count();
186    let file_path = tool_args
187        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
188        .and_then(|v| {
189            v.get("file_path")
190                .and_then(|p| p.as_str().map(|s| s.to_string()))
191        });
192
193    if let Some(path) = file_path {
194        // 只取文件名部分,避免过长
195        let short = short_path(&path, 40);
196        format!("{} ({} 行)", short, lines)
197    } else {
198        format!("{} 行", lines)
199    }
200}
201
202/// Bash 工具摘要:显示命令预览
203fn get_bash_summary(content: &str, tool_args: Option<&str>) -> String {
204    let command = tool_args
205        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
206        .and_then(|v| {
207            v.get("command")
208                .and_then(|c| c.as_str().map(|s| s.to_string()))
209        });
210
211    let lines = content.lines().count();
212    let line_info = if lines > 1 {
213        format!(" ({} 行输出)", lines)
214    } else {
215        String::new()
216    };
217
218    if let Some(cmd) = command {
219        // 截取命令的第一行前 50 字符
220        let first_line = cmd.lines().next().unwrap_or(&cmd);
221        let short_cmd: String = first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
222        let suffix = if first_line.chars().count() > CLASSIFY_TRUNCATE_LEN {
223            "…"
224        } else {
225            ""
226        };
227        format!("{}{}{}", short_cmd, suffix, line_info)
228    } else {
229        format!("完成{}", line_info)
230    }
231}
232
233/// TodoWrite 工具摘要:显示操作描述
234fn get_todo_write_summary(_content: &str, tool_args: Option<&str>) -> String {
235    tool_args
236        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
237        .map(|v| {
238            let is_merge = v.get("merge").and_then(|m| m.as_bool()).unwrap_or(false);
239            let count = v
240                .get("todos")
241                .and_then(|t| t.as_array())
242                .map(|a| a.len())
243                .unwrap_or(0);
244            if is_merge {
245                format!("更新 {} 项待办", count)
246            } else {
247                format!("写入 {} 项待办", count)
248            }
249        })
250        .unwrap_or_else(|| "写入待办".to_string())
251}
252
253/// TodoRead 工具摘要:显示读取数量
254fn get_todo_read_summary(content: &str) -> String {
255    if let Ok(items) = serde_json::from_str::<Vec<serde_json::Value>>(content) {
256        format!("读取 {} 项待办", items.len())
257    } else {
258        get_generic_summary(content)
259    }
260}
261
262/// Task 工具摘要
263fn get_task_summary(content: &str, tool_args: Option<&str>) -> String {
264    let parsed = tool_args.and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok());
265
266    if let Some(ref v) = parsed {
267        let action = v.get("action").and_then(|a| a.as_str()).unwrap_or("");
268        match action {
269            "create" => {
270                let title = v
271                    .get("title")
272                    .and_then(|t| t.as_str())
273                    .unwrap_or("untitled");
274                let short: String = title.chars().take(CLASSIFY_TITLE_TRUNCATE_LEN).collect();
275                format!("create: \"{}\"", short)
276            }
277            "list" => {
278                // 从 content 中尝试统计任务数
279                let count = content.lines().filter(|l| l.contains("\"id\"")).count();
280                if count > 0 {
281                    format!("list: {} 项任务", count)
282                } else {
283                    "list".to_string()
284                }
285            }
286            "get" => {
287                let task_id = v
288                    .get("taskId")
289                    .and_then(|t| t.as_u64())
290                    .map(|id| format!("#{}", id))
291                    .unwrap_or_default();
292                format!("get {}", task_id)
293            }
294            "update" => {
295                let task_id = v
296                    .get("taskId")
297                    .and_then(|t| t.as_u64())
298                    .map(|id| format!("#{}", id))
299                    .unwrap_or_default();
300                let status = v.get("status").and_then(|s| s.as_str()).unwrap_or("");
301                if !status.is_empty() {
302                    format!("update {} -> {}", task_id, status)
303                } else {
304                    format!("update {}", task_id)
305                }
306            }
307            _ => get_generic_summary(content),
308        }
309    } else {
310        get_generic_summary(content)
311    }
312}
313
314/// 通用摘要(原有逻辑)
315/// Agent 工具摘要:提取 description + 首行输出
316fn get_agent_summary(content: &str, tool_args: Option<&str>) -> String {
317    let lines = content.lines().count();
318    let desc = tool_args
319        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
320        .and_then(|v| {
321            v.get("description")
322                .and_then(|d| d.as_str().map(|s| s.to_string()))
323        });
324
325    // 首行非空内容作为摘要
326    let first_line = content.lines().find(|l| !l.trim().is_empty()).unwrap_or("");
327
328    if let Some(d) = desc {
329        let max_d: String = d.chars().take(20).collect();
330        if first_line.is_empty() {
331            max_d
332        } else {
333            let max_f: String = first_line.chars().take(40).collect();
334            format!("{}: {}", max_d, max_f)
335        }
336    } else if first_line.is_empty() {
337        format!("{} 行", lines)
338    } else {
339        let max_f: String = first_line.chars().take(50).collect();
340        max_f
341    }
342}
343
344/// Teammate 工具摘要:提取 name + 首行输出
345fn get_teammate_summary(content: &str, tool_args: Option<&str>) -> String {
346    let name = tool_args
347        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
348        .and_then(|v| {
349            v.get("name")
350                .and_then(|n| n.as_str().map(|s| s.to_string()))
351        });
352
353    let first_line = content.lines().find(|l| !l.trim().is_empty()).unwrap_or("");
354
355    if let Some(n) = name {
356        if first_line.is_empty() {
357            n
358        } else {
359            let max_f: String = first_line.chars().take(40).collect();
360            format!("{}: {}", n, max_f)
361        }
362    } else if first_line.is_empty() {
363        "完成".to_string()
364    } else {
365        let max_f: String = first_line.chars().take(50).collect();
366        max_f
367    }
368}
369
370/// Compact 工具摘要:提取压缩信息
371fn get_compact_summary(content: &str) -> String {
372    // 内容格式: "📦 上下文已压缩 (N 条消息 → 摘要, transcript: path)"
373    // 直接取第一行作为摘要
374    content
375        .lines()
376        .next()
377        .map(|l| {
378            let chars: String = l.chars().take(HOOK_LOG_DESC_MAX_LEN).collect();
379            chars
380        })
381        .unwrap_or_else(|| "压缩完成".to_string())
382}
383
384fn get_generic_summary(content: &str) -> String {
385    let lines = content.lines().count();
386    let chars = content.chars().count();
387
388    if lines > 1 {
389        if chars > CLASSIFY_SIZE_THRESHOLD_BYTES {
390            format!("{} 行, {:.1}KB", lines, chars as f64 / 1024.0)
391        } else {
392            format!("{} 行, {} 字符", lines, chars)
393        }
394    } else if chars > CLASSIFY_SIZE_THRESHOLD_CHARS {
395        format!("{:.1}KB", chars as f64 / 1024.0)
396    } else {
397        format!("{} 字符", chars)
398    }
399}
400
401/// 截断路径,保留文件名和部分目录
402fn short_path(path: &str, max_len: usize) -> String {
403    if path.chars().count() <= max_len {
404        return path.to_string();
405    }
406    // 取最后几个路径段
407    let parts: Vec<&str> = path.split('/').collect();
408    if parts.len() <= 2 {
409        let truncated: String = path.chars().take(max_len.saturating_sub(1)).collect();
410        return format!("{}…", truncated);
411    }
412    // 保留最后 2-3 段
413    let mut result = String::new();
414    for i in (0..parts.len()).rev() {
415        let candidate = parts[i..].join("/");
416        if candidate.chars().count() + 2 > max_len {
417            break;
418        }
419        result = candidate;
420    }
421    if result.is_empty() {
422        result = parts.last().unwrap_or(&"").to_string();
423    }
424    format!("…/{}", result)
425}
426
427/// Write 工具摘要:显示文件路径和字符数
428fn get_write_summary(content: &str, tool_args: Option<&str>) -> String {
429    let file_path = tool_args
430        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
431        .and_then(|v| {
432            v.get("path")
433                .and_then(|p| p.as_str().map(|s| s.to_string()))
434        });
435
436    if let Some(path) = file_path {
437        let short = short_path(&path, 40);
438        // content 通常是工具的返回信息,如 "文件已写入" 等
439        let first_line = content.lines().next().unwrap_or("");
440        if first_line.is_empty() {
441            format!("写入 {}", short)
442        } else {
443            let truncated: String = first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
444            format!("写入 {}: {}", short, truncated)
445        }
446    } else {
447        get_generic_summary(content)
448    }
449}
450
451/// Edit 工具摘要:显示文件路径和编辑结果
452fn get_edit_summary(content: &str, tool_args: Option<&str>) -> String {
453    let file_path = tool_args
454        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
455        .and_then(|v| {
456            v.get("path")
457                .and_then(|p| p.as_str().map(|s| s.to_string()))
458        });
459
460    if let Some(path) = file_path {
461        let short = short_path(&path, 40);
462        let first_line = content.lines().next().unwrap_or("");
463        if first_line.is_empty() {
464            format!("编辑 {}", short)
465        } else {
466            let truncated: String = first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
467            format!("编辑 {}: {}", short, truncated)
468        }
469    } else {
470        get_generic_summary(content)
471    }
472}
473
474/// Glob 工具摘要:显示搜索模式和匹配数量
475fn get_glob_summary(content: &str, tool_args: Option<&str>) -> String {
476    let pattern = tool_args
477        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
478        .and_then(|v| {
479            v.get("pattern")
480                .and_then(|p| p.as_str().map(|s| s.to_string()))
481        });
482
483    // 统计匹配的文件数(每行一个文件路径)
484    let match_count = content.lines().filter(|l| !l.trim().is_empty()).count();
485
486    if let Some(pat) = pattern {
487        let short: String = pat.chars().take(30).collect();
488        let suffix = if pat.chars().count() > 30 { "…" } else { "" };
489        format!("{}{} → {} 个匹配", short, suffix, match_count)
490    } else {
491        format!("{} 个匹配", match_count)
492    }
493}
494
495/// Grep 工具摘要:显示搜索模式和匹配数量
496fn get_grep_summary(content: &str, tool_args: Option<&str>) -> String {
497    let pattern = tool_args
498        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
499        .and_then(|v| {
500            v.get("pattern")
501                .and_then(|p| p.as_str().map(|s| s.to_string()))
502        });
503
504    let lines = content.lines().count();
505
506    if let Some(pat) = pattern {
507        let short: String = pat.chars().take(30).collect();
508        let suffix = if pat.chars().count() > 30 { "…" } else { "" };
509        if lines > 1 {
510            format!("{}{} → {} 行匹配", short, suffix, lines)
511        } else {
512            format!("{}{}", short, suffix)
513        }
514    } else {
515        get_generic_summary(content)
516    }
517}
518
519/// WebFetch 工具摘要:显示 URL
520fn get_web_fetch_summary(content: &str, tool_args: Option<&str>) -> String {
521    let url = tool_args
522        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
523        .and_then(|v| v.get("url").and_then(|u| u.as_str().map(|s| s.to_string())));
524
525    let lines = content.lines().count();
526
527    if let Some(u) = url {
528        let short: String = u.chars().take(50).collect();
529        let suffix = if u.chars().count() > 50 { "…" } else { "" };
530        if lines > 1 {
531            format!("{}{} ({} 行)", short, suffix, lines)
532        } else {
533            format!("{}{}", short, suffix)
534        }
535    } else {
536        get_generic_summary(content)
537    }
538}
539
540/// WebSearch 工具摘要:显示搜索关键词和结果数量
541fn get_web_search_summary(content: &str, tool_args: Option<&str>) -> String {
542    let query = tool_args
543        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
544        .and_then(|v| {
545            v.get("query")
546                .and_then(|q| q.as_str().map(|s| s.to_string()))
547        });
548
549    // 尝试统计结果数量(通常每条结果包含 URL)
550    let result_count = content.lines().filter(|l| l.contains("http")).count();
551
552    if let Some(q) = query {
553        let short: String = q.chars().take(30).collect();
554        let suffix = if q.chars().count() > 30 { "…" } else { "" };
555        if result_count > 0 {
556            format!("{}{} → {} 条结果", short, suffix, result_count)
557        } else {
558            format!("{}{}", short, suffix)
559        }
560    } else {
561        get_generic_summary(content)
562    }
563}
564
565/// Browser 工具摘要:显示操作和 URL
566fn get_browser_summary(content: &str, tool_args: Option<&str>) -> String {
567    let parsed = tool_args.and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok());
568
569    if let Some(ref v) = parsed {
570        let action = v.get("action").and_then(|a| a.as_str()).unwrap_or("");
571        let url = v.get("url").and_then(|u| u.as_str());
572
573        match action {
574            "open" | "navigate" => {
575                if let Some(u) = url {
576                    let short: String = u.chars().take(40).collect();
577                    let suffix = if u.chars().count() > 40 { "…" } else { "" };
578                    format!("{}: {}{}", action, short, suffix)
579                } else {
580                    format!("{}: {}", action, get_generic_summary(content))
581                }
582            }
583            "screenshot" | "snapshot" | "content" => {
584                let first_line = content.lines().next().unwrap_or("");
585                if first_line.is_empty() {
586                    action.to_string()
587                } else {
588                    let truncated: String =
589                        first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
590                    format!("{}: {}", action, truncated)
591                }
592            }
593            _ => {
594                let first_line = content.lines().next().unwrap_or("");
595                if first_line.is_empty() {
596                    action.to_string()
597                } else {
598                    let truncated: String =
599                        first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
600                    truncated
601                }
602            }
603        }
604    } else {
605        get_generic_summary(content)
606    }
607}
608
609/// TaskOutput 工具摘要:显示 task_id
610fn get_task_output_result_summary(content: &str, tool_args: Option<&str>) -> String {
611    let task_id = tool_args
612        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
613        .and_then(|v| {
614            v.get("task_id")
615                .and_then(|t| t.as_str().map(|s| s.to_string()))
616        });
617
618    let first_line = content.lines().find(|l| !l.trim().is_empty()).unwrap_or("");
619
620    if let Some(id) = task_id {
621        if first_line.is_empty() {
622            format!("获取任务 {} 输出", id)
623        } else {
624            let truncated: String = first_line.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
625            format!("任务 {}: {}", id, truncated)
626        }
627    } else {
628        get_generic_summary(content)
629    }
630}
631
632/// LoadSkill 工具摘要:显示技能名
633fn get_load_skill_result_summary(tool_args: Option<&str>) -> String {
634    let name = tool_args
635        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
636        .and_then(|v| {
637            v.get("name")
638                .and_then(|n| n.as_str().map(|s| s.to_string()))
639        });
640
641    if let Some(n) = name {
642        format!("技能已加载: {}", n)
643    } else {
644        "技能已加载".to_string()
645    }
646}
647
648/// WorkDone 工具摘要:显示摘要信息
649fn get_work_done_result_summary(tool_args: Option<&str>) -> String {
650    tool_args
651        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
652        .and_then(|v| {
653            v.get("summary")
654                .and_then(|s| s.as_str().map(|s| s.to_string()))
655        })
656        .map(|s| {
657            let truncated: String = s.chars().take(CLASSIFY_TRUNCATE_LEN).collect();
658            format!("完成: {}", truncated)
659        })
660        .unwrap_or_else(|| "工作完成".to_string())
661}
662
663/// Plan 工具摘要:进入/退出计划模式
664fn get_plan_result_summary(tool_name: &str) -> String {
665    if tool_name == tool_names::ENTER_PLAN_MODE {
666        "进入计划模式".to_string()
667    } else {
668        "退出计划模式".to_string()
669    }
670}
671
672/// Worktree 工具摘要:进入/退出工作树
673fn get_worktree_result_summary(tool_name: &str) -> String {
674    if tool_name == tool_names::ENTER_WORKTREE {
675        "进入工作树".to_string()
676    } else {
677        "退出工作树".to_string()
678    }
679}
680
681/// LoadTool 工具摘要:显示工具名
682fn get_load_tool_result_summary(tool_args: Option<&str>) -> String {
683    let name = tool_args
684        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
685        .and_then(|v| {
686            v.get("name")
687                .and_then(|n| n.as_str().map(|s| s.to_string()))
688        });
689
690    if let Some(n) = name {
691        format!("工具已加载: {}", n)
692    } else {
693        "工具已加载".to_string()
694    }
695}
696
697/// ComputerUse 工具摘要:显示操作类型
698#[cfg(target_os = "macos")]
699fn get_computer_use_result_summary(tool_args: Option<&str>) -> String {
700    let action = tool_args
701        .and_then(|args| serde_json::from_str::<serde_json::Value>(args).ok())
702        .and_then(|v| {
703            v.get("action")
704                .and_then(|a| a.as_str().map(|s| s.to_string()))
705        });
706
707    if let Some(a) = action {
708        format!("计算机操作: {}", a)
709    } else {
710        "计算机操作完成".to_string()
711    }
712}