1use std::{error::Error, fmt, path::Path, rc::Rc};
2
3use agent_client_protocol as acp;
4use agentic_coding_protocol::{self as acp_old, AgentRequest};
5use anyhow::Result;
6use gpui::{AppContext, AsyncApp, Entity, Task};
7use project::Project;
8use ui::App;
9
10use crate::AcpThread;
11
12pub trait AgentConnection {
13 fn name(&self) -> &'static str;
14
15 fn new_thread(
16 self: Rc<Self>,
17 project: Entity<Project>,
18 cwd: &Path,
19 cx: &mut AsyncApp,
20 ) -> Task<Result<Entity<AcpThread>>>;
21
22 fn authenticate(&self, cx: &mut App) -> Task<Result<()>>;
23
24 fn prompt(&self, params: acp::PromptToolArguments, cx: &mut App) -> Task<Result<()>>;
25
26 fn cancel(&self, cx: &mut App);
27}
28
29#[derive(Debug)]
30pub struct Unauthenticated;
31
32impl Error for Unauthenticated {}
33impl fmt::Display for Unauthenticated {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "Unauthenticated")
36 }
37}
38
39pub struct OldAcpAgentConnection {
40 pub name: &'static str,
41 pub connection: acp_old::AgentConnection,
42 pub child_status: Task<Result<()>>,
43}
44
45impl AgentConnection for OldAcpAgentConnection {
46 fn name(&self) -> &'static str {
47 self.name
48 }
49
50 fn new_thread(
51 self: Rc<Self>,
52 project: Entity<Project>,
53 _cwd: &Path,
54 cx: &mut AsyncApp,
55 ) -> Task<Result<Entity<AcpThread>>> {
56 let task = self.connection.request_any(
57 acp_old::InitializeParams {
58 protocol_version: acp_old::ProtocolVersion::latest(),
59 }
60 .into_any(),
61 );
62 cx.spawn(async move |cx| {
63 let result = task.await?;
64 let result = acp_old::InitializeParams::response_from_any(result)?;
65
66 if !result.is_authenticated {
67 anyhow::bail!(Unauthenticated)
68 }
69
70 cx.update(|cx| {
71 let thread = cx.new(|cx| {
72 let session_id = acp::SessionId("acp-old-no-id".into());
73 AcpThread::new(self.clone(), project, session_id, cx)
74 });
75 thread
76 })
77 })
78 }
79
80 fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
81 let task = self
82 .connection
83 .request_any(acp_old::AuthenticateParams.into_any());
84 cx.foreground_executor().spawn(async move {
85 task.await?;
86 Ok(())
87 })
88 }
89
90 fn prompt(&self, params: acp::PromptToolArguments, cx: &mut App) -> Task<Result<()>> {
91 let chunks = params
92 .prompt
93 .into_iter()
94 .filter_map(|block| match block {
95 acp::ContentBlock::Text(text) => {
96 Some(acp_old::UserMessageChunk::Text { text: text.text })
97 }
98 acp::ContentBlock::ResourceLink(link) => Some(acp_old::UserMessageChunk::Path {
99 path: link.uri.into(),
100 }),
101 _ => None,
102 })
103 .collect();
104
105 let task = self
106 .connection
107 .request_any(acp_old::SendUserMessageParams { chunks }.into_any());
108 cx.foreground_executor().spawn(async move {
109 task.await?;
110 anyhow::Ok(())
111 })
112 }
113
114 fn cancel(&self, cx: &mut App) {
115 let task = self
116 .connection
117 .request_any(acp_old::CancelSendMessageParams.into_any());
118 cx.foreground_executor()
119 .spawn(async move {
120 task.await?;
121 anyhow::Ok(())
122 })
123 .detach_and_log_err(cx)
124 }
125}