Skip to content

Commit 3a75df2

Browse files
authored
New edit tool (#45)
1 parent 091828a commit 3a75df2

27 files changed

+1021
-90
lines changed

crates/code_assistant/assets/icons/file_icons/file_types.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@
398398
},
399399
"plus": {
400400
"icon": "icons/plus.svg"
401+
},
402+
"edit": {
403+
"icon": "icons/square-pen.svg"
401404
}
402405
}
403406
}
Lines changed: 1 addition & 0 deletions
Loading

crates/code_assistant/src/agent/persistence.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub trait AgentStatePersistence: Send + Sync {
1717
/// Save the current agent state
1818
fn save_agent_state(
1919
&mut self,
20+
name: String,
2021
messages: Vec<Message>,
2122
tool_executions: Vec<ToolExecution>,
2223
working_memory: WorkingMemory,
@@ -51,6 +52,7 @@ impl MockStatePersistence {
5152
impl AgentStatePersistence for MockStatePersistence {
5253
fn save_agent_state(
5354
&mut self,
55+
_name: String,
5456
messages: Vec<Message>,
5557
tool_executions: Vec<ToolExecution>,
5658
working_memory: WorkingMemory,
@@ -86,6 +88,7 @@ impl SessionStatePersistence {
8688
impl AgentStatePersistence for SessionStatePersistence {
8789
fn save_agent_state(
8890
&mut self,
91+
name: String,
8992
messages: Vec<Message>,
9093
tool_executions: Vec<ToolExecution>,
9194
working_memory: WorkingMemory,
@@ -100,6 +103,7 @@ impl AgentStatePersistence for SessionStatePersistence {
100103

101104
session_manager.save_session_state(
102105
&self.session_id,
106+
name,
103107
messages,
104108
tool_executions,
105109
working_memory,
@@ -118,15 +122,17 @@ const STATE_FILE: &str = ".code-assistant.state.json";
118122
pub struct FileStatePersistence {
119123
state_file_path: PathBuf,
120124
tool_syntax: ToolSyntax,
125+
use_diff_blocks: bool,
121126
}
122127

123128
impl FileStatePersistence {
124-
pub fn new(working_dir: &Path, tool_syntax: ToolSyntax) -> Self {
129+
pub fn new(working_dir: &Path, tool_syntax: ToolSyntax, use_diff_blocks: bool) -> Self {
125130
let state_file_path = working_dir.join(STATE_FILE);
126131
info!("Using state file: {}", state_file_path.display());
127132
Self {
128133
state_file_path,
129134
tool_syntax,
135+
use_diff_blocks,
130136
}
131137
}
132138

@@ -163,6 +169,7 @@ impl FileStatePersistence {
163169
impl AgentStatePersistence for FileStatePersistence {
164170
fn save_agent_state(
165171
&mut self,
172+
name: String,
166173
messages: Vec<Message>,
167174
tool_executions: Vec<ToolExecution>,
168175
working_memory: WorkingMemory,
@@ -181,7 +188,7 @@ impl AgentStatePersistence for FileStatePersistence {
181188
// Create a ChatSession with the current state
182189
let session = ChatSession {
183190
id: "terminal-session".to_string(),
184-
name: "Terminal Session".to_string(),
191+
name: name,
185192
created_at: SystemTime::now(),
186193
updated_at: SystemTime::now(),
187194
messages,
@@ -190,6 +197,7 @@ impl AgentStatePersistence for FileStatePersistence {
190197
init_path,
191198
initial_project,
192199
tool_syntax: self.tool_syntax,
200+
use_diff_blocks: self.use_diff_blocks,
193201
next_request_id,
194202
};
195203

crates/code_assistant/src/agent/runner.rs

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct Agent {
3434
working_memory: WorkingMemory,
3535
llm_provider: Box<dyn LLMProvider>,
3636
tool_syntax: ToolSyntax,
37+
tool_scope: ToolScope,
3738
project_manager: Box<dyn ProjectManager>,
3839
command_executor: Box<dyn CommandExecutor>,
3940
ui: Arc<Box<dyn UserInterface>>,
@@ -91,6 +92,7 @@ impl Agent {
9192
working_memory: WorkingMemory::default(),
9293
llm_provider,
9394
tool_syntax,
95+
tool_scope: ToolScope::Agent, // Default to Agent scope
9496
project_manager,
9597
ui,
9698
command_executor,
@@ -108,6 +110,13 @@ impl Agent {
108110
}
109111
}
110112

113+
/// Enable diff blocks format for file editing (uses replace_in_file tool instead of edit)
114+
pub fn enable_diff_blocks(&mut self) {
115+
self.tool_scope = ToolScope::AgentWithDiffBlocks;
116+
// Clear cached system message so it gets regenerated with the new scope
117+
self.cached_system_message = OnceLock::new();
118+
}
119+
111120
/// Set the shared pending message reference from SessionInstance
112121
pub fn set_pending_message_ref(&mut self, pending_ref: Arc<Mutex<Option<String>>>) {
113122
self.pending_message_ref = Some(pending_ref);
@@ -119,6 +128,12 @@ impl Agent {
119128
self.enable_naming_reminders = false;
120129
}
121130

131+
/// Set session name (used for tests)
132+
#[cfg(test)]
133+
pub(crate) fn set_session_name(&mut self, name: String) {
134+
self.session_name = name;
135+
}
136+
122137
/// Get and clear the pending message from shared state
123138
fn get_and_clear_pending_message(&self) -> Option<String> {
124139
if let Some(ref pending_ref) = self.pending_message_ref {
@@ -185,6 +200,7 @@ impl Agent {
185200
self.message_history.len()
186201
);
187202
self.state_persistence.save_agent_state(
203+
self.session_name.clone(),
188204
self.message_history.clone(),
189205
self.tool_executions.clone(),
190206
self.working_memory.clone(),
@@ -605,7 +621,7 @@ impl Agent {
605621
}
606622

607623
// Generate the system message using the tools module
608-
let mut system_message = generate_system_message(self.tool_syntax, ToolScope::Agent);
624+
let mut system_message = generate_system_message(self.tool_syntax, self.tool_scope);
609625

610626
// Add project information
611627
let mut project_info = String::new();
@@ -724,28 +740,55 @@ impl Agent {
724740
}
725741

726742
/// Inject system reminder for session naming if needed
727-
fn inject_naming_reminder_if_needed(&self, mut messages: Vec<Message>) -> Vec<Message> {
743+
pub(crate) fn inject_naming_reminder_if_needed(
744+
&self,
745+
mut messages: Vec<Message>,
746+
) -> Vec<Message> {
728747
// Only inject if enabled, session is not named yet, and we have messages
729748
if !self.enable_naming_reminders || !self.session_name.is_empty() || messages.is_empty() {
730749
return messages;
731750
}
732751

733-
// Find the last user message and add system reminder
734-
if let Some(last_msg) = messages.last_mut() {
735-
if matches!(last_msg.role, MessageRole::User) {
736-
let reminder_text = "\n\n<system-reminder>\nThis is an automatic reminder from the system. Please use the `name_session` tool first, provided the user has already given you a clear task or question. You can chain additional tools after using the `name_session` tool.\n</system-reminder>";
737-
738-
trace!("Injecting session naming reminder");
739-
740-
match &mut last_msg.content {
741-
MessageContent::Text(text) => {
742-
text.push_str(reminder_text);
743-
}
752+
// Find the last actual user message (not tool results) and add system reminder
753+
// Iterate backwards through messages to find the last user message with actual content
754+
for msg in messages.iter_mut().rev() {
755+
if matches!(msg.role, MessageRole::User) {
756+
let is_actual_user_message = match &msg.content {
757+
MessageContent::Text(_) => true, // Text content is always actual user input
744758
MessageContent::Structured(blocks) => {
745-
blocks.push(ContentBlock::Text {
746-
text: reminder_text.to_string(),
747-
});
759+
// Check if this message contains tool results
760+
// If it contains only ToolResult blocks, it's not an actual user message
761+
blocks
762+
.iter()
763+
.any(|block| !matches!(block, ContentBlock::ToolResult { .. }))
764+
}
765+
};
766+
767+
if is_actual_user_message {
768+
let reminder_text = "<system-reminder>\nThis is an automatic reminder from the system. Please use the `name_session` tool first, provided the user has already given you a clear task or question. You can chain additional tools after using the `name_session` tool.\n</system-reminder>";
769+
770+
trace!("Injecting session naming reminder to actual user message");
771+
772+
match &mut msg.content {
773+
MessageContent::Text(original_text) => {
774+
// Convert from Text to Structured with two ContentBlocks
775+
let original_block = ContentBlock::Text {
776+
text: original_text.clone(),
777+
};
778+
let reminder_block = ContentBlock::Text {
779+
text: reminder_text.to_string(),
780+
};
781+
msg.content =
782+
MessageContent::Structured(vec![original_block, reminder_block]);
783+
}
784+
MessageContent::Structured(blocks) => {
785+
// Add reminder as a new ContentBlock
786+
blocks.push(ContentBlock::Text {
787+
text: reminder_text.to_string(),
788+
});
789+
}
748790
}
791+
break; // Found and updated the last actual user message, we're done
749792
}
750793
}
751794
}
@@ -784,7 +827,7 @@ impl Agent {
784827
tools: match self.tool_syntax {
785828
ToolSyntax::Native => {
786829
Some(crate::tools::AnnotatedToolDefinition::to_tool_definitions(
787-
ToolRegistry::global().get_tool_definitions_for_scope(ToolScope::Agent),
830+
ToolRegistry::global().get_tool_definitions_for_scope(self.tool_scope),
788831
))
789832
}
790833
ToolSyntax::Xml => None,

0 commit comments

Comments
 (0)