mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-10 00:14:16 +00:00
## Problem
Provider construction, model discovery, validation, and cleanup lived in
one registry module. API and admin routes depended on registry-shaped
app state and legacy process-level provider helpers.
## Changes
| Before | After |
| --- | --- |
| `providers.registry` mixed provider factories, config, cache,
discovery, validation, and cleanup. | `providers.runtime` splits
factories, config, cache, model cache, discovery, validation, and
runtime orchestration. |
| API and admin routes read `app.state.provider_registry` and sometimes
created registries ad hoc. | API and admin routes use app-scoped
`ProviderRuntime` through `app.state.provider_runtime`. |
| `api.dependencies` kept process-global provider cache helpers. |
`api.dependencies` resolves providers only through the app-scoped
runtime. |
| Registry-shaped tests preserved old internal boundaries. |
Runtime-shaped tests assert provider config, construction, cache,
discovery, validation, and import boundaries. |
<!-- greptile_comment -->
<details open><summary><h3>Greptile Summary</h3></summary>
This PR moves provider lifecycle ownership from the old registry module
into an app-scoped runtime package. The main changes are:
- Split provider config, factory wiring, instance cache, model cache,
discovery, validation, and cleanup into `providers.runtime` modules.
- Updated API and admin routes to resolve providers and model metadata
through `app.state.provider_runtime`.
- Removed legacy process-global provider helpers and the deleted
`providers.registry` module.
- Updated docs, smoke metadata, import-boundary checks, and tests for
the new runtime ownership model.
- Bumped the package version and lockfile metadata for the production
refactor.
</details>
<h3>Confidence Score: 5/5</h3>
The provider runtime refactor appears merge-safe with no identified
blocking issues.
The changes consistently move provider ownership to app-scoped runtime
modules and update API, admin, docs, smoke metadata, import-boundary
checks, and tests around that architecture.
<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>
**What T-Rex did**
- Ran a baseline and head comparison of provider registry and runtime
states, verifying the after-state shows head
state\_has\_provider\_registry=False and
state\_has\_provider\_runtime=True, that GET /v1/models and admin
endpoints respond with 200, and that provider\_resolver\_called via
runtime, with assertions passing.
- Verified that the four focused provider-runtime contract tests passed
in both the before and after refactor runs, including runtime split
checks, with exit code 0.
- Identified environmental blockers that prevented the smoke-runtime
workflow from running, including uv unavailability, missing pytest for
/usr/local/bin/python, and Python 3.11 being used despite pyproject.toml
requiring \>=3.14.
<a
href="https://app.greptile.com/trex/runs/12528505/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=1"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"
height="32"></picture></a>
<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>
<sub>Reviews (1): Last reviewed commit: ["Refactor provider runtime
ownership"](01d5894881)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=40312173)</sub>
<!-- /greptile_comment -->
72 lines
2.9 KiB
Python
72 lines
2.9 KiB
Python
"""Provider model-list metadata cache."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable
|
|
|
|
from config.provider_catalog import SUPPORTED_PROVIDER_IDS
|
|
from providers.model_listing import ProviderModelInfo, model_infos_from_ids
|
|
|
|
|
|
class ProviderModelCache:
|
|
"""Store provider model metadata for instant model-list responses."""
|
|
|
|
def __init__(self) -> None:
|
|
self._model_infos_by_provider: dict[str, dict[str, ProviderModelInfo]] = {}
|
|
|
|
def cache_model_ids(self, provider_id: str, model_ids: Iterable[str]) -> None:
|
|
"""Store raw provider model ids with unknown capability metadata."""
|
|
self.cache_model_infos(provider_id, model_infos_from_ids(model_ids))
|
|
|
|
def cache_model_infos(
|
|
self, provider_id: str, model_infos: Iterable[ProviderModelInfo]
|
|
) -> None:
|
|
"""Store provider model metadata by raw provider model id."""
|
|
clean_infos = {
|
|
info.model_id: info for info in model_infos if info.model_id.strip()
|
|
}
|
|
self._model_infos_by_provider[provider_id] = clean_infos
|
|
|
|
def cached_model_ids(self) -> dict[str, frozenset[str]]:
|
|
"""Return cached raw provider model ids by provider."""
|
|
return {
|
|
provider_id: frozenset(infos)
|
|
for provider_id, infos in self._model_infos_by_provider.items()
|
|
}
|
|
|
|
def has_provider(self, provider_id: str) -> bool:
|
|
"""Return whether this provider has any cached model-list result."""
|
|
return provider_id in self._model_infos_by_provider
|
|
|
|
def cached_model_supports_thinking(
|
|
self, provider_id: str, model_id: str
|
|
) -> bool | None:
|
|
"""Return cached thinking support when a provider exposes it."""
|
|
info = self._model_infos_by_provider.get(provider_id, {}).get(model_id)
|
|
if info is None:
|
|
return None
|
|
return info.supports_thinking
|
|
|
|
def cached_prefixed_model_refs(self) -> tuple[str, ...]:
|
|
"""Return cached provider models in user-selectable ``provider/model`` form."""
|
|
return tuple(info.model_id for info in self.cached_prefixed_model_infos())
|
|
|
|
def cached_prefixed_model_infos(self) -> tuple[ProviderModelInfo, ...]:
|
|
"""Return cached provider models with user-selectable prefixed ids."""
|
|
infos: list[ProviderModelInfo] = []
|
|
for provider_id in SUPPORTED_PROVIDER_IDS:
|
|
provider_infos = self._model_infos_by_provider.get(provider_id, {})
|
|
infos.extend(
|
|
ProviderModelInfo(
|
|
model_id=f"{provider_id}/{info.model_id}",
|
|
supports_thinking=info.supports_thinking,
|
|
)
|
|
for info in sorted(
|
|
provider_infos.values(), key=lambda item: item.model_id
|
|
)
|
|
)
|
|
return tuple(infos)
|
|
|
|
def clear(self) -> None:
|
|
"""Clear all cached model metadata."""
|
|
self._model_infos_by_provider.clear()
|