From f1baceb8ab1c84fa81dc8cb0c1c3e6864698b23d Mon Sep 17 00:00:00 2001 From: Johannes du Plessis Date: Thu, 4 Jun 2026 17:38:54 -0700 Subject: [PATCH] feat: add Gemini 3.5 Flash provider (#1420) --- agent/dashboard/options.py | 23 +++++++++++++++++++++-- agent/utils/model.py | 2 +- tests/test_google_model.py | 24 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/agent/dashboard/options.py b/agent/dashboard/options.py index 97d5a40c..0562786b 100644 --- a/agent/dashboard/options.py +++ b/agent/dashboard/options.py @@ -25,6 +25,12 @@ SUPPORTED_MODELS: list[ModelOption] = [ "efforts": ["none", "low", "medium", "high", "xhigh"], "default_effort": "xhigh", }, + { + "id": "google_genai:gemini-3.5-flash", + "label": "Gemini 3.5 Flash", + "efforts": ["minimal", "low", "medium", "high"], + "default_effort": "medium", + }, { "id": "fireworks:accounts/fireworks/models/kimi-k2p6", "label": "Kimi K2.6", @@ -63,6 +69,20 @@ def _provider_of(model_id: str) -> str | None: return provider if rest else None +def _fallback_effort_for(model: ModelOption, effort: object) -> str | None: + if not isinstance(effort, str): + return None + if effort in model["efforts"]: + return effort + if ( + model["id"].startswith("google_genai:") + and effort == "none" + and "minimal" in model["efforts"] + ): + return "minimal" + return None + + def provider_fallback_pair(model_id: object, effort: object = None) -> tuple[str, str] | None: """Newest supported ``(model_id, effort)`` for the same provider as ``model_id``. @@ -79,8 +99,7 @@ def provider_fallback_pair(model_id: object, effort: object = None) -> tuple[str return None for m in SUPPORTED_MODELS: if _provider_of(m["id"]) == provider: - new_effort = effort if (isinstance(effort, str) and effort in m["efforts"]) else None - return m["id"], new_effort or m["default_effort"] + return m["id"], _fallback_effort_for(m, effort) or m["default_effort"] return None diff --git a/agent/utils/model.py b/agent/utils/model.py index f4bf536e..86f5f480 100644 --- a/agent/utils/model.py +++ b/agent/utils/model.py @@ -126,7 +126,7 @@ def fireworks_reasoning_effort_for(profile_effort: str | None) -> FireworksReaso def google_thinking_level_for(profile_effort: str | None) -> GoogleThinkingLevel | None: """Map profile effort to Gemini 3+ ``thinking_level``.""" - if profile_effort == "none": + if profile_effort in ("minimal", "none"): return "minimal" if profile_effort == "low": return "low" diff --git a/tests/test_google_model.py b/tests/test_google_model.py index c6788795..3b7d0c1f 100644 --- a/tests/test_google_model.py +++ b/tests/test_google_model.py @@ -1,3 +1,4 @@ +from agent.dashboard.options import SUPPORTED_MODELS, provider_fallback_pair from agent.utils.model import ( google_thinking_level_for, is_gemini_3_family, @@ -11,11 +12,34 @@ def test_gemini_3_family_detection() -> None: def test_google_thinking_level_maps_effort() -> None: + assert google_thinking_level_for("minimal") == "minimal" + assert google_thinking_level_for("none") == "minimal" assert google_thinking_level_for("medium") == "medium" assert google_thinking_level_for("high") == "high" assert google_thinking_level_for("unknown") is None +def test_gemini_35_flash_is_supported_with_documented_efforts() -> None: + gemini = next(m for m in SUPPORTED_MODELS if m["id"] == "google_genai:gemini-3.5-flash") + assert gemini["label"] == "Gemini 3.5 Flash" + assert gemini["efforts"] == ["minimal", "low", "medium", "high"] + assert gemini["default_effort"] == "medium" + + +def test_google_provider_fallback_uses_gemini_35_flash() -> None: + assert provider_fallback_pair("google_genai:gemini-3-flash-preview", "high") == ( + "google_genai:gemini-3.5-flash", + "high", + ) + + +def test_google_provider_fallback_maps_legacy_none_to_minimal() -> None: + assert provider_fallback_pair("google_genai:gemini-3-flash-preview", "none") == ( + "google_genai:gemini-3.5-flash", + "minimal", + ) + + def test_provider_model_kwargs_for_google() -> None: kwargs = provider_model_kwargs( "google_genai:gemini-3.5-flash",