diff --git a/Cargo.lock b/Cargo.lock index 9f643e6048..564819fab8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,7 +183,6 @@ dependencies = [ "language_models", "log", "lsp", - "open", "parking_lot", "paths", "pretty_assertions", diff --git a/assets/settings/default.json b/assets/settings/default.json index 3ecdcdd6c6..3e3db5a810 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1117,15 +1117,12 @@ "get_code_actions": true, "go_to_definition": true, "list_directory": true, - "project_notifications": false, "move_path": true, "rename_symbol": true, "read_file": true, - "open": true, "grep": true, "spawn_agent": true, "terminal": true, - "thinking": true, "update_plan": true, "search_web": true, }, @@ -1138,16 +1135,13 @@ "diagnostics": true, "fetch": true, "list_directory": true, - "project_notifications": false, "find_path": true, "find_references": true, "get_code_actions": true, "go_to_definition": true, "read_file": true, - "open": true, "grep": true, "spawn_agent": true, - "thinking": true, "update_plan": true, "search_web": true, }, diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml index 1317221206..a7ed9fd72b 100644 --- a/crates/agent/Cargo.toml +++ b/crates/agent/Cargo.toml @@ -46,7 +46,6 @@ language.workspace = true language_model.workspace = true language_models.workspace = true log.workspace = true -open.workspace = true parking_lot.workspace = true paths.workspace = true project.workspace = true diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index abdecdfce3..921c9e89eb 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -2,9 +2,9 @@ use crate::{ ApplyCodeActionTool, CodeActionStore, ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread, DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, FindReferencesTool, GetCodeActionsTool, GoToDefinitionTool, GrepTool, - ListDirectoryTool, MovePathTool, OpenTool, ProjectSnapshot, ReadFileTool, RenameTool, - SpawnAgentTool, SystemPromptTemplate, Templates, TerminalTool, ToolPermissionDecision, - UpdatePlanTool, WebSearchTool, WriteFileTool, decide_permission_from_settings, + ListDirectoryTool, MovePathTool, ProjectSnapshot, ReadFileTool, RenameTool, SpawnAgentTool, + SystemPromptTemplate, Templates, TerminalTool, ToolPermissionDecision, UpdatePlanTool, + WebSearchTool, WriteFileTool, decide_permission_from_settings, }; use acp_thread::{MentionUri, UserMessageId}; use action_log::ActionLog; @@ -1607,7 +1607,6 @@ impl Thread { self.add_tool(GrepTool::new(self.project.clone())); self.add_tool(ListDirectoryTool::new(self.project.clone())); self.add_tool(MovePathTool::new(self.project.clone())); - self.add_tool(OpenTool::new(self.project.clone())); if cx.has_flag::() { self.add_tool(UpdatePlanTool); } diff --git a/crates/agent/src/tools.rs b/crates/agent/src/tools.rs index d59cfd9d07..18222cbbe2 100644 --- a/crates/agent/src/tools.rs +++ b/crates/agent/src/tools.rs @@ -16,7 +16,6 @@ mod go_to_definition_tool; mod grep_tool; mod list_directory_tool; mod move_path_tool; -mod open_tool; mod read_file_tool; mod rename_tool; mod spawn_agent_tool; @@ -72,7 +71,6 @@ pub use go_to_definition_tool::*; pub use grep_tool::*; pub use list_directory_tool::*; pub use move_path_tool::*; -pub use open_tool::*; pub use read_file_tool::*; pub use rename_tool::*; pub use spawn_agent_tool::*; @@ -166,7 +164,6 @@ tools! { GrepTool, ListDirectoryTool, MovePathTool, - OpenTool, ReadFileTool, RenameTool, SpawnAgentTool, diff --git a/crates/agent/src/tools/open_tool.rs b/crates/agent/src/tools/open_tool.rs deleted file mode 100644 index 7329965e62..0000000000 --- a/crates/agent/src/tools/open_tool.rs +++ /dev/null @@ -1,227 +0,0 @@ -use super::tool_permissions::{ - ResolvedProjectPath, authorize_symlink_access, canonicalize_worktree_roots, - resolve_project_path, -}; -use crate::{AgentTool, ToolInput}; -use agent_client_protocol::schema as acp; -use futures::FutureExt as _; -use gpui::{App, AppContext as _, Entity, SharedString, Task}; -use project::Project; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, sync::Arc}; -use util::markdown::MarkdownEscaped; - -/// This tool opens a file or URL with the default application associated with it on the user's operating system: -/// -/// - On macOS, it's equivalent to the `open` command -/// - On Windows, it's equivalent to `start` -/// - On Linux, it uses something like `xdg-open`, `gio open`, `gnome-open`, `kde-open`, `wslview` as appropriate -/// -/// For example, it can open a web browser with a URL, open a PDF file with the default PDF viewer, etc. -/// -/// You MUST ONLY use this tool when the user has explicitly requested opening something. You MUST NEVER assume that the user would like for you to use this tool. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct OpenToolInput { - /// The path or URL to open with the default application. - path_or_url: String, -} - -pub struct OpenTool { - project: Entity, -} - -impl OpenTool { - pub fn new(project: Entity) -> Self { - Self { project } - } -} - -impl AgentTool for OpenTool { - type Input = OpenToolInput; - type Output = String; - - const NAME: &'static str = "open"; - - fn kind() -> acp::ToolKind { - acp::ToolKind::Execute - } - - fn initial_title( - &self, - input: Result, - _cx: &mut App, - ) -> SharedString { - if let Ok(input) = input { - format!("Open `{}`", MarkdownEscaped(&input.path_or_url)).into() - } else { - "Open file or URL".into() - } - } - - fn run( - self: Arc, - input: ToolInput, - event_stream: crate::ToolCallEventStream, - cx: &mut App, - ) -> Task> { - let project = self.project.clone(); - cx.spawn(async move |cx| { - let input = input.recv().await.map_err(|e| e.to_string())?; - - // If path_or_url turns out to be a path in the project, make it absolute. - let (abs_path, initial_title) = cx.update(|cx| { - let abs_path = to_absolute_path(&input.path_or_url, project.clone(), cx); - let initial_title = self.initial_title(Ok(input.clone()), cx); - (abs_path, initial_title) - }); - - let fs = project.read_with(cx, |project, _cx| project.fs().clone()); - let canonical_roots = canonicalize_worktree_roots(&project, &fs, cx).await; - - // Symlink escape authorization replaces (rather than supplements) - // the normal tool-permission prompt. The symlink prompt already - // requires explicit user approval with the canonical target shown, - // which is strictly more security-relevant than a generic confirm. - let symlink_escape = project.read_with(cx, |project, cx| { - match resolve_project_path( - project, - PathBuf::from(&input.path_or_url), - &canonical_roots, - cx, - ) { - Ok(ResolvedProjectPath::SymlinkEscape { - canonical_target, .. - }) => Some(canonical_target), - _ => None, - } - }); - - let authorize = if let Some(canonical_target) = symlink_escape { - cx.update(|cx| { - authorize_symlink_access( - Self::NAME, - &input.path_or_url, - &canonical_target, - &event_stream, - cx, - ) - }) - } else { - cx.update(|cx| { - let context = crate::ToolPermissionContext::new( - Self::NAME, - vec![input.path_or_url.clone()], - ); - event_stream.authorize(initial_title, context, cx) - }) - }; - - futures::select! { - result = authorize.fuse() => result.map_err(|e| e.to_string())?, - _ = event_stream.cancelled_by_user().fuse() => { - return Err("Open cancelled by user".to_string()); - } - } - - let path_or_url = input.path_or_url.clone(); - cx.background_spawn(async move { - match abs_path { - Some(path) => open::that(path), - None => open::that(path_or_url), - } - .map_err(|e| format!("Failed to open URL or file path: {e}")) - }) - .await?; - - Ok(format!("Successfully opened {}", input.path_or_url)) - }) - } -} - -fn to_absolute_path( - potential_path: &str, - project: Entity, - cx: &mut App, -) -> Option { - let project = project.read(cx); - project - .find_project_path(PathBuf::from(potential_path), cx) - .and_then(|project_path| project.absolute_path(&project_path, cx)) -} - -#[cfg(test)] -mod tests { - use super::*; - use gpui::TestAppContext; - use project::{FakeFs, Project}; - use settings::SettingsStore; - use std::path::Path; - use tempfile::TempDir; - - #[gpui::test] - async fn test_to_absolute_path(cx: &mut TestAppContext) { - init_test(cx); - let temp_dir = TempDir::new().expect("Failed to create temp directory"); - let temp_path = temp_dir.path().to_string_lossy().into_owned(); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree( - &temp_path, - serde_json::json!({ - "src": { - "main.rs": "fn main() {}", - "lib.rs": "pub fn lib_fn() {}" - }, - "docs": { - "readme.md": "# Project Documentation" - } - }), - ) - .await; - - // Use the temp_path as the root directory, not just its filename - let project = Project::test(fs.clone(), [temp_dir.path()], cx).await; - - // Test cases where the function should return Some - cx.update(|cx| { - // Project-relative paths should return Some - // Create paths using the last segment of the temp path to simulate a project-relative path - let root_dir_name = Path::new(&temp_path) - .file_name() - .unwrap_or_else(|| std::ffi::OsStr::new("temp")) - .to_string_lossy(); - - assert!( - to_absolute_path(&format!("{root_dir_name}/src/main.rs"), project.clone(), cx) - .is_some(), - "Failed to resolve main.rs path" - ); - - assert!( - to_absolute_path( - &format!("{root_dir_name}/docs/readme.md",), - project.clone(), - cx, - ) - .is_some(), - "Failed to resolve readme.md path" - ); - - // External URL should return None - let result = to_absolute_path("https://example.com", project.clone(), cx); - assert_eq!(result, None, "External URLs should return None"); - - // Path outside project - let result = to_absolute_path("../invalid/path", project.clone(), cx); - assert_eq!(result, None, "Paths outside the project should return None"); - }); - } - - fn init_test(cx: &mut TestAppContext) { - cx.update(|cx| { - let settings_store = SettingsStore::test(cx); - cx.set_global(settings_store); - }); - } -} diff --git a/docs/.doc-examples/reference.md b/docs/.doc-examples/reference.md index 5b250fc004..35fda196f8 100644 --- a/docs/.doc-examples/reference.md +++ b/docs/.doc-examples/reference.md @@ -52,22 +52,10 @@ Searches file contents across the project using regular expressions, preferred f Lists files and directories in a given path, providing an overview of filesystem contents. -### `now` {#now} - -Returns the current date and time. - -### `open` {#open} - -Opens a file or URL with the default application associated with it on the user's operating system. - ### `read_file` {#read-file} Reads the content of a specified file in the project, allowing access to file contents. -### `thinking` {#thinking} - -Allows the Agent to work through problems, brainstorm ideas, or plan without executing actions, useful for complex problem-solving. - ### `search_web` {#search-web} Searches the web for information, providing results with snippets and links from relevant web pages, useful for accessing real-time information. @@ -94,13 +82,9 @@ Edits files by replacing specific text with new content. Moves or renames a file or directory in the project, performing a rename if only the filename differs. -### `restore_file_from_disk` {#restore-file} +### `write_file` {#write-file} -Discards unsaved changes in open buffers by reloading file contents from disk. Useful for resetting files to their on-disk state before retrying an edit. - -### `save_file` {#save-file} - -Saves files that have unsaved changes. Used when files need to be saved before further edits can be made. +Creates a new file or overwrites an existing file with completely new contents. ### `terminal` {#terminal} @@ -108,7 +92,7 @@ Executes shell commands and returns the combined output, creating a new shell pr ## Other Tools {#other-tools} -### `subagent` {#subagent} +### `spawn_agent` {#spawn-agent} Spawns a subagent with its own context window to perform a delegated task. Useful for running parallel investigations, completing self-contained tasks, or performing research where only the outcome matters. Each subagent has access to the same tools as the parent agent. diff --git a/docs/src/ai/mcp.md b/docs/src/ai/mcp.md index fb3e2b25e0..6582508f5c 100644 --- a/docs/src/ai/mcp.md +++ b/docs/src/ai/mcp.md @@ -110,7 +110,6 @@ As an example, [the Dagger team suggests](https://container-use.com/agent-integr "list_directory": false, "diagnostics": false, "read_file": false, - "open": false, "move_path": false, "grep": false, "edit_file": false, diff --git a/docs/src/ai/tool-permissions.md b/docs/src/ai/tool-permissions.md index 61d599a9d1..040ece8041 100644 --- a/docs/src/ai/tool-permissions.md +++ b/docs/src/ai/tool-permissions.md @@ -43,18 +43,17 @@ The `tool_permissions` setting lets you customize tool permissions by specifying ## Supported Tools -| Tool | Input Matched Against | -| ------------------------ | ---------------------------- | -| `terminal` | The shell command string | -| `edit_file` | The file path | -| `delete_path` | The path being deleted | -| `move_path` | Source and destination paths | -| `copy_path` | Source and destination paths | -| `create_directory` | The directory path | -| `restore_file_from_disk` | The file paths | -| `save_file` | The file paths | -| `fetch` | The URL | -| `search_web` | The search query | +| Tool | Input Matched Against | +| ------------------ | ---------------------------- | +| `terminal` | The shell command string | +| `edit_file` | The file path | +| `write_file` | The file path | +| `delete_path` | The path being deleted | +| `move_path` | Source and destination paths | +| `copy_path` | Source and destination paths | +| `create_directory` | The directory path | +| `fetch` | The URL | +| `search_web` | The search query | For MCP tools, use the format `mcp::`. For example, a tool called `create_issue` on a server called `github` would be `mcp:github:create_issue`. diff --git a/docs/src/ai/tools.md b/docs/src/ai/tools.md index 627a905899..9a85468f67 100644 --- a/docs/src/ai/tools.md +++ b/docs/src/ai/tools.md @@ -41,22 +41,10 @@ Searches file contents across the project using regular expressions, preferred f Lists files and directories in a given path, providing an overview of filesystem contents. -### `now` - -Returns the current date and time. - -### `open` - -Opens a file or URL with the default application associated with it on the user's operating system. - ### `read_file` Reads the content of a specified file in the project, allowing access to file contents. -### `thinking` - -Allows the Agent to work through problems, brainstorm ideas, or plan without executing actions, useful for complex problem-solving. - ### `search_web` Searches the web for information, providing results with snippets and links from relevant web pages, useful for accessing real-time information. @@ -89,13 +77,9 @@ Edits files by replacing specific text with new content. Moves or renames a file or directory in the project, performing a rename if only the filename differs. -### `restore_file_from_disk` +### `write_file` -Discards unsaved changes in open buffers by reloading file contents from disk. Useful for resetting files to their on-disk state before retrying an edit. - -### `save_file` - -Saves files that have unsaved changes. Used when files need to be saved before further edits can be made. +Creates a new file or overwrites an existing file with completely new contents. ### `terminal`