Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
25 changes: 15 additions & 10 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub struct CliSession {
edit_mode: Option<EditMode>,
retry_config: Option<RetryConfig>,
output_format: String,
renderer: output::Renderer,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -259,6 +260,7 @@ impl CliSession {
edit_mode,
retry_config,
output_format,
renderer: output::Renderer::new(),
}
}

Expand Down Expand Up @@ -769,7 +771,7 @@ impl CliSession {

self.messages.clear();
tracing::info!("Chat context cleared by user.");
output::render_message(
self.renderer.render_message(
&Message::assistant().with_text("Chat context cleared.\n"),
self.debug,
);
Expand Down Expand Up @@ -847,7 +849,7 @@ impl CliSession {
&[],
)
.await?;
output::render_message(&plan_response, self.debug);
self.renderer.render_message(&plan_response, self.debug);
output::hide_thinking();
let planner_response_type = classify_planner_response(
&self.session_id,
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);
self.renderer.render_message(&message, self.debug);
}
}
}
Expand Down Expand Up @@ -1074,7 +1076,10 @@ impl CliSession {
}
break;
}
None => break,
None => {
self.renderer.finish();
break;
}
}
}
_ = cancel_token_clone.cancelled() => {
Expand Down Expand Up @@ -1171,7 +1176,7 @@ impl CliSession {
}
self.push_message(response_message);
self.push_message(Message::assistant().with_text(interrupt_prompt));
output::render_message(
self.renderer.render_message(
&Message::assistant().with_text(interrupt_prompt),
self.debug,
);
Expand All @@ -1180,7 +1185,7 @@ impl CliSession {
match last_msg.content.first() {
Some(MessageContent::ToolResponse(_)) => {
self.push_message(Message::assistant().with_text(interrupt_prompt));
output::render_message(
self.renderer.render_message(
&Message::assistant().with_text(interrupt_prompt),
self.debug,
);
Expand All @@ -1189,7 +1194,7 @@ impl CliSession {
self.messages.pop();
let assistant_msg = Message::assistant().with_text(interrupt_prompt);
self.push_message(assistant_msg.clone());
output::render_message(&assistant_msg, self.debug);
self.renderer.render_message(&assistant_msg, self.debug);
}
None => {
// Empty message content — nothing to do, just continue gracefully
Expand Down Expand Up @@ -1246,7 +1251,7 @@ impl CliSession {
}

/// Render all past messages from the session history
pub fn render_message_history(&self) {
pub fn render_message_history(&mut self) {
if self.messages.is_empty() {
return;
}
Expand All @@ -1259,7 +1264,7 @@ impl CliSession {

// Render each message
for message in self.messages.iter() {
output::render_message(message, self.debug);
self.renderer.render_message(message, self.debug);
}

println!();
Expand Down Expand Up @@ -1363,7 +1368,7 @@ impl CliSession {
}

if msg.role == rmcp::model::Role::User {
output::render_message(&msg, self.debug);
self.renderer.render_message(&msg, self.debug);
}
self.push_message(msg);
}
Expand Down
115 changes: 70 additions & 45 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,62 +214,87 @@ pub fn set_thinking_message(s: &String) {
}
}

pub fn render_message(message: &Message, debug: bool) {
let theme = get_theme();

for content in &message.content {
match content {
MessageContent::ActionRequired(action) => match &action.data {
ActionRequiredData::ToolConfirmation { tool_name, .. } => {
println!("action_required(tool_confirmation): {}", tool_name)
}
ActionRequiredData::Elicitation { message, .. } => {
println!("action_required(elicitation): {}", message)
pub struct Renderer {
pub text_messages: String,
}

impl Renderer {
pub fn new() -> Self {
Self {
text_messages: String::new(),
}
}

pub fn reset(&mut self) {
self.text_messages.clear();
}

pub fn finish(&mut self) {
print_markdown(&self.text_messages, get_theme());
self.reset();
}

pub fn render_message(&mut self, message: &Message, debug: bool) {
let theme = get_theme();

for content in &message.content {
match content {
MessageContent::ActionRequired(action) => 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::Text(text) => self.add_text_message(&text.text),
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
MessageContent::Image(image) => {
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
}
ActionRequiredData::ElicitationResponse { id, .. } => {
println!("action_required(elicitation_response): {}", id)
MessageContent::Thinking(thinking) => {
if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok()
&& std::io::stdout().is_terminal()
{
println!("\n{}", style("Thinking:").dim().italic());
print_markdown(&thinking.thinking, theme);
}
}
},
MessageContent::Text(text) => print_markdown(&text.text, theme),
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
MessageContent::Image(image) => {
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()
{
MessageContent::RedactedThinking(_) => {
// For redacted thinking, print thinking was redacted
println!("\n{}", style("Thinking:").dim().italic());
print_markdown(&thinking.thinking, theme);
print_markdown("Thinking was redacted", theme);
}
}
MessageContent::RedactedThinking(_) => {
// For redacted thinking, print thinking was redacted
println!("\n{}", style("Thinking:").dim().italic());
print_markdown("Thinking was redacted", theme);
}
MessageContent::SystemNotification(notification) => {
use goose::conversation::message::SystemNotificationType;
MessageContent::SystemNotification(notification) => {
use goose::conversation::message::SystemNotificationType;

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

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

let _ = std::io::stdout().flush();
fn add_text_message(&mut self, message: &str) {
self.text_messages.push_str(message);
}
}

pub fn render_text(text: &str, color: Option<Color>, dim: bool) {
Expand Down