assistant_tools: Reduce allocations (#30776)

tidely created

Another batch of allocation savings. Noteworthy ones are
`find_path_tool.rs` where one clone of *all* found matches was saved and
`web_tool_search.rs` where the tooltip no longer clones the entire url
on every hover.

I'd also like to propose using `std::borrow::Cow` a lot more around the
codebase instead of Strings. There are hundreds if not 1000+ clones that
can be saved pretty regularly simply by switching to Cow. ยดCowยด's are
likely not used because they aren't compatible with futures and because
it could cause lifetime bloat. However if we use `Cow<'static, str>`
(static lifetime) for when we need to pass them into futures, we could
save a TON of allocations for `&'static str`. Additionally I often see
structs being created using `String`'s just to be deserialized
afterwards, which only requires a reference.

Release Notes:

- N/A

Change summary

crates/assistant_tools/src/fetch_tool.rs      | 14 +++++++-------
crates/assistant_tools/src/find_path_tool.rs  | 14 +++++++-------
crates/assistant_tools/src/terminal_tool.rs   |  5 ++---
crates/assistant_tools/src/web_search_tool.rs |  2 +-
4 files changed, 17 insertions(+), 18 deletions(-)

Detailed changes

crates/assistant_tools/src/fetch_tool.rs ๐Ÿ”—

@@ -1,6 +1,6 @@
-use std::cell::RefCell;
 use std::rc::Rc;
 use std::sync::Arc;
+use std::{borrow::Cow, cell::RefCell};
 
 use crate::schema::json_schema_for;
 use anyhow::{Context as _, Result, anyhow, bail};
@@ -39,10 +39,11 @@ impl FetchTool {
     }
 
     async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
-        let mut url = url.to_owned();
-        if !url.starts_with("https://") && !url.starts_with("http://") {
-            url = format!("https://{url}");
-        }
+        let url = if !url.starts_with("https://") && !url.starts_with("http://") {
+            Cow::Owned(format!("https://{url}"))
+        } else {
+            Cow::Borrowed(url)
+        };
 
         let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
 
@@ -156,8 +157,7 @@ impl Tool for FetchTool {
 
         let text = cx.background_spawn({
             let http_client = self.http_client.clone();
-            let url = input.url.clone();
-            async move { Self::build_message(http_client, &url).await }
+            async move { Self::build_message(http_client, &input.url).await }
         });
 
         cx.foreground_executor()

crates/assistant_tools/src/find_path_tool.rs ๐Ÿ”—

@@ -119,14 +119,16 @@ impl Tool for FindPathTool {
                     )
                     .unwrap();
                 }
+
+                for mat in matches.iter().skip(offset).take(RESULTS_PER_PAGE) {
+                    write!(&mut message, "\n{}", mat.display()).unwrap();
+                }
+
                 let output = FindPathToolOutput {
                     glob,
-                    paths: matches.clone(),
+                    paths: matches,
                 };
 
-                for mat in matches.into_iter().skip(offset).take(RESULTS_PER_PAGE) {
-                    write!(&mut message, "\n{}", mat.display()).unwrap();
-                }
                 Ok(ToolResultOutput {
                     content: ToolResultContent::Text(message),
                     output: Some(serde_json::to_value(output)?),
@@ -235,8 +237,6 @@ impl ToolCard for FindPathToolCard {
             format!("{} matches", self.paths.len()).into()
         };
 
-        let glob_label = self.glob.to_string();
-
         let content = if !self.paths.is_empty() && self.expanded {
             Some(
                 v_flex()
@@ -310,7 +310,7 @@ impl ToolCard for FindPathToolCard {
             .gap_1()
             .child(
                 ToolCallCardHeader::new(IconName::SearchCode, matches_label)
-                    .with_code_path(glob_label)
+                    .with_code_path(&self.glob)
                     .disclosure_slot(
                         Disclosure::new("path-search-disclosure", self.expanded)
                             .opened_icon(IconName::ChevronUp)

crates/assistant_tools/src/terminal_tool.rs ๐Ÿ”—

@@ -182,9 +182,8 @@ impl Tool for TerminalTool {
                 let mut child = pair.slave.spawn_command(cmd)?;
                 let mut reader = pair.master.try_clone_reader()?;
                 drop(pair);
-                let mut content = Vec::new();
-                reader.read_to_end(&mut content)?;
-                let mut content = String::from_utf8(content)?;
+                let mut content = String::new();
+                reader.read_to_string(&mut content)?;
                 // Massage the pty output a bit to try to match what the terminal codepath gives us
                 LineEnding::normalize(&mut content);
                 content = content

crates/assistant_tools/src/web_search_tool.rs ๐Ÿ”—

@@ -166,7 +166,7 @@ impl ToolCard for WebSearchToolCard {
                     .gap_1()
                     .children(response.results.iter().enumerate().map(|(index, result)| {
                         let title = result.title.clone();
-                        let url = result.url.clone();
+                        let url = SharedString::from(result.url.clone());
 
                         Button::new(("result", index), title)
                             .label_size(LabelSize::Small)