synaps_cli/core/
models.rs1pub const KNOWN_MODELS: &[(&str, &str)] = &[
5 ("claude-sonnet-4-6", "Sonnet 4.6 — balanced (default)"),
6 ("claude-opus-4-7", "Opus 4.7 — most capable"),
7 ("claude-opus-4-6", "Opus 4.6 — previous flagship"),
8 ("claude-haiku-4-5-20251001", "Haiku 4.5 — fast"),
9];
10
11pub fn default_model() -> &'static str {
12 KNOWN_MODELS[0].0
13}
14
15pub fn model_supports_adaptive_thinking(model: &str) -> bool {
27 let m = model.to_ascii_lowercase();
28 if m.contains("opus-4-7") || m.contains("opus-4-8") || m.contains("opus-4-9") {
33 return true;
34 }
35 if m.contains("sonnet-4-7") || m.contains("sonnet-4-8") || m.contains("sonnet-4-9") {
36 return true;
37 }
38 if m.contains("opus-5") || m.contains("sonnet-5") || m.contains("haiku-5") {
40 return true;
41 }
42 false
43}
44
45pub fn effort_for_thinking_level(level: &str) -> Option<&'static str> {
52 match level {
53 "low" => Some("low"),
54 "medium" | "med" => Some("medium"),
55 "high" => Some("high"),
56 "xhigh" => Some("xhigh"),
57 "adaptive" => None, _ => Some("high"), }
60}
61
62pub fn thinking_level_for_budget(budget: u32) -> &'static str {
69 match budget {
70 0 => "adaptive",
71 1..=2048 => "low",
72 2049..=4096 => "medium",
73 4097..=16384 => "high",
74 _ => "xhigh",
75 }
76}
77
78pub const DEFAULT_LEGACY_ADAPTIVE_FALLBACK: u32 = 16384;
81
82pub fn model_supports_1m(model: &str) -> bool {
90 let m = model.to_ascii_lowercase();
91 m.contains("opus-4-6") || m.contains("opus-4-7")
93 || m.contains("opus-4-8") || m.contains("opus-4-9")
94 || m.contains("sonnet-4")
95 || m.contains("opus-5") || m.contains("sonnet-5")
96}
97
98pub fn context_window_for_model(_model: &str) -> u64 {
112 200_000
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn default_model_is_first_entry() {
121 assert_eq!(default_model(), KNOWN_MODELS[0].0);
122 }
123
124 #[test]
125 fn known_models_has_expected_ids() {
126 let ids: Vec<&str> = KNOWN_MODELS.iter().map(|(id, _)| *id).collect();
127 assert!(ids.contains(&"claude-opus-4-7"));
128 assert!(ids.contains(&"claude-sonnet-4-6"));
129 assert!(ids.contains(&"claude-haiku-4-5-20251001"));
130 }
131
132 #[test]
133 fn descriptions_are_non_empty() {
134 for (_, desc) in KNOWN_MODELS {
135 assert!(!desc.is_empty(), "empty description");
136 }
137 }
138
139 #[test]
140 fn context_window_default_is_200k_for_all_models() {
141 assert_eq!(context_window_for_model("claude-opus-4-7"), 200_000);
143 assert_eq!(context_window_for_model("claude-opus-4-6"), 200_000);
144 assert_eq!(context_window_for_model("claude-sonnet-4-6"), 200_000);
145 assert_eq!(context_window_for_model("claude-opus-4-5-20251101"), 200_000);
146 assert_eq!(context_window_for_model("claude-haiku-4-5-20251001"), 200_000);
147 }
148
149 #[test]
150 fn model_supports_1m_for_opus_46_and_sonnet_4() {
151 assert!(model_supports_1m("claude-opus-4-6"));
152 assert!(model_supports_1m("claude-opus-4-7"));
153 assert!(model_supports_1m("claude-sonnet-4-6"));
154 assert!(model_supports_1m("claude-sonnet-4-5-20250929"));
155 }
156
157 #[test]
158 fn model_supports_1m_rejects_older_and_haiku() {
159 assert!(!model_supports_1m("claude-opus-4-5"));
160 assert!(!model_supports_1m("claude-haiku-4-5-20251001"));
161 assert!(!model_supports_1m("claude-opus-3-5"));
162 }
163
164 #[test]
165 fn model_supports_1m_assumed_for_5x() {
166 assert!(model_supports_1m("claude-opus-5-0"));
167 assert!(model_supports_1m("claude-sonnet-5-1"));
168 }
169
170 #[test]
171 fn model_supports_1m_case_insensitive() {
172 assert!(model_supports_1m("CLAUDE-OPUS-4-6"));
173 }
174
175 #[test]
176 fn context_window_haiku_is_200k() {
177 assert_eq!(context_window_for_model("claude-haiku-4-5-20251001"), 200_000);
178 }
179
180 #[test]
181 fn context_window_opus3_is_200k() {
182 assert_eq!(context_window_for_model("claude-opus-3-5-20250101"), 200_000);
183 }
184
185 #[test]
186 fn context_window_unknown_defaults_200k() {
187 assert_eq!(context_window_for_model("some-future-model"), 200_000);
188 assert_eq!(context_window_for_model(""), 200_000);
189 }
190
191 #[test]
192 fn adaptive_thinking_required_for_opus_4_7_plus() {
193 assert!(model_supports_adaptive_thinking("claude-opus-4-7"));
194 assert!(model_supports_adaptive_thinking("claude-opus-4-8"));
195 }
196
197 #[test]
198 fn adaptive_thinking_not_for_opus_4_6() {
199 assert!(!model_supports_adaptive_thinking("claude-opus-4-6"));
201 }
202
203 #[test]
204 fn adaptive_thinking_not_for_sonnet_4_6() {
205 assert!(!model_supports_adaptive_thinking("claude-sonnet-4-6"));
206 }
207
208 #[test]
209 fn adaptive_thinking_required_for_sonnet_4_7_plus() {
210 assert!(model_supports_adaptive_thinking("claude-sonnet-4-7"));
211 }
212
213 #[test]
214 fn adaptive_thinking_not_for_older_models() {
215 assert!(!model_supports_adaptive_thinking("claude-opus-4-5"));
216 assert!(!model_supports_adaptive_thinking("claude-sonnet-4-5"));
217 assert!(!model_supports_adaptive_thinking("claude-haiku-4-5-20251001"));
218 assert!(!model_supports_adaptive_thinking("claude-opus-3-5"));
219 }
220
221 #[test]
222 fn adaptive_thinking_assumed_for_5x() {
223 assert!(model_supports_adaptive_thinking("claude-opus-5-0"));
224 assert!(model_supports_adaptive_thinking("claude-sonnet-5-1"));
225 }
226
227 #[test]
228 fn adaptive_thinking_case_insensitive() {
229 assert!(model_supports_adaptive_thinking("CLAUDE-OPUS-4-7"));
230 }
231}