mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
Expose lifecycle timestamps on app-server thread items (#57172)
## 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
This commit is contained in:
@@ -17,7 +17,7 @@ use crate::history_cell::split_reasoning_summary_parts;
|
||||
use crate::inline_visualization::InlineVisualizationContext;
|
||||
use crate::legacy_core::config::Config;
|
||||
use codex_app_server_protocol::Thread;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadItemPayload;
|
||||
use codex_app_server_protocol::UserInput;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
@@ -107,7 +107,7 @@ pub(crate) fn thread_to_transcript_cells(
|
||||
thread_items_to_transcript_cells(
|
||||
thread_id,
|
||||
&cwd,
|
||||
turn.items,
|
||||
turn.items.into_iter().map(|entry| entry.payload),
|
||||
raw_reasoning_visibility,
|
||||
config,
|
||||
)
|
||||
@@ -124,7 +124,7 @@ pub(crate) fn thread_to_transcript_cells(
|
||||
pub(crate) fn thread_items_to_transcript_cells(
|
||||
thread_id: Option<ThreadId>,
|
||||
cwd: &AbsolutePathBuf,
|
||||
items: impl IntoIterator<Item = ThreadItem>,
|
||||
items: impl IntoIterator<Item = ThreadItemPayload>,
|
||||
raw_reasoning_visibility: RawReasoningVisibility,
|
||||
config: Option<&Config>,
|
||||
) -> TranscriptCells {
|
||||
@@ -134,7 +134,7 @@ pub(crate) fn thread_items_to_transcript_cells(
|
||||
let mut cells: TranscriptCells = Vec::new();
|
||||
let mut pending = None;
|
||||
for item in items {
|
||||
if matches!(item, ThreadItem::Reasoning { .. })
|
||||
if matches!(item, ThreadItemPayload::Reasoning { .. })
|
||||
&& let Some(group) = &mut pending
|
||||
{
|
||||
for cell in item_to_cells(
|
||||
@@ -156,7 +156,7 @@ pub(crate) fn thread_items_to_transcript_cells(
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
item @ ThreadItem::McpToolCall { .. } => {
|
||||
item @ ThreadItemPayload::McpToolCall { .. } => {
|
||||
let Some(call) = tools::McpHistory::from_item(item) else {
|
||||
PendingActivity::flush(&mut pending, &mut cells);
|
||||
continue;
|
||||
@@ -179,7 +179,7 @@ pub(crate) fn thread_items_to_transcript_cells(
|
||||
cells.push(Arc::new(cell));
|
||||
}
|
||||
}
|
||||
item @ ThreadItem::CommandExecution { .. } => {
|
||||
item @ ThreadItemPayload::CommandExecution { .. } => {
|
||||
if matches!(pending, Some(PendingActivity::Computer(_))) {
|
||||
PendingActivity::flush(&mut pending, &mut cells);
|
||||
}
|
||||
@@ -220,14 +220,14 @@ pub(crate) fn thread_items_to_transcript_cells(
|
||||
|
||||
/// Project one item without changing the active widget or its turn lifecycle.
|
||||
fn item_to_cells(
|
||||
item: ThreadItem,
|
||||
item: ThreadItemPayload,
|
||||
cwd: &AbsolutePathBuf,
|
||||
raw_reasoning_visibility: RawReasoningVisibility,
|
||||
inline_visualization_context: Option<InlineVisualizationContext>,
|
||||
) -> TranscriptCells {
|
||||
let mut cells: TranscriptCells = Vec::new();
|
||||
match item {
|
||||
ThreadItem::UserMessage {
|
||||
ThreadItemPayload::UserMessage {
|
||||
id,
|
||||
client_id,
|
||||
content,
|
||||
@@ -266,7 +266,7 @@ fn item_to_cells(
|
||||
remote_image_urls: item.image_urls(),
|
||||
}));
|
||||
}
|
||||
ThreadItem::AgentMessage { text, .. } => {
|
||||
ThreadItemPayload::AgentMessage { text, .. } => {
|
||||
let parsed = parse_assistant_markdown(&text, cwd.as_path());
|
||||
if !parsed.visible_markdown.trim().is_empty() {
|
||||
cells.push(Arc::new(AgentMarkdownCell::new_with_inline_visualizations(
|
||||
@@ -276,7 +276,7 @@ fn item_to_cells(
|
||||
)));
|
||||
}
|
||||
}
|
||||
ThreadItem::FunctionCallOutput {
|
||||
ThreadItemPayload::FunctionCallOutput {
|
||||
name,
|
||||
namespace,
|
||||
output,
|
||||
@@ -296,7 +296,7 @@ fn item_to_cells(
|
||||
)));
|
||||
}
|
||||
}
|
||||
ThreadItem::Plan { text, .. } => {
|
||||
ThreadItemPayload::Plan { text, .. } => {
|
||||
if !text.trim().is_empty() {
|
||||
cells.push(Arc::new(crate::history_cell::new_proposed_plan(
|
||||
text,
|
||||
@@ -304,7 +304,7 @@ fn item_to_cells(
|
||||
)));
|
||||
}
|
||||
}
|
||||
ThreadItem::Reasoning {
|
||||
ThreadItemPayload::Reasoning {
|
||||
id,
|
||||
summary,
|
||||
content,
|
||||
@@ -329,7 +329,7 @@ fn item_to_cells(
|
||||
cells.push(Arc::new(cell));
|
||||
}
|
||||
}
|
||||
item @ ThreadItem::CommandExecution { .. } => {
|
||||
item @ ThreadItemPayload::CommandExecution { .. } => {
|
||||
if let Some(command) = tools::CommandHistory::from_item(item) {
|
||||
cells.push(Arc::new(command.into_cell()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user