acp: Use the custom claude installation to perform login (#37169)

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: morgankrey <morgan@zed.dev>
This commit is contained in:
Antonio Scandurra
2025-08-29 14:16:02 +00:00
committed by GitHub
co-authored by Bennet Bo Fenner Agus Zubiaga Nathan Sobo Cole Miller morgankrey
parent 5001c03711
commit 11fb57a6d9
7 changed files with 94 additions and 31 deletions
Generated
+1
View File
@@ -414,6 +414,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"smol",
"streaming_diff",
"task",
+6 -4
View File
@@ -44,11 +44,11 @@ pub fn init(cx: &mut App) {
pub struct AgentServerDelegate {
project: Entity<Project>,
status_tx: watch::Sender<SharedString>,
status_tx: Option<watch::Sender<SharedString>>,
}
impl AgentServerDelegate {
pub fn new(project: Entity<Project>, status_tx: watch::Sender<SharedString>) -> Self {
pub fn new(project: Entity<Project>, status_tx: Option<watch::Sender<SharedString>>) -> Self {
Self { project, status_tx }
}
@@ -72,7 +72,7 @@ impl AgentServerDelegate {
"External agents are not yet available in remote projects."
)));
};
let mut status_tx = self.status_tx;
let status_tx = self.status_tx;
cx.spawn(async move |cx| {
if !ignore_system_version {
@@ -165,7 +165,9 @@ impl AgentServerDelegate {
.detach();
file_name
} else {
status_tx.send("Installing…".into()).ok();
if let Some(mut status_tx) = status_tx {
status_tx.send("Installing…".into()).ok();
}
let dir = dir.clone();
cx.background_spawn(Self::download_latest_version(
fs,
+39 -1
View File
@@ -1,8 +1,8 @@
use language_models::provider::anthropic::AnthropicLanguageModelProvider;
use settings::SettingsStore;
use std::any::Any;
use std::path::Path;
use std::rc::Rc;
use std::{any::Any, path::PathBuf};
use anyhow::Result;
use gpui::{App, AppContext as _, SharedString, Task};
@@ -13,9 +13,47 @@ use acp_thread::AgentConnection;
#[derive(Clone)]
pub struct ClaudeCode;
pub struct ClaudeCodeLoginCommand {
pub path: PathBuf,
pub arguments: Vec<String>,
}
impl ClaudeCode {
const BINARY_NAME: &'static str = "claude-code-acp";
const PACKAGE_NAME: &'static str = "@zed-industries/claude-code-acp";
pub fn login_command(
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<ClaudeCodeLoginCommand>> {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).claude.clone()
});
cx.spawn(async move |cx| {
let mut command = if let Some(settings) = settings {
settings.command
} else {
cx.update(|cx| {
delegate.get_or_npm_install_builtin_agent(
Self::BINARY_NAME.into(),
Self::PACKAGE_NAME.into(),
"node_modules/@anthropic-ai/claude-code/cli.js".into(),
true,
None,
cx,
)
})?
.await?
};
command.args.push("/login".into());
Ok(ClaudeCodeLoginCommand {
path: command.path,
arguments: command.args,
})
})
}
}
impl AgentServer for ClaudeCode {
+1 -1
View File
@@ -498,7 +498,7 @@ pub async fn new_test_thread(
current_dir: impl AsRef<Path>,
cx: &mut TestAppContext,
) -> Entity<AcpThread> {
let delegate = AgentServerDelegate::new(project.clone(), watch::channel("".into()).0);
let delegate = AgentServerDelegate::new(project.clone(), None);
let connection = cx
.update(|cx| server.connect(current_dir.as_ref(), delegate, cx))
+1
View File
@@ -80,6 +80,7 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
shlex.workspace = true
smol.workspace = true
streaming_diff.workspace = true
task.workspace = true
+1 -1
View File
@@ -645,7 +645,7 @@ impl MessageEditor {
self.project.read(cx).fs().clone(),
self.history_store.clone(),
));
let delegate = AgentServerDelegate::new(self.project.clone(), watch::channel("".into()).0);
let delegate = AgentServerDelegate::new(self.project.clone(), None);
let connection = server.connect(Path::new(""), delegate, cx);
cx.spawn(async move |_, cx| {
let agent = connection.await?;
+45 -24
View File
@@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities};
use agent_servers::{AgentServer, AgentServerDelegate, ClaudeCode};
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore};
use anyhow::{Result, anyhow, bail};
use anyhow::{Context as _, Result, anyhow, bail};
use audio::{Audio, Sound};
use buffer_diff::BufferDiff;
use client::zed_urls;
@@ -423,7 +423,7 @@ impl AcpThreadView {
.map(|worktree| worktree.read(cx).abs_path())
.unwrap_or_else(|| paths::home_dir().as_path().into());
let (tx, mut rx) = watch::channel("Loading…".into());
let delegate = AgentServerDelegate::new(project.clone(), tx);
let delegate = AgentServerDelegate::new(project.clone(), Some(tx));
let connect_task = agent.connect(&root_dir, delegate, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
@@ -1386,31 +1386,52 @@ impl AcpThreadView {
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return Task::ready(Ok(()));
};
let project = workspace.read(cx).project().read(cx);
let project_entity = workspace.read(cx).project();
let project = project_entity.read(cx);
let cwd = project.first_project_directory(cx);
let shell = project.terminal_settings(&cwd, cx).shell.clone();
let terminal = terminal_panel.update(cx, |terminal_panel, cx| {
terminal_panel.spawn_task(
&SpawnInTerminal {
id: task::TaskId("claude-login".into()),
full_label: "claude /login".to_owned(),
label: "claude /login".to_owned(),
command: Some("claude".to_owned()),
args: vec!["/login".to_owned()],
command_label: "claude /login".to_owned(),
cwd,
use_new_terminal: true,
allow_concurrent_runs: true,
hide: task::HideStrategy::Always,
shell,
..Default::default()
},
window,
cx,
)
});
cx.spawn(async move |cx| {
let delegate = AgentServerDelegate::new(project_entity.clone(), None);
let command = ClaudeCode::login_command(delegate, cx);
window.spawn(cx, async move |cx| {
let login_command = command.await?;
let command = login_command
.path
.to_str()
.with_context(|| format!("invalid login command: {:?}", login_command.path))?;
let command = shlex::try_quote(command)?;
let args = login_command
.arguments
.iter()
.map(|arg| {
Ok(shlex::try_quote(arg)
.context("Failed to quote argument")?
.to_string())
})
.collect::<Result<Vec<_>>>()?;
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.spawn_task(
&SpawnInTerminal {
id: task::TaskId("claude-login".into()),
full_label: "claude /login".to_owned(),
label: "claude /login".to_owned(),
command: Some(command.into()),
args,
command_label: "claude /login".to_owned(),
cwd,
use_new_terminal: true,
allow_concurrent_runs: true,
hide: task::HideStrategy::Always,
shell,
..Default::default()
},
window,
cx,
)
})?;
let terminal = terminal.await?;
let mut exit_status = terminal
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?