Skip to content

Commit 37948cc

Browse files
feat: add proxy configuration support
- Add proxy settings UI component with enable/disable toggle - Support HTTP, HTTPS, NO_PROXY, and ALL_PROXY environment variables - Store proxy settings in app database for persistence - Apply proxy settings on app startup and when saved - Pass proxy environment variables to Claude command execution - Integrate proxy settings into main Settings page with unified save - Add proxy support for both system binary and sidecar execution This allows users to configure proxy settings for Claude API requests, which is essential for users behind corporate firewalls or in regions requiring proxy access. Fixes network connectivity issues in restricted environments.
1 parent cee7134 commit 37948cc

File tree

8 files changed

+444
-1
lines changed

8 files changed

+444
-1
lines changed

src-tauri/src/claude_binary.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ fn compare_versions(a: &str, b: &str) -> Ordering {
509509
/// This ensures commands like Claude can find Node.js and other dependencies
510510
pub fn create_command_with_env(program: &str) -> Command {
511511
let mut cmd = Command::new(program);
512+
513+
info!("Creating command for: {}", program);
512514

513515
// Inherit essential environment variables from parent process
514516
for (key, value) in std::env::vars() {
@@ -525,11 +527,25 @@ pub fn create_command_with_env(program: &str) -> Command {
525527
|| key == "NVM_BIN"
526528
|| key == "HOMEBREW_PREFIX"
527529
|| key == "HOMEBREW_CELLAR"
530+
// Add proxy environment variables (only uppercase)
531+
|| key == "HTTP_PROXY"
532+
|| key == "HTTPS_PROXY"
533+
|| key == "NO_PROXY"
534+
|| key == "ALL_PROXY"
528535
{
529536
debug!("Inheriting env var: {}={}", key, value);
530537
cmd.env(&key, &value);
531538
}
532539
}
540+
541+
// Log proxy-related environment variables for debugging
542+
info!("Command will use proxy settings:");
543+
if let Ok(http_proxy) = std::env::var("HTTP_PROXY") {
544+
info!(" HTTP_PROXY={}", http_proxy);
545+
}
546+
if let Ok(https_proxy) = std::env::var("HTTPS_PROXY") {
547+
info!(" HTTPS_PROXY={}", https_proxy);
548+
}
533549

534550
// Add NVM support if the program is in an NVM directory
535551
if program.contains("/.nvm/versions/node/") {

src-tauri/src/commands/agents.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,18 @@ fn create_agent_sidecar_command(
796796
// Set working directory
797797
sidecar_cmd = sidecar_cmd.current_dir(project_path);
798798

799+
// Pass through proxy environment variables if they exist (only uppercase)
800+
for (key, value) in std::env::vars() {
801+
if key == "HTTP_PROXY"
802+
|| key == "HTTPS_PROXY"
803+
|| key == "NO_PROXY"
804+
|| key == "ALL_PROXY"
805+
{
806+
debug!("Setting proxy env var for agent sidecar: {}={}", key, value);
807+
sidecar_cmd = sidecar_cmd.env(&key, &value);
808+
}
809+
}
810+
799811
Ok(sidecar_cmd)
800812
}
801813

src-tauri/src/commands/claude.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,18 @@ fn create_sidecar_command(
288288
// Set working directory
289289
sidecar_cmd = sidecar_cmd.current_dir(project_path);
290290

291+
// Pass through proxy environment variables if they exist (only uppercase)
292+
for (key, value) in std::env::vars() {
293+
if key == "HTTP_PROXY"
294+
|| key == "HTTPS_PROXY"
295+
|| key == "NO_PROXY"
296+
|| key == "ALL_PROXY"
297+
{
298+
log::debug!("Setting proxy env var for sidecar: {}={}", key, value);
299+
sidecar_cmd = sidecar_cmd.env(&key, &value);
300+
}
301+
}
302+
291303
Ok(sidecar_cmd)
292304
}
293305

src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod mcp;
44
pub mod usage;
55
pub mod storage;
66
pub mod slash_commands;
7+
pub mod proxy;

src-tauri/src/commands/proxy.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use serde::{Deserialize, Serialize};
2+
use tauri::State;
3+
use rusqlite::params;
4+
5+
use crate::commands::agents::AgentDb;
6+
7+
#[derive(Debug, Serialize, Deserialize, Clone)]
8+
pub struct ProxySettings {
9+
pub http_proxy: Option<String>,
10+
pub https_proxy: Option<String>,
11+
pub no_proxy: Option<String>,
12+
pub all_proxy: Option<String>,
13+
pub enabled: bool,
14+
}
15+
16+
impl Default for ProxySettings {
17+
fn default() -> Self {
18+
Self {
19+
http_proxy: None,
20+
https_proxy: None,
21+
no_proxy: None,
22+
all_proxy: None,
23+
enabled: false,
24+
}
25+
}
26+
}
27+
28+
/// Get proxy settings from the database
29+
#[tauri::command]
30+
pub async fn get_proxy_settings(db: State<'_, AgentDb>) -> Result<ProxySettings, String> {
31+
let conn = db.0.lock().map_err(|e| e.to_string())?;
32+
33+
let mut settings = ProxySettings::default();
34+
35+
// Query each proxy setting
36+
let keys = vec![
37+
("proxy_enabled", "enabled"),
38+
("proxy_http", "http_proxy"),
39+
("proxy_https", "https_proxy"),
40+
("proxy_no", "no_proxy"),
41+
("proxy_all", "all_proxy"),
42+
];
43+
44+
for (db_key, field) in keys {
45+
if let Ok(value) = conn.query_row(
46+
"SELECT value FROM app_settings WHERE key = ?1",
47+
params![db_key],
48+
|row| row.get::<_, String>(0),
49+
) {
50+
match field {
51+
"enabled" => settings.enabled = value == "true",
52+
"http_proxy" => settings.http_proxy = Some(value).filter(|s| !s.is_empty()),
53+
"https_proxy" => settings.https_proxy = Some(value).filter(|s| !s.is_empty()),
54+
"no_proxy" => settings.no_proxy = Some(value).filter(|s| !s.is_empty()),
55+
"all_proxy" => settings.all_proxy = Some(value).filter(|s| !s.is_empty()),
56+
_ => {}
57+
}
58+
}
59+
}
60+
61+
Ok(settings)
62+
}
63+
64+
/// Save proxy settings to the database
65+
#[tauri::command]
66+
pub async fn save_proxy_settings(
67+
db: State<'_, AgentDb>,
68+
settings: ProxySettings,
69+
) -> Result<(), String> {
70+
let conn = db.0.lock().map_err(|e| e.to_string())?;
71+
72+
// Save each setting
73+
let values = vec![
74+
("proxy_enabled", settings.enabled.to_string()),
75+
("proxy_http", settings.http_proxy.clone().unwrap_or_default()),
76+
("proxy_https", settings.https_proxy.clone().unwrap_or_default()),
77+
("proxy_no", settings.no_proxy.clone().unwrap_or_default()),
78+
("proxy_all", settings.all_proxy.clone().unwrap_or_default()),
79+
];
80+
81+
for (key, value) in values {
82+
conn.execute(
83+
"INSERT OR REPLACE INTO app_settings (key, value) VALUES (?1, ?2)",
84+
params![key, value],
85+
).map_err(|e| format!("Failed to save {}: {}", key, e))?;
86+
}
87+
88+
// Apply the proxy settings immediately to the current process
89+
apply_proxy_settings(&settings);
90+
91+
Ok(())
92+
}
93+
94+
/// Apply proxy settings as environment variables
95+
pub fn apply_proxy_settings(settings: &ProxySettings) {
96+
log::info!("Applying proxy settings: enabled={}", settings.enabled);
97+
98+
if !settings.enabled {
99+
// Clear proxy environment variables if disabled
100+
log::info!("Clearing proxy environment variables");
101+
std::env::remove_var("HTTP_PROXY");
102+
std::env::remove_var("HTTPS_PROXY");
103+
std::env::remove_var("NO_PROXY");
104+
std::env::remove_var("ALL_PROXY");
105+
// Also clear lowercase versions
106+
std::env::remove_var("http_proxy");
107+
std::env::remove_var("https_proxy");
108+
std::env::remove_var("no_proxy");
109+
std::env::remove_var("all_proxy");
110+
return;
111+
}
112+
113+
// Ensure NO_PROXY includes localhost by default
114+
let mut no_proxy_list = vec!["localhost", "127.0.0.1", "::1", "0.0.0.0"];
115+
if let Some(user_no_proxy) = &settings.no_proxy {
116+
if !user_no_proxy.is_empty() {
117+
no_proxy_list.push(user_no_proxy.as_str());
118+
}
119+
}
120+
let no_proxy_value = no_proxy_list.join(",");
121+
122+
// Set proxy environment variables (uppercase is standard)
123+
if let Some(http_proxy) = &settings.http_proxy {
124+
if !http_proxy.is_empty() {
125+
log::info!("Setting HTTP_PROXY={}", http_proxy);
126+
std::env::set_var("HTTP_PROXY", http_proxy);
127+
}
128+
}
129+
130+
if let Some(https_proxy) = &settings.https_proxy {
131+
if !https_proxy.is_empty() {
132+
log::info!("Setting HTTPS_PROXY={}", https_proxy);
133+
std::env::set_var("HTTPS_PROXY", https_proxy);
134+
}
135+
}
136+
137+
// Always set NO_PROXY to include localhost
138+
log::info!("Setting NO_PROXY={}", no_proxy_value);
139+
std::env::set_var("NO_PROXY", &no_proxy_value);
140+
141+
if let Some(all_proxy) = &settings.all_proxy {
142+
if !all_proxy.is_empty() {
143+
log::info!("Setting ALL_PROXY={}", all_proxy);
144+
std::env::set_var("ALL_PROXY", all_proxy);
145+
}
146+
}
147+
148+
// Log current proxy environment variables for debugging
149+
log::info!("Current proxy environment variables:");
150+
for (key, value) in std::env::vars() {
151+
if key.contains("PROXY") || key.contains("proxy") {
152+
log::info!(" {}={}", key, value);
153+
}
154+
}
155+
}

src-tauri/src/main.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use commands::storage::{
4242
storage_list_tables, storage_read_table, storage_update_row, storage_delete_row,
4343
storage_insert_row, storage_execute_sql, storage_reset_database,
4444
};
45+
use commands::proxy::{get_proxy_settings, save_proxy_settings, apply_proxy_settings};
4546
use process::ProcessRegistryState;
4647
use std::sync::Mutex;
4748
use tauri::Manager;
@@ -57,6 +58,55 @@ fn main() {
5758
.setup(|app| {
5859
// Initialize agents database
5960
let conn = init_database(&app.handle()).expect("Failed to initialize agents database");
61+
62+
// Load and apply proxy settings from the database
63+
{
64+
let db = AgentDb(Mutex::new(conn));
65+
let proxy_settings = match db.0.lock() {
66+
Ok(conn) => {
67+
// Directly query proxy settings from the database
68+
let mut settings = commands::proxy::ProxySettings::default();
69+
70+
let keys = vec![
71+
("proxy_enabled", "enabled"),
72+
("proxy_http", "http_proxy"),
73+
("proxy_https", "https_proxy"),
74+
("proxy_no", "no_proxy"),
75+
("proxy_all", "all_proxy"),
76+
];
77+
78+
for (db_key, field) in keys {
79+
if let Ok(value) = conn.query_row(
80+
"SELECT value FROM app_settings WHERE key = ?1",
81+
rusqlite::params![db_key],
82+
|row| row.get::<_, String>(0),
83+
) {
84+
match field {
85+
"enabled" => settings.enabled = value == "true",
86+
"http_proxy" => settings.http_proxy = Some(value).filter(|s| !s.is_empty()),
87+
"https_proxy" => settings.https_proxy = Some(value).filter(|s| !s.is_empty()),
88+
"no_proxy" => settings.no_proxy = Some(value).filter(|s| !s.is_empty()),
89+
"all_proxy" => settings.all_proxy = Some(value).filter(|s| !s.is_empty()),
90+
_ => {}
91+
}
92+
}
93+
}
94+
95+
log::info!("Loaded proxy settings: enabled={}", settings.enabled);
96+
settings
97+
}
98+
Err(e) => {
99+
log::warn!("Failed to lock database for proxy settings: {}", e);
100+
commands::proxy::ProxySettings::default()
101+
}
102+
};
103+
104+
// Apply the proxy settings
105+
apply_proxy_settings(&proxy_settings);
106+
}
107+
108+
// Re-open the connection for the app to manage
109+
let conn = init_database(&app.handle()).expect("Failed to initialize agents database");
60110
app.manage(AgentDb(Mutex::new(conn)));
61111

62112
// Initialize checkpoint state
@@ -195,6 +245,10 @@ fn main() {
195245
commands::slash_commands::slash_command_get,
196246
commands::slash_commands::slash_command_save,
197247
commands::slash_commands::slash_command_delete,
248+
249+
// Proxy Settings
250+
get_proxy_settings,
251+
save_proxy_settings,
198252
])
199253
.run(tauri::generate_context!())
200254
.expect("error while running tauri application");

0 commit comments

Comments
 (0)