Use agent name from extension (#44496)

Richard Feldman created

Previously this rendered `mistral-vibe` and not `Mistral Vibe`:

<img width="242" height="199" alt="Screenshot 2025-12-09 at 2 52 48 PM"
src="https://github.com/user-attachments/assets/f85cbf20-91d1-4c05-8b3a-fa5b544acb1c"
/>

Release Notes:

- Render agent display names from extension in menu

Change summary

crates/agent_ui/src/agent_configuration.rs | 37 +++++++++++++++++------
crates/agent_ui/src/agent_panel.rs         |  5 ++
crates/project/src/agent_server_store.rs   | 23 ++++++++++++++
3 files changed, 54 insertions(+), 11 deletions(-)

Detailed changes

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -977,7 +977,10 @@ impl AgentConfiguration {
                 } else {
                     AgentIcon::Name(IconName::Ai)
                 };
-                (name, icon)
+                let display_name = agent_server_store
+                    .agent_display_name(&name)
+                    .unwrap_or_else(|| name.0.clone());
+                (name, icon, display_name)
             })
             .collect();
 
@@ -1084,6 +1087,7 @@ impl AgentConfiguration {
                             .child(self.render_agent_server(
                                 AgentIcon::Name(IconName::AiClaude),
                                 "Claude Code",
+                                "Claude Code",
                                 false,
                                 cx,
                             ))
@@ -1091,6 +1095,7 @@ impl AgentConfiguration {
                             .child(self.render_agent_server(
                                 AgentIcon::Name(IconName::AiOpenAi),
                                 "Codex CLI",
+                                "Codex CLI",
                                 false,
                                 cx,
                             ))
@@ -1098,16 +1103,23 @@ impl AgentConfiguration {
                             .child(self.render_agent_server(
                                 AgentIcon::Name(IconName::AiGemini),
                                 "Gemini CLI",
+                                "Gemini CLI",
                                 false,
                                 cx,
                             ))
                             .map(|mut parent| {
-                                for (name, icon) in user_defined_agents {
+                                for (name, icon, display_name) in user_defined_agents {
                                     parent = parent
                                         .child(
                                             Divider::horizontal().color(DividerColor::BorderFaded),
                                         )
-                                        .child(self.render_agent_server(icon, name, true, cx));
+                                        .child(self.render_agent_server(
+                                            icon,
+                                            name,
+                                            display_name,
+                                            true,
+                                            cx,
+                                        ));
                                 }
                                 parent
                             }),
@@ -1118,11 +1130,13 @@ impl AgentConfiguration {
     fn render_agent_server(
         &self,
         icon: AgentIcon,
-        name: impl Into<SharedString>,
+        id: impl Into<SharedString>,
+        display_name: impl Into<SharedString>,
         external: bool,
         cx: &mut Context<Self>,
     ) -> impl IntoElement {
-        let name = name.into();
+        let id = id.into();
+        let display_name = display_name.into();
         let icon = match icon {
             AgentIcon::Name(icon_name) => Icon::new(icon_name)
                 .size(IconSize::Small)
@@ -1132,12 +1146,15 @@ impl AgentConfiguration {
                 .color(Color::Muted),
         };
 
-        let tooltip_id = SharedString::new(format!("agent-source-{}", name));
-        let tooltip_message = format!("The {} agent was installed from an extension.", name);
+        let tooltip_id = SharedString::new(format!("agent-source-{}", id));
+        let tooltip_message = format!(
+            "The {} agent was installed from an extension.",
+            display_name
+        );
 
-        let agent_server_name = ExternalAgentServerName(name.clone());
+        let agent_server_name = ExternalAgentServerName(id.clone());
 
-        let uninstall_btn_id = SharedString::from(format!("uninstall-{}", name));
+        let uninstall_btn_id = SharedString::from(format!("uninstall-{}", id));
         let uninstall_button = IconButton::new(uninstall_btn_id, IconName::Trash)
             .icon_color(Color::Muted)
             .icon_size(IconSize::Small)
@@ -1161,7 +1178,7 @@ impl AgentConfiguration {
                 h_flex()
                     .gap_1p5()
                     .child(icon)
-                    .child(Label::new(name))
+                    .child(Label::new(display_name))
                     .when(external, |this| {
                         this.child(
                             div()

crates/agent_ui/src/agent_panel.rs 🔗

@@ -2083,8 +2083,11 @@ impl AgentPanel {
 
                                 for agent_name in agent_names {
                                     let icon_path = agent_server_store.agent_icon(&agent_name);
+                                    let display_name = agent_server_store
+                                        .agent_display_name(&agent_name)
+                                        .unwrap_or_else(|| agent_name.0.clone());
 
-                                    let mut entry = ContextMenuEntry::new(agent_name.clone());
+                                    let mut entry = ContextMenuEntry::new(display_name);
 
                                     if let Some(icon_path) = icon_path {
                                         entry = entry.custom_icon_svg(icon_path);

crates/project/src/agent_server_store.rs 🔗

@@ -137,6 +137,7 @@ pub struct AgentServerStore {
     state: AgentServerStoreState,
     external_agents: HashMap<ExternalAgentServerName, Box<dyn ExternalAgentServer>>,
     agent_icons: HashMap<ExternalAgentServerName, SharedString>,
+    agent_display_names: HashMap<ExternalAgentServerName, SharedString>,
 }
 
 pub struct AgentServersUpdated;
@@ -155,6 +156,7 @@ mod ext_agent_tests {
             state: AgentServerStoreState::Collab,
             external_agents: HashMap::default(),
             agent_icons: HashMap::default(),
+            agent_display_names: HashMap::default(),
         }
     }
 
@@ -258,6 +260,7 @@ impl AgentServerStore {
         self.external_agents.retain(|name, agent| {
             if agent.downcast_mut::<LocalExtensionArchiveAgent>().is_some() {
                 self.agent_icons.remove(name);
+                self.agent_display_names.remove(name);
                 false
             } else {
                 // Keep the hardcoded external agents that don't come from extensions
@@ -275,6 +278,12 @@ impl AgentServerStore {
                 for (ext_id, manifest) in manifests {
                     for (agent_name, agent_entry) in &manifest.agent_servers {
                         // Store absolute icon path if provided, resolving symlinks for dev extensions
+                        // Store display name from manifest
+                        self.agent_display_names.insert(
+                            ExternalAgentServerName(agent_name.clone().into()),
+                            SharedString::from(agent_entry.name.clone()),
+                        );
+
                         let icon_path = if let Some(icon) = &agent_entry.icon {
                             let icon_path = extensions_dir.join(ext_id).join(icon);
                             // Canonicalize to resolve symlinks (dev extensions are symlinked)
@@ -310,6 +319,12 @@ impl AgentServerStore {
                 let mut agents = vec![];
                 for (ext_id, manifest) in manifests {
                     for (agent_name, agent_entry) in &manifest.agent_servers {
+                        // Store display name from manifest
+                        self.agent_display_names.insert(
+                            ExternalAgentServerName(agent_name.clone().into()),
+                            SharedString::from(agent_entry.name.clone()),
+                        );
+
                         // Store absolute icon path if provided, resolving symlinks for dev extensions
                         let icon = if let Some(icon) = &agent_entry.icon {
                             let icon_path = extensions_dir.join(ext_id).join(icon);
@@ -369,6 +384,10 @@ impl AgentServerStore {
         self.agent_icons.get(name).cloned()
     }
 
+    pub fn agent_display_name(&self, name: &ExternalAgentServerName) -> Option<SharedString> {
+        self.agent_display_names.get(name).cloned()
+    }
+
     pub fn init_remote(session: &AnyProtoClient) {
         session.add_entity_message_handler(Self::handle_external_agents_updated);
         session.add_entity_message_handler(Self::handle_loading_status_updated);
@@ -559,6 +578,7 @@ impl AgentServerStore {
             },
             external_agents: Default::default(),
             agent_icons: Default::default(),
+            agent_display_names: Default::default(),
         };
         if let Some(_events) = extension::ExtensionEvents::try_global(cx) {}
         this.agent_servers_settings_changed(cx);
@@ -609,6 +629,7 @@ impl AgentServerStore {
             },
             external_agents: external_agents.into_iter().collect(),
             agent_icons: HashMap::default(),
+            agent_display_names: HashMap::default(),
         }
     }
 
@@ -617,6 +638,7 @@ impl AgentServerStore {
             state: AgentServerStoreState::Collab,
             external_agents: Default::default(),
             agent_icons: Default::default(),
+            agent_display_names: Default::default(),
         }
     }
 
@@ -2040,6 +2062,7 @@ mod extension_agent_tests {
             state: AgentServerStoreState::Collab,
             external_agents: HashMap::default(),
             agent_icons: HashMap::default(),
+            agent_display_names: HashMap::default(),
         };
 
         // Seed with extension agents (contain ": ") and custom agents (don't contain ": ")