fix(catalog): accept current live Models.dev schema in parser (#4185)#4191
Conversation
Live models.dev/catalog.json sends `interleaved` in two shapes: a bare
boolean (`interleaved: true`, ~32 provider rows) and an object
(`interleaved: { "field": "reasoning_content" }`, the majority). The
parser modeled only the object form, so `from_str::<ModelsDevCatalog>`
rejected every boolean row before the live catalog could be used.
Replace the object-only `ModelsDevInterleaved` struct with an untagged
enum that accepts both shapes, and add `is_enabled()` / `field()`
accessors so downstream consumers read the metadata without matching the
enum. The object variant keeps `field` optional and ignores unknown
sibling keys, so partial/empty objects and future keys still parse.
Scope is strictly parser tolerance: no bundled data changes, no picker
wiring. Verified against a live catalog.json fetch on 2026-07-07
(151 providers; 32 boolean rows, 530 object rows, no other shapes).
Tests cover boolean true/false, object-with-field, empty/unknown-key
objects, and a mixed live-ish sample with unmodeled provider/model keys.
|
Claude finished @Hmbown's task in 1m 29s —— View job Claude finished reviewing
SummaryThis is a clean, tightly-scoped fix. Correctness
Provider/model/route safety (EPIC #2608)
Reuse/simplification
Tests
Security
Note on verification: I wasn't able to run No blocking issues found. This looks ready to merge pending the actual CI run. |
Summary
Fixes #4185 — the foundation of the Models.dev catalog chain (#4184 → #4185 → #4187 → #4186 → #4188).
Live
https://models.dev/catalog.jsonsends the provider-rowinterleavedfield in two shapes:interleaved: true(32 provider rows as of 2026-07-07, e.g.vercel,amazon-bedrock,azure,togetherai), andinterleaved: { "field": "reasoning_content" }(the majority, 530 rows).The parser modeled only the object shape (
ModelsDevInterleavedwas a struct with an optionalfield), soserde_json::from_str::<ModelsDevCatalog>rejected every boolean row and the live catalog could not be parsed at all.Change
ModelsDevInterleavedstruct with an#[serde(untagged)]enum that accepts both the boolean and object shapes.is_enabled()andfield()accessors so downstream lanes (Normalize Models.dev provider IDs onto CodeWhale provider kinds #4186 / Fetch and cache live Models.dev catalog into ProviderLake #4187) can read interleaved metadata without matching the enum.fieldoptional and ignores unknown sibling keys, so empty/partial objects and future upstream keys still parse.Scope is strictly parser tolerance. No bundled-data changes (that's #4188), no picker wiring (separate lane). Only
crates/config/src/models_dev.rsis touched;ModelsDevInterleavedhas no other consumers in the workspace and the bundled snapshot never used the field.Verification
Confirmed against a live
catalog.jsonfetch on 2026-07-07: 151 providers, 32 booleaninterleavedrows, 530 object rows, no other shapes.cargo fmt --all --check— cleancargo clippy --workspace --all-features --locked(with the repo's-D warnings+ agreed-Aset) — cleancargo test --workspace --locked— passes, except one pre-existing, environment-dependent failure unrelated to this change (tui::hotbar::actions::tests::skill_hotbar_action_activates_skill_through_dollar_alias, "skill registered from the startup skill cache"), which fails identically on pristineorigin/mainwith this change stashed out.cargo build --release -p codewhale-tui— succeeds.New tests in
crates/config/src/models_dev.rscover:interleaved: true,interleaved: false,interleaved: { "field": "..." }, empty/unknown-key objects, and a mixed live-ish sample with unmodeled provider/model keys.Acceptance (from #4185)
interleaved: trueparses successfully.interleaved: falseparses successfully.interleaved: { "field": "..." }parses successfully.catalog.jsoncan deserialize without schema errors.