acp.rs

  1use std::path::Path;
  2
  3use crate::{
  4    Agent, AgentThread, AgentThreadEntryContent, AgentThreadSummary, ResponseEvent, ThreadId,
  5};
  6use agentic_coding_protocol::{self as acp};
  7use anyhow::{Context as _, Result};
  8use async_trait::async_trait;
  9use futures::channel::mpsc::UnboundedReceiver;
 10use gpui::{AppContext, AsyncApp, Entity, Task};
 11use project::Project;
 12use smol::process::Child;
 13use util::ResultExt;
 14
 15pub struct AcpAgent {
 16    connection: acp::AgentConnection,
 17    _handler_task: Task<()>,
 18    _io_task: Task<()>,
 19}
 20
 21struct AcpClientDelegate {
 22    project: Entity<Project>,
 23    cx: AsyncApp,
 24    // sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
 25}
 26
 27#[async_trait(?Send)]
 28impl acp::Client for AcpClientDelegate {
 29    async fn read_file(&self, request: acp::ReadFileParams) -> Result<acp::ReadFileResponse> {
 30        let cx = &mut self.cx.clone();
 31        let buffer = self
 32            .project
 33            .update(cx, |project, cx| {
 34                let path = project
 35                    .project_path_for_absolute_path(Path::new(&request.path), cx)
 36                    .context("Failed to get project path")?;
 37                anyhow::Ok(project.open_buffer(path, cx))
 38            })??
 39            .await?;
 40
 41        buffer.update(cx, |buffer, _| acp::ReadFileResponse {
 42            content: buffer.text(),
 43            version: acp::FileVersion(0),
 44        })
 45    }
 46
 47    async fn glob_search(&self, request: acp::GlobSearchParams) -> Result<acp::GlobSearchResponse> {
 48        todo!()
 49    }
 50
 51    async fn end_turn(&self, request: acp::EndTurnParams) -> Result<acp::EndTurnResponse> {
 52        todo!()
 53    }
 54}
 55
 56impl AcpAgent {
 57    pub fn stdio(mut process: Child, project: Entity<Project>, cx: AsyncApp) -> Self {
 58        let stdin = process.stdin.take().expect("process didn't have stdin");
 59        let stdout = process.stdout.take().expect("process didn't have stdout");
 60
 61        let (connection, handler_fut, io_fut) = acp::AgentConnection::connect_to_agent(
 62            AcpClientDelegate {
 63                project,
 64                cx: cx.clone(),
 65            },
 66            stdin,
 67            stdout,
 68        );
 69
 70        let io_task = cx.background_spawn(async move {
 71            io_fut.await.log_err();
 72            process.status().await.log_err();
 73        });
 74
 75        Self {
 76            connection,
 77            _handler_task: cx.foreground_executor().spawn(handler_fut),
 78            _io_task: io_task,
 79        }
 80    }
 81}
 82
 83impl Agent for AcpAgent {
 84    type Thread = AcpAgentThread;
 85
 86    async fn threads(&self) -> Result<Vec<AgentThreadSummary>> {
 87        let response = self.connection.request(acp::GetThreadsParams).await?;
 88        response
 89            .threads
 90            .into_iter()
 91            .map(|thread| {
 92                Ok(AgentThreadSummary {
 93                    id: thread.id.into(),
 94                    title: thread.title,
 95                    created_at: thread.modified_at,
 96                })
 97            })
 98            .collect()
 99    }
100
101    async fn create_thread(&self) -> Result<Self::Thread> {
102        let response = self.connection.request(acp::CreateThreadParams).await?;
103        Ok(AcpAgentThread {
104            id: response.thread_id,
105        })
106    }
107
108    async fn open_thread(&self, id: ThreadId) -> Result<Self::Thread> {
109        todo!()
110    }
111}
112
113pub struct AcpAgentThread {
114    id: acp::ThreadId,
115}
116
117impl AgentThread for AcpAgentThread {
118    async fn entries(&self) -> Result<Vec<AgentThreadEntryContent>> {
119        todo!()
120    }
121
122    async fn send(
123        &self,
124        message: crate::Message,
125    ) -> Result<UnboundedReceiver<Result<ResponseEvent>>> {
126        todo!()
127    }
128}
129
130impl From<acp::ThreadId> for ThreadId {
131    fn from(thread_id: acp::ThreadId) -> Self {
132        Self(thread_id.0)
133    }
134}
135
136impl From<ThreadId> for acp::ThreadId {
137    fn from(thread_id: ThreadId) -> Self {
138        acp::ThreadId(thread_id.0)
139    }
140}