mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-03 14:05:26 +02:00
58aef0dc8a
## 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"](https://github.com/alishahryar1/free-claude-code/commit/01d589488185c1f85112f1a49c47f04512846161) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=40312173)</sub> <!-- /greptile_comment -->
94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
"""App-scoped provider runtime orchestration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable, MutableMapping
|
|
|
|
from config.settings import Settings
|
|
from providers.base import BaseProvider
|
|
from providers.model_listing import ProviderModelInfo
|
|
|
|
from .cache import ProviderCache
|
|
from .discovery import ProviderModelDiscovery
|
|
from .model_cache import ProviderModelCache
|
|
from .validation import ConfiguredModelValidator
|
|
|
|
|
|
class ProviderRuntime:
|
|
"""Own provider instances, model discovery, validation, and cleanup."""
|
|
|
|
def __init__(
|
|
self,
|
|
settings: Settings,
|
|
providers: MutableMapping[str, BaseProvider] | None = None,
|
|
) -> None:
|
|
self.settings = settings
|
|
self._provider_cache = ProviderCache(settings, providers)
|
|
self._model_cache = ProviderModelCache()
|
|
self._discovery = ProviderModelDiscovery(
|
|
settings,
|
|
self.resolve_provider,
|
|
self._model_cache,
|
|
)
|
|
self._validator = ConfiguredModelValidator(
|
|
settings,
|
|
self.resolve_provider,
|
|
self._model_cache,
|
|
)
|
|
|
|
def is_cached(self, provider_id: str) -> bool:
|
|
"""Return whether a provider for this id is already cached."""
|
|
return self._provider_cache.is_cached(provider_id)
|
|
|
|
def resolve_provider(self, provider_id: str) -> BaseProvider:
|
|
"""Return an existing provider or create it lazily."""
|
|
return self._provider_cache.get(provider_id)
|
|
|
|
def cache_model_ids(self, provider_id: str, model_ids: Iterable[str]) -> None:
|
|
"""Store raw provider model ids for later instant API responses."""
|
|
self._model_cache.cache_model_ids(provider_id, model_ids)
|
|
|
|
def cache_model_infos(
|
|
self, provider_id: str, model_infos: Iterable[ProviderModelInfo]
|
|
) -> None:
|
|
"""Store provider model metadata for later instant API responses."""
|
|
self._model_cache.cache_model_infos(provider_id, model_infos)
|
|
|
|
def cached_model_ids(self) -> dict[str, frozenset[str]]:
|
|
"""Return cached raw provider model ids by provider."""
|
|
return self._model_cache.cached_model_ids()
|
|
|
|
def cached_model_supports_thinking(
|
|
self, provider_id: str, model_id: str
|
|
) -> bool | None:
|
|
"""Return cached thinking support when a provider exposes it."""
|
|
return self._model_cache.cached_model_supports_thinking(provider_id, model_id)
|
|
|
|
def cached_prefixed_model_refs(self) -> tuple[str, ...]:
|
|
"""Return cached provider models in user-selectable ``provider/model`` form."""
|
|
return self._model_cache.cached_prefixed_model_refs()
|
|
|
|
def cached_prefixed_model_infos(self) -> tuple[ProviderModelInfo, ...]:
|
|
"""Return cached provider models with user-selectable prefixed ids."""
|
|
return self._model_cache.cached_prefixed_model_infos()
|
|
|
|
async def refresh_model_list_cache(self, *, only_missing: bool = False) -> None:
|
|
"""Best-effort refresh of model lists for usable providers."""
|
|
await self._discovery.refresh_model_list_cache(only_missing=only_missing)
|
|
|
|
def start_model_list_refresh(self) -> None:
|
|
"""Start a non-blocking cache warmup for missing eligible provider lists."""
|
|
self._discovery.start_model_list_refresh()
|
|
|
|
async def validate_configured_models(self) -> None:
|
|
"""Fail unless every configured chat model exists upstream."""
|
|
await self._validator.validate_configured_models()
|
|
|
|
async def cleanup(self) -> None:
|
|
"""Cancel discovery, clean provider instances, and clear model metadata."""
|
|
try:
|
|
await self._discovery.cleanup()
|
|
await self._provider_cache.cleanup()
|
|
finally:
|
|
self._model_cache.clear()
|