Skip to main content

codex_app_server_client/
events.rs

1use crate::protocol::{ServerNotification, TurnError, TurnStatus};
2use crate::Event;
3
4#[derive(Clone, Debug, Default)]
5pub struct EventCollector {
6    thread_id: Option<String>,
7    turn_id: Option<String>,
8    agent_message: String,
9    latest_diff: Option<String>,
10    completed: bool,
11    terminal_status: Option<TurnStatus>,
12    errors: Vec<TurnError>,
13}
14
15impl EventCollector {
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    pub fn for_turn(thread_id: impl Into<String>, turn_id: impl Into<String>) -> Self {
21        Self {
22            thread_id: Some(thread_id.into()),
23            turn_id: Some(turn_id.into()),
24            ..Self::default()
25        }
26    }
27
28    pub fn observe(&mut self, event: &Event) {
29        if let Event::Notification(notification) = event {
30            self.observe_notification(notification);
31        }
32    }
33
34    pub fn observe_notification(&mut self, notification: &ServerNotification) {
35        match notification {
36            ServerNotification::ItemAgentMessageDelta(delta)
37                if self.matches_turn(&delta.thread_id, &delta.turn_id) =>
38            {
39                self.agent_message.push_str(&delta.delta);
40            }
41            ServerNotification::TurnDiffUpdated(diff)
42                if self.matches_turn(&diff.thread_id, &diff.turn_id) =>
43            {
44                self.latest_diff = Some(diff.diff.clone());
45            }
46            ServerNotification::TurnCompleted(completed)
47                if self.matches_turn(&completed.thread_id, &completed.turn.id) =>
48            {
49                self.terminal_status = Some(completed.turn.status);
50                self.completed = matches!(
51                    completed.turn.status,
52                    TurnStatus::Completed | TurnStatus::Interrupted | TurnStatus::Failed
53                );
54                if let Some(error) = &completed.turn.error {
55                    self.errors.push(error.clone());
56                }
57            }
58            ServerNotification::Error(error)
59                if self.matches_turn(&error.thread_id, &error.turn_id) =>
60            {
61                self.errors.push(error.error.clone());
62            }
63            _ => {}
64        }
65    }
66
67    pub fn agent_message(&self) -> &str {
68        &self.agent_message
69    }
70
71    pub fn latest_diff(&self) -> Option<&str> {
72        self.latest_diff.as_deref()
73    }
74
75    pub fn is_complete(&self) -> bool {
76        self.completed
77    }
78
79    pub fn terminal_status(&self) -> Option<&TurnStatus> {
80        self.terminal_status.as_ref()
81    }
82
83    pub fn errors(&self) -> &[TurnError] {
84        &self.errors
85    }
86
87    pub fn output_bytes(&self) -> usize {
88        self.agent_message.len()
89            + self.latest_diff.as_ref().map_or(0, String::len)
90            + self
91                .errors
92                .iter()
93                .map(|error| error.message.len())
94                .sum::<usize>()
95    }
96
97    fn matches_turn(&self, thread_id: &str, turn_id: &str) -> bool {
98        self.thread_id.as_deref().is_none_or(|id| id == thread_id)
99            && self.turn_id.as_deref().is_none_or(|id| id == turn_id)
100    }
101}