//! Hot-path helpers for recording code-mode runtime cell lifecycles. //! //! The public `exec` tool is reduced as a first-class `CodeCell` instead of a //! generic tool call. This module keeps the runtime response serialization and //! lifecycle event policy inside the trace crate while core carries a compact, //! no-op capable handle through execution and waits. use std::sync::Arc; use codex_code_mode::RuntimeResponse; use serde::Serialize; use tracing::warn; use crate::model::AgentThreadId; use crate::model::CodeCellRuntimeStatus; use crate::model::CodexTurnId; use crate::model::ModelVisibleCallId; use crate::payload::RawPayloadKind; use crate::payload::RawPayloadRef; use crate::raw_event::RawTraceEventContext; use crate::raw_event::RawTraceEventPayload; use crate::writer::TraceWriter; /// No-op capable trace handle for one code-mode runtime cell. #[derive(Clone, Debug)] pub struct CodeCellTraceContext { state: CodeCellTraceContextState, } #[derive(Clone, Debug)] enum CodeCellTraceContextState { Disabled, Enabled(EnabledCodeCellTraceContext), } #[derive(Clone, Debug)] struct EnabledCodeCellTraceContext { writer: Arc, thread_id: AgentThreadId, codex_turn_id: CodexTurnId, runtime_cell_id: String, } /// Raw code-mode response captured at the runtime boundary. /// /// This is not the model-visible custom-tool output. The reducer links that /// output through `CodeCell.output_item_ids` once the conversation item appears. /// Keeping the raw runtime payload here preserves stored-value and lifecycle /// evidence without duplicating the model-facing transcript. #[derive(Serialize)] struct CodeCellResponseTracePayload<'a> { response: &'a RuntimeResponse, } impl CodeCellTraceContext { /// Builds a context that accepts trace calls and records nothing. pub(crate) fn disabled() -> Self { Self { state: CodeCellTraceContextState::Disabled, } } /// Builds a context for an already-known code-mode runtime cell. pub(crate) fn enabled( writer: Arc, thread_id: impl Into, codex_turn_id: impl Into, runtime_cell_id: impl Into, ) -> Self { Self { state: CodeCellTraceContextState::Enabled(EnabledCodeCellTraceContext { writer, thread_id: thread_id.into(), codex_turn_id: codex_turn_id.into(), runtime_cell_id: runtime_cell_id.into(), }), } } /// Records the parent runtime object before JavaScript can issue nested tool calls. pub fn record_started( &self, model_visible_call_id: impl Into, source_js: impl Into, ) { let CodeCellTraceContextState::Enabled(context) = &self.state else { return; }; append_with_context_best_effort( context, RawTraceEventPayload::CodeCellStarted { runtime_cell_id: context.runtime_cell_id.clone(), model_visible_call_id: model_visible_call_id.into(), source_js: source_js.into(), }, ); } /// Records the first response returned by the public code-mode `exec` tool. /// /// A yielded response returns control to the model while the cell keeps /// running. Terminal initial responses should be followed by `record_ended` /// by the caller so the reducer can distinguish model-visible output from /// runtime completion. pub fn record_initial_response(&self, response: &RuntimeResponse) { let CodeCellTraceContextState::Enabled(context) = &self.state else { return; }; append_with_context_best_effort( context, RawTraceEventPayload::CodeCellInitialResponse { runtime_cell_id: context.runtime_cell_id.clone(), status: code_cell_status_for_runtime_response(response), response_payload: code_cell_response_payload(context, response), }, ); } /// Records the terminal lifecycle point for a code-mode runtime cell. pub fn record_ended(&self, response: &RuntimeResponse) { let CodeCellTraceContextState::Enabled(context) = &self.state else { return; }; append_with_context_best_effort( context, RawTraceEventPayload::CodeCellEnded { runtime_cell_id: context.runtime_cell_id.clone(), status: code_cell_status_for_runtime_response(response), response_payload: code_cell_response_payload(context, response), }, ); } } fn code_cell_status_for_runtime_response(response: &RuntimeResponse) -> CodeCellRuntimeStatus { match response { RuntimeResponse::Yielded { .. } => CodeCellRuntimeStatus::Yielded, RuntimeResponse::Terminated { .. } => CodeCellRuntimeStatus::Terminated, RuntimeResponse::Result { error_text, .. } => { if error_text.is_some() { CodeCellRuntimeStatus::Failed } else { CodeCellRuntimeStatus::Completed } } } } fn code_cell_response_payload( context: &EnabledCodeCellTraceContext, response: &RuntimeResponse, ) -> Option { write_json_payload_best_effort( &context.writer, RawPayloadKind::ToolResult, &CodeCellResponseTracePayload { response }, ) } fn write_json_payload_best_effort( writer: &TraceWriter, kind: RawPayloadKind, payload: &impl Serialize, ) -> Option { match writer.write_json_payload(kind, payload) { Ok(payload_ref) => Some(payload_ref), Err(err) => { warn!("failed to write rollout trace payload: {err:#}"); None } } } fn append_with_context_best_effort( context: &EnabledCodeCellTraceContext, payload: RawTraceEventPayload, ) { let event_context = RawTraceEventContext { thread_id: Some(context.thread_id.clone()), codex_turn_id: Some(context.codex_turn_id.clone()), }; if let Err(err) = context.writer.append_with_context(event_context, payload) { warn!("failed to append rollout trace event: {err:#}"); } }