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}