From 30f73707a697c209ea7e38c01df176d3c1b3c523 Mon Sep 17 00:00:00 2001 From: Arnesh Date: Mon, 17 Aug 2026 08:28:00 +0000 Subject: [PATCH] google_ai: Add Gemini 3.7 Flash (#62670) Follow-up to #62010, which added Gemini 3.6 Flash. Google released Gemini 3.7 Flash on August 13, 2026. It keeps the 1 million token context window and the 64k output limit, and supports thinking. Unlike 3.5 and 3.6 Flash, it does not accept `thinking_level: MINIMAL` (the API returns a validation error). So it exposes Low, Medium and High, defaulting to Medium, and `disabled_thinking_level` in `completion.rs` returns Low for it instead of Minimal when the user turns thinking off. The provider builds its model list from `google_ai::Model::iter()`, so adding the enum variant is enough for it to show up in the model dropdown. Release Notes: - Added Gemini 3.7 Flash to the Google AI models --- crates/google_ai/src/completion.rs | 24 +++++++++++++++++++++- crates/google_ai/src/google_ai.rs | 33 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/crates/google_ai/src/completion.rs b/crates/google_ai/src/completion.rs index 16c4f86eb41..6652722787c 100644 --- a/crates/google_ai/src/completion.rs +++ b/crates/google_ai/src/completion.rs @@ -249,7 +249,12 @@ fn is_google_thinking_model(model_id: &str) -> bool { fn disabled_thinking_level(model_id: &str) -> Option { match model_id { - model_id if model_id.starts_with("gemini-3") && model_id.contains("-pro") => { + // `gemini-3.7-flash` rejects `MINIMAL` with a validation error, so `LOW` is + // the lowest level available to it. + model_id + if model_id.starts_with("gemini-3.7-flash") + || (model_id.starts_with("gemini-3") && model_id.contains("-pro")) => + { Some(ThinkingLevel::Low) } model_id if model_id.starts_with("gemini-3") => Some(ThinkingLevel::Minimal), @@ -704,6 +709,23 @@ mod tests { assert_eq!(mapper.stop_reason, StopReason::Refusal); } + #[test] + fn test_disabled_thinking_level_per_model() { + assert_eq!( + disabled_thinking_level("gemini-3.7-flash"), + Some(ThinkingLevel::Low) + ); + assert_eq!( + disabled_thinking_level("gemini-3.1-pro-preview"), + Some(ThinkingLevel::Low) + ); + assert_eq!( + disabled_thinking_level("gemini-3.6-flash"), + Some(ThinkingLevel::Minimal) + ); + assert_eq!(disabled_thinking_level("gemini-2.5-flash"), None); + } + #[test] fn test_function_call_with_signature_creates_tool_use_with_signature() { let mut mapper = GoogleEventMapper::new(); diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index fb88d2ecb5c..3919dc51e56 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -533,6 +533,8 @@ pub enum Model { Gemini35Flash, #[serde(rename = "gemini-3.6-flash")] Gemini36Flash, + #[serde(rename = "gemini-3.7-flash")] + Gemini37Flash, #[serde(rename = "gemini-3.1-pro-preview", alias = "gemini-3-pro-preview")] Gemini31Pro, #[serde(rename = "custom")] @@ -560,6 +562,7 @@ impl Model { Self::Gemini3Flash => "gemini-3-flash-preview", Self::Gemini35Flash => "gemini-3.5-flash", Self::Gemini36Flash => "gemini-3.6-flash", + Self::Gemini37Flash => "gemini-3.7-flash", Self::Gemini31Pro => "gemini-3.1-pro-preview", Self::Custom { name, .. } => name, } @@ -573,6 +576,7 @@ impl Model { Self::Gemini3Flash => "gemini-3-flash-preview", Self::Gemini35Flash => "gemini-3.5-flash", Self::Gemini36Flash => "gemini-3.6-flash", + Self::Gemini37Flash => "gemini-3.7-flash", Self::Gemini31Pro => "gemini-3.1-pro-preview", Self::Custom { name, .. } => name, } @@ -587,6 +591,7 @@ impl Model { Self::Gemini3Flash => "Gemini 3 Flash", Self::Gemini35Flash => "Gemini 3.5 Flash", Self::Gemini36Flash => "Gemini 3.6 Flash", + Self::Gemini37Flash => "Gemini 3.7 Flash", Self::Gemini31Pro => "Gemini 3.1 Pro", Self::Custom { name, display_name, .. @@ -603,6 +608,7 @@ impl Model { | Self::Gemini3Flash | Self::Gemini35Flash | Self::Gemini36Flash + | Self::Gemini37Flash | Self::Gemini31Pro => 1_048_576, Self::Custom { max_tokens, .. } => *max_tokens, } @@ -617,6 +623,7 @@ impl Model { | Model::Gemini3Flash | Model::Gemini35Flash | Model::Gemini36Flash + | Model::Gemini37Flash | Model::Gemini31Pro => Some(65_536), Model::Custom { .. } => None, } @@ -640,6 +647,7 @@ impl Model { | Self::Gemini3Flash | Self::Gemini35Flash | Self::Gemini36Flash + | Self::Gemini37Flash | Self::Gemini31Pro | Self::Custom { mode: GoogleModelMode::Thinking { .. }, @@ -659,7 +667,7 @@ impl Model { ThinkingLevel::Medium, ThinkingLevel::High, ], - Self::Gemini31Pro => &[ + Self::Gemini37Flash | Self::Gemini31Pro => &[ ThinkingLevel::Low, ThinkingLevel::Medium, ThinkingLevel::High, @@ -674,6 +682,7 @@ impl Model { Self::Gemini3Flash => Some(ThinkingLevel::High), Self::Gemini35Flash => Some(ThinkingLevel::Medium), Self::Gemini36Flash => Some(ThinkingLevel::Medium), + Self::Gemini37Flash => Some(ThinkingLevel::Medium), Self::Gemini31Pro => Some(ThinkingLevel::High), _ => None, } @@ -692,6 +701,7 @@ impl Model { | Self::Gemini3Flash | Self::Gemini35Flash | Self::Gemini36Flash + | Self::Gemini37Flash | Self::Gemini31Pro => GoogleModelMode::Thinking { budget_tokens: None, }, @@ -724,6 +734,27 @@ mod tests { assert_eq!(deserialized, Model::Gemini36Flash); } + #[test] + fn test_gemini_3_7_flash_model_metadata() { + let model = Model::Gemini37Flash; + assert_eq!(model.id(), "gemini-3.7-flash"); + assert_eq!(model.request_id(), "gemini-3.7-flash"); + assert_eq!(model.display_name(), "Gemini 3.7 Flash"); + assert_eq!( + model.supported_thinking_levels(), + &[ + ThinkingLevel::Low, + ThinkingLevel::Medium, + ThinkingLevel::High + ] + ); + + let serialized = serde_json::to_value(&model).unwrap(); + assert_eq!(serialized, json!("gemini-3.7-flash")); + let deserialized: Model = serde_json::from_value(json!("gemini-3.7-flash")).unwrap(); + assert_eq!(deserialized, Model::Gemini37Flash); + } + #[test] fn test_function_call_part_with_signature_serializes_correctly() { let part = FunctionCallPart {