pub const IMAGE_TOKEN_COST: u32 = 765;
#[must_use]
pub fn estimate_text_tokens(text: &str) -> u32 {
let bytes = u32::try_from(text.len()).unwrap_or(u32::MAX);
bytes.div_ceil(4)
}
#[must_use]
pub const fn estimate_image_tokens() -> u32 {
IMAGE_TOKEN_COST
}
pub const MESSAGE_FRAME_TOKENS: u32 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreflightOverflow {
Ok {
estimated: u32,
},
Overflow {
estimated: u32,
window: u32,
limit: u32,
},
}
#[must_use]
pub fn check_context_overflow(
estimated: u32,
window: u32,
threshold_ratio: f32,
) -> PreflightOverflow {
if window == 0 {
return PreflightOverflow::Ok { estimated };
}
let ratio = threshold_ratio.clamp(0.1, 1.0);
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
reason = "token limits are u32; ratio is clamped to 0.1..=1.0"
)]
let numer = (ratio * 1000.0).round() as u64;
let numer = numer.clamp(100, 1000);
let limit = (u64::from(window).saturating_mul(numer).saturating_add(999) / 1000)
.min(u64::from(u32::MAX));
let limit = u32::try_from(limit).unwrap_or(u32::MAX);
if estimated > limit {
PreflightOverflow::Overflow {
estimated,
window,
limit,
}
} else {
PreflightOverflow::Ok { estimated }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_estimate_bytes_over_four() {
assert_eq!(estimate_text_tokens("abcd"), 1);
assert_eq!(estimate_text_tokens("abcde"), 2);
}
#[test]
fn overflow_threshold() {
let est = 900;
match check_context_overflow(est, 1000, 0.85) {
PreflightOverflow::Overflow { limit, .. } => {
assert_eq!(limit, 850);
}
PreflightOverflow::Ok { .. } => unreachable!("expected overflow"),
}
assert!(matches!(
check_context_overflow(800, 1000, 0.85),
PreflightOverflow::Ok { .. }
));
}
}