Checkpoint: Wiring up acp crate

Agus Zubiaga , Conrad Irwin , Ben Brandt , and Max created

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by:
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Max <max@zed.dev>

Change summary

Cargo.lock                  | 166 ++++++++++++++++++++++++--------------
crates/agent2/Cargo.toml    |   9 ++
crates/agent2/src/acp.rs    | 105 ++++++++++++++++++++++++
crates/agent2/src/agent2.rs |  56 +++++++++++--
4 files changed, 267 insertions(+), 69 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -86,7 +86,7 @@ dependencies = [
  "rand 0.8.5",
  "ref-cast",
  "rope",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -109,12 +109,18 @@ dependencies = [
 name = "agent2"
 version = "0.1.0"
 dependencies = [
+ "agentic-coding-protocol",
  "anyhow",
+ "async-trait",
  "chrono",
+ "collections",
  "futures 0.3.31",
  "gpui",
+ "language",
  "project",
  "serde_json",
+ "settings",
+ "smol",
  "util",
  "uuid",
  "workspace-hack",
@@ -137,7 +143,7 @@ dependencies = [
  "ollama",
  "open_ai",
  "paths",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "serde_json_lenient",
@@ -203,7 +209,7 @@ dependencies = [
  "release_channel",
  "rope",
  "rules_library",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_json",
@@ -232,6 +238,20 @@ dependencies = [
  "zed_llm_client",
 ]
 
+[[package]]
+name = "agentic-coding-protocol"
+version = "0.0.1"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "chrono",
+ "futures 0.3.31",
+ "parking_lot",
+ "schemars 1.0.1",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "ahash"
 version = "0.7.8"
@@ -428,7 +448,7 @@ dependencies = [
  "chrono",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "strum 0.27.1",
@@ -755,7 +775,7 @@ dependencies = [
  "regex",
  "reqwest_client",
  "rust-embed",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -1216,7 +1236,7 @@ dependencies = [
  "log",
  "paths",
  "release_channel",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -1926,7 +1946,7 @@ dependencies = [
  "aws-sdk-bedrockruntime",
  "aws-smithy-types",
  "futures 0.3.31",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "strum 0.27.1",
@@ -2440,7 +2460,7 @@ dependencies = [
  "log",
  "postage",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "settings",
@@ -2896,7 +2916,7 @@ dependencies = [
  "release_channel",
  "rpc",
  "rustls-pki-types",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -3171,7 +3191,7 @@ dependencies = [
  "release_channel",
  "rich_text",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "serde_json",
@@ -3360,7 +3380,7 @@ dependencies = [
  "log",
  "parking_lot",
  "postage",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "smol",
@@ -4135,7 +4155,7 @@ dependencies = [
  "parking_lot",
  "paths",
  "proto",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -4155,7 +4175,7 @@ name = "dap-types"
 version = "0.0.1"
 source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308"
 dependencies = [
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
 ]
@@ -4380,7 +4400,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "workspace-hack",
@@ -4833,7 +4853,7 @@ dependencies = [
  "rand 0.8.5",
  "release_channel",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -5307,7 +5327,7 @@ dependencies = [
  "release_channel",
  "remote",
  "reqwest_client",
- "schemars",
+ "schemars 0.8.22",
  "semantic_version",
  "serde",
  "serde_json",
@@ -5508,7 +5528,7 @@ dependencies = [
  "picker",
  "pretty_assertions",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_derive",
@@ -6169,7 +6189,7 @@ dependencies = [
  "pretty_assertions",
  "regex",
  "rope",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "smol",
@@ -6211,7 +6231,7 @@ dependencies = [
  "indoc",
  "pretty_assertions",
  "regex",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -6254,7 +6274,7 @@ dependencies = [
  "postage",
  "pretty_assertions",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "serde_json",
@@ -7091,7 +7111,7 @@ dependencies = [
  "menu",
  "project",
  "rope",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -7112,7 +7132,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "strum 0.27.1",
@@ -7213,7 +7233,7 @@ dependencies = [
  "reqwest_client",
  "resvg",
  "scap",
- "schemars",
+ "schemars 0.8.22",
  "seahash",
  "semantic_version",
  "serde",
@@ -8121,7 +8141,7 @@ dependencies = [
  "language",
  "log",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "theme",
@@ -8678,7 +8698,7 @@ dependencies = [
  "editor",
  "gpui",
  "log",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "shellexpand 2.1.2",
@@ -8873,7 +8893,7 @@ dependencies = [
  "rand 0.8.5",
  "regex",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -8941,7 +8961,7 @@ dependencies = [
  "log",
  "parking_lot",
  "proto",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "smol",
@@ -8987,7 +9007,7 @@ dependencies = [
  "project",
  "proto",
  "release_channel",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -9083,7 +9103,7 @@ dependencies = [
  "regex",
  "rope",
  "rust-embed",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "serde_json_lenient",
@@ -9474,7 +9494,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "workspace-hack",
@@ -9580,7 +9600,7 @@ dependencies = [
  "parking_lot",
  "postage",
  "release_channel",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "smol",
@@ -10039,7 +10059,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "strum 0.27.1",
@@ -10822,7 +10842,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "workspace-hack",
@@ -10893,7 +10913,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "strum 0.27.1",
@@ -10907,7 +10927,7 @@ dependencies = [
  "anyhow",
  "futures 0.3.31",
  "http_client",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "workspace-hack",
@@ -11083,7 +11103,7 @@ dependencies = [
  "outline",
  "pretty_assertions",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_json",
@@ -11856,7 +11876,7 @@ dependencies = [
  "env_logger 0.11.8",
  "gpui",
  "menu",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "ui",
@@ -12285,7 +12305,7 @@ dependencies = [
  "release_channel",
  "remote",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -12328,7 +12348,7 @@ dependencies = [
  "menu",
  "pretty_assertions",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_derive",
@@ -12984,7 +13004,7 @@ dependencies = [
  "project",
  "release_channel",
  "remote",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -13172,7 +13192,7 @@ dependencies = [
  "prost 0.9.0",
  "release_channel",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "shlex",
@@ -13287,7 +13307,7 @@ dependencies = [
  "picker",
  "project",
  "runtimelib",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -14057,7 +14077,7 @@ dependencies = [
  "anyhow",
  "clap",
  "env_logger 0.11.8",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "theme",
@@ -14072,7 +14092,21 @@ checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
 dependencies = [
  "dyn-clone",
  "indexmap",
- "schemars_derive",
+ "schemars_derive 0.8.22",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
+dependencies = [
+ "chrono",
+ "dyn-clone",
+ "ref-cast",
+ "schemars_derive 1.0.1",
  "serde",
  "serde_json",
 ]
@@ -14089,6 +14123,18 @@ dependencies = [
  "syn 2.0.101",
 ]
 
+[[package]]
+name = "schemars_derive"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.101",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.1"
@@ -14261,7 +14307,7 @@ dependencies = [
  "language",
  "menu",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -14562,7 +14608,7 @@ dependencies = [
  "pretty_assertions",
  "release_channel",
  "rust-embed",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "serde_json",
@@ -14586,7 +14632,7 @@ dependencies = [
  "fs",
  "gpui",
  "log",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "theme",
@@ -14890,7 +14936,7 @@ dependencies = [
  "indoc",
  "parking_lot",
  "paths",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json_lenient",
  "snippet",
@@ -15726,7 +15772,7 @@ dependencies = [
  "menu",
  "picker",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -15807,7 +15853,7 @@ dependencies = [
  "parking_lot",
  "pretty_assertions",
  "proto",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "serde_json_lenient",
@@ -15913,7 +15959,7 @@ dependencies = [
  "rand 0.8.5",
  "regex",
  "release_channel",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "settings",
@@ -15960,7 +16006,7 @@ dependencies = [
  "project",
  "rand 0.8.5",
  "regex",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_json",
@@ -16015,7 +16061,7 @@ dependencies = [
  "palette",
  "parking_lot",
  "refineable",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_derive",
  "serde_json",
@@ -16302,7 +16348,7 @@ dependencies = [
  "project",
  "remote",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "smallvec",
@@ -17480,7 +17526,7 @@ dependencies = [
  "project_panel",
  "regex",
  "release_channel",
- "schemars",
+ "schemars 0.8.22",
  "search",
  "serde",
  "serde_derive",
@@ -18333,7 +18379,7 @@ dependencies = [
  "language",
  "picker",
  "project",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "telemetry",
@@ -19375,7 +19421,7 @@ dependencies = [
  "postage",
  "project",
  "remote",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "session",
@@ -19607,7 +19653,7 @@ dependencies = [
  "pretty_assertions",
  "rand 0.8.5",
  "rpc",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "serde_json",
  "settings",
@@ -20069,7 +20115,7 @@ name = "zed_actions"
 version = "0.1.0"
 dependencies = [
  "gpui",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "uuid",
  "workspace-hack",
@@ -20396,7 +20442,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "gpui",
- "schemars",
+ "schemars 0.8.22",
  "serde",
  "settings",
  "workspace-hack",

crates/agent2/Cargo.toml 🔗

@@ -15,18 +15,27 @@ doctest = false
 [features]
 test-support = [
     "gpui/test-support",
+    "project/test-support",
 ]
 
 [dependencies]
 anyhow.workspace = true
+async-trait.workspace = true
+collections.workspace = true
 chrono.workspace = true
 futures.workspace = true
+language.workspace = true
 gpui.workspace = true
 project.workspace = true
+smol.workspace = true
 uuid.workspace = true
 workspace-hack.workspace = true
+util.workspace = true
+agentic-coding-protocol = { path = "../../../agentic-coding-protocol" }
 
 [dev-dependencies]
 gpui = { workspace = true, "features" = ["test-support"] }
+project = { workspace = true, "features" = ["test-support"] }
 serde_json.workspace = true
 util.workspace = true
+settings.workspace = true

crates/agent2/src/acp.rs 🔗

@@ -0,0 +1,105 @@
+use crate::{Agent, AgentThread, AgentThreadEntry, AgentThreadSummary, ResponseEvent, ThreadId};
+use agentic_coding_protocol as acp;
+use anyhow::{Context as _, Result};
+use async_trait::async_trait;
+use futures::channel::mpsc::UnboundedReceiver;
+use gpui::{AppContext, AsyncApp, Entity, Task};
+use project::Project;
+use smol::process::Child;
+use util::ResultExt;
+
+pub struct AcpAgent {
+    connection: acp::Connection,
+    _handler_task: Task<()>,
+    _io_task: Task<()>,
+}
+
+struct AcpClientDelegate {
+    project: Entity<Project>,
+    cx: AsyncApp,
+    // sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
+}
+
+#[async_trait]
+impl acp::Client for AcpClientDelegate {
+    async fn read_file(&self, request: acp::ReadFileParams) -> Result<acp::ReadFileResponse> {
+        let cx = &mut self.cx.clone();
+        let buffer = self
+            .project
+            .update(cx, |project, cx| {
+                let path = project
+                    .project_path_for_absolute_path(request.path, cx)
+                    .context("Failed to get project path")?;
+                project.open_buffer(path, cx)
+            })?
+            .await?;
+
+        anyhow::Ok(buffer.update(cx, |buffer, cx| acp::ReadFileResponse {
+            content: buffer.text(),
+            // todo!
+            version: 0,
+        }))
+    }
+}
+
+impl AcpAgent {
+    pub fn stdio(process: Child, project: Entity<Project>, cx: AsyncApp) -> Self {
+        let stdin = process.stdin.expect("process didn't have stdin");
+        let stdout = process.stdout.expect("process didn't have stdout");
+
+        let (connection, handler_fut, io_fut) =
+            acp::Connection::client_to_agent(AcpClientDelegate { project, cx }, stdin, stdout);
+
+        let io_task = cx.background_spawn(async move {
+            io_fut.await.log_err();
+        });
+
+        Self {
+            connection,
+            _handler_task: cx.foreground_executor().spawn(handler_fut),
+            _io_task: io_task,
+        }
+    }
+}
+
+impl Agent for AcpAgent {
+    type Thread = AcpAgentThread;
+
+    async fn threads(&self) -> Result<Vec<AgentThreadSummary>> {
+        let threads = self.connection.request(acp::ListThreadsParams).await?;
+        threads
+            .threads
+            .into_iter()
+            .map(|thread| {
+                Ok(AgentThreadSummary {
+                    id: ThreadId(thread.id.0),
+                    title: thread.title,
+                    created_at: thread.created_at,
+                })
+            })
+            .collect()
+    }
+
+    async fn create_thread(&self) -> Result<Self::Thread> {
+        todo!()
+    }
+
+    async fn open_thread(&self, id: crate::ThreadId) -> Result<Self::Thread> {
+        todo!()
+    }
+}
+
+struct AcpAgentThread {}
+
+impl AgentThread for AcpAgentThread {
+    async fn entries(&self) -> Result<Vec<AgentThreadEntry>> {
+        todo!()
+    }
+
+    async fn send(
+        &self,
+        message: crate::Message,
+    ) -> Result<UnboundedReceiver<Result<ResponseEvent>>> {
+        todo!()
+    }
+}

crates/agent2/src/agent2.rs 🔗

@@ -1,3 +1,5 @@
+mod acp;
+
 use anyhow::{Result, anyhow};
 use chrono::{DateTime, Utc};
 use futures::{
@@ -9,7 +11,6 @@ use futures::{
 use gpui::{AppContext, AsyncApp, Context, Entity, Task, WeakEntity};
 use project::Project;
 use std::{future, ops::Range, path::PathBuf, pin::pin, sync::Arc};
-use uuid::Uuid;
 
 pub trait Agent: 'static {
     type Thread: AgentThread;
@@ -53,7 +54,7 @@ impl ReadFileRequest {
     }
 }
 
-pub struct ThreadId(Uuid);
+pub struct ThreadId(String);
 
 pub struct FileVersion(u64);
 
@@ -363,14 +364,27 @@ impl<T: AgentThread> Thread<T> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use agentic_coding_protocol::Client;
     use gpui::{BackgroundExecutor, TestAppContext};
     use project::FakeFs;
     use serde_json::json;
-    use std::path::Path;
+    use settings::SettingsStore;
+    use smol::process::Child;
+    use std::env;
     use util::path;
 
+    fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+            Project::init_settings(cx);
+        });
+    }
+
     #[gpui::test]
     async fn test_basic(cx: &mut TestAppContext) {
+        init_test(cx);
+
         cx.executor().allow_parking();
 
         let fs = FakeFs::new(cx.executor());
@@ -380,19 +394,43 @@ mod tests {
         )
         .await;
         let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
-        let agent = GeminiAgent::start("~/gemini-cli/change-me.js", &cx.executor())
-            .await
-            .unwrap();
+        let agent = GeminiAgent::start(&cx.executor()).await.unwrap();
         let thread_store = ThreadStore::load(Arc::new(agent), project, &mut cx.to_async())
             .await
             .unwrap();
     }
 
-    struct GeminiAgent {}
+    struct TestClient;
+
+    #[async_trait]
+    impl Client for TestClient {
+        async fn read_file(&self, _request: ReadFileParams) -> Result<ReadFileResponse> {
+            Ok(ReadFileResponse {
+                version: FileVersion(0),
+                content: "the content".into(),
+            })
+        }
+    }
+
+    struct GeminiAgent {
+        child: Child,
+        _task: Task<()>,
+    }
 
     impl GeminiAgent {
-        pub fn start(path: impl AsRef<Path>, executor: &BackgroundExecutor) -> Task<Result<Self>> {
-            executor.spawn(async move { Ok(GeminiAgent {}) })
+        pub fn start(executor: &BackgroundExecutor) -> Task<Result<Self>> {
+            executor.spawn(async move {
+                // todo!
+                let child = util::command::new_smol_command("node")
+                    .arg("../gemini-cli/packages/cli")
+                    .arg("--acp")
+                    .env("GEMINI_API_KEY", env::var("GEMINI_API_KEY").unwrap())
+                    .kill_on_drop(true)
+                    .spawn()
+                    .unwrap();
+
+                Ok(GeminiAgent { child })
+            })
         }
     }