[core] Support interleaved response items (#30876)

## Summary

- preserve reasoning item IDs on summary part and text delta events
- track streamed response items by ID so reasoning summaries can
continue after later items begin
- keep TUI output complete and deduplicated when reasoning and
final-answer events interleave

## Stack

- **1/2: this PR — interleaved item support**
- 2/2: #30752 — wire reasoning summary delivery configuration through
the CLI and app-server

## Validation

- just test -p codex-api preserves_reasoning_summary_item_ids
- just test -p codex-core
interleaved_reasoning_summary_events_keep_reasoning_item_metadata
- just test -p codex-tui
live_reasoning_summary_is_not_rendered_twice_when_item_completes
- just fix -p codex-api -p codex-core -p codex-tui
- just fmt
This commit is contained in:
Alexi Christakis
2026-07-06 09:12:45 -07:00
committed by GitHub
parent bce481fdcb
commit 8917244f7d
9 changed files with 246 additions and 81 deletions
+2
View File
@@ -101,6 +101,7 @@ pub enum ResponseEvent {
delta: String,
},
ReasoningSummaryDelta {
item_id: String,
delta: String,
summary_index: i64,
},
@@ -109,6 +110,7 @@ pub enum ResponseEvent {
content_index: i64,
},
ReasoningSummaryPartAdded {
item_id: String,
summary_index: i64,
},
RateLimits(RateLimitSnapshot),
+44 -2
View File
@@ -351,8 +351,11 @@ pub fn process_responses_event(
}
}
"response.reasoning_summary_text.delta" => {
if let (Some(delta), Some(summary_index)) = (event.delta, event.summary_index) {
if let (Some(item_id), Some(delta), Some(summary_index)) =
(event.item_id, event.delta, event.summary_index)
{
return Ok(Some(ResponseEvent::ReasoningSummaryDelta {
item_id,
delta,
summary_index,
}));
@@ -444,8 +447,9 @@ pub fn process_responses_event(
}
}
"response.reasoning_summary_part.added" => {
if let Some(summary_index) = event.summary_index {
if let (Some(item_id), Some(summary_index)) = (event.item_id, event.summary_index) {
return Ok(Some(ResponseEvent::ReasoningSummaryPartAdded {
item_id,
summary_index,
}));
}
@@ -796,6 +800,44 @@ mod tests {
}
}
#[tokio::test]
async fn preserves_reasoning_summary_item_ids() {
let events = run_sse(vec![
json!({
"type": "response.reasoning_summary_part.added",
"item_id": "reasoning-1",
"summary_index": 0
}),
json!({
"type": "response.reasoning_summary_text.delta",
"item_id": "reasoning-1",
"summary_index": 0,
"delta": "Checking"
}),
json!({
"type": "response.completed",
"response": { "id": "resp1" }
}),
])
.await;
assert_matches!(
&events[0],
ResponseEvent::ReasoningSummaryPartAdded {
item_id,
summary_index: 0,
} if item_id == "reasoning-1"
);
assert_matches!(
&events[1],
ResponseEvent::ReasoningSummaryDelta {
item_id,
delta,
summary_index: 0,
} if item_id == "reasoning-1" && delta == "Checking"
);
}
#[tokio::test]
async fn error_when_missing_completed() {
let item1 = json!({