Module: Brute::Truncation
- Defined in:
- lib/brute/truncation.rb
Overview
Universal tool output truncation.
Every tool result passes through Truncation.truncate() before entering the LLM context. This is the primary guard against context window explosion — even if a tool has no internal limits, this module caps the output to a safe size.
Existing features (ref: opencode truncate.ts):
- Line + byte dual cap — truncate when output exceeds MAX_LINES (2000) or MAX_BYTES (50 KB), whichever is hit first.
- Head mode (default) — keep the first N lines / bytes. Used for most tool output where the beginning is most relevant.
- Tail mode — keep the last N lines / bytes. Used for shell output where errors and summaries appear at the end.
- Overflow to disk — when truncating, write the full text to a file under TRUNCATION_DIR (e.g. ~/.local/share/brute/tool-output/). Return a preview + hint pointing to the saved file.
- Hint message — when truncated, append a contextual hint: "Full output saved to: (path). Use Read with offset/limit to view specific sections."
- Configurable limits — allow overriding MAX_LINES / MAX_BYTES via per-call options.
- Retention cleanup — purge saved output files older than a configurable retention period from a truncation directory.
- Per-line truncation — truncate individual lines longer than MAX_LINE_LENGTH (2000 chars) with a suffix.
Constant Summary collapse
- MAX_LINES =
2000- MAX_BYTES =
50 KB
50 * 1024
- MAX_LINE_LENGTH =
2000- TRUNCATION_MARKER =
"[Output truncated:"- TRUNCATION_DIR =
File.join( Dir.home, ".local", "share", "brute", "tool-output", )
Class Method Summary collapse
-
.already_truncated?(text) ⇒ Boolean
Check whether text already contains a truncation marker.
-
.cleanup!(dir, retention_days: 7) ⇒ Object
Purge files older than retention_days from the given directory.
-
.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) ⇒ Object
Truncate text to fit within line and byte limits.
-
.truncate_line(line, max: MAX_LINE_LENGTH) ⇒ Object
Truncate a single line if it exceeds MAX_LINE_LENGTH.
Class Method Details
.already_truncated?(text) ⇒ Boolean
Check whether text already contains a truncation marker.
109 110 111 |
# File 'lib/brute/truncation.rb', line 109 def self.already_truncated?(text) text.include?(TRUNCATION_MARKER) end |
.cleanup!(dir, retention_days: 7) ⇒ Object
Purge files older than retention_days from the given directory.
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/brute/truncation.rb', line 123 def self.cleanup!(dir, retention_days: 7) if File.directory?(dir) cutoff = Time.now - (retention_days * 86400) Dir.glob(File.join(dir, "*")).each do |path| if File.file?(path) && File.mtime(path) < cutoff File.delete(path) end end else nil end end |
.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) ⇒ Object
Truncate text to fit within line and byte limits.
Returns the text unchanged if it fits. Otherwise returns a truncated preview with a hint message.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/brute/truncation.rb', line 63 def self.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) if text.nil? || text.empty? text else # Per-line truncation first — cap individual lines lines = text.lines.map { |line| truncate_line(line) } text = lines.join if lines.size <= max_lines && text.bytesize <= max_bytes text else # Determine how many lines we can keep within both caps if direction == :tail kept = lines.last(max_lines) else kept = lines.first(max_lines) end # Enforce byte cap result_lines = [] bytes = 0 kept.each do |line| if bytes + line.bytesize > max_bytes break end result_lines << line bytes += line.bytesize end result = result_lines.join total = lines.size shown = result_lines.size # Overflow to disk — save the full output so it can be inspected later saved_path = save_to_disk(text, truncation_dir) hint = "\n#{TRUNCATION_MARKER} showing #{shown} of #{total} lines]" if saved_path hint += "\nFull output saved to: #{saved_path}. Use Read with offset/limit to view specific sections." end result + hint end end end |
.truncate_line(line, max: MAX_LINE_LENGTH) ⇒ Object
Truncate a single line if it exceeds MAX_LINE_LENGTH.
114 115 116 117 118 119 120 |
# File 'lib/brute/truncation.rb', line 114 def self.truncate_line(line, max: MAX_LINE_LENGTH) if line.length <= max line else line[0, max] + "... (line truncated to #{max} chars)\n" end end |