edit file fixes

This commit is contained in:
Dhanji Prasanna
2025-09-30 20:41:14 +10:00
parent 3c4da6f974
commit b3c2c0ad30

View File

@@ -251,11 +251,11 @@ impl ContextWindow {
// - Code/JSON: ~3 characters per token (more symbols)
// - Add 10% buffer for safety
let base_estimate = if text.contains("{") || text.contains("```") || text.contains("fn ") {
(text.len() as f32 / 3.0).ceil() as u32 // Code/JSON
(text.len() as f32 / 3.0).ceil() as u32 // Code/JSON
} else {
(text.len() as f32 / 4.0).ceil() as u32 // Regular text
(text.len() as f32 / 4.0).ceil() as u32 // Regular text
};
(base_estimate as f32 * 1.1).ceil() as u32 // Add 10% buffer
(base_estimate as f32 * 1.1).ceil() as u32 // Add 10% buffer
}
pub fn update_usage(&mut self, usage: &g3_providers::Usage) {
@@ -275,7 +275,6 @@ impl ContextWindow {
self.total_tokens.saturating_sub(self.used_tokens)
}
/// Check if we should trigger summarization (at 80% capacity)
pub fn should_summarize(&self) -> bool {
// Trigger at 80% OR if we're getting close to absolute limits
@@ -578,6 +577,7 @@ impl Agent {
"You are G3, an AI programming agent. Your goal is to analyze, write and modify code to achieve given goals.
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
Always start by reading the project's README. Create one if this is a new project or making major changes.
IMPORTANT: You must call tools to achieve goals. When you receive a request:
1. Analyze and identify what needs to be done
@@ -954,14 +954,18 @@ The tool will execute immediately and you'll receive the result (success or erro
);
// Notify user about summarization
println!("\n📊 Context window reaching capacity ({}%). Creating summary...",
self.context_window.percentage_used() as u32);
println!(
"\n📊 Context window reaching capacity ({}%). Creating summary...",
self.context_window.percentage_used() as u32
);
// Create summary request with FULL history
let summary_prompt = self.context_window.create_summary_prompt();
// Get the full conversation history
let conversation_text = self.context_window.conversation_history
let conversation_text = self
.context_window
.conversation_history
.iter()
.map(|m| format!("{:?}: {}", m.role, m.content))
.collect::<Vec<_>>()
@@ -970,13 +974,14 @@ The tool will execute immediately and you'll receive the result (success or erro
let summary_messages = vec![
Message {
role: MessageRole::System,
content: "You are a helpful assistant that creates concise summaries.".to_string(),
content: "You are a helpful assistant that creates concise summaries."
.to_string(),
},
Message {
role: MessageRole::User,
content: format!("Based on this conversation history, {}\n\nConversation:\n{}",
summary_prompt,
conversation_text
content: format!(
"Based on this conversation history, {}\n\nConversation:\n{}",
summary_prompt, conversation_text
),
},
];
@@ -992,7 +997,9 @@ The tool will execute immediately and you'll receive the result (success or erro
let model_limit = 200_000u32;
let current_usage = self.context_window.used_tokens;
// Leave some buffer (5k tokens) for safety
let available = model_limit.saturating_sub(current_usage).saturating_sub(5000);
let available = model_limit
.saturating_sub(current_usage)
.saturating_sub(5000);
// Cap at a reasonable summary size (10k tokens max)
Some(available.min(10_000))
}
@@ -1001,7 +1008,9 @@ The tool will execute immediately and you'll receive the result (success or erro
let model_limit = self.context_window.total_tokens;
let current_usage = self.context_window.used_tokens;
// Leave 1k buffer
let available = model_limit.saturating_sub(current_usage).saturating_sub(1000);
let available = model_limit
.saturating_sub(current_usage)
.saturating_sub(1000);
// Cap at 3k for embedded models
Some(available.min(3000))
}
@@ -1012,8 +1021,10 @@ The tool will execute immediately and you'll receive the result (success or erro
}
};
info!("Requesting summary with max_tokens: {:?} (current usage: {} tokens)",
summary_max_tokens, self.context_window.used_tokens);
info!(
"Requesting summary with max_tokens: {:?} (current usage: {} tokens)",
summary_max_tokens, self.context_window.used_tokens
);
let summary_request = CompletionRequest {
messages: summary_messages,
@@ -1604,54 +1615,42 @@ The tool will execute immediately and you'll receive the result (success or erro
"edit_file" => {
debug!("Processing edit_file tool call");
// Extract arguments
let args_obj = tool_call.args.as_object();
if args_obj.is_none() {
return Ok("❌ Invalid arguments: expected object".to_string());
}
let args_obj = args_obj.unwrap();
// Extract arguments with better error handling
let args_obj = match tool_call.args.as_object() {
Some(obj) => obj,
None => return Ok("❌ Invalid arguments: expected object".to_string()),
};
// Get file_path
let file_path = args_obj.get("file_path")
.and_then(|v| v.as_str());
if file_path.is_none() {
return Ok("❌ Missing file_path argument".to_string());
}
let file_path = file_path.unwrap();
let file_path = match args_obj.get("file_path").and_then(|v| v.as_str()) {
Some(path) => path,
None => return Ok("❌ Missing or invalid file_path argument".to_string()),
};
// Get content
let content = args_obj.get("content")
.and_then(|v| v.as_str());
if content.is_none() {
return Ok("❌ Missing content argument".to_string());
}
let content = content.unwrap();
let content = match args_obj.get("content").and_then(|v| v.as_str()) {
Some(c) => c,
None => return Ok("❌ Missing or invalid content argument".to_string()),
};
// Get start_of_range
let start_of_range = args_obj.get("start_of_range")
.and_then(|v| v.as_i64())
.map(|v| v as usize);
if start_of_range.is_none() {
return Ok("❌ Missing or invalid start_of_range argument".to_string());
}
let start_of_range = start_of_range.unwrap();
let start_line = match args_obj.get("start_of_range").and_then(|v| v.as_i64()) {
Some(n) if n >= 1 => n as usize,
Some(_) => {
return Ok(
"❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string()
)
}
None => return Ok("❌ Missing or invalid start_of_range argument".to_string()),
};
// Get end_of_range
let end_of_range = args_obj.get("end_of_range")
.and_then(|v| v.as_i64())
.map(|v| v as usize);
if end_of_range.is_none() {
return Ok("❌ Missing or invalid end_of_range argument".to_string());
}
let end_of_range = end_of_range.unwrap();
let end_line = match args_obj.get("end_of_range").and_then(|v| v.as_i64()) {
Some(n) if n >= start_line as i64 => n as usize,
Some(_) => return Ok("❌ end_of_range must be >= start_of_range".to_string()),
None => return Ok("❌ Missing or invalid end_of_range argument".to_string()),
};
// Validate range
if start_of_range < 1 {
return Ok("❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string());
}
if end_of_range < start_of_range {
return Ok("❌ end_of_range must be >= start_of_range".to_string());
}
debug!(
"edit_file: path={}, start={}, end={}",
file_path, start_line, end_line
);
// Read the existing file
let existing_content = match std::fs::read_to_string(file_path) {
@@ -1659,37 +1658,69 @@ The tool will execute immediately and you'll receive the result (success or erro
Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", file_path, e)),
};
// Split into lines
let mut lines: Vec<String> = existing_content.lines().map(|s| s.to_string()).collect();
// Split into lines, preserving empty lines
let mut lines: Vec<String> =
existing_content.lines().map(|s| s.to_string()).collect();
let original_line_count = lines.len();
// Check if range is valid
if start_of_range > lines.len() + 1 {
return Ok(format!("❌ start_of_range {} exceeds file length ({} lines)", start_of_range, lines.len()));
// Validate the range
if start_line > lines.len() {
// Allow appending at the end if start_line == lines.len() + 1
if start_line == lines.len() + 1 && end_line == start_line {
// This is an append operation
lines.extend(content.lines().map(|s| s.to_string()));
// Write back to file
let new_content = lines.join("\n");
match std::fs::write(file_path, &new_content) {
Ok(()) => {
let lines_added = content.lines().count();
return Ok(format!(
"✅ Successfully appended {} lines to '{}'. File now has {} lines (was {} lines)",
lines_added, file_path, lines.len(), original_line_count
));
}
Err(e) => {
return Ok(format!(
"❌ Failed to write to file '{}': {}",
file_path, e
))
}
}
} else {
return Ok(format!(
"❌ start_of_range {} exceeds file length ({} lines)",
start_line,
lines.len()
));
}
}
// Prepare new content lines
// Split the new content into lines
let new_lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
// Calculate the actual end of range (capped at file length)
let actual_end = end_of_range.min(lines.len());
// Perform the replacement
// Convert from 1-indexed (inclusive) to 0-indexed range for splice
// splice takes start..end where end is EXCLUSIVE, so for inclusive end_line, we need end_line + 1
let start_idx = start_line - 1;
let end_idx = (end_line + 1).min(lines.len() + 1); // +1 because splice end is exclusive
let actual_end_line = end_line.min(lines.len()); // For reporting
let lines_being_replaced = actual_end_line - start_line + 1;
// Replace the range with new content
// Convert to 0-indexed for vector operations
let start_idx = start_of_range - 1;
let end_idx = actual_end;
// Remove old lines and insert new ones
debug!(
"Replacing lines {}..={} (0-indexed splice: {}..{})",
start_line, end_line, start_idx, end_idx
);
lines.splice(start_idx..end_idx, new_lines.clone());
// Write back to file
// Write the result back to the file
let new_content = lines.join("\n");
match std::fs::write(file_path, &new_content) {
Ok(()) => {
let lines_replaced = end_idx - start_idx;
let lines_added = new_lines.len();
Ok(format!(
"✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines",
file_path, lines_replaced, start_of_range, actual_end, lines_added, lines.len()
"✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines (was {} lines)",
file_path, lines_being_replaced, start_line, actual_end_line,
new_lines.len(), lines.len(), original_line_count
))
}
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)),