Skip to main content

ai_session/output/
mod.rs

1//! Intelligent output management for AI sessions
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Output manager for intelligent processing
8pub struct OutputManager {
9    /// Output parser
10    pub parser: OutputParser,
11    /// Semantic compressor
12    pub compressor: SemanticCompressor,
13    /// Output cache
14    _cache: HashMap<String, ParsedOutput>,
15}
16
17impl OutputManager {
18    /// Create a new output manager
19    pub fn new() -> Self {
20        Self {
21            parser: OutputParser::new(),
22            compressor: SemanticCompressor::new(),
23            _cache: HashMap::new(),
24        }
25    }
26
27    /// Process raw output
28    pub fn process_output(&mut self, raw_output: &str) -> Result<ProcessedOutput> {
29        // Parse the output
30        let parsed = self.parser.parse(raw_output)?;
31
32        // Extract highlights
33        let highlights = self.extract_highlights(&parsed);
34
35        // Compress if needed
36        let compressed = if raw_output.len() > 1024 {
37            Some(self.compressor.compress(raw_output)?)
38        } else {
39            None
40        };
41
42        Ok(ProcessedOutput {
43            raw: raw_output.to_string(),
44            parsed: parsed.clone(),
45            highlights,
46            compressed,
47            timestamp: chrono::Utc::now(),
48        })
49    }
50
51    /// Extract highlights from parsed output
52    fn extract_highlights(&self, parsed: &ParsedOutput) -> Vec<Highlight> {
53        let mut highlights = Vec::new();
54
55        match parsed {
56            ParsedOutput::CodeExecution { result: _, metrics } => {
57                if metrics.execution_time > std::time::Duration::from_secs(5) {
58                    highlights.push(Highlight {
59                        category: HighlightCategory::Performance,
60                        message: format!("Slow execution: {:?}", metrics.execution_time),
61                        severity: Severity::Warning,
62                    });
63                }
64            }
65            ParsedOutput::BuildOutput { status, .. } => match status {
66                BuildStatus::Failed(error) => {
67                    highlights.push(Highlight {
68                        category: HighlightCategory::Error,
69                        message: error.clone(),
70                        severity: Severity::Error,
71                    });
72                }
73                BuildStatus::Warning(warning) => {
74                    highlights.push(Highlight {
75                        category: HighlightCategory::Warning,
76                        message: warning.clone(),
77                        severity: Severity::Warning,
78                    });
79                }
80                _ => {}
81            },
82            ParsedOutput::TestResults { failed, .. } => {
83                if *failed > 0 {
84                    highlights.push(Highlight {
85                        category: HighlightCategory::TestFailure,
86                        message: format!("{} tests failed", failed),
87                        severity: Severity::Error,
88                    });
89                }
90            }
91            ParsedOutput::StructuredLog { level, message, .. } => {
92                if matches!(level, LogLevel::Error | LogLevel::Warning) {
93                    highlights.push(Highlight {
94                        category: HighlightCategory::Log,
95                        message: message.clone(),
96                        severity: match level {
97                            LogLevel::Error => Severity::Error,
98                            LogLevel::Warning => Severity::Warning,
99                            _ => Severity::Info,
100                        },
101                    });
102                }
103            }
104            _ => {}
105        }
106
107        highlights
108    }
109}
110
111impl Default for OutputManager {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117/// Output parser
118pub struct OutputParser {
119    /// Pattern matchers
120    patterns: HashMap<String, regex::Regex>,
121}
122
123impl OutputParser {
124    /// Create a new parser
125    pub fn new() -> Self {
126        let mut patterns = HashMap::new();
127
128        // Add common patterns
129        patterns.insert(
130            "error".to_string(),
131            regex::Regex::new(r"(?i)(error|exception|failure)").unwrap(),
132        );
133        patterns.insert(
134            "warning".to_string(),
135            regex::Regex::new(r"(?i)(warning|warn)").unwrap(),
136        );
137        patterns.insert(
138            "success".to_string(),
139            regex::Regex::new(r"(?i)(success|passed|completed)").unwrap(),
140        );
141
142        Self { patterns }
143    }
144
145    /// Parse output into structured format
146    pub fn parse(&self, output: &str) -> Result<ParsedOutput> {
147        // Simple heuristic-based parsing
148        // In a real implementation, this would be much more sophisticated
149
150        if output.contains("BUILD SUCCESSFUL") || output.contains("Build succeeded") {
151            Ok(ParsedOutput::BuildOutput {
152                status: BuildStatus::Success,
153                artifacts: Vec::new(),
154            })
155        } else if output.contains("BUILD FAILED") || output.contains("Build failed") {
156            Ok(ParsedOutput::BuildOutput {
157                status: BuildStatus::Failed("Build failed".to_string()),
158                artifacts: Vec::new(),
159            })
160        } else if output.contains("tests passed") || output.contains("All tests passed") {
161            Ok(ParsedOutput::TestResults {
162                passed: 1, // Placeholder
163                failed: 0,
164                details: TestDetails::default(),
165            })
166        } else if self.patterns["error"].is_match(output) {
167            Ok(ParsedOutput::StructuredLog {
168                level: LogLevel::Error,
169                message: output.to_string(),
170                context: LogContext::default(),
171            })
172        } else {
173            Ok(ParsedOutput::PlainText(output.to_string()))
174        }
175    }
176}
177
178impl Default for OutputParser {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184/// Semantic compressor
185pub struct SemanticCompressor {
186    /// Compression level (0.0 - 1.0)
187    _compression_level: f32,
188}
189
190impl SemanticCompressor {
191    /// Create a new compressor
192    pub fn new() -> Self {
193        Self {
194            _compression_level: 0.5,
195        }
196    }
197
198    /// Compress output semantically
199    pub fn compress(&self, output: &str) -> Result<CompressedOutput> {
200        // Simple implementation: just truncate for now
201        // In a real implementation, this would use NLP techniques
202        let compressed = if output.len() > 500 {
203            format!("{}... (truncated)", &output[..500])
204        } else {
205            output.to_string()
206        };
207
208        let compressed_len = compressed.len();
209        let original_len = output.len();
210
211        Ok(CompressedOutput {
212            original_size: original_len,
213            compressed_size: compressed_len,
214            content: compressed,
215            compression_ratio: compressed_len as f32 / original_len as f32,
216        })
217    }
218}
219
220impl Default for SemanticCompressor {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226/// Parsed output types
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub enum ParsedOutput {
229    /// Plain text output
230    PlainText(String),
231
232    /// Code execution result
233    CodeExecution {
234        result: String,
235        metrics: ExecutionMetrics,
236    },
237
238    /// Build output
239    BuildOutput {
240        status: BuildStatus,
241        artifacts: Vec<Artifact>,
242    },
243
244    /// Test results
245    TestResults {
246        passed: usize,
247        failed: usize,
248        details: TestDetails,
249    },
250
251    /// Structured log
252    StructuredLog {
253        level: LogLevel,
254        message: String,
255        context: LogContext,
256    },
257}
258
259/// Execution metrics
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ExecutionMetrics {
262    /// Execution time
263    pub execution_time: std::time::Duration,
264    /// Memory usage
265    pub memory_usage: Option<usize>,
266    /// CPU usage
267    pub cpu_usage: Option<f32>,
268}
269
270/// Build status
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub enum BuildStatus {
273    Success,
274    Failed(String),
275    Warning(String),
276    InProgress,
277}
278
279/// Build artifact
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct Artifact {
282    /// Artifact name
283    pub name: String,
284    /// Artifact path
285    pub path: String,
286    /// Size in bytes
287    pub size: usize,
288}
289
290/// Test details
291#[derive(Debug, Clone, Default, Serialize, Deserialize)]
292pub struct TestDetails {
293    /// Test suite name
294    pub suite: Option<String>,
295    /// Duration
296    pub duration: Option<std::time::Duration>,
297    /// Failed test names
298    pub failed_tests: Vec<String>,
299}
300
301/// Log level
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
303pub enum LogLevel {
304    Trace,
305    Debug,
306    Info,
307    Warning,
308    Error,
309}
310
311/// Log context
312#[derive(Debug, Clone, Default, Serialize, Deserialize)]
313pub struct LogContext {
314    /// Source file
315    pub file: Option<String>,
316    /// Line number
317    pub line: Option<usize>,
318    /// Additional fields
319    pub fields: HashMap<String, serde_json::Value>,
320}
321
322/// Processed output
323#[derive(Debug, Clone)]
324pub struct ProcessedOutput {
325    /// Raw output
326    pub raw: String,
327    /// Parsed output
328    pub parsed: ParsedOutput,
329    /// Highlights
330    pub highlights: Vec<Highlight>,
331    /// Compressed version
332    pub compressed: Option<CompressedOutput>,
333    /// Timestamp
334    pub timestamp: chrono::DateTime<chrono::Utc>,
335}
336
337/// Output highlight
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct Highlight {
340    /// Category
341    pub category: HighlightCategory,
342    /// Message
343    pub message: String,
344    /// Severity
345    pub severity: Severity,
346}
347
348/// Highlight category
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350pub enum HighlightCategory {
351    Error,
352    Warning,
353    Performance,
354    TestFailure,
355    Log,
356    Success,
357}
358
359/// Severity level
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
361pub enum Severity {
362    Info,
363    Warning,
364    Error,
365    Critical,
366}
367
368/// Compressed output
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct CompressedOutput {
371    /// Original size
372    pub original_size: usize,
373    /// Compressed size
374    pub compressed_size: usize,
375    /// Compressed content
376    pub content: String,
377    /// Compression ratio
378    pub compression_ratio: f32,
379}
380
381// Add regex dependency to Cargo.toml for this module
382use once_cell::sync::Lazy;
383static _REGEX_DEPENDENCY: Lazy<()> = Lazy::new(|| {
384    // This is just a marker to remind about adding regex = "1.10" to Cargo.toml
385});
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    #[test]
392    fn test_output_parser() {
393        let parser = OutputParser::new();
394
395        let output = "BUILD SUCCESSFUL";
396        let parsed = parser.parse(output).unwrap();
397
398        match parsed {
399            ParsedOutput::BuildOutput { status, .. } => {
400                assert!(matches!(status, BuildStatus::Success));
401            }
402            _ => panic!("Expected BuildOutput"),
403        }
404    }
405
406    #[test]
407    fn test_output_manager() {
408        let mut manager = OutputManager::new();
409
410        let output = "Error: Something went wrong";
411        let processed = manager.process_output(output).unwrap();
412
413        assert!(!processed.highlights.is_empty());
414        assert_eq!(processed.highlights[0].severity, Severity::Error);
415    }
416}