use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::compress::{estimate_tokens, truncate_to_tokens};
pub trait Referable {
fn index(&self) -> usize;
fn url(&self) -> &str;
}
impl<T: Referable> Referable for &T {
fn index(&self) -> usize {
(*self).index()
}
fn url(&self) -> &str {
(*self).url()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Reference {
pub index: usize,
pub url: String,
}
impl Referable for Reference {
fn index(&self) -> usize {
self.index
}
fn url(&self) -> &str {
&self.url
}
}
pub fn render_block<T: Referable>(references: &[T]) -> String {
if references.is_empty() {
return String::new();
}
let mut s = String::from("References:\n");
for r in references {
s.push_str(&format!("[{}] {}\n", r.index(), r.url()));
}
s.truncate(s.trim_end().len());
s
}
const MIN_BODY_TOKENS: usize = 64;
const FIT_PASSES: usize = 6;
pub fn cited_indices(text: &str) -> BTreeSet<usize> {
let mut out = BTreeSet::new();
let bytes = text.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] != b'[' {
i += 1;
continue;
}
let mut j = i + 1;
while j < bytes.len() && bytes[j].is_ascii_digit() {
j += 1;
}
if j > i + 1 && j < bytes.len() && bytes[j] == b']' {
if let Ok(n) = text[i + 1..j].parse::<usize>() {
out.insert(n);
}
}
i = j.max(i + 1);
}
out
}
fn assemble(body: &str, block: &str) -> String {
if block.is_empty() {
body.to_string()
} else {
format!("{body}\n\n{block}")
}
}
pub fn fit_to_budget<T: Referable>(
body: &str,
references: &[T],
max_tokens: Option<usize>,
) -> (String, Vec<usize>) {
let all = || references.iter().map(Referable::index).collect::<Vec<_>>();
let Some(max_tokens) = max_tokens else {
return (assemble(body, &render_block(references)), all());
};
let mut budget = max_tokens;
for pass in 0..FIT_PASSES {
let body = truncate_to_tokens(body, budget);
let cited = cited_indices(&body);
let kept: Vec<&T> = references
.iter()
.filter(|r| cited.contains(&r.index()))
.collect();
let block = render_block(&kept);
let content = assemble(&body, &block);
let total = estimate_tokens(&content);
if total <= max_tokens {
return (content, kept.iter().map(|r| r.index()).collect());
}
if pass + 1 == FIT_PASSES || budget <= MIN_BODY_TOKENS {
return drop_until_fits(&body, &kept, max_tokens);
}
let scaled = (budget as u128 * max_tokens as u128 / total as u128) as usize;
let next = scaled.max(MIN_BODY_TOKENS);
if next >= budget {
return drop_until_fits(&body, &kept, max_tokens);
}
budget = next;
}
unreachable!("the loop returns on its final pass")
}
fn drop_until_fits<T: Referable>(
body: &str,
kept: &[&T],
max_tokens: usize,
) -> (String, Vec<usize>) {
let mut kept = kept.to_vec();
while !kept.is_empty() {
let content = assemble(body, &render_block(&kept));
if estimate_tokens(&content) <= max_tokens {
return (content, kept.iter().map(|r| r.index()).collect());
}
kept.pop();
}
(body.to_string(), Vec::new())
}
#[cfg(test)]
mod tests {
use super::*;
fn refs(n: usize) -> Vec<Reference> {
(1..=n)
.map(|i| Reference {
index: i,
url: format!("https://example.com/very/long/path/segment/{i}?query=value#frag"),
})
.collect()
}
fn body_citing(n: usize) -> String {
(1..=n)
.map(|i| format!("Item {i} with filler prose describing the thing [{i}]."))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn cited_indices_finds_markers() {
let got = cited_indices("a [1] b [12] c [x] d [3]");
assert_eq!(got.into_iter().collect::<Vec<_>>(), vec![1, 3, 12]);
}
#[test]
fn no_budget_keeps_everything() {
let (content, kept) = fit_to_budget(&body_citing(3), &refs(3), None);
assert_eq!(kept, vec![1, 2, 3]);
assert!(content.contains("References:"));
assert!(content.contains("[3] https://example.com"));
}
#[test]
fn link_dense_page_respects_the_cap() {
let refs = refs(120);
let (content, kept) = fit_to_budget(&body_citing(120), &refs, Some(200));
assert!(
estimate_tokens(&content) <= 200,
"estimate {} content: {content}",
estimate_tokens(&content)
);
assert!(kept.len() < 120, "kept {} of 120", kept.len());
}
#[test]
fn every_kept_reference_is_still_cited() {
let refs = refs(120);
let (content, kept) = fit_to_budget(&body_citing(120), &refs, Some(300));
let body = content.split("References:").next().unwrap();
let cited = cited_indices(body);
for index in &kept {
assert!(cited.contains(index), "kept [{index}] is not cited");
}
}
#[test]
fn a_generous_budget_is_actually_used() {
let refs = refs(120);
let (content, _) = fit_to_budget(&body_citing(120), &refs, Some(1000));
let used = estimate_tokens(&content);
assert!(used <= 1000, "over budget: {used}");
assert!(used >= 800, "left {} of 1000 tokens unused", 1000 - used);
}
#[test]
fn tiny_budget_still_fits() {
let refs = refs(40);
let (content, _) = fit_to_budget(&body_citing(40), &refs, Some(80));
assert!(
estimate_tokens(&content) <= 80,
"estimate {}",
estimate_tokens(&content)
);
}
#[test]
fn generous_budget_is_a_noop() {
let refs = refs(3);
let body = body_citing(3);
let (content, kept) = fit_to_budget(&body, &refs, Some(100_000));
assert_eq!(kept, vec![1, 2, 3]);
assert_eq!(content, assemble(&body, &render_block(&refs)));
}
#[test]
fn body_without_references_is_plain_truncation() {
let empty: [Reference; 0] = [];
let (content, kept) = fit_to_budget(&"word ".repeat(500), &empty, Some(50));
assert!(kept.is_empty());
assert!(content.contains("…[truncated]"));
assert!(estimate_tokens(&content) <= 50);
}
}