Codex Handbook
core/tests/suite/window_headers.rs 147 lines
use super::compact::COMPACT_WARNING_MESSAGE;use anyhow::Result;use codex_core::CodexThread;use codex_core::compact::SUMMARIZATION_PROMPT;use codex_protocol::protocol::EventMsg;use codex_protocol::protocol::Op;use codex_protocol::protocol::WarningEvent;use codex_protocol::user_input::UserInput;use core_test_support::responses::ResponsesRequest;use core_test_support::responses::ev_assistant_message;use core_test_support::responses::ev_completed;use core_test_support::responses::mount_sse_sequence;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 window_id_advances_after_compact_persists_on_resume_and_resets_on_fork() -> Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let request_log = mount_sse_sequence(        &server,        vec![            sse(vec![                ev_assistant_message("msg-1", "first reply"),                ev_completed("resp-1"),            ]),            sse(vec![                ev_assistant_message("msg-2", "summary"),                ev_completed("resp-2"),            ]),            sse(vec![ev_completed("resp-3")]),            sse(vec![ev_completed("resp-4")]),            sse(vec![ev_completed("resp-5")]),        ],    )    .await;    let mut builder = test_codex().with_config(|config| {        config.model_provider.name = "Non-OpenAI Model provider".to_string();        config.compact_prompt = Some(SUMMARIZATION_PROMPT.to_string());    });    let initial = builder.build(&server).await?;    let initial_thread = Arc::clone(&initial.codex);    let rollout_path = initial        .session_configured        .rollout_path        .clone()        .expect("rollout path");    submit_user_turn(&initial_thread, "before compact").await?;    submit_compact_turn(&initial_thread).await?;    submit_user_turn(&initial_thread, "after compact").await?;    shutdown_thread(&initial_thread).await?;    let resumed = builder        .resume(&server, initial.home.clone(), rollout_path.clone())        .await?;    submit_user_turn(&resumed.codex, "after resume").await?;    shutdown_thread(&resumed.codex).await?;    let forked = resumed        .thread_manager        .fork_thread(            /*snapshot*/ 0usize,            resumed.config.clone(),            rollout_path,            /*thread_source*/ None,            /*parent_trace*/ None,        )        .await?;    submit_user_turn(&forked.thread, "after fork").await?;    shutdown_thread(&forked.thread).await?;    let requests = request_log.requests();    assert_eq!(requests.len(), 5, "expected five model requests");    let (initial_thread_id, first_generation) = window_id_parts(&requests[0]);    let (compact_thread_id, compact_generation) = window_id_parts(&requests[1]);    let (after_compact_thread_id, after_compact_generation) = window_id_parts(&requests[2]);    let (after_resume_thread_id, after_resume_generation) = window_id_parts(&requests[3]);    let (after_fork_thread_id, after_fork_generation) = window_id_parts(&requests[4]);    assert_eq!(first_generation, 0);    assert_eq!(compact_thread_id, initial_thread_id);    assert_eq!(compact_generation, 0);    assert_eq!(after_compact_thread_id, initial_thread_id);    assert_eq!(after_compact_generation, 1);    assert_eq!(after_resume_thread_id, initial_thread_id);    assert_eq!(after_resume_generation, 1);    assert_ne!(after_fork_thread_id, initial_thread_id);    assert_eq!(after_fork_generation, 0);    Ok(())}async fn submit_user_turn(codex: &Arc<CodexThread>, text: &str) -> Result<()> {    codex        .submit(Op::UserInput {            items: vec![UserInput::Text {                text: text.to_string(),                text_elements: Vec::new(),            }],            final_output_json_schema: None,            responsesapi_client_metadata: None,            additional_context: Default::default(),            thread_settings: Default::default(),        })        .await?;    wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;    Ok(())}async fn submit_compact_turn(codex: &Arc<CodexThread>) -> Result<()> {    codex.submit(Op::Compact).await?;    let warning_event = wait_for_event(codex, |event| matches!(event, EventMsg::Warning(_))).await;    let EventMsg::Warning(WarningEvent { message }) = warning_event else {        panic!("expected warning event after compact");    };    assert_eq!(message, COMPACT_WARNING_MESSAGE);    wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;    Ok(())}async fn shutdown_thread(codex: &Arc<CodexThread>) -> Result<()> {    codex.submit(Op::Shutdown).await?;    wait_for_event(codex, |event| matches!(event, EventMsg::ShutdownComplete)).await;    Ok(())}fn window_id_parts(request: &ResponsesRequest) -> (String, u64) {    let window_id = request        .header("x-codex-window-id")        .expect("missing x-codex-window-id header");    let (thread_id, generation) = window_id        .rsplit_once(':')        .expect("window id header should contain a generation");    let generation = generation        .parse::<u64>()        .expect("window generation should be a valid integer");    (thread_id.to_string(), generation)}