kmp_application/queries/
bundle_truncator.rs1use kmp_domain::{KmpMode, ResolutionTier, TierBudget, TokenEstimator};
2
3use super::bundle_section_renderer::TaggedSection;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct TruncationMetadata {
7 pub budget_requested: u32,
8 pub budget_used: u32,
9 pub total_before_truncation: u32,
10 pub sections_kept: u32,
11 pub sections_dropped: u32,
12 pub token_estimator: String,
13}
14
15pub(crate) fn limit_sections_by_tier_budget(
21 sections: Vec<TaggedSection>,
22 token_budget: Option<u32>,
23 resolved_mode: KmpMode,
24 estimator: &dyn TokenEstimator,
25) -> (Vec<(String, String)>, Option<TruncationMetadata>) {
26 let Some(budget) = token_budget else {
27 let pairs = sections
28 .into_iter()
29 .map(|s| (s.content, s.source_id))
30 .collect();
31 return (pairs, None);
32 };
33
34 let tier_budget = TierBudget::from_total_with_mode(budget, resolved_mode);
35 let total_sections = sections.len() as u32;
36 let total_before: u32 = sections
37 .iter()
38 .map(|s| estimator.estimate_tokens(&s.content))
39 .sum();
40
41 let mut l0_used = 0u32;
42 let mut l1_used = 0u32;
43 let mut l2_used = 0u32;
44 let mut kept = Vec::new();
45 let mut total_used = 0u32;
46
47 for section in sections {
48 let tokens = estimator.estimate_tokens(§ion.content);
49 let (tier_used, tier_cap) = match section.tier {
50 ResolutionTier::L0Summary => (&mut l0_used, tier_budget.l0),
51 ResolutionTier::L1CausalSpine => (&mut l1_used, tier_budget.l1),
52 ResolutionTier::L2EvidencePack => (&mut l2_used, tier_budget.l2),
53 };
54
55 let fits_tier = kept.is_empty() || *tier_used + tokens <= tier_cap;
57 let fits_total = kept.is_empty() || total_used + tokens <= budget;
58
59 if fits_tier && fits_total {
60 *tier_used += tokens;
61 total_used += tokens;
62 kept.push((section.content, section.source_id));
63 }
64 }
66
67 let sections_kept = kept.len() as u32;
68 let truncation = TruncationMetadata {
69 budget_requested: budget,
70 budget_used: total_used,
71 total_before_truncation: total_before,
72 sections_kept,
73 sections_dropped: total_sections - sections_kept,
74 token_estimator: estimator.name().to_string(),
75 };
76
77 (kept, Some(truncation))
78}