Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod elicitation;
mod export;
mod input;
mod output;
pub mod streaming_buffer;
mod task_execution_display;
mod thinking;

Expand Down Expand Up @@ -961,6 +962,7 @@ impl CliSession {

let mut progress_bars = output::McpSpinners::new();
let cancel_token_clone = cancel_token.clone();
let mut markdown_buffer = streaming_buffer::MarkdownBuffer::new();

use futures::StreamExt;
loop {
Expand Down Expand Up @@ -1033,7 +1035,7 @@ impl CliSession {
if is_stream_json_mode {
emit_stream_event(&StreamEvent::Message { message: message.clone() });
} else if !is_json_mode {
output::render_message(&message, self.debug);
output::render_message_streaming(&message, &mut markdown_buffer, self.debug);
}
}
}
Expand Down Expand Up @@ -1087,6 +1089,11 @@ impl CliSession {
}
}

// Flush any remaining buffered markdown content
if !is_json_mode && !is_stream_json_mode {
output::flush_markdown_buffer_current_theme(&mut markdown_buffer);
}

if is_json_mode {
let metadata = match self
.agent
Expand Down
102 changes: 102 additions & 0 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

use super::streaming_buffer::MarkdownBuffer;

pub const DEFAULT_MIN_PRIORITY: f32 = 0.0;
pub const DEFAULT_CLI_LIGHT_THEME: &str = "GitHub";
pub const DEFAULT_CLI_DARK_THEME: &str = "zenburn";
Expand Down Expand Up @@ -272,6 +274,106 @@ pub fn render_message(message: &Message, debug: bool) {
let _ = std::io::stdout().flush();
}

/// Render a streaming message, using a buffer to accumulate text content
/// and only render when markdown constructs are complete.
/// Returns true if the message contained text content (for tracking purposes).
Comment thread
DOsinga marked this conversation as resolved.
Outdated
pub fn render_message_streaming(
message: &Message,
buffer: &mut MarkdownBuffer,
debug: bool,
) -> bool {

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns a bool indicating whether text content was present, but this return value is never used at the call site in mod.rs line 1038. Either use the return value for some purpose (e.g., tracking/logging) or change the function signature to return () to match render_message.

Copilot uses AI. Check for mistakes.
let theme = get_theme();
let mut had_text = false;

for content in &message.content {
match content {
MessageContent::Text(text) => {
had_text = true;
// Push to buffer and render any safe content
if let Some(safe_content) = buffer.push(&text.text) {
print_markdown(&safe_content, theme);
}
}
// For non-text content, flush the buffer first then render normally
MessageContent::ToolRequest(req) => {
flush_markdown_buffer(buffer, theme);
render_tool_request(req, theme, debug);
}
MessageContent::ToolResponse(resp) => {
flush_markdown_buffer(buffer, theme);
render_tool_response(resp, theme, debug);
}
MessageContent::ActionRequired(action) => {
flush_markdown_buffer(buffer, theme);
match &action.data {
ActionRequiredData::ToolConfirmation { tool_name, .. } => {
println!("action_required(tool_confirmation): {}", tool_name)
}
ActionRequiredData::Elicitation { message, .. } => {
println!("action_required(elicitation): {}", message)
}
ActionRequiredData::ElicitationResponse { id, .. } => {
println!("action_required(elicitation_response): {}", id)
}
}
}
MessageContent::Image(image) => {
flush_markdown_buffer(buffer, theme);
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
}
MessageContent::Thinking(thinking) => {
if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok()
&& std::io::stdout().is_terminal()
{
flush_markdown_buffer(buffer, theme);
println!("\n{}", style("Thinking:").dim().italic());
print_markdown(&thinking.thinking, theme);
}
}
MessageContent::RedactedThinking(_) => {
flush_markdown_buffer(buffer, theme);
println!("\n{}", style("Thinking:").dim().italic());
print_markdown("Thinking was redacted", theme);
}
MessageContent::SystemNotification(notification) => {
use goose::conversation::message::SystemNotificationType;

match notification.notification_type {
SystemNotificationType::ThinkingMessage => {
show_thinking();
set_thinking_message(&notification.msg);
}
SystemNotificationType::InlineMessage => {
flush_markdown_buffer(buffer, theme);
hide_thinking();
println!("\n{}", style(&notification.msg).yellow());
}
}
}
_ => {
flush_markdown_buffer(buffer, theme);
println!("WARNING: Message content type could not be rendered");
}
}
}

let _ = std::io::stdout().flush();
had_text
}

/// Flush any remaining content in the markdown buffer
pub fn flush_markdown_buffer(buffer: &mut MarkdownBuffer, theme: Theme) {
let remaining = buffer.flush();
if !remaining.is_empty() {
print_markdown(&remaining, theme);
}
}

/// Convenience function to flush with the current theme
pub fn flush_markdown_buffer_current_theme(buffer: &mut MarkdownBuffer) {
flush_markdown_buffer(buffer, get_theme());
}

pub fn render_text(text: &str, color: Option<Color>, dim: bool) {
render_text_no_newlines(format!("\n{}\n\n", text).as_str(), color, dim);
}
Expand Down
Loading
Loading