1use agent_client_protocol as acp;
2use fs::Fs;
3use settings::{SettingsStore, update_settings_file};
4use std::path::Path;
5use std::rc::Rc;
6use std::sync::Arc;
7use std::{any::Any, path::PathBuf};
8
9use anyhow::{Context as _, Result};
10use collections::HashMap;
11use gpui::{App, AppContext as _, SharedString, Task};
12use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
13
14use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
15use acp_thread::AgentConnection;
16
17#[derive(Clone)]
18pub struct ClaudeCode;
19
20pub struct AgentServerLoginCommand {
21 pub path: PathBuf,
22 pub arguments: Vec<String>,
23}
24
25impl AgentServer for ClaudeCode {
26 fn telemetry_id(&self) -> &'static str {
27 "claude-code"
28 }
29
30 fn name(&self) -> SharedString {
31 "Claude Code".into()
32 }
33
34 fn logo(&self) -> ui::IconName {
35 ui::IconName::AiClaude
36 }
37
38 fn default_mode(&self, cx: &mut App) -> Option<acp::SessionModeId> {
39 let settings = cx.read_global(|settings: &SettingsStore, _| {
40 settings.get::<AllAgentServersSettings>(None).claude.clone()
41 });
42
43 settings
44 .as_ref()
45 .and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
46 }
47
48 fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
49 update_settings_file(fs, cx, |settings, _| {
50 settings
51 .agent_servers
52 .get_or_insert_default()
53 .claude
54 .get_or_insert_default()
55 .default_mode = mode_id.map(|m| m.to_string())
56 });
57 }
58
59 fn login_commands(&self) -> &'static [&'static str] {
60 &["login"]
61 }
62
63 fn logout_commands(&self) -> &'static [&'static str] {
64 &["logout"]
65 }
66
67 fn connect(
68 &self,
69 root_dir: Option<&Path>,
70 delegate: AgentServerDelegate,
71 cx: &mut App,
72 ) -> Task<
73 Result<(
74 Rc<dyn AgentConnection>,
75 HashMap<String, task::SpawnInTerminal>,
76 )>,
77 > {
78 let name = self.name();
79 let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
80 let is_remote = delegate.project.read(cx).is_via_remote_server();
81 let store = delegate.store.downgrade();
82 let extra_env = load_proxy_env(cx);
83 let default_mode = self.default_mode(cx);
84
85 cx.spawn(async move |cx| {
86 let (command, root_dir, auth_commands) = store
87 .update(cx, |store, cx| {
88 let agent = store
89 .get_external_agent(&CLAUDE_CODE_NAME.into())
90 .context("Claude Code is not registered")?;
91 anyhow::Ok(agent.get_command(
92 root_dir.as_deref(),
93 extra_env,
94 delegate.status_tx,
95 delegate.new_version_available,
96 &mut cx.to_async(),
97 ))
98 })??
99 .await?;
100 let connection = crate::acp::connect(
101 name,
102 command,
103 root_dir.as_ref(),
104 default_mode,
105 is_remote,
106 cx,
107 )
108 .await?;
109 Ok((connection, auth_commands))
110 })
111 }
112
113 fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
114 self
115 }
116}