claude.rs

  1use settings::SettingsStore;
  2use std::path::Path;
  3use std::rc::Rc;
  4use std::{any::Any, path::PathBuf};
  5
  6use anyhow::{Result, bail};
  7use gpui::{App, AppContext as _, SharedString, Task};
  8
  9use crate::{AgentServer, AgentServerDelegate, AllAgentServersSettings};
 10use acp_thread::AgentConnection;
 11
 12#[derive(Clone)]
 13pub struct ClaudeCode;
 14
 15pub struct ClaudeCodeLoginCommand {
 16    pub path: PathBuf,
 17    pub arguments: Vec<String>,
 18}
 19
 20impl ClaudeCode {
 21    const BINARY_NAME: &'static str = "claude-code-acp";
 22    const PACKAGE_NAME: &'static str = "@zed-industries/claude-code-acp";
 23
 24    pub fn login_command(
 25        delegate: AgentServerDelegate,
 26        cx: &mut App,
 27    ) -> Task<Result<ClaudeCodeLoginCommand>> {
 28        let custom_command = cx.read_global(|settings: &SettingsStore, _| {
 29            settings
 30                .get::<AllAgentServersSettings>(None)
 31                .get("claude")
 32                .cloned()
 33        });
 34
 35        cx.spawn(async move |cx| {
 36            let mut command = if custom_command.is_some() {
 37                bail!("Cannot construct login command because a custom command was specified for claude-code-acp in settings")
 38            } else {
 39                cx.update(|cx| {
 40                    delegate.get_or_npm_install_builtin_agent(
 41                        Self::BINARY_NAME.into(),
 42                        Self::PACKAGE_NAME.into(),
 43                        "node_modules/@anthropic-ai/claude-code/cli.js".into(),
 44                        false,
 45                        Some("0.2.5".parse().unwrap()),
 46                        cx,
 47                    )
 48                })?
 49                .await?
 50            };
 51            command.args.push("/login".into());
 52
 53            Ok(ClaudeCodeLoginCommand {
 54                path: command.path,
 55                arguments: command.args,
 56            })
 57        })
 58    }
 59}
 60
 61impl AgentServer for ClaudeCode {
 62    fn telemetry_id(&self) -> &'static str {
 63        "claude-code"
 64    }
 65
 66    fn name(&self) -> SharedString {
 67        "Claude Code".into()
 68    }
 69
 70    fn logo(&self) -> ui::IconName {
 71        ui::IconName::AiClaude
 72    }
 73
 74    fn connect(
 75        &self,
 76        root_dir: &Path,
 77        delegate: AgentServerDelegate,
 78        cx: &mut App,
 79    ) -> Task<Result<Rc<dyn AgentConnection>>> {
 80        let root_dir = root_dir.to_path_buf();
 81        let fs = delegate.project().read(cx).fs().clone();
 82        let server_name = self.name();
 83        let custom_command = cx.read_global(|settings: &SettingsStore, _| {
 84            settings
 85                .get::<AllAgentServersSettings>(None)
 86                .get("claude")
 87                .cloned()
 88        });
 89
 90        cx.spawn(async move |cx| {
 91            let mut command = if let Some(custom_command) = custom_command {
 92                custom_command
 93            } else {
 94                cx.update(|cx| {
 95                    delegate.get_or_npm_install_builtin_agent(
 96                        Self::BINARY_NAME.into(),
 97                        Self::PACKAGE_NAME.into(),
 98                        format!("node_modules/{}/dist/index.js", Self::PACKAGE_NAME).into(),
 99                        false,
100                        None,
101                        cx,
102                    )
103                })?
104                .await?
105            };
106
107            command
108                .env
109                .get_or_insert_default()
110                .insert("ANTHROPIC_API_KEY".to_owned(), "".to_owned());
111
112            let root_dir_exists = fs.is_dir(&root_dir).await;
113            anyhow::ensure!(
114                root_dir_exists,
115                "Session root {} does not exist or is not a directory",
116                root_dir.to_string_lossy()
117            );
118
119            crate::acp::connect(server_name, command.clone(), &root_dir, cx).await
120        })
121    }
122
123    fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
124        self
125    }
126}