use serde::{Deserialize, Serialize};
use std::sync::atomic::AtomicU8;
use super::Config;
static SESSION_DEGRADE_LEVEL: AtomicU8 = AtomicU8::new(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Effort {
Minimal,
Low,
Medium,
High,
}
impl Effort {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"minimal" => Some(Self::Minimal),
"low" => Some(Self::Low),
"medium" => Some(Self::Medium),
"high" => Some(Self::High),
_ => None,
}
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Minimal => "minimal",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TeeMode {
Never,
Failures,
#[default]
HighCompression,
Always,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum RecoveryHints {
Off,
#[default]
Minimal,
Full,
}
impl RecoveryHints {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"off" | "none" | "false" => Some(Self::Off),
"minimal" | "min" | "on" | "true" => Some(Self::Minimal),
"full" => Some(Self::Full),
_ => None,
}
}
#[must_use]
pub fn from_env() -> Option<Self> {
Self::parse(&std::env::var("LEAN_CTX_RECOVERY_HINTS").ok()?)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Off => "off",
Self::Minimal => "minimal",
Self::Full => "full",
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TerseAgent {
#[default]
Off,
Lite,
Full,
Ultra,
}
impl TerseAgent {
pub fn from_env() -> Self {
match std::env::var("LEAN_CTX_TERSE_AGENT")
.unwrap_or_default()
.to_lowercase()
.as_str()
{
"lite" => Self::Lite,
"full" => Self::Full,
"ultra" => Self::Ultra,
_ => Self::Off,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OutputDensity {
#[default]
Normal,
Terse,
Ultra,
}
impl OutputDensity {
pub fn from_env() -> Self {
match std::env::var("LEAN_CTX_OUTPUT_DENSITY")
.unwrap_or_default()
.to_lowercase()
.as_str()
{
"terse" => Self::Terse,
"ultra" => Self::Ultra,
_ => Self::Normal,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ResponseVerbosity {
#[default]
Full,
HeadersOnly,
}
impl ResponseVerbosity {
pub fn effective() -> Self {
if let Ok(v) = std::env::var("LEAN_CTX_RESPONSE_VERBOSITY") {
match v.trim().to_lowercase().as_str() {
"headers_only" | "headers" | "minimal" => return Self::HeadersOnly,
"full" | "" => return Self::Full,
_ => {}
}
}
Config::load().response_verbosity
}
pub fn is_headers_only(&self) -> bool {
matches!(self, Self::HeadersOnly)
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum CompressionLevel {
Off,
#[default]
Lite,
Standard,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SessionDegrade {
Set(CompressionLevel),
Clear,
Leave,
}
impl CompressionLevel {
pub fn to_components(&self) -> (TerseAgent, OutputDensity, &'static str, bool) {
match self {
Self::Off => (TerseAgent::Off, OutputDensity::Normal, "off", false),
Self::Lite => (TerseAgent::Lite, OutputDensity::Terse, "off", true),
Self::Standard => (TerseAgent::Full, OutputDensity::Terse, "compact", true),
Self::Max => (TerseAgent::Ultra, OutputDensity::Ultra, "tdd", true),
}
}
pub fn from_legacy(terse_agent: &TerseAgent, output_density: &OutputDensity) -> Self {
match (terse_agent, output_density) {
(TerseAgent::Ultra, _) | (_, OutputDensity::Ultra) => Self::Max,
(TerseAgent::Full, _) => Self::Standard,
(TerseAgent::Lite, _) | (_, OutputDensity::Terse) => Self::Lite,
_ => Self::Off,
}
}
pub fn from_env() -> Option<Self> {
std::env::var("LEAN_CTX_COMPRESSION").ok().and_then(|v| {
match v.trim().to_lowercase().as_str() {
"off" => Some(Self::Off),
"lite" => Some(Self::Lite),
"standard" => Some(Self::Standard),
"max" => Some(Self::Max),
_ => None,
}
})
}
pub fn effective(config: &Config) -> Self {
if let Some(degraded) = Self::session_degrade_level() {
return degraded;
}
if let Some(env_level) = Self::from_env() {
return env_level;
}
if config.compression_level != Self::Off {
return config.compression_level;
}
if config.ultra_compact {
return Self::Max;
}
let ta_env = TerseAgent::from_env();
let od_env = OutputDensity::from_env();
let ta = if ta_env == TerseAgent::Off {
config.terse_agent.clone()
} else {
ta_env
};
let od = if od_env == OutputDensity::Normal {
config.output_density.clone()
} else {
od_env
};
Self::from_legacy(&ta, &od)
}
pub fn session_degrade_level() -> Option<Self> {
match SESSION_DEGRADE_LEVEL.load(std::sync::atomic::Ordering::Relaxed) {
1 => Some(Self::Off),
2 => Some(Self::Lite),
_ => None,
}
}
pub fn set_session_degrade(level: &Self) {
let val = match level {
Self::Off => 1u8,
Self::Lite => 2u8,
_ => 0u8,
};
SESSION_DEGRADE_LEVEL.store(val, std::sync::atomic::Ordering::Relaxed);
}
pub fn clear_session_degrade() {
SESSION_DEGRADE_LEVEL.store(0, std::sync::atomic::Ordering::Relaxed);
}
pub fn degrade_action(correction_count: u32, retrieve_count: u32) -> SessionDegrade {
let pressure = correction_count.max(retrieve_count);
if pressure >= 5 {
SessionDegrade::Set(Self::Off)
} else if pressure >= 3 {
SessionDegrade::Set(Self::Lite)
} else if pressure == 0 {
SessionDegrade::Clear
} else {
SessionDegrade::Leave
}
}
pub fn apply_degrade_action(action: SessionDegrade) {
match action {
SessionDegrade::Set(level) => Self::set_session_degrade(&level),
SessionDegrade::Clear => Self::clear_session_degrade(),
SessionDegrade::Leave => {}
}
}
pub fn from_str_label(s: &str) -> Option<Self> {
match s.trim().to_lowercase().as_str() {
"off" => Some(Self::Off),
"lite" => Some(Self::Lite),
"standard" | "std" => Some(Self::Standard),
"max" => Some(Self::Max),
_ => None,
}
}
pub fn is_active(&self) -> bool {
!matches!(self, Self::Off)
}
pub fn label(&self) -> &'static str {
match self {
Self::Off => "off",
Self::Lite => "lite",
Self::Standard => "standard",
Self::Max => "max",
}
}
pub fn description(&self) -> &'static str {
match self {
Self::Off => "No compression — full verbose output",
Self::Lite => "Light compression — concise output, basic terse filtering",
Self::Standard => {
"Standard compression — dense output, compact protocol, pattern-aware"
}
Self::Max => "Maximum compression — expert mode, TDD protocol, all layers active",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RulesScope {
Both,
Global,
Project,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RulesInjection {
Shared,
Dedicated,
Off,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionInheritance {
Off,
On,
}
#[cfg(test)]
mod tests {
use super::{CompressionLevel, SessionDegrade};
#[test]
fn degrade_action_thresholds_are_pressure_based() {
use CompressionLevel::{Lite, Off};
use SessionDegrade::{Clear, Leave, Set};
assert_eq!(CompressionLevel::degrade_action(5, 0), Set(Off));
assert_eq!(CompressionLevel::degrade_action(0, 5), Set(Off));
assert_eq!(CompressionLevel::degrade_action(9, 1), Set(Off));
assert_eq!(CompressionLevel::degrade_action(3, 0), Set(Lite));
assert_eq!(CompressionLevel::degrade_action(0, 4), Set(Lite));
assert_eq!(CompressionLevel::degrade_action(4, 4), Set(Lite));
assert_eq!(CompressionLevel::degrade_action(0, 0), Clear);
assert_eq!(CompressionLevel::degrade_action(2, 1), Leave);
assert_eq!(CompressionLevel::degrade_action(1, 2), Leave);
}
}