feat(model): add gemini-3.5-flash (#403)

## Summary

Updates the built-in Gemini catalog for the current Gemini API lineup by
adding `gemini-3.5-flash` and promoting `gemini-3.1-flash-lite` to the
canonical small default. The old `gemini-3.1-flash-lite-preview` ID
remains accepted as an alias and resolves to the stable API ID, avoiding
a breaking change for existing workflows.

The catalog test changes remove Gemini-specific data assertions and keep
only a generic small-default invariant, so future declarative catalog
updates do not require Rust test churn.

## Verification

- `cargo nextest run -p fabro-model`
- `cargo +nightly-2026-04-14 fmt --check --all`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, default reasoning) via
[Codex](https://openai.com/codex)
This commit is contained in:
Bryan Helmkamp 2026-05-25 17:22:32 -04:00 • committed by GitHub
parent 705079a271
commit bb3f4bd62b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 98 deletions

View file

@ -28,8 +28,9 @@ No single model is best at everything. Fabro lets you assign the right model to
| `gpt-5.4-pro` | openai | `gpt54-pro` | 1M | $30.00 / $180.00 | 20 tok/s |
| `gemini-3.1-pro-preview` | gemini | `gemini-pro` | 1M | $2.00 / $12.00 | 85 tok/s |
| `gemini-3.1-pro-preview-customtools` | gemini | `gemini-customtools` | 1M | $2.00 / $12.00 | 85 tok/s |
| `gemini-3.5-flash` | gemini | `gemini-35-flash` | 1M | $1.50 / $9.00 | 150 tok/s |
| `gemini-3-flash-preview` | gemini | `gemini-flash` | 1M | $0.50 / $3.00 | 150 tok/s |
| `gemini-3.1-flash-lite-preview` | gemini | `gemini-flash-lite` | 1M | $0.25 / $1.50 | 200 tok/s |
| `gemini-3.1-flash-lite` | gemini | `gemini-flash-lite`, `gemini-3.1-flash-lite-preview` | 1M | $0.25 / $1.50 | 200 tok/s |
| `kimi-k2.5` | kimi | `kimi` | 262K | $0.60 / $3.00 | 50 tok/s |
| `glm-4.7` | zai | `glm`, `glm4` | 203K | $0.60 / $2.20 | 100 tok/s |
| `minimax-m2.5` | minimax | `minimax` | 197K | $0.30 / $1.20 | 45 tok/s |

View file

@ -1910,35 +1910,26 @@ enabled = true
fn builtin_small_defaults_are_marked_per_provider() {
let catalog = Catalog::builtin();
assert_eq!(
catalog
.small_default_for_provider(&ProviderId::anthropic())
.unwrap()
.id,
"claude-haiku-4-5"
);
assert_eq!(
catalog
.small_default_for_provider(&ProviderId::openai())
.unwrap()
.id,
"gpt-5.4-mini"
);
assert_eq!(
catalog
.small_default_for_provider(&ProviderId::gemini())
.unwrap()
.id,
"gemini-3.1-flash-lite-preview"
);
assert!(catalog.get("claude-haiku-4-5").unwrap().small_default);
assert!(catalog.get("gpt-5.4-mini").unwrap().small_default);
let small_defaults = catalog
.list(None)
.into_iter()
.filter(|model| model.small_default)
.collect::<Vec<_>>();
assert!(
catalog
.get("gemini-3.1-flash-lite-preview")
.unwrap()
.small_default
!small_defaults.is_empty(),
"built-in catalog should mark at least one small default model"
);
for model in small_defaults {
assert_eq!(
catalog
.small_default_for_provider(&model.provider)
.unwrap()
.id,
model.id
);
}
}
#[test]
@ -3693,68 +3684,6 @@ reasoning_effort = "levels"
assert!(Catalog::builtin().get("nonexistent-model").is_none());
}
#[test]
fn gemini_3_1_flash_lite_in_catalog() {
let m = Catalog::builtin()
.get("gemini-3.1-flash-lite-preview")
.unwrap();
insta::assert_debug_snapshot!(m, @r#"
Model {
id: "gemini-3.1-flash-lite-preview",
provider: gemini,
family: "gemini-3",
display_name: "Gemini 3.1 Flash Lite (Preview)",
limits: ModelLimits {
context_window: 1048576,
max_output: Some(
65536,
),
},
training: Some(
"2025-01-01",
),
knowledge_cutoff: Some(
"January 2025",
),
features: ModelFeatures {
tools: true,
vision: true,
reasoning: true,
reasoning_effort: Levels,
prompt_cache: false,
},
costs: ModelCosts {
input_cost_per_mtok: Some(
0.25,
),
output_cost_per_mtok: Some(
1.5,
),
cache_input_cost_per_mtok: Some(
0.0625,
),
},
estimated_output_tps: Some(
200.0,
),
aliases: [
"gemini-flash-lite",
],
default: false,
small_default: true,
configured: false,
}
"#);
}
#[test]
fn gemini_flash_lite_alias() {
assert_eq!(
Catalog::builtin().get("gemini-flash-lite").unwrap().id,
"gemini-3.1-flash-lite-preview"
);
}
#[test]
fn kimi_k2_5_in_catalog() {
let m = Catalog::builtin().get("kimi-k2.5").unwrap();

View file

@ -60,6 +60,31 @@ input_cost_per_mtok = 2.0
output_cost_per_mtok = 12.0
cache_input_cost_per_mtok = 0.5
[models."gemini-3.5-flash"]
provider = "gemini"
api_id = "gemini-3.5-flash"
display_name = "Gemini 3.5 Flash"
family = "gemini-3"
training = "2025-01-01"
knowledge_cutoff = "January 2025"
estimated_output_tps = 150
aliases = ["gemini-35-flash"]
[models."gemini-3.5-flash".limits]
context_window = 1048576
max_output = 65536
[models."gemini-3.5-flash".features]
tools = true
vision = true
reasoning = true
reasoning_effort = "levels"
[models."gemini-3.5-flash".costs]
input_cost_per_mtok = 1.5
output_cost_per_mtok = 9.0
cache_input_cost_per_mtok = 0.15
[models."gemini-3-flash-preview"]
provider = "gemini"
api_id = "gemini-3-flash-preview"
@ -85,28 +110,28 @@ input_cost_per_mtok = 0.5
output_cost_per_mtok = 3.0
cache_input_cost_per_mtok = 0.125
[models."gemini-3.1-flash-lite-preview"]
[models."gemini-3.1-flash-lite"]
provider = "gemini"
api_id = "gemini-3.1-flash-lite-preview"
display_name = "Gemini 3.1 Flash Lite (Preview)"
api_id = "gemini-3.1-flash-lite"
display_name = "Gemini 3.1 Flash Lite"
family = "gemini-3"
training = "2025-01-01"
knowledge_cutoff = "January 2025"
estimated_output_tps = 200
aliases = ["gemini-flash-lite"]
aliases = ["gemini-flash-lite", "gemini-3.1-flash-lite-preview"]
small_default = true
[models."gemini-3.1-flash-lite-preview".limits]
[models."gemini-3.1-flash-lite".limits]
context_window = 1048576
max_output = 65536
[models."gemini-3.1-flash-lite-preview".features]
[models."gemini-3.1-flash-lite".features]
tools = true
vision = true
reasoning = true
reasoning_effort = "levels"
[models."gemini-3.1-flash-lite-preview".costs]
[models."gemini-3.1-flash-lite".costs]
input_cost_per_mtok = 0.25
output_cost_per_mtok = 1.5
cache_input_cost_per_mtok = 0.0625
cache_input_cost_per_mtok = 0.025