feat: include "reasoning" messages in Rust TUI (#892)

As shown in the screenshot, we now include reasoning messages from the
model in the TUI under the heading "codex reasoning":


![image](https://github.com/user-attachments/assets/d8eb3dc3-2f9f-4e95-847e-d24b421249a8)

To ensure these are visible by default when using `o4-mini`, this also
changes the default value for `summary` (formerly `generate_summary`,
which is deprecated in favor of `summary` according to the docs) from
unset to `"auto"`.
This commit is contained in:
Michael Bolin
2025-05-10 21:43:27 -07:00
committed by GitHub
parent 2b122da087
commit b4785b5f88
9 changed files with 65 additions and 3 deletions

View File

@@ -26,6 +26,7 @@ use crate::client_common::Prompt;
use crate::client_common::Reasoning;
use crate::client_common::ResponseEvent;
use crate::client_common::ResponseStream;
use crate::client_common::Summary;
use crate::error::CodexErr;
use crate::error::Result;
use crate::flags::CODEX_RS_SSE_FIXTURE;
@@ -173,7 +174,7 @@ impl ModelClient {
parallel_tool_calls: false,
reasoning: Some(Reasoning {
effort: "high",
generate_summary: None,
summary: Some(Summary::Auto),
}),
previous_response_id: prompt.prev_id.clone(),
store: prompt.store,

View File

@@ -36,7 +36,19 @@ pub enum ResponseEvent {
pub(crate) struct Reasoning {
pub(crate) effort: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) generate_summary: Option<bool>,
pub(crate) summary: Option<Summary>,
}
/// A summary of the reasoning performed by the model. This can be useful for
/// debugging and understanding the model's reasoning process.
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum Summary {
Auto,
#[allow(dead_code)] // Will go away once this is configurable.
Concise,
#[allow(dead_code)] // Will go away once this is configurable.
Detailed,
}
#[derive(Debug, Serialize)]

View File

@@ -49,6 +49,7 @@ use crate::mcp_connection_manager::try_parse_fully_qualified_tool_name;
use crate::mcp_tool_call::handle_mcp_tool_call;
use crate::models::ContentItem;
use crate::models::FunctionCallOutputPayload;
use crate::models::ReasoningItemReasoningSummary;
use crate::models::ResponseInputItem;
use crate::models::ResponseItem;
use crate::models::ShellToolCallParams;
@@ -934,6 +935,18 @@ async fn handle_response_item(
}
}
}
ResponseItem::Reasoning { id: _, summary } => {
for item in summary {
let text = match item {
ReasoningItemReasoningSummary::SummaryText { text } => text,
};
let event = Event {
id: sub_id.to_string(),
msg: EventMsg::AgentReasoning { text },
};
sess.tx_event.send(event).await.ok();
}
}
ResponseItem::FunctionCall {
name,
arguments,

View File

@@ -33,6 +33,10 @@ pub enum ResponseItem {
role: String,
content: Vec<ContentItem>,
},
Reasoning {
id: String,
summary: Vec<ReasoningItemReasoningSummary>,
},
FunctionCall {
name: String,
// The Responses API returns the function call arguments as a *string* that contains
@@ -67,6 +71,12 @@ impl From<ResponseInputItem> for ResponseItem {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ReasoningItemReasoningSummary {
SummaryText { text: String },
}
impl From<Vec<InputItem>> for ResponseInputItem {
fn from(items: Vec<InputItem>) -> Self {
Self::Message {

View File

@@ -317,6 +317,11 @@ pub enum EventMsg {
message: String,
},
/// Reasoning event from agent.
AgentReasoning {
text: String,
},
/// Ack the client's configure message.
SessionConfigured {
/// Tell the client what model is being queried.

View File

@@ -114,7 +114,7 @@ impl RolloutRecorder {
ResponseItem::Message { .. }
| ResponseItem::FunctionCall { .. }
| ResponseItem::FunctionCallOutput { .. } => {}
ResponseItem::Other => {
ResponseItem::Reasoning { .. } | ResponseItem::Other => {
// These should never be serialized.
continue;
}

View File

@@ -225,6 +225,10 @@ impl ChatWidget<'_> {
self.conversation_history.add_agent_message(message);
self.request_redraw()?;
}
EventMsg::AgentReasoning { text } => {
self.conversation_history.add_agent_reasoning(text);
self.request_redraw()?;
}
EventMsg::TaskStarted => {
self.bottom_pane.set_task_running(true)?;
self.request_redraw()?;

View File

@@ -174,6 +174,10 @@ impl ConversationHistoryWidget {
self.add_to_history(HistoryCell::new_agent_message(message));
}
pub fn add_agent_reasoning(&mut self, text: String) {
self.add_to_history(HistoryCell::new_agent_reasoning(text));
}
pub fn add_background_event(&mut self, message: String) {
self.add_to_history(HistoryCell::new_background_event(message));
}

View File

@@ -41,6 +41,9 @@ pub(crate) enum HistoryCell {
/// Message from the agent.
AgentMessage { lines: Vec<Line<'static>> },
/// Reasoning event from the agent.
AgentReasoning { lines: Vec<Line<'static>> },
/// An exec tool call that has not finished yet.
ActiveExecCommand {
call_id: String,
@@ -134,6 +137,15 @@ impl HistoryCell {
HistoryCell::AgentMessage { lines }
}
pub(crate) fn new_agent_reasoning(text: String) -> Self {
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::from("codex reasoning".magenta().italic()));
append_markdown(&text, &mut lines);
lines.push(Line::from(""));
HistoryCell::AgentReasoning { lines }
}
pub(crate) fn new_active_exec_command(call_id: String, command: Vec<String>) -> Self {
let command_escaped = escape_command(&command);
let start = Instant::now();
@@ -363,6 +375,7 @@ impl HistoryCell {
HistoryCell::WelcomeMessage { lines, .. }
| HistoryCell::UserPrompt { lines, .. }
| HistoryCell::AgentMessage { lines, .. }
| HistoryCell::AgentReasoning { lines, .. }
| HistoryCell::BackgroundEvent { lines, .. }
| HistoryCell::ErrorEvent { lines, .. }
| HistoryCell::SessionInfo { lines, .. }