mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
## What changed - Add nullable `startedAtMs` and `completedAtMs` fields to `ThreadItem` in live notifications and turn history. Values are Unix milliseconds when recorded. - Preserve recorded item times through history reconstruction, pagination, and resume, including command start times recorded during approval. - Separate the Rust item payload into `ThreadItemPayload` while keeping the JSON payload flat, and update consumers, schemas, and generated TypeScript and Python types. ## Testing Extend coverage for timestamp serialization, history reconstruction, paginated reads and resume, and persisted Python SDK items. GitOrigin-RevId: ecbcee95af52db7c807b46a6e9deb30e5fba1f80
80 lines
2.5 KiB
Rust
80 lines
2.5 KiB
Rust
//! Apply live exploration compatibility to replay and repair groups split by history pages.
|
|
|
|
use super::tools::CommandHistory;
|
|
use crate::exec_cell::ExecCell;
|
|
use crate::history_cell::HistoryCell;
|
|
use codex_app_server_protocol::ThreadItemPayload;
|
|
use codex_app_server_protocol::Turn;
|
|
use std::sync::Arc;
|
|
|
|
pub(crate) fn join_exploration_groups(
|
|
older: &Arc<dyn HistoryCell>,
|
|
newer: &Arc<dyn HistoryCell>,
|
|
turns: &[Turn],
|
|
) -> Option<Arc<dyn HistoryCell>> {
|
|
let older = older.as_any().downcast_ref::<ExecCell>()?;
|
|
let newer = newer.as_any().downcast_ref::<ExecCell>()?;
|
|
let items = adjacent_items(older, newer, turns)?;
|
|
let mut group = completed_group(items)?;
|
|
group.group.details = newer.group.details.clone();
|
|
group
|
|
.group
|
|
.details
|
|
.prepend(older.group.details.clone(), older.group.calls.len());
|
|
Some(Arc::new(group))
|
|
}
|
|
|
|
pub(crate) fn older_exploration_group(
|
|
older: &dyn HistoryCell,
|
|
newer: &ExecCell,
|
|
turns: &[Turn],
|
|
) -> Option<ExecCell> {
|
|
let older = older.as_any().downcast_ref::<ExecCell>()?;
|
|
let items = adjacent_items(older, newer, turns)?;
|
|
let older_items = items
|
|
.iter()
|
|
.filter(|entry| !matches!(&entry.payload, ThreadItemPayload::Reasoning { .. }))
|
|
.take(older.group.calls.len())
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
let mut group = completed_group(&older_items)?;
|
|
group.group.details = older.group.details.clone();
|
|
Some(group)
|
|
}
|
|
|
|
fn adjacent_items<'a>(
|
|
older: &ExecCell,
|
|
newer: &ExecCell,
|
|
turns: &'a [Turn],
|
|
) -> Option<&'a [codex_app_server_protocol::ThreadItem]> {
|
|
if !older.is_exploring_cell() || older.should_flush() || !newer.is_exploring_cell() {
|
|
return None;
|
|
}
|
|
let ids = older
|
|
.iter_calls()
|
|
.chain(newer.iter_calls())
|
|
.map(|call| call.call_id.as_str())
|
|
.collect::<Vec<_>>();
|
|
super::computer_groups::adjacent_activity_items(&ids, turns)
|
|
}
|
|
|
|
pub(super) fn completed_group(items: &[codex_app_server_protocol::ThreadItem]) -> Option<ExecCell> {
|
|
let mut group: Option<ExecCell> = None;
|
|
for entry in items {
|
|
let item = &entry.payload;
|
|
if matches!(item, ThreadItemPayload::Reasoning { .. }) {
|
|
continue;
|
|
}
|
|
let call = CommandHistory::from_item(item.clone())?.into_cell();
|
|
if !call.is_exploring_cell() {
|
|
return None;
|
|
}
|
|
if let Some(group) = &mut group {
|
|
group.append_completed(call).ok()?;
|
|
} else {
|
|
group = Some(call);
|
|
}
|
|
}
|
|
group
|
|
}
|