Skip to main content

lean_ctx/core/config/
response_shaping.rs

1//! Configuration for proxy response shaping (#1125).
2
3use serde::{Deserialize, Serialize};
4
5/// Top-level response-shaping configuration.
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(default)]
8pub struct ResponseShapingConfig {
9    /// Enable proxy response shaping. Defaults to the conservative gentle mode.
10    pub enabled: bool,
11    /// `off`, `gentle`, or `aggressive`.
12    pub mode: String,
13    pub preamble: PreambleConfig,
14    pub code_repetition: CodeRepetitionConfig,
15    pub narration: NarrationConfig,
16    pub confirmation: ConfirmationConfig,
17}
18
19impl Default for ResponseShapingConfig {
20    fn default() -> Self {
21        Self {
22            enabled: true,
23            mode: "gentle".into(),
24            preamble: PreambleConfig::default(),
25            code_repetition: CodeRepetitionConfig::default(),
26            narration: NarrationConfig::default(),
27            confirmation: ConfirmationConfig::default(),
28        }
29    }
30}
31
32/// Preamble stripping controls.
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34#[serde(default)]
35pub struct PreambleConfig {
36    pub strip: bool,
37    /// Maximum preamble length in approximate tokens.
38    pub max_pattern_length: usize,
39}
40
41impl Default for PreambleConfig {
42    fn default() -> Self {
43        Self {
44            strip: true,
45            max_pattern_length: 50,
46        }
47    }
48}
49
50/// Reserved for explicit aggressive code-repetition shaping. It remains off by
51/// default because a read-cache baseline is required to prove repetition.
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53#[serde(default)]
54pub struct CodeRepetitionConfig {
55    pub enabled: bool,
56    pub similarity_threshold: f64,
57    pub min_block_lines: usize,
58}
59
60impl Default for CodeRepetitionConfig {
61    fn default() -> Self {
62        Self {
63            enabled: false,
64            similarity_threshold: 0.8,
65            min_block_lines: 10,
66        }
67    }
68}
69
70/// Post-action narration controls.
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72#[serde(default)]
73pub struct NarrationConfig {
74    pub compress_post_action: bool,
75    pub max_summary_tokens: usize,
76}
77
78impl Default for NarrationConfig {
79    fn default() -> Self {
80        Self {
81            compress_post_action: true,
82            max_summary_tokens: 30,
83        }
84    }
85}
86
87/// Trailing confirmation controls.
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89#[serde(default)]
90pub struct ConfirmationConfig {
91    pub strip_trailing: bool,
92}
93
94impl Default for ConfirmationConfig {
95    fn default() -> Self {
96        Self {
97            strip_trailing: true,
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn defaults_are_enabled_and_gentle() {
108        let cfg = ResponseShapingConfig::default();
109        assert!(cfg.enabled);
110        assert_eq!(cfg.mode, "gentle");
111        assert!(cfg.preamble.strip);
112        assert!(cfg.confirmation.strip_trailing);
113    }
114
115    #[test]
116    fn proposed_toml_shape_round_trips() {
117        let cfg: ResponseShapingConfig = toml::from_str(
118            r#"
119            enabled = true
120            mode = "aggressive"
121            [preamble]
122            max_pattern_length = 42
123            [code_repetition]
124            enabled = true
125            similarity_threshold = 0.9
126            min_block_lines = 12
127            [narration]
128            max_summary_tokens = 24
129            [confirmation]
130            strip_trailing = false
131            "#,
132        )
133        .expect("response-shaping TOML");
134        assert_eq!(cfg.mode, "aggressive");
135        assert_eq!(cfg.preamble.max_pattern_length, 42);
136        assert!(cfg.code_repetition.enabled);
137        assert_eq!(cfg.narration.max_summary_tokens, 24);
138        assert!(!cfg.confirmation.strip_trailing);
139    }
140
141    #[test]
142    fn omitted_nested_values_use_safe_defaults() {
143        let cfg: ResponseShapingConfig = toml::from_str("mode = \"off\"").unwrap();
144        assert!(!cfg.enabled || cfg.mode == "off");
145        assert_eq!(cfg.code_repetition, CodeRepetitionConfig::default());
146        assert_eq!(cfg.narration, NarrationConfig::default());
147    }
148}