harn_vm/orchestration/compaction/
tool_output.rs1use super::{is_failure_signal_line, snap_to_line_end, snap_to_line_start};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct MicrocompactedToolOutput {
5 pub text: String,
6 pub dropped_bytes: usize,
8}
9
10pub fn microcompact_tool_output_result(output: &str, max_chars: usize) -> MicrocompactedToolOutput {
12 if output.len() <= max_chars || max_chars < 200 {
13 return MicrocompactedToolOutput {
14 text: output.to_string(),
15 dropped_bytes: 0,
16 };
17 }
18 let mut offset = 0;
19 let diagnostic_lines = output
20 .split_inclusive('\n')
21 .filter_map(|segment| {
22 let start = offset;
23 offset += segment.len();
24 let line = segment.strip_suffix('\n').unwrap_or(segment);
25 is_failure_signal_line(line).then_some((line, start..start + line.len()))
26 })
27 .take(32)
28 .collect::<Vec<_>>();
29 if !diagnostic_lines.is_empty() {
30 let diagnostics = diagnostic_lines
31 .iter()
32 .map(|(line, _)| *line)
33 .collect::<Vec<_>>()
34 .join("\n");
35 let budget = max_chars.saturating_sub(diagnostics.len() + 64);
36 let keep = budget / 2;
37 if keep >= 80 && output.len() > keep * 2 {
38 let head = snap_to_line_end(output, keep);
39 let tail = snap_to_line_start(output, output.len().saturating_sub(keep));
40 let retained = retained_interval_bytes(
41 output.len(),
42 std::iter::once(0..head.len())
43 .chain(diagnostic_lines.iter().map(|(_, range)| range.clone()))
44 .chain(std::iter::once(output.len() - tail.len()..output.len())),
45 );
46 return MicrocompactedToolOutput {
47 text: format!(
48 "{head}\n\n[diagnostic lines preserved]\n{diagnostics}\n\n[... output compacted ...]\n\n{tail}"
49 ),
50 dropped_bytes: output.len().saturating_sub(retained),
51 };
52 }
53 }
54 let keep = max_chars / 2;
55 let head = snap_to_line_end(output, keep);
56 let tail = snap_to_line_start(output, output.len().saturating_sub(keep));
57 let snipped = output.len().saturating_sub(head.len() + tail.len());
58 MicrocompactedToolOutput {
59 text: format!("{head}\n\n[... {snipped} characters snipped ...]\n\n{tail}"),
60 dropped_bytes: snipped,
61 }
62}
63
64pub fn microcompact_tool_output(output: &str, max_chars: usize) -> String {
67 microcompact_tool_output_result(output, max_chars).text
68}
69
70fn retained_interval_bytes(
71 source_len: usize,
72 intervals: impl IntoIterator<Item = std::ops::Range<usize>>,
73) -> usize {
74 let mut intervals = intervals
75 .into_iter()
76 .filter(|range| range.start < range.end)
77 .collect::<Vec<_>>();
78 intervals.sort_by_key(|range| range.start);
79 let mut retained = 0;
80 let mut covered_end = 0;
81 for range in intervals {
82 let start = range.start.min(source_len).max(covered_end);
83 let end = range.end.min(source_len);
84 if start < end {
85 retained += end - start;
86 covered_end = end;
87 }
88 }
89 retained
90}