1use serde_json::Value;
12
13pub const MESSAGE_OVERHEAD: u64 = 4;
15
16pub fn estimate(text: &str) -> u64 {
18 (text.chars().count() as u64).div_ceil(4)
19}
20
21pub fn estimate_value(v: &Value) -> u64 {
23 match v {
24 Value::String(s) => estimate(s),
25 Value::Null => 1,
26 other => estimate(&other.to_string()),
27 }
28}
29
30pub const DEFAULT_MODEL_WINDOW: u64 = 128_000;
33
34pub fn window_for_model(model: &str) -> u64 {
37 let m = model.to_ascii_lowercase();
38 if m.contains("claude") {
39 200_000
40 } else if m.contains("gpt-4o")
41 || m.contains("gpt-4.1")
42 || m.contains("gpt-5")
43 || m.starts_with("o1")
44 || m.starts_with("o3")
45 || m.starts_with("o4")
46 {
47 if m.contains("gpt-4.1") {
48 1_000_000
49 } else {
50 128_000
51 }
52 } else if m.contains("gemini") {
53 1_000_000
54 } else {
55 DEFAULT_MODEL_WINDOW
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn estimates_are_monotone_and_rounded_up() {
65 assert_eq!(estimate(""), 0);
66 assert_eq!(estimate("abcd"), 1);
67 assert_eq!(estimate("abcde"), 2);
68 assert!(estimate("a much longer sentence with many words") > estimate("short"));
69 assert_eq!(estimate_value(&serde_json::json!(null)), 1);
70 assert_eq!(estimate_value(&serde_json::json!("abcd")), 1);
71 assert!(estimate_value(&serde_json::json!({"k": "vvvv"})) >= 2);
72 assert_eq!(window_for_model("claude-sonnet-5"), 200_000);
73 assert_eq!(window_for_model("gpt-4.1-mini"), 1_000_000);
74 assert_eq!(window_for_model("something-else"), DEFAULT_MODEL_WINDOW);
75 }
76}