diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index b41af656e68f..a7ee039e2626 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -926,7 +926,7 @@ impl Session { .reply( self.messages.clone(), session_config.clone(), - Some(cancel_token), + Some(cancel_token.clone()), ) .await?; @@ -1285,6 +1285,55 @@ impl Session { } Some(Err(e)) => { + // Check if it's a ProviderError::ContextLengthExceeded + if e.downcast_ref::() + .map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_))) + .unwrap_or(false) { + + output::render_text( + "Context limit reached. Performing auto-compaction...", + Some(Color::Yellow), + true + ); + + // Try auto-compaction first - keep the stream alive! + if let Ok(compact_result) = goose::context_mgmt::auto_compact::perform_compaction(&self.agent, self.messages.messages()).await { + self.messages = compact_result.messages; + + // Persist the compacted messages + if let Some(session_file) = &self.session_file { + let provider = self.agent.provider().await.ok(); + let working_dir = std::env::current_dir().ok(); + if let Err(e) = session::persist_messages_with_schedule_id( + session_file, + &self.messages, + provider, + self.scheduled_job_id.clone(), + working_dir, + ).await { + eprintln!("Failed to persist compacted messages: {}", e); + } + } + + output::render_text( + "Compaction complete. Conversation has been automatically compacted to continue.", + Some(Color::Yellow), + true + ); + + // Restart the stream after successful compaction - keep the stream alive! + stream = self + .agent + .reply( + self.messages.clone(), + session_config.clone(), + Some(cancel_token.clone()) + ) + .await?; + continue; + } + // Auto-compaction failed, fall through to common error handling below + } eprintln!("Error: {}", e); cancel_token_clone.cancel(); drop(stream); @@ -1296,47 +1345,46 @@ impl Session { if e.downcast_ref::() .map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_))) .unwrap_or(false) { + output::render_error(&format!("Error: Context length exceeded: {}", e)); - output::render_error(&format!("Error: Context length exceeded: {}", e)); - - let prompt = "The tool calling loop was interrupted. How would you like to proceed?"; - let selected = match cliclack::select(prompt.to_string()) - .item("clear", "Clear Session", "Removes all messages from Goose's memory") - .item("summarize", "Summarize Session", "Summarize the session to reduce context length") - .interact() - { - Ok(choice) => Some(choice), - Err(e) => { - if e.kind() == std::io::ErrorKind::Interrupted { - // If interrupted, do nothing and let user handle it manually - output::render_text("Operation cancelled. You can use /clear or /summarize to continue.", Some(Color::Yellow), true); - None - } else { - return Err(e.into()); + let prompt = "The tool calling loop was interrupted. How would you like to proceed?"; + let selected = match cliclack::select(prompt.to_string()) + .item("clear", "Clear Session", "Removes all messages from Goose's memory") + .item("summarize", "Summarize Session", "Summarize the session to reduce context length") + .interact() + { + Ok(choice) => Some(choice), + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + // If interrupted, do nothing and let user handle it manually + output::render_text("Operation cancelled. You can use /clear or /summarize to continue.", Some(Color::Yellow), true); + None + } else { + return Err(e.into()); + } } - } - }; + }; - if let Some(choice) = selected { - match choice { - "clear" => { - self.messages.clear(); - let msg = format!("Session cleared.\n{}", "-".repeat(50)); - output::render_text(&msg, Some(Color::Yellow), true); - } - "summarize" => { - // Use the helper function to summarize context - let message_suffix = "Goose summarized messages for you."; - if let Err(e) = Self::summarize_context_messages(&mut self.messages, &self.agent, message_suffix).await { - output::render_error(&format!("Failed to summarize: {}", e)); - output::render_text("Consider using /clear to start fresh.", Some(Color::Yellow), true); + if let Some(choice) = selected { + match choice { + "clear" => { + self.messages.clear(); + let msg = format!("Session cleared.\n{}", "-".repeat(50)); + output::render_text(&msg, Some(Color::Yellow), true); + } + "summarize" => { + // Use the helper function to summarize context + let message_suffix = "Goose summarized messages for you."; + if let Err(e) = Self::summarize_context_messages(&mut self.messages, &self.agent, message_suffix).await { + output::render_error(&format!("Failed to summarize: {}", e)); + output::render_text("Consider using /clear to start fresh.", Some(Color::Yellow), true); + } + } + _ => { + unreachable!() } - } - _ => { - unreachable!() } } - } } else { output::render_error( "The error above was an exception we were not able to handle.\n\ diff --git a/ui/desktop/src/utils.ts b/ui/desktop/src/utils.ts index 7378ae768234..b31c8ed37ade 100644 --- a/ui/desktop/src/utils.ts +++ b/ui/desktop/src/utils.ts @@ -10,4 +10,4 @@ export function snakeToTitleCase(snake: string): string { .split('_') .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); -} \ No newline at end of file +}