vtcode_core/commands/
compress_context.rs

1//! Compress context command implementation
2
3use crate::config::constants::tools;
4use crate::config::models::ModelId;
5use crate::config::types::AgentConfig;
6use crate::gemini::models::SystemInstruction;
7use crate::gemini::{Content, FunctionResponse, GenerateContentRequest, Part};
8use crate::llm::make_client;
9use anyhow::Result;
10use console::style;
11use serde_json::json;
12
13/// Handle the compress-context command - demonstrate context compression
14pub async fn handle_compress_context_command(
15    config: AgentConfig,
16    _input: Option<std::path::PathBuf>,
17    _output: Option<std::path::PathBuf>,
18) -> Result<()> {
19    println!("{}", style("[CONTEXT] Compression Demo").cyan().bold());
20    println!(
21        "{}",
22        style("Following Cognition's context engineering principles...").dim()
23    );
24
25    // Create a sample long conversation history to compress
26    let sample_conversation = vec![
27        Content::user_text("I want to create a Rust web application with user authentication"),
28        Content::system_text(
29            "I'll help you create a Rust web application with authentication. Let me start by exploring the current directory structure.",
30        ),
31        Content::user_parts(vec![Part::FunctionResponse {
32            function_response: FunctionResponse {
33                name: tools::LIST_FILES.to_string(),
34                response: json!({"path": ".", "files": ["Cargo.toml", "src/main.rs"], "directories": ["src", "tests"]}),
35            },
36        }]),
37        Content::system_text(
38            "I can see you already have a basic Rust project. Let me check what's in the main.rs file.",
39        ),
40        Content::user_parts(vec![Part::FunctionResponse {
41            function_response: FunctionResponse {
42                name: tools::READ_FILE.to_string(),
43                response: json!({"path": "src/main.rs", "content": "fn main() {\n    println!(\"Hello World!\");\n}", "metadata": {"size": 45}}),
44            },
45        }]),
46        Content::system_text(
47            "Now I need to add web framework dependencies. I'll update Cargo.toml to include Axum and other necessary crates.",
48        ),
49        Content::user_parts(vec![Part::FunctionResponse {
50            function_response: FunctionResponse {
51                name: tools::EDIT_FILE.to_string(),
52                response: json!({"status": "modified", "path": "Cargo.toml", "action": {"replacements_made": 1}}),
53            },
54        }]),
55        Content::system_text("Good! Now let me create the authentication module structure."),
56        Content::user_parts(vec![Part::FunctionResponse {
57            function_response: FunctionResponse {
58                name: tools::WRITE_FILE.to_string(),
59                response: json!({"status": "created", "path": "src/auth.rs", "bytes_written": 234}),
60            },
61        }]),
62        Content::system_text("Now I'll create the main web server with authentication endpoints."),
63        Content::user_parts(vec![Part::FunctionResponse {
64            function_response: FunctionResponse {
65                name: tools::EDIT_FILE.to_string(),
66                response: json!({"status": "modified", "path": "src/main.rs", "action": {"replacements_made": 3}}),
67            },
68        }]),
69    ];
70
71    println!(
72        "{} {}",
73        style("Original conversation length:").yellow(),
74        sample_conversation.len()
75    );
76    println!(
77        "{} {:.1}KB",
78        style("Estimated token usage:").yellow(),
79        sample_conversation.len() as f64 * 0.5
80    ); // Rough estimate
81
82    // Create compression prompt following Cognition's principles
83    let compression_prompt = r#"You are a context compression specialist. Your task is to compress the following agent conversation history while preserving:
84
851. KEY DECISIONS made by the agent
862. IMPORTANT ACTIONS taken (tool calls and their results)
873. CRITICAL CONTEXT about the current state
884. USER INTENT and requirements
895. TECHNICAL DECISIONS (frameworks, libraries, architecture choices)
90
91IMPORTANT: Do NOT lose information about:
92- What files were created/modified and why
93- What dependencies were added
94- What the current state of the project is
95- What the user's original request was
96
97Compress this conversation into a concise summary that captures all essential information:
98
99ORIGINAL CONVERSATION:"#;
100
101    // Build the conversation content for compression
102    let mut compression_content = vec![Content::user_text(compression_prompt)];
103
104    // Add each conversation turn
105    for (i, content) in sample_conversation.iter().enumerate() {
106        let role_indicator = match content.role.as_str() {
107            "user" => "USER",
108            "system" => "AGENT",
109            _ => "UNKNOWN",
110        };
111
112        let mut content_summary = format!("\n--- Turn {} ({}) ---\n", i + 1, role_indicator);
113
114        for part in &content.parts {
115            if let Some(text) = part.as_text() {
116                content_summary.push_str(text);
117            } else if let Part::FunctionCall { function_call } = part {
118                content_summary.push_str(&format!(
119                    "\n[TOOL CALL: {}({})]",
120                    function_call.name, function_call.args
121                ));
122            } else if let Part::FunctionResponse { function_response } = part {
123                content_summary.push_str(&format!(
124                    "\n[TOOL RESULT: {}]",
125                    serde_json::to_string_pretty(&function_response.response).unwrap_or_default()
126                ));
127            }
128        }
129
130        if i == 0 {
131            compression_content[0] =
132                Content::user_text(format!("{}{}", compression_prompt, content_summary));
133        } else {
134            compression_content.push(Content::user_text(content_summary));
135        }
136    }
137
138    // Add final instruction
139    compression_content.push(Content::user_text(
140        r#"
141COMPRESSION REQUIREMENTS:
142- Preserve all key decisions and their rationale
143- Keep track of what files were created/modified
144- Maintain information about current project state
145- Include user's original intent
146- Note any important technical choices made
147
148COMPRESSED SUMMARY:"#,
149    ));
150
151    // Create request for compression
152    let compression_request = GenerateContentRequest {
153        contents: compression_content,
154        tools: None,
155        tool_config: None,
156        generation_config: Some(json!({
157            "maxOutputTokens": 1000,
158            "temperature": 0.1
159        })),
160        system_instruction: Some(SystemInstruction::new(
161            r#"You are an expert at compressing agent conversation history.
162Your goal is to create a compressed summary that maintains all critical information while being concise.
163Focus on: key decisions, actions taken, current state, and user requirements."#,
164        )),
165    };
166
167    let model_id = config
168        .model
169        .parse::<ModelId>()
170        .map_err(|_| anyhow::anyhow!("Invalid model: {}", config.model))?;
171    let mut client = make_client(config.api_key.clone(), model_id);
172    println!("{}", style("Compressing conversation...").cyan());
173
174    // Convert the request to a string prompt
175    let _prompt = compression_request
176        .contents
177        .iter()
178        .map(|content| {
179            content
180                .parts
181                .iter()
182                .map(|part| match part {
183                    crate::gemini::Part::Text { text } => text.clone(),
184                    _ => String::new(),
185                })
186                .collect::<Vec<_>>()
187                .join("\n")
188        })
189        .collect::<Vec<_>>()
190        .join("\n\n");
191
192    // Convert the compression request to a string prompt
193    let prompt = compression_request
194        .contents
195        .iter()
196        .map(|content| {
197            content
198                .parts
199                .iter()
200                .map(|part| match part {
201                    crate::gemini::Part::Text { text } => text.clone(),
202                    _ => String::new(),
203                })
204                .collect::<Vec<_>>()
205                .join("\n")
206        })
207        .collect::<Vec<_>>()
208        .join("\n\n");
209
210    let compressed_response = client.generate(&prompt).await?;
211
212    // Print the compressed response content directly
213    println!("{}", style("Compressed Summary:").green().bold());
214    println!("{}", compressed_response.content);
215
216    println!("\n{}", style(" Key Principles Applied:").yellow().bold());
217    println!("  • {}", style("Share full context and traces").dim());
218    println!("  • {}", style("Actions carry implicit decisions").dim());
219    println!(
220        "  • {}",
221        style("Single-threaded agents are more reliable").dim()
222    );
223    println!(
224        "  • {}",
225        style("Context compression enables longer conversations").dim()
226    );
227
228    Ok(())
229}