Implement auth for edit_file_tool

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Antonio Scandurra 2025-08-08 11:10:02 +02:00
parent 294109c6da
commit da5f2978fd
3 changed files with 55 additions and 57 deletions

View file

@ -103,7 +103,7 @@ impl AgentTool for ToolRequiringPermission {
event_stream: ToolCallEventStream,
cx: &mut App,
) -> Task<Result<String>> {
let auth_check = event_stream.authorize("Authorize?");
let auth_check = event_stream.authorize("Authorize?".into());
cx.foreground_executor().spawn(async move {
auth_check.await?;
Ok("Allowed".to_string())

View file

@ -925,11 +925,7 @@ impl ToolCallEventStream {
});
}
pub fn authorize<T>(&self, title: T) -> impl use<T> + Future<Output = Result<()>>
where
T: Into<String>,
{
let title = title.into();
pub fn authorize(&self, title: String) -> impl use<> + Future<Output = Result<()>> {
self.stream.authorize_tool_call(
&self.tool_use_id,
title,

View file

@ -12,6 +12,7 @@ use project::lsp_store::{FormatTrigger, LspFormatTarget};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
use smol::stream::StreamExt as _;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@ -98,6 +99,54 @@ impl EditFileTool {
pub fn new(project: Entity<Project>, thread: Entity<Thread>) -> Self {
Self { project, thread }
}
fn authorize(
&self,
input: &EditFileToolInput,
event_stream: &ToolCallEventStream,
cx: &App,
) -> Task<Result<()>> {
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
return Task::ready(Ok(()));
}
// If any path component matches the local settings folder, then this could affect
// the editor in ways beyond the project source, so prompt.
let local_settings_folder = paths::local_settings_folder_relative_path();
let path = Path::new(&input.path);
if path
.components()
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
{
return cx.foreground_executor().spawn(
event_stream.authorize(format!("{} (local settings)", input.display_description)),
);
}
// It's also possible that the global config dir is configured to be inside the project,
// so check for that edge case too.
if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
if canonical_path.starts_with(paths::config_dir()) {
return cx.foreground_executor().spawn(
event_stream
.authorize(format!("{} (global settings)", input.display_description)),
);
}
}
// Check if path is inside the global config directory
// First check if it's already inside project - if not, try to canonicalize
let project_path = self.project.read(cx).find_project_path(&input.path, cx);
// If the path is inside the project, and it's not one of the above edge cases,
// then no confirmation is necessary. Otherwise, confirmation is necessary.
if project_path.is_some() {
Task::ready(Ok(()))
} else {
cx.foreground_executor()
.spawn(event_stream.authorize(input.display_description.clone()))
}
}
}
impl AgentTool for EditFileTool {
@ -112,59 +161,9 @@ impl AgentTool for EditFileTool {
}
fn initial_title(&self, input: Self::Input) -> SharedString {
let path = Path::new(&input.path);
let mut description = input.display_description.clone();
// Add context about why confirmation may be needed
let local_settings_folder = paths::local_settings_folder_relative_path();
if path
.components()
.any(|c| c.as_os_str() == local_settings_folder.as_os_str())
{
description.push_str(" (local settings)");
} else if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
if canonical_path.starts_with(paths::config_dir()) {
description.push_str(" (global settings)");
}
}
description.into()
input.display_description.into()
}
// todo!
// fn needs_authorization(&self, input: Self::Input, cx: &App) -> bool {
// if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
// return false;
// }
// // If any path component matches the local settings folder, then this could affect
// // the editor in ways beyond the project source, so prompt.
// let local_settings_folder = paths::local_settings_folder_relative_path();
// let path = Path::new(&input.path);
// if path
// .components()
// .any(|component| component.as_os_str() == local_settings_folder.as_os_str())
// {
// return true;
// }
// // It's also possible that the global config dir is configured to be inside the project,
// // so check for that edge case too.
// if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
// if canonical_path.starts_with(paths::config_dir()) {
// return true;
// }
// }
// // Check if path is inside the global config directory
// // First check if it's already inside project - if not, try to canonicalize
// let project_path = self.project.read(cx).find_project_path(&input.path, cx);
// // If the path is inside the project, and it's not one of the above edge cases,
// // then no confirmation is necessary. Otherwise, confirmation is necessary.
// project_path.is_none()
// }
fn run(
self: Arc<Self>,
input: Self::Input,
@ -184,7 +183,10 @@ impl AgentTool for EditFileTool {
let model = thread.selected_model.clone();
let action_log = thread.action_log().clone();
let authorize = self.authorize(&input, &event_stream, cx);
cx.spawn(async move |cx: &mut AsyncApp| {
authorize.await?;
let edit_format = EditFormat::from_model(model.clone())?;
let edit_agent = EditAgent::new(
model,