Skip to main content

vtcode_config/constants/
output_limits.rs

1//! Output truncation limits inspired by OpenAI Codex multi-tier truncation.
2//! Prevents OOM with three independent size limits.
3//! Reference: <https://openai.com/index/unrolling-the-codex-agent-loop/>
4
5/// Maximum size for single agent message payloads (bytes) - 4 MB.
6pub const MAX_AGENT_MESSAGES_SIZE: usize = 4 * 1024 * 1024;
7
8/// Maximum size for entire message history payloads (bytes) - 24 MB.
9pub const MAX_ALL_MESSAGES_SIZE: usize = 24 * 1024 * 1024;
10
11/// Maximum retained error-log payload bytes in the in-memory collector (10 MiB).
12pub const ERROR_LOG_BUFFER_SIZE_LIMIT_BYTES: usize = 10 * 1024 * 1024;
13
14/// Maximum size per line (bytes) - 256 KB.
15/// Prevents OOM on malformed output with very long lines.
16pub const MAX_LINE_LENGTH: usize = 256 * 1024;
17
18/// Default message count limit for history.
19pub const DEFAULT_MESSAGE_LIMIT: usize = 4_000;
20
21/// Maximum message count limit.
22pub const MAX_MESSAGE_LIMIT: usize = 20_000;
23
24/// Maximum fallback preview size for one execution result (64 KiB).
25/// Historical names are retained for API compatibility; these are per-result
26/// limits, never an aggregate allowance. History compaction bounds accumulated
27/// context without disabling tools or hiding subsequent evidence.
28pub const TURN_PREVIEW_BUDGET_BYTES: usize = 64 * 1024;
29/// Maximum fallback preview size for one planning result (96 KiB).
30pub const TURN_PREVIEW_BUDGET_BYTES_PLANNING: usize = 96 * 1024;
31
32/// Effective per-result preview limit for the active workflow mode.
33#[inline]
34pub const fn turn_preview_budget_bytes(planning_active: bool) -> usize {
35    if planning_active {
36        TURN_PREVIEW_BUDGET_BYTES_PLANNING
37    } else {
38        TURN_PREVIEW_BUDGET_BYTES
39    }
40}
41
42/// Legacy small-preview threshold retained for API compatibility.
43/// Per-result limiting no longer needs a separate verifier reserve.
44pub const TINY_PREVIEW_BYPASS_BYTES: usize = 1024;
45
46/// Legacy verifier reserve retained for API compatibility; no longer enforced.
47pub const TURN_TINY_PREVIEW_BUDGET_BYTES: usize = 8 * 1024;
48
49/// Truncation marker appended when content is cut off.
50const TRUNCATION_MARKER: &str = "\n[... content truncated due to size limit ...]";
51
52/// Collect content with lazy truncation (Codex pattern).
53/// Marks truncated but continues draining to prevent pipe blocking.
54///
55/// # Arguments
56/// * `output` - Accumulated output buffer
57/// * `new_content` - New content to append
58/// * `max_size` - Maximum allowed size
59/// * `truncated` - Mutable flag tracking truncation state
60///
61/// # Returns
62/// `true` if content was appended, `false` if truncated
63#[inline]
64fn collect_with_truncation(output: &mut String, new_content: &str, max_size: usize, truncated: &mut bool) -> bool {
65    let new_size = output.len() + new_content.len();
66
67    if new_size > max_size {
68        if !*truncated {
69            output.push_str(TRUNCATION_MARKER);
70            *truncated = true;
71        }
72        return false;
73    }
74
75    output.push_str(new_content);
76    true
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_collect_within_limit() {
85        let mut output = String::new();
86        let mut truncated = false;
87
88        assert!(collect_with_truncation(&mut output, "hello", 100, &mut truncated));
89        assert_eq!(output, "hello");
90        assert!(!truncated);
91    }
92
93    #[test]
94    fn test_collect_at_limit_triggers_truncation() {
95        let mut output = String::from("hello");
96        let mut truncated = false;
97
98        assert!(!collect_with_truncation(&mut output, " world that exceeds", 10, &mut truncated));
99        assert!(output.contains(TRUNCATION_MARKER));
100        assert!(truncated);
101    }
102
103    #[test]
104    fn test_truncation_marker_appended_only_once() {
105        let mut output = String::new();
106        let mut truncated = false;
107
108        // Triggers initial truncation
109        collect_with_truncation(&mut output, "first content", 5, &mut truncated);
110        let len_after_marker = output.len();
111        assert!(truncated);
112
113        // Should not append marker again
114        collect_with_truncation(&mut output, "second content", 5, &mut truncated);
115        assert_eq!(output.len(), len_after_marker);
116    }
117
118    #[test]
119    fn turn_preview_budget_splits_by_workflow_mode() {
120        assert_eq!(turn_preview_budget_bytes(false), 64 * 1024);
121        assert_eq!(turn_preview_budget_bytes(true), 96 * 1024);
122    }
123}