agent: Make read_file and edit_file tool call titles more specific (#37639)

For read_file and edit_file, show the worktree-relative path if there's
only one visible worktree, and the "full path" otherwise. Also restores
the display of line numbers for read_file calls.

Release Notes:

- N/A
This commit is contained in:
Cole Miller
2025-09-08 12:57:22 -04:00
committed by GitHub
parent a66cd820b3
commit 5f01f6d75f
22 changed files with 393 additions and 173 deletions
+167 -77
View File
@@ -120,11 +120,17 @@ impl From<EditFileToolOutput> for LanguageModelToolResultContent {
pub struct EditFileTool {
thread: WeakEntity<Thread>,
language_registry: Arc<LanguageRegistry>,
project: Entity<Project>,
}
impl EditFileTool {
pub fn new(thread: WeakEntity<Thread>, language_registry: Arc<LanguageRegistry>) -> Self {
pub fn new(
project: Entity<Project>,
thread: WeakEntity<Thread>,
language_registry: Arc<LanguageRegistry>,
) -> Self {
Self {
project,
thread,
language_registry,
}
@@ -195,22 +201,50 @@ impl AgentTool for EditFileTool {
acp::ToolKind::Edit
}
fn initial_title(&self, input: Result<Self::Input, serde_json::Value>) -> SharedString {
fn initial_title(
&self,
input: Result<Self::Input, serde_json::Value>,
cx: &mut App,
) -> SharedString {
match input {
Ok(input) => input.display_description.into(),
Ok(input) => self
.project
.read(cx)
.find_project_path(&input.path, cx)
.and_then(|project_path| {
self.project
.read(cx)
.short_full_path_for_project_path(&project_path, cx)
})
.unwrap_or(Path::new(&input.path).into())
.to_string_lossy()
.to_string()
.into(),
Err(raw_input) => {
if let Some(input) =
serde_json::from_value::<EditFileToolPartialInput>(raw_input).ok()
{
let path = input.path.trim();
if !path.is_empty() {
return self
.project
.read(cx)
.find_project_path(&input.path, cx)
.and_then(|project_path| {
self.project
.read(cx)
.short_full_path_for_project_path(&project_path, cx)
})
.unwrap_or(Path::new(&input.path).into())
.to_string_lossy()
.to_string()
.into();
}
let description = input.display_description.trim();
if !description.is_empty() {
return description.to_string().into();
}
let path = input.path.trim().to_string();
if !path.is_empty() {
return path.into();
}
}
DEFAULT_UI_TEXT.into()
@@ -545,7 +579,7 @@ mod tests {
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project,
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
@@ -560,11 +594,12 @@ mod tests {
path: "root/nonexistent_file.txt".into(),
mode: EditFileMode::Edit,
};
Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run(
input,
ToolCallEventStream::test().0,
cx,
)
Arc::new(EditFileTool::new(
project,
thread.downgrade(),
language_registry,
))
.run(input, ToolCallEventStream::test().0, cx)
})
.await;
assert_eq!(
@@ -743,7 +778,7 @@ mod tests {
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project,
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
@@ -775,6 +810,7 @@ mod tests {
mode: EditFileMode::Overwrite,
};
Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry.clone(),
))
@@ -833,11 +869,12 @@ mod tests {
path: "root/src/main.rs".into(),
mode: EditFileMode::Overwrite,
};
Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run(
input,
ToolCallEventStream::test().0,
cx,
)
Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
))
.run(input, ToolCallEventStream::test().0, cx)
});
// Stream the unformatted content
@@ -885,7 +922,7 @@ mod tests {
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project,
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
@@ -918,6 +955,7 @@ mod tests {
mode: EditFileMode::Overwrite,
};
Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry.clone(),
))
@@ -969,11 +1007,12 @@ mod tests {
path: "root/src/main.rs".into(),
mode: EditFileMode::Overwrite,
};
Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run(
input,
ToolCallEventStream::test().0,
cx,
)
Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
))
.run(input, ToolCallEventStream::test().0, cx)
});
// Stream the content with trailing whitespace
@@ -1012,7 +1051,7 @@ mod tests {
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project,
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
@@ -1020,7 +1059,11 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
));
fs.insert_tree("/root", json!({})).await;
// Test 1: Path with .zed component should require confirmation
@@ -1148,7 +1191,7 @@ mod tests {
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project,
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
@@ -1156,7 +1199,11 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
));
// Test global config paths - these should require confirmation if they exist and are outside the project
let test_cases = vec![
@@ -1264,7 +1311,11 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
));
// Test files in different worktrees
let test_cases = vec![
@@ -1344,7 +1395,11 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
));
// Test edge cases
let test_cases = vec![
@@ -1427,7 +1482,11 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
language_registry,
));
// Test different EditFileMode values
let modes = vec![
@@ -1507,48 +1566,67 @@ mod tests {
cx,
)
});
let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry));
let tool = Arc::new(EditFileTool::new(
project,
thread.downgrade(),
language_registry,
));
assert_eq!(
tool.initial_title(Err(json!({
"path": "src/main.rs",
"display_description": "",
"old_string": "old code",
"new_string": "new code"
}))),
"src/main.rs"
);
assert_eq!(
tool.initial_title(Err(json!({
"path": "",
"display_description": "Fix error handling",
"old_string": "old code",
"new_string": "new code"
}))),
"Fix error handling"
);
assert_eq!(
tool.initial_title(Err(json!({
"path": "src/main.rs",
"display_description": "Fix error handling",
"old_string": "old code",
"new_string": "new code"
}))),
"Fix error handling"
);
assert_eq!(
tool.initial_title(Err(json!({
"path": "",
"display_description": "",
"old_string": "old code",
"new_string": "new code"
}))),
DEFAULT_UI_TEXT
);
assert_eq!(
tool.initial_title(Err(serde_json::Value::Null)),
DEFAULT_UI_TEXT
);
cx.update(|cx| {
// ...
assert_eq!(
tool.initial_title(
Err(json!({
"path": "src/main.rs",
"display_description": "",
"old_string": "old code",
"new_string": "new code"
})),
cx
),
"src/main.rs"
);
assert_eq!(
tool.initial_title(
Err(json!({
"path": "",
"display_description": "Fix error handling",
"old_string": "old code",
"new_string": "new code"
})),
cx
),
"Fix error handling"
);
assert_eq!(
tool.initial_title(
Err(json!({
"path": "src/main.rs",
"display_description": "Fix error handling",
"old_string": "old code",
"new_string": "new code"
})),
cx
),
"src/main.rs"
);
assert_eq!(
tool.initial_title(
Err(json!({
"path": "",
"display_description": "",
"old_string": "old code",
"new_string": "new code"
})),
cx
),
DEFAULT_UI_TEXT
);
assert_eq!(
tool.initial_title(Err(serde_json::Value::Null), cx),
DEFAULT_UI_TEXT
);
});
}
#[gpui::test]
@@ -1575,7 +1653,11 @@ mod tests {
// Ensure the diff is finalized after the edit completes.
{
let tool = Arc::new(EditFileTool::new(thread.downgrade(), languages.clone()));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages.clone(),
));
let (stream_tx, mut stream_rx) = ToolCallEventStream::test();
let edit = cx.update(|cx| {
tool.run(
@@ -1600,7 +1682,11 @@ mod tests {
// Ensure the diff is finalized if an error occurs while editing.
{
model.forbid_requests();
let tool = Arc::new(EditFileTool::new(thread.downgrade(), languages.clone()));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages.clone(),
));
let (stream_tx, mut stream_rx) = ToolCallEventStream::test();
let edit = cx.update(|cx| {
tool.run(
@@ -1623,7 +1709,11 @@ mod tests {
// Ensure the diff is finalized if the tool call gets dropped.
{
let tool = Arc::new(EditFileTool::new(thread.downgrade(), languages.clone()));
let tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages.clone(),
));
let (stream_tx, mut stream_rx) = ToolCallEventStream::test();
let edit = cx.update(|cx| {
tool.run(