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