agent_core/core/
models.rs1pub const KNOWN_MODELS: &[(&str, &str)] = &[
5 ("claude-sonnet-4-6", "Sonnet 4.6 — balanced (default)"),
6 ("claude-fable-5-1", "Fable 5.1"),
7 ("claude-fable-5", "Fable 5"),
8 ("claude-opus-5-5", "Opus 5.5 — most capable Opus"),
9 ("claude-opus-4-7", "Opus 4.7"),
10 ("claude-opus-4-6", "Opus 4.6 — previous flagship"),
11 ("claude-haiku-4-5-20251001", "Haiku 4.5 — fast"),
12];
13
14pub fn default_model() -> &'static str {
15 KNOWN_MODELS[0].0
16}
17
18pub fn model_supports_adaptive_thinking(model: &str) -> bool {
30 let m = model.to_ascii_lowercase();
31 if m.contains("opus-4-7") || m.contains("opus-4-8") || m.contains("opus-4-9") {
36 return true;
37 }
38 if m.contains("sonnet-4-7") || m.contains("sonnet-4-8") || m.contains("sonnet-4-9") {
39 return true;
40 }
41 if m.contains("opus-5")
43 || m.contains("sonnet-5")
44 || m.contains("haiku-5")
45 || m.contains("fable-5")
46 {
47 return true;
48 }
49 false
50}
51
52pub fn effort_for_thinking_level(level: &str) -> Option<&'static str> {
59 match level {
60 "low" => Some("low"),
61 "medium" | "med" => Some("medium"),
62 "high" => Some("high"),
63 "xhigh" => Some("xhigh"),
64 "adaptive" => None, _ => Some("high"), }
67}
68
69pub fn thinking_level_for_budget(budget: u32) -> &'static str {
76 match budget {
77 0 => "adaptive",
78 1..=2048 => "low",
79 2049..=4096 => "medium",
80 4097..=16384 => "high",
81 _ => "xhigh",
82 }
83}
84
85pub fn budget_for_thinking_level(level: &str) -> Option<u32> {
90 match level {
91 "adaptive" | "off" => Some(0),
92 "low" => Some(2048),
93 "medium" => Some(4096),
94 "high" => Some(16384),
95 "xhigh" => Some(32768),
96 _ => None,
97 }
98}
99
100pub const DEFAULT_LEGACY_ADAPTIVE_FALLBACK: u32 = 16384;
103
104pub fn model_supports_1m(model: &str) -> bool {
112 let m = model.to_ascii_lowercase();
113 m.contains("opus-4-6")
115 || m.contains("opus-4-7")
116 || m.contains("opus-4-8")
117 || m.contains("opus-4-9")
118 || m.contains("sonnet-4")
119 || m.contains("opus-5")
120 || m.contains("sonnet-5")
121 || m.contains("fable-5")
122}
123
124pub fn context_window_for_model(_model: &str) -> u64 {
138 200_000
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn default_model_is_first_entry() {
147 assert_eq!(default_model(), KNOWN_MODELS[0].0);
148 }
149
150 #[test]
151 fn known_models_has_expected_ids() {
152 let ids: Vec<&str> = KNOWN_MODELS.iter().map(|(id, _)| *id).collect();
153 assert!(ids.contains(&"claude-fable-5-1"));
154 assert!(ids.contains(&"claude-fable-5"));
155 assert!(ids.contains(&"claude-opus-5-5"));
156 assert!(ids.contains(&"claude-opus-4-7"));
157 assert!(ids.contains(&"claude-sonnet-4-6"));
158 assert!(ids.contains(&"claude-haiku-4-5-20251001"));
159 }
160
161 #[test]
162 fn descriptions_are_non_empty() {
163 for (_, desc) in KNOWN_MODELS {
164 assert!(!desc.is_empty(), "empty description");
165 }
166 }
167
168 #[test]
169 fn budget_for_thinking_level_round_trips() {
170 for budget in [0u32, 2048, 4096, 16384, 32768] {
171 let level = thinking_level_for_budget(budget);
172 assert_eq!(
173 budget_for_thinking_level(level),
174 Some(budget),
175 "level {}",
176 level
177 );
178 }
179 assert_eq!(budget_for_thinking_level("off"), Some(0));
180 assert_eq!(budget_for_thinking_level("custom(8192)"), None);
181 }
182
183 #[test]
184 fn context_window_default_is_200k_for_all_models() {
185 assert_eq!(context_window_for_model("claude-opus-4-7"), 200_000);
187 assert_eq!(context_window_for_model("claude-opus-4-6"), 200_000);
188 assert_eq!(context_window_for_model("claude-sonnet-4-6"), 200_000);
189 assert_eq!(
190 context_window_for_model("claude-opus-4-5-20251101"),
191 200_000
192 );
193 assert_eq!(
194 context_window_for_model("claude-haiku-4-5-20251001"),
195 200_000
196 );
197 }
198
199 #[test]
200 fn model_supports_1m_for_opus_46_and_sonnet_4() {
201 assert!(model_supports_1m("claude-opus-4-6"));
202 assert!(model_supports_1m("claude-opus-4-7"));
203 assert!(model_supports_1m("claude-sonnet-4-6"));
204 assert!(model_supports_1m("claude-sonnet-4-5-20250929"));
205 }
206
207 #[test]
208 fn model_supports_1m_rejects_older_and_haiku() {
209 assert!(!model_supports_1m("claude-opus-4-5"));
210 assert!(!model_supports_1m("claude-haiku-4-5-20251001"));
211 assert!(!model_supports_1m("claude-opus-3-5"));
212 }
213
214 #[test]
215 fn model_supports_1m_assumed_for_5x() {
216 assert!(model_supports_1m("claude-opus-5-0"));
217 assert!(model_supports_1m("claude-sonnet-5-1"));
218 assert!(model_supports_1m("claude-fable-5"));
219 }
220
221 #[test]
222 fn model_supports_1m_case_insensitive() {
223 assert!(model_supports_1m("CLAUDE-OPUS-4-6"));
224 }
225
226 #[test]
227 fn context_window_haiku_is_200k() {
228 assert_eq!(
229 context_window_for_model("claude-haiku-4-5-20251001"),
230 200_000
231 );
232 }
233
234 #[test]
235 fn context_window_opus3_is_200k() {
236 assert_eq!(
237 context_window_for_model("claude-opus-3-5-20250101"),
238 200_000
239 );
240 }
241
242 #[test]
243 fn context_window_unknown_defaults_200k() {
244 assert_eq!(context_window_for_model("some-future-model"), 200_000);
245 assert_eq!(context_window_for_model(""), 200_000);
246 }
247
248 #[test]
249 fn adaptive_thinking_required_for_opus_4_7_plus() {
250 assert!(model_supports_adaptive_thinking("claude-opus-4-7"));
251 assert!(model_supports_adaptive_thinking("claude-opus-4-8"));
252 }
253
254 #[test]
255 fn adaptive_thinking_not_for_opus_4_6() {
256 assert!(!model_supports_adaptive_thinking("claude-opus-4-6"));
258 }
259
260 #[test]
261 fn adaptive_thinking_not_for_sonnet_4_6() {
262 assert!(!model_supports_adaptive_thinking("claude-sonnet-4-6"));
263 }
264
265 #[test]
266 fn adaptive_thinking_required_for_sonnet_4_7_plus() {
267 assert!(model_supports_adaptive_thinking("claude-sonnet-4-7"));
268 }
269
270 #[test]
271 fn adaptive_thinking_not_for_older_models() {
272 assert!(!model_supports_adaptive_thinking("claude-opus-4-5"));
273 assert!(!model_supports_adaptive_thinking("claude-sonnet-4-5"));
274 assert!(!model_supports_adaptive_thinking(
275 "claude-haiku-4-5-20251001"
276 ));
277 assert!(!model_supports_adaptive_thinking("claude-opus-3-5"));
278 }
279
280 #[test]
281 fn adaptive_thinking_assumed_for_5x() {
282 assert!(model_supports_adaptive_thinking("claude-opus-5-0"));
283 assert!(model_supports_adaptive_thinking("claude-sonnet-5-1"));
284 }
285
286 #[test]
287 fn opus_5_5_is_a_known_adaptive_1m_capable_model() {
288 assert!(KNOWN_MODELS.iter().any(|(id, _)| *id == "claude-opus-5-5"));
290 assert!(!KNOWN_MODELS.iter().any(|(id, _)| *id == "claude-opus-5.5"));
291 assert!(model_supports_adaptive_thinking("claude-opus-5-5"));
292 assert!(model_supports_1m("claude-opus-5-5"));
293 assert_ne!(default_model(), "claude-opus-5-5");
295 }
296
297 #[test]
298 fn adaptive_thinking_case_insensitive() {
299 assert!(model_supports_adaptive_thinking("CLAUDE-OPUS-4-7"));
300 }
301}