8080 # models.dev. OpenRouter is omitted because it's live-fetched per-user.
8181}
8282
83+ _GOOGLE_GENERATIVE_AI_PROVIDER = "Google Generative AI"
84+ _GOOGLE_MODEL_PREFIX = "models/"
85+
86+
87+ def _catalog_model_identity (provider : str , model : dict [str , Any ]) -> tuple [str | None , str ]:
88+ """Return a provider-aware identity for matching static and models.dev rows."""
89+ name = model .get ("name" )
90+ if provider == _GOOGLE_GENERATIVE_AI_PROVIDER and isinstance (name , str ):
91+ name = name .removeprefix (_GOOGLE_MODEL_PREFIX )
92+ return name , model .get ("model_type" ) or "llm"
93+
8394
8495def _snapshot_dir () -> Path :
8596 """Return the directory where the disk snapshot lives.
@@ -352,18 +363,18 @@ def apply_models_dev_overrides(
352363 the fresher metadata (context windows, pricing, capabilities) is preserved.
353364
354365 models.dev exposes no ``deprecated`` field of its own, so this function
355- preserves the static-list curation by name : any model that was already
356- flagged deprecated in the bundled ``*_constants.py`` lists keeps that flag
357- after the override. Dated-snapshot ids
366+ preserves the static-list curation by catalog identity : any model that was
367+ already flagged deprecated in the bundled ``*_constants.py`` lists keeps
368+ that flag after the override. Dated-snapshot ids
358369 (e.g. ``claude-opus-4-5-20251101``, ``gpt-4o-2024-05-13``) and language
359370 models whose most recent date is older than :data:`_AGE_DEPRECATION_DAYS` are also
360371 auto-flagged in :func:`_translate_model_entry`. ``now`` is forwarded for
361372 testability.
362373 """
363374 now = now or datetime .now (tz = timezone .utc )
364- # Build provider_name -> {model_name: deprecated } from the static lists so
365- # we can preserve the static curation through the override.
366- static_deprecated_by_provider : dict [str , set [str ]] = {}
375+ # Build provider_name -> {model identity } from the static lists so we can
376+ # preserve the static curation through the override.
377+ static_deprecated_by_provider : dict [str , set [tuple [ str | None , str ] ]] = {}
367378 for group in static_lists :
368379 for entry in group :
369380 if not isinstance (entry , dict ):
@@ -373,7 +384,7 @@ def apply_models_dev_overrides(
373384 if not provider or not name :
374385 continue
375386 if entry .get ("deprecated" ):
376- static_deprecated_by_provider .setdefault (provider , set ()).add (name )
387+ static_deprecated_by_provider .setdefault (provider , set ()).add (_catalog_model_identity ( provider , entry ) )
377388
378389 # Build provider_name -> translated list once.
379390 overrides : dict [str , list [dict [str , Any ]]] = {}
@@ -382,15 +393,12 @@ def apply_models_dev_overrides(
382393 if not isinstance (provider_block , dict ):
383394 continue
384395 static_deprecated = static_deprecated_by_provider .get (provider_name , set ())
385- translated = [
386- _translate_model_entry (
387- provider_name ,
388- m ,
389- deprecated = (m .get ("id" ) in static_deprecated ),
390- now = now ,
391- )
392- for m in _provider_model_dicts (provider_block )
393- ]
396+ translated = []
397+ for model in _provider_model_dicts (provider_block ):
398+ translated_model = _translate_model_entry (provider_name , model , now = now )
399+ if _catalog_model_identity (provider_name , translated_model ) in static_deprecated :
400+ translated_model ["deprecated" ] = True
401+ translated .append (translated_model )
394402 if translated :
395403 overrides [provider_name ] = translated
396404
@@ -403,13 +411,30 @@ def _fold_custom_static_entries(provider: str, group: list[dict[str, Any]]) -> N
403411 models.dev only knows the models it ships, so any model a user added to
404412 the bundled ``*_constants.py`` lists would otherwise be silently dropped
405413 when its provider's group is replaced. Carrying those rows over (matched
406- by name) keeps user-added models selectable while models.dev still wins
407- for every model it does cover. The override list is mutated in place so a
408- later same-provider group (e.g. the OpenAI embeddings group) folds its
409- custom rows into the same list already appended to ``replaced``.
414+ by provider-aware identity) keeps user-added models selectable while
415+ models.dev still wins for every model it does cover. The override list
416+ is mutated in place so a later same-provider group (e.g. the OpenAI
417+ embeddings group) folds its custom rows into the same list already
418+ appended to ``replaced``.
410419 """
411- override_names = {m .get ("name" ) for m in overrides [provider ] if isinstance (m , dict )}
412- custom_entries = [m for m in group if isinstance (m , dict ) and m .get ("name" ) not in override_names ]
420+ overrides_by_identity = {
421+ _catalog_model_identity (provider , model ): model for model in overrides [provider ] if isinstance (model , dict )
422+ }
423+ custom_entries = []
424+ for static_model in group :
425+ if not isinstance (static_model , dict ):
426+ continue
427+ override_model = overrides_by_identity .get (_catalog_model_identity (provider , static_model ))
428+ if override_model is None :
429+ custom_entries .append (static_model )
430+ continue
431+
432+ static_name = static_model .get ("name" )
433+ if provider == _GOOGLE_GENERATIVE_AI_PROVIDER and static_name != override_model .get ("name" ):
434+ override_model ["name" ] = static_name
435+ if static_icon := static_model .get ("icon" ):
436+ override_model ["icon" ] = static_icon
437+
413438 if custom_entries :
414439 overrides [provider ].extend (custom_entries )
415440
0 commit comments