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}