use serde_json::Value;
pub const MESSAGE_OVERHEAD: u64 = 4;
pub fn estimate(text: &str) -> u64 {
(text.chars().count() as u64).div_ceil(4)
}
pub fn estimate_value(v: &Value) -> u64 {
match v {
Value::String(s) => estimate(s),
Value::Null => 1,
other => estimate(&other.to_string()),
}
}
pub const DEFAULT_MODEL_WINDOW: u64 = 128_000;
pub fn window_for_model(model: &str) -> u64 {
let m = model.to_ascii_lowercase();
if m.contains("claude") {
200_000
} else if m.contains("gpt-4o")
|| m.contains("gpt-4.1")
|| m.contains("gpt-5")
|| m.starts_with("o1")
|| m.starts_with("o3")
|| m.starts_with("o4")
{
if m.contains("gpt-4.1") {
1_000_000
} else {
128_000
}
} else if m.contains("gemini") {
1_000_000
} else {
DEFAULT_MODEL_WINDOW
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn estimates_are_monotone_and_rounded_up() {
assert_eq!(estimate(""), 0);
assert_eq!(estimate("abcd"), 1);
assert_eq!(estimate("abcde"), 2);
assert!(estimate("a much longer sentence with many words") > estimate("short"));
assert_eq!(estimate_value(&serde_json::json!(null)), 1);
assert_eq!(estimate_value(&serde_json::json!("abcd")), 1);
assert!(estimate_value(&serde_json::json!({"k": "vvvv"})) >= 2);
assert_eq!(window_for_model("claude-sonnet-5"), 200_000);
assert_eq!(window_for_model("gpt-4.1-mini"), 1_000_000);
assert_eq!(window_for_model("something-else"), DEFAULT_MODEL_WINDOW);
}
}