edit file fixes
This commit is contained in:
@@ -275,7 +275,6 @@ impl ContextWindow {
|
|||||||
self.total_tokens.saturating_sub(self.used_tokens)
|
self.total_tokens.saturating_sub(self.used_tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Check if we should trigger summarization (at 80% capacity)
|
/// Check if we should trigger summarization (at 80% capacity)
|
||||||
pub fn should_summarize(&self) -> bool {
|
pub fn should_summarize(&self) -> bool {
|
||||||
// Trigger at 80% OR if we're getting close to absolute limits
|
// 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 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.
|
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:
|
IMPORTANT: You must call tools to achieve goals. When you receive a request:
|
||||||
1. Analyze and identify what needs to be done
|
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
|
// Notify user about summarization
|
||||||
println!("\n📊 Context window reaching capacity ({}%). Creating summary...",
|
println!(
|
||||||
self.context_window.percentage_used() as u32);
|
"\n📊 Context window reaching capacity ({}%). Creating summary...",
|
||||||
|
self.context_window.percentage_used() as u32
|
||||||
|
);
|
||||||
|
|
||||||
// Create summary request with FULL history
|
// Create summary request with FULL history
|
||||||
let summary_prompt = self.context_window.create_summary_prompt();
|
let summary_prompt = self.context_window.create_summary_prompt();
|
||||||
|
|
||||||
// Get the full conversation history
|
// Get the full conversation history
|
||||||
let conversation_text = self.context_window.conversation_history
|
let conversation_text = self
|
||||||
|
.context_window
|
||||||
|
.conversation_history
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| format!("{:?}: {}", m.role, m.content))
|
.map(|m| format!("{:?}: {}", m.role, m.content))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -970,13 +974,14 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
let summary_messages = vec![
|
let summary_messages = vec![
|
||||||
Message {
|
Message {
|
||||||
role: MessageRole::System,
|
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 {
|
Message {
|
||||||
role: MessageRole::User,
|
role: MessageRole::User,
|
||||||
content: format!("Based on this conversation history, {}\n\nConversation:\n{}",
|
content: format!(
|
||||||
summary_prompt,
|
"Based on this conversation history, {}\n\nConversation:\n{}",
|
||||||
conversation_text
|
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 model_limit = 200_000u32;
|
||||||
let current_usage = self.context_window.used_tokens;
|
let current_usage = self.context_window.used_tokens;
|
||||||
// Leave some buffer (5k tokens) for safety
|
// 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)
|
// Cap at a reasonable summary size (10k tokens max)
|
||||||
Some(available.min(10_000))
|
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 model_limit = self.context_window.total_tokens;
|
||||||
let current_usage = self.context_window.used_tokens;
|
let current_usage = self.context_window.used_tokens;
|
||||||
// Leave 1k buffer
|
// 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
|
// Cap at 3k for embedded models
|
||||||
Some(available.min(3000))
|
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)",
|
info!(
|
||||||
summary_max_tokens, self.context_window.used_tokens);
|
"Requesting summary with max_tokens: {:?} (current usage: {} tokens)",
|
||||||
|
summary_max_tokens, self.context_window.used_tokens
|
||||||
|
);
|
||||||
|
|
||||||
let summary_request = CompletionRequest {
|
let summary_request = CompletionRequest {
|
||||||
messages: summary_messages,
|
messages: summary_messages,
|
||||||
@@ -1604,54 +1615,42 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
"edit_file" => {
|
"edit_file" => {
|
||||||
debug!("Processing edit_file tool call");
|
debug!("Processing edit_file tool call");
|
||||||
|
|
||||||
// Extract arguments
|
// Extract arguments with better error handling
|
||||||
let args_obj = tool_call.args.as_object();
|
let args_obj = match tool_call.args.as_object() {
|
||||||
if args_obj.is_none() {
|
Some(obj) => obj,
|
||||||
return Ok("❌ Invalid arguments: expected object".to_string());
|
None => return Ok("❌ Invalid arguments: expected object".to_string()),
|
||||||
}
|
};
|
||||||
let args_obj = args_obj.unwrap();
|
|
||||||
|
|
||||||
// Get file_path
|
let file_path = match args_obj.get("file_path").and_then(|v| v.as_str()) {
|
||||||
let file_path = args_obj.get("file_path")
|
Some(path) => path,
|
||||||
.and_then(|v| v.as_str());
|
None => return Ok("❌ Missing or invalid file_path argument".to_string()),
|
||||||
if file_path.is_none() {
|
};
|
||||||
return Ok("❌ Missing file_path argument".to_string());
|
|
||||||
}
|
|
||||||
let file_path = file_path.unwrap();
|
|
||||||
|
|
||||||
// Get content
|
let content = match args_obj.get("content").and_then(|v| v.as_str()) {
|
||||||
let content = args_obj.get("content")
|
Some(c) => c,
|
||||||
.and_then(|v| v.as_str());
|
None => return Ok("❌ Missing or invalid content argument".to_string()),
|
||||||
if content.is_none() {
|
};
|
||||||
return Ok("❌ Missing content argument".to_string());
|
|
||||||
}
|
|
||||||
let content = content.unwrap();
|
|
||||||
|
|
||||||
// Get start_of_range
|
let start_line = match args_obj.get("start_of_range").and_then(|v| v.as_i64()) {
|
||||||
let start_of_range = args_obj.get("start_of_range")
|
Some(n) if n >= 1 => n as usize,
|
||||||
.and_then(|v| v.as_i64())
|
Some(_) => {
|
||||||
.map(|v| v as usize);
|
return Ok(
|
||||||
if start_of_range.is_none() {
|
"❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string()
|
||||||
return Ok("❌ Missing or invalid start_of_range argument".to_string());
|
)
|
||||||
}
|
}
|
||||||
let start_of_range = start_of_range.unwrap();
|
None => return Ok("❌ Missing or invalid start_of_range argument".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
// Get end_of_range
|
let end_line = match args_obj.get("end_of_range").and_then(|v| v.as_i64()) {
|
||||||
let end_of_range = args_obj.get("end_of_range")
|
Some(n) if n >= start_line as i64 => n as usize,
|
||||||
.and_then(|v| v.as_i64())
|
Some(_) => return Ok("❌ end_of_range must be >= start_of_range".to_string()),
|
||||||
.map(|v| v as usize);
|
None => return Ok("❌ Missing or invalid end_of_range argument".to_string()),
|
||||||
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();
|
|
||||||
|
|
||||||
// Validate range
|
debug!(
|
||||||
if start_of_range < 1 {
|
"edit_file: path={}, start={}, end={}",
|
||||||
return Ok("❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string());
|
file_path, start_line, end_line
|
||||||
}
|
);
|
||||||
if end_of_range < start_of_range {
|
|
||||||
return Ok("❌ end_of_range must be >= start_of_range".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the existing file
|
// Read the existing file
|
||||||
let existing_content = match std::fs::read_to_string(file_path) {
|
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)),
|
Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", file_path, e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Split into lines
|
// Split into lines, preserving empty lines
|
||||||
let mut lines: Vec<String> = existing_content.lines().map(|s| s.to_string()).collect();
|
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
|
// Validate the range
|
||||||
if start_of_range > lines.len() + 1 {
|
if start_line > lines.len() {
|
||||||
return Ok(format!("❌ start_of_range {} exceeds file length ({} lines)", start_of_range, 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
|
||||||
// Prepare new content lines
|
lines.extend(content.lines().map(|s| s.to_string()));
|
||||||
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());
|
|
||||||
|
|
||||||
// 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
|
|
||||||
lines.splice(start_idx..end_idx, new_lines.clone());
|
|
||||||
|
|
||||||
// Write back to file
|
// Write back to file
|
||||||
let new_content = lines.join("\n");
|
let new_content = lines.join("\n");
|
||||||
match std::fs::write(file_path, &new_content) {
|
match std::fs::write(file_path, &new_content) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let lines_replaced = end_idx - start_idx;
|
let lines_added = content.lines().count();
|
||||||
let lines_added = new_lines.len();
|
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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the new content into lines
|
||||||
|
let new_lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Replacing lines {}..={} (0-indexed splice: {}..{})",
|
||||||
|
start_line, end_line, start_idx, end_idx
|
||||||
|
);
|
||||||
|
lines.splice(start_idx..end_idx, new_lines.clone());
|
||||||
|
|
||||||
|
// Write the result back to the file
|
||||||
|
let new_content = lines.join("\n");
|
||||||
|
match std::fs::write(file_path, &new_content) {
|
||||||
|
Ok(()) => {
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines",
|
"✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines (was {} lines)",
|
||||||
file_path, lines_replaced, start_of_range, actual_end, lines_added, lines.len()
|
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)),
|
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)),
|
||||||
|
|||||||
Reference in New Issue
Block a user