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        reasoning_config: None,
166    };
167
168    let model_id = config
169        .model
170        .parse::<ModelId>()
171        .map_err(|_| anyhow::anyhow!("Invalid model: {}", config.model))?;
172    let mut client = make_client(config.api_key.clone(), model_id);
173    println!("{}", style("Compressing conversation...").cyan());
174
175    // Convert the request to a string prompt
176    let _prompt = compression_request
177        .contents
178        .iter()
179        .map(|content| {
180            content
181                .parts
182                .iter()
183                .map(|part| match part {
184                    crate::gemini::Part::Text { text } => text.clone(),
185                    _ => String::new(),
186                })
187                .collect::<Vec<_>>()
188                .join("\n")
189        })
190        .collect::<Vec<_>>()
191        .join("\n\n");
192
193    // Convert the compression request to a string prompt
194    let prompt = compression_request
195        .contents
196        .iter()
197        .map(|content| {
198            content
199                .parts
200                .iter()
201                .map(|part| match part {
202                    crate::gemini::Part::Text { text } => text.clone(),
203                    _ => String::new(),
204                })
205                .collect::<Vec<_>>()
206                .join("\n")
207        })
208        .collect::<Vec<_>>()
209        .join("\n\n");
210
211    let compressed_response = client.generate(&prompt).await?;
212
213    // Print the compressed response content directly
214    println!("{}", style("Compressed Summary:").green().bold());
215    println!("{}", compressed_response.content);
216
217    println!("\n{}", style(" Key Principles Applied:").yellow().bold());
218    println!("  • {}", style("Share full context and traces").dim());
219    println!("  • {}", style("Actions carry implicit decisions").dim());
220    println!(
221        "  • {}",
222        style("Single-threaded agents are more reliable").dim()
223    );
224    println!(
225        "  • {}",
226        style("Context compression enables longer conversations").dim()
227    );
228
229    Ok(())
230}