use crate::cognitive_cache::{ConfidenceTier, HotCache, HotCacheEntry};
use crate::token_budget::TokenBudget;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionLevel {
None,
Light,
Heavy,
}
#[derive(Debug, Clone)]
pub struct ColdRecall {
pub memory_id: i64,
pub content: String,
pub relevance_score: f32,
pub tier: ConfidenceTier,
}
pub fn build_context_md(hot: &HotCache, cold: &[ColdRecall], max_tokens: usize) -> String {
if max_tokens == 0 {
return String::new();
}
let mut output = String::new();
let mut current_tokens = 0;
output.push_str("# Nexus Project Context\n\n");
let loud_entries: Vec<&HotCacheEntry> = hot
.entries
.iter()
.filter(|e| e.tier == ConfidenceTier::Loud)
.collect();
let clear_entries: Vec<&HotCacheEntry> = hot
.entries
.iter()
.filter(|e| e.tier == ConfidenceTier::Clear)
.collect();
let whisper_entries: Vec<&HotCacheEntry> = hot
.entries
.iter()
.filter(|e| e.tier == ConfidenceTier::Whisper)
.collect();
let loud_section = format_tier_section(
"## High Relevance (Loud)",
&loud_entries,
CompressionLevel::None,
);
let loud_tokens = TokenBudget::estimate_tokens(&loud_section);
if current_tokens + loud_tokens <= max_tokens {
output.push_str(&loud_section);
current_tokens += loud_tokens;
} else {
return output;
}
let clear_section = format_tier_section(
"## Relevant (Clear)",
&clear_entries,
CompressionLevel::Light,
);
let clear_tokens = TokenBudget::estimate_tokens(&clear_section);
if current_tokens + clear_tokens <= max_tokens {
output.push_str(&clear_section);
current_tokens += clear_tokens;
}
if (current_tokens as f32 / max_tokens as f32) < 0.80 {
let whisper_section = format_tier_section(
"## Low Signal (Whisper)",
&whisper_entries,
CompressionLevel::Heavy,
);
let whisper_tokens = TokenBudget::estimate_tokens(&whisper_section);
if current_tokens + whisper_tokens <= max_tokens {
output.push_str(&whisper_section);
current_tokens += whisper_tokens;
}
}
if !cold.is_empty() && max_tokens - current_tokens > 200 {
output.push_str("## Recalled Memories\n\n");
for recall in cold {
let entry_str = format!(
"- [Recall {}] {}\n",
recall.memory_id,
compress_text(&recall.content, CompressionLevel::Light)
);
let entry_tokens = TokenBudget::estimate_tokens(&entry_str);
if current_tokens + entry_tokens <= max_tokens {
output.push_str(&entry_str);
current_tokens += entry_tokens;
} else {
break;
}
}
}
output
}
fn format_tier_section(
title: &str,
entries: &[&HotCacheEntry],
compression: CompressionLevel,
) -> String {
if entries.is_empty() {
return String::new();
}
let mut section = format!("{}\n\n", title);
for entry in entries {
let content = compress_text(&entry.content, compression);
section.push_str(&format!("### Memory {}\n{}\n\n", entry.memory_id, content));
}
section
}
fn compress_text(text: &str, level: CompressionLevel) -> String {
match level {
CompressionLevel::None => text.to_string(),
CompressionLevel::Light => text.lines().next().unwrap_or("").to_string(),
CompressionLevel::Heavy => {
let first_line = text.lines().next().unwrap_or("");
if first_line.len() > 80 {
let truncate_at = first_line
.char_indices()
.take_while(|(idx, _)| *idx < 77)
.last()
.map(|(idx, c)| idx + c.len_utf8())
.unwrap_or(0);
format!("{}...", &first_line[..truncate_at])
} else {
first_line.to_string()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cognitive_cache::ConfidenceTier;
use chrono::Utc;
#[test]
fn test_build_context_md_ordering_and_budget() {
let mut hot = HotCache::default();
hot.entries.push(HotCacheEntry {
memory_id: 1,
content: "Loud Content".into(),
relevance_score: 0.9,
tier: ConfidenceTier::Loud,
promoted_at: Utc::now(),
last_surfaced: Utc::now(),
hot_streak: 1,
pinned: false,
source_agent: None,
});
hot.entries.push(HotCacheEntry {
memory_id: 2,
content: "Clear Content Line 1\nLine 2".into(),
relevance_score: 0.75,
tier: ConfidenceTier::Clear,
promoted_at: Utc::now(),
last_surfaced: Utc::now(),
hot_streak: 1,
pinned: false,
source_agent: None,
});
let context = build_context_md(&hot, &[], 1000);
assert!(context.contains("High Relevance (Loud)"));
assert!(context.contains("Loud Content"));
assert!(context.contains("Relevant (Clear)"));
assert!(context.contains("Clear Content Line 1"));
assert!(!context.contains("Line 2"));
let tight_context = build_context_md(&hot, &[], 10);
assert!(tight_context.len() < context.len());
}
#[test]
fn test_build_context_md_with_cold_recall() {
let hot = HotCache::default();
let cold = vec![ColdRecall {
memory_id: 99,
content: "Cold Content".into(),
relevance_score: 0.68,
tier: ConfidenceTier::Whisper,
}];
let context = build_context_md(&hot, &cold, 1000);
assert!(context.contains("Recalled Memories"));
assert!(context.contains("[Recall 99] Cold Content"));
}
#[test]
fn test_compress_text_utf8_multibyte() {
let long_multibyte = "ア".repeat(40); let compressed = compress_text(&long_multibyte, CompressionLevel::Heavy);
assert!(compressed.ends_with("..."));
assert!(compressed.is_char_boundary(compressed.len()));
assert!(compressed.len() <= 83);
}
}