agent_code_lib/services/
output_store.rs1use std::path::{Path, PathBuf};
8
9use crate::services::secret_masker;
10
11const INLINE_THRESHOLD: usize = 64 * 1024;
13
14pub fn persist_if_large(content: &str, tool_name: &str, tool_use_id: &str) -> String {
20 persist_if_large_in(&output_store_dir(), content, tool_name, tool_use_id)
21}
22
23pub(crate) fn persist_if_large_in(
26 store_dir: &Path,
27 content: &str,
28 _tool_name: &str,
29 tool_use_id: &str,
30) -> String {
31 if content.len() <= INLINE_THRESHOLD {
32 return content.to_string();
33 }
34
35 let _ = std::fs::create_dir_all(store_dir);
36
37 let filename = format!("{tool_use_id}.txt");
38 let path = store_dir.join(&filename);
39
40 let persisted = secret_masker::mask(content);
44
45 match std::fs::write(&path, &persisted) {
46 Ok(()) => {
47 let preview = &content[..INLINE_THRESHOLD.min(content.len())];
48 format!(
49 "{preview}\n\n(Output truncated. Full result ({} bytes) saved to {})",
50 content.len(),
51 path.display()
52 )
53 }
54 Err(_) => {
55 let preview = &content[..INLINE_THRESHOLD.min(content.len())];
57 format!(
58 "{preview}\n\n(Output truncated: {} bytes total)",
59 content.len()
60 )
61 }
62 }
63}
64
65pub fn read_persisted(tool_use_id: &str) -> Option<String> {
67 let path = output_store_dir().join(format!("{tool_use_id}.txt"));
68 std::fs::read_to_string(path).ok()
69}
70
71pub fn cleanup_old_outputs() {
73 let dir = output_store_dir();
74 if !dir.is_dir() {
75 return;
76 }
77
78 let cutoff = std::time::SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60);
79
80 if let Ok(entries) = std::fs::read_dir(&dir) {
81 for entry in entries.flatten() {
82 if let Ok(meta) = entry.metadata()
83 && let Ok(modified) = meta.modified()
84 && modified < cutoff
85 {
86 let _ = std::fs::remove_file(entry.path());
87 }
88 }
89 }
90}
91
92fn output_store_dir() -> PathBuf {
93 dirs::cache_dir()
94 .unwrap_or_else(|| PathBuf::from("/tmp"))
95 .join("agent-code")
96 .join("tool-results")
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn persist_if_large_passes_small_content_through_unchanged() {
105 let dir = tempfile::tempdir().unwrap();
106 let small = "tiny output with api_key=irrelevant_for_small_content";
107 let out = persist_if_large_in(dir.path(), small, "Bash", "tool-1");
109 assert_eq!(out, small);
110 assert!(
111 std::fs::read_dir(dir.path())
112 .map(|mut it| it.next().is_none())
113 .unwrap_or(true),
114 "small content should not write to disk",
115 );
116 }
117
118 #[test]
119 fn persist_if_large_masks_secrets_on_disk_but_not_in_preview() {
120 let dir = tempfile::tempdir().unwrap();
121 let aws_key = "AKIAIOSFODNN7EXAMPLE";
122 let mut content = String::with_capacity(INLINE_THRESHOLD + 1024);
125 content.push_str("prefix noise ");
126 content.push_str(aws_key);
127 content.push_str(" more noise ");
128 while content.len() <= INLINE_THRESHOLD {
129 content.push_str("filler ");
130 }
131
132 let preview = persist_if_large_in(dir.path(), &content, "Bash", "tool-big");
133
134 assert!(
137 preview.contains(aws_key),
138 "preview should keep raw secret for in-memory use",
139 );
140 assert!(preview.contains("Output truncated"));
141
142 let disk_path = dir.path().join("tool-big.txt");
144 assert!(disk_path.exists(), "persisted file not created");
145 let on_disk = std::fs::read_to_string(&disk_path).unwrap();
146 assert!(
147 !on_disk.contains(aws_key),
148 "raw secret found on disk: {on_disk}",
149 );
150 assert!(on_disk.contains("[REDACTED:aws_access_key]"));
151 }
152
153 #[test]
154 fn persist_if_large_redacts_generic_credential_on_disk() {
155 let dir = tempfile::tempdir().unwrap();
156 let secret = "supersecretproductiontoken1234567890";
157 let assignment = format!("DATABASE_PASSWORD={secret}");
158 let mut content = assignment.clone();
159 while content.len() <= INLINE_THRESHOLD {
160 content.push_str(" padding padding padding padding padding ");
161 }
162
163 let _ = persist_if_large_in(dir.path(), &content, "Bash", "tool-db");
164
165 let disk_path = dir.path().join("tool-db.txt");
166 let on_disk = std::fs::read_to_string(&disk_path).unwrap();
167 assert!(!on_disk.contains(secret));
168 assert!(on_disk.contains("[REDACTED:credential]"));
169 }
170
171 #[test]
172 fn persist_if_large_at_exact_threshold_passes_through() {
173 let dir = tempfile::tempdir().unwrap();
178 let content = "a".repeat(INLINE_THRESHOLD);
179 let out = persist_if_large_in(dir.path(), &content, "Bash", "tool-boundary-eq");
180 assert_eq!(out.len(), INLINE_THRESHOLD);
181 assert_eq!(out, content);
182 assert!(
183 std::fs::read_dir(dir.path())
184 .map(|mut it| it.next().is_none())
185 .unwrap_or(true),
186 "content at exact threshold should not write to disk",
187 );
188 }
189
190 #[test]
191 fn persist_if_large_at_threshold_plus_one_writes_to_disk() {
192 let dir = tempfile::tempdir().unwrap();
194 let content = "a".repeat(INLINE_THRESHOLD + 1);
195 let preview = persist_if_large_in(dir.path(), &content, "Bash", "tool-boundary-plus-one");
196 assert!(preview.contains("Output truncated"));
197 assert!(dir.path().join("tool-boundary-plus-one.txt").exists());
198 }
199}