Display tool icons

Agus Zubiaga , Mikayla Maki , Antonio Scandurra , and Nathan Sobo created

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

crates/acp/src/acp.rs         | 59 ++++++++++++++++++++++--------------
crates/acp/src/server.rs      |  4 +-
crates/acp/src/thread_view.rs |  2 
3 files changed, 39 insertions(+), 26 deletions(-)

Detailed changes

crates/acp/src/acp.rs 🔗

@@ -10,7 +10,7 @@ use language::LanguageRegistry;
 use markdown::Markdown;
 use project::Project;
 use std::{mem, ops::Range, path::PathBuf, sync::Arc};
-use ui::App;
+use ui::{App, IconName};
 use util::{ResultExt, debug_panic};
 
 pub use server::AcpServer;
@@ -124,6 +124,7 @@ pub enum AgentThreadEntryContent {
 pub struct ToolCall {
     id: ToolCallId,
     label: Entity<Markdown>,
+    icon: IconName,
     status: ToolCallStatus,
 }
 
@@ -272,6 +273,7 @@ impl AcpThread {
     pub fn request_tool_call(
         &mut self,
         label: String,
+        icon: acp::Icon,
         confirmation: acp::ToolCallConfirmation,
         cx: &mut Context<Self>,
     ) -> ToolCallRequest {
@@ -282,23 +284,29 @@ impl AcpThread {
             respond_tx: tx,
         };
 
-        let id = self.insert_tool_call(label, status, cx);
+        let id = self.insert_tool_call(label, status, icon, cx);
         ToolCallRequest { id, outcome: rx }
     }
 
-    pub fn push_tool_call(&mut self, label: String, cx: &mut Context<Self>) -> ToolCallId {
+    pub fn push_tool_call(
+        &mut self,
+        label: String,
+        icon: acp::Icon,
+        cx: &mut Context<Self>,
+    ) -> ToolCallId {
         let status = ToolCallStatus::Allowed {
             status: acp::ToolCallStatus::Running,
             content: None,
         };
 
-        self.insert_tool_call(label, status, cx)
+        self.insert_tool_call(label, status, icon, cx)
     }
 
     fn insert_tool_call(
         &mut self,
         label: String,
         status: ToolCallStatus,
+        icon: acp::Icon,
         cx: &mut Context<Self>,
     ) -> ToolCallId {
         let language_registry = self.project.read(cx).languages().clone();
@@ -310,6 +318,7 @@ impl AcpThread {
                 label: cx.new(|cx| {
                     Markdown::new(label.into(), Some(language_registry.clone()), None, cx)
                 }),
+                icon: acp_icon_to_ui_icon(icon),
                 status,
             }),
             cx,
@@ -433,6 +442,19 @@ impl AcpThread {
     }
 }
 
+fn acp_icon_to_ui_icon(icon: acp::Icon) -> IconName {
+    match icon {
+        acp::Icon::FileSearch => IconName::FileSearch,
+        acp::Icon::Folder => IconName::Folder,
+        acp::Icon::Globe => IconName::Globe,
+        acp::Icon::Hammer => IconName::Hammer,
+        acp::Icon::LightBulb => IconName::LightBulb,
+        acp::Icon::Pencil => IconName::Pencil,
+        acp::Icon::Regex => IconName::Regex,
+        acp::Icon::Terminal => IconName::Terminal,
+    }
+}
+
 pub struct ToolCallRequest {
     pub id: ToolCallId,
     pub outcome: oneshot::Receiver<acp::ToolCallConfirmationOutcome>,
@@ -518,19 +540,14 @@ mod tests {
             })
             .await
             .unwrap();
-        thread.read_with(cx, |thread, cx| {
-            let AgentThreadEntryContent::ToolCall(ToolCall {
-                label,
-                status: ToolCallStatus::Allowed { .. },
-                ..
-            }) = &thread.entries()[1].content
-            else {
-                panic!();
-            };
-
-            label.read_with(cx, |md, _cx| {
-                assert_eq!(md.source(), "ReadFile");
-            });
+        thread.read_with(cx, |thread, _cx| {
+            assert!(matches!(
+                &thread.entries()[1].content,
+                AgentThreadEntryContent::ToolCall(ToolCall {
+                    status: ToolCallStatus::Allowed { .. },
+                    ..
+                })
+            ));
 
             assert!(matches!(
                 thread.entries[2].content,
@@ -558,15 +575,15 @@ mod tests {
 
         run_until_tool_call(&thread, cx).await;
 
-        let tool_call_id = thread.read_with(cx, |thread, cx| {
+        let tool_call_id = thread.read_with(cx, |thread, _cx| {
             let AgentThreadEntryContent::ToolCall(ToolCall {
                 id,
-                label,
                 status:
                     ToolCallStatus::WaitingForConfirmation {
                         confirmation: acp::ToolCallConfirmation::Execute { root_command, .. },
                         ..
                     },
+                ..
             }) = &thread.entries()[1].content
             else {
                 panic!();
@@ -574,10 +591,6 @@ mod tests {
 
             assert_eq!(root_command, "echo");
 
-            label.read_with(cx, |md, _cx| {
-                assert_eq!(md.source(), "Shell");
-            });
-
             *id
         });
 

crates/acp/src/server.rs 🔗

@@ -185,7 +185,7 @@ impl acp::Client for AcpClientDelegate {
         let ToolCallRequest { id, outcome } = cx
             .update(|cx| {
                 self.update_thread(&request.thread_id.into(), cx, |thread, cx| {
-                    thread.request_tool_call(request.label, request.confirmation, cx)
+                    thread.request_tool_call(request.label, request.icon, request.confirmation, cx)
                 })
             })?
             .context("Failed to update thread")?;
@@ -204,7 +204,7 @@ impl acp::Client for AcpClientDelegate {
         let entry_id = cx
             .update(|cx| {
                 self.update_thread(&request.thread_id.into(), cx, |thread, cx| {
-                    thread.push_tool_call(request.label, cx)
+                    thread.push_tool_call(request.label, request.icon, cx)
                 })
             })?
             .context("Failed to update thread")?;

crates/acp/src/thread_view.rs 🔗

@@ -324,7 +324,7 @@ impl AcpThreadView {
                     .w_full()
                     .gap_1p5()
                     .child(
-                        Icon::new(IconName::Cog)
+                        Icon::new(tool_call.icon.into())
                             .size(IconSize::Small)
                             .color(Color::Muted),
                     )