Skip to main content

lean_ctx/proxy_setup/
util.rs

1//! Shared proxy-setup constants and helpers.
2
3pub(crate) const PROXY_ENV_START: &str = "# >>> lean-ctx proxy env >>>";
4pub(crate) const PROXY_ENV_END: &str = "# <<< lean-ctx proxy env <<<";
5pub(crate) const DEFAULT_PROXY_PORT: u16 = 4444;
6
7/// Comment written in place of the `ANTHROPIC_BASE_URL` export when no Anthropic API
8/// key is detectable. A Claude Pro/Max subscription authenticates via OAuth against
9/// `api.anthropic.com` directly and is rejected by any custom base URL, so we must not
10/// route it through the proxy.
11pub(crate) const ANTHROPIC_OMITTED_NOTE: &str = "ANTHROPIC_BASE_URL omitted: Claude Pro/Max subscription authenticates against api.anthropic.com directly (set ANTHROPIC_API_KEY to route Claude through the proxy)";
12
13/// Comment written when Grok is not routable through the proxy (no session and no API key).
14pub(crate) const GROK_OMITTED_NOTE: &str = "Grok proxy env omitted: run `grok login` (subscription) or set XAI_API_KEY to route Grok through lean-ctx";
15
16/// Comment written when Command Code is not routable through the proxy (no session and no API key).
17pub(crate) const COMMANDCODE_OMITTED_NOTE: &str =
18    "Command Code omitted (no ~/.commandcode auth — run `cmd login` or set COMMAND_CODE_API_KEY)";
19
20pub fn is_local_lean_ctx_url(url: &str) -> bool {
21    url.starts_with("http://127.0.0.1:") || url.starts_with("http://localhost:")
22}
23
24/// Proxy reachability timeout. Priority: env var > config.toml > 200ms default.
25pub fn proxy_timeout() -> std::time::Duration {
26    if let Ok(val) = std::env::var("LEAN_CTX_PROXY_TIMEOUT_MS")
27        && let Ok(ms) = val.parse::<u64>()
28    {
29        return std::time::Duration::from_millis(ms);
30    }
31    if let Some(ms) = crate::core::config::Config::load().proxy_timeout_ms {
32        return std::time::Duration::from_millis(ms);
33    }
34    std::time::Duration::from_millis(200)
35}
36
37pub(crate) fn is_proxy_reachable(port: u16) -> bool {
38    use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
39    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
40    TcpStream::connect_timeout(&addr, proxy_timeout()).is_ok()
41}
42
43pub fn default_port() -> u16 {
44    if let Ok(val) = std::env::var("LEAN_CTX_PROXY_PORT")
45        && let Ok(port) = val.parse::<u16>()
46    {
47        return port;
48    }
49    let cfg = crate::core::config::Config::load();
50    if let Some(port) = cfg.proxy_port {
51        return port;
52    }
53    uid_based_port()
54}
55
56/// Derives a deterministic port from the user's UID to avoid collisions
57/// on multi-user systems. uid 1000 → 4444, uid 1001 → 4445, etc.
58/// System accounts (uid < 1000) and root always get the base port 4444.
59pub(crate) fn uid_based_port() -> u16 {
60    #[cfg(unix)]
61    {
62        // SAFETY: `getuid` takes no arguments, always succeeds, and only reads
63        // the calling process's real UID — no preconditions, no UB.
64        let uid = unsafe { libc::getuid() } as u16;
65        let offset = uid.saturating_sub(1000) % 1000;
66        DEFAULT_PROXY_PORT + offset
67    }
68    #[cfg(not(unix))]
69    {
70        DEFAULT_PROXY_PORT
71    }
72}