Skip to main content

lean_ctx_server/tools/
ctx_context.rs

1// SPDX-License-Identifier: Apache-2.0
2use crate::tools::CrpMode;
3use lean_ctx::core::cache::SessionCache;
4
5pub fn handle_status(cache: &SessionCache, turn_count: usize, crp_mode: CrpMode) -> String {
6    let entries = cache.get_all_entries();
7    let mut result = Vec::new();
8
9    result.push(format!("Multi-turn context (turn {turn_count}):"));
10    result.push(format!("  Cached files: {}", entries.len()));
11
12    let total_tokens: usize = entries.iter().map(|(_, e)| e.original_tokens).sum();
13    let total_reads: u32 = entries.iter().map(|(_, e)| e.read_count()).sum();
14    result.push(format!("  Total original tokens: {total_tokens}"));
15    result.push(format!("  Total reads: {total_reads}"));
16
17    let frequent: Vec<_> = entries.iter().filter(|(_, e)| e.read_count() > 1).collect();
18    if !frequent.is_empty() {
19        result.push(format!("\n  Frequently accessed ({}):", frequent.len()));
20        for (path, entry) in &frequent {
21            result.push(format!(
22                "    {} ({}x, {} tok)",
23                lean_ctx::core::protocol::shorten_path(path),
24                entry.read_count(),
25                entry.original_tokens
26            ));
27        }
28    }
29
30    let mode_label = match crp_mode {
31        CrpMode::Off => "off",
32        CrpMode::Compact => "compact",
33        CrpMode::Tdd => "tdd",
34    };
35    result.push(format!("\n  CRP mode: {mode_label}"));
36
37    let complexity = lean_ctx::core::adaptive::classify_from_context(cache);
38    result.push(format!("\n  {}", complexity.encoded_suffix()));
39
40    let hints = generate_prefill_hints(cache);
41    if !hints.is_empty() {
42        result.push("\nSMART HINTS:".to_string());
43        for hint in &hints {
44            result.push(format!("  → {hint}"));
45        }
46    }
47
48    result.join("\n")
49}
50
51fn generate_prefill_hints(cache: &SessionCache) -> Vec<String> {
52    let entries = cache.get_all_entries();
53    let mut hints = Vec::new();
54
55    let read_heavy: Vec<_> = entries
56        .iter()
57        .filter(|(_, e)| e.read_count() >= 3 && e.original_tokens > 500)
58        .collect();
59    for (path, entry) in &read_heavy {
60        let short = lean_ctx::core::protocol::shorten_path(path);
61        hints.push(format!(
62            "{short} read {}x ({} tok) — consider mode=map for future reads",
63            entry.read_count(),
64            entry.original_tokens
65        ));
66    }
67
68    let large: Vec<_> = entries
69        .iter()
70        .filter(|(_, e)| e.original_tokens > 2000 && e.read_count() <= 1)
71        .collect();
72    for (path, entry) in &large {
73        let short = lean_ctx::core::protocol::shorten_path(path);
74        hints.push(format!(
75            "{short} is large ({} tok) — consider mode=signatures or aggressive",
76            entry.original_tokens
77        ));
78    }
79
80    let stale_count = entries.iter().filter(|(_, e)| e.read_count() == 1).count();
81    if stale_count > 5 {
82        hints.push(format!(
83            "{stale_count} files read only once — ctx_control(action=\"evict\", targets=…) frees their context"
84        ));
85    }
86
87    hints.truncate(5);
88    hints
89}