Skip to main content

ralph_workflow/json_parser/codex/
parser.rs

1use super::printer::{SharedPrinter, StdoutPrinter};
2use crate::json_parser::printer::Printable;
3use crate::json_parser::types::ToolActivityTracker;
4use io::CodexParserState;
5use std::cell::RefCell;
6use std::rc::Rc;
7
8/// Codex event parser
9pub struct CodexParser {
10    colors: Colors,
11    verbosity: Verbosity,
12    log_path: Option<PathBuf>,
13    display_name: String,
14    state: CodexParserState,
15    show_streaming_metrics: bool,
16    printer: SharedPrinter,
17    /// Tracks active tool executions for idle-timeout suppression. Incremented on
18    /// `ItemStarted`, saturating-decremented on `ItemCompleted`, hard-reset to 0 on
19    /// `TurnCompleted` / `TurnFailed`.
20    tool_activity_tracker: ToolActivityTracker,
21}
22
23impl CodexParser {
24    pub(crate) fn new(colors: Colors, verbosity: Verbosity) -> Self {
25        Self::with_printer(
26            colors,
27            verbosity,
28            Rc::new(RefCell::new(StdoutPrinter::new())),
29        )
30    }
31
32    pub(crate) fn with_printer(
33        colors: Colors,
34        verbosity: Verbosity,
35        printer: SharedPrinter,
36    ) -> Self {
37        let verbose_warnings = matches!(verbosity, Verbosity::Debug);
38
39        Self {
40            colors,
41            verbosity,
42            log_path: None,
43            display_name: "Codex".to_string(),
44            state: CodexParserState::new(verbose_warnings),
45            show_streaming_metrics: false,
46            printer,
47            tool_activity_tracker: ToolActivityTracker::new(),
48        }
49    }
50
51    /// Register a shared counter that the idle-timeout monitor polls to detect active tool
52    /// execution. Incremented on `ItemStarted`, saturating-decremented on `ItemCompleted`,
53    /// hard-reset to 0 on `TurnCompleted` / `TurnFailed`. This prevents the idle-timeout monitor
54    /// from killing the agent during long-running writes or other concurrent tool calls.
55    pub(crate) fn with_tool_activity_tracker(
56        mut self,
57        tracker: std::sync::Arc<std::sync::atomic::AtomicU32>,
58    ) -> Self {
59        self.tool_activity_tracker = ToolActivityTracker::with_tracker(tracker);
60        self
61    }
62
63    pub(crate) const fn with_show_streaming_metrics(mut self, show: bool) -> Self {
64        self.show_streaming_metrics = show;
65        self
66    }
67
68    pub(crate) fn with_display_name(mut self, display_name: &str) -> Self {
69        self.display_name = display_name.to_string();
70        self
71    }
72
73    pub(crate) fn with_log_file(mut self, path: &str) -> Self {
74        self.log_path = Some(PathBuf::from(path));
75        self
76    }
77
78    #[cfg(any(test, feature = "test-utils"))]
79    #[must_use]
80    pub fn with_terminal_mode(self, mode: crate::json_parser::TerminalMode) -> Self {
81        *self.state.terminal_mode.borrow_mut() = mode;
82        self
83    }
84
85    #[cfg(any(test, feature = "test-utils"))]
86    pub fn with_printer_for_test(
87        colors: Colors,
88        verbosity: Verbosity,
89        printer: SharedPrinter,
90    ) -> Self {
91        Self::with_printer(colors, verbosity, printer)
92    }
93
94    #[cfg(any(test, feature = "test-utils"))]
95    #[must_use]
96    pub fn with_log_file_for_test(mut self, path: &str) -> Self {
97        self.log_path = Some(PathBuf::from(path));
98        self
99    }
100
101    #[cfg(any(test, feature = "test-utils"))]
102    #[must_use]
103    pub fn with_display_name_for_test(mut self, display_name: &str) -> Self {
104        self.display_name = display_name.to_string();
105        self
106    }
107
108    #[cfg(any(test, feature = "test-utils"))]
109    pub fn parse_stream_for_test<R: std::io::BufRead>(
110        &mut self,
111        reader: R,
112        workspace: &dyn Workspace,
113    ) -> std::io::Result<()> {
114        self.parse_stream(reader, workspace)
115    }
116
117    #[cfg(any(test, feature = "test-utils"))]
118    pub fn printer(&self) -> SharedPrinter {
119        Rc::clone(&self.printer)
120    }
121
122    pub(crate) fn with_printer_mut<R>(&mut self, f: impl FnOnce(&mut dyn Printable) -> R) -> R {
123        let mut printer_ref = self.printer.borrow_mut();
124        f(&mut *printer_ref)
125    }
126
127    #[cfg(any(test, feature = "test-utils"))]
128    pub fn streaming_metrics(&self) -> crate::json_parser::health::StreamingQualityMetrics {
129        self.state
130            .streaming_session
131            .borrow()
132            .get_streaming_quality_metrics()
133    }
134
135    #[inline]
136    fn optional_output(output: String) -> Option<String> {
137        if output.is_empty() {
138            None
139        } else {
140            Some(output)
141        }
142    }
143}