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) ⇒ String
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.
93 94 95 |
# File 'lib/brute/truncation.rb', line 93 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.
104 105 106 107 108 109 110 |
# File 'lib/brute/truncation.rb', line 104 def self.cleanup!(dir, retention_days: 7) return unless File.directory?(dir) cutoff = Time.now - (retention_days * 86400) Dir.glob(File.join(dir, "*")).each do |path| File.delete(path) if File.file?(path) && File.mtime(path) < cutoff end end |
.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) ⇒ String
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.
57 58 59 60 61 62 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 |
# File 'lib/brute/truncation.rb', line 57 def self.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) return text if text.nil? || text.empty? # Per-line truncation first — cap individual lines lines = text.lines.map { |line| truncate_line(line) } text = lines.join return text if lines.size <= max_lines && text.bytesize <= max_bytes # Determine how many lines we can keep within both caps kept = direction == :tail ? lines.last(max_lines) : lines.first(max_lines) # Enforce byte cap result_lines = [] bytes = 0 kept.each do |line| break if bytes + line.bytesize > max_bytes 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 |
.truncate_line(line, max: MAX_LINE_LENGTH) ⇒ Object
Truncate a single line if it exceeds MAX_LINE_LENGTH.
98 99 100 101 |
# File 'lib/brute/truncation.rb', line 98 def self.truncate_line(line, max: MAX_LINE_LENGTH) return line if line.length <= max line[0, max] + "... (line truncated to #{max} chars)\n" end |