Detailed changes
@@ -140,6 +140,10 @@ dependencies = [
name = "agent-client-protocol"
version = "0.0.13"
dependencies = [
+ "anyhow",
+ "futures 0.3.31",
+ "log",
+ "parking_lot",
"schemars",
"serde",
"serde_json",
@@ -9624,9 +9628,9 @@ dependencies = [
[[package]]
name = "lock_api"
-version = "0.4.12"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
@@ -11315,9 +11319,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -11325,9 +11329,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.10"
+version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
@@ -999,7 +999,7 @@ impl AcpThread {
let result = this
.update(cx, |this, cx| {
this.connection.prompt(
- acp::PromptArguments {
+ acp::PromptRequest {
prompt: message,
session_id: this.session_id.clone(),
},
@@ -1595,6 +1595,11 @@ mod tests {
connection,
child_status: io_task,
current_thread: thread_rc,
+ auth_methods: [acp::AuthMethod {
+ id: acp::AuthMethodId("acp-old-no-id".into()),
+ label: "Log in".into(),
+ description: None,
+ }],
};
AcpThread::new(
@@ -16,11 +16,11 @@ pub trait AgentConnection {
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>>;
- fn auth_methods(&self) -> Vec<acp::AuthMethod>;
+ fn auth_methods(&self) -> &[acp::AuthMethod];
fn authenticate(&self, method: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>>;
- fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>>;
+ fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>>;
fn cancel(&self, session_id: &acp::SessionId, cx: &mut App);
}
@@ -356,6 +356,7 @@ pub struct OldAcpAgentConnection {
pub connection: acp_old::AgentConnection,
pub child_status: Task<Result<()>>,
pub current_thread: Rc<RefCell<WeakEntity<AcpThread>>>,
+ pub auth_methods: [acp::AuthMethod; 1],
}
impl AgentConnection for OldAcpAgentConnection {
@@ -391,12 +392,8 @@ impl AgentConnection for OldAcpAgentConnection {
})
}
- fn auth_methods(&self) -> Vec<acp::AuthMethod> {
- vec![acp::AuthMethod {
- id: acp::AuthMethodId("acp-old-no-id".into()),
- label: "Log in".into(),
- description: None,
- }]
+ fn auth_methods(&self) -> &[acp::AuthMethod] {
+ &self.auth_methods
}
fn authenticate(&self, _method_id: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>> {
@@ -409,7 +406,7 @@ impl AgentConnection for OldAcpAgentConnection {
})
}
- fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
+ fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>> {
let chunks = params
.prompt
.into_iter()
@@ -1,123 +1,87 @@
use agent_client_protocol as acp;
-use anyhow::anyhow;
use collections::HashMap;
-use context_server::listener::McpServerTool;
-use context_server::types::requests;
-use context_server::{ContextServer, ContextServerCommand, ContextServerId};
-use futures::channel::{mpsc, oneshot};
+use futures::channel::oneshot;
use project::Project;
-use smol::stream::StreamExt as _;
use std::cell::RefCell;
+use std::path::Path;
use std::rc::Rc;
-use std::{path::Path, sync::Arc};
use util::ResultExt;
-use anyhow::{Context, Result};
+use anyhow::{Context as _, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task, WeakEntity};
-use crate::mcp_server::ZedMcpServer;
-use crate::{AgentServerCommand, mcp_server};
+use crate::AgentServerCommand;
use acp_thread::{AcpThread, AgentConnection, AuthRequired};
pub struct AcpConnection {
- auth_methods: Rc<RefCell<Vec<acp::AuthMethod>>>,
server_name: &'static str,
- context_server: Arc<context_server::ContextServer>,
+ connection: Rc<acp::AgentConnection>,
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
- _session_update_task: Task<()>,
+ auth_methods: Vec<acp::AuthMethod>,
+ _io_task: Task<Result<()>>,
+}
+
+pub struct AcpSession {
+ thread: WeakEntity<AcpThread>,
}
impl AcpConnection {
pub async fn stdio(
server_name: &'static str,
command: AgentServerCommand,
- working_directory: Option<Arc<Path>>,
+ root_dir: &Path,
cx: &mut AsyncApp,
) -> Result<Self> {
- let context_server: Arc<ContextServer> = ContextServer::stdio(
- ContextServerId(format!("{}-mcp-server", server_name).into()),
- ContextServerCommand {
- path: command.path,
- args: command.args,
- env: command.env,
- },
- working_directory,
- )
- .into();
-
- let (notification_tx, mut notification_rx) = mpsc::unbounded();
+ let mut child = util::command::new_smol_command(&command.path)
+ .args(command.args.iter().map(|arg| arg.as_str()))
+ .envs(command.env.iter().flatten())
+ .current_dir(root_dir)
+ .stdin(std::process::Stdio::piped())
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::inherit())
+ .kill_on_drop(true)
+ .spawn()?;
+
+ let stdout = child.stdout.take().expect("Failed to take stdout");
+ let stdin = child.stdin.take().expect("Failed to take stdin");
let sessions = Rc::new(RefCell::new(HashMap::default()));
- let session_update_handler_task = cx.spawn({
- let sessions = sessions.clone();
- async move |cx| {
- while let Some(notification) = notification_rx.next().await {
- Self::handle_session_notification(notification, sessions.clone(), cx)
- }
+ let client = ClientDelegate {
+ sessions: sessions.clone(),
+ cx: cx.clone(),
+ };
+ let (connection, io_task) = acp::AgentConnection::new(client, stdin, stdout, {
+ let foreground_executor = cx.foreground_executor().clone();
+ move |fut| {
+ foreground_executor.spawn(fut).detach();
}
});
- context_server
- .start_with_handlers(
- vec![(acp::AGENT_METHODS.session_update, {
- Box::new(move |notification, _cx| {
- let notification_tx = notification_tx.clone();
- log::trace!(
- "ACP Notification: {}",
- serde_json::to_string_pretty(¬ification).unwrap()
- );
-
- if let Some(notification) =
- serde_json::from_value::<acp::SessionNotification>(notification)
- .log_err()
- {
- notification_tx.unbounded_send(notification).ok();
- }
- })
- })],
- cx,
- )
+ let io_task = cx.background_spawn(io_task);
+
+ let response = connection
+ .initialize(acp::InitializeRequest {
+ protocol_version: acp::VERSION,
+ client_capabilities: acp::ClientCapabilities {
+ fs: acp::FileSystemCapability {
+ read_text_file: true,
+ write_text_file: true,
+ },
+ },
+ })
.await?;
+ // todo! check version
+
Ok(Self {
- auth_methods: Default::default(),
+ auth_methods: response.auth_methods,
+ connection: connection.into(),
server_name,
- context_server,
sessions,
- _session_update_task: session_update_handler_task,
+ _io_task: io_task,
})
}
-
- pub fn handle_session_notification(
- notification: acp::SessionNotification,
- threads: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
- cx: &mut AsyncApp,
- ) {
- let threads = threads.borrow();
- let Some(thread) = threads
- .get(¬ification.session_id)
- .and_then(|session| session.thread.upgrade())
- else {
- log::error!(
- "Thread not found for session ID: {}",
- notification.session_id
- );
- return;
- };
-
- thread
- .update(cx, |thread, cx| {
- thread.handle_session_update(notification.update, cx)
- })
- .log_err();
- }
-}
-
-pub struct AcpSession {
- thread: WeakEntity<AcpThread>,
- cancel_tx: Option<oneshot::Sender<()>>,
- _mcp_server: ZedMcpServer,
}
impl AgentConnection for AcpConnection {
@@ -127,52 +91,19 @@ impl AgentConnection for AcpConnection {
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
- let client = self.context_server.client();
+ let conn = self.connection.clone();
let sessions = self.sessions.clone();
- let auth_methods = self.auth_methods.clone();
let cwd = cwd.to_path_buf();
cx.spawn(async move |cx| {
- let client = client.context("MCP server is not initialized yet")?;
- let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
-
- let mcp_server = ZedMcpServer::new(thread_rx, cx).await?;
-
- let response = client
- .request::<requests::CallTool>(context_server::types::CallToolParams {
- name: acp::AGENT_METHODS.new_session.into(),
- arguments: Some(serde_json::to_value(acp::NewSessionArguments {
- mcp_servers: vec![mcp_server.server_config()?],
- client_tools: acp::ClientTools {
- request_permission: Some(acp::McpToolId {
- mcp_server: mcp_server::SERVER_NAME.into(),
- tool_name: mcp_server::RequestPermissionTool::NAME.into(),
- }),
- read_text_file: Some(acp::McpToolId {
- mcp_server: mcp_server::SERVER_NAME.into(),
- tool_name: mcp_server::ReadTextFileTool::NAME.into(),
- }),
- write_text_file: Some(acp::McpToolId {
- mcp_server: mcp_server::SERVER_NAME.into(),
- tool_name: mcp_server::WriteTextFileTool::NAME.into(),
- }),
- },
- cwd,
- })?),
- meta: None,
+ let response = conn
+ .new_session(acp::NewSessionRequest {
+ // todo! Zed MCP server?
+ mcp_servers: vec![],
+ cwd,
})
.await?;
- if response.is_error.unwrap_or_default() {
- return Err(anyhow!(response.text_contents()));
- }
-
- let result = serde_json::from_value::<acp::NewSessionOutput>(
- response.structured_content.context("Empty response")?,
- )?;
-
- auth_methods.replace(result.auth_methods);
-
- let Some(session_id) = result.session_id else {
+ let Some(session_id) = response.session_id else {
anyhow::bail!(AuthRequired);
};
@@ -186,12 +117,8 @@ impl AgentConnection for AcpConnection {
)
})?;
- thread_tx.send(thread.downgrade())?;
-
let session = AcpSession {
thread: thread.downgrade(),
- cancel_tx: None,
- _mcp_server: mcp_server,
};
sessions.borrow_mut().insert(session_id, session);
@@ -199,94 +126,115 @@ impl AgentConnection for AcpConnection {
})
}
- fn auth_methods(&self) -> Vec<agent_client_protocol::AuthMethod> {
- self.auth_methods.borrow().clone()
+ fn auth_methods(&self) -> &[acp::AuthMethod] {
+ &self.auth_methods
}
fn authenticate(&self, method_id: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>> {
- let client = self.context_server.client();
+ let conn = self.connection.clone();
cx.foreground_executor().spawn(async move {
- let params = acp::AuthenticateArguments { method_id };
-
- let response = client
- .context("MCP server is not initialized yet")?
- .request::<requests::CallTool>(context_server::types::CallToolParams {
- name: acp::AGENT_METHODS.authenticate.into(),
- arguments: Some(serde_json::to_value(params)?),
- meta: None,
+ let result = conn
+ .authenticate(acp::AuthenticateRequest {
+ method_id: method_id.clone(),
})
.await?;
- if response.is_error.unwrap_or_default() {
- Err(anyhow!(response.text_contents()))
- } else {
- Ok(())
- }
+ Ok(result)
})
}
- fn prompt(
- &self,
- params: agent_client_protocol::PromptArguments,
- cx: &mut App,
- ) -> Task<Result<()>> {
- let client = self.context_server.client();
- let sessions = self.sessions.clone();
+ fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>> {
+ let conn = self.connection.clone();
+ cx.foreground_executor()
+ .spawn(async move { Ok(conn.prompt(params).await?) })
+ }
- cx.foreground_executor().spawn(async move {
- let client = client.context("MCP server is not initialized yet")?;
-
- let (new_cancel_tx, cancel_rx) = oneshot::channel();
- {
- let mut sessions = sessions.borrow_mut();
- let session = sessions
- .get_mut(¶ms.session_id)
- .context("Session not found")?;
- session.cancel_tx.replace(new_cancel_tx);
- }
+ fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) {
+ self.connection.cancel(session_id.clone()).log_err();
+ }
+}
- let result = client
- .request_with::<requests::CallTool>(
- context_server::types::CallToolParams {
- name: acp::AGENT_METHODS.prompt.into(),
- arguments: Some(serde_json::to_value(params)?),
- meta: None,
- },
- Some(cancel_rx),
- None,
- )
- .await;
+struct ClientDelegate {
+ sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
+ cx: AsyncApp,
+}
- if let Err(err) = &result
- && err.is::<context_server::client::RequestCanceled>()
- {
- return Ok(());
- }
+impl acp::Client for ClientDelegate {
+ async fn request_permission(
+ &self,
+ arguments: acp::RequestPermissionRequest,
+ ) -> Result<acp::RequestPermissionResponse, acp::Error> {
+ let cx = &mut self.cx.clone();
+ let result = self
+ .sessions
+ .borrow()
+ .get(&arguments.session_id)
+ .context("Failed to get session")?
+ .thread
+ .update(cx, |thread, cx| {
+ thread.request_tool_call_permission(arguments.tool_call, arguments.options, cx)
+ })?
+ .await;
- let response = result?;
+ let outcome = match result {
+ Ok(option) => acp::RequestPermissionOutcome::Selected { option_id: option },
+ Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Cancelled,
+ };
- if response.is_error.unwrap_or_default() {
- return Err(anyhow!(response.text_contents()));
- }
+ Ok(acp::RequestPermissionResponse { outcome })
+ }
- Ok(())
- })
+ async fn write_text_file(
+ &self,
+ arguments: acp::WriteTextFileRequest,
+ ) -> Result<(), acp::Error> {
+ let cx = &mut self.cx.clone();
+ self.sessions
+ .borrow()
+ .get(&arguments.session_id)
+ .context("Failed to get session")?
+ .thread
+ .update(cx, |thread, cx| {
+ thread.write_text_file(arguments.path, arguments.content, cx)
+ })?
+ .await?;
+
+ Ok(())
}
- fn cancel(&self, session_id: &agent_client_protocol::SessionId, _cx: &mut App) {
- let mut sessions = self.sessions.borrow_mut();
+ async fn read_text_file(
+ &self,
+ arguments: acp::ReadTextFileRequest,
+ ) -> Result<acp::ReadTextFileResponse, acp::Error> {
+ let cx = &mut self.cx.clone();
+ let content = self
+ .sessions
+ .borrow()
+ .get(&arguments.session_id)
+ .context("Failed to get session")?
+ .thread
+ .update(cx, |thread, cx| {
+ thread.read_text_file(arguments.path, arguments.line, arguments.limit, false, cx)
+ })?
+ .await?;
- if let Some(cancel_tx) = sessions
- .get_mut(session_id)
- .and_then(|session| session.cancel_tx.take())
- {
- cancel_tx.send(()).ok();
- }
+ Ok(acp::ReadTextFileResponse { content })
}
-}
-impl Drop for AcpConnection {
- fn drop(&mut self) {
- self.context_server.stop().log_err();
+ async fn session_notification(
+ &self,
+ notification: acp::SessionNotification,
+ ) -> Result<(), acp::Error> {
+ let cx = &mut self.cx.clone();
+ let sessions = self.sessions.borrow();
+ let session = sessions
+ .get(¬ification.session_id)
+ .context("Failed to get session")?;
+
+ session.thread.update(cx, |thread, cx| {
+ thread.handle_session_update(notification.update, cx)
+ })??;
+
+ Ok(())
}
}
@@ -1,15 +1,12 @@
mod acp_connection;
mod claude;
-mod codex;
mod gemini;
-mod mcp_server;
mod settings;
#[cfg(test)]
mod e2e_tests;
pub use claude::*;
-pub use codex::*;
pub use gemini::*;
pub use settings::*;
@@ -39,7 +36,6 @@ pub trait AgentServer: Send {
fn connect(
&self,
- // these will go away when old_acp is fully removed
root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
@@ -183,15 +183,15 @@ impl AgentConnection for ClaudeAgentConnection {
})
}
- fn auth_methods(&self) -> Vec<acp::AuthMethod> {
- vec![]
+ fn auth_methods(&self) -> &[acp::AuthMethod] {
+ &[]
}
fn authenticate(&self, _: acp::AuthMethodId, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Authentication not supported")))
}
- fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
+ fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>> {
let sessions = self.sessions.borrow();
let Some(session) = sessions.get(¶ms.session_id) else {
return Task::ready(Err(anyhow!(
@@ -1,78 +0,0 @@
-use project::Project;
-use settings::SettingsStore;
-use std::path::Path;
-use std::rc::Rc;
-
-use anyhow::Result;
-use gpui::{App, Entity, Task};
-
-use crate::acp_connection::AcpConnection;
-use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings};
-use acp_thread::AgentConnection;
-
-#[derive(Clone)]
-pub struct Codex;
-
-impl AgentServer for Codex {
- fn name(&self) -> &'static str {
- "Codex"
- }
-
- fn empty_state_headline(&self) -> &'static str {
- "Welcome to Codex"
- }
-
- fn empty_state_message(&self) -> &'static str {
- "What can I help with?"
- }
-
- fn logo(&self) -> ui::IconName {
- ui::IconName::AiOpenAi
- }
-
- fn connect(
- &self,
- _root_dir: &Path,
- project: &Entity<Project>,
- cx: &mut App,
- ) -> Task<Result<Rc<dyn AgentConnection>>> {
- let project = project.clone();
- let server_name = self.name();
- let working_directory = project.read(cx).active_project_directory(cx);
- cx.spawn(async move |cx| {
- let settings = cx.read_global(|settings: &SettingsStore, _| {
- settings.get::<AllAgentServersSettings>(None).codex.clone()
- })?;
-
- let Some(command) =
- AgentServerCommand::resolve("codex", &["mcp"], settings, &project, cx).await
- else {
- anyhow::bail!("Failed to find codex binary");
- };
- // todo! check supported version
-
- let conn = AcpConnection::stdio(server_name, command, working_directory, cx).await?;
- Ok(Rc::new(conn) as _)
- })
- }
-}
-
-#[cfg(test)]
-pub(crate) mod tests {
- use super::*;
- use crate::AgentServerCommand;
- use std::path::Path;
-
- crate::common_e2e_tests!(Codex, allow_option_id = "approve");
-
- pub fn local_command() -> AgentServerCommand {
- let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("../../../codex/codex-rs/target/debug/codex");
-
- AgentServerCommand {
- path: cli_path,
- args: vec![],
- env: None,
- }
- }
-}
@@ -375,9 +375,6 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
gemini: Some(AgentServerSettings {
command: crate::gemini::tests::local_command(),
}),
- codex: Some(AgentServerSettings {
- command: crate::codex::tests::local_command(),
- }),
},
cx,
);
@@ -14,7 +14,7 @@ use crate::AllAgentServersSettings;
#[derive(Clone)]
pub struct Gemini;
-const MCP_ARG: &str = "--experimental-mcp";
+const ACP_ARG: &str = "--experimental-acp";
impl AgentServer for Gemini {
fn name(&self) -> &'static str {
@@ -35,26 +35,26 @@ impl AgentServer for Gemini {
fn connect(
&self,
- _root_dir: &Path,
+ root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let project = project.clone();
let server_name = self.name();
- let working_directory = project.read(cx).active_project_directory(cx);
+ let root_dir = root_dir.to_path_buf();
cx.spawn(async move |cx| {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).gemini.clone()
})?;
let Some(command) =
- AgentServerCommand::resolve("gemini", &[MCP_ARG], settings, &project, cx).await
+ AgentServerCommand::resolve("gemini", &[ACP_ARG], settings, &project, cx).await
else {
anyhow::bail!("Failed to find gemini binary");
};
// todo! check supported version
- let conn = AcpConnection::stdio(server_name, command, working_directory, cx).await?;
+ let conn = AcpConnection::stdio(server_name, command, &root_dir, cx).await?;
Ok(Rc::new(conn) as _)
})
}
@@ -1,208 +0,0 @@
-use acp_thread::AcpThread;
-use agent_client_protocol as acp;
-use anyhow::Result;
-use context_server::listener::{McpServerTool, ToolResponse};
-use context_server::types::{
- Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities,
- ToolsCapabilities, requests,
-};
-use futures::channel::oneshot;
-use gpui::{App, AsyncApp, Task, WeakEntity};
-use indoc::indoc;
-
-pub struct ZedMcpServer {
- server: context_server::listener::McpServer,
-}
-
-pub const SERVER_NAME: &str = "zed";
-
-impl ZedMcpServer {
- pub async fn new(
- thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
- cx: &AsyncApp,
- ) -> Result<Self> {
- let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
- mcp_server.handle_request::<requests::Initialize>(Self::handle_initialize);
-
- mcp_server.add_tool(RequestPermissionTool {
- thread_rx: thread_rx.clone(),
- });
- mcp_server.add_tool(ReadTextFileTool {
- thread_rx: thread_rx.clone(),
- });
- mcp_server.add_tool(WriteTextFileTool {
- thread_rx: thread_rx.clone(),
- });
-
- Ok(Self { server: mcp_server })
- }
-
- pub fn server_config(&self) -> Result<acp::McpServer> {
- #[cfg(not(test))]
- let zed_path = anyhow::Context::context(
- std::env::current_exe(),
- "finding current executable path for use in mcp_server",
- )?;
-
- #[cfg(test)]
- let zed_path = crate::e2e_tests::get_zed_path();
-
- Ok(acp::McpServer {
- name: SERVER_NAME.into(),
- command: zed_path,
- args: vec![
- "--nc".into(),
- self.server.socket_path().display().to_string(),
- ],
- env: vec![],
- })
- }
-
- fn handle_initialize(_: InitializeParams, cx: &App) -> Task<Result<InitializeResponse>> {
- cx.foreground_executor().spawn(async move {
- Ok(InitializeResponse {
- protocol_version: ProtocolVersion("2025-06-18".into()),
- capabilities: ServerCapabilities {
- experimental: None,
- logging: None,
- completions: None,
- prompts: None,
- resources: None,
- tools: Some(ToolsCapabilities {
- list_changed: Some(false),
- }),
- },
- server_info: Implementation {
- name: SERVER_NAME.into(),
- version: "0.1.0".into(),
- },
- meta: None,
- })
- })
- }
-}
-
-// Tools
-
-#[derive(Clone)]
-pub struct RequestPermissionTool {
- thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
-}
-
-impl McpServerTool for RequestPermissionTool {
- type Input = acp::RequestPermissionArguments;
- type Output = acp::RequestPermissionOutput;
-
- const NAME: &'static str = "Confirmation";
-
- fn description(&self) -> &'static str {
- indoc! {"
- Request permission for tool calls.
-
- This tool is meant to be called programmatically by the agent loop, not the LLM.
- "}
- }
-
- async fn run(
- &self,
- input: Self::Input,
- cx: &mut AsyncApp,
- ) -> Result<ToolResponse<Self::Output>> {
- let mut thread_rx = self.thread_rx.clone();
- let Some(thread) = thread_rx.recv().await?.upgrade() else {
- anyhow::bail!("Thread closed");
- };
-
- let result = thread
- .update(cx, |thread, cx| {
- thread.request_tool_call_permission(input.tool_call, input.options, cx)
- })?
- .await;
-
- let outcome = match result {
- Ok(option_id) => acp::RequestPermissionOutcome::Selected { option_id },
- Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Canceled,
- };
-
- Ok(ToolResponse {
- content: vec![],
- structured_content: acp::RequestPermissionOutput { outcome },
- })
- }
-}
-
-#[derive(Clone)]
-pub struct ReadTextFileTool {
- thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
-}
-
-impl McpServerTool for ReadTextFileTool {
- type Input = acp::ReadTextFileArguments;
- type Output = acp::ReadTextFileOutput;
-
- const NAME: &'static str = "Read";
-
- fn description(&self) -> &'static str {
- "Reads the content of the given file in the project including unsaved changes."
- }
-
- async fn run(
- &self,
- input: Self::Input,
- cx: &mut AsyncApp,
- ) -> Result<ToolResponse<Self::Output>> {
- let mut thread_rx = self.thread_rx.clone();
- let Some(thread) = thread_rx.recv().await?.upgrade() else {
- anyhow::bail!("Thread closed");
- };
-
- let content = thread
- .update(cx, |thread, cx| {
- thread.read_text_file(input.path, input.line, input.limit, false, cx)
- })?
- .await?;
-
- Ok(ToolResponse {
- content: vec![],
- structured_content: acp::ReadTextFileOutput { content },
- })
- }
-}
-
-#[derive(Clone)]
-pub struct WriteTextFileTool {
- thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
-}
-
-impl McpServerTool for WriteTextFileTool {
- type Input = acp::WriteTextFileArguments;
- type Output = ();
-
- const NAME: &'static str = "Write";
-
- fn description(&self) -> &'static str {
- "Write to a file replacing its contents"
- }
-
- async fn run(
- &self,
- input: Self::Input,
- cx: &mut AsyncApp,
- ) -> Result<ToolResponse<Self::Output>> {
- let mut thread_rx = self.thread_rx.clone();
- let Some(thread) = thread_rx.recv().await?.upgrade() else {
- anyhow::bail!("Thread closed");
- };
-
- thread
- .update(cx, |thread, cx| {
- thread.write_text_file(input.path, input.content, cx)
- })?
- .await?;
-
- Ok(ToolResponse {
- content: vec![],
- structured_content: (),
- })
- }
-}
@@ -13,7 +13,6 @@ pub fn init(cx: &mut App) {
pub struct AllAgentServersSettings {
pub gemini: Option<AgentServerSettings>,
pub claude: Option<AgentServerSettings>,
- pub codex: Option<AgentServerSettings>,
}
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
@@ -30,21 +29,13 @@ impl settings::Settings for AllAgentServersSettings {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let mut settings = AllAgentServersSettings::default();
- for AllAgentServersSettings {
- gemini,
- claude,
- codex,
- } in sources.defaults_and_customizations()
- {
+ for AllAgentServersSettings { gemini, claude } in sources.defaults_and_customizations() {
if gemini.is_some() {
settings.gemini = gemini.clone();
}
if claude.is_some() {
settings.claude = claude.clone();
}
- if codex.is_some() {
- settings.codex = codex.clone();
- }
}
Ok(settings)
@@ -2211,13 +2211,16 @@ impl Render for AcpThreadView {
.child(self.render_pending_auth_state())
.child(h_flex().mt_1p5().justify_center().children(
connection.auth_methods().into_iter().map(|method| {
- Button::new(SharedString::from(method.id.0.clone()), method.label)
- .on_click({
- let method_id = method.id.clone();
- cx.listener(move |this, _, window, cx| {
- this.authenticate(method_id.clone(), window, cx)
- })
+ Button::new(
+ SharedString::from(method.id.0.clone()),
+ method.label.clone(),
+ )
+ .on_click({
+ let method_id = method.id.clone();
+ cx.listener(move |this, _, window, cx| {
+ this.authenticate(method_id.clone(), window, cx)
})
+ })
}),
)),
ThreadState::Loading { .. } => v_flex().flex_1().child(self.render_empty_state(cx)),
@@ -1991,20 +1991,6 @@ impl AgentPanel {
);
}),
)
- .item(
- ContextMenuEntry::new("New Codex Thread")
- .icon(IconName::AiOpenAi)
- .icon_color(Color::Muted)
- .handler(move |window, cx| {
- window.dispatch_action(
- NewExternalAgentThread {
- agent: Some(crate::ExternalAgent::Codex),
- }
- .boxed_clone(),
- cx,
- );
- }),
- )
});
menu
}))
@@ -2666,25 +2652,6 @@ impl AgentPanel {
)
},
),
- )
- .child(
- NewThreadButton::new(
- "new-codex-thread-btn",
- "New Codex Thread",
- IconName::AiOpenAi,
- )
- .on_click(
- |window, cx| {
- window.dispatch_action(
- Box::new(NewExternalAgentThread {
- agent: Some(
- crate::ExternalAgent::Codex,
- ),
- }),
- cx,
- )
- },
- ),
),
)
}),
@@ -150,7 +150,6 @@ enum ExternalAgent {
#[default]
Gemini,
ClaudeCode,
- Codex,
}
impl ExternalAgent {
@@ -158,7 +157,6 @@ impl ExternalAgent {
match self {
ExternalAgent::Gemini => Rc::new(agent_servers::Gemini),
ExternalAgent::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
- ExternalAgent::Codex => Rc::new(agent_servers::Codex),
}
}
}