use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
use std::time::Duration;
use serde::Serialize;
const INTROSPECT_MAX_AGE_SECS: u64 = 86_400;
const PROXY_PROBE_TIMEOUT: Duration = Duration::from_millis(150);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BridgeEngagement {
ProxyDown,
NoRequests,
Engaged,
}
#[derive(Debug, Clone, Serialize)]
pub struct BridgeStatus {
pub engagement: BridgeEngagement,
pub proxy_running: bool,
pub total_requests: u64,
pub tool_count: usize,
}
#[must_use]
pub fn classify(proxy_running: bool, total_requests: u64) -> BridgeEngagement {
match (proxy_running, total_requests > 0) {
(false, _) => BridgeEngagement::ProxyDown,
(true, false) => BridgeEngagement::NoRequests,
(true, true) => BridgeEngagement::Engaged,
}
}
impl BridgeStatus {
#[must_use]
pub fn detect() -> Self {
let proxy_running = probe_proxy(crate::proxy_setup::default_port());
let total_requests = persisted_request_count(INTROSPECT_MAX_AGE_SECS);
let tool_count = crate::server::registry::tool_count();
let engagement = classify(proxy_running, total_requests);
Self {
engagement,
proxy_running,
total_requests,
tool_count,
}
}
#[must_use]
pub fn summary_line(&self) -> String {
match self.engagement {
BridgeEngagement::Engaged => format!(
"Bridge: connected — {} tools, {} requests intercepted",
self.tool_count, self.total_requests
),
BridgeEngagement::NoRequests => format!(
"Bridge: proxy up, 0 requests intercepted — {} tools exposed (route the editor through lean-ctx)",
self.tool_count
),
BridgeEngagement::ProxyDown => format!(
"Bridge: OFF — proxy not reachable; savings cannot be measured ({} tools registered)",
self.tool_count
),
}
}
#[must_use]
pub fn zero_savings_reason(&self, tokens_saved: u64) -> Option<String> {
if tokens_saved > 0 {
return None;
}
Some(match self.engagement {
BridgeEngagement::ProxyDown => "saved=0 because the bridge is OFF — the proxy is not \
running, so no requests are intercepted. Start it (`lean-ctx serve`) and confirm \
`/lean-ctx` shows connected; reads/commands will then record savings."
.to_string(),
BridgeEngagement::NoRequests => {
"saved=0 because the proxy has not intercepted any LLM \
request yet. Verify your editor's mcp.json routes through lean-ctx (`/lean-ctx` → \
connected), then retry."
.to_string()
}
BridgeEngagement::Engaged => "saved=0 is real for this window — the bridge is engaged \
but no compressible context was seen yet (e.g. only cold first reads). Re-run a \
read to populate the cache and savings will appear."
.to_string(),
})
}
}
fn probe_proxy(port: u16) -> bool {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
TcpStream::connect_timeout(&addr, PROXY_PROBE_TIMEOUT).is_ok()
}
fn persisted_request_count(max_age_secs: u64) -> u64 {
crate::proxy::introspect::load_persisted(max_age_secs)
.as_ref()
.and_then(|v| v.get("cumulative"))
.and_then(|c| c.get("total_requests"))
.and_then(serde_json::Value::as_u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_proxy_down_when_not_running() {
assert_eq!(classify(false, 0), BridgeEngagement::ProxyDown);
assert_eq!(classify(false, 1234), BridgeEngagement::ProxyDown);
}
#[test]
fn classify_no_requests_when_running_but_idle() {
assert_eq!(classify(true, 0), BridgeEngagement::NoRequests);
}
#[test]
fn classify_engaged_when_running_with_traffic() {
assert_eq!(classify(true, 1), BridgeEngagement::Engaged);
assert_eq!(classify(true, 50_000), BridgeEngagement::Engaged);
}
#[test]
fn zero_savings_reason_is_none_when_savings_present() {
let status = BridgeStatus {
engagement: BridgeEngagement::Engaged,
proxy_running: true,
total_requests: 10,
tool_count: 69,
};
assert!(status.zero_savings_reason(42).is_none());
}
#[test]
fn zero_savings_reason_distinguishes_off_from_real_zero() {
let off = BridgeStatus {
engagement: BridgeEngagement::ProxyDown,
proxy_running: false,
total_requests: 0,
tool_count: 69,
};
let real = BridgeStatus {
engagement: BridgeEngagement::Engaged,
proxy_running: true,
total_requests: 10,
tool_count: 69,
};
let off_msg = off.zero_savings_reason(0).expect("off has a reason");
let real_msg = real.zero_savings_reason(0).expect("engaged has a reason");
assert!(off_msg.contains("bridge is OFF"), "got: {off_msg}");
assert!(real_msg.contains("is real"), "got: {real_msg}");
assert_ne!(off_msg, real_msg, "off and real-zero must differ");
}
#[test]
fn summary_line_reflects_engagement() {
let engaged = BridgeStatus {
engagement: BridgeEngagement::Engaged,
proxy_running: true,
total_requests: 7,
tool_count: 69,
};
let line = engaged.summary_line();
assert!(line.contains("connected"));
assert!(line.contains("69 tools"));
assert!(line.contains("7 requests"));
}
}