1use crate::ndjson::NdjsonResult;
2use crate::types::RunnerMode;
3
4#[derive(Debug, Clone, Default)]
6pub struct WslOptions {
7 pub distro: Option<String>,
9 pub claude_path: Option<String>,
11}
12
13#[derive(Debug, Clone)]
15pub struct BufferConfig {
16 pub stdout_cap_bytes: usize,
18 pub stderr_cap_bytes: usize,
20 #[allow(dead_code)] pub stderr_receipt_cap_bytes: usize,
23}
24
25impl Default for BufferConfig {
26 fn default() -> Self {
27 Self {
28 stdout_cap_bytes: 2 * 1024 * 1024, stderr_cap_bytes: 256 * 1024, stderr_receipt_cap_bytes: 2048, }
32 }
33}
34
35#[derive(Debug)]
37pub struct ClaudeResponse {
38 pub stdout: String,
40 pub stderr: String,
42 pub exit_code: i32,
44 pub runner_used: RunnerMode,
46 pub runner_distro: Option<String>,
48 pub timed_out: bool,
50 pub ndjson_result: NdjsonResult,
52 #[allow(dead_code)] pub stdout_truncated: bool,
55 #[allow(dead_code)] pub stderr_truncated: bool,
58 #[allow(dead_code)] pub stdout_total_bytes: usize,
61 #[allow(dead_code)] pub stderr_total_bytes: usize,
64}
65
66impl ClaudeResponse {
67 #[must_use]
72 #[allow(dead_code)] pub fn stderr_for_receipt(&self, max_bytes: usize) -> String {
74 if self.stderr.len() <= max_bytes {
75 self.stderr.clone()
76 } else {
77 let bytes = self.stderr.as_bytes();
79 let start = bytes.len().saturating_sub(max_bytes);
80 String::from_utf8_lossy(&bytes[start..]).to_string()
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::{BufferConfig, ClaudeResponse, WslOptions};
88 use crate::ndjson::NdjsonResult;
89 use crate::types::RunnerMode;
90
91 #[test]
92 fn test_wsl_options_default() {
93 let options = WslOptions::default();
94 assert!(options.distro.is_none());
95 assert!(options.claude_path.is_none());
96 }
97
98 #[test]
101 fn test_buffer_config_default() {
102 let config = BufferConfig::default();
103 assert_eq!(config.stdout_cap_bytes, 2 * 1024 * 1024); assert_eq!(config.stderr_cap_bytes, 256 * 1024); assert_eq!(config.stderr_receipt_cap_bytes, 2048); }
107
108 #[test]
109 fn test_buffer_config_custom() {
110 let config = BufferConfig {
111 stdout_cap_bytes: 1024,
112 stderr_cap_bytes: 512,
113 stderr_receipt_cap_bytes: 256,
114 };
115 assert_eq!(config.stdout_cap_bytes, 1024);
116 assert_eq!(config.stderr_cap_bytes, 512);
117 assert_eq!(config.stderr_receipt_cap_bytes, 256);
118 }
119
120 #[test]
121 fn test_claude_response_stderr_for_receipt_no_truncation() {
122 let response = ClaudeResponse {
123 stdout: String::new(),
124 stderr: "Short error message".to_string(),
125 exit_code: 0,
126 runner_used: RunnerMode::Native,
127 runner_distro: None,
128 timed_out: false,
129 ndjson_result: NdjsonResult::NoValidJson {
130 tail_excerpt: String::new(),
131 },
132 stdout_truncated: false,
133 stderr_truncated: false,
134 stdout_total_bytes: 0,
135 stderr_total_bytes: 20,
136 };
137
138 let stderr_receipt = response.stderr_for_receipt(2048);
139 assert_eq!(stderr_receipt, "Short error message");
140 }
141
142 #[test]
143 fn test_claude_response_stderr_for_receipt_with_truncation() {
144 let long_stderr = "x".repeat(3000);
145 let response = ClaudeResponse {
146 stdout: String::new(),
147 stderr: long_stderr,
148 exit_code: 0,
149 runner_used: RunnerMode::Native,
150 runner_distro: None,
151 timed_out: false,
152 ndjson_result: NdjsonResult::NoValidJson {
153 tail_excerpt: String::new(),
154 },
155 stdout_truncated: false,
156 stderr_truncated: false,
157 stdout_total_bytes: 0,
158 stderr_total_bytes: 3000,
159 };
160
161 let stderr_receipt = response.stderr_for_receipt(2048);
162 assert_eq!(stderr_receipt.len(), 2048);
163 assert_eq!(stderr_receipt, "x".repeat(2048));
165 }
166
167 #[test]
168 fn test_claude_response_stderr_for_receipt_exact_limit() {
169 let stderr = "x".repeat(2048);
170 let response = ClaudeResponse {
171 stdout: String::new(),
172 stderr: stderr.clone(),
173 exit_code: 0,
174 runner_used: RunnerMode::Native,
175 runner_distro: None,
176 timed_out: false,
177 ndjson_result: NdjsonResult::NoValidJson {
178 tail_excerpt: String::new(),
179 },
180 stdout_truncated: false,
181 stderr_truncated: false,
182 stdout_total_bytes: 0,
183 stderr_total_bytes: 2048,
184 };
185
186 let stderr_receipt = response.stderr_for_receipt(2048);
187 assert_eq!(stderr_receipt.len(), 2048);
188 assert_eq!(stderr_receipt, stderr);
189 }
190
191 #[test]
192 fn test_claude_response_stderr_for_receipt_custom_limit() {
193 let stderr = "Hello, world! This is a test message.".to_string();
194 let response = ClaudeResponse {
195 stdout: String::new(),
196 stderr: stderr.clone(),
197 exit_code: 0,
198 runner_used: RunnerMode::Native,
199 runner_distro: None,
200 timed_out: false,
201 ndjson_result: NdjsonResult::NoValidJson {
202 tail_excerpt: String::new(),
203 },
204 stdout_truncated: false,
205 stderr_truncated: false,
206 stdout_total_bytes: 0,
207 stderr_total_bytes: stderr.len(),
208 };
209
210 let stderr_receipt = response.stderr_for_receipt(10);
211 assert_eq!(stderr_receipt.len(), 10);
212 assert_eq!(stderr_receipt, "t message.");
214 }
215}