1pub const MODEL_OUTPUT_BUDGET: usize = 30_000;
10
11#[must_use]
18pub fn truncate_for_model(text: &str, budget: usize) -> (String, bool) {
19 if text.len() <= budget {
20 return (text.to_string(), false);
21 }
22
23 let head_len = budget / 2;
24 let tail_len = budget - head_len;
25 let head_end = floor_boundary(text, head_len);
26 let tail_start = ceil_boundary(text, text.len() - tail_len);
27 let elided = tail_start.saturating_sub(head_end);
28
29 let marker = format!("\n[... {elided} bytes truncated ...]\n");
30 let mut out = String::with_capacity(head_end + marker.len() + (text.len() - tail_start));
31 out.push_str(&text[..head_end]);
32 out.push_str(&marker);
33 out.push_str(&text[tail_start..]);
34 (out, true)
35}
36
37fn floor_boundary(text: &str, mut idx: usize) -> usize {
39 if idx >= text.len() {
40 return text.len();
41 }
42 while idx > 0 && !text.is_char_boundary(idx) {
43 idx -= 1;
44 }
45 idx
46}
47
48fn ceil_boundary(text: &str, mut idx: usize) -> usize {
50 while idx < text.len() && !text.is_char_boundary(idx) {
51 idx += 1;
52 }
53 idx
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn below_budget_is_untouched() {
62 let (out, truncated) = truncate_for_model("short", 100);
63 assert_eq!(out, "short");
64 assert!(!truncated);
65 }
66
67 #[test]
68 fn over_budget_keeps_head_and_tail() {
69 let text: String = (0..1000).map(|_| 'x').collect::<String>() + "TAILMARK";
70 let head: String = std::iter::repeat_n('H', 500).collect();
71 let text = head.clone() + &text;
72 let (out, truncated) = truncate_for_model(&text, 200);
73 assert!(truncated);
74 assert!(out.starts_with("HHHH"), "keeps the head");
75 assert!(out.ends_with("TAILMARK"), "keeps the tail");
76 assert!(out.contains("truncated"), "has the marker");
77 assert!(out.len() < text.len());
78 }
79
80 #[test]
81 fn utf8_multibyte_seam_does_not_panic() {
82 let text: String = std::iter::repeat_n('あ', 10_000).collect();
84 let (out, truncated) = truncate_for_model(&text, 1000);
85 assert!(truncated);
86 assert!(out.starts_with('あ') && out.ends_with('あ'));
88 }
89}