From c0710fa8ca3c3f579e76fe8d658c79ae6f497107 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 16 Sep 2025 12:18:10 +0200 Subject: [PATCH] agent_servers: Set proxy env for all ACP agents (#38247) - Use ProxySettings::proxy_url to read from settings or env - Export HTTP(S)_PROXY and NO_PROXY for agent CLIs - Add read_no_proxy_from_env and move parsing from main Closes https://github.com/zed-industries/claude-code-acp/issues/46 Release Notes: - acp: Pass proxy settings through to all ACP agents --- Cargo.lock | 1 + crates/agent_servers/Cargo.toml | 1 + crates/agent_servers/src/agent_servers.rs | 28 ++++++++++++++++++++++- crates/agent_servers/src/claude.rs | 5 ++-- crates/agent_servers/src/custom.rs | 5 ++-- crates/agent_servers/src/gemini.rs | 23 ++++--------------- crates/client/src/client.rs | 16 ++++++++++++- crates/http_client/src/http_client.rs | 6 +++++ crates/zed/src/main.rs | 12 +--------- 9 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b1427a435a07c7485388a668e51035d55b56b39..deb0dba0dac2f766d243a21801902bee66d156b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "futures 0.3.31", "gpui", "gpui_tokio", + "http_client", "indoc", "language", "language_model", diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index bb3fe6ff9078535b500e28f4beeab957929546a5..a168da05c83482f9d5b34118a74ee5e1f15e2e37 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -30,6 +30,7 @@ fs.workspace = true futures.workspace = true gpui.workspace = true gpui_tokio = { workspace = true, optional = true } +http_client.workspace = true indoc.workspace = true language.workspace = true language_model.workspace = true diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs index 2c2900cb79328249355704606652c54d08f072e5..b9751d7f63053bf073bcc8181f0cc2f8211d5c9f 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -7,15 +7,19 @@ mod gemini; pub mod e2e_tests; pub use claude::*; +use client::ProxySettings; +use collections::HashMap; pub use custom::*; use fs::Fs; pub use gemini::*; +use http_client::read_no_proxy_from_env; use project::agent_server_store::AgentServerStore; use acp_thread::AgentConnection; use anyhow::Result; -use gpui::{App, Entity, SharedString, Task}; +use gpui::{App, AppContext, Entity, SharedString, Task}; use project::Project; +use settings::SettingsStore; use std::{any::Any, path::Path, rc::Rc, sync::Arc}; pub use acp::AcpConnection; @@ -77,3 +81,25 @@ impl dyn AgentServer { self.into_any().downcast().ok() } } + +/// Load the default proxy environment variables to pass through to the agent +pub fn load_proxy_env(cx: &mut App) -> HashMap { + let proxy_url = cx + .read_global(|settings: &SettingsStore, _| settings.get::(None).proxy_url()); + let mut env = HashMap::default(); + + if let Some(proxy_url) = &proxy_url { + let env_var = if proxy_url.scheme() == "https" { + "HTTPS_PROXY" + } else { + "HTTP_PROXY" + }; + env.insert(env_var.to_owned(), proxy_url.to_string()); + } + + if let Some(no_proxy) = read_no_proxy_from_env() { + env.insert("NO_PROXY".to_owned(), no_proxy); + } + + env +} diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index c75c9539abe5fdd03293d98719d4a905b368c4a4..dafb7ea5b706af8e52562681d634dadf07ae488f 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -10,7 +10,7 @@ use anyhow::{Context as _, Result}; use gpui::{App, AppContext as _, SharedString, Task}; use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME}; -use crate::{AgentServer, AgentServerDelegate}; +use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; #[derive(Clone)] @@ -60,6 +60,7 @@ impl AgentServer for ClaudeCode { let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); + let extra_env = load_proxy_env(cx); let default_mode = self.default_mode(cx); cx.spawn(async move |cx| { @@ -70,7 +71,7 @@ impl AgentServer for ClaudeCode { .context("Claude Code is not registered")?; anyhow::Ok(agent.get_command( root_dir.as_deref(), - Default::default(), + extra_env, delegate.status_tx, delegate.new_version_available, &mut cx.to_async(), diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index f035952a7939201e4b7d990b97e1fc695105d505..7c8924a3dd65e9f963b418aa6872b1bc14886040 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -1,4 +1,4 @@ -use crate::AgentServerDelegate; +use crate::{AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use agent_client_protocol as acp; use anyhow::{Context as _, Result}; @@ -65,6 +65,7 @@ impl crate::AgentServer for CustomAgentServer { let is_remote = delegate.project.read(cx).is_via_remote_server(); let default_mode = self.default_mode(cx); let store = delegate.store.downgrade(); + let extra_env = load_proxy_env(cx); cx.spawn(async move |cx| { let (command, root_dir, login) = store @@ -76,7 +77,7 @@ impl crate::AgentServer for CustomAgentServer { })?; anyhow::Ok(agent.get_command( root_dir.as_deref(), - Default::default(), + extra_env, delegate.status_tx, delegate.new_version_available, &mut cx.to_async(), diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index e897bbd747b2ba7cf3947ed5cd3e594408bc1c2a..9407a42e68d34e38e78f2103b29f980f874fb3db 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -1,15 +1,12 @@ use std::rc::Rc; use std::{any::Any, path::Path}; -use crate::{AgentServer, AgentServerDelegate}; +use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use anyhow::{Context as _, Result}; -use client::ProxySettings; -use collections::HashMap; -use gpui::{App, AppContext, SharedString, Task}; +use gpui::{App, SharedString, Task}; use language_models::provider::google::GoogleLanguageModelProvider; use project::agent_server_store::GEMINI_NAME; -use settings::SettingsStore; #[derive(Clone)] pub struct Gemini; @@ -37,14 +34,12 @@ impl AgentServer for Gemini { let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); - let proxy_url = cx.read_global(|settings: &SettingsStore, _| { - settings.get::(None).proxy.clone() - }); + let mut extra_env = load_proxy_env(cx); let default_mode = self.default_mode(cx); cx.spawn(async move |cx| { - let mut extra_env = HashMap::default(); extra_env.insert("SURFACE".to_owned(), "zed".to_owned()); + if let Some(api_key) = cx .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)? .await @@ -52,7 +47,7 @@ impl AgentServer for Gemini { { extra_env.insert("GEMINI_API_KEY".into(), api_key); } - let (mut command, root_dir, login) = store + let (command, root_dir, login) = store .update(cx, |store, cx| { let agent = store .get_external_agent(&GEMINI_NAME.into()) @@ -67,14 +62,6 @@ impl AgentServer for Gemini { })?? .await?; - // Add proxy flag if proxy settings are configured in Zed and not in the args - if let Some(proxy_url_value) = &proxy_url - && !command.args.iter().any(|arg| arg.contains("--proxy")) - { - command.args.push("--proxy".into()); - command.args.push(proxy_url_value.clone()); - } - let connection = crate::acp::connect( name, command, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index cb8185c7ed326ed7d45726a99077c53903118316..8888c73f4496d39b33cb868444f39234f7d074f7 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -22,7 +22,7 @@ use futures::{ channel::oneshot, future::BoxFuture, }; use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions}; -use http_client::{HttpClient, HttpClientWithUrl, http}; +use http_client::{HttpClient, HttpClientWithUrl, http, read_proxy_from_env}; use parking_lot::RwLock; use postage::watch; use proxy::connect_proxy_stream; @@ -132,6 +132,20 @@ pub struct ProxySettings { pub proxy: Option, } +impl ProxySettings { + pub fn proxy_url(&self) -> Option { + self.proxy + .as_ref() + .and_then(|input| { + input + .parse::() + .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) + .ok() + }) + .or_else(read_proxy_from_env) + } +} + impl Settings for ProxySettings { type FileContent = ProxySettingsContent; diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index 62468573ed29687c0436e98a0174baa515b0ee3d..1429b7bf941fab5b1b508b977e898b8e153942d1 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -318,6 +318,12 @@ pub fn read_proxy_from_env() -> Option { .and_then(|env| env.parse().ok()) } +pub fn read_no_proxy_from_env() -> Option { + const ENV_VARS: &[&str] = &["NO_PROXY", "no_proxy"]; + + ENV_VARS.iter().find_map(|var| std::env::var(var).ok()) +} + pub struct BlockedHttpClient; impl BlockedHttpClient { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 63ee30043b61fd55af2edad7cd4a7a407af743bd..882c29e7b0f25c0f076e142bb7006ea4450c565e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -19,7 +19,6 @@ use git::GitHostingProviderRegistry; use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _}; use gpui_tokio::Tokio; -use http_client::{Url, read_proxy_from_env}; use language::LanguageRegistry; use onboarding::{FIRST_OPEN, show_onboarding_view}; use prompt_store::PromptBuilder; @@ -398,16 +397,7 @@ pub fn main() { std::env::consts::OS, std::env::consts::ARCH ); - let proxy_str = ProxySettings::get_global(cx).proxy.to_owned(); - let proxy_url = proxy_str - .as_ref() - .and_then(|input| { - input - .parse::() - .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) - .ok() - }) - .or_else(read_proxy_from_env); + let proxy_url = ProxySettings::get_global(cx).proxy_url(); let http = { let _guard = Tokio::handle(cx).enter();