stdio_agent_server.rs

  1use crate::{AgentServer, AgentServerCommand, AgentServerVersion};
  2use acp_thread::{AgentConnection, LoadError, OldAcpAgentConnection, OldAcpClientDelegate};
  3use agentic_coding_protocol as acp_old;
  4use anyhow::{Result, anyhow};
  5use gpui::{App, AsyncApp, Entity, Task, WeakEntity, prelude::*};
  6use project::Project;
  7use std::{cell::RefCell, path::Path, rc::Rc};
  8use util::ResultExt;
  9
 10pub trait StdioAgentServer: Send + Clone {
 11    fn logo(&self) -> ui::IconName;
 12    fn name(&self) -> &'static str;
 13    fn empty_state_headline(&self) -> &'static str;
 14    fn empty_state_message(&self) -> &'static str;
 15
 16    fn command(
 17        &self,
 18        project: &Entity<Project>,
 19        cx: &mut AsyncApp,
 20    ) -> impl Future<Output = Result<AgentServerCommand>>;
 21
 22    fn version(
 23        &self,
 24        command: &AgentServerCommand,
 25    ) -> impl Future<Output = Result<AgentServerVersion>> + Send;
 26}
 27
 28impl<T: StdioAgentServer + 'static> AgentServer for T {
 29    fn name(&self) -> &'static str {
 30        self.name()
 31    }
 32
 33    fn empty_state_headline(&self) -> &'static str {
 34        self.empty_state_headline()
 35    }
 36
 37    fn empty_state_message(&self) -> &'static str {
 38        self.empty_state_message()
 39    }
 40
 41    fn logo(&self) -> ui::IconName {
 42        self.logo()
 43    }
 44
 45    fn connect(
 46        &self,
 47        root_dir: &Path,
 48        project: &Entity<Project>,
 49        cx: &mut App,
 50    ) -> Task<Result<Rc<dyn AgentConnection>>> {
 51        let root_dir = root_dir.to_path_buf();
 52        let project = project.clone();
 53        let this = self.clone();
 54        let name = self.name();
 55
 56        cx.spawn(async move |cx| {
 57            let command = this.command(&project, cx).await?;
 58
 59            let mut child = util::command::new_smol_command(&command.path)
 60                .args(command.args.iter())
 61                .current_dir(root_dir)
 62                .stdin(std::process::Stdio::piped())
 63                .stdout(std::process::Stdio::piped())
 64                .stderr(std::process::Stdio::inherit())
 65                .kill_on_drop(true)
 66                .spawn()?;
 67
 68            let stdin = child.stdin.take().unwrap();
 69            let stdout = child.stdout.take().unwrap();
 70
 71            let foreground_executor = cx.foreground_executor().clone();
 72
 73            let thread_rc = Rc::new(RefCell::new(WeakEntity::new_invalid()));
 74
 75            let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
 76                OldAcpClientDelegate::new(thread_rc.clone(), cx.clone()),
 77                stdin,
 78                stdout,
 79                move |fut| foreground_executor.spawn(fut).detach(),
 80            );
 81
 82            let io_task = cx.background_spawn(async move {
 83                io_fut.await.log_err();
 84            });
 85
 86            let child_status = cx.background_spawn(async move {
 87                let result = match child.status().await {
 88                    Err(e) => Err(anyhow!(e)),
 89                    Ok(result) if result.success() => Ok(()),
 90                    Ok(result) => {
 91                        if let Some(AgentServerVersion::Unsupported {
 92                            error_message,
 93                            upgrade_message,
 94                            upgrade_command,
 95                        }) = this.version(&command).await.log_err()
 96                        {
 97                            Err(anyhow!(LoadError::Unsupported {
 98                                error_message,
 99                                upgrade_message,
100                                upgrade_command
101                            }))
102                        } else {
103                            Err(anyhow!(LoadError::Exited(result.code().unwrap_or(-127))))
104                        }
105                    }
106                };
107                drop(io_task);
108                result
109            });
110
111            let connection: Rc<dyn AgentConnection> = Rc::new(OldAcpAgentConnection {
112                name,
113                connection,
114                child_status,
115            });
116
117            Ok(connection)
118        })
119    }
120}