Skip to main content

codetether_agent/provider/
limits.rs

1//! Canonical context-window limits for known LLM models.
2//!
3//! This is the **single source of truth** for
4//! [`context_window_for_model`]. Every subsystem — session compaction,
5//! RLM routing, TUI token badges, Bedrock estimates — must delegate here
6//! rather than maintaining its own heuristic map.
7//!
8//! # Adding a new model
9//!
10//! Add a `contains` match arm below. Order matters: more specific
11//! patterns must come before broader ones (e.g. `"claude-opus-4-7"`
12//! before `"claude"`).
13//!
14//! # Examples
15//!
16//! ```rust
17//! use codetether_agent::provider::limits::context_window_for_model;
18//!
19//! assert_eq!(context_window_for_model("zai/glm-5"), 200_000);
20//! assert_eq!(context_window_for_model("kimi-k2.5"), 256_000);
21//! assert_eq!(context_window_for_model("claude-opus-4-7"), 1_000_000);
22//! assert_eq!(context_window_for_model("unknown-model"), 128_000);
23//! ```
24
25/// Return the context window size (in tokens) for known models.
26///
27/// Uses case-insensitive substring matching against the model identifier.
28/// Returns 128 000 for unknown models (conservative for most modern LLMs).
29pub fn context_window_for_model(model: &str) -> usize {
30    let m = model.to_ascii_lowercase();
31
32    // ── Most specific patterns first ───────────────────────────────
33    if m.contains("claude-opus-4-7") || m.contains("claude-opus-4.7") || m.contains("4.7-opus") {
34        1_000_000
35    } else if m.contains("glm-5") || m.contains("glm5") {
36        200_000
37    } else if m.contains("kimi-k2") || m.contains("kimi.k2") {
38        256_000
39    } else if m.contains("gpt-5") {
40        256_000
41    } else if m.contains("gpt-4o") || m.contains("gpt-4-turbo") || m.contains("gpt-4") {
42        128_000
43    } else if m.contains("claude") {
44        200_000
45    } else if m.contains("gemini-2.5-pro") || m.contains("gemini-2-pro") {
46        2_000_000
47    } else if m.contains("gemini") {
48        1_000_000
49    } else if m.contains("minimax") || m.contains("m2.5") {
50        256_000
51    } else if m.contains("qwen") || m.contains("qwq") {
52        131_072
53    } else if m.contains("deepseek-r1") || m.contains("deepseek-v3") {
54        128_000
55    } else if m.contains("llama-4") || m.contains("llama4") {
56        256_000
57    } else if m.contains("nova-pro") || m.contains("nova-lite") || m.contains("nova-premier") {
58        300_000
59    } else if m.contains("nova-micro") || m.contains("nova-2") {
60        128_000
61    } else if m.contains("moonshot") || m.contains("k1.5") || m.contains("k1.6") {
62        200_000
63    } else if m.contains("mistral-large") || m.contains("magistral") {
64        128_000
65    } else if m.contains("mistral") {
66        32_000
67    } else if m.contains("jamba") {
68        256_000
69    } else {
70        128_000 // conservative default
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn known_models() {
80        assert_eq!(context_window_for_model("zai/glm-5"), 200_000);
81        assert_eq!(context_window_for_model("glm-5-pro"), 200_000);
82        assert_eq!(context_window_for_model("kimi-k2.5"), 256_000);
83        assert_eq!(
84            context_window_for_model("anthropic.claude-opus-4-7-v1:0"),
85            1_000_000
86        );
87        assert_eq!(context_window_for_model("claude-3-5-sonnet"), 200_000);
88        assert_eq!(context_window_for_model("gpt-4o"), 128_000);
89        assert_eq!(context_window_for_model("gpt-4o-mini"), 128_000);
90        assert_eq!(context_window_for_model("gemini-2.0-flash"), 1_000_000);
91        assert_eq!(context_window_for_model("gemini-1.5-pro"), 1_000_000);
92        assert_eq!(context_window_for_model("minimax-m2.5"), 256_000);
93        assert_eq!(context_window_for_model("qwen-2.5-coder"), 131_072);
94    }
95
96    #[test]
97    fn case_insensitive() {
98        assert_eq!(
99            context_window_for_model("Claude-Opus-4-7"),
100            context_window_for_model("claude-opus-4-7")
101        );
102    }
103
104    #[test]
105    fn unknown_falls_back() {
106        assert_eq!(context_window_for_model("totally-unknown-model"), 128_000);
107    }
108}