diff --git a/Cargo.lock b/Cargo.lock index 611fbc0c21..347b925bed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -724,6 +724,7 @@ dependencies = [ "itertools 0.14.0", "language", "language_model", + "open", "project", "rand 0.8.5", "release_channel", diff --git a/Cargo.toml b/Cargo.toml index fa8b799a9e..6b01ae7988 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -470,6 +470,7 @@ mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] } nanoid = "0.4" nbformat = { version = "0.10.0" } nix = "0.29" +open = "5.0.0" num-format = "0.4.4" ordered-float = "2.1.1" palette = { version = "0.7.5", default-features = false, features = ["std"] } diff --git a/crates/assistant_tools/Cargo.toml b/crates/assistant_tools/Cargo.toml index 3893747ca9..1120b539c8 100644 --- a/crates/assistant_tools/Cargo.toml +++ b/crates/assistant_tools/Cargo.toml @@ -35,6 +35,7 @@ ui.workspace = true util.workspace = true workspace.workspace = true worktree.workspace = true +open = { workspace = true } [dev-dependencies] collections = { workspace = true, features = ["test-support"] } diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index b85ec55e98..81a1a125bc 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -10,6 +10,7 @@ mod find_replace_file_tool; mod list_directory_tool; mod move_path_tool; mod now_tool; +mod open_tool; mod path_search_tool; mod read_file_tool; mod regex_search_tool; @@ -34,6 +35,7 @@ use crate::fetch_tool::FetchTool; use crate::find_replace_file_tool::FindReplaceFileTool; use crate::list_directory_tool::ListDirectoryTool; use crate::now_tool::NowTool; +use crate::open_tool::OpenTool; use crate::path_search_tool::PathSearchTool; use crate::read_file_tool::ReadFileTool; use crate::regex_search_tool::RegexSearchTool; @@ -55,6 +57,7 @@ pub fn init(http_client: Arc, cx: &mut App) { registry.register_tool(EditFilesTool); registry.register_tool(ListDirectoryTool); registry.register_tool(NowTool); + registry.register_tool(OpenTool); registry.register_tool(PathSearchTool); registry.register_tool(ReadFileTool); registry.register_tool(RegexSearchTool); diff --git a/crates/assistant_tools/src/open_tool.rs b/crates/assistant_tools/src/open_tool.rs new file mode 100644 index 0000000000..392d603dba --- /dev/null +++ b/crates/assistant_tools/src/open_tool.rs @@ -0,0 +1,68 @@ +use anyhow::{anyhow, Context as _, Result}; +use assistant_tool::{ActionLog, Tool}; +use gpui::{App, AppContext, Entity, Task}; +use language_model::LanguageModelRequestMessage; +use project::Project; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use ui::IconName; +use util::markdown::MarkdownString; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct OpenToolInput { + /// The path or URL to open with the default application. + path_or_url: String, +} + +pub struct OpenTool; + +impl Tool for OpenTool { + fn name(&self) -> String { + "open".to_string() + } + + fn needs_confirmation(&self) -> bool { + true + } + + fn description(&self) -> String { + include_str!("./open_tool/description.md").to_string() + } + + fn icon(&self) -> IconName { + IconName::ExternalLink + } + + fn input_schema(&self) -> serde_json::Value { + let schema = schemars::schema_for!(OpenToolInput); + serde_json::to_value(&schema).unwrap() + } + + fn ui_text(&self, input: &serde_json::Value) -> String { + match serde_json::from_value::(input.clone()) { + Ok(input) => format!("Open `{}`", MarkdownString::escape(&input.path_or_url)), + Err(_) => "Open file or URL".to_string(), + } + } + + fn run( + self: Arc, + input: serde_json::Value, + _messages: &[LanguageModelRequestMessage], + _project: Entity, + _action_log: Entity, + cx: &mut App, + ) -> Task> { + let input: OpenToolInput = match serde_json::from_value(input) { + Ok(input) => input, + Err(err) => return Task::ready(Err(anyhow!(err))), + }; + + cx.background_spawn(async move { + open::that(&input.path_or_url).context("Failed to open URL or file path")?; + + Ok(format!("Successfully opened {}", input.path_or_url)) + }) + } +} diff --git a/crates/assistant_tools/src/open_tool/description.md b/crates/assistant_tools/src/open_tool/description.md new file mode 100644 index 0000000000..1e7eb4127d --- /dev/null +++ b/crates/assistant_tools/src/open_tool/description.md @@ -0,0 +1,6 @@ +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.