claude.rs

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