opendev_runtime/
tool_summarizer.rs1pub fn summarize_tool_result(tool_name: &str, output: Option<&str>, error: Option<&str>) -> String {
12 if let Some(err) = error {
14 let truncated = if err.len() > 200 { &err[..200] } else { err };
15 return format!("Error: {truncated}");
16 }
17
18 let result_str = output.unwrap_or("");
19
20 if result_str.is_empty() {
21 return "Success (no output)".to_string();
22 }
23
24 match tool_name {
25 "read_file" | "Read" => {
27 let lines = result_str.lines().count();
28 let chars = result_str.len();
29 format!("Read file ({lines} lines, {chars} chars)")
30 }
31
32 "write_file" | "Write" => "File written successfully".to_string(),
34
35 "edit_file" | "Edit" => "File edited successfully".to_string(),
37
38 "delete_file" | "Delete" => "File deleted".to_string(),
40
41 "search" | "Grep" | "file_search" => {
43 if result_str.contains("No matches found") || result_str.trim().is_empty() {
44 "Search completed (0 matches)".to_string()
45 } else {
46 let match_count = result_str.lines().count();
47 format!("Search completed ({match_count} matches found)")
48 }
49 }
50
51 "list_files" | "list_directory" | "List" => {
53 let file_count = if result_str.is_empty() {
54 0
55 } else {
56 result_str.lines().count()
57 };
58 format!("Listed directory ({file_count} items)")
59 }
60
61 "run_command" | "Run" | "bash_execute" | "Bash" => {
63 let lines = result_str.lines().count();
64 if lines > 10 {
65 format!("Command executed ({lines} lines of output)")
66 } else if result_str.len() < 100 {
67 format!("Output: {}", &result_str[..result_str.len().min(100)])
68 } else {
69 "Command executed successfully".to_string()
70 }
71 }
72
73 "fetch_url" | "Fetch" | "web_fetch" | "web_search" => {
75 "Content fetched successfully".to_string()
76 }
77
78 "capture_screenshot" | "web_screenshot" | "analyze_image" => {
80 "Image processed successfully".to_string()
81 }
82
83 "git" => {
85 let lines = result_str.lines().count();
86 if lines > 10 {
87 format!("Git operation completed ({lines} lines)")
88 } else if result_str.len() < 100 {
89 format!("Output: {}", &result_str[..result_str.len().min(100)])
90 } else {
91 "Git operation completed".to_string()
92 }
93 }
94
95 "write_todos" => {
97 let count = result_str
98 .lines()
99 .filter(|l| {
100 let t = l.trim();
101 t.starts_with("[todo]") || t.starts_with("[doing]") || t.starts_with("[done]")
102 })
103 .count();
104 if count == 1 {
105 "Created 1 todo".to_string()
106 } else if count > 1 {
107 format!("Created {count} todos")
108 } else {
109 "Todos updated".to_string()
110 }
111 }
112 "update_todo" => "Todo updated".to_string(),
113 "complete_todo" => "Todo completed".to_string(),
114 "list_todos" => {
115 let count = result_str
116 .lines()
117 .filter(|l| {
118 let t = l.trim();
119 t.starts_with("[todo]") || t.starts_with("[doing]") || t.starts_with("[done]")
120 })
121 .count();
122 format!("{count} todos listed")
123 }
124 "clear_todos" => "All todos cleared".to_string(),
125
126 _ => {
128 if result_str.len() < 100 {
129 result_str.to_string()
130 } else {
131 let chars = result_str.len();
132 let lines = result_str.lines().count();
133 format!("Success ({lines} lines, {chars} chars)")
134 }
135 }
136 }
137}
138
139pub fn safe_truncate(s: &str, max_bytes: usize) -> &str {
141 if s.len() <= max_bytes {
142 return s;
143 }
144 let mut end = max_bytes;
146 while end > 0 && !s.is_char_boundary(end) {
147 end -= 1;
148 }
149 &s[..end]
150}
151
152pub fn build_background_result(
158 content: &str,
159 messages: &[serde_json::Value],
160 total_budget: usize,
161) -> String {
162 let mut result = String::new();
163
164 let content_cap = total_budget * 2 / 3;
166 let trimmed = safe_truncate(content, content_cap);
167 result.push_str(trimmed);
168 if content.len() > content_cap {
169 result.push_str("... [truncated]");
170 }
171
172 if content.len() < 500 {
174 let subagent_outputs: Vec<&str> = messages
175 .iter()
176 .filter_map(|m| {
177 let role = m.get("role")?.as_str()?;
178 let name = m.get("name")?.as_str()?;
179 let text = m.get("content")?.as_str()?;
180 if role == "tool" && name == "spawn_subagent" && !text.is_empty() {
181 Some(text)
182 } else {
183 None
184 }
185 })
186 .collect();
187
188 if !subagent_outputs.is_empty() {
189 let remaining = total_budget.saturating_sub(result.len() + 100);
190 let per_agent = remaining / subagent_outputs.len();
191
192 result.push_str("\n\n## Subagent Outputs\n");
193 for (i, output) in subagent_outputs.iter().enumerate() {
194 result.push_str(&format!("\n### Subagent {}\n", i + 1));
195 let trimmed = safe_truncate(output, per_agent);
196 result.push_str(trimmed);
197 if output.len() > per_agent {
198 result.push_str("... [truncated]");
199 }
200 result.push('\n');
201 }
202 }
203 }
204
205 if result.len() > total_budget {
207 let end = safe_truncate(&result, total_budget).len();
208 result.truncate(end);
209 result.push_str("... [truncated]");
210 }
211 result
212}
213
214#[cfg(test)]
215#[path = "tool_summarizer_tests.rs"]
216mod tests;