codex_app_server_client/
events.rs1use 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}