Skip to main content

lean_ctx/core/
protocol.rs

1use std::path::Path;
2
3pub fn shorten_path(path: &str) -> String {
4    let p = Path::new(path);
5    if let Some(name) = p.file_name() {
6        return name.to_string_lossy().to_string();
7    }
8    path.to_string()
9}
10
11#[allow(dead_code)]
12pub fn format_type_short(ty: &str) -> String {
13    match ty {
14        "string" | "String" => ":s".to_string(),
15        "number" | "i32" | "i64" | "u32" | "u64" | "usize" | "f32" | "f64" => ":n".to_string(),
16        "boolean" | "bool" => ":b".to_string(),
17        "void" | "()" => "".to_string(),
18        t if t.starts_with("Promise<") => format!("→{}", &t[8..t.len() - 1]),
19        t if t.starts_with("Option<") => format!(":?{}", &t[7..t.len() - 1]),
20        t if t.starts_with("Vec<") => format!(":[{}]", &t[4..t.len() - 1]),
21        t if t.starts_with("Result<") => format!("→!{}", &t[7..t.len() - 1]),
22        _ => format!(":{ty}"),
23    }
24}
25
26pub fn format_savings(original: usize, compressed: usize) -> String {
27    let saved = original.saturating_sub(compressed);
28    if original == 0 {
29        return "0 tok saved".to_string();
30    }
31    let pct = (saved as f64 / original as f64 * 100.0).round() as usize;
32    format!("[{saved} tok saved ({pct}%)]")
33}
34
35pub struct InstructionTemplate {
36    pub code: &'static str,
37    pub full: &'static str,
38}
39
40const TEMPLATES: &[InstructionTemplate] = &[
41    InstructionTemplate {
42        code: "ACT1",
43        full: "Act immediately, report result in one line",
44    },
45    InstructionTemplate {
46        code: "BRIEF",
47        full: "Summarize approach in 1-2 lines, then act",
48    },
49    InstructionTemplate {
50        code: "FULL",
51        full: "Outline approach, consider edge cases, then act",
52    },
53    InstructionTemplate {
54        code: "DELTA",
55        full: "Only show changed lines, not full files",
56    },
57    InstructionTemplate {
58        code: "NOREPEAT",
59        full: "Never repeat known context. Reference cached files by Fn ID",
60    },
61    InstructionTemplate {
62        code: "STRUCT",
63        full: "Use notation, not sentences. Changes: +line/-line/~line",
64    },
65    InstructionTemplate {
66        code: "1LINE",
67        full: "One line per action. Summarize, don't explain",
68    },
69    InstructionTemplate {
70        code: "NODOC",
71        full: "Don't add comments that narrate what code does",
72    },
73    InstructionTemplate {
74        code: "ACTFIRST",
75        full: "Execute tool calls immediately. Never narrate before acting",
76    },
77    InstructionTemplate {
78        code: "QUALITY",
79        full: "Never skip edge case analysis or error handling to save tokens",
80    },
81    InstructionTemplate {
82        code: "NOMOCK",
83        full: "Never use mock data, fake values, or placeholder code",
84    },
85    InstructionTemplate {
86        code: "FREF",
87        full: "Reference files by Fn refs only, never full paths",
88    },
89    InstructionTemplate {
90        code: "DIFF",
91        full: "For code changes: show only diff lines, not full files",
92    },
93    InstructionTemplate {
94        code: "ABBREV",
95        full: "Use abbreviations: fn, cfg, impl, deps, req, res, ctx, err",
96    },
97    InstructionTemplate {
98        code: "SYMBOLS",
99        full: "Use TDD notation: +=add -=remove ~=modify ->=returns ok/fail for status",
100    },
101];
102
103/// Build the decoder block that explains all instruction codes (sent once per session).
104pub fn instruction_decoder_block() -> String {
105    let mut lines = vec!["INSTRUCTION CODES:".to_string()];
106    for t in TEMPLATES {
107        lines.push(format!("  {} = {}", t.code, t.full));
108    }
109    lines.join("\n")
110}
111
112/// Encode an instruction suffix using short codes with budget hints.
113/// Response budget is dynamic based on task complexity to shape LLM output length.
114pub fn encode_instructions(complexity: &str) -> String {
115    match complexity {
116        "mechanical" => "MODE: ACT1 DELTA 1LINE | BUDGET: <=50 tokens, 1 line answer".to_string(),
117        "simple" => "MODE: BRIEF DELTA 1LINE | BUDGET: <=100 tokens, structured".to_string(),
118        "standard" => "MODE: BRIEF DELTA NOREPEAT STRUCT | BUDGET: <=200 tokens".to_string(),
119        "complex" => {
120            "MODE: FULL QUALITY NOREPEAT STRUCT FREF DIFF | BUDGET: <=500 tokens".to_string()
121        }
122        "architectural" => {
123            "MODE: FULL QUALITY NOREPEAT STRUCT FREF | BUDGET: unlimited".to_string()
124        }
125        _ => "MODE: BRIEF | BUDGET: <=200 tokens".to_string(),
126    }
127}
128
129/// Encode instructions with SNR metric for context quality awareness.
130pub fn encode_instructions_with_snr(complexity: &str, compression_pct: f64) -> String {
131    let snr = if compression_pct > 0.0 {
132        1.0 - (compression_pct / 100.0)
133    } else {
134        1.0
135    };
136    let base = encode_instructions(complexity);
137    format!("{base} | SNR: {snr:.2}")
138}
139
140/// Estimate token savings of encoded vs full instruction text.
141#[allow(dead_code)]
142pub fn instruction_encoding_savings() -> (usize, usize) {
143    use super::tokens::count_tokens;
144    let decoder = instruction_decoder_block();
145    let decoder_cost = count_tokens(&decoder);
146
147    let full_mechanical = "TASK COMPLEXITY: mechanical\nMinimal reasoning needed. Act immediately, report result in one line.";
148    let encoded_mechanical = "MODE: ACT1 DELTA 1LINE | BUDGET: <=50 tokens, 1 line answer";
149
150    let saving_per_call =
151        count_tokens(full_mechanical).saturating_sub(count_tokens(encoded_mechanical));
152    (decoder_cost, saving_per_call.max(1))
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn decoder_block_contains_all_codes() {
161        let block = instruction_decoder_block();
162        for t in TEMPLATES {
163            assert!(
164                block.contains(t.code),
165                "decoder should contain code {}",
166                t.code
167            );
168        }
169    }
170
171    #[test]
172    fn encoded_instructions_are_compact() {
173        use super::super::tokens::count_tokens;
174        let full = "TASK COMPLEXITY: mechanical\nMinimal reasoning needed. Act immediately, report result in one line. Show only changed lines, not full files.";
175        let encoded = encode_instructions("mechanical");
176        assert!(
177            count_tokens(&encoded) <= count_tokens(full),
178            "encoded ({}) should be <= full ({})",
179            count_tokens(&encoded),
180            count_tokens(full)
181        );
182    }
183
184    #[test]
185    fn encoding_has_positive_savings() {
186        let (decoder_cost, saving_per_call) = instruction_encoding_savings();
187        assert!(decoder_cost > 0);
188        assert!(saving_per_call > 0);
189    }
190
191    #[test]
192    fn all_complexity_levels_encode() {
193        for level in &["mechanical", "standard", "architectural"] {
194            let encoded = encode_instructions(level);
195            assert!(encoded.starts_with("MODE:"), "should start with MODE:");
196        }
197    }
198}