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