1use std::fmt::Display;
9use std::sync::Arc;
10use std::sync::atomic::AtomicU64;
11use std::sync::atomic::Ordering;
12
13use codex_protocol::models::ResponseItem;
14use serde::Serialize;
15use serde_json::Value as JsonValue;
16use tracing::warn;
17
18use crate::inference::trace_response_item_json;
19use crate::model::AgentThreadId;
20use crate::model::CodexTurnId;
21use crate::model::CompactionId;
22use crate::model::CompactionRequestId;
23use crate::payload::RawPayloadKind;
24use crate::raw_event::RawTraceEventContext;
25use crate::raw_event::RawTraceEventPayload;
26use crate::writer::TraceWriter;
27
28static NEXT_COMPACTION_REQUEST: AtomicU64 = AtomicU64::new(1);
29
30#[derive(Clone, Debug)]
35pub struct CompactionTraceContext {
36 state: CompactionTraceContextState,
37}
38
39#[derive(Clone, Debug)]
40enum CompactionTraceContextState {
41 Disabled,
42 Enabled(EnabledCompactionTraceContext),
43}
44
45#[derive(Clone, Debug)]
46struct EnabledCompactionTraceContext {
47 writer: Arc<TraceWriter>,
48 thread_id: AgentThreadId,
49 codex_turn_id: CodexTurnId,
50 compaction_id: CompactionId,
51 model: String,
52 provider_name: String,
53}
54
55#[derive(Clone, Debug)]
57pub struct CompactionTraceAttempt {
58 state: CompactionTraceAttemptState,
59}
60
61#[derive(Clone, Debug)]
62enum CompactionTraceAttemptState {
63 Disabled,
64 Enabled(EnabledCompactionTraceAttempt),
65}
66
67#[derive(Clone, Debug)]
68struct EnabledCompactionTraceAttempt {
69 context: EnabledCompactionTraceContext,
70 compaction_request_id: CompactionRequestId,
71}
72
73#[derive(Serialize)]
74struct TracedCompactionCompleted {
75 output_items: Vec<JsonValue>,
76}
77
78#[derive(Serialize)]
84pub struct CompactionCheckpointTracePayload<'a> {
85 pub input_history: &'a [ResponseItem],
86 pub replacement_history: &'a [ResponseItem],
87}
88
89impl CompactionTraceContext {
90 pub fn disabled() -> Self {
92 Self {
93 state: CompactionTraceContextState::Disabled,
94 }
95 }
96
97 pub fn enabled(
99 writer: Arc<TraceWriter>,
100 thread_id: AgentThreadId,
101 codex_turn_id: CodexTurnId,
102 compaction_id: CompactionId,
103 model: String,
104 provider_name: String,
105 ) -> Self {
106 Self {
107 state: CompactionTraceContextState::Enabled(EnabledCompactionTraceContext {
108 writer,
109 thread_id,
110 codex_turn_id,
111 compaction_id,
112 model,
113 provider_name,
114 }),
115 }
116 }
117
118 pub fn is_enabled(&self) -> bool {
120 matches!(self.state, CompactionTraceContextState::Enabled(_))
121 }
122
123 pub fn start_attempt(&self, request: &impl Serialize) -> CompactionTraceAttempt {
125 let CompactionTraceContextState::Enabled(context) = &self.state else {
126 return CompactionTraceAttempt::disabled();
127 };
128
129 let attempt = CompactionTraceAttempt {
130 state: CompactionTraceAttemptState::Enabled(EnabledCompactionTraceAttempt {
131 context: context.clone(),
132 compaction_request_id: next_compaction_request_id(),
133 }),
134 };
135 attempt.record_started(request);
136 attempt
137 }
138
139 pub fn record_installed(&self, checkpoint: &CompactionCheckpointTracePayload<'_>) {
144 let CompactionTraceContextState::Enabled(context) = &self.state else {
145 return;
146 };
147 let checkpoint_payload = match context
148 .writer
149 .write_json_payload(RawPayloadKind::CompactionCheckpoint, checkpoint)
150 {
151 Ok(payload_ref) => payload_ref,
152 Err(err) => {
153 warn!("failed to write rollout trace payload: {err:#}");
154 return;
155 }
156 };
157
158 let event_context = RawTraceEventContext {
159 thread_id: Some(context.thread_id.clone()),
160 codex_turn_id: Some(context.codex_turn_id.clone()),
161 };
162 if let Err(err) = context.writer.append_with_context(
163 event_context,
164 RawTraceEventPayload::CompactionInstalled {
165 compaction_id: context.compaction_id.clone(),
166 checkpoint_payload,
167 },
168 ) {
169 warn!("failed to append rollout trace event: {err:#}");
170 }
171 }
172}
173
174impl CompactionTraceAttempt {
175 fn disabled() -> Self {
177 Self {
178 state: CompactionTraceAttemptState::Disabled,
179 }
180 }
181
182 fn record_started(&self, request: &impl Serialize) {
183 let CompactionTraceAttemptState::Enabled(attempt) = &self.state else {
184 return;
185 };
186 let Some(request_payload) = write_json_payload_best_effort(
187 &attempt.context.writer,
188 RawPayloadKind::CompactionRequest,
189 request,
190 ) else {
191 return;
192 };
193
194 append_with_context_best_effort(
195 &attempt.context,
196 RawTraceEventPayload::CompactionRequestStarted {
197 compaction_id: attempt.context.compaction_id.clone(),
198 compaction_request_id: attempt.compaction_request_id.clone(),
199 thread_id: attempt.context.thread_id.clone(),
200 codex_turn_id: attempt.context.codex_turn_id.clone(),
201 model: attempt.context.model.clone(),
202 provider_name: attempt.context.provider_name.clone(),
203 request_payload,
204 },
205 );
206 }
207
208 pub fn record_completed(&self, output_items: &[ResponseItem]) {
214 let CompactionTraceAttemptState::Enabled(attempt) = &self.state else {
215 return;
216 };
217 let response_payload = TracedCompactionCompleted {
218 output_items: output_items.iter().map(trace_response_item_json).collect(),
219 };
220 let Some(response_payload) = write_json_payload_best_effort(
221 &attempt.context.writer,
222 RawPayloadKind::CompactionResponse,
223 &response_payload,
224 ) else {
225 return;
226 };
227
228 append_with_context_best_effort(
229 &attempt.context,
230 RawTraceEventPayload::CompactionRequestCompleted {
231 compaction_id: attempt.context.compaction_id.clone(),
232 compaction_request_id: attempt.compaction_request_id.clone(),
233 response_payload,
234 },
235 );
236 }
237
238 pub fn record_result<E: Display>(&self, result: Result<&[ResponseItem], E>) {
240 match result {
241 Ok(output_items) => self.record_completed(output_items),
242 Err(err) => self.record_failed(err),
243 }
244 }
245
246 pub fn record_failed(&self, error: impl Display) {
248 let CompactionTraceAttemptState::Enabled(attempt) = &self.state else {
249 return;
250 };
251 append_with_context_best_effort(
252 &attempt.context,
253 RawTraceEventPayload::CompactionRequestFailed {
254 compaction_id: attempt.context.compaction_id.clone(),
255 compaction_request_id: attempt.compaction_request_id.clone(),
256 error: error.to_string(),
257 },
258 );
259 }
260}
261
262fn next_compaction_request_id() -> CompactionRequestId {
263 let ordinal = NEXT_COMPACTION_REQUEST.fetch_add(1, Ordering::Relaxed);
264 format!("compaction_request:{ordinal}")
265}
266
267fn write_json_payload_best_effort(
268 writer: &TraceWriter,
269 kind: RawPayloadKind,
270 payload: &impl Serialize,
271) -> Option<crate::RawPayloadRef> {
272 writer.write_json_payload(kind, payload).ok()
273}
274
275fn append_with_context_best_effort(
276 context: &EnabledCompactionTraceContext,
277 payload: RawTraceEventPayload,
278) {
279 let event_context = RawTraceEventContext {
280 thread_id: Some(context.thread_id.clone()),
281 codex_turn_id: Some(context.codex_turn_id.clone()),
282 };
283 let _ = context.writer.append_with_context(event_context, payload);
284}