mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
Release superseded assistant text from live paginated history (#58751)
## Why Live paginated history retains completed assistant text even when summaries only need the latest message. Release older text for threads with persisted history while preserving full resume responses. ## What changed - Evict superseded assistant text from live turn state while preserving corrected items and the latest assistant message. - Restore evicted text for full resume responses from prepared history pages, fetching remaining items by ID in bounded batches. Return an error if saved text is missing. - Only load the active turn when it belongs in the requested initial page, and use the item ID index for targeted history reads. ## Testing Add reducer coverage for text eviction and corrected messages. Extend running-thread resume coverage to compare full and summary items with live notifications, exercise restoration when the prepared page lacks the active turn, and check missing-text errors. GitOrigin-RevId: 3229c0cea820f117dc6331312f1e6056a8d1d52c
This commit is contained in:
@@ -398,6 +398,37 @@ impl ThreadHistoryBuilder {
|
||||
.map(|turn| turn.rollout_start_index)
|
||||
}
|
||||
|
||||
/// Keeps the latest assistant item in memory for live summaries. Full reconnects
|
||||
/// load older text from history. Rollout replay uses the regular reducer.
|
||||
pub fn handle_live_paginated_event(&mut self, event: &EventMsg) {
|
||||
let previous_len = self
|
||||
.current_turn
|
||||
.as_ref()
|
||||
.map_or(0, |turn| turn.items.len());
|
||||
self.handle_event_for_history_mode(event, ThreadHistoryMode::Paginated);
|
||||
if let EventMsg::ItemCompleted(completed) = event
|
||||
&& let codex_protocol::items::TurnItem::AgentMessage(item) = &completed.item
|
||||
&& let Some(turn) = self
|
||||
.current_turn
|
||||
.as_mut()
|
||||
.filter(|turn| turn.id == completed.turn_id)
|
||||
{
|
||||
if turn.items.len() > previous_len {
|
||||
if let Some(index) = turn.evictable_agent_item.replace(previous_len)
|
||||
&& let ThreadItemPayload::AgentMessage { text, .. } = &mut turn.items[index]
|
||||
{
|
||||
*text = String::new();
|
||||
}
|
||||
} else if turn
|
||||
.evictable_agent_item
|
||||
.is_some_and(|index| turn.items[index].id() == item.id)
|
||||
{
|
||||
// A read by ID could still find the prior version of a corrected item.
|
||||
turn.evictable_agent_item = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared reducer for persisted rollout replay and in-memory current-turn
|
||||
/// tracking used by running thread resume/rejoin.
|
||||
///
|
||||
@@ -1561,6 +1592,7 @@ impl ThreadHistoryBuilder {
|
||||
root_turn_id: None,
|
||||
items: Vec::new(),
|
||||
item_index: TurnItemIndex::default(),
|
||||
evictable_agent_item: None,
|
||||
item_times: HashMap::new(),
|
||||
error: None,
|
||||
status: TurnStatus::Completed,
|
||||
@@ -1889,6 +1921,7 @@ struct PendingTurn {
|
||||
root_turn_id: Option<String>,
|
||||
items: Vec<ThreadItemPayload>,
|
||||
item_index: TurnItemIndex,
|
||||
evictable_agent_item: Option<usize>,
|
||||
item_times: HashMap<String, ItemTimes>,
|
||||
error: Option<TurnError>,
|
||||
status: TurnStatus,
|
||||
@@ -4344,6 +4377,43 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_paginated_turn_releases_superseded_agent_text() {
|
||||
let mut builder = ThreadHistoryBuilder::new();
|
||||
let turn_id = builder.ensure_turn().id.clone();
|
||||
for (id, text) in [
|
||||
("a", "old"),
|
||||
("b", "draft"),
|
||||
("b", "corrected"),
|
||||
("c", "latest"),
|
||||
] {
|
||||
builder.handle_live_paginated_event(&EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id: ThreadId::default(),
|
||||
turn_id: turn_id.clone(),
|
||||
item: CoreTurnItem::AgentMessage(codex_protocol::items::AgentMessageItem {
|
||||
id: id.into(),
|
||||
content: vec![codex_protocol::items::AgentMessageContent::Text {
|
||||
text: text.into(),
|
||||
}],
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
delivery: None,
|
||||
questions: None,
|
||||
}),
|
||||
started_at_ms: None,
|
||||
completed_at_ms: 1,
|
||||
}));
|
||||
}
|
||||
let mut texts = builder.current_turn.unwrap().items.into_iter().map(|item| {
|
||||
let ThreadItemPayload::AgentMessage { text, .. } = item else {
|
||||
panic!("expected assistant item")
|
||||
};
|
||||
text
|
||||
});
|
||||
assert_eq!(texts.next().unwrap().capacity(), 0);
|
||||
assert_eq!(texts.collect::<Vec<_>>(), ["corrected", "latest"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_turn_summary_preserves_selection_and_completed_metadata() {
|
||||
let mut builder = ThreadHistoryBuilder::new();
|
||||
|
||||
Reference in New Issue
Block a user