Skip to content

Commit 210c818

Browse files
committed
Add timestamp to exec json stream events
Signed-off-by: Jörg Hubacher <[email protected]>
1 parent 13c42a0 commit 210c818

File tree

12 files changed

+50
-3
lines changed

12 files changed

+50
-3
lines changed

codex-rs/core/src/codex.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,19 @@ fn maybe_push_chat_wire_api_deprecation(
204204
});
205205
}
206206

207+
fn session_timestamp_from_history(initial_history: &InitialHistory) -> Option<String> {
208+
let items = match initial_history {
209+
InitialHistory::New => return None,
210+
InitialHistory::Resumed(resumed) => resumed.history.as_slice(),
211+
InitialHistory::Forked(items) => items.as_slice(),
212+
};
213+
214+
items.iter().find_map(|item| match item {
215+
RolloutItem::SessionMeta(line) => Some(line.meta.timestamp.clone()),
216+
_ => None,
217+
})
218+
}
219+
207220
impl Codex {
208221
/// Spawn a new [`Codex`] and initialize the session.
209222
pub async fn spawn(
@@ -597,6 +610,8 @@ impl Session {
597610
anyhow::Error::from(e)
598611
})?;
599612
let rollout_path = rollout_recorder.rollout_path.clone();
613+
let session_timestamp = session_timestamp_from_history(&initial_history)
614+
.or_else(|| rollout_recorder.session_timestamp());
600615

601616
let mut post_session_configured_events = Vec::<Event>::new();
602617

@@ -685,6 +700,7 @@ impl Session {
685700
id: INITIAL_SUBMIT_ID.to_owned(),
686701
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
687702
session_id: conversation_id,
703+
timestamp: session_timestamp,
688704
model: session_configuration.model.clone(),
689705
model_provider_id: config.model_provider_id.clone(),
690706
approval_policy: session_configuration.approval_policy.value(),

codex-rs/core/src/rollout/recorder.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ use codex_protocol::protocol::SessionSource;
4747
pub struct RolloutRecorder {
4848
tx: Sender<RolloutCmd>,
4949
pub(crate) rollout_path: PathBuf,
50+
session_timestamp: Option<String>,
5051
}
5152

5253
#[derive(Clone)]
@@ -160,6 +161,9 @@ impl RolloutRecorder {
160161
None,
161162
),
162163
};
164+
let session_timestamp = meta
165+
.as_ref()
166+
.map(|session_meta| session_meta.timestamp.clone());
163167

164168
// Clone the cwd for the spawned task to collect git info asynchronously
165169
let cwd = config.cwd.clone();
@@ -174,7 +178,15 @@ impl RolloutRecorder {
174178
// driver instead of blocking the runtime.
175179
tokio::task::spawn(rollout_writer(file, rx, meta, cwd));
176180

177-
Ok(Self { tx, rollout_path })
181+
Ok(Self {
182+
tx,
183+
rollout_path,
184+
session_timestamp,
185+
})
186+
}
187+
188+
pub fn session_timestamp(&self) -> Option<String> {
189+
self.session_timestamp.clone()
178190
}
179191

180192
pub(crate) async fn record_items(&self, items: &[RolloutItem]) -> std::io::Result<()> {

codex-rs/exec/src/event_processor_with_jsonl_output.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ impl EventProcessorWithJsonOutput {
170170
fn handle_session_configured(&self, payload: &SessionConfiguredEvent) -> Vec<ThreadEvent> {
171171
vec![ThreadEvent::ThreadStarted(ThreadStartedEvent {
172172
thread_id: payload.session_id.to_string(),
173+
timestamp: payload.timestamp.clone(),
173174
})]
174175
}
175176

codex-rs/exec/src/exec_events.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ pub enum ThreadEvent {
3939
pub struct ThreadStartedEvent {
4040
/// The identified of the new thread. Can be used to resume the thread later.
4141
pub thread_id: String,
42+
/// RFC3339 timestamp (UTC) when the session was created.
43+
#[serde(skip_serializing_if = "Option::is_none")]
44+
pub timestamp: Option<String>,
4245
}
4346

4447
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, Default)]

codex-rs/exec/tests/event_processor_with_json_output.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ fn session_configured_produces_thread_started_event() {
7272
codex_protocol::ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")
7373
.unwrap();
7474
let rollout_path = PathBuf::from("/tmp/rollout.json");
75+
let timestamp = Some("2025-09-05T16:53:11.850Z".to_string());
7576
let ev = event(
7677
"e1",
7778
EventMsg::SessionConfigured(SessionConfiguredEvent {
7879
session_id,
80+
timestamp: timestamp.clone(),
7981
model: "codex-mini-latest".to_string(),
8082
model_provider_id: "test-provider".to_string(),
8183
approval_policy: AskForApproval::Never,
@@ -93,6 +95,7 @@ fn session_configured_produces_thread_started_event() {
9395
out,
9496
vec![ThreadEvent::ThreadStarted(ThreadStartedEvent {
9597
thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
98+
timestamp,
9699
})]
97100
);
98101
}

codex-rs/mcp-server/src/outgoing_message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ mod tests {
257257
id: "1".to_string(),
258258
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
259259
session_id: conversation_id,
260+
timestamp: None,
260261
model: "gpt-4o".to_string(),
261262
model_provider_id: "test-provider".to_string(),
262263
approval_policy: AskForApproval::Never,
@@ -296,6 +297,7 @@ mod tests {
296297
let rollout_file = NamedTempFile::new()?;
297298
let session_configured_event = SessionConfiguredEvent {
298299
session_id: conversation_id,
300+
timestamp: None,
299301
model: "gpt-4o".to_string(),
300302
model_provider_id: "test-provider".to_string(),
301303
approval_policy: AskForApproval::Never,

codex-rs/protocol/src/protocol.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,9 @@ pub struct SkillsListEntry {
17741774
pub struct SessionConfiguredEvent {
17751775
/// Name left as session_id instead of conversation_id for backwards compatibility.
17761776
pub session_id: ConversationId,
1777+
/// RFC3339 timestamp (UTC) when the session was created.
1778+
#[serde(skip_serializing_if = "Option::is_none")]
1779+
pub timestamp: Option<String>,
17771780

17781781
/// Tell the client what model is being queried.
17791782
pub model: String,
@@ -1938,6 +1941,7 @@ mod tests {
19381941
id: "1234".to_string(),
19391942
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
19401943
session_id: conversation_id,
1944+
timestamp: None,
19411945
model: "codex-mini-latest".to_string(),
19421946
model_provider_id: "openai".to_string(),
19431947
approval_policy: AskForApproval::Never,

codex-rs/tui/src/app.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,7 @@ mod tests {
15551555
let make_header = |is_first| {
15561556
let event = SessionConfiguredEvent {
15571557
session_id: ConversationId::new(),
1558+
timestamp: None,
15581559
model: "gpt-test".to_string(),
15591560
model_provider_id: "test-provider".to_string(),
15601561
approval_policy: AskForApproval::Never,
@@ -1610,6 +1611,7 @@ mod tests {
16101611
let conversation_id = ConversationId::new();
16111612
let event = SessionConfiguredEvent {
16121613
session_id: conversation_id,
1614+
timestamp: None,
16131615
model: "gpt-test".to_string(),
16141616
model_provider_id: "test-provider".to_string(),
16151617
approval_policy: AskForApproval::Never,

codex-rs/tui/src/chatwidget/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ async fn resumed_initial_messages_render_history() {
105105
let rollout_file = NamedTempFile::new().unwrap();
106106
let configured = codex_core::protocol::SessionConfiguredEvent {
107107
session_id: conversation_id,
108+
timestamp: None,
108109
model: "test-model".to_string(),
109110
model_provider_id: "test-provider".to_string(),
110111
approval_policy: AskForApproval::Never,

codex-rs/tui2/src/app.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,7 @@ mod tests {
23092309
let make_header = |is_first| {
23102310
let event = SessionConfiguredEvent {
23112311
session_id: ConversationId::new(),
2312+
timestamp: None,
23122313
model: "gpt-test".to_string(),
23132314
model_provider_id: "test-provider".to_string(),
23142315
approval_policy: AskForApproval::Never,
@@ -2604,6 +2605,7 @@ mod tests {
26042605
let conversation_id = ConversationId::new();
26052606
let event = SessionConfiguredEvent {
26062607
session_id: conversation_id,
2608+
timestamp: None,
26072609
model: "gpt-test".to_string(),
26082610
model_provider_id: "test-provider".to_string(),
26092611
approval_policy: AskForApproval::Never,

0 commit comments

Comments
 (0)