Files
Ali Khokhar 58aef0dc8a Refactor provider runtime ownership (#925)
## 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 -->
2026-06-27 15:04:34 -07:00

475 lines
17 KiB
Python

"""Hierarchical public capability map.
This module is the architectural companion to ``smoke.features``. The feature
inventory says which public features must be covered; this map records the
subfeature contracts, owning module boundary, and coverage owners that protect
them while the internals are refactored.
"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class CapabilityContract:
capability: str
subfeature: str
feature_id: str
owner: str
inputs: str
outputs: str
failure: str
pytest_tests: tuple[str, ...]
smoke_tests: tuple[str, ...] = ()
CAPABILITY_CONTRACTS: tuple[CapabilityContract, ...] = (
CapabilityContract(
"api_compatibility",
"routes_and_probes",
"anthropic_api_routes",
"api.routes",
"Anthropic-compatible HTTP requests",
"JSON or Anthropic SSE responses",
"HTTPException or Anthropic error payload",
("tests/api/test_api.py",),
("test_probe_and_models_routes", "test_stop_endpoint_reports_no_messaging"),
),
CapabilityContract(
"api_compatibility",
"head_options_probes",
"probe_routes",
"api.routes",
"HEAD/OPTIONS probe requests",
"204 with Allow header",
"auth failure when configured",
("tests/api/test_api.py::test_probe_endpoints_return_204_with_allow_headers",),
("test_probe_and_models_routes",),
),
CapabilityContract(
"api_compatibility",
"client_request_shapes",
"drop_in_claude_code_replacement",
"api.handlers.messages.MessagesHandler",
"Claude Code, VS Code, and JetBrains shaped requests",
"compatible streaming response",
"provider or validation error in Anthropic shape",
("tests/api/test_api.py", "tests/cli/test_cli.py"),
("test_vscode_and_jetbrains_shaped_requests",),
),
CapabilityContract(
"api_compatibility",
"responses_api",
"drop_in_codex_replacement",
"api.handlers.responses.ResponsesHandler",
"OpenAI Responses requests from Codex",
"Responses SSE or JSON response",
"OpenAI-shaped error or conversion error",
(
"tests/api/test_openai_responses.py",
"tests/core/openai_responses/test_sse.py",
"tests/cli/test_entrypoints.py",
"tests/cli/test_codex_model_catalog.py",
),
(
"test_probe_and_models_routes",
"test_provider_codex_responses_text_e2e",
),
),
CapabilityContract(
"api_compatibility",
"client_extensions",
"vscode_extension",
"api.handlers.messages.MessagesHandler",
"VS Code beta query/header variants",
"accepted Anthropic-compatible response",
"HTTP error only for auth/provider failures",
(),
("test_vscode_and_jetbrains_shaped_requests",),
),
CapabilityContract(
"api_compatibility",
"client_extensions",
"intellij_extension",
"api.handlers.messages.MessagesHandler",
"JetBrains/ACP request variants",
"accepted Anthropic-compatible response",
"HTTP error only for auth/provider failures",
(),
("test_vscode_and_jetbrains_shaped_requests",),
),
CapabilityContract(
"auth",
"anthropic_headers",
"optional_authentication",
"api.dependencies.require_api_key",
"x-api-key, authorization, anthropic-auth-token",
"request accepted or 401",
"401 missing/invalid API key",
("tests/api/test_auth.py",),
("test_auth_token_is_enforced_for_all_supported_header_shapes",),
),
CapabilityContract(
"provider_routing",
"provider_runtime",
"provider_matrix",
"providers.runtime.ProviderRuntime",
"provider id and Settings",
"configured BaseProvider instance",
"503 for missing credentials; invalid_request_error for unknown provider",
("tests/api/test_dependencies.py", "tests/providers/test_provider_runtime.py"),
(
"test_configured_provider_models_stream_successfully",
"test_provider_matrix_presence_e2e",
),
),
CapabilityContract(
"provider_routing",
"claude_model_resolution",
"per_model_mapping",
"api.model_router.ModelRouter",
"Claude model name and configured MODEL_* values",
"provider id plus provider model name",
"settings validation rejects invalid provider prefixes",
("tests/api/test_model_router.py", "tests/config/test_config.py"),
("test_model_mapping_configuration_is_consistent",),
),
CapabilityContract(
"provider_routing",
"mixed_provider_resolution",
"mixed_provider_mapping",
"api.model_router.ModelRouter",
"Opus/Sonnet/Haiku/fallback model config",
"distinct provider selections per request",
"skip live execution when providers are not configured",
("tests/api/test_model_router.py", "tests/config/test_config.py"),
("test_mixed_provider_model_mapping_when_configured",),
),
CapabilityContract(
"provider_routing",
"provider_runtime_config",
"provider_proxy_timeout_config",
"providers.runtime.ProviderRuntime",
"provider proxy, timeout, and rate-limit settings",
"provider client and scoped limiter config",
"provider construction failure",
("tests/api/test_dependencies.py", "tests/providers/test_provider_runtime.py"),
),
CapabilityContract(
"provider_routing",
"zero_cost_backends",
"zero_cost_provider_access",
"providers.runtime.ProviderRuntime",
"configured free/local provider",
"streaming response from selected backend",
"missing env or upstream unavailable skip in smoke",
("tests/api/test_dependencies.py", "tests/providers/"),
("test_configured_provider_models_stream_successfully",),
),
CapabilityContract(
"streaming_conversion",
"anthropic_sse_lifecycle",
"streaming_error_mapping",
"core.anthropic.streaming.AnthropicStreamLedger",
"provider stream chunks or native SSE events",
"Anthropic message/content/error SSE lifecycle",
"Anthropic-compatible error event",
(
"tests/contracts/test_stream_contracts.py",
"tests/providers/test_streaming_errors.py",
),
),
CapabilityContract(
"streaming_conversion",
"thinking_blocks",
"thinking_token_support",
"core.anthropic.thinking",
"reasoning_content, reasoning_details, <think> text, native thinking",
"Claude thinking blocks or suppression",
"thinking hidden when disabled",
(
"tests/contracts/test_stream_contracts.py",
"tests/providers/test_converter.py",
"tests/providers/test_deepseek.py",
"tests/providers/test_nvidia_nim_request.py",
"tests/providers/test_open_router.py",
),
(
"test_per_model_thinking_config_e2e",
"test_provider_reasoning_tool_continuation_e2e",
),
),
CapabilityContract(
"streaming_conversion",
"heuristic_tools",
"heuristic_tool_parser",
"core.anthropic.tools",
"textual tool-call output",
"structured Anthropic tool_use blocks",
"text fallback when malformed",
("tests/providers/test_parsers.py", "tests/contracts/test_stream_contracts.py"),
(
"test_live_tool_use_when_configured_model_supports_tools",
"test_provider_reasoning_tool_continuation_e2e",
),
),
CapabilityContract(
"streaming_conversion",
"subagent_task_control",
"subagent_control",
"core.anthropic.streaming.AnthropicStreamLedger",
"Task tool call arguments",
"run_in_background=false",
"invalid JSON flushed as safe object",
("tests/providers/test_subagent_interception.py",),
),
CapabilityContract(
"request_behavior",
"local_optimizations",
"request_optimization",
"api.optimization_handlers",
"Claude Code probe/title/suggestion/filepath requests",
"local MessagesResponse without provider call",
"falls through to provider when unmatched/disabled",
(
"tests/api/test_optimization_handlers.py",
"tests/api/test_routes_optimizations.py",
),
("test_optimization_fast_paths_do_not_need_provider",),
),
CapabilityContract(
"request_behavior",
"token_counting",
"count_tokens_contract",
"core.anthropic.get_token_count",
"messages, system blocks, tools, images, thinking, tool results",
"positive input token estimate",
"500 with readable detail on unexpected failure",
("tests/api/test_request_utils.py",),
("test_count_tokens_accepts_thinking_tools_and_results",),
),
CapabilityContract(
"config",
"env_precedence",
"config_env_precedence",
"config.settings.Settings",
"process env, user env file, repo env file, FCC_ENV_FILE",
"deterministic settings values",
"validation error for invalid settings",
("tests/config/test_config.py",),
),
CapabilityContract(
"config",
"removed_env_migration",
"removed_env_migration",
"config.settings.Settings",
"NIM_ENABLE_THINKING or ENABLE_THINKING in env or dotenv",
"startup succeeds and stale keys do not change thinking defaults",
"removed key ignored",
("tests/config/test_config.py",),
),
CapabilityContract(
"provider_runtime",
"rate_limit_and_disconnect",
"smart_rate_limiting",
"providers.rate_limit.GlobalRateLimiter",
"concurrent provider requests and 429/disconnect failures",
"proactive throttle, retry, cleanup",
"mapped provider error or smoke skip for upstream disconnect",
("tests/providers/test_provider_rate_limit.py",),
("test_client_disconnect_mid_stream_does_not_crash_server",),
),
CapabilityContract(
"local_providers",
"lmstudio_native_messages",
"lmstudio_endpoint",
"providers.anthropic_messages.AnthropicMessagesTransport",
"Anthropic request body and local LM Studio URL",
"native /messages stream pass-through",
"SSE error event for local upstream failure",
(
"tests/providers/test_lmstudio.py",
"tests/providers/test_anthropic_messages.py",
),
("test_lmstudio_models_endpoint_when_available",),
),
CapabilityContract(
"local_providers",
"llamacpp_native_messages",
"llamacpp_endpoint",
"providers.anthropic_messages.AnthropicMessagesTransport",
"Anthropic request body and llama.cpp URL",
"native /messages stream pass-through",
"SSE error event for local upstream failure",
(
"tests/providers/test_llamacpp.py",
"tests/providers/test_anthropic_messages.py",
),
("test_llamacpp_models_endpoint_when_available",),
),
CapabilityContract(
"local_providers",
"ollama_native_messages",
"ollama_endpoint",
"providers.ollama.OllamaProvider",
"Anthropic request body and local Ollama root URL",
"Anthropic SSE stream through the proxy",
"SSE error event for local upstream failure",
("tests/providers/test_ollama.py",),
("test_ollama_models_endpoint_when_available",),
),
CapabilityContract(
"openrouter",
"native_anthropic_messages",
"provider_matrix",
"providers.open_router.OpenRouterProvider",
"Anthropic Messages request for OpenRouter",
"native OpenRouter Anthropic SSE",
"Anthropic SSE error shape",
(
"tests/providers/test_open_router.py",
"tests/providers/test_anthropic_messages.py",
),
("test_configured_provider_models_stream_successfully",),
),
CapabilityContract(
"messaging",
"discord_telegram_platforms",
"discord_telegram_bot",
"messaging.workflow.MessagingWorkflow",
"Discord/Telegram incoming messages and API callbacks",
"live progress edits and final transcript",
"platform retry/fallback or user-facing error",
(
"tests/messaging/test_discord_platform.py",
"tests/messaging/test_telegram.py",
),
("test_telegram_bot_api_permissions", "test_discord_bot_api_permissions"),
),
CapabilityContract(
"messaging",
"commands",
"messaging_commands",
"messaging.commands",
"/stop, /clear, /stats command messages",
"task cancellation, cleanup, or stats response",
"best-effort platform cleanup",
(
"tests/messaging/test_handler.py",
"tests/messaging/test_handler_integration.py",
),
),
CapabilityContract(
"messaging",
"tree_threading",
"tree_threading",
"messaging.trees.TreeQueueManager",
"reply-based message graph",
"queued branches and scoped cancellation",
"node error propagation",
(
"tests/messaging/test_tree_queue.py",
"tests/messaging/test_tree_concurrency.py",
),
),
CapabilityContract(
"persistence",
"restart_restore",
"restart_restore",
"messaging.session.SessionStore",
"stored tree JSON",
"restored node-to-tree mapping",
"stale pending work marked lost",
("tests/messaging/test_restart_reply_restore.py",),
),
CapabilityContract(
"persistence",
"session_store",
"session_persistence",
"messaging.session.SessionStore",
"tree data, node mapping, and message log",
"JSON persistence compatible with existing files",
"best-effort flush error logging",
("tests/messaging/test_session_store_edge_cases.py",),
),
CapabilityContract(
"voice",
"voice_transcription",
"voice_notes",
"messaging.voice.VoiceTranscriptionService",
"Discord/Telegram audio file and voice backend settings",
"transcribed prompt routed to handler",
"missing optional extra or backend error shown to user",
(
"tests/messaging/test_voice_handlers.py",
"tests/messaging/test_transcription.py",
),
("test_voice_transcription_backend_when_explicitly_enabled",),
),
CapabilityContract(
"cli",
"package_entrypoints",
"package_cli_entrypoints",
"cli.entrypoints",
"installed console scripts",
"config scaffold or uvicorn server startup",
"process cleanup in finally",
("tests/cli/test_entrypoints.py",),
(
"test_fcc_init_scaffolds_user_config",
"test_free_claude_code_entrypoint_starts_server",
),
),
CapabilityContract(
"cli",
"claude_cli_drop_in",
"claude_cli_drop_in",
"cli.managed.session.ManagedClaudeSession",
"Claude CLI binary and proxy env",
"stream-json events and session id mapping",
"stderr/error event and process cleanup",
("tests/cli/test_cli.py",),
(
"test_claude_cli_prompt_when_available",
"test_nvidia_nim_cli_matrix_e2e",
"test_openrouter_free_cli_matrix_e2e",
),
),
CapabilityContract(
"cli",
"codex_cli_drop_in",
"drop_in_codex_replacement",
"cli.launchers.codex",
"Codex CLI binary and fcc provider env",
"Responses config, auth env, and native /model catalog injection",
"proxy preflight and catalog fail-open warning",
(
"tests/cli/test_codex_model_catalog.py",
"tests/cli/test_entrypoints.py",
),
(),
),
CapabilityContract(
"extensibility",
"provider_platform_abcs",
"extensible_provider_platform_abcs",
"providers.runtime and messaging.platforms.factory",
"new provider/platform implementations",
"registered BaseProvider or messaging component bundle",
"unknown platform returns None; unknown provider errors",
(
"tests/contracts/test_feature_manifest.py",
"tests/providers/test_provider_runtime.py",
),
),
)
def capability_names() -> set[str]:
return {contract.capability for contract in CAPABILITY_CONTRACTS}
def contracted_feature_ids() -> set[str]:
return {contract.feature_id for contract in CAPABILITY_CONTRACTS}