From 0ffd29ce8b3703242c6cf41b69c59b90fbc883f4 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Wed, 15 Oct 2025 22:09:26 -0400 Subject: [PATCH] Skip hidden & real format (#5194) Co-authored-by: Douwe Osinga Co-authored-by: Michael Neale --- crates/goose/src/context_mgmt/mod.rs | 98 ++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs index 41de720a..8b2c73c3 100644 --- a/crates/goose/src/context_mgmt/mod.rs +++ b/crates/goose/src/context_mgmt/mod.rs @@ -1,5 +1,5 @@ -use crate::conversation::message::Message; use crate::conversation::message::MessageMetadata; +use crate::conversation::message::{Message, MessageContent}; use crate::conversation::Conversation; use crate::prompt_template::render_global_file; use crate::providers::base::{Provider, ProviderUsage}; @@ -74,7 +74,6 @@ pub async fn check_and_compact_messages( threshold_override: Option, session_metadata: Option<&crate::session::Session>, ) -> std::result::Result<(bool, Conversation, Vec, Option), anyhow::Error> { - // Check if compaction is needed (unless forced) if !force_compact { let check_result = check_compaction_needed( agent, @@ -134,7 +133,7 @@ pub async fn check_and_compact_messages( }; let provider = agent.provider().await?; - let summary = summarize(provider.clone(), messages).await?; + let summary = do_compact(provider.clone(), messages).await?; let (summary_message, summarization_usage) = match summary { Some((summary_message, provider_usage)) => (summary_message, Some(provider_usage)), @@ -227,7 +226,6 @@ async fn check_compaction_needed( threshold_override: Option, session_metadata: Option<&crate::session::Session>, ) -> Result { - // Get threshold from config or use override let config = Config::global(); // TODO(Douwe): check the default here; it seems to reset to 0.3 sometimes let threshold = threshold_override.unwrap_or_else(|| { @@ -293,46 +291,106 @@ async fn check_compaction_needed( }) } -async fn summarize( +async fn do_compact( provider: Arc, messages: &[Message], -) -> anyhow::Result, anyhow::Error> { - if messages.is_empty() { - return std::prelude::rust_2015::Ok(None); - } - - // Format all messages as a single string for the summarization prompt - let messages_text = messages +) -> Result, anyhow::Error> { + let agent_visible_messages: Vec<&Message> = messages .iter() - .map(|msg| format!("{:?}", msg)) + .filter(|msg| msg.is_agent_visible()) + .collect(); + + let messages_text = agent_visible_messages + .iter() + .map(|&msg| format_message_for_compacting(msg)) .collect::>() - .join("\n\n"); + .join("\n"); let context = SummarizeContext { messages: messages_text, }; - // Render the one-shot summarization prompt let system_prompt = render_global_file("summarize_oneshot.md", &context)?; - // Create a simple user message requesting summarization let user_message = Message::user() .with_text("Please summarize the conversation history provided in the system prompt."); let summarization_request = vec![user_message]; - // Send the request to the provider and fetch the response let (mut response, mut provider_usage) = provider .complete_fast(&system_prompt, &summarization_request, &[]) .await?; - // Set role to user as it will be used in following conversation as user content response.role = Role::User; - // Ensure we have token counts, estimating if necessary provider_usage .ensure_tokens(&system_prompt, &summarization_request, &response, &[]) .await .map_err(|e| anyhow::anyhow!("Failed to ensure usage tokens: {}", e))?; - std::prelude::rust_2015::Ok(Some((response, provider_usage))) + Ok(Some((response, provider_usage))) +} + +fn format_message_for_compacting(msg: &Message) -> String { + let content_parts: Vec = msg + .content + .iter() + .map(|content| match content { + MessageContent::Text(text) => text.text.clone(), + MessageContent::Image(img) => format!("[image: {}]", img.mime_type), + MessageContent::ToolRequest(req) => { + if let Ok(call) = &req.tool_call { + format!( + "tool_request({}): {}", + call.name, + serde_json::to_string_pretty(&call.arguments) + .unwrap_or_else(|_| "<>".to_string()) + ) + } else { + "tool_request: [error]".to_string() + } + } + MessageContent::ToolResponse(res) => { + if let Ok(contents) = &res.tool_result { + let text_items: Vec = contents + .iter() + .filter_map(|content| { + content.as_text().map(|text_str| text_str.text.clone()) + }) + .collect(); + + if !text_items.is_empty() { + format!("tool_response: {}", text_items.join("\n")) + } else { + "tool_response: [non-text content]".to_string() + } + } else { + "tool_response: [error]".to_string() + } + } + MessageContent::ToolConfirmationRequest(req) => { + format!("tool_confirmation_request: {}", req.tool_name) + } + MessageContent::FrontendToolRequest(req) => { + if let Ok(call) = &req.tool_call { + format!("frontend_tool_request: {}", call.name) + } else { + "frontend_tool_request: [error]".to_string() + } + } + MessageContent::Thinking(thinking) => format!("thinking: {}", thinking.thinking), + MessageContent::RedactedThinking(_) => "redacted_thinking".to_string(), + MessageContent::ConversationCompacted(compact) => format!("compacted: {}", compact.msg), + }) + .collect(); + + let role_str = match msg.role { + Role::User => "user", + Role::Assistant => "assistant", + }; + + if content_parts.is_empty() { + format!("[{}]: ", role_str) + } else { + format!("[{}]: {}", role_str, content_parts.join("\n")) + } }