restyle thinking outputs (#3755)
<img width="1205" height="930" alt="Screenshot 2025-09-16 at 2 23 18 PM" src="https://github.com/user-attachments/assets/bb2494f1-dd59-4bc9-9c4e-740605c999fd" />
This commit is contained in:
@@ -226,12 +226,11 @@ impl ChatWidget {
|
||||
// At the end of a reasoning block, record transcript-only content.
|
||||
self.full_reasoning_buffer.push_str(&self.reasoning_buffer);
|
||||
if !self.full_reasoning_buffer.is_empty() {
|
||||
for cell in history_cell::new_reasoning_summary_block(
|
||||
let cell = history_cell::new_reasoning_summary_block(
|
||||
self.full_reasoning_buffer.clone(),
|
||||
&self.config,
|
||||
) {
|
||||
self.add_boxed_history(cell);
|
||||
}
|
||||
);
|
||||
self.add_boxed_history(cell);
|
||||
}
|
||||
self.reasoning_buffer.clear();
|
||||
self.full_reasoning_buffer.clear();
|
||||
|
||||
@@ -121,6 +121,45 @@ impl HistoryCell for UserHistoryCell {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReasoningSummaryCell {
|
||||
_header: Vec<Line<'static>>,
|
||||
content: Vec<Line<'static>>,
|
||||
}
|
||||
|
||||
impl ReasoningSummaryCell {
|
||||
pub(crate) fn new(header: Vec<Line<'static>>, content: Vec<Line<'static>>) -> Self {
|
||||
Self {
|
||||
_header: header,
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HistoryCell for ReasoningSummaryCell {
|
||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||
let summary_lines = self
|
||||
.content
|
||||
.iter()
|
||||
.map(|l| l.clone().dim().italic())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
word_wrap_lines(
|
||||
&summary_lines,
|
||||
RtOptions::new(width as usize)
|
||||
.initial_indent("• ".into())
|
||||
.subsequent_indent(" ".into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
||||
let mut out: Vec<Line<'static>> = Vec::new();
|
||||
out.push("thinking".magenta().bold().into());
|
||||
out.extend(self.content.clone());
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AgentMessageCell {
|
||||
lines: Vec<Line<'static>>,
|
||||
@@ -1417,7 +1456,7 @@ pub(crate) fn new_reasoning_block(
|
||||
pub(crate) fn new_reasoning_summary_block(
|
||||
full_reasoning_buffer: String,
|
||||
config: &Config,
|
||||
) -> Vec<Box<dyn HistoryCell>> {
|
||||
) -> Box<dyn HistoryCell> {
|
||||
if config.model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental {
|
||||
// Experimental format is following:
|
||||
// ** header **
|
||||
@@ -1434,27 +1473,19 @@ pub(crate) fn new_reasoning_summary_block(
|
||||
// then we don't have a summary to inject into history
|
||||
if after_close_idx < full_reasoning_buffer.len() {
|
||||
let header_buffer = full_reasoning_buffer[..after_close_idx].to_string();
|
||||
let summary_buffer = full_reasoning_buffer[after_close_idx..].to_string();
|
||||
|
||||
let mut header_lines: Vec<Line<'static>> = Vec::new();
|
||||
header_lines.push(Line::from("Thinking".magenta().italic()));
|
||||
let mut header_lines = Vec::new();
|
||||
append_markdown(&header_buffer, &mut header_lines, config);
|
||||
|
||||
let mut summary_lines: Vec<Line<'static>> = Vec::new();
|
||||
summary_lines.push(Line::from("Thinking".magenta().bold()));
|
||||
let summary_buffer = full_reasoning_buffer[after_close_idx..].to_string();
|
||||
let mut summary_lines = Vec::new();
|
||||
append_markdown(&summary_buffer, &mut summary_lines, config);
|
||||
|
||||
return vec![
|
||||
Box::new(TranscriptOnlyHistoryCell {
|
||||
lines: header_lines,
|
||||
}),
|
||||
Box::new(AgentMessageCell::new(summary_lines, true)),
|
||||
];
|
||||
return Box::new(ReasoningSummaryCell::new(header_lines, summary_lines));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vec![Box::new(new_reasoning_block(full_reasoning_buffer, config))]
|
||||
Box::new(new_reasoning_block(full_reasoning_buffer, config))
|
||||
}
|
||||
|
||||
struct OutputLinesParams {
|
||||
@@ -1558,6 +1589,7 @@ mod tests {
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigToml;
|
||||
use dirs::home_dir;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn test_config() -> Config {
|
||||
Config::load_from_base_config_with_overrides(
|
||||
@@ -2076,17 +2108,35 @@ mod tests {
|
||||
let rendered = render_lines(&lines).join("\n");
|
||||
insta::assert_snapshot!(rendered);
|
||||
}
|
||||
#[test]
|
||||
fn reasoning_summary_block() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
|
||||
&config,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
|
||||
|
||||
let rendered_transcript = render_transcript(cell.as_ref());
|
||||
assert_eq!(
|
||||
rendered_transcript,
|
||||
vec!["thinking", "Detailed reasoning goes here."]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let cells =
|
||||
let cell =
|
||||
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &config);
|
||||
|
||||
assert_eq!(cells.len(), 1);
|
||||
let rendered = render_transcript(cells[0].as_ref());
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(rendered, vec!["thinking", "Detailed reasoning goes here."]);
|
||||
}
|
||||
|
||||
@@ -2095,13 +2145,12 @@ mod tests {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let cells = new_reasoning_summary_block(
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing".to_string(),
|
||||
&config,
|
||||
);
|
||||
|
||||
assert_eq!(cells.len(), 1);
|
||||
let rendered = render_transcript(cells[0].as_ref());
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(
|
||||
rendered,
|
||||
vec!["thinking", "**High level reasoning without closing"]
|
||||
@@ -2113,25 +2162,23 @@ mod tests {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let cells = new_reasoning_summary_block(
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**".to_string(),
|
||||
&config,
|
||||
);
|
||||
|
||||
assert_eq!(cells.len(), 1);
|
||||
let rendered = render_transcript(cells[0].as_ref());
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(
|
||||
rendered,
|
||||
vec!["thinking", "High level reasoning without closing"]
|
||||
);
|
||||
|
||||
let cells = new_reasoning_summary_block(
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**\n\n ".to_string(),
|
||||
&config,
|
||||
);
|
||||
|
||||
assert_eq!(cells.len(), 1);
|
||||
let rendered = render_transcript(cells[0].as_ref());
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(
|
||||
rendered,
|
||||
vec!["thinking", "High level reasoning without closing"]
|
||||
@@ -2143,21 +2190,18 @@ mod tests {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let cells = new_reasoning_summary_block(
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level plan**\n\nWe should fix the bug next.".to_string(),
|
||||
&config,
|
||||
);
|
||||
|
||||
assert_eq!(cells.len(), 2);
|
||||
|
||||
let header_lines = render_transcript(cells[0].as_ref());
|
||||
assert_eq!(header_lines, vec!["Thinking", "High level plan"]);
|
||||
|
||||
let summary_lines = render_transcript(cells[1].as_ref());
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
assert_eq!(rendered_display, vec!["• We should fix the bug next."]);
|
||||
|
||||
let rendered_transcript = render_transcript(cell.as_ref());
|
||||
assert_eq!(
|
||||
summary_lines,
|
||||
vec!["codex", "Thinking", "We should fix the bug next."]
|
||||
)
|
||||
rendered_transcript,
|
||||
vec!["thinking", "We should fix the bug next."]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,11 +333,11 @@ mod tests {
|
||||
);
|
||||
for (i, l) in non_blank.iter().enumerate() {
|
||||
assert_eq!(
|
||||
l.style.fg,
|
||||
l.spans[0].style.fg,
|
||||
Some(Color::Green),
|
||||
"wrapped line {} should preserve green style, got {:?}",
|
||||
i,
|
||||
l.style.fg
|
||||
l.spans[0].style.fg
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,6 @@ where
|
||||
|
||||
// Build first wrapped line with initial indent.
|
||||
let mut first_line = rt_opts.initial_indent.clone();
|
||||
first_line.style = first_line.style.patch(line.style);
|
||||
{
|
||||
let sliced = slice_line_spans(line, &span_bounds, first_line_range);
|
||||
let mut spans = first_line.spans;
|
||||
@@ -216,7 +215,6 @@ where
|
||||
continue;
|
||||
}
|
||||
let mut subsequent_line = rt_opts.subsequent_indent.clone();
|
||||
subsequent_line.style = subsequent_line.style.patch(line.style);
|
||||
let offset_range = (r.start + base)..(r.end + base);
|
||||
let sliced = slice_line_spans(line, &span_bounds, &offset_range);
|
||||
let mut spans = subsequent_line.spans;
|
||||
|
||||
Reference in New Issue
Block a user