From caab634c57a6feee21da6714bd8d1cb1ae5c6ce3 Mon Sep 17 00:00:00 2001 From: aibrahim-oai Date: Sun, 13 Jul 2025 23:39:56 -0700 Subject: [PATCH 1/4] feat(cli): show raw reasoning content --- codex-rs/config.md | 10 ++++++++++ codex-rs/core/src/codex.rs | 14 +++++++++++++- codex-rs/core/src/config.rs | 12 ++++++++++++ codex-rs/core/src/models.rs | 8 ++++++++ codex-rs/core/src/protocol.rs | 10 +++++++++- codex-rs/exec/src/event_processor.rs | 20 +++++++++++++++++++- codex-rs/exec/src/lib.rs | 7 +++++-- codex-rs/mcp-server/src/codex_tool_runner.rs | 1 + codex-rs/tui/src/chatwidget.rs | 8 ++++++++ 9 files changed, 85 insertions(+), 5 deletions(-) diff --git a/codex-rs/config.md b/codex-rs/config.md index 438b7e767d..5399513c85 100644 --- a/codex-rs/config.md +++ b/codex-rs/config.md @@ -452,6 +452,16 @@ Setting `hide_agent_reasoning` to `true` suppresses these events in **both** the hide_agent_reasoning = true # defaults to false ``` +## show_reasoning_content + +When enabled, Codex will display the raw chain-of-thought text returned by the +model for reasoning events. This content is not moderated and may contain +hallucinations or other undesirable text, so it is disabled by default. + +```toml +show_reasoning_content = true # defaults to false +``` + ## model_context_window The size of the context window for the model, in tokens. diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 52c37c51ee..c3b20ef7e1 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -56,12 +56,14 @@ use crate::mcp_tool_call::handle_mcp_tool_call; use crate::models::ContentItem; use crate::models::FunctionCallOutputPayload; use crate::models::LocalShellAction; +use crate::models::ReasoningItemContent; use crate::models::ReasoningItemReasoningSummary; use crate::models::ResponseInputItem; use crate::models::ResponseItem; use crate::models::ShellToolCallParams; use crate::project_doc::get_user_instructions; use crate::protocol::AgentMessageEvent; +use crate::protocol::AgentReasoningContentEvent; use crate::protocol::AgentReasoningEvent; use crate::protocol::ApplyPatchApprovalRequestEvent; use crate::protocol::AskForApproval; @@ -1196,7 +1198,7 @@ async fn handle_response_item( } None } - ResponseItem::Reasoning { id: _, summary } => { + ResponseItem::Reasoning { id: _, summary, content } => { for item in summary { let text = match item { ReasoningItemReasoningSummary::SummaryText { text } => text, @@ -1207,6 +1209,16 @@ async fn handle_response_item( }; sess.tx_event.send(event).await.ok(); } + for item in content { + let text = match item { + ReasoningItemContent::ReasoningText { text } => text, + }; + let event = Event { + id: sub_id.to_string(), + msg: EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }), + }; + sess.tx_event.send(event).await.ok(); + } None } ResponseItem::FunctionCall { diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index d67e692fc8..e6a094346c 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -57,6 +57,10 @@ pub struct Config { /// users are only interested in the final agent responses. pub hide_agent_reasoning: bool, + /// When `true`, the raw chain-of-thought text from reasoning events will be + /// displayed in the UI in addition to the reasoning summaries. + pub show_reasoning_content: bool, + /// Disable server-side response storage (sends the full conversation /// context with every request). Currently necessary for OpenAI customers /// who have opted into Zero Data Retention (ZDR). @@ -313,6 +317,10 @@ pub struct ConfigToml { /// UI/output. Defaults to `false`. pub hide_agent_reasoning: Option, + /// When set to `true`, raw chain-of-thought text from reasoning events will + /// be shown in the UI. + pub show_reasoning_content: Option, + pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, @@ -477,6 +485,7 @@ impl Config { codex_linux_sandbox_exe, hide_agent_reasoning: cfg.hide_agent_reasoning.unwrap_or(false), + show_reasoning_content: cfg.show_reasoning_content.unwrap_or(false), model_reasoning_effort: config_profile .model_reasoning_effort .or(cfg.model_reasoning_effort) @@ -796,6 +805,7 @@ disable_response_storage = true tui: Tui::default(), codex_linux_sandbox_exe: None, hide_agent_reasoning: false, + show_reasoning_content: false, model_reasoning_effort: ReasoningEffort::High, model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: false, @@ -842,6 +852,7 @@ disable_response_storage = true tui: Tui::default(), codex_linux_sandbox_exe: None, hide_agent_reasoning: false, + show_reasoning_content: false, model_reasoning_effort: ReasoningEffort::default(), model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: false, @@ -903,6 +914,7 @@ disable_response_storage = true tui: Tui::default(), codex_linux_sandbox_exe: None, hide_agent_reasoning: false, + show_reasoning_content: false, model_reasoning_effort: ReasoningEffort::default(), model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: false, diff --git a/codex-rs/core/src/models.rs b/codex-rs/core/src/models.rs index 6b392fb19d..864ab1d799 100644 --- a/codex-rs/core/src/models.rs +++ b/codex-rs/core/src/models.rs @@ -43,6 +43,8 @@ pub enum ResponseItem { Reasoning { id: String, summary: Vec, + #[serde(default)] + content: Vec, }, LocalShellCall { /// Set when using the chat completions API. @@ -128,6 +130,12 @@ pub enum ReasoningItemReasoningSummary { SummaryText { text: String }, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ReasoningItemContent { + ReasoningText { text: String }, +} + impl From> for ResponseInputItem { fn from(items: Vec) -> Self { Self::Message { diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index fa25a2fe38..dd64b4c8a8 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -282,9 +282,12 @@ pub enum EventMsg { /// Agent text output message AgentMessage(AgentMessageEvent), - /// Reasoning event from agent. + /// Reasoning summary from agent. AgentReasoning(AgentReasoningEvent), + /// Raw chain-of-thought from agent. + AgentReasoningContent(AgentReasoningContentEvent), + /// Ack the client's configure message. SessionConfigured(SessionConfiguredEvent), @@ -345,6 +348,11 @@ pub struct AgentReasoningEvent { pub text: String, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AgentReasoningContentEvent { + pub text: String, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct McpToolCallBeginEvent { /// Identifier so this can be paired with the McpToolCallEnd event. diff --git a/codex-rs/exec/src/event_processor.rs b/codex-rs/exec/src/event_processor.rs index 540e014298..03100ce455 100644 --- a/codex-rs/exec/src/event_processor.rs +++ b/codex-rs/exec/src/event_processor.rs @@ -15,6 +15,7 @@ use codex_core::protocol::McpToolCallBeginEvent; use codex_core::protocol::McpToolCallEndEvent; use codex_core::protocol::PatchApplyBeginEvent; use codex_core::protocol::PatchApplyEndEvent; +use codex_core::protocol::AgentReasoningContentEvent; use codex_core::protocol::SessionConfiguredEvent; use codex_core::protocol::TokenUsage; use owo_colors::OwoColorize; @@ -50,10 +51,15 @@ pub(crate) struct EventProcessor { /// Whether to include `AgentReasoning` events in the output. show_agent_reasoning: bool, + show_agent_reasoning_content: bool, } impl EventProcessor { - pub(crate) fn create_with_ansi(with_ansi: bool, show_agent_reasoning: bool) -> Self { + pub(crate) fn create_with_ansi( + with_ansi: bool, + show_agent_reasoning: bool, + show_agent_reasoning_content: bool, + ) -> Self { let call_id_to_command = HashMap::new(); let call_id_to_patch = HashMap::new(); let call_id_to_tool_call = HashMap::new(); @@ -71,6 +77,7 @@ impl EventProcessor { cyan: Style::new().cyan(), call_id_to_tool_call, show_agent_reasoning, + show_agent_reasoning_content, } } else { Self { @@ -85,6 +92,7 @@ impl EventProcessor { cyan: Style::new(), call_id_to_tool_call, show_agent_reasoning, + show_agent_reasoning_content, } } } @@ -449,6 +457,16 @@ impl EventProcessor { ); } } + EventMsg::AgentReasoningContent(agent_reasoning_event) => { + if self.show_agent_reasoning && self.show_agent_reasoning_content { + ts_println!( + self, + "{}\n{}", + "thinking".style(self.italic).style(self.magenta), + agent_reasoning_event.text + ); + } + } EventMsg::SessionConfigured(session_configured_event) => { let SessionConfiguredEvent { session_id, diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 44dddd4d0f..85f154c5aa 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -115,8 +115,11 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any }; let config = Config::load_with_cli_overrides(cli_kv_overrides, overrides)?; - let mut event_processor = - EventProcessor::create_with_ansi(stdout_with_ansi, !config.hide_agent_reasoning); + let mut event_processor = EventProcessor::create_with_ansi( + stdout_with_ansi, + !config.hide_agent_reasoning, + config.show_reasoning_content, + ); // Print the effective configuration and prompt so users can see what Codex // is using. event_processor.print_config_summary(&config, &prompt); diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 7c3b02fe5e..26f0636ae6 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -175,6 +175,7 @@ pub async fn run_codex_tool_session( | EventMsg::TaskStarted | EventMsg::TokenCount(_) | EventMsg::AgentReasoning(_) + | EventMsg::AgentReasoningContent(_) | EventMsg::McpToolCallBegin(_) | EventMsg::McpToolCallEnd(_) | EventMsg::ExecCommandBegin(_) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 865e339763..f1923be1a1 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use codex_core::codex_wrapper::init_codex; use codex_core::config::Config; use codex_core::protocol::AgentMessageEvent; +use codex_core::protocol::AgentReasoningContentEvent; use codex_core::protocol::AgentReasoningEvent; use codex_core::protocol::ApplyPatchApprovalRequestEvent; use codex_core::protocol::ErrorEvent; @@ -251,6 +252,13 @@ impl ChatWidget<'_> { self.request_redraw(); } } + EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }) => { + if !self.config.hide_agent_reasoning && self.config.show_reasoning_content { + self.conversation_history + .add_agent_reasoning(&self.config, text); + self.request_redraw(); + } + } EventMsg::TaskStarted => { self.bottom_pane.clear_ctrl_c_quit_hint(); self.bottom_pane.set_task_running(true); From 9f65dafe5556d81f7a3ccd7980d097a5e7dfde80 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 14 Jul 2025 08:23:14 -0700 Subject: [PATCH 2/4] fmt --- codex-rs/core/src/codex.rs | 13 +++++++++++-- codex-rs/exec/src/event_processor.rs | 1 - codex-rs/tui/src/chatwidget.rs | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c3b20ef7e1..d39f239b88 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1127,11 +1127,14 @@ async fn try_run_turn( // If we execute a function call in the middle of handling the stream, it can time out. let mut input = Vec::new(); while let Some(event) = stream.next().await { - input.push(event?); + let event = event?; + info!("Received stream event: {event:?}"); + input.push(event); } let mut output = Vec::new(); for event in input { + info!("Processing event: {event:?}"); match event { ResponseEvent::Created => { let mut state = sess.state.lock().unwrap(); @@ -1198,11 +1201,16 @@ async fn handle_response_item( } None } - ResponseItem::Reasoning { id: _, summary, content } => { + ResponseItem::Reasoning { + id: _, + summary, + content, + } => { for item in summary { let text = match item { ReasoningItemReasoningSummary::SummaryText { text } => text, }; + info!("Agent reasoning: {text}"); let event = Event { id: sub_id.to_string(), msg: EventMsg::AgentReasoning(AgentReasoningEvent { text }), @@ -1213,6 +1221,7 @@ async fn handle_response_item( let text = match item { ReasoningItemContent::ReasoningText { text } => text, }; + info!("Agent reasoning content: {text}"); let event = Event { id: sub_id.to_string(), msg: EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }), diff --git a/codex-rs/exec/src/event_processor.rs b/codex-rs/exec/src/event_processor.rs index 03100ce455..57a364c3f1 100644 --- a/codex-rs/exec/src/event_processor.rs +++ b/codex-rs/exec/src/event_processor.rs @@ -15,7 +15,6 @@ use codex_core::protocol::McpToolCallBeginEvent; use codex_core::protocol::McpToolCallEndEvent; use codex_core::protocol::PatchApplyBeginEvent; use codex_core::protocol::PatchApplyEndEvent; -use codex_core::protocol::AgentReasoningContentEvent; use codex_core::protocol::SessionConfiguredEvent; use codex_core::protocol::TokenUsage; use owo_colors::OwoColorize; diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index f1923be1a1..3841db1dd9 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -254,6 +254,7 @@ impl ChatWidget<'_> { } EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }) => { if !self.config.hide_agent_reasoning && self.config.show_reasoning_content { + let text = format!("Full Reasoning Content: {text}"); self.conversation_history .add_agent_reasoning(&self.config, text); self.request_redraw(); From 961fb2d14f60b9549fc7e6bf2b5a26c553a31fa7 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 31 Jul 2025 09:21:04 -0700 Subject: [PATCH 3/4] revive --- codex-rs/core/src/codex.rs | 5 +++-- codex-rs/core/src/protocol.rs | 3 ++- codex-rs/exec/src/event_processor_with_human_output.rs | 6 ++++++ codex-rs/tui/src/chatwidget.rs | 8 ++------ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 710cb832e9..c282a94b91 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1005,6 +1005,7 @@ async fn run_task(sess: Arc, sub_id: String, input: Vec) { id, summary, encrypted_content, + content, }, None, ) => { @@ -1012,6 +1013,7 @@ async fn run_task(sess: Arc, sub_id: String, input: Vec) { id: id.clone(), summary: summary.clone(), encrypted_content: encrypted_content.clone(), + content: content.clone(), }); } _ => { @@ -1272,12 +1274,12 @@ async fn handle_response_item( id: _, summary, content, + encrypted_content: _, } => { for item in summary { let text = match item { ReasoningItemReasoningSummary::SummaryText { text } => text, }; - info!("Agent reasoning: {text}"); let event = Event { id: sub_id.to_string(), msg: EventMsg::AgentReasoning(AgentReasoningEvent { text }), @@ -1288,7 +1290,6 @@ async fn handle_response_item( let text = match item { ReasoningItemContent::ReasoningText { text } => text, }; - info!("Agent reasoning content: {text}"); let event = Event { id: sub_id.to_string(), msg: EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }), diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index 2111aff74f..36152b4cb8 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -308,7 +308,7 @@ pub enum EventMsg { /// Agent reasoning delta event from agent. AgentReasoningDelta(AgentReasoningDeltaEvent), - + /// Raw chain-of-thought from agent. AgentReasoningContent(AgentReasoningContentEvent), @@ -417,6 +417,7 @@ pub struct AgentReasoningContentEvent { pub text: String, } +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct AgentReasoningDeltaEvent { pub delta: String, } diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 54604d538b..ec7bd8b5c4 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -4,6 +4,7 @@ use codex_core::config::Config; use codex_core::plan_tool::UpdatePlanArgs; use codex_core::protocol::AgentMessageDeltaEvent; use codex_core::protocol::AgentMessageEvent; +use codex_core::protocol::AgentReasoningContentEvent; use codex_core::protocol::AgentReasoningDeltaEvent; use codex_core::protocol::BackgroundEventEvent; use codex_core::protocol::ErrorEvent; @@ -204,6 +205,11 @@ impl EventProcessor for EventProcessorWithHumanOutput { #[allow(clippy::expect_used)] std::io::stdout().flush().expect("could not flush stdout"); } + EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }) => { + print!("{text}"); + #[allow(clippy::expect_used)] + std::io::stdout().flush().expect("could not flush stdout"); + } EventMsg::AgentMessage(AgentMessageEvent { message }) => { // if answer_started is false, this means we haven't received any // delta. Thus, we need to print the message as a new answer. diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index f81e35d375..c4c2de7761 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -275,12 +275,8 @@ impl ChatWidget<'_> { self.request_redraw(); } EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }) => { - if !self.config.hide_agent_reasoning && self.config.show_reasoning_content { - let text = format!("Full Reasoning Content: {text}"); - self.conversation_history - .add_agent_reasoning(&self.config, text); - self.request_redraw(); - } + self.add_to_history(HistoryCell::new_agent_reasoning(&self.config, text)); + self.request_redraw(); } EventMsg::TaskStarted => { self.bottom_pane.clear_ctrl_c_quit_hint(); From d1275897ab19ad2ac18a2b7326244a766b3423f1 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 31 Jul 2025 09:44:22 -0700 Subject: [PATCH 4/4] adding config --- codex-rs/core/src/codex.rs | 23 ++++++++++++++--------- codex-rs/core/src/models.rs | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c282a94b91..308916341a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -223,6 +223,7 @@ pub(crate) struct Session { state: Mutex, codex_linux_sandbox_exe: Option, user_shell: shell::Shell, + show_reasoning_content: bool, } impl Session { @@ -722,6 +723,7 @@ async fn submission_loop( codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), disable_response_storage, user_shell: default_shell, + show_reasoning_content: config.show_reasoning_content, })); // Patch restored state into the newly created session. @@ -1286,15 +1288,18 @@ async fn handle_response_item( }; sess.tx_event.send(event).await.ok(); } - for item in content { - let text = match item { - ReasoningItemContent::ReasoningText { text } => text, - }; - let event = Event { - id: sub_id.to_string(), - msg: EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }), - }; - sess.tx_event.send(event).await.ok(); + if sess.show_reasoning_content && content.is_some() { + let content = content.unwrap(); + for item in content { + let text = match item { + ReasoningItemContent::ReasoningText { text } => text, + }; + let event = Event { + id: sub_id.to_string(), + msg: EventMsg::AgentReasoningContent(AgentReasoningContentEvent { text }), + }; + sess.tx_event.send(event).await.ok(); + } } None } diff --git a/codex-rs/core/src/models.rs b/codex-rs/core/src/models.rs index 2a5e8c6f22..98d8727e77 100644 --- a/codex-rs/core/src/models.rs +++ b/codex-rs/core/src/models.rs @@ -45,8 +45,8 @@ pub enum ResponseItem { Reasoning { id: String, summary: Vec, - #[serde(default)] - content: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + content: Option>, encrypted_content: Option, }, LocalShellCall {