Add turn.failed and rename session created to thread started (#4478)

Don't produce completed when turn failed.
This commit is contained in:
pakrym-oai
2025-09-29 18:38:04 -07:00
committed by GitHub
parent bf76258cdc
commit 4a80059b1b
3 changed files with 72 additions and 18 deletions

View File

@@ -6,12 +6,14 @@ use ts_rs::TS;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum ConversationEvent { pub enum ConversationEvent {
#[serde(rename = "session.created")] #[serde(rename = "thread.started")]
SessionCreated(SessionCreatedEvent), ThreadStarted(ThreadStartedEvent),
#[serde(rename = "turn.started")] #[serde(rename = "turn.started")]
TurnStarted(TurnStartedEvent), TurnStarted(TurnStartedEvent),
#[serde(rename = "turn.completed")] #[serde(rename = "turn.completed")]
TurnCompleted(TurnCompletedEvent), TurnCompleted(TurnCompletedEvent),
#[serde(rename = "turn.failed")]
TurnFailed(TurnFailedEvent),
#[serde(rename = "item.started")] #[serde(rename = "item.started")]
ItemStarted(ItemStartedEvent), ItemStarted(ItemStartedEvent),
#[serde(rename = "item.updated")] #[serde(rename = "item.updated")]
@@ -23,8 +25,8 @@ pub enum ConversationEvent {
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct SessionCreatedEvent { pub struct ThreadStartedEvent {
pub session_id: String, pub thread_id: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)]
@@ -35,6 +37,11 @@ pub struct TurnCompletedEvent {
pub usage: Usage, pub usage: Usage,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct TurnFailedEvent {
pub error: ConversationErrorEvent,
}
/// Minimal usage summary for a turn. /// Minimal usage summary for a turn.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)]
pub struct Usage { pub struct Usage {

View File

@@ -20,10 +20,11 @@ use crate::exec_events::ItemUpdatedEvent;
use crate::exec_events::PatchApplyStatus; use crate::exec_events::PatchApplyStatus;
use crate::exec_events::PatchChangeKind; use crate::exec_events::PatchChangeKind;
use crate::exec_events::ReasoningItem; use crate::exec_events::ReasoningItem;
use crate::exec_events::SessionCreatedEvent; use crate::exec_events::ThreadStartedEvent;
use crate::exec_events::TodoItem; use crate::exec_events::TodoItem;
use crate::exec_events::TodoListItem; use crate::exec_events::TodoListItem;
use crate::exec_events::TurnCompletedEvent; use crate::exec_events::TurnCompletedEvent;
use crate::exec_events::TurnFailedEvent;
use crate::exec_events::TurnStartedEvent; use crate::exec_events::TurnStartedEvent;
use crate::exec_events::Usage; use crate::exec_events::Usage;
use codex_core::config::Config; use codex_core::config::Config;
@@ -53,6 +54,7 @@ pub struct ExperimentalEventProcessorWithJsonOutput {
// Tracks the todo list for the current turn (at most one per turn). // Tracks the todo list for the current turn (at most one per turn).
running_todo_list: Option<RunningTodoList>, running_todo_list: Option<RunningTodoList>,
last_total_token_usage: Option<codex_core::protocol::TokenUsage>, last_total_token_usage: Option<codex_core::protocol::TokenUsage>,
last_critical_error: Option<ConversationErrorEvent>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -76,6 +78,7 @@ impl ExperimentalEventProcessorWithJsonOutput {
running_patch_applies: HashMap::new(), running_patch_applies: HashMap::new(),
running_todo_list: None, running_todo_list: None,
last_total_token_usage: None, last_total_token_usage: None,
last_critical_error: None,
} }
} }
@@ -96,9 +99,13 @@ impl ExperimentalEventProcessorWithJsonOutput {
} }
EventMsg::TaskStarted(ev) => self.handle_task_started(ev), EventMsg::TaskStarted(ev) => self.handle_task_started(ev),
EventMsg::TaskComplete(_) => self.handle_task_complete(), EventMsg::TaskComplete(_) => self.handle_task_complete(),
EventMsg::Error(ev) => vec![ConversationEvent::Error(ConversationErrorEvent { EventMsg::Error(ev) => {
message: ev.message.clone(), let error = ConversationErrorEvent {
})], message: ev.message.clone(),
};
self.last_critical_error = Some(error.clone());
vec![ConversationEvent::Error(error)]
}
EventMsg::StreamError(ev) => vec![ConversationEvent::Error(ConversationErrorEvent { EventMsg::StreamError(ev) => vec![ConversationEvent::Error(ConversationErrorEvent {
message: ev.message.clone(), message: ev.message.clone(),
})], })],
@@ -119,8 +126,8 @@ impl ExperimentalEventProcessorWithJsonOutput {
&self, &self,
payload: &SessionConfiguredEvent, payload: &SessionConfiguredEvent,
) -> Vec<ConversationEvent> { ) -> Vec<ConversationEvent> {
vec![ConversationEvent::SessionCreated(SessionCreatedEvent { vec![ConversationEvent::ThreadStarted(ThreadStartedEvent {
session_id: payload.session_id.to_string(), thread_id: payload.session_id.to_string(),
})] })]
} }
@@ -296,7 +303,8 @@ impl ExperimentalEventProcessorWithJsonOutput {
vec![ConversationEvent::ItemStarted(ItemStartedEvent { item })] vec![ConversationEvent::ItemStarted(ItemStartedEvent { item })]
} }
fn handle_task_started(&self, _: &TaskStartedEvent) -> Vec<ConversationEvent> { fn handle_task_started(&mut self, _: &TaskStartedEvent) -> Vec<ConversationEvent> {
self.last_critical_error = None;
vec![ConversationEvent::TurnStarted(TurnStartedEvent {})] vec![ConversationEvent::TurnStarted(TurnStartedEvent {})]
} }
@@ -325,9 +333,13 @@ impl ExperimentalEventProcessorWithJsonOutput {
})); }));
} }
items.push(ConversationEvent::TurnCompleted(TurnCompletedEvent { if let Some(error) = self.last_critical_error.take() {
usage, items.push(ConversationEvent::TurnFailed(TurnFailedEvent { error }));
})); } else {
items.push(ConversationEvent::TurnCompleted(TurnCompletedEvent {
usage,
}));
}
items items
} }

View File

@@ -1,5 +1,6 @@
use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::AgentMessageEvent;
use codex_core::protocol::AgentReasoningEvent; use codex_core::protocol::AgentReasoningEvent;
use codex_core::protocol::ErrorEvent;
use codex_core::protocol::Event; use codex_core::protocol::Event;
use codex_core::protocol::EventMsg; use codex_core::protocol::EventMsg;
use codex_core::protocol::ExecCommandBeginEvent; use codex_core::protocol::ExecCommandBeginEvent;
@@ -21,10 +22,11 @@ use codex_exec::exec_events::ItemUpdatedEvent;
use codex_exec::exec_events::PatchApplyStatus; use codex_exec::exec_events::PatchApplyStatus;
use codex_exec::exec_events::PatchChangeKind; use codex_exec::exec_events::PatchChangeKind;
use codex_exec::exec_events::ReasoningItem; use codex_exec::exec_events::ReasoningItem;
use codex_exec::exec_events::SessionCreatedEvent; use codex_exec::exec_events::ThreadStartedEvent;
use codex_exec::exec_events::TodoItem as ExecTodoItem; use codex_exec::exec_events::TodoItem as ExecTodoItem;
use codex_exec::exec_events::TodoListItem as ExecTodoListItem; use codex_exec::exec_events::TodoListItem as ExecTodoListItem;
use codex_exec::exec_events::TurnCompletedEvent; use codex_exec::exec_events::TurnCompletedEvent;
use codex_exec::exec_events::TurnFailedEvent;
use codex_exec::exec_events::TurnStartedEvent; use codex_exec::exec_events::TurnStartedEvent;
use codex_exec::exec_events::Usage; use codex_exec::exec_events::Usage;
use codex_exec::experimental_event_processor_with_json_output::ExperimentalEventProcessorWithJsonOutput; use codex_exec::experimental_event_processor_with_json_output::ExperimentalEventProcessorWithJsonOutput;
@@ -40,7 +42,7 @@ fn event(id: &str, msg: EventMsg) -> Event {
} }
#[test] #[test]
fn session_configured_produces_session_created_event() { fn session_configured_produces_thread_started_event() {
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None); let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
let session_id = codex_protocol::mcp_protocol::ConversationId::from_string( let session_id = codex_protocol::mcp_protocol::ConversationId::from_string(
"67e55044-10b1-426f-9247-bb680e5fe0c8", "67e55044-10b1-426f-9247-bb680e5fe0c8",
@@ -62,8 +64,8 @@ fn session_configured_produces_session_created_event() {
let out = ep.collect_conversation_events(&ev); let out = ep.collect_conversation_events(&ev);
assert_eq!( assert_eq!(
out, out,
vec![ConversationEvent::SessionCreated(SessionCreatedEvent { vec![ConversationEvent::ThreadStarted(ThreadStartedEvent {
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
})] })]
); );
} }
@@ -334,6 +336,39 @@ fn stream_error_event_produces_error() {
); );
} }
#[test]
fn error_followed_by_task_complete_produces_turn_failed() {
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
let error_event = event(
"e1",
EventMsg::Error(ErrorEvent {
message: "boom".to_string(),
}),
);
assert_eq!(
ep.collect_conversation_events(&error_event),
vec![ConversationEvent::Error(ConversationErrorEvent {
message: "boom".to_string(),
})]
);
let complete_event = event(
"e2",
EventMsg::TaskComplete(codex_core::protocol::TaskCompleteEvent {
last_agent_message: None,
}),
);
assert_eq!(
ep.collect_conversation_events(&complete_event),
vec![ConversationEvent::TurnFailed(TurnFailedEvent {
error: ConversationErrorEvent {
message: "boom".to_string(),
},
})]
);
}
#[test] #[test]
fn exec_command_end_success_produces_completed_command_item() { fn exec_command_end_success_produces_completed_command_item() {
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None); let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);