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