Add Native Agent to UI and implement NativeAgentServer

- 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
This commit is contained in:
Nathan Sobo 2025-08-02 09:17:26 -06:00
parent 4f2d6a9ea9
commit bc1f861d3f
7 changed files with 188 additions and 89 deletions

3
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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::*;

View file

@ -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>)
})
}
}

View file

@ -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

View file

@ -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))

View file

@ -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),
}
}
}