use anyhow::Result; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_protocol::user_input::ByteRange; use codex_protocol::user_input::TextElement; use codex_protocol::user_input::UserInput; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_reasoning_item; use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; use std::sync::Arc; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; let mut builder = test_codex(); let initial = builder.build(&server).await?; let codex = Arc::clone(&initial.codex); let home = initial.home.clone(); let rollout_path = initial.session_configured.rollout_path.clone(); let initial_sse = sse(vec![ ev_response_created("resp-initial"), ev_assistant_message("msg-1", "Completed first turn"), ev_completed("resp-initial"), ]); mount_sse_once(&server, initial_sse).await; let text_elements = vec![TextElement { byte_range: ByteRange { start: 0, end: 6 }, placeholder: Some("".into()), }]; codex .submit(Op::UserInput { items: vec![UserInput::Text { text: "Record some messages".into(), text_elements: text_elements.clone(), }], final_output_json_schema: None, }) .await?; wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; let resumed = builder.resume(&server, home, rollout_path).await?; let initial_messages = resumed .session_configured .initial_messages .expect("expected initial messages to be present for resumed session"); match initial_messages.as_slice() { [ EventMsg::UserMessage(first_user), EventMsg::TokenCount(_), EventMsg::AgentMessage(assistant_message), EventMsg::TokenCount(_), ] => { assert_eq!(first_user.message, "Record some messages"); assert_eq!(first_user.text_elements, text_elements); assert_eq!(assistant_message.message, "Completed first turn"); } other => panic!("unexpected initial messages after resume: {other:#?}"), } Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; let mut builder = test_codex().with_config(|config| { config.show_raw_agent_reasoning = true; }); let initial = builder.build(&server).await?; let codex = Arc::clone(&initial.codex); let home = initial.home.clone(); let rollout_path = initial.session_configured.rollout_path.clone(); let initial_sse = sse(vec![ ev_response_created("resp-initial"), ev_reasoning_item("reason-1", &["Summarized step"], &["raw detail"]), ev_assistant_message("msg-1", "Completed reasoning turn"), ev_completed("resp-initial"), ]); mount_sse_once(&server, initial_sse).await; codex .submit(Op::UserInput { items: vec![UserInput::Text { text: "Record reasoning messages".into(), text_elements: Vec::new(), }], final_output_json_schema: None, }) .await?; wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; let resumed = builder.resume(&server, home, rollout_path).await?; let initial_messages = resumed .session_configured .initial_messages .expect("expected initial messages to be present for resumed session"); match initial_messages.as_slice() { [ EventMsg::UserMessage(first_user), EventMsg::TokenCount(_), EventMsg::AgentReasoning(reasoning), EventMsg::AgentReasoningRawContent(raw), EventMsg::AgentMessage(assistant_message), EventMsg::TokenCount(_), ] => { assert_eq!(first_user.message, "Record reasoning messages"); assert_eq!(reasoning.text, "Summarized step"); assert_eq!(raw.text, "raw detail"); assert_eq!(assistant_message.message, "Completed reasoning turn"); } other => panic!("unexpected initial messages after resume: {other:#?}"), } Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn resume_switches_models_preserves_base_instructions() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; let mut builder = test_codex().with_config(|config| { config.model = Some("gpt-5.2".to_string()); }); let initial = builder.build(&server).await?; let codex = Arc::clone(&initial.codex); let home = initial.home.clone(); let rollout_path = initial.session_configured.rollout_path.clone(); let initial_sse = sse(vec![ ev_response_created("resp-initial"), ev_assistant_message("msg-1", "Completed first turn"), ev_completed("resp-initial"), ]); let initial_mock = mount_sse_once(&server, initial_sse).await; codex .submit(Op::UserInput { items: vec![UserInput::Text { text: "Record initial instructions".into(), text_elements: Vec::new(), }], final_output_json_schema: None, }) .await?; wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; let initial_body = initial_mock.single_request().body_json(); let initial_instructions = initial_body .get("instructions") .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); let resumed_sse = sse(vec![ ev_response_created("resp-resume"), ev_assistant_message("msg-2", "Resumed turn"), ev_completed("resp-resume"), ]); let resumed_mock = mount_sse_once(&server, resumed_sse).await; let mut resume_builder = test_codex().with_config(|config| { config.model = Some("gpt-5.2-codex".to_string()); }); let resumed = resume_builder.resume(&server, home, rollout_path).await?; resumed .codex .submit(Op::UserInput { items: vec![UserInput::Text { text: "Resume with different model".into(), text_elements: Vec::new(), }], final_output_json_schema: None, }) .await?; wait_for_event(&resumed.codex, |event| { matches!(event, EventMsg::TurnComplete(_)) }) .await; let resumed_body = resumed_mock.single_request().body_json(); let resumed_instructions = resumed_body .get("instructions") .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); assert_eq!(resumed_instructions, initial_instructions); Ok(()) }