Aggregate tool usage from condensed entries.
(entries: &[CondensedEntry])
| 137 | |
| 138 | /// Aggregate tool usage from condensed entries. |
| 139 | pub fn aggregate_tool_usage(entries: &[CondensedEntry]) -> Vec<ToolUseSummary> { |
| 140 | let mut tools: HashMap<String, ToolUseSummary> = HashMap::new(); |
| 141 | |
| 142 | for entry in entries.iter().filter(|e| e.is_tool()) { |
| 143 | let name = entry.tool_name.as_deref().unwrap_or("Unknown"); |
| 144 | |
| 145 | let summary = tools |
| 146 | .entry(name.to_string()) |
| 147 | .or_insert_with(|| ToolUseSummary::new(name, 0, Vec::new())); |
| 148 | |
| 149 | summary.invocation_count += 1; |
| 150 | |
| 151 | // Track files affected by file-modifying tools |
| 152 | if let Some(detail) = &entry.tool_detail { |
| 153 | if matches!(name, "Edit" | "Write" | "MultiEdit" | "NotebookEdit") |
| 154 | && !summary.files_affected.contains(detail) |
| 155 | { |
| 156 | summary.files_affected.push(detail.clone()); |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | let mut result: Vec<_> = tools.into_values().collect(); |
| 162 | result.sort_by(|a, b| a.tool_name.cmp(&b.tool_name)); |
| 163 | result |
| 164 | } |
| 165 | |
| 166 | /// Extract user content from a transcript message value. |
| 167 | fn extract_user_content(message: &serde_json::Value) -> Option<String> { |