Compact session automatically for streaming providers on Context Length Exceeded (#4565)

This commit is contained in:
David Katz
2025-09-22 17:24:50 -04:00
committed by GitHub
parent 8198e1b043
commit 179ee01e93
+85 -37
View File
@@ -926,7 +926,7 @@ impl Session {
.reply( .reply(
self.messages.clone(), self.messages.clone(),
session_config.clone(), session_config.clone(),
Some(cancel_token), Some(cancel_token.clone()),
) )
.await?; .await?;
@@ -1285,6 +1285,55 @@ impl Session {
} }
Some(Err(e)) => { Some(Err(e)) => {
// Check if it's a ProviderError::ContextLengthExceeded
if e.downcast_ref::<goose::providers::errors::ProviderError>()
.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); eprintln!("Error: {}", e);
cancel_token_clone.cancel(); cancel_token_clone.cancel();
drop(stream); drop(stream);
@@ -1296,47 +1345,46 @@ impl Session {
if e.downcast_ref::<goose::providers::errors::ProviderError>() if e.downcast_ref::<goose::providers::errors::ProviderError>()
.map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_))) .map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_)))
.unwrap_or(false) { .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())
let prompt = "The tool calling loop was interrupted. How would you like to proceed?"; .item("clear", "Clear Session", "Removes all messages from Goose's memory")
let selected = match cliclack::select(prompt.to_string()) .item("summarize", "Summarize Session", "Summarize the session to reduce context length")
.item("clear", "Clear Session", "Removes all messages from Goose's memory") .interact()
.item("summarize", "Summarize Session", "Summarize the session to reduce context length") {
.interact() Ok(choice) => Some(choice),
{ Err(e) => {
Ok(choice) => Some(choice), if e.kind() == std::io::ErrorKind::Interrupted {
Err(e) => { // If interrupted, do nothing and let user handle it manually
if e.kind() == std::io::ErrorKind::Interrupted { output::render_text("Operation cancelled. You can use /clear or /summarize to continue.", Some(Color::Yellow), true);
// If interrupted, do nothing and let user handle it manually None
output::render_text("Operation cancelled. You can use /clear or /summarize to continue.", Some(Color::Yellow), true); } else {
None return Err(e.into());
} 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);
} }
} }
_ => { };
unreachable!()
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!()
}
} }
} }
}
} else { } else {
output::render_error( output::render_error(
"The error above was an exception we were not able to handle.\n\ "The error above was an exception we were not able to handle.\n\