mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex-analytics] add item lifecycle timing (#20514)
## Why Tool families already disagree on what their existing `duration` fields mean, so lifecycle latency should live on the shared item envelope instead of being inferred from per-tool execution fields. Carrying that envelope through app-server notifications gives downstream consumers one reusable timing signal without pretending every tool has the same execution semantics. ## What changed - Adds `started_at_ms` to core `ItemStartedEvent` values and `completed_at_ms` to core `ItemCompletedEvent` values. - Populates those timestamps in the shared session lifecycle emitters, so protocol-native items get timing without each producer tracking its own clock state. - Exposes `startedAtMs` on app-server `item/started` notifications and `completedAtMs` on `item/completed` notifications. - Maps the lifecycle timestamps through the app-server boundary while leaving legacy-converted notifications nullable when no lifecycle timestamp exists. - Regenerates the app-server JSON schema and TypeScript fixtures for the notification-envelope change and updates downstream fixtures that construct those notifications directly. - Extends the existing web-search and image-generation integration flows to assert the new lifecycle timestamps on the native item events. ## Verification - `cargo check -p codex-protocol -p codex-core -p codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec -p codex-app-server-client` - `cargo test -p codex-core --test all web_search_item_is_emitted` - `cargo test -p codex-core --test all image_generation_call_event_is_emitted` - `cargo test -p codex-app-server-protocol` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514). * #18748 * #18747 * #17090 * #17089 * __->__ #20514
This commit is contained in:
committed by
GitHub
Unverified
parent
e7e6267ab3
commit
aee1fe2659
@@ -1139,6 +1139,7 @@ mod tests {
|
||||
ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: text.to_string(),
|
||||
@@ -2012,6 +2013,7 @@ mod tests {
|
||||
codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: "hello".to_string(),
|
||||
|
||||
@@ -1932,6 +1932,11 @@
|
||||
},
|
||||
"ItemCompletedNotification": {
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
@@ -1943,6 +1948,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
@@ -2030,6 +2036,11 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2039,6 +2050,7 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -5896,4 +5908,4 @@
|
||||
}
|
||||
],
|
||||
"title": "ServerNotification"
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -10109,6 +10109,11 @@
|
||||
"ItemCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/v2/ThreadItem"
|
||||
},
|
||||
@@ -10120,6 +10125,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
@@ -10213,6 +10219,11 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/v2/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -10222,6 +10233,7 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -18261,4 +18273,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocol",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -6762,6 +6762,11 @@
|
||||
"ItemCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
@@ -6773,6 +6778,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
@@ -6866,6 +6872,11 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6875,6 +6886,7 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -16146,4 +16158,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocolV2",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -1370,6 +1370,11 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
@@ -1381,10 +1386,11 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"title": "ItemCompletedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1373,6 +1373,11 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1382,9 +1387,10 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"title": "ItemStartedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -3,4 +3,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ThreadItem } from "./ThreadItem";
|
||||
|
||||
export type ItemCompletedNotification = { item: ThreadItem, threadId: string, turnId: string, };
|
||||
export type ItemCompletedNotification = { item: ThreadItem, threadId: string, turnId: string,
|
||||
/**
|
||||
* Unix timestamp (in milliseconds) when this item lifecycle completed.
|
||||
*/
|
||||
completedAtMs: number, };
|
||||
|
||||
+5
-1
@@ -3,4 +3,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ThreadItem } from "./ThreadItem";
|
||||
|
||||
export type ItemStartedNotification = { item: ThreadItem, threadId: string, turnId: string, };
|
||||
export type ItemStartedNotification = { item: ThreadItem, threadId: string, turnId: string,
|
||||
/**
|
||||
* Unix timestamp (in milliseconds) when this item lifecycle started.
|
||||
*/
|
||||
startedAtMs: number, };
|
||||
|
||||
@@ -69,6 +69,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id: response.turn_id,
|
||||
item,
|
||||
completed_at_ms: response.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentSpawnBegin(begin_event) => {
|
||||
@@ -87,6 +88,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
started_at_ms: begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentSpawnEnd(end_event) => {
|
||||
@@ -125,6 +127,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
completed_at_ms: end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentInteractionBegin(begin_event) => {
|
||||
@@ -144,6 +147,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
started_at_ms: begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentInteractionEnd(end_event) => {
|
||||
@@ -171,6 +175,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
completed_at_ms: end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabWaitingBegin(begin_event) => {
|
||||
@@ -194,6 +199,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
started_at_ms: begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabWaitingEnd(end_event) => {
|
||||
@@ -229,6 +235,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
completed_at_ms: end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabCloseBegin(begin_event) => {
|
||||
@@ -247,6 +254,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
started_at_ms: begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabCloseEnd(end_event) => {
|
||||
@@ -279,6 +287,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
completed_at_ms: end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabResumeBegin(begin_event) => {
|
||||
@@ -297,6 +306,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
started_at_ms: begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabResumeEnd(end_event) => {
|
||||
@@ -329,6 +339,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
completed_at_ms: end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::AgentMessageContentDelta(event) => {
|
||||
@@ -378,6 +389,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: item_started_event.item.into(),
|
||||
started_at_ms: item_started_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::ItemCompleted(item_completed_event) => {
|
||||
@@ -385,6 +397,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: item_completed_event.item.into(),
|
||||
completed_at_ms: item_completed_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::PatchApplyUpdated(event) => {
|
||||
@@ -400,6 +413,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: build_command_execution_begin_item(&exec_command_begin_event),
|
||||
started_at_ms: exec_command_begin_event.started_at_ms,
|
||||
})
|
||||
}
|
||||
EventMsg::ExecCommandOutputDelta(exec_command_output_delta_event) => {
|
||||
@@ -428,6 +442,7 @@ pub fn item_event_to_server_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: build_command_execution_end_item(&exec_command_end_event),
|
||||
completed_at_ms: exec_command_end_event.completed_at_ms,
|
||||
})
|
||||
}
|
||||
_ => unreachable!("unsupported item event"),
|
||||
@@ -480,6 +495,7 @@ mod tests {
|
||||
fn collab_resume_begin_maps_to_item_started_resume_agent() {
|
||||
let event = CollabResumeBeginEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
started_at_ms: 123,
|
||||
sender_thread_id: ThreadId::new(),
|
||||
receiver_thread_id: ThreadId::new(),
|
||||
receiver_agent_nickname: None,
|
||||
@@ -496,6 +512,7 @@ mod tests {
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: event.started_at_ms,
|
||||
item: ThreadItem::CollabAgentToolCall {
|
||||
id: event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
@@ -515,6 +532,7 @@ mod tests {
|
||||
fn collab_resume_end_maps_to_item_completed_resume_agent() {
|
||||
let event = CollabResumeEndEvent {
|
||||
call_id: "call-2".to_string(),
|
||||
completed_at_ms: 456,
|
||||
sender_thread_id: ThreadId::new(),
|
||||
receiver_thread_id: ThreadId::new(),
|
||||
receiver_agent_nickname: None,
|
||||
@@ -533,6 +551,7 @@ mod tests {
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
completed_at_ms: event.completed_at_ms,
|
||||
item: ThreadItem::CollabAgentToolCall {
|
||||
id: event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
|
||||
@@ -1356,6 +1356,7 @@ mod tests {
|
||||
id: "user-item-id".to_string(),
|
||||
content: Vec::new(),
|
||||
}),
|
||||
started_at_ms: 0,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: turn_id.to_string(),
|
||||
@@ -1820,6 +1821,7 @@ mod tests {
|
||||
call_id: "exec-1".into(),
|
||||
process_id: Some("pid-1".into()),
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "hello world".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
@@ -1983,6 +1985,7 @@ mod tests {
|
||||
codex_protocol::dynamic_tools::DynamicToolCallRequest {
|
||||
call_id: "dyn-1".into(),
|
||||
turn_id: "turn-1".into(),
|
||||
started_at_ms: 0,
|
||||
namespace: Some("codex_app".into()),
|
||||
tool: "lookup_ticket".into(),
|
||||
arguments: serde_json::json!({"id":"ABC-123"}),
|
||||
@@ -1991,6 +1994,7 @@ mod tests {
|
||||
EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
call_id: "dyn-1".into(),
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
namespace: Some("codex_app".into()),
|
||||
tool: "lookup_ticket".into(),
|
||||
arguments: serde_json::json!({"id":"ABC-123"}),
|
||||
@@ -2046,6 +2050,7 @@ mod tests {
|
||||
call_id: "exec-declined".into(),
|
||||
process_id: Some("pid-2".into()),
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["ls".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown { cmd: "ls".into() }],
|
||||
@@ -2293,6 +2298,7 @@ mod tests {
|
||||
call_id: "exec-late".into(),
|
||||
process_id: Some("pid-42".into()),
|
||||
turn_id: "turn-a".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "done".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
@@ -2384,6 +2390,7 @@ mod tests {
|
||||
call_id: "exec-unknown-turn".into(),
|
||||
process_id: Some("pid-42".into()),
|
||||
turn_id: "turn-missing".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "done".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
@@ -2732,6 +2739,7 @@ mod tests {
|
||||
}),
|
||||
EventMsg::CollabResumeEnd(codex_protocol::protocol::CollabResumeEndEvent {
|
||||
call_id: "resume-1".into(),
|
||||
completed_at_ms: 0,
|
||||
sender_thread_id: ThreadId::try_from("00000000-0000-0000-0000-000000000001")
|
||||
.expect("valid sender thread id"),
|
||||
receiver_thread_id: ThreadId::try_from("00000000-0000-0000-0000-000000000002")
|
||||
@@ -2788,6 +2796,7 @@ mod tests {
|
||||
}),
|
||||
EventMsg::CollabAgentSpawnEnd(codex_protocol::protocol::CollabAgentSpawnEndEvent {
|
||||
call_id: "spawn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
sender_thread_id,
|
||||
new_thread_id: Some(spawned_thread_id),
|
||||
new_agent_nickname: Some("Scout".into()),
|
||||
@@ -2849,6 +2858,7 @@ mod tests {
|
||||
EventMsg::CollabAgentInteractionBegin(
|
||||
codex_protocol::protocol::CollabAgentInteractionBeginEvent {
|
||||
call_id: "send-1".into(),
|
||||
started_at_ms: 0,
|
||||
sender_thread_id: sender,
|
||||
receiver_thread_id: receiver,
|
||||
prompt: "new task".into(),
|
||||
@@ -2857,6 +2867,7 @@ mod tests {
|
||||
EventMsg::CollabAgentInteractionEnd(
|
||||
codex_protocol::protocol::CollabAgentInteractionEndEvent {
|
||||
call_id: "send-1".into(),
|
||||
completed_at_ms: 0,
|
||||
sender_thread_id: sender,
|
||||
receiver_thread_id: receiver,
|
||||
receiver_agent_nickname: None,
|
||||
|
||||
@@ -6928,6 +6928,9 @@ pub struct ItemStartedNotification {
|
||||
pub item: ThreadItem,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
/// Unix timestamp (in milliseconds) when this item lifecycle started.
|
||||
#[ts(type = "number")]
|
||||
pub started_at_ms: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -6990,6 +6993,9 @@ pub struct ItemCompletedNotification {
|
||||
pub item: ThreadItem,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
/// Unix timestamp (in milliseconds) when this item lifecycle completed.
|
||||
#[ts(type = "number")]
|
||||
pub completed_at_ms: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
|
||||
@@ -116,6 +116,8 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::error;
|
||||
@@ -816,6 +818,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let notification = ItemStartedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: turn_id.clone(),
|
||||
started_at_ms: request.started_at_ms,
|
||||
item,
|
||||
};
|
||||
outgoing
|
||||
@@ -968,6 +971,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let started = ItemStartedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: event_turn_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
item: item.clone(),
|
||||
};
|
||||
outgoing
|
||||
@@ -976,6 +980,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let completed = ItemCompletedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: event_turn_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
item,
|
||||
};
|
||||
outgoing
|
||||
@@ -1025,6 +1030,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let started = ItemStartedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: event_turn_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
item: item.clone(),
|
||||
};
|
||||
outgoing
|
||||
@@ -1033,6 +1039,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let completed = ItemCompletedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: event_turn_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
item,
|
||||
};
|
||||
outgoing
|
||||
@@ -1368,6 +1375,7 @@ async fn start_command_execution_item(
|
||||
let notification = ItemStartedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id,
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
item: ThreadItem::CommandExecution {
|
||||
id: item_id,
|
||||
command,
|
||||
@@ -1427,6 +1435,7 @@ async fn complete_command_execution_item(
|
||||
let notification = ItemCompletedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
item,
|
||||
};
|
||||
outgoing
|
||||
@@ -1474,6 +1483,7 @@ pub(crate) async fn maybe_emit_hook_prompt_item_completed(
|
||||
let notification = ItemCompletedNotification {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: turn_id.to_string(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
item: ThreadItem::HookPrompt {
|
||||
id: hook_prompt.id,
|
||||
fragments: hook_prompt
|
||||
@@ -2077,6 +2087,13 @@ async fn on_command_execution_request_approval_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn now_unix_timestamp_ms() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis() as i64)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -41,6 +41,7 @@ use crate::session_prefix::format_subagent_notification_message;
|
||||
use crate::skills::SkillRenderSideEffects;
|
||||
use crate::skills_load_input_from_config;
|
||||
use crate::turn_metadata::TurnMetadataState;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use async_channel::Receiver;
|
||||
use async_channel::Sender;
|
||||
use chrono::Local;
|
||||
@@ -1652,6 +1653,7 @@ impl Session {
|
||||
thread_id: self.conversation_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
item: item.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -1669,6 +1671,7 @@ impl Session {
|
||||
thread_id: self.conversation_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
item,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::state::TaskKind;
|
||||
use crate::tools::format_exec_output_str;
|
||||
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use crate::user_shell_command::user_shell_command_record_item;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
@@ -164,6 +165,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
call_id: call_id.clone(),
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
@@ -236,6 +238,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
call_id,
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
@@ -260,6 +263,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
call_id: call_id.clone(),
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
command: display_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: parsed_cmd.clone(),
|
||||
@@ -304,6 +308,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
call_id,
|
||||
process_id: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
command: display_command,
|
||||
cwd,
|
||||
parsed_cmd,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::session::session::Session;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
@@ -77,6 +78,7 @@ pub(crate) async fn emit_exec_command_begin(
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: process_id.map(str::to_owned),
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
command: command.to_vec(),
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: parsed_cmd.to_vec(),
|
||||
@@ -472,6 +474,7 @@ async fn emit_exec_end(
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: exec_input.process_id.map(str::to_owned),
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
command: exec_input.command.to_vec(),
|
||||
cwd: exec_input.cwd.clone(),
|
||||
parsed_cmd: exec_input.parsed_cmd.to_vec(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallRequest;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
@@ -102,9 +103,11 @@ async fn request_dynamic_tool(
|
||||
}
|
||||
|
||||
let started_at = Instant::now();
|
||||
let started_at_ms = now_unix_timestamp_ms();
|
||||
let event = EventMsg::DynamicToolCallRequest(DynamicToolCallRequest {
|
||||
call_id: call_id.clone(),
|
||||
turn_id: turn_id.clone(),
|
||||
started_at_ms,
|
||||
namespace: namespace.clone(),
|
||||
tool: tool.clone(),
|
||||
arguments: arguments.clone(),
|
||||
@@ -116,6 +119,7 @@ async fn request_dynamic_tool(
|
||||
Some(response) => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
call_id,
|
||||
turn_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
namespace,
|
||||
tool,
|
||||
arguments,
|
||||
@@ -127,6 +131,7 @@ async fn request_dynamic_tool(
|
||||
None => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
call_id,
|
||||
turn_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
namespace,
|
||||
tool,
|
||||
arguments,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
@@ -34,6 +35,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
}
|
||||
@@ -54,6 +56,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseEndEvent {
|
||||
call_id: call_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname.clone(),
|
||||
@@ -75,6 +78,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
@@ -46,6 +47,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabResumeBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname.clone(),
|
||||
@@ -101,6 +103,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabResumeEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::agent::control::render_input_preview;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
@@ -45,6 +46,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentInteractionBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
prompt: prompt.clone(),
|
||||
@@ -67,6 +69,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentInteractionEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::agent::exceeds_thread_spawn_depth_limit;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::apply_role_to_config;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
@@ -50,6 +51,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentSpawnBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
prompt: prompt.clone(),
|
||||
model: args.model.clone().unwrap_or_default(),
|
||||
@@ -146,6 +148,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentSpawnEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
new_thread_id,
|
||||
new_agent_nickname,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::agent::status::is_final;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use futures::FutureExt;
|
||||
use futures::StreamExt;
|
||||
@@ -73,6 +74,7 @@ impl ToolHandler for Handler {
|
||||
.send_event(
|
||||
&turn,
|
||||
CollabWaitingBeginEvent {
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_ids: receiver_thread_ids.clone(),
|
||||
receiver_agents: receiver_agents.clone(),
|
||||
@@ -105,6 +107,7 @@ impl ToolHandler for Handler {
|
||||
CollabWaitingEndEvent {
|
||||
sender_thread_id: session.conversation_id,
|
||||
call_id: call_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
agent_statuses: build_wait_agent_statuses(
|
||||
&statuses,
|
||||
&receiver_agents,
|
||||
@@ -173,6 +176,7 @@ impl ToolHandler for Handler {
|
||||
CollabWaitingEndEvent {
|
||||
sender_thread_id: session.conversation_id,
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
agent_statuses,
|
||||
statuses: statuses_by_id,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
@@ -43,6 +44,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
}
|
||||
@@ -63,6 +65,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseEndEvent {
|
||||
call_id: call_id.clone(),
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname.clone(),
|
||||
@@ -87,6 +90,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabCloseEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id: agent_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use super::*;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -97,6 +98,7 @@ async fn handle_message_submission(
|
||||
&turn,
|
||||
CollabAgentInteractionBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
prompt: prompt.clone(),
|
||||
@@ -132,6 +134,7 @@ async fn handle_message_submission(
|
||||
&turn,
|
||||
CollabAgentInteractionEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_id,
|
||||
receiver_agent_nickname: receiver_agent.agent_nickname,
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::agent::control::render_input_preview;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::apply_role_to_config;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::Op;
|
||||
@@ -49,6 +50,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentSpawnBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
prompt: prompt.clone(),
|
||||
model: args.model.clone().unwrap_or_default(),
|
||||
@@ -168,6 +170,7 @@ impl ToolHandler for Handler {
|
||||
&turn,
|
||||
CollabAgentSpawnEndEvent {
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
new_thread_id,
|
||||
new_agent_nickname,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
@@ -48,6 +49,7 @@ impl ToolHandler for Handler {
|
||||
.send_event(
|
||||
&turn,
|
||||
CollabWaitingBeginEvent {
|
||||
started_at_ms: now_unix_timestamp_ms(),
|
||||
sender_thread_id: session.conversation_id,
|
||||
receiver_thread_ids: Vec::new(),
|
||||
receiver_agents: Vec::new(),
|
||||
@@ -71,6 +73,7 @@ impl ToolHandler for Handler {
|
||||
CollabWaitingEndEvent {
|
||||
sender_thread_id: session.conversation_id,
|
||||
call_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
agent_statuses: Vec::new(),
|
||||
statuses: HashMap::new(),
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ fn now_unix_timestamp_secs() -> i64 {
|
||||
now_unix_timestamp_ms() / 1000
|
||||
}
|
||||
|
||||
fn now_unix_timestamp_ms() -> i64 {
|
||||
pub(crate) fn now_unix_timestamp_ms() -> i64 {
|
||||
let duration = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -303,6 +303,15 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let started = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ItemStarted(ItemStartedEvent {
|
||||
item: TurnItem::WebSearch(item),
|
||||
started_at_ms,
|
||||
..
|
||||
}) => Some((item.clone(), *started_at_ms)),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
let begin = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::WebSearchBegin(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
@@ -311,16 +320,20 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
|
||||
let completed = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
item: TurnItem::WebSearch(item),
|
||||
completed_at_ms,
|
||||
..
|
||||
}) => Some(item.clone()),
|
||||
}) => Some((item.clone(), *completed_at_ms)),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(begin.call_id, "web-search-1");
|
||||
assert_eq!(completed.id, begin.call_id);
|
||||
assert_eq!(started.0.id, begin.call_id);
|
||||
assert!(started.1 > 0);
|
||||
assert_eq!(completed.0.id, begin.call_id);
|
||||
assert!(completed.1 > 0);
|
||||
assert_eq!(
|
||||
completed.action,
|
||||
completed.0.action,
|
||||
WebSearchAction::Search {
|
||||
query: Some("weather seattle".to_string()),
|
||||
queries: None,
|
||||
@@ -369,11 +382,29 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let started = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ItemStarted(ItemStartedEvent {
|
||||
item: TurnItem::ImageGeneration(item),
|
||||
started_at_ms,
|
||||
..
|
||||
}) => Some((item.clone(), *started_at_ms)),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
let begin = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ImageGenerationBegin(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
let completed = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
item: TurnItem::ImageGeneration(item),
|
||||
completed_at_ms,
|
||||
..
|
||||
}) => Some((item.clone(), *completed_at_ms)),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
let end = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::ImageGenerationEnd(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
@@ -381,6 +412,10 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> {
|
||||
.await;
|
||||
|
||||
assert_eq!(begin.call_id, call_id);
|
||||
assert_eq!(started.0.id, call_id);
|
||||
assert!(started.1 > 0);
|
||||
assert_eq!(completed.0.id, call_id);
|
||||
assert!(completed.1 > 0);
|
||||
assert_eq!(end.call_id, call_id);
|
||||
assert_eq!(end.status, "completed");
|
||||
assert_eq!(end.revised_prompt, Some("A tiny blue square".to_string()));
|
||||
|
||||
@@ -20,6 +20,7 @@ fn failed_turn_does_not_overwrite_output_last_message_file() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ fn command_execution_started_and_completed_translate_to_thread_events() {
|
||||
item: command_item,
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
assert_eq!(
|
||||
started,
|
||||
@@ -216,6 +217,7 @@ fn command_execution_started_and_completed_translate_to_thread_events() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
assert_eq!(
|
||||
@@ -250,6 +252,7 @@ fn empty_reasoning_items_are_ignored() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -274,6 +277,7 @@ fn unsupported_items_do_not_consume_synthetic_ids() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -295,6 +299,7 @@ fn unsupported_items_do_not_consume_synthetic_ids() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -327,6 +332,7 @@ fn reasoning_items_emit_summary_not_raw_content() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -362,6 +368,7 @@ fn web_search_completion_preserves_query_and_action() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -399,6 +406,7 @@ fn web_search_start_and_completion_reuse_item_id() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
|
||||
let completed = processor.collect_thread_events(ServerNotification::ItemCompleted(
|
||||
@@ -413,6 +421,7 @@ fn web_search_start_and_completion_reuse_item_id() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -472,6 +481,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
let completed = processor.collect_thread_events(ServerNotification::ItemCompleted(
|
||||
ItemCompletedNotification {
|
||||
@@ -492,6 +502,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -559,6 +570,7 @@ fn mcp_tool_call_failure_sets_failed_status() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -604,6 +616,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
let completed = processor.collect_thread_events(ServerNotification::ItemCompleted(
|
||||
ItemCompletedNotification {
|
||||
@@ -627,6 +640,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -695,6 +709,7 @@ fn collab_spawn_begin_and_end_emit_item_events() {
|
||||
},
|
||||
thread_id: "thread-parent".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
let completed = processor.collect_thread_events(ServerNotification::ItemCompleted(
|
||||
ItemCompletedNotification {
|
||||
@@ -717,6 +732,7 @@ fn collab_spawn_begin_and_end_emit_item_events() {
|
||||
},
|
||||
thread_id: "thread-parent".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -795,6 +811,7 @@ fn file_change_completion_maps_change_kinds() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -845,6 +862,7 @@ fn file_change_declined_maps_to_failed_status() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -882,6 +900,7 @@ fn agent_message_item_updates_final_message() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -916,6 +935,7 @@ fn agent_message_item_started_is_ignored() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
|
||||
assert_eq!(
|
||||
@@ -940,6 +960,7 @@ fn reasoning_item_completed_uses_synthetic_id() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -1296,6 +1317,7 @@ fn turn_completion_reconciles_started_items_from_turn_items() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
}));
|
||||
assert_eq!(
|
||||
started,
|
||||
@@ -1378,6 +1400,7 @@ fn turn_completion_overwrites_stale_final_message_from_turn_items() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -1426,6 +1449,7 @@ fn turn_completion_preserves_streamed_final_message_when_turn_items_are_empty()
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -1470,6 +1494,7 @@ fn failed_turn_clears_stale_final_message() {
|
||||
},
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ pub struct DynamicToolCallRequest {
|
||||
pub call_id: String,
|
||||
pub turn_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
#[serde(default)]
|
||||
pub namespace: Option<String>,
|
||||
pub tool: String,
|
||||
pub arguments: JsonValue,
|
||||
|
||||
@@ -1828,6 +1828,7 @@ pub struct ItemStartedEvent {
|
||||
pub thread_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub item: TurnItem,
|
||||
pub started_at_ms: i64,
|
||||
}
|
||||
|
||||
impl HasLegacyEvent for ItemStartedEvent {
|
||||
@@ -1854,6 +1855,15 @@ pub struct ItemCompletedEvent {
|
||||
pub thread_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub item: TurnItem,
|
||||
// Old rollout files may contain ItemCompleted events for PlanItem without
|
||||
// this field. Default to 0 so those persisted rollouts still deserialize
|
||||
// after tightening the core event contract.
|
||||
#[serde(default = "default_item_completed_at_ms")]
|
||||
pub completed_at_ms: i64,
|
||||
}
|
||||
|
||||
const fn default_item_completed_at_ms() -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
pub trait HasLegacyEvent {
|
||||
@@ -2348,6 +2358,8 @@ pub struct DynamicToolCallResponseEvent {
|
||||
pub call_id: String,
|
||||
/// Turn ID that this dynamic tool call belongs to.
|
||||
pub turn_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Dynamic tool namespace, when one was provided.
|
||||
#[serde(default)]
|
||||
pub namespace: Option<String>,
|
||||
@@ -3058,6 +3070,8 @@ pub struct ExecCommandBeginEvent {
|
||||
pub process_id: Option<String>,
|
||||
/// Turn ID that this command belongs to.
|
||||
pub turn_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// The command to be executed.
|
||||
pub command: Vec<String>,
|
||||
/// The command's working directory if not the default cwd for the agent.
|
||||
@@ -3082,6 +3096,8 @@ pub struct ExecCommandEndEvent {
|
||||
pub process_id: Option<String>,
|
||||
/// Turn ID that this command belongs to.
|
||||
pub turn_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// The command that was executed.
|
||||
pub command: Vec<String>,
|
||||
/// The command's working directory if not the default cwd for the agent.
|
||||
@@ -3750,6 +3766,8 @@ pub enum TurnAbortReason {
|
||||
pub struct CollabAgentSpawnBeginEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
|
||||
@@ -3789,6 +3807,8 @@ pub struct CollabAgentStatusEntry {
|
||||
pub struct CollabAgentSpawnEndEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the newly spawned agent, if it was created.
|
||||
@@ -3814,6 +3834,8 @@ pub struct CollabAgentSpawnEndEvent {
|
||||
pub struct CollabAgentInteractionBeginEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -3827,6 +3849,8 @@ pub struct CollabAgentInteractionBeginEvent {
|
||||
pub struct CollabAgentInteractionEndEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -3846,6 +3870,8 @@ pub struct CollabAgentInteractionEndEvent {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
||||
pub struct CollabWaitingBeginEvent {
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receivers.
|
||||
@@ -3863,6 +3889,8 @@ pub struct CollabWaitingEndEvent {
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// ID of the waiting call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Optional receiver metadata paired with final statuses.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub agent_statuses: Vec<CollabAgentStatusEntry>,
|
||||
@@ -3874,6 +3902,8 @@ pub struct CollabWaitingEndEvent {
|
||||
pub struct CollabCloseBeginEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -3884,6 +3914,8 @@ pub struct CollabCloseBeginEvent {
|
||||
pub struct CollabCloseEndEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -3903,6 +3935,8 @@ pub struct CollabCloseEndEvent {
|
||||
pub struct CollabResumeBeginEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub started_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -3919,6 +3953,8 @@ pub struct CollabResumeBeginEvent {
|
||||
pub struct CollabResumeEndEvent {
|
||||
/// Identifier for the collab tool call.
|
||||
pub call_id: String,
|
||||
#[serde(default)]
|
||||
pub completed_at_ms: i64,
|
||||
/// Thread ID of the sender.
|
||||
pub sender_thread_id: ThreadId,
|
||||
/// Thread ID of the receiver.
|
||||
@@ -4596,6 +4632,7 @@ mod tests {
|
||||
queries: None,
|
||||
},
|
||||
}),
|
||||
started_at_ms: 0,
|
||||
};
|
||||
|
||||
let legacy_events = event.as_legacy_events(/*show_raw_agent_reasoning*/ false);
|
||||
@@ -4612,6 +4649,7 @@ mod tests {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
item: TurnItem::UserMessage(UserMessageItem::new(&[])),
|
||||
started_at_ms: 0,
|
||||
};
|
||||
|
||||
assert!(
|
||||
@@ -4633,6 +4671,7 @@ mod tests {
|
||||
result: String::new(),
|
||||
saved_path: None,
|
||||
}),
|
||||
started_at_ms: 0,
|
||||
};
|
||||
|
||||
let legacy_events = event.as_legacy_events(/*show_raw_agent_reasoning*/ false);
|
||||
@@ -4648,6 +4687,7 @@ mod tests {
|
||||
let event = ItemStartedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
started_at_ms: 0,
|
||||
item: TurnItem::FileChange(FileChangeItem {
|
||||
id: "patch-1".into(),
|
||||
changes: [(
|
||||
@@ -4683,6 +4723,7 @@ mod tests {
|
||||
let event = ItemStartedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
started_at_ms: 0,
|
||||
item: TurnItem::McpToolCall(McpToolCallItem {
|
||||
id: "mcp-1".into(),
|
||||
server: "server".into(),
|
||||
@@ -4724,6 +4765,7 @@ mod tests {
|
||||
result: "Zm9v".into(),
|
||||
saved_path: Some(test_path_buf("/tmp/ig-1.png").abs()),
|
||||
}),
|
||||
completed_at_ms: 0,
|
||||
};
|
||||
|
||||
let legacy_events = event.as_legacy_events(/*show_raw_agent_reasoning*/ false);
|
||||
@@ -4748,6 +4790,7 @@ mod tests {
|
||||
let event = ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
item: TurnItem::FileChange(FileChangeItem {
|
||||
id: "patch-1".into(),
|
||||
changes: [(
|
||||
@@ -4785,6 +4828,7 @@ mod tests {
|
||||
let event = ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
item: TurnItem::McpToolCall(McpToolCallItem {
|
||||
id: "mcp-1".into(),
|
||||
server: "server".into(),
|
||||
@@ -4821,6 +4865,34 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_started_event_requires_started_at_ms() {
|
||||
let mut value = serde_json::to_value(ItemStartedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
item: TurnItem::UserMessage(UserMessageItem::new(&[])),
|
||||
started_at_ms: 123,
|
||||
})
|
||||
.unwrap();
|
||||
value.as_object_mut().unwrap().remove("started_at_ms");
|
||||
|
||||
assert!(serde_json::from_value::<ItemStartedEvent>(value).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_completed_event_defaults_missing_completed_at_ms() {
|
||||
let mut value = serde_json::to_value(ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
item: TurnItem::UserMessage(UserMessageItem::new(&[])),
|
||||
completed_at_ms: 123,
|
||||
})
|
||||
.unwrap();
|
||||
value.as_object_mut().unwrap().remove("completed_at_ms");
|
||||
|
||||
let event = serde_json::from_value::<ItemCompletedEvent>(value).unwrap();
|
||||
assert_eq!(event.completed_at_ms, 0);
|
||||
}
|
||||
#[test]
|
||||
fn rollback_failed_error_does_not_affect_turn_status() {
|
||||
let event = ErrorEvent {
|
||||
|
||||
@@ -2634,6 +2634,7 @@ async fn inactive_thread_file_change_approval_recovers_buffered_changes() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: "turn-approval".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: ThreadItem::FileChange {
|
||||
id: "patch-approval".to_string(),
|
||||
changes: vec![FileUpdateChange {
|
||||
@@ -4766,6 +4767,7 @@ async fn replace_chat_widget_reseeds_collab_agent_metadata_for_replay() {
|
||||
codex_app_server_protocol::ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: ThreadItem::CollabAgentToolCall {
|
||||
id: "wait-1".to_string(),
|
||||
tool: codex_app_server_protocol::CollabAgentTool::Wait,
|
||||
|
||||
@@ -16,6 +16,7 @@ async fn collab_spawn_end_shows_requested_model_and_effort() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "call-spawn".to_string(),
|
||||
tool: AppServerCollabAgentTool::SpawnAgent,
|
||||
@@ -34,6 +35,7 @@ async fn collab_spawn_end_shows_requested_model_and_effort() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "call-spawn".to_string(),
|
||||
tool: AppServerCollabAgentTool::SpawnAgent,
|
||||
@@ -90,6 +92,7 @@ async fn live_app_server_user_message_item_completed_does_not_duplicate_rendered
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::UserMessage {
|
||||
id: "user-1".to_string(),
|
||||
content: vec![AppServerUserInput::Text {
|
||||
@@ -135,6 +138,7 @@ async fn live_app_server_turn_completed_clears_working_status_after_answer_item(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::AgentMessage {
|
||||
id: "msg-1".to_string(),
|
||||
text: "Yes. What do you need?".to_string(),
|
||||
@@ -287,6 +291,7 @@ async fn live_app_server_file_change_item_started_preserves_changes() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::FileChange {
|
||||
id: "patch-1".to_string(),
|
||||
changes: vec![FileUpdateChange {
|
||||
@@ -320,6 +325,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: command.clone(),
|
||||
@@ -341,6 +347,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command,
|
||||
@@ -396,6 +403,7 @@ async fn live_app_server_collab_wait_items_render_history() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "wait-1".to_string(),
|
||||
tool: AppServerCollabAgentTool::Wait,
|
||||
@@ -418,6 +426,7 @@ async fn live_app_server_collab_wait_items_render_history() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "wait-1".to_string(),
|
||||
tool: AppServerCollabAgentTool::Wait,
|
||||
@@ -471,6 +480,7 @@ async fn live_app_server_collab_spawn_completed_renders_requested_model_and_effo
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "spawn-1".to_string(),
|
||||
tool: AppServerCollabAgentTool::SpawnAgent,
|
||||
@@ -490,6 +500,7 @@ async fn live_app_server_collab_spawn_completed_renders_requested_model_and_effo
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::CollabAgentToolCall {
|
||||
id: "spawn-1".to_string(),
|
||||
tool: AppServerCollabAgentTool::SpawnAgent,
|
||||
|
||||
@@ -659,6 +659,7 @@ pub(super) fn handle_agent_reasoning_final(chat: &mut ChatWidget) {
|
||||
.last_turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "turn-1".to_string()),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::Reasoning {
|
||||
id: "reasoning-1".to_string(),
|
||||
summary: Vec::new(),
|
||||
@@ -677,6 +678,7 @@ pub(super) fn handle_entered_review_mode(chat: &mut ChatWidget, review: impl Int
|
||||
.last_turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "turn-1".to_string()),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::EnteredReviewMode {
|
||||
id: "review-start".to_string(),
|
||||
review: review.into(),
|
||||
@@ -705,6 +707,7 @@ pub(super) fn handle_exited_review_mode(chat: &mut ChatWidget) {
|
||||
.last_turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "turn-1".to_string()),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::ExitedReviewMode {
|
||||
id: "review-end".to_string(),
|
||||
review: String::new(),
|
||||
@@ -761,6 +764,7 @@ pub(super) fn handle_patch_apply_begin(
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: thread_id(chat),
|
||||
turn_id: turn_id.into(),
|
||||
started_at_ms: 0,
|
||||
item: AppServerThreadItem::FileChange {
|
||||
id: call_id.into(),
|
||||
changes: file_update_changes_from_tui(changes),
|
||||
@@ -782,6 +786,7 @@ pub(super) fn handle_patch_apply_end(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: thread_id(chat),
|
||||
turn_id: turn_id.into(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::FileChange {
|
||||
id: call_id.into(),
|
||||
changes: file_update_changes_from_tui(changes),
|
||||
@@ -801,6 +806,7 @@ pub(super) fn handle_view_image_tool_call(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: thread_id(chat),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::ImageView {
|
||||
id: call_id.into(),
|
||||
path,
|
||||
@@ -820,6 +826,7 @@ pub(super) fn handle_image_generation_end(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: thread_id(chat),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::ImageGeneration {
|
||||
id: call_id.into(),
|
||||
status: "completed".to_string(),
|
||||
@@ -977,6 +984,7 @@ pub(super) fn handle_exec_begin(chat: &mut ChatWidget, item: AppServerThreadItem
|
||||
.last_turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "turn-1".to_string()),
|
||||
started_at_ms: 0,
|
||||
item,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
@@ -1016,6 +1024,7 @@ pub(super) fn complete_assistant_message(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: chat.thread_id.map(|id| id.to_string()).unwrap_or_default(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::AgentMessage {
|
||||
id: item_id.to_string(),
|
||||
text: text.to_string(),
|
||||
@@ -1058,6 +1067,7 @@ pub(super) fn complete_user_message_for_inputs(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: chat.thread_id.map(|id| id.to_string()).unwrap_or_default(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::UserMessage {
|
||||
id: item_id.to_string(),
|
||||
content,
|
||||
@@ -1199,6 +1209,7 @@ pub(super) fn handle_exec_end(chat: &mut ChatWidget, item: AppServerThreadItem)
|
||||
.last_turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "turn-1".to_string()),
|
||||
completed_at_ms: 0,
|
||||
item,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
|
||||
@@ -801,6 +801,7 @@ async fn live_reasoning_summary_is_not_rendered_twice_when_item_completes() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::Reasoning {
|
||||
id: "reasoning-1".to_string(),
|
||||
summary: vec!["Summary only".to_string()],
|
||||
|
||||
@@ -154,6 +154,7 @@ async fn live_app_server_review_prompt_item_is_not_rendered() {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
item: review_mode_item.clone(),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
@@ -166,6 +167,7 @@ async fn live_app_server_review_prompt_item_is_not_rendered() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: review_mode_item,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
@@ -176,6 +178,7 @@ async fn live_app_server_review_prompt_item_is_not_rendered() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::UserMessage {
|
||||
id: "review-prompt".to_string(),
|
||||
content: vec![AppServerUserInput::Text {
|
||||
|
||||
@@ -1149,6 +1149,7 @@ async fn slash_copy_state_tracks_plan_item_completion() {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: String::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::Plan {
|
||||
id: "plan-1".to_string(),
|
||||
text: plan_text.clone(),
|
||||
|
||||
Reference in New Issue
Block a user