Rename conversation to thread in codex exec (#4482)
This commit is contained in:
@@ -2,10 +2,10 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
/// Top-level events emitted on the Codex Exec conversation stream.
|
/// Top-level events emitted on the Codex Exec thread stream.
|
||||||
#[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 ThreadEvent {
|
||||||
#[serde(rename = "thread.started")]
|
#[serde(rename = "thread.started")]
|
||||||
ThreadStarted(ThreadStartedEvent),
|
ThreadStarted(ThreadStartedEvent),
|
||||||
#[serde(rename = "turn.started")]
|
#[serde(rename = "turn.started")]
|
||||||
@@ -21,7 +21,7 @@ pub enum ConversationEvent {
|
|||||||
#[serde(rename = "item.completed")]
|
#[serde(rename = "item.completed")]
|
||||||
ItemCompleted(ItemCompletedEvent),
|
ItemCompleted(ItemCompletedEvent),
|
||||||
#[serde(rename = "error")]
|
#[serde(rename = "error")]
|
||||||
Error(ConversationErrorEvent),
|
Error(ThreadErrorEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
@@ -39,7 +39,7 @@ pub struct TurnCompletedEvent {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct TurnFailedEvent {
|
pub struct TurnFailedEvent {
|
||||||
pub error: ConversationErrorEvent,
|
pub error: ThreadErrorEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal usage summary for a turn.
|
/// Minimal usage summary for a turn.
|
||||||
@@ -52,37 +52,37 @@ pub struct Usage {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct ItemStartedEvent {
|
pub struct ItemStartedEvent {
|
||||||
pub item: ConversationItem,
|
pub item: ThreadItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct ItemCompletedEvent {
|
pub struct ItemCompletedEvent {
|
||||||
pub item: ConversationItem,
|
pub item: ThreadItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct ItemUpdatedEvent {
|
pub struct ItemUpdatedEvent {
|
||||||
pub item: ConversationItem,
|
pub item: ThreadItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fatal error emitted by the stream.
|
/// Fatal error emitted by the stream.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct ConversationErrorEvent {
|
pub struct ThreadErrorEvent {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Canonical representation of a conversation item and its domain-specific payload.
|
/// Canonical representation of a thread item and its domain-specific payload.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct ConversationItem {
|
pub struct ThreadItem {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub details: ConversationItemDetails,
|
pub details: ThreadItemDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Typed payloads for each supported conversation item type.
|
/// Typed payloads for each supported thread item type.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
#[serde(tag = "item_type", rename_all = "snake_case")]
|
#[serde(tag = "item_type", rename_all = "snake_case")]
|
||||||
pub enum ConversationItemDetails {
|
pub enum ThreadItemDetails {
|
||||||
AssistantMessage(AssistantMessageItem),
|
AssistantMessage(AssistantMessageItem),
|
||||||
Reasoning(ReasoningItem),
|
Reasoning(ReasoningItem),
|
||||||
CommandExecution(CommandExecutionItem),
|
CommandExecution(CommandExecutionItem),
|
||||||
@@ -93,7 +93,7 @@ pub enum ConversationItemDetails {
|
|||||||
Error(ErrorItem),
|
Error(ErrorItem),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Session conversation metadata.
|
/// Session metadata.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
|
||||||
pub struct SessionItem {
|
pub struct SessionItem {
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ use crate::event_processor::handle_last_message;
|
|||||||
use crate::exec_events::AssistantMessageItem;
|
use crate::exec_events::AssistantMessageItem;
|
||||||
use crate::exec_events::CommandExecutionItem;
|
use crate::exec_events::CommandExecutionItem;
|
||||||
use crate::exec_events::CommandExecutionStatus;
|
use crate::exec_events::CommandExecutionStatus;
|
||||||
use crate::exec_events::ConversationErrorEvent;
|
|
||||||
use crate::exec_events::ConversationEvent;
|
|
||||||
use crate::exec_events::ConversationItem;
|
|
||||||
use crate::exec_events::ConversationItemDetails;
|
|
||||||
use crate::exec_events::FileChangeItem;
|
use crate::exec_events::FileChangeItem;
|
||||||
use crate::exec_events::FileUpdateChange;
|
use crate::exec_events::FileUpdateChange;
|
||||||
use crate::exec_events::ItemCompletedEvent;
|
use crate::exec_events::ItemCompletedEvent;
|
||||||
@@ -22,6 +18,10 @@ use crate::exec_events::McpToolCallStatus;
|
|||||||
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::ThreadErrorEvent;
|
||||||
|
use crate::exec_events::ThreadEvent;
|
||||||
|
use crate::exec_events::ThreadItem;
|
||||||
|
use crate::exec_events::ThreadItemDetails;
|
||||||
use crate::exec_events::ThreadStartedEvent;
|
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;
|
||||||
@@ -59,7 +59,7 @@ pub struct ExperimentalEventProcessorWithJsonOutput {
|
|||||||
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>,
|
||||||
running_mcp_tool_calls: HashMap<String, RunningMcpToolCall>,
|
running_mcp_tool_calls: HashMap<String, RunningMcpToolCall>,
|
||||||
last_critical_error: Option<ConversationErrorEvent>,
|
last_critical_error: Option<ThreadErrorEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -95,7 +95,7 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_conversation_events(&mut self, event: &Event) -> Vec<ConversationEvent> {
|
pub fn collect_thread_events(&mut self, event: &Event) -> Vec<ThreadEvent> {
|
||||||
match &event.msg {
|
match &event.msg {
|
||||||
EventMsg::SessionConfigured(ev) => self.handle_session_configured(ev),
|
EventMsg::SessionConfigured(ev) => self.handle_session_configured(ev),
|
||||||
EventMsg::AgentMessage(ev) => self.handle_agent_message(ev),
|
EventMsg::AgentMessage(ev) => self.handle_agent_message(ev),
|
||||||
@@ -115,13 +115,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) => {
|
EventMsg::Error(ev) => {
|
||||||
let error = ConversationErrorEvent {
|
let error = ThreadErrorEvent {
|
||||||
message: ev.message.clone(),
|
message: ev.message.clone(),
|
||||||
};
|
};
|
||||||
self.last_critical_error = Some(error.clone());
|
self.last_critical_error = Some(error.clone());
|
||||||
vec![ConversationEvent::Error(error)]
|
vec![ThreadEvent::Error(error)]
|
||||||
}
|
}
|
||||||
EventMsg::StreamError(ev) => vec![ConversationEvent::Error(ConversationErrorEvent {
|
EventMsg::StreamError(ev) => vec![ThreadEvent::Error(ThreadErrorEvent {
|
||||||
message: ev.message.clone(),
|
message: ev.message.clone(),
|
||||||
})],
|
})],
|
||||||
EventMsg::PlanUpdate(ev) => self.handle_plan_update(ev),
|
EventMsg::PlanUpdate(ev) => self.handle_plan_update(ev),
|
||||||
@@ -137,43 +137,36 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_session_configured(
|
fn handle_session_configured(&self, payload: &SessionConfiguredEvent) -> Vec<ThreadEvent> {
|
||||||
&self,
|
vec![ThreadEvent::ThreadStarted(ThreadStartedEvent {
|
||||||
payload: &SessionConfiguredEvent,
|
|
||||||
) -> Vec<ConversationEvent> {
|
|
||||||
vec![ConversationEvent::ThreadStarted(ThreadStartedEvent {
|
|
||||||
thread_id: payload.session_id.to_string(),
|
thread_id: payload.session_id.to_string(),
|
||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_agent_message(&self, payload: &AgentMessageEvent) -> Vec<ConversationEvent> {
|
fn handle_agent_message(&self, payload: &AgentMessageEvent) -> Vec<ThreadEvent> {
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: self.get_next_item_id(),
|
id: self.get_next_item_id(),
|
||||||
|
|
||||||
details: ConversationItemDetails::AssistantMessage(AssistantMessageItem {
|
details: ThreadItemDetails::AssistantMessage(AssistantMessageItem {
|
||||||
text: payload.message.clone(),
|
text: payload.message.clone(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })]
|
||||||
item,
|
|
||||||
})]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reasoning_event(&self, ev: &AgentReasoningEvent) -> Vec<ConversationEvent> {
|
fn handle_reasoning_event(&self, ev: &AgentReasoningEvent) -> Vec<ThreadEvent> {
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: self.get_next_item_id(),
|
id: self.get_next_item_id(),
|
||||||
|
|
||||||
details: ConversationItemDetails::Reasoning(ReasoningItem {
|
details: ThreadItemDetails::Reasoning(ReasoningItem {
|
||||||
text: ev.text.clone(),
|
text: ev.text.clone(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })]
|
||||||
item,
|
|
||||||
})]
|
|
||||||
}
|
}
|
||||||
fn handle_exec_command_begin(&mut self, ev: &ExecCommandBeginEvent) -> Vec<ConversationEvent> {
|
fn handle_exec_command_begin(&mut self, ev: &ExecCommandBeginEvent) -> Vec<ThreadEvent> {
|
||||||
let item_id = self.get_next_item_id();
|
let item_id = self.get_next_item_id();
|
||||||
|
|
||||||
let command_string = match shlex::try_join(ev.command.iter().map(String::as_str)) {
|
let command_string = match shlex::try_join(ev.command.iter().map(String::as_str)) {
|
||||||
@@ -195,9 +188,9 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: item_id,
|
id: item_id,
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command: command_string,
|
command: command_string,
|
||||||
aggregated_output: String::new(),
|
aggregated_output: String::new(),
|
||||||
exit_code: None,
|
exit_code: None,
|
||||||
@@ -205,10 +198,10 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent { item })]
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent { item })]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mcp_tool_call_begin(&mut self, ev: &McpToolCallBeginEvent) -> Vec<ConversationEvent> {
|
fn handle_mcp_tool_call_begin(&mut self, ev: &McpToolCallBeginEvent) -> Vec<ThreadEvent> {
|
||||||
let item_id = self.get_next_item_id();
|
let item_id = self.get_next_item_id();
|
||||||
let server = ev.invocation.server.clone();
|
let server = ev.invocation.server.clone();
|
||||||
let tool = ev.invocation.tool.clone();
|
let tool = ev.invocation.tool.clone();
|
||||||
@@ -222,19 +215,19 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: item_id,
|
id: item_id,
|
||||||
details: ConversationItemDetails::McpToolCall(McpToolCallItem {
|
details: ThreadItemDetails::McpToolCall(McpToolCallItem {
|
||||||
server,
|
server,
|
||||||
tool,
|
tool,
|
||||||
status: McpToolCallStatus::InProgress,
|
status: McpToolCallStatus::InProgress,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent { item })]
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent { item })]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mcp_tool_call_end(&mut self, ev: &McpToolCallEndEvent) -> Vec<ConversationEvent> {
|
fn handle_mcp_tool_call_end(&mut self, ev: &McpToolCallEndEvent) -> Vec<ThreadEvent> {
|
||||||
let status = if ev.is_success() {
|
let status = if ev.is_success() {
|
||||||
McpToolCallStatus::Completed
|
McpToolCallStatus::Completed
|
||||||
} else {
|
} else {
|
||||||
@@ -256,21 +249,19 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: item_id,
|
id: item_id,
|
||||||
details: ConversationItemDetails::McpToolCall(McpToolCallItem {
|
details: ThreadItemDetails::McpToolCall(McpToolCallItem {
|
||||||
server,
|
server,
|
||||||
tool,
|
tool,
|
||||||
status,
|
status,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })]
|
||||||
item,
|
|
||||||
})]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_patch_apply_begin(&mut self, ev: &PatchApplyBeginEvent) -> Vec<ConversationEvent> {
|
fn handle_patch_apply_begin(&mut self, ev: &PatchApplyBeginEvent) -> Vec<ThreadEvent> {
|
||||||
self.running_patch_applies
|
self.running_patch_applies
|
||||||
.insert(ev.call_id.clone(), ev.clone());
|
.insert(ev.call_id.clone(), ev.clone());
|
||||||
|
|
||||||
@@ -285,17 +276,17 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_patch_apply_end(&mut self, ev: &PatchApplyEndEvent) -> Vec<ConversationEvent> {
|
fn handle_patch_apply_end(&mut self, ev: &PatchApplyEndEvent) -> Vec<ThreadEvent> {
|
||||||
if let Some(running_patch_apply) = self.running_patch_applies.remove(&ev.call_id) {
|
if let Some(running_patch_apply) = self.running_patch_applies.remove(&ev.call_id) {
|
||||||
let status = if ev.success {
|
let status = if ev.success {
|
||||||
PatchApplyStatus::Completed
|
PatchApplyStatus::Completed
|
||||||
} else {
|
} else {
|
||||||
PatchApplyStatus::Failed
|
PatchApplyStatus::Failed
|
||||||
};
|
};
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: self.get_next_item_id(),
|
id: self.get_next_item_id(),
|
||||||
|
|
||||||
details: ConversationItemDetails::FileChange(FileChangeItem {
|
details: ThreadItemDetails::FileChange(FileChangeItem {
|
||||||
changes: running_patch_apply
|
changes: running_patch_apply
|
||||||
.changes
|
.changes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -308,15 +299,13 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
return vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })];
|
||||||
item,
|
|
||||||
})];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_exec_command_end(&mut self, ev: &ExecCommandEndEvent) -> Vec<ConversationEvent> {
|
fn handle_exec_command_end(&mut self, ev: &ExecCommandEndEvent) -> Vec<ThreadEvent> {
|
||||||
let Some(RunningCommand { command, item_id }) = self.running_commands.remove(&ev.call_id)
|
let Some(RunningCommand { command, item_id }) = self.running_commands.remove(&ev.call_id)
|
||||||
else {
|
else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -330,10 +319,10 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
} else {
|
} else {
|
||||||
CommandExecutionStatus::Failed
|
CommandExecutionStatus::Failed
|
||||||
};
|
};
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: item_id,
|
id: item_id,
|
||||||
|
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command,
|
command,
|
||||||
aggregated_output: ev.aggregated_output.clone(),
|
aggregated_output: ev.aggregated_output.clone(),
|
||||||
exit_code: Some(ev.exit_code),
|
exit_code: Some(ev.exit_code),
|
||||||
@@ -341,9 +330,7 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })]
|
||||||
item,
|
|
||||||
})]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn todo_items_from_plan(&self, args: &UpdatePlanArgs) -> Vec<TodoItem> {
|
fn todo_items_from_plan(&self, args: &UpdatePlanArgs) -> Vec<TodoItem> {
|
||||||
@@ -356,16 +343,16 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_plan_update(&mut self, args: &UpdatePlanArgs) -> Vec<ConversationEvent> {
|
fn handle_plan_update(&mut self, args: &UpdatePlanArgs) -> Vec<ThreadEvent> {
|
||||||
let items = self.todo_items_from_plan(args);
|
let items = self.todo_items_from_plan(args);
|
||||||
|
|
||||||
if let Some(running) = &mut self.running_todo_list {
|
if let Some(running) = &mut self.running_todo_list {
|
||||||
running.items = items.clone();
|
running.items = items.clone();
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: running.item_id.clone(),
|
id: running.item_id.clone(),
|
||||||
details: ConversationItemDetails::TodoList(TodoListItem { items }),
|
details: ThreadItemDetails::TodoList(TodoListItem { items }),
|
||||||
};
|
};
|
||||||
return vec![ConversationEvent::ItemUpdated(ItemUpdatedEvent { item })];
|
return vec![ThreadEvent::ItemUpdated(ItemUpdatedEvent { item })];
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_id = self.get_next_item_id();
|
let item_id = self.get_next_item_id();
|
||||||
@@ -373,19 +360,19 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
item_id: item_id.clone(),
|
item_id: item_id.clone(),
|
||||||
items: items.clone(),
|
items: items.clone(),
|
||||||
});
|
});
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: item_id,
|
id: item_id,
|
||||||
details: ConversationItemDetails::TodoList(TodoListItem { items }),
|
details: ThreadItemDetails::TodoList(TodoListItem { items }),
|
||||||
};
|
};
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent { item })]
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent { item })]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_task_started(&mut self, _: &TaskStartedEvent) -> Vec<ConversationEvent> {
|
fn handle_task_started(&mut self, _: &TaskStartedEvent) -> Vec<ThreadEvent> {
|
||||||
self.last_critical_error = None;
|
self.last_critical_error = None;
|
||||||
vec![ConversationEvent::TurnStarted(TurnStartedEvent {})]
|
vec![ThreadEvent::TurnStarted(TurnStartedEvent {})]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_task_complete(&mut self) -> Vec<ConversationEvent> {
|
fn handle_task_complete(&mut self) -> Vec<ThreadEvent> {
|
||||||
let usage = if let Some(u) = &self.last_total_token_usage {
|
let usage = if let Some(u) = &self.last_total_token_usage {
|
||||||
Usage {
|
Usage {
|
||||||
input_tokens: u.input_tokens,
|
input_tokens: u.input_tokens,
|
||||||
@@ -399,23 +386,19 @@ impl ExperimentalEventProcessorWithJsonOutput {
|
|||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
if let Some(running) = self.running_todo_list.take() {
|
if let Some(running) = self.running_todo_list.take() {
|
||||||
let item = ConversationItem {
|
let item = ThreadItem {
|
||||||
id: running.item_id,
|
id: running.item_id,
|
||||||
details: ConversationItemDetails::TodoList(TodoListItem {
|
details: ThreadItemDetails::TodoList(TodoListItem {
|
||||||
items: running.items,
|
items: running.items,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
items.push(ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
items.push(ThreadEvent::ItemCompleted(ItemCompletedEvent { item }));
|
||||||
item,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = self.last_critical_error.take() {
|
if let Some(error) = self.last_critical_error.take() {
|
||||||
items.push(ConversationEvent::TurnFailed(TurnFailedEvent { error }));
|
items.push(ThreadEvent::TurnFailed(TurnFailedEvent { error }));
|
||||||
} else {
|
} else {
|
||||||
items.push(ConversationEvent::TurnCompleted(TurnCompletedEvent {
|
items.push(ThreadEvent::TurnCompleted(TurnCompletedEvent { usage }));
|
||||||
usage,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
@@ -431,7 +414,7 @@ impl EventProcessor for ExperimentalEventProcessorWithJsonOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_event(&mut self, event: Event) -> CodexStatus {
|
fn process_event(&mut self, event: Event) -> CodexStatus {
|
||||||
let aggregated = self.collect_conversation_events(&event);
|
let aggregated = self.collect_thread_events(&event);
|
||||||
for conv_event in aggregated {
|
for conv_event in aggregated {
|
||||||
match serde_json::to_string(&conv_event) {
|
match serde_json::to_string(&conv_event) {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ use codex_core::protocol::SessionConfiguredEvent;
|
|||||||
use codex_exec::exec_events::AssistantMessageItem;
|
use codex_exec::exec_events::AssistantMessageItem;
|
||||||
use codex_exec::exec_events::CommandExecutionItem;
|
use codex_exec::exec_events::CommandExecutionItem;
|
||||||
use codex_exec::exec_events::CommandExecutionStatus;
|
use codex_exec::exec_events::CommandExecutionStatus;
|
||||||
use codex_exec::exec_events::ConversationErrorEvent;
|
|
||||||
use codex_exec::exec_events::ConversationEvent;
|
|
||||||
use codex_exec::exec_events::ConversationItem;
|
|
||||||
use codex_exec::exec_events::ConversationItemDetails;
|
|
||||||
use codex_exec::exec_events::ItemCompletedEvent;
|
use codex_exec::exec_events::ItemCompletedEvent;
|
||||||
use codex_exec::exec_events::ItemStartedEvent;
|
use codex_exec::exec_events::ItemStartedEvent;
|
||||||
use codex_exec::exec_events::ItemUpdatedEvent;
|
use codex_exec::exec_events::ItemUpdatedEvent;
|
||||||
@@ -27,6 +23,10 @@ use codex_exec::exec_events::McpToolCallStatus;
|
|||||||
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::ThreadErrorEvent;
|
||||||
|
use codex_exec::exec_events::ThreadEvent;
|
||||||
|
use codex_exec::exec_events::ThreadItem;
|
||||||
|
use codex_exec::exec_events::ThreadItemDetails;
|
||||||
use codex_exec::exec_events::ThreadStartedEvent;
|
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;
|
||||||
@@ -67,10 +67,10 @@ fn session_configured_produces_thread_started_event() {
|
|||||||
rollout_path,
|
rollout_path,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&ev);
|
let out = ep.collect_thread_events(&ev);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::ThreadStarted(ThreadStartedEvent {
|
vec![ThreadEvent::ThreadStarted(ThreadStartedEvent {
|
||||||
thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
@@ -79,17 +79,14 @@ fn session_configured_produces_thread_started_event() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn task_started_produces_turn_started_event() {
|
fn task_started_produces_turn_started_event() {
|
||||||
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
||||||
let out = ep.collect_conversation_events(&event(
|
let out = ep.collect_thread_events(&event(
|
||||||
"t1",
|
"t1",
|
||||||
EventMsg::TaskStarted(codex_core::protocol::TaskStartedEvent {
|
EventMsg::TaskStarted(codex_core::protocol::TaskStartedEvent {
|
||||||
model_context_window: Some(32_000),
|
model_context_window: Some(32_000),
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(out, vec![ThreadEvent::TurnStarted(TurnStartedEvent {})]);
|
||||||
out,
|
|
||||||
vec![ConversationEvent::TurnStarted(TurnStartedEvent {})]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -117,13 +114,13 @@ fn plan_update_emits_todo_list_started_updated_and_completed() {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_first = ep.collect_conversation_events(&first);
|
let out_first = ep.collect_thread_events(&first);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_first,
|
out_first,
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent {
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::TodoList(ExecTodoListItem {
|
details: ThreadItemDetails::TodoList(ExecTodoListItem {
|
||||||
items: vec![
|
items: vec![
|
||||||
ExecTodoItem {
|
ExecTodoItem {
|
||||||
text: "step one".to_string(),
|
text: "step one".to_string(),
|
||||||
@@ -156,13 +153,13 @@ fn plan_update_emits_todo_list_started_updated_and_completed() {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_second = ep.collect_conversation_events(&second);
|
let out_second = ep.collect_thread_events(&second);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_second,
|
out_second,
|
||||||
vec![ConversationEvent::ItemUpdated(ItemUpdatedEvent {
|
vec![ThreadEvent::ItemUpdated(ItemUpdatedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::TodoList(ExecTodoListItem {
|
details: ThreadItemDetails::TodoList(ExecTodoListItem {
|
||||||
items: vec![
|
items: vec![
|
||||||
ExecTodoItem {
|
ExecTodoItem {
|
||||||
text: "step one".to_string(),
|
text: "step one".to_string(),
|
||||||
@@ -185,14 +182,14 @@ fn plan_update_emits_todo_list_started_updated_and_completed() {
|
|||||||
last_agent_message: None,
|
last_agent_message: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_complete = ep.collect_conversation_events(&complete);
|
let out_complete = ep.collect_thread_events(&complete);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_complete,
|
out_complete,
|
||||||
vec![
|
vec![
|
||||||
ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::TodoList(ExecTodoListItem {
|
details: ThreadItemDetails::TodoList(ExecTodoListItem {
|
||||||
items: vec![
|
items: vec![
|
||||||
ExecTodoItem {
|
ExecTodoItem {
|
||||||
text: "step one".to_string(),
|
text: "step one".to_string(),
|
||||||
@@ -206,7 +203,7 @@ fn plan_update_emits_todo_list_started_updated_and_completed() {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
ConversationEvent::TurnCompleted(TurnCompletedEvent {
|
ThreadEvent::TurnCompleted(TurnCompletedEvent {
|
||||||
usage: Usage::default(),
|
usage: Usage::default(),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
@@ -229,13 +226,13 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
|
|||||||
invocation: invocation.clone(),
|
invocation: invocation.clone(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let begin_events = ep.collect_conversation_events(&begin);
|
let begin_events = ep.collect_thread_events(&begin);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
begin_events,
|
begin_events,
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent {
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::McpToolCall(McpToolCallItem {
|
details: ThreadItemDetails::McpToolCall(McpToolCallItem {
|
||||||
server: "server_a".to_string(),
|
server: "server_a".to_string(),
|
||||||
tool: "tool_x".to_string(),
|
tool: "tool_x".to_string(),
|
||||||
status: McpToolCallStatus::InProgress,
|
status: McpToolCallStatus::InProgress,
|
||||||
@@ -257,13 +254,13 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let end_events = ep.collect_conversation_events(&end);
|
let end_events = ep.collect_thread_events(&end);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
end_events,
|
end_events,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::McpToolCall(McpToolCallItem {
|
details: ThreadItemDetails::McpToolCall(McpToolCallItem {
|
||||||
server: "server_a".to_string(),
|
server: "server_a".to_string(),
|
||||||
tool: "tool_x".to_string(),
|
tool: "tool_x".to_string(),
|
||||||
status: McpToolCallStatus::Completed,
|
status: McpToolCallStatus::Completed,
|
||||||
@@ -289,7 +286,7 @@ fn mcp_tool_call_failure_sets_failed_status() {
|
|||||||
invocation: invocation.clone(),
|
invocation: invocation.clone(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
ep.collect_conversation_events(&begin);
|
ep.collect_thread_events(&begin);
|
||||||
|
|
||||||
let end = event(
|
let end = event(
|
||||||
"m4",
|
"m4",
|
||||||
@@ -300,13 +297,13 @@ fn mcp_tool_call_failure_sets_failed_status() {
|
|||||||
result: Err("tool exploded".to_string()),
|
result: Err("tool exploded".to_string()),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let events = ep.collect_conversation_events(&end);
|
let events = ep.collect_thread_events(&end);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::McpToolCall(McpToolCallItem {
|
details: ThreadItemDetails::McpToolCall(McpToolCallItem {
|
||||||
server: "server_b".to_string(),
|
server: "server_b".to_string(),
|
||||||
tool: "tool_y".to_string(),
|
tool: "tool_y".to_string(),
|
||||||
status: McpToolCallStatus::Failed,
|
status: McpToolCallStatus::Failed,
|
||||||
@@ -335,14 +332,14 @@ fn plan_update_after_complete_starts_new_todo_list_with_new_id() {
|
|||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let _ = ep.collect_conversation_events(&start);
|
let _ = ep.collect_thread_events(&start);
|
||||||
let complete = event(
|
let complete = event(
|
||||||
"t2",
|
"t2",
|
||||||
EventMsg::TaskComplete(codex_core::protocol::TaskCompleteEvent {
|
EventMsg::TaskComplete(codex_core::protocol::TaskCompleteEvent {
|
||||||
last_agent_message: None,
|
last_agent_message: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let _ = ep.collect_conversation_events(&complete);
|
let _ = ep.collect_thread_events(&complete);
|
||||||
|
|
||||||
// Second turn: a new todo list should have a new id
|
// Second turn: a new todo list should have a new id
|
||||||
let start_again = event(
|
let start_again = event(
|
||||||
@@ -355,10 +352,10 @@ fn plan_update_after_complete_starts_new_todo_list_with_new_id() {
|
|||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&start_again);
|
let out = ep.collect_thread_events(&start_again);
|
||||||
|
|
||||||
match &out[0] {
|
match &out[0] {
|
||||||
ConversationEvent::ItemStarted(ItemStartedEvent { item }) => {
|
ThreadEvent::ItemStarted(ItemStartedEvent { item }) => {
|
||||||
assert_eq!(&item.id, "item_1");
|
assert_eq!(&item.id, "item_1");
|
||||||
}
|
}
|
||||||
other => panic!("unexpected event: {other:?}"),
|
other => panic!("unexpected event: {other:?}"),
|
||||||
@@ -374,13 +371,13 @@ fn agent_reasoning_produces_item_completed_reasoning() {
|
|||||||
text: "thinking...".to_string(),
|
text: "thinking...".to_string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&ev);
|
let out = ep.collect_thread_events(&ev);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::Reasoning(ReasoningItem {
|
details: ThreadItemDetails::Reasoning(ReasoningItem {
|
||||||
text: "thinking...".to_string(),
|
text: "thinking...".to_string(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -397,13 +394,13 @@ fn agent_message_produces_item_completed_assistant_message() {
|
|||||||
message: "hello".to_string(),
|
message: "hello".to_string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&ev);
|
let out = ep.collect_thread_events(&ev);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::AssistantMessage(AssistantMessageItem {
|
details: ThreadItemDetails::AssistantMessage(AssistantMessageItem {
|
||||||
text: "hello".to_string(),
|
text: "hello".to_string(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -414,7 +411,7 @@ fn agent_message_produces_item_completed_assistant_message() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn error_event_produces_error() {
|
fn error_event_produces_error() {
|
||||||
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
||||||
let out = ep.collect_conversation_events(&event(
|
let out = ep.collect_thread_events(&event(
|
||||||
"e1",
|
"e1",
|
||||||
EventMsg::Error(codex_core::protocol::ErrorEvent {
|
EventMsg::Error(codex_core::protocol::ErrorEvent {
|
||||||
message: "boom".to_string(),
|
message: "boom".to_string(),
|
||||||
@@ -422,7 +419,7 @@ fn error_event_produces_error() {
|
|||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::Error(ConversationErrorEvent {
|
vec![ThreadEvent::Error(ThreadErrorEvent {
|
||||||
message: "boom".to_string(),
|
message: "boom".to_string(),
|
||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
@@ -431,7 +428,7 @@ fn error_event_produces_error() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn stream_error_event_produces_error() {
|
fn stream_error_event_produces_error() {
|
||||||
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
||||||
let out = ep.collect_conversation_events(&event(
|
let out = ep.collect_thread_events(&event(
|
||||||
"e1",
|
"e1",
|
||||||
EventMsg::StreamError(codex_core::protocol::StreamErrorEvent {
|
EventMsg::StreamError(codex_core::protocol::StreamErrorEvent {
|
||||||
message: "retrying".to_string(),
|
message: "retrying".to_string(),
|
||||||
@@ -439,7 +436,7 @@ fn stream_error_event_produces_error() {
|
|||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::Error(ConversationErrorEvent {
|
vec![ThreadEvent::Error(ThreadErrorEvent {
|
||||||
message: "retrying".to_string(),
|
message: "retrying".to_string(),
|
||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
@@ -456,8 +453,8 @@ fn error_followed_by_task_complete_produces_turn_failed() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ep.collect_conversation_events(&error_event),
|
ep.collect_thread_events(&error_event),
|
||||||
vec![ConversationEvent::Error(ConversationErrorEvent {
|
vec![ThreadEvent::Error(ThreadErrorEvent {
|
||||||
message: "boom".to_string(),
|
message: "boom".to_string(),
|
||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
@@ -469,9 +466,9 @@ fn error_followed_by_task_complete_produces_turn_failed() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ep.collect_conversation_events(&complete_event),
|
ep.collect_thread_events(&complete_event),
|
||||||
vec![ConversationEvent::TurnFailed(TurnFailedEvent {
|
vec![ThreadEvent::TurnFailed(TurnFailedEvent {
|
||||||
error: ConversationErrorEvent {
|
error: ThreadErrorEvent {
|
||||||
message: "boom".to_string(),
|
message: "boom".to_string(),
|
||||||
},
|
},
|
||||||
})]
|
})]
|
||||||
@@ -492,13 +489,13 @@ fn exec_command_end_success_produces_completed_command_item() {
|
|||||||
parsed_cmd: Vec::new(),
|
parsed_cmd: Vec::new(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_begin = ep.collect_conversation_events(&begin);
|
let out_begin = ep.collect_thread_events(&begin);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_begin,
|
out_begin,
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent {
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command: "bash -lc 'echo hi'".to_string(),
|
command: "bash -lc 'echo hi'".to_string(),
|
||||||
aggregated_output: String::new(),
|
aggregated_output: String::new(),
|
||||||
exit_code: None,
|
exit_code: None,
|
||||||
@@ -521,13 +518,13 @@ fn exec_command_end_success_produces_completed_command_item() {
|
|||||||
formatted_output: String::new(),
|
formatted_output: String::new(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_ok = ep.collect_conversation_events(&end_ok);
|
let out_ok = ep.collect_thread_events(&end_ok);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_ok,
|
out_ok,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command: "bash -lc 'echo hi'".to_string(),
|
command: "bash -lc 'echo hi'".to_string(),
|
||||||
aggregated_output: "hi\n".to_string(),
|
aggregated_output: "hi\n".to_string(),
|
||||||
exit_code: Some(0),
|
exit_code: Some(0),
|
||||||
@@ -553,11 +550,11 @@ fn exec_command_end_failure_produces_failed_command_item() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ep.collect_conversation_events(&begin),
|
ep.collect_thread_events(&begin),
|
||||||
vec![ConversationEvent::ItemStarted(ItemStartedEvent {
|
vec![ThreadEvent::ItemStarted(ItemStartedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command: "sh -c 'exit 1'".to_string(),
|
command: "sh -c 'exit 1'".to_string(),
|
||||||
aggregated_output: String::new(),
|
aggregated_output: String::new(),
|
||||||
exit_code: None,
|
exit_code: None,
|
||||||
@@ -580,13 +577,13 @@ fn exec_command_end_failure_produces_failed_command_item() {
|
|||||||
formatted_output: String::new(),
|
formatted_output: String::new(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_fail = ep.collect_conversation_events(&end_fail);
|
let out_fail = ep.collect_thread_events(&end_fail);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_fail,
|
out_fail,
|
||||||
vec![ConversationEvent::ItemCompleted(ItemCompletedEvent {
|
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
|
||||||
item: ConversationItem {
|
item: ThreadItem {
|
||||||
id: "item_0".to_string(),
|
id: "item_0".to_string(),
|
||||||
details: ConversationItemDetails::CommandExecution(CommandExecutionItem {
|
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
|
||||||
command: "sh -c 'exit 1'".to_string(),
|
command: "sh -c 'exit 1'".to_string(),
|
||||||
aggregated_output: String::new(),
|
aggregated_output: String::new(),
|
||||||
exit_code: Some(1),
|
exit_code: Some(1),
|
||||||
@@ -601,7 +598,7 @@ fn exec_command_end_failure_produces_failed_command_item() {
|
|||||||
fn exec_command_end_without_begin_is_ignored() {
|
fn exec_command_end_without_begin_is_ignored() {
|
||||||
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
let mut ep = ExperimentalEventProcessorWithJsonOutput::new(None);
|
||||||
|
|
||||||
// End event arrives without a prior Begin; should produce no conversation events.
|
// End event arrives without a prior Begin; should produce no thread events.
|
||||||
let end_only = event(
|
let end_only = event(
|
||||||
"c1",
|
"c1",
|
||||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||||
@@ -614,7 +611,7 @@ fn exec_command_end_without_begin_is_ignored() {
|
|||||||
formatted_output: String::new(),
|
formatted_output: String::new(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&end_only);
|
let out = ep.collect_thread_events(&end_only);
|
||||||
assert!(out.is_empty());
|
assert!(out.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,7 +650,7 @@ fn patch_apply_success_produces_item_completed_patchapply() {
|
|||||||
changes: changes.clone(),
|
changes: changes.clone(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_begin = ep.collect_conversation_events(&begin);
|
let out_begin = ep.collect_thread_events(&begin);
|
||||||
assert!(out_begin.is_empty());
|
assert!(out_begin.is_empty());
|
||||||
|
|
||||||
// End (success) -> item.completed (item_0)
|
// End (success) -> item.completed (item_0)
|
||||||
@@ -666,15 +663,15 @@ fn patch_apply_success_produces_item_completed_patchapply() {
|
|||||||
success: true,
|
success: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_end = ep.collect_conversation_events(&end);
|
let out_end = ep.collect_thread_events(&end);
|
||||||
assert_eq!(out_end.len(), 1);
|
assert_eq!(out_end.len(), 1);
|
||||||
|
|
||||||
// Validate structure without relying on HashMap iteration order
|
// Validate structure without relying on HashMap iteration order
|
||||||
match &out_end[0] {
|
match &out_end[0] {
|
||||||
ConversationEvent::ItemCompleted(ItemCompletedEvent { item }) => {
|
ThreadEvent::ItemCompleted(ItemCompletedEvent { item }) => {
|
||||||
assert_eq!(&item.id, "item_0");
|
assert_eq!(&item.id, "item_0");
|
||||||
match &item.details {
|
match &item.details {
|
||||||
ConversationItemDetails::FileChange(file_update) => {
|
ThreadItemDetails::FileChange(file_update) => {
|
||||||
assert_eq!(file_update.status, PatchApplyStatus::Completed);
|
assert_eq!(file_update.status, PatchApplyStatus::Completed);
|
||||||
|
|
||||||
let mut actual: Vec<(String, PatchChangeKind)> = file_update
|
let mut actual: Vec<(String, PatchChangeKind)> = file_update
|
||||||
@@ -722,7 +719,7 @@ fn patch_apply_failure_produces_item_completed_patchapply_failed() {
|
|||||||
changes: changes.clone(),
|
changes: changes.clone(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert!(ep.collect_conversation_events(&begin).is_empty());
|
assert!(ep.collect_thread_events(&begin).is_empty());
|
||||||
|
|
||||||
// End (failure) -> item.completed (item_0) with Failed status
|
// End (failure) -> item.completed (item_0) with Failed status
|
||||||
let end = event(
|
let end = event(
|
||||||
@@ -734,14 +731,14 @@ fn patch_apply_failure_produces_item_completed_patchapply_failed() {
|
|||||||
success: false,
|
success: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out_end = ep.collect_conversation_events(&end);
|
let out_end = ep.collect_thread_events(&end);
|
||||||
assert_eq!(out_end.len(), 1);
|
assert_eq!(out_end.len(), 1);
|
||||||
|
|
||||||
match &out_end[0] {
|
match &out_end[0] {
|
||||||
ConversationEvent::ItemCompleted(ItemCompletedEvent { item }) => {
|
ThreadEvent::ItemCompleted(ItemCompletedEvent { item }) => {
|
||||||
assert_eq!(&item.id, "item_0");
|
assert_eq!(&item.id, "item_0");
|
||||||
match &item.details {
|
match &item.details {
|
||||||
ConversationItemDetails::FileChange(file_update) => {
|
ThreadItemDetails::FileChange(file_update) => {
|
||||||
assert_eq!(file_update.status, PatchApplyStatus::Failed);
|
assert_eq!(file_update.status, PatchApplyStatus::Failed);
|
||||||
assert_eq!(file_update.changes.len(), 1);
|
assert_eq!(file_update.changes.len(), 1);
|
||||||
assert_eq!(file_update.changes[0].path, "file.txt".to_string());
|
assert_eq!(file_update.changes[0].path, "file.txt".to_string());
|
||||||
@@ -778,10 +775,7 @@ fn task_complete_produces_turn_completed_with_usage() {
|
|||||||
rate_limits: None,
|
rate_limits: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(ep.collect_thread_events(&token_count_event).is_empty());
|
||||||
ep.collect_conversation_events(&token_count_event)
|
|
||||||
.is_empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then TaskComplete should produce turn.completed with the captured usage.
|
// Then TaskComplete should produce turn.completed with the captured usage.
|
||||||
let complete_event = event(
|
let complete_event = event(
|
||||||
@@ -790,10 +784,10 @@ fn task_complete_produces_turn_completed_with_usage() {
|
|||||||
last_agent_message: Some("done".to_string()),
|
last_agent_message: Some("done".to_string()),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let out = ep.collect_conversation_events(&complete_event);
|
let out = ep.collect_thread_events(&complete_event);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![ConversationEvent::TurnCompleted(TurnCompletedEvent {
|
vec![ThreadEvent::TurnCompleted(TurnCompletedEvent {
|
||||||
usage: Usage {
|
usage: Usage {
|
||||||
input_tokens: 1200,
|
input_tokens: 1200,
|
||||||
cached_input_tokens: 200,
|
cached_input_tokens: 200,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline/promises";
|
|||||||
import { stdin as input, stdout as output } from "node:process";
|
import { stdin as input, stdout as output } from "node:process";
|
||||||
|
|
||||||
import { Codex } from "@openai/codex-sdk";
|
import { Codex } from "@openai/codex-sdk";
|
||||||
import type { ConversationEvent, ConversationItem } from "@openai/codex-sdk";
|
import type { ThreadEvent, ThreadItem } from "@openai/codex-sdk";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
const executablePath =
|
const executablePath =
|
||||||
@@ -15,7 +15,7 @@ const codex = new Codex({ executablePath });
|
|||||||
const thread = codex.startThread();
|
const thread = codex.startThread();
|
||||||
const rl = createInterface({ input, output });
|
const rl = createInterface({ input, output });
|
||||||
|
|
||||||
const handleItemCompleted = (item: ConversationItem): void => {
|
const handleItemCompleted = (item: ThreadItem): void => {
|
||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case "assistant_message":
|
case "assistant_message":
|
||||||
console.log(`Assistant: ${item.text}`);
|
console.log(`Assistant: ${item.text}`);
|
||||||
@@ -37,7 +37,7 @@ const handleItemCompleted = (item: ConversationItem): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleItemUpdated = (item: ConversationItem): void => {
|
const handleItemUpdated = (item: ThreadItem): void => {
|
||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case "todo_list": {
|
case "todo_list": {
|
||||||
console.log(`Todo:`);
|
console.log(`Todo:`);
|
||||||
@@ -49,7 +49,7 @@ const handleItemUpdated = (item: ConversationItem): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEvent = (event: ConversationEvent): void => {
|
const handleEvent = (event: ThreadEvent): void => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "item.completed":
|
case "item.completed":
|
||||||
handleItemCompleted(event.item);
|
handleItemCompleted(event.item);
|
||||||
@@ -63,6 +63,9 @@ const handleEvent = (event: ConversationEvent): void => {
|
|||||||
`Used ${event.usage.input_tokens} input tokens, ${event.usage.cached_input_tokens} cached input tokens, ${event.usage.output_tokens} output tokens.`,
|
`Used ${event.usage.input_tokens} input tokens, ${event.usage.cached_input_tokens} cached input tokens, ${event.usage.output_tokens} output tokens.`,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "turn.failed":
|
||||||
|
console.error(`Turn failed: ${event.error.message}`);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// based on event types from codex-rs/exec/src/exec_events.rs
|
// based on event types from codex-rs/exec/src/exec_events.rs
|
||||||
|
|
||||||
import type { ConversationItem } from "./items";
|
import type { ThreadItem } from "./items";
|
||||||
|
|
||||||
export type SessionCreatedEvent = {
|
export type ThreadStartedEvent = {
|
||||||
type: "session.created";
|
type: "thread.started";
|
||||||
session_id: string;
|
thread_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TurnStartedEvent = {
|
export type TurnStartedEvent = {
|
||||||
@@ -22,31 +22,41 @@ export type TurnCompletedEvent = {
|
|||||||
usage: Usage;
|
usage: Usage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TurnFailedEvent = {
|
||||||
|
type: "turn.failed";
|
||||||
|
error: ThreadError;
|
||||||
|
};
|
||||||
|
|
||||||
export type ItemStartedEvent = {
|
export type ItemStartedEvent = {
|
||||||
type: "item.started";
|
type: "item.started";
|
||||||
item: ConversationItem;
|
item: ThreadItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ItemUpdatedEvent = {
|
export type ItemUpdatedEvent = {
|
||||||
type: "item.updated";
|
type: "item.updated";
|
||||||
item: ConversationItem;
|
item: ThreadItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ItemCompletedEvent = {
|
export type ItemCompletedEvent = {
|
||||||
type: "item.completed";
|
type: "item.completed";
|
||||||
item: ConversationItem;
|
item: ThreadItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConversationErrorEvent = {
|
export type ThreadError = {
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ThreadErrorEvent = {
|
||||||
type: "error";
|
type: "error";
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConversationEvent =
|
export type ThreadEvent =
|
||||||
| SessionCreatedEvent
|
| ThreadStartedEvent
|
||||||
| TurnStartedEvent
|
| TurnStartedEvent
|
||||||
| TurnCompletedEvent
|
| TurnCompletedEvent
|
||||||
|
| TurnFailedEvent
|
||||||
| ItemStartedEvent
|
| ItemStartedEvent
|
||||||
| ItemUpdatedEvent
|
| ItemUpdatedEvent
|
||||||
| ItemCompletedEvent
|
| ItemCompletedEvent
|
||||||
| ConversationErrorEvent;
|
| ThreadErrorEvent;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export type CodexExecArgs = {
|
|||||||
|
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
sessionId?: string | null;
|
threadId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CodexExec {
|
export class CodexExec {
|
||||||
@@ -17,8 +17,8 @@ export class CodexExec {
|
|||||||
|
|
||||||
async *run(args: CodexExecArgs): AsyncGenerator<string> {
|
async *run(args: CodexExecArgs): AsyncGenerator<string> {
|
||||||
const commandArgs: string[] = ["exec", "--experimental-json"];
|
const commandArgs: string[] = ["exec", "--experimental-json"];
|
||||||
if (args.sessionId) {
|
if (args.threadId) {
|
||||||
commandArgs.push("resume", args.sessionId, args.input);
|
commandArgs.push("resume", args.threadId, args.input);
|
||||||
} else {
|
} else {
|
||||||
commandArgs.push(args.input);
|
commandArgs.push(args.input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
export type {
|
export type {
|
||||||
ConversationEvent,
|
ThreadEvent,
|
||||||
SessionCreatedEvent,
|
ThreadStartedEvent,
|
||||||
TurnStartedEvent,
|
TurnStartedEvent,
|
||||||
TurnCompletedEvent,
|
TurnCompletedEvent,
|
||||||
|
TurnFailedEvent,
|
||||||
ItemStartedEvent,
|
ItemStartedEvent,
|
||||||
ItemUpdatedEvent,
|
ItemUpdatedEvent,
|
||||||
ItemCompletedEvent,
|
ItemCompletedEvent,
|
||||||
ConversationErrorEvent,
|
ThreadError,
|
||||||
|
ThreadErrorEvent,
|
||||||
} from "./events";
|
} from "./events";
|
||||||
export type {
|
export type {
|
||||||
ConversationItem,
|
ThreadItem,
|
||||||
AssistantMessageItem,
|
AssistantMessageItem,
|
||||||
ReasoningItem,
|
ReasoningItem,
|
||||||
CommandExecutionItem,
|
CommandExecutionItem,
|
||||||
|
|||||||
@@ -78,17 +78,7 @@ export type SessionItem = {
|
|||||||
session_id: string;
|
session_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConversationItem =
|
export type ThreadItem =
|
||||||
| AssistantMessageItem
|
|
||||||
| ReasoningItem
|
|
||||||
| CommandExecutionItem
|
|
||||||
| FileChangeItem
|
|
||||||
| McpToolCallItem
|
|
||||||
| WebSearchItem
|
|
||||||
| TodoListItem
|
|
||||||
| ErrorItem;
|
|
||||||
|
|
||||||
export type ConversationItemDetails =
|
|
||||||
| AssistantMessageItem
|
| AssistantMessageItem
|
||||||
| ReasoningItem
|
| ReasoningItem
|
||||||
| CommandExecutionItem
|
| CommandExecutionItem
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { CodexOptions } from "./codexOptions";
|
import { CodexOptions } from "./codexOptions";
|
||||||
import { ConversationEvent } from "./events";
|
import { ThreadEvent } from "./events";
|
||||||
import { CodexExec } from "./exec";
|
import { CodexExec } from "./exec";
|
||||||
import { ConversationItem } from "./items";
|
import { ThreadItem } from "./items";
|
||||||
|
|
||||||
export type RunResult = {
|
export type RunResult = {
|
||||||
items: ConversationItem[];
|
items: ThreadItem[];
|
||||||
finalResponse: string;
|
finalResponse: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RunStreamedResult = {
|
export type RunStreamedResult = {
|
||||||
events: AsyncGenerator<ConversationEvent>;
|
events: AsyncGenerator<ThreadEvent>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Input = string;
|
export type Input = string;
|
||||||
@@ -29,17 +29,17 @@ export class Thread {
|
|||||||
return { events: this.runStreamedInternal(input) };
|
return { events: this.runStreamedInternal(input) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async *runStreamedInternal(input: string): AsyncGenerator<ConversationEvent> {
|
private async *runStreamedInternal(input: string): AsyncGenerator<ThreadEvent> {
|
||||||
const generator = this.exec.run({
|
const generator = this.exec.run({
|
||||||
input,
|
input,
|
||||||
baseUrl: this.options.baseUrl,
|
baseUrl: this.options.baseUrl,
|
||||||
apiKey: this.options.apiKey,
|
apiKey: this.options.apiKey,
|
||||||
sessionId: this.id,
|
threadId: this.id,
|
||||||
});
|
});
|
||||||
for await (const item of generator) {
|
for await (const item of generator) {
|
||||||
const parsed = JSON.parse(item) as ConversationEvent;
|
const parsed = JSON.parse(item) as ThreadEvent;
|
||||||
if (parsed.type === "session.created") {
|
if (parsed.type === "thread.started") {
|
||||||
this.id = parsed.session_id;
|
this.id = parsed.thread_id;
|
||||||
}
|
}
|
||||||
yield parsed;
|
yield parsed;
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ export class Thread {
|
|||||||
|
|
||||||
async run(input: string): Promise<RunResult> {
|
async run(input: string): Promise<RunResult> {
|
||||||
const generator = this.runStreamedInternal(input);
|
const generator = this.runStreamedInternal(input);
|
||||||
const items: ConversationItem[] = [];
|
const items: ThreadItem[] = [];
|
||||||
let finalResponse: string = "";
|
let finalResponse: string = "";
|
||||||
for await (const event of generator) {
|
for await (const event of generator) {
|
||||||
if (event.type === "item.completed") {
|
if (event.type === "item.completed") {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
||||||
|
|
||||||
describe("Codex", () => {
|
describe("Codex", () => {
|
||||||
it("returns session events", async () => {
|
it("returns thread events", async () => {
|
||||||
const { url, close } = await startResponsesTestProxy({
|
const { url, close } = await startResponsesTestProxy({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
||||||
@@ -65,7 +65,7 @@ describe("Codex", () => {
|
|||||||
await thread.run("first input");
|
await thread.run("first input");
|
||||||
await thread.run("second input");
|
await thread.run("second input");
|
||||||
|
|
||||||
// Check second request continues conversation
|
// Check second request continues the same thread
|
||||||
expect(requests.length).toBeGreaterThanOrEqual(2);
|
expect(requests.length).toBeGreaterThanOrEqual(2);
|
||||||
const secondRequest = requests[1];
|
const secondRequest = requests[1];
|
||||||
expect(secondRequest).toBeDefined();
|
expect(secondRequest).toBeDefined();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from "path";
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Codex } from "../src/codex";
|
import { Codex } from "../src/codex";
|
||||||
import { ConversationEvent } from "../src/index";
|
import { ThreadEvent } from "../src/index";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
assistantMessage,
|
assistantMessage,
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
||||||
|
|
||||||
describe("Codex", () => {
|
describe("Codex", () => {
|
||||||
it("returns session events", async () => {
|
it("returns thread events", async () => {
|
||||||
const { url, close } = await startResponsesTestProxy({
|
const { url, close } = await startResponsesTestProxy({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
||||||
@@ -28,15 +28,15 @@ describe("Codex", () => {
|
|||||||
const thread = client.startThread();
|
const thread = client.startThread();
|
||||||
const result = await thread.runStreamed("Hello, world!");
|
const result = await thread.runStreamed("Hello, world!");
|
||||||
|
|
||||||
const events: ConversationEvent[] = [];
|
const events: ThreadEvent[] = [];
|
||||||
for await (const event of result.events) {
|
for await (const event of result.events) {
|
||||||
events.push(event);
|
events.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(events).toEqual([
|
expect(events).toEqual([
|
||||||
{
|
{
|
||||||
type: "session.created",
|
type: "thread.started",
|
||||||
session_id: expect.any(String),
|
thread_id: expect.any(String),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "turn.started",
|
type: "turn.started",
|
||||||
@@ -91,7 +91,7 @@ describe("Codex", () => {
|
|||||||
const second = await thread.runStreamed("second input");
|
const second = await thread.runStreamed("second input");
|
||||||
await drainEvents(second.events);
|
await drainEvents(second.events);
|
||||||
|
|
||||||
// Check second request continues conversation
|
// Check second request continues the same thread
|
||||||
expect(requests.length).toBeGreaterThanOrEqual(2);
|
expect(requests.length).toBeGreaterThanOrEqual(2);
|
||||||
const secondRequest = requests[1];
|
const secondRequest = requests[1];
|
||||||
expect(secondRequest).toBeDefined();
|
expect(secondRequest).toBeDefined();
|
||||||
@@ -159,7 +159,7 @@ describe("Codex", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function drainEvents(events: AsyncGenerator<ConversationEvent>): Promise<void> {
|
async function drainEvents(events: AsyncGenerator<ThreadEvent>): Promise<void> {
|
||||||
let done = false;
|
let done = false;
|
||||||
do {
|
do {
|
||||||
done = (await events.next()).done ?? false;
|
done = (await events.next()).done ?? false;
|
||||||
|
|||||||
Reference in New Issue
Block a user