1use settings::SettingsStore;
2use std::path::Path;
3use std::rc::Rc;
4use std::{any::Any, path::PathBuf};
5
6use anyhow::Result;
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 settings = cx.read_global(|settings: &SettingsStore, _| {
29 settings.get::<AllAgentServersSettings>(None).claude.clone()
30 });
31
32 cx.spawn(async move |cx| {
33 let mut command = if let Some(settings) = settings {
34 settings.command
35 } else {
36 cx.update(|cx| {
37 delegate.get_or_npm_install_builtin_agent(
38 Self::BINARY_NAME.into(),
39 Self::PACKAGE_NAME.into(),
40 "node_modules/@anthropic-ai/claude-code/cli.js".into(),
41 true,
42 Some("0.2.5".parse().unwrap()),
43 cx,
44 )
45 })?
46 .await?
47 };
48 command.args.push("/login".into());
49
50 Ok(ClaudeCodeLoginCommand {
51 path: command.path,
52 arguments: command.args,
53 })
54 })
55 }
56}
57
58impl AgentServer for ClaudeCode {
59 fn telemetry_id(&self) -> &'static str {
60 "claude-code"
61 }
62
63 fn name(&self) -> SharedString {
64 "Claude Code".into()
65 }
66
67 fn logo(&self) -> ui::IconName {
68 ui::IconName::AiClaude
69 }
70
71 fn connect(
72 &self,
73 root_dir: &Path,
74 delegate: AgentServerDelegate,
75 cx: &mut App,
76 ) -> Task<Result<Rc<dyn AgentConnection>>> {
77 let root_dir = root_dir.to_path_buf();
78 let fs = delegate.project().read(cx).fs().clone();
79 let server_name = self.name();
80 let settings = cx.read_global(|settings: &SettingsStore, _| {
81 settings.get::<AllAgentServersSettings>(None).claude.clone()
82 });
83 let project = delegate.project().clone();
84
85 cx.spawn(async move |cx| {
86 let mut project_env = project
87 .update(cx, |project, cx| {
88 project.directory_environment(root_dir.as_path().into(), cx)
89 })?
90 .await
91 .unwrap_or_default();
92 let mut command = if let Some(settings) = settings {
93 settings.command
94 } else {
95 cx.update(|cx| {
96 delegate.get_or_npm_install_builtin_agent(
97 Self::BINARY_NAME.into(),
98 Self::PACKAGE_NAME.into(),
99 format!("node_modules/{}/dist/index.js", Self::PACKAGE_NAME).into(),
100 true,
101 None,
102 cx,
103 )
104 })?
105 .await?
106 };
107 project_env.extend(command.env.take().unwrap_or_default());
108 command.env = Some(project_env);
109
110 command
111 .env
112 .get_or_insert_default()
113 .insert("ANTHROPIC_API_KEY".to_owned(), "".to_owned());
114
115 let root_dir_exists = fs.is_dir(&root_dir).await;
116 anyhow::ensure!(
117 root_dir_exists,
118 "Session root {} does not exist or is not a directory",
119 root_dir.to_string_lossy()
120 );
121
122 crate::acp::connect(server_name, command.clone(), &root_dir, cx).await
123 })
124 }
125
126 fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
127 self
128 }
129}