//! Shared reducer test fixtures. //! //! These helpers only write common trace scaffolding. Scenario-specific event //! sequences stay in each test so the behavior under test remains visible. use serde_json::json; use tempfile::TempDir; use crate::model::ToolCallSummary; use crate::payload::RawPayloadKind; use crate::payload::RawPayloadRef; use crate::raw_event::RawTraceEventContext; use crate::raw_event::RawTraceEventPayload; use crate::replay_bundle; use crate::writer::TraceWriter; pub(crate) const ROOT_THREAD_ID: &str = "thread-root"; pub(crate) const AGENT_ROOT_THREAD_ID: &str = "019d0000-0000-7000-8000-000000000001"; pub(crate) fn message(role: &str, text: &str) -> serde_json::Value { json!({ "type": "message", "role": role, "content": [{"type": "input_text", "text": text}] }) } pub(crate) fn generic_summary(label: &str) -> ToolCallSummary { ToolCallSummary::Generic { label: label.to_string(), input_preview: None, output_preview: None, } } pub(crate) fn create_started_writer(temp: &TempDir) -> anyhow::Result { create_started_writer_for_thread(temp, ROOT_THREAD_ID, "/root") } pub(crate) fn create_started_agent_writer(temp: &TempDir) -> anyhow::Result { create_started_writer_for_thread(temp, AGENT_ROOT_THREAD_ID, "/root") } pub(crate) fn create_started_writer_for_thread( temp: &TempDir, thread_id: &str, agent_path: &str, ) -> anyhow::Result { let writer = TraceWriter::create( temp.path(), "trace-1".to_string(), "rollout-1".to_string(), thread_id.to_string(), )?; start_thread(&writer, thread_id, agent_path)?; Ok(writer) } pub(crate) fn start_thread( writer: &TraceWriter, thread_id: &str, agent_path: &str, ) -> anyhow::Result<()> { writer.append(RawTraceEventPayload::ThreadStarted { thread_id: thread_id.to_string(), agent_path: agent_path.to_string(), metadata_payload: None, })?; Ok(()) } pub(crate) fn start_turn(writer: &TraceWriter, turn_id: &str) -> anyhow::Result<()> { start_turn_for_thread(writer, ROOT_THREAD_ID, turn_id) } pub(crate) fn start_agent_turn(writer: &TraceWriter, turn_id: &str) -> anyhow::Result<()> { start_turn_for_thread(writer, AGENT_ROOT_THREAD_ID, turn_id) } pub(crate) fn start_turn_for_thread( writer: &TraceWriter, thread_id: &str, turn_id: &str, ) -> anyhow::Result<()> { writer.append(RawTraceEventPayload::CodexTurnStarted { codex_turn_id: turn_id.to_string(), thread_id: thread_id.to_string(), })?; Ok(()) } pub(crate) fn trace_context(turn_id: &str) -> RawTraceEventContext { trace_context_for_thread(ROOT_THREAD_ID, turn_id) } pub(crate) fn trace_context_for_agent(turn_id: &str) -> RawTraceEventContext { trace_context_for_thread(AGENT_ROOT_THREAD_ID, turn_id) } pub(crate) fn trace_context_for_thread(thread_id: &str, turn_id: &str) -> RawTraceEventContext { RawTraceEventContext { thread_id: Some(thread_id.to_string()), codex_turn_id: Some(turn_id.to_string()), } } pub(crate) fn append_inference_start( writer: &TraceWriter, inference_call_id: &str, codex_turn_id: &str, request_payload: RawPayloadRef, ) -> anyhow::Result<()> { append_inference_start_for_thread( writer, ROOT_THREAD_ID, codex_turn_id, inference_call_id, request_payload, ) } pub(crate) fn append_inference_start_for_thread( writer: &TraceWriter, thread_id: &str, codex_turn_id: &str, inference_call_id: &str, request_payload: RawPayloadRef, ) -> anyhow::Result<()> { writer.append(RawTraceEventPayload::InferenceStarted { inference_call_id: inference_call_id.to_string(), thread_id: thread_id.to_string(), codex_turn_id: codex_turn_id.to_string(), model: "gpt-test".to_string(), provider_name: "test-provider".to_string(), request_payload, })?; Ok(()) } pub(crate) fn append_inference_completion( writer: &TraceWriter, inference_call_id: &str, response_id: &str, response_payload: RawPayloadRef, ) -> anyhow::Result<()> { writer.append(RawTraceEventPayload::InferenceCompleted { inference_call_id: inference_call_id.to_string(), response_id: Some(response_id.to_string()), upstream_request_id: None, response_payload, })?; Ok(()) } pub(crate) fn append_inference_request( writer: &TraceWriter, thread_id: &str, turn_id: &str, inference_id: &str, input: Vec, ) -> anyhow::Result<()> { let request = writer.write_json_payload(RawPayloadKind::InferenceRequest, &json!({ "input": input }))?; append_inference_start_for_thread(writer, thread_id, turn_id, inference_id, request) } pub(crate) fn append_completed_inference( writer: &TraceWriter, thread_id: &str, turn_id: &str, inference_id: &str, input: Vec, output_items: Vec, ) -> anyhow::Result<()> { append_inference_request(writer, thread_id, turn_id, inference_id, input)?; let response = writer.write_json_payload( RawPayloadKind::InferenceResponse, &json!({ "response_id": format!("resp-{inference_id}"), "output_items": output_items, }), )?; writer.append_with_context( trace_context_for_thread(thread_id, turn_id), RawTraceEventPayload::InferenceCompleted { inference_call_id: inference_id.to_string(), response_id: Some(format!("resp-{inference_id}")), upstream_request_id: None, response_payload: response, }, )?; Ok(()) } pub(crate) fn expect_replay_error(temp: &TempDir, expected: &str) -> anyhow::Result<()> { let Err(err) = replay_bundle(temp.path()) else { panic!("expected replay error containing {expected}"); }; let message = err.to_string(); assert!(message.contains(expected), "unexpected error: {message}"); Ok(()) }