Add Native Agent to UI and implement NativeAgentServer

Nathan Sobo created

- Created NativeAgentServer that implements AgentServer trait
- Added NativeAgent to ExternalAgent enum
- Added Native Agent option to both the + menu and empty state view
- Added necessary dependencies (agent_servers, ui) to agent2 crate
- Added agent2 dependency to agent_ui crate
- Temporarily removed feature flag check for testing

Change summary

Cargo.lock                               |   3 
crates/agent2/Cargo.toml                 |   2 
crates/agent2/src/agent2.rs              |   2 
crates/agent2/src/native_agent_server.rs |  51 ++++++
crates/agent_ui/Cargo.toml               |   1 
crates/agent_ui/src/agent_panel.rs       | 216 +++++++++++++++----------
crates/agent_ui/src/agent_ui.rs          |   2 
7 files changed, 188 insertions(+), 89 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -155,6 +155,7 @@ version = "0.1.0"
 dependencies = [
  "acp_thread",
  "agent-client-protocol",
+ "agent_servers",
  "anyhow",
  "assistant_tool",
  "assistant_tools",
@@ -182,6 +183,7 @@ dependencies = [
  "settings",
  "smol",
  "thiserror 2.0.12",
+ "ui",
  "util",
  "uuid",
  "worktree",
@@ -250,6 +252,7 @@ dependencies = [
  "acp_thread",
  "agent",
  "agent-client-protocol",
+ "agent2",
  "agent_servers",
  "agent_settings",
  "ai_onboarding",

crates/agent2/Cargo.toml 🔗

@@ -14,6 +14,7 @@ workspace = true
 [dependencies]
 acp_thread.workspace = true
 agent-client-protocol.workspace = true
+agent_servers.workspace = true
 anyhow.workspace = true
 assistant_tool.workspace = true
 assistant_tools.workspace = true
@@ -36,6 +37,7 @@ serde_json.workspace = true
 settings.workspace = true
 smol.workspace = true
 thiserror.workspace = true
+ui.workspace = true
 util.workspace = true
 uuid.workspace = true
 worktree.workspace = true

crates/agent2/src/agent2.rs 🔗

@@ -1,4 +1,5 @@
 mod agent;
+mod native_agent_server;
 mod prompts;
 mod templates;
 mod thread;
@@ -8,4 +9,5 @@ mod tools;
 mod tests;
 
 pub use agent::*;
+pub use native_agent_server::NativeAgentServer;
 pub use thread::*;

crates/agent2/src/native_agent_server.rs 🔗

@@ -0,0 +1,51 @@
+use std::path::Path;
+use std::rc::Rc;
+
+use agent_servers::AgentServer;
+use anyhow::Result;
+use gpui::{App, AppContext, Entity, Task};
+use project::Project;
+
+use crate::{templates::Templates, NativeAgent, NativeAgentConnection};
+
+#[derive(Clone)]
+pub struct NativeAgentServer;
+
+impl AgentServer for NativeAgentServer {
+    fn name(&self) -> &'static str {
+        "Native Agent"
+    }
+
+    fn empty_state_headline(&self) -> &'static str {
+        "Native Agent"
+    }
+
+    fn empty_state_message(&self) -> &'static str {
+        "How can I help you today?"
+    }
+
+    fn logo(&self) -> ui::IconName {
+        // Using the ZedAssistant icon as it's the native built-in agent
+        ui::IconName::ZedAssistant
+    }
+
+    fn connect(
+        &self,
+        _root_dir: &Path,
+        _project: &Entity<Project>,
+        cx: &mut App,
+    ) -> Task<Result<Rc<dyn acp_thread::AgentConnection>>> {
+        cx.spawn(async move |cx| {
+            // Create templates (you might want to load these from files or resources)
+            let templates = Templates::new();
+
+            // Create the native agent
+            let agent = cx.update(|cx| cx.new(|_| NativeAgent::new(templates)))?;
+
+            // Create the connection wrapper
+            let connection = NativeAgentConnection(agent);
+
+            Ok(Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>)
+        })
+    }
+}

crates/agent_ui/Cargo.toml 🔗

@@ -19,6 +19,7 @@ test-support = ["gpui/test-support", "language/test-support"]
 acp_thread.workspace = true
 agent-client-protocol.workspace = true
 agent.workspace = true
+agent2.workspace = true
 agent_servers.workspace = true
 agent_settings.workspace = true
 ai_onboarding.workspace = true

crates/agent_ui/src/agent_panel.rs 🔗

@@ -1954,40 +1954,54 @@ impl AgentPanel {
                                     this
                                 }
                             })
-                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
-                                this.separator()
-                                    .header("External Agents")
-                                    .item(
-                                        ContextMenuEntry::new("New Gemini Thread")
-                                            .icon(IconName::AiGemini)
-                                            .icon_color(Color::Muted)
-                                            .handler(move |window, cx| {
-                                                window.dispatch_action(
-                                                    NewExternalAgentThread {
-                                                        agent: Some(crate::ExternalAgent::Gemini),
-                                                    }
-                                                    .boxed_clone(),
-                                                    cx,
-                                                );
-                                            }),
-                                    )
-                                    .item(
-                                        ContextMenuEntry::new("New Claude Code Thread")
-                                            .icon(IconName::AiClaude)
-                                            .icon_color(Color::Muted)
-                                            .handler(move |window, cx| {
-                                                window.dispatch_action(
-                                                    NewExternalAgentThread {
-                                                        agent: Some(
-                                                            crate::ExternalAgent::ClaudeCode,
-                                                        ),
-                                                    }
-                                                    .boxed_clone(),
-                                                    cx,
-                                                );
-                                            }),
-                                    )
-                            });
+                            // Temporarily removed feature flag check for testing
+                            // .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
+                            //     this
+                            .separator()
+                            .header("External Agents")
+                            .item(
+                                ContextMenuEntry::new("New Gemini Thread")
+                                    .icon(IconName::AiGemini)
+                                    .icon_color(Color::Muted)
+                                    .handler(move |window, cx| {
+                                        window.dispatch_action(
+                                            NewExternalAgentThread {
+                                                agent: Some(crate::ExternalAgent::Gemini),
+                                            }
+                                            .boxed_clone(),
+                                            cx,
+                                        );
+                                    }),
+                            )
+                            .item(
+                                ContextMenuEntry::new("New Claude Code Thread")
+                                    .icon(IconName::AiClaude)
+                                    .icon_color(Color::Muted)
+                                    .handler(move |window, cx| {
+                                        window.dispatch_action(
+                                            NewExternalAgentThread {
+                                                agent: Some(crate::ExternalAgent::ClaudeCode),
+                                            }
+                                            .boxed_clone(),
+                                            cx,
+                                        );
+                                    }),
+                            )
+                            .item(
+                                ContextMenuEntry::new("New Native Agent Thread")
+                                    .icon(IconName::ZedAssistant)
+                                    .icon_color(Color::Muted)
+                                    .handler(move |window, cx| {
+                                        window.dispatch_action(
+                                            NewExternalAgentThread {
+                                                agent: Some(crate::ExternalAgent::NativeAgent),
+                                            }
+                                            .boxed_clone(),
+                                            cx,
+                                        );
+                                    }),
+                            );
+                        // });
                         menu
                     }))
                 }
@@ -2594,63 +2608,87 @@ impl AgentPanel {
                                         ),
                                     ),
                             )
-                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
-                                this.child(
-                                    h_flex()
-                                        .w_full()
-                                        .gap_2()
-                                        .child(
-                                            NewThreadButton::new(
-                                                "new-gemini-thread-btn",
-                                                "New Gemini Thread",
-                                                IconName::AiGemini,
-                                            )
-                                            // .keybinding(KeyBinding::for_action_in(
-                                            //     &OpenHistory,
-                                            //     &self.focus_handle(cx),
-                                            //     window,
-                                            //     cx,
-                                            // ))
-                                            .on_click(
-                                                |window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(NewExternalAgentThread {
-                                                            agent: Some(
-                                                                crate::ExternalAgent::Gemini,
-                                                            ),
-                                                        }),
-                                                        cx,
-                                                    )
-                                                },
-                                            ),
+                            // Temporarily removed feature flag check for testing
+                            // .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
+                            //     this
+                            .child(
+                                h_flex()
+                                    .w_full()
+                                    .gap_2()
+                                    .child(
+                                        NewThreadButton::new(
+                                            "new-gemini-thread-btn",
+                                            "New Gemini Thread",
+                                            IconName::AiGemini,
                                         )
-                                        .child(
-                                            NewThreadButton::new(
-                                                "new-claude-thread-btn",
-                                                "New Claude Code Thread",
-                                                IconName::AiClaude,
-                                            )
-                                            // .keybinding(KeyBinding::for_action_in(
-                                            //     &OpenHistory,
-                                            //     &self.focus_handle(cx),
-                                            //     window,
-                                            //     cx,
-                                            // ))
-                                            .on_click(
-                                                |window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(NewExternalAgentThread {
-                                                            agent: Some(
-                                                                crate::ExternalAgent::ClaudeCode,
-                                                            ),
-                                                        }),
-                                                        cx,
-                                                    )
-                                                },
-                                            ),
+                                        // .keybinding(KeyBinding::for_action_in(
+                                        //     &OpenHistory,
+                                        //     &self.focus_handle(cx),
+                                        //     window,
+                                        //     cx,
+                                        // ))
+                                        .on_click(
+                                            |window, cx| {
+                                                window.dispatch_action(
+                                                    Box::new(NewExternalAgentThread {
+                                                        agent: Some(crate::ExternalAgent::Gemini),
+                                                    }),
+                                                    cx,
+                                                )
+                                            },
                                         ),
-                                )
-                            }),
+                                    )
+                                    .child(
+                                        NewThreadButton::new(
+                                            "new-claude-thread-btn",
+                                            "New Claude Code Thread",
+                                            IconName::AiClaude,
+                                        )
+                                        // .keybinding(KeyBinding::for_action_in(
+                                        //     &OpenHistory,
+                                        //     &self.focus_handle(cx),
+                                        //     window,
+                                        //     cx,
+                                        // ))
+                                        .on_click(
+                                            |window, cx| {
+                                                window.dispatch_action(
+                                                    Box::new(NewExternalAgentThread {
+                                                        agent: Some(
+                                                            crate::ExternalAgent::ClaudeCode,
+                                                        ),
+                                                    }),
+                                                    cx,
+                                                )
+                                            },
+                                        ),
+                                    )
+                                    .child(
+                                        NewThreadButton::new(
+                                            "new-native-agent-thread-btn",
+                                            "New Native Agent Thread",
+                                            IconName::ZedAssistant,
+                                        )
+                                        // .keybinding(KeyBinding::for_action_in(
+                                        //     &OpenHistory,
+                                        //     &self.focus_handle(cx),
+                                        //     window,
+                                        //     cx,
+                                        // ))
+                                        .on_click(
+                                            |window, cx| {
+                                                window.dispatch_action(
+                                                    Box::new(NewExternalAgentThread {
+                                                        agent: Some(
+                                                            crate::ExternalAgent::NativeAgent,
+                                                        ),
+                                                    }),
+                                                    cx,
+                                                )
+                                            },
+                                        ),
+                                    ),
+                            ), // })
                     )
                     .when_some(configuration_error.as_ref(), |this, err| {
                         this.child(self.render_configuration_error(err, &focus_handle, window, cx))

crates/agent_ui/src/agent_ui.rs 🔗

@@ -150,6 +150,7 @@ enum ExternalAgent {
     #[default]
     Gemini,
     ClaudeCode,
+    NativeAgent,
 }
 
 impl ExternalAgent {
@@ -157,6 +158,7 @@ impl ExternalAgent {
         match self {
             ExternalAgent::Gemini => Rc::new(agent_servers::Gemini),
             ExternalAgent::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
+            ExternalAgent::NativeAgent => Rc::new(agent2::NativeAgentServer),
         }
     }
 }