Compiling

Agus Zubiaga created

Change summary

Cargo.lock                                 |  16 
crates/acp_thread/src/acp_thread.rs        |   7 
crates/acp_thread/src/connection.rs        |   4 
crates/acp_thread/src/old_acp_support.rs   |  11 
crates/agent_servers/src/acp_connection.rs | 342 ++++++++++-------------
crates/agent_servers/src/agent_servers.rs  |   4 
crates/agent_servers/src/claude.rs         |   6 
crates/agent_servers/src/codex.rs          |  78 -----
crates/agent_servers/src/e2e_tests.rs      |   3 
crates/agent_servers/src/gemini.rs         |  10 
crates/agent_servers/src/mcp_server.rs     | 208 --------------
crates/agent_servers/src/settings.rs       |  11 
crates/agent_ui/src/acp/thread_view.rs     |  15 
crates/agent_ui/src/agent_panel.rs         |  33 --
crates/agent_ui/src/agent_ui.rs            |   2 
15 files changed, 185 insertions(+), 565 deletions(-)

Detailed changes

Cargo.lock 🔗

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

crates/acp_thread/src/acp_thread.rs 🔗

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

crates/acp_thread/src/connection.rs 🔗

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

crates/acp_thread/src/old_acp_support.rs 🔗

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

crates/agent_servers/src/acp_connection.rs 🔗

@@ -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(&notification).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(&notification.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(&params.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(&notification.session_id)
+            .context("Failed to get session")?;
+
+        session.thread.update(cx, |thread, cx| {
+            thread.handle_session_update(notification.update, cx)
+        })??;
+
+        Ok(())
     }
 }

crates/agent_servers/src/agent_servers.rs 🔗

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

crates/agent_servers/src/claude.rs 🔗

@@ -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(&params.session_id) else {
             return Task::ready(Err(anyhow!(

crates/agent_servers/src/codex.rs 🔗

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

crates/agent_servers/src/e2e_tests.rs 🔗

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

crates/agent_servers/src/gemini.rs 🔗

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

crates/agent_servers/src/mcp_server.rs 🔗

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

crates/agent_servers/src/settings.rs 🔗

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

crates/agent_ui/src/acp/thread_view.rs 🔗

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

crates/agent_ui/src/agent_panel.rs 🔗

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

crates/agent_ui/src/agent_ui.rs 🔗

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