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":  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:
@@ -26,6 +26,7 @@ use crate::client_common::Prompt;
|
|||||||
use crate::client_common::Reasoning;
|
use crate::client_common::Reasoning;
|
||||||
use crate::client_common::ResponseEvent;
|
use crate::client_common::ResponseEvent;
|
||||||
use crate::client_common::ResponseStream;
|
use crate::client_common::ResponseStream;
|
||||||
|
use crate::client_common::Summary;
|
||||||
use crate::error::CodexErr;
|
use crate::error::CodexErr;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||||
@@ -173,7 +174,7 @@ impl ModelClient {
|
|||||||
parallel_tool_calls: false,
|
parallel_tool_calls: false,
|
||||||
reasoning: Some(Reasoning {
|
reasoning: Some(Reasoning {
|
||||||
effort: "high",
|
effort: "high",
|
||||||
generate_summary: None,
|
summary: Some(Summary::Auto),
|
||||||
}),
|
}),
|
||||||
previous_response_id: prompt.prev_id.clone(),
|
previous_response_id: prompt.prev_id.clone(),
|
||||||
store: prompt.store,
|
store: prompt.store,
|
||||||
|
|||||||
@@ -36,7 +36,19 @@ pub enum ResponseEvent {
|
|||||||
pub(crate) struct Reasoning {
|
pub(crate) struct Reasoning {
|
||||||
pub(crate) effort: &'static str,
|
pub(crate) effort: &'static str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[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)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|||||||
@@ -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::mcp_tool_call::handle_mcp_tool_call;
|
||||||
use crate::models::ContentItem;
|
use crate::models::ContentItem;
|
||||||
use crate::models::FunctionCallOutputPayload;
|
use crate::models::FunctionCallOutputPayload;
|
||||||
|
use crate::models::ReasoningItemReasoningSummary;
|
||||||
use crate::models::ResponseInputItem;
|
use crate::models::ResponseInputItem;
|
||||||
use crate::models::ResponseItem;
|
use crate::models::ResponseItem;
|
||||||
use crate::models::ShellToolCallParams;
|
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 {
|
ResponseItem::FunctionCall {
|
||||||
name,
|
name,
|
||||||
arguments,
|
arguments,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ pub enum ResponseItem {
|
|||||||
role: String,
|
role: String,
|
||||||
content: Vec<ContentItem>,
|
content: Vec<ContentItem>,
|
||||||
},
|
},
|
||||||
|
Reasoning {
|
||||||
|
id: String,
|
||||||
|
summary: Vec<ReasoningItemReasoningSummary>,
|
||||||
|
},
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
name: String,
|
name: String,
|
||||||
// The Responses API returns the function call arguments as a *string* that contains
|
// 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 {
|
impl From<Vec<InputItem>> for ResponseInputItem {
|
||||||
fn from(items: Vec<InputItem>) -> Self {
|
fn from(items: Vec<InputItem>) -> Self {
|
||||||
Self::Message {
|
Self::Message {
|
||||||
|
|||||||
@@ -317,6 +317,11 @@ pub enum EventMsg {
|
|||||||
message: String,
|
message: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Reasoning event from agent.
|
||||||
|
AgentReasoning {
|
||||||
|
text: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Ack the client's configure message.
|
/// Ack the client's configure message.
|
||||||
SessionConfigured {
|
SessionConfigured {
|
||||||
/// Tell the client what model is being queried.
|
/// Tell the client what model is being queried.
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ impl RolloutRecorder {
|
|||||||
ResponseItem::Message { .. }
|
ResponseItem::Message { .. }
|
||||||
| ResponseItem::FunctionCall { .. }
|
| ResponseItem::FunctionCall { .. }
|
||||||
| ResponseItem::FunctionCallOutput { .. } => {}
|
| ResponseItem::FunctionCallOutput { .. } => {}
|
||||||
ResponseItem::Other => {
|
ResponseItem::Reasoning { .. } | ResponseItem::Other => {
|
||||||
// These should never be serialized.
|
// These should never be serialized.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,6 +225,10 @@ impl ChatWidget<'_> {
|
|||||||
self.conversation_history.add_agent_message(message);
|
self.conversation_history.add_agent_message(message);
|
||||||
self.request_redraw()?;
|
self.request_redraw()?;
|
||||||
}
|
}
|
||||||
|
EventMsg::AgentReasoning { text } => {
|
||||||
|
self.conversation_history.add_agent_reasoning(text);
|
||||||
|
self.request_redraw()?;
|
||||||
|
}
|
||||||
EventMsg::TaskStarted => {
|
EventMsg::TaskStarted => {
|
||||||
self.bottom_pane.set_task_running(true)?;
|
self.bottom_pane.set_task_running(true)?;
|
||||||
self.request_redraw()?;
|
self.request_redraw()?;
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ impl ConversationHistoryWidget {
|
|||||||
self.add_to_history(HistoryCell::new_agent_message(message));
|
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) {
|
pub fn add_background_event(&mut self, message: String) {
|
||||||
self.add_to_history(HistoryCell::new_background_event(message));
|
self.add_to_history(HistoryCell::new_background_event(message));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub(crate) enum HistoryCell {
|
|||||||
/// Message from the agent.
|
/// Message from the agent.
|
||||||
AgentMessage { lines: Vec<Line<'static>> },
|
AgentMessage { lines: Vec<Line<'static>> },
|
||||||
|
|
||||||
|
/// Reasoning event from the agent.
|
||||||
|
AgentReasoning { lines: Vec<Line<'static>> },
|
||||||
|
|
||||||
/// An exec tool call that has not finished yet.
|
/// An exec tool call that has not finished yet.
|
||||||
ActiveExecCommand {
|
ActiveExecCommand {
|
||||||
call_id: String,
|
call_id: String,
|
||||||
@@ -134,6 +137,15 @@ impl HistoryCell {
|
|||||||
HistoryCell::AgentMessage { lines }
|
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 {
|
pub(crate) fn new_active_exec_command(call_id: String, command: Vec<String>) -> Self {
|
||||||
let command_escaped = escape_command(&command);
|
let command_escaped = escape_command(&command);
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -363,6 +375,7 @@ impl HistoryCell {
|
|||||||
HistoryCell::WelcomeMessage { lines, .. }
|
HistoryCell::WelcomeMessage { lines, .. }
|
||||||
| HistoryCell::UserPrompt { lines, .. }
|
| HistoryCell::UserPrompt { lines, .. }
|
||||||
| HistoryCell::AgentMessage { lines, .. }
|
| HistoryCell::AgentMessage { lines, .. }
|
||||||
|
| HistoryCell::AgentReasoning { lines, .. }
|
||||||
| HistoryCell::BackgroundEvent { lines, .. }
|
| HistoryCell::BackgroundEvent { lines, .. }
|
||||||
| HistoryCell::ErrorEvent { lines, .. }
|
| HistoryCell::ErrorEvent { lines, .. }
|
||||||
| HistoryCell::SessionInfo { lines, .. }
|
| HistoryCell::SessionInfo { lines, .. }
|
||||||
|
|||||||
Reference in New Issue
Block a user