[codex] Simplify concurrent reasoning summary rollout

This commit is contained in:
ashwinnathan-openai
2026-07-06 19:26:34 +00:00
parent 952fe096e0
commit 8d7549e2f4
14 changed files with 223 additions and 75 deletions
+3
View File
@@ -131,6 +131,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*item_ids_enabled*/ false,
/*parallel_reasoning_summaries_enabled*/ false,
/*attestation_provider*/ None,
);
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
@@ -265,6 +266,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*item_ids_enabled*/ false,
/*parallel_reasoning_summaries_enabled*/ false,
/*attestation_provider*/ None,
);
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
@@ -385,6 +387,7 @@ async fn responses_respects_model_info_overrides_from_config() {
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*item_ids_enabled*/ false,
/*parallel_reasoning_summaries_enabled*/ false,
/*attestation_provider*/ None,
);
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
+4
View File
@@ -1334,6 +1334,8 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
/*parallel_reasoning_summaries_enabled*/
config.features.enabled(Feature::ParallelReasoningSummaries),
/*attestation_provider*/ None,
);
let responses_metadata = test_turn_responses_metadata(&client, thread_id);
@@ -2376,6 +2378,7 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> {
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.model_reasoning_summary = Some(ReasoningSummary::Concise);
let _ = config.features.enable(Feature::ParallelReasoningSummaries);
})
.build(&server)
.await?;
@@ -2956,6 +2959,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*item_ids_enabled*/ false,
/*parallel_reasoning_summaries_enabled*/ false,
/*attestation_provider*/ None,
);
let responses_metadata = test_turn_responses_metadata(&client, thread_id);
+25 -9
View File
@@ -388,10 +388,11 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
let mut provider = websocket_provider(&server);
provider.name = "OpenAI".to_string();
let mut harness =
websocket_harness_with_provider_options(provider, /*runtime_metrics_enabled*/ true).await;
harness.model_info.supports_reasoning_summaries = true;
harness.summary = ReasoningSummary::Concise;
let harness = websocket_harness_with_provider_options(
provider, /*runtime_metrics_enabled*/ true,
/*parallel_reasoning_summaries_enabled*/ true,
)
.await;
let mut client_session = harness.client.new_session();
let prompt = prompt_with_input(vec![message_item("hello")]);
let responses_metadata = prewarm_metadata(&harness, /*turn_id*/ None);
@@ -446,7 +447,6 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
follow_up["stream_options"]["reasoning_summary_delivery"].as_str(),
Some("concurrent_cutoff")
);
server.shutdown().await;
}
@@ -1000,8 +1000,11 @@ async fn responses_websocket_v2_incremental_requests_are_reused_across_turns() {
// use OpenAI provider to check metadata logic
let mut provider = websocket_provider(&server);
provider.name = ModelProviderInfo::create_openai_provider(/*base_url*/ None).name;
let harness =
websocket_harness_with_provider_options(provider, /*runtime_metrics_enabled*/ false).await;
let harness = websocket_harness_with_provider_options(
provider, /*runtime_metrics_enabled*/ false,
/*parallel_reasoning_summaries_enabled*/ false,
)
.await;
// Turn one: initiate
let prompt_one = prompt_with_input(vec![message_item("hello")]);
@@ -2205,13 +2208,18 @@ async fn websocket_harness_with_options(
server: &WebSocketTestServer,
runtime_metrics_enabled: bool,
) -> WebsocketTestHarness {
websocket_harness_with_provider_options(websocket_provider(server), runtime_metrics_enabled)
.await
websocket_harness_with_provider_options(
websocket_provider(server),
runtime_metrics_enabled,
/*parallel_reasoning_summaries_enabled*/ false,
)
.await
}
async fn websocket_harness_with_provider_options(
provider: ModelProviderInfo,
runtime_metrics_enabled: bool,
parallel_reasoning_summaries_enabled: bool,
) -> WebsocketTestHarness {
let codex_home = TempDir::new().unwrap();
let mut config = load_default_config_for_test(&codex_home).await;
@@ -2222,6 +2230,12 @@ async fn websocket_harness_with_provider_options(
.enable(Feature::RuntimeMetrics)
.expect("test config should allow feature update");
}
if parallel_reasoning_summaries_enabled {
config
.features
.enable(Feature::ParallelReasoningSummaries)
.expect("test config should allow feature update");
}
let config = Arc::new(config);
let model_info = codex_core::test_support::construct_model_info_offline(MODEL, &config);
let thread_id = ThreadId::new();
@@ -2261,6 +2275,8 @@ async fn websocket_harness_with_provider_options(
runtime_metrics_enabled,
/*beta_features_header*/ None,
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
/*parallel_reasoning_summaries_enabled*/
config.features.enabled(Feature::ParallelReasoningSummaries),
/*attestation_provider*/ None,
);
+77 -4
View File
@@ -1,6 +1,7 @@
#![cfg(not(target_os = "windows"))]
use anyhow::Ok;
use codex_features::Feature;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::ReasoningSummary;
@@ -1085,6 +1086,63 @@ async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let TestCodex { codex, .. } = test_codex().build(&server).await?;
let stream = sse(vec![
ev_response_created("resp-1"),
ev_reasoning_item_added("reasoning-1", &[""]),
ev_reasoning_summary_text_delta("step one"),
ev_reasoning_item("reasoning-1", &["step one"], &[]),
ev_completed("resp-1"),
]);
let response_mock = mount_sse_once(&server, stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "reason through it".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: Default::default(),
})
.await?;
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ItemStarted(ItemStartedEvent {
item: TurnItem::Reasoning(item),
..
}) => Some(item.clone()),
_ => None,
})
.await;
let delta_event = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ReasoningContentDelta(event) => Some(event.clone()),
_ => None,
})
.await;
assert_eq!(delta_event.item_id, reasoning_item.id);
assert_eq!(delta_event.delta, "step one");
assert_eq!(
response_mock
.single_request()
.body_json()
.get("stream_options"),
None
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn concurrent_cutoff_emits_only_current_reasoning_summary_done() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
@@ -1097,6 +1155,7 @@ async fn concurrent_cutoff_emits_only_current_reasoning_summary_done() -> anyhow
})
.with_config(|config| {
config.model_reasoning_summary = Some(ReasoningSummary::Auto);
let _ = config.features.enable(Feature::ParallelReasoningSummaries);
})
.build(&server)
.await?;
@@ -1124,14 +1183,14 @@ async fn concurrent_cutoff_emits_only_current_reasoning_summary_done() -> anyhow
// Once a later output item starts, summaries for the earlier item are stale.
summary_done("reasoning-1", 1, "late step"),
ev_output_text_delta("Done"),
with_output_index(
ev_reasoning_item("reasoning-1", &["step one", "late step"], &[]),
/*output_index*/ 0,
),
with_output_index(
ev_assistant_message("message-1", "Done"),
/*output_index*/ 1,
),
with_output_index(
ev_reasoning_item("reasoning-1", &["step one", "late step"], &[]),
/*output_index*/ 0,
),
ev_completed("resp-1"),
]),
)
@@ -1159,10 +1218,22 @@ async fn concurrent_cutoff_emits_only_current_reasoning_summary_done() -> anyhow
})
.await;
let mut started_item_ids = vec![reasoning_item.id.clone()];
let mut completed_item_ids = Vec::new();
let mut summary_deltas = Vec::new();
let mut summary_sections = Vec::new();
loop {
match wait_for_event(&codex, |_| true).await {
EventMsg::ItemStarted(ItemStartedEvent { item, .. })
if matches!(&item, TurnItem::Reasoning(_) | TurnItem::AgentMessage(_)) =>
{
started_item_ids.push(item.id());
}
EventMsg::ItemCompleted(ItemCompletedEvent { item, .. })
if matches!(&item, TurnItem::Reasoning(_) | TurnItem::AgentMessage(_)) =>
{
completed_item_ids.push(item.id());
}
EventMsg::ReasoningContentDelta(event) => {
summary_deltas.push((event.item_id, event.delta, event.summary_index));
}
@@ -1178,6 +1249,8 @@ async fn concurrent_cutoff_emits_only_current_reasoning_summary_done() -> anyhow
summary_deltas,
vec![(reasoning_item.id, "step one".to_string(), 0)]
);
assert_eq!(started_item_ids, vec!["reasoning-1", "message-1"]);
assert_eq!(completed_item_ids, vec!["reasoning-1", "message-1"]);
assert_eq!(summary_sections, Vec::new());
assert_eq!(
response_mock.single_request().body_json()["stream_options"]["reasoning_summary_delivery"]