Get one test passing w/ gemini cli

Max Brunsfeld created

Change summary

Cargo.lock                  |  2 
crates/agent2/Cargo.toml    |  1 
crates/agent2/src/acp.rs    | 42 ++++++++++++------
crates/agent2/src/agent2.rs | 84 +++++++-------------------------------
4 files changed, 47 insertions(+), 82 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -114,6 +114,7 @@ dependencies = [
  "async-trait",
  "chrono",
  "collections",
+ "env_logger 0.11.8",
  "futures 0.3.31",
  "gpui",
  "language",
@@ -246,6 +247,7 @@ dependencies = [
  "async-trait",
  "chrono",
  "futures 0.3.31",
+ "log",
  "parking_lot",
  "schemars 1.0.1",
  "serde",

crates/agent2/Cargo.toml 🔗

@@ -34,6 +34,7 @@ util.workspace = true
 agentic-coding-protocol = { path = "../../../agentic-coding-protocol" }
 
 [dev-dependencies]
+env_logger.workspace = true
 gpui = { workspace = true, "features" = ["test-support"] }
 project = { workspace = true, "features" = ["test-support"] }
 serde_json.workspace = true

crates/agent2/src/acp.rs 🔗

@@ -1,3 +1,5 @@
+use std::path::Path;
+
 use crate::{Agent, AgentThread, AgentThreadEntry, AgentThreadSummary, ResponseEvent, ThreadId};
 use agentic_coding_protocol as acp;
 use anyhow::{Context as _, Result};
@@ -9,7 +11,7 @@ use smol::process::Child;
 use util::ResultExt;
 
 pub struct AcpAgent {
-    connection: acp::Connection,
+    connection: acp::AgentConnection,
     _handler_task: Task<()>,
     _io_task: Task<()>,
 }
@@ -20,7 +22,7 @@ struct AcpClientDelegate {
     // sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
 }
 
-#[async_trait]
+#[async_trait(?Send)]
 impl acp::Client for AcpClientDelegate {
     async fn read_file(&self, request: acp::ReadFileParams) -> Result<acp::ReadFileResponse> {
         let cx = &mut self.cx.clone();
@@ -28,30 +30,40 @@ impl acp::Client for AcpClientDelegate {
             .project
             .update(cx, |project, cx| {
                 let path = project
-                    .project_path_for_absolute_path(request.path, cx)
+                    .project_path_for_absolute_path(Path::new(&request.path), cx)
                     .context("Failed to get project path")?;
-                project.open_buffer(path, cx)
-            })?
+                anyhow::Ok(project.open_buffer(path, cx))
+            })??
             .await?;
 
-        anyhow::Ok(buffer.update(cx, |buffer, cx| acp::ReadFileResponse {
+        buffer.update(cx, |buffer, _| acp::ReadFileResponse {
             content: buffer.text(),
-            // todo!
-            version: 0,
-        }))
+            version: acp::FileVersion(0),
+        })
+    }
+
+    async fn glob_search(&self, request: acp::GlobSearchParams) -> Result<acp::GlobSearchResponse> {
+        todo!()
     }
 }
 
 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");
+    pub fn stdio(mut process: Child, project: Entity<Project>, cx: AsyncApp) -> Self {
+        let stdin = process.stdin.take().expect("process didn't have stdin");
+        let stdout = process.stdout.take().expect("process didn't have stdout");
 
-        let (connection, handler_fut, io_fut) =
-            acp::Connection::client_to_agent(AcpClientDelegate { project, cx }, stdin, stdout);
+        let (connection, handler_fut, io_fut) = acp::AgentConnection::connect_to_agent(
+            AcpClientDelegate {
+                project,
+                cx: cx.clone(),
+            },
+            stdin,
+            stdout,
+        );
 
         let io_task = cx.background_spawn(async move {
             io_fut.await.log_err();
+            process.status().await.log_err();
         });
 
         Self {
@@ -89,7 +101,7 @@ impl Agent for AcpAgent {
     }
 }
 
-struct AcpAgentThread {}
+pub struct AcpAgentThread {}
 
 impl AgentThread for AcpAgentThread {
     async fn entries(&self) -> Result<Vec<AgentThreadEntry>> {

crates/agent2/src/agent2.rs 🔗

@@ -364,16 +364,16 @@ impl<T: AgentThread> Thread<T> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use agentic_coding_protocol::Client;
-    use gpui::{BackgroundExecutor, TestAppContext};
+    use crate::acp::AcpAgent;
+    use gpui::TestAppContext;
     use project::FakeFs;
     use serde_json::json;
     use settings::SettingsStore;
-    use smol::process::Child;
-    use std::env;
+    use std::{env, process::Stdio};
     use util::path;
 
     fn init_test(cx: &mut TestAppContext) {
+        env_logger::init();
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
@@ -394,74 +394,24 @@ mod tests {
         )
         .await;
         let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
-        let agent = GeminiAgent::start(&cx.executor()).await.unwrap();
+        let agent = gemini_agent(project.clone(), cx.to_async()).unwrap();
         let thread_store = ThreadStore::load(Arc::new(agent), project, &mut cx.to_async())
             .await
             .unwrap();
     }
 
-    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(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 })
-            })
-        }
-    }
-
-    impl Agent for GeminiAgent {
-        type Thread = GeminiAgentThread;
-
-        async fn threads(&self) -> Result<Vec<AgentThreadSummary>> {
-            todo!()
-        }
-
-        async fn create_thread(&self) -> Result<Self::Thread> {
-            todo!()
-        }
-
-        async fn open_thread(&self, id: ThreadId) -> Result<Self::Thread> {
-            todo!()
-        }
-    }
-
-    struct GeminiAgentThread {}
-
-    impl AgentThread for GeminiAgentThread {
-        async fn entries(&self) -> Result<Vec<AgentThreadEntry>> {
-            todo!()
-        }
+    pub fn gemini_agent(project: Entity<Project>, cx: AsyncApp) -> Result<AcpAgent> {
+        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())
+            .stdin(Stdio::piped())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::inherit())
+            .kill_on_drop(true)
+            .spawn()
+            .unwrap();
 
-        async fn send(
-            &self,
-            _message: Message,
-        ) -> Result<mpsc::UnboundedReceiver<Result<ResponseEvent>>> {
-            todo!()
-        }
+        Ok(AcpAgent::stdio(child, project, cx))
     }
 }