ferrin_google/
capabilities.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct ModelCapabilities {
6 pub is_gemini: bool,
8 pub is_gemini_2_5: bool,
11 pub is_gemma: bool,
13 pub supports_gemini2_tools: bool,
16 pub supports_file_search: bool,
18 pub uses_gemini3_features: bool,
21}
22
23fn segment_has_prefix(model_id: &str, prefix: &str) -> bool {
24 model_id.split('/').any(|segment| {
25 segment
26 .to_ascii_lowercase()
27 .strip_prefix(prefix)
28 .is_some_and(|rest| rest.is_empty() || rest.starts_with(['.', '-']))
29 })
30}
31
32fn segment_equals(model_id: &str, names: &[&str]) -> bool {
33 model_id
34 .split('/')
35 .any(|segment| names.contains(&segment.to_ascii_lowercase().as_str()))
36}
37
38fn is_known_pre_gemini2(model_id: &str) -> bool {
39 segment_has_prefix(model_id, "gemini-1")
40 || segment_equals(model_id, &["gemini-pro", "gemini-pro-vision"])
41 || segment_has_prefix(model_id, "gemini-robotics-er-1.5")
42}
43
44#[must_use]
46pub fn capabilities(model_id: &str) -> ModelCapabilities {
47 let lower = model_id.to_ascii_lowercase();
48 let is_gemini = model_id
49 .split('/')
50 .any(|segment| segment.to_ascii_lowercase().starts_with("gemini-"));
51 let is_gemini_2 = segment_has_prefix(model_id, "gemini-2");
52 let pre_gemini2 = is_known_pre_gemini2(model_id);
53 let uses_gemini3_features = is_gemini && !(pre_gemini2 || is_gemini_2);
54 let is_gemini_2_5 = segment_has_prefix(model_id, "gemini-2.5");
55 ModelCapabilities {
56 is_gemini,
57 is_gemini_2_5,
58 is_gemma: lower.starts_with("gemma-"),
59 supports_gemini2_tools: (is_gemini && !pre_gemini2) || lower.contains("nano-banana"),
60 supports_file_search: is_gemini_2_5 || uses_gemini3_features,
61 uses_gemini3_features,
62 }
63}
64
65pub const GEMINI_2_5_MAX_OUTPUT_TOKENS: u32 = 65_536;
67
68#[must_use]
70pub fn max_thinking_tokens_gemini_2_5(model_id: &str) -> u32 {
71 let lower = model_id.to_ascii_lowercase();
72 if lower.contains("2.5-pro") || lower.contains("gemini-3-pro-image") {
73 32_768
74 } else {
75 24_576
76 }
77}
78
79#[must_use]
83pub fn minimum_thinking_level_gemini3(model_id: &str) -> &'static str {
84 let name = model_id
85 .rsplit('/')
86 .next()
87 .unwrap_or_default()
88 .to_ascii_lowercase();
89 if name == "gemini-flash-latest" {
90 return "low";
91 }
92 let Some(rest) = name.strip_prefix("gemini-") else {
93 return "minimal";
94 };
95 let Some((version, tail)) = rest.split_once("-flash") else {
96 return "minimal";
97 };
98 let Some((major, minor)) = version.split_once('.') else {
99 return "minimal";
100 };
101 let (Ok(major), Ok(minor)) = (major.parse::<u32>(), minor.parse::<u32>()) else {
102 return "minimal";
103 };
104 let flash_variant = match tail.strip_prefix('-') {
105 None if tail.is_empty() => true,
106 None => false,
107 Some(suffix) => !(suffix == "lite" || suffix.starts_with("lite-")),
108 };
109 if !flash_variant {
110 return "minimal";
111 }
112 if major > 3 || (major == 3 && minor >= 7) {
113 "low"
114 } else {
115 "minimal"
116 }
117}