Skip to main content

lean_ctx/core/config/
enterprise.rs

1//! `[enterprise]` — connect a lean-ctx Runtime to a LeanCTX Enterprise Suite.
2//!
3//! When `gateway_url` is set, the proxy injects `x-leanctx-*` metadata headers
4//! on every upstream request so the Suite can attribute context savings,
5//! populate the economics ledger, and make routing decisions.
6
7use serde::{Deserialize, Serialize};
8
9/// Enterprise Suite connection configuration (`[enterprise]` in config.toml).
10///
11/// Example:
12/// ```toml
13/// [enterprise]
14/// gateway_url = "https://api.leanctx.com"
15/// instance_token = "lctx_inst_..."
16/// instance_id = "runtime-macbook-yves"
17/// ```
18#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
19#[serde(default)]
20pub struct EnterpriseConfig {
21    /// Base URL of the LeanCTX Enterprise Suite Gateway.
22    /// When set, all provider requests are routed through this gateway AND
23    /// `x-leanctx-*` metadata headers are injected.
24    /// Env override: `LEAN_CTX_ENTERPRISE_GATEWAY_URL`.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub gateway_url: Option<String>,
27
28    /// Bearer token for authenticating this Runtime instance with the Suite.
29    /// Env override: `LEAN_CTX_ENTERPRISE_TOKEN`.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub instance_token: Option<String>,
32
33    /// Stable identifier for this Runtime instance (e.g. "macbook-yves",
34    /// "ci-runner-3"). Sent as `x-leanctx-instance`. Auto-generated from
35    /// hostname if unset.
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub instance_id: Option<String>,
38
39    /// Whether to inject `x-leanctx-*` headers even when NOT routing through
40    /// the gateway (i.e. when using direct provider upstreams but still wanting
41    /// to report metadata to a sidecar collector). Default: headers are only
42    /// injected when `gateway_url` is set.
43    #[serde(default)]
44    pub always_inject_headers: bool,
45
46    /// Disable the enterprise integration without removing the config block.
47    #[serde(default)]
48    pub disabled: bool,
49}
50
51impl EnterpriseConfig {
52    /// Resolve effective gateway URL (config → env).
53    pub fn effective_gateway_url(&self) -> Option<String> {
54        self.effective_gateway_url_owned()
55    }
56
57    /// Resolve effective gateway URL with env override (owned).
58    pub fn effective_gateway_url_owned(&self) -> Option<String> {
59        if self.disabled {
60            return None;
61        }
62        std::env::var("LEAN_CTX_ENTERPRISE_GATEWAY_URL")
63            .ok()
64            .filter(|s| !s.is_empty())
65            .or_else(|| self.gateway_url.clone())
66    }
67
68    /// Resolve the instance token (config → env).
69    pub fn effective_token(&self) -> Option<String> {
70        if self.disabled {
71            return None;
72        }
73        std::env::var("LEAN_CTX_ENTERPRISE_TOKEN")
74            .ok()
75            .filter(|s| !s.is_empty())
76            .or_else(|| self.instance_token.clone())
77    }
78
79    /// Resolve instance ID (config → hostname fallback).
80    pub fn effective_instance_id(&self) -> String {
81        if let Some(id) = &self.instance_id {
82            return id.clone();
83        }
84        gethostname::gethostname()
85            .into_string()
86            .unwrap_or_else(|_| "unknown".to_owned())
87    }
88
89    /// Whether header injection is active for the current request.
90    pub fn should_inject_headers(&self) -> bool {
91        if self.disabled {
92            return false;
93        }
94        self.always_inject_headers || self.gateway_url.is_some()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn disabled_blocks_everything() {
104        let cfg = EnterpriseConfig {
105            gateway_url: Some("https://api.leanctx.com".to_owned()),
106            instance_token: Some("tok".to_owned()),
107            disabled: true,
108            ..Default::default()
109        };
110        assert_eq!(cfg.effective_gateway_url(), None);
111        assert_eq!(cfg.effective_token(), None);
112        assert!(!cfg.should_inject_headers());
113    }
114
115    #[test]
116    fn headers_injected_when_gateway_set() {
117        let cfg = EnterpriseConfig {
118            gateway_url: Some("https://api.leanctx.com".to_owned()),
119            ..Default::default()
120        };
121        assert!(cfg.should_inject_headers());
122    }
123
124    #[test]
125    fn always_inject_without_gateway() {
126        let cfg = EnterpriseConfig {
127            always_inject_headers: true,
128            ..Default::default()
129        };
130        assert!(cfg.should_inject_headers());
131    }
132
133    #[test]
134    fn instance_id_fallback() {
135        let cfg = EnterpriseConfig::default();
136        let id = cfg.effective_instance_id();
137        assert!(!id.is_empty(), "`id` is empty");
138    }
139}