"""Adapter registry. One place decides which concrete adapter backs each port, driven purely by the ``*_PROVIDER`` environment switches. Everything defaults to ``mock`` so a fresh clone runs with no credentials — *except* the model provider: ``agy`` (Google Antigravity) is the only one the platform talks to, and its accounts are added from the admin console. Adapters that hold process state (the mock Git forge, the mock port ledger) are cached as singletons so behaviour is consistent within a process. """ from __future__ import annotations import logging from functools import lru_cache from app.core.config import settings from app.core.errors import ProviderUnavailableError from app.infrastructure.adapters import mock logger = logging.getLogger("agy.registry") @lru_cache def get_model_provider(): """The one model adapter: agy over its logged-in account pool.""" configured = settings.ai_default_provider.strip().lower() if configured != "agy": # Tolerate a stale ``AI_DEFAULT_PROVIDER`` from before agy became the # only provider: warn loudly, keep serving with agy either way. logger.warning( "AI_DEFAULT_PROVIDER=%r is obsolete — agy is the only supported " "model provider; using it", settings.ai_default_provider, ) from app.infrastructure.adapters.agy import ( AgyModelAdapter, load_agy_accounts, report_agy_state, ) return AgyModelAdapter(loader=load_agy_accounts, report=report_agy_state) @lru_cache def get_git_provider(): provider = settings.git_provider.lower() if provider == "mock": return mock.MockGitAdapter() if provider == "gitea": from app.infrastructure.adapters.gitea import GiteaGitAdapter return GiteaGitAdapter( base_url=settings.gitea_internal_url or settings.gitea_url, admin_user=settings.gitea_admin_user, admin_token=settings.gitea_admin_token, ) raise ProviderUnavailableError( f"GIT_PROVIDER='{provider}' پشتیبانی نمی‌شود. مقادیر مجاز: mock | gitea" ) @lru_cache def get_domain_router(): provider = settings.domain_router.lower() if provider == "mock": return mock.MockDomainRouterAdapter( root_domain=settings.preview_root_domain, port_start=settings.preview_port_start, port_end=settings.preview_port_end, ) if provider == "caddy": from app.infrastructure.adapters.caddy import CaddyDomainRouterAdapter return CaddyDomainRouterAdapter( caddyfile_path=settings.caddyfile_path, root_domain=settings.preview_root_domain, port_start=settings.preview_port_start, port_end=settings.preview_port_end, ) raise ProviderUnavailableError( f"DOMAIN_ROUTER='{provider}' پشتیبانی نمی‌شود. مقادیر مجاز: mock | caddy" ) @lru_cache def get_sandbox_provider(): provider = settings.sandbox_provider.lower() if provider == "mock": return mock.LocalMockSandboxAdapter(max_output_bytes=settings.sandbox_max_output_bytes) if provider in {"daytona", "e2b"}: from app.infrastructure.adapters.sandbox_remote import build_remote_sandbox_adapter return build_remote_sandbox_adapter( provider, daytona_api_key=settings.daytona_api_key, daytona_api_url=settings.daytona_api_url, e2b_api_key=settings.e2b_api_key, ) raise ProviderUnavailableError( f"SANDBOX_PROVIDER='{provider}' پشتیبانی نمی‌شود. مقادیر مجاز: mock | daytona | e2b" ) @lru_cache def get_payment_provider(): provider = settings.payment_provider.lower() if provider == "mock": return mock.LocalMockPaymentAdapter() if provider == "stripe": from app.infrastructure.adapters.stripe_payments import StripePaymentAdapter return StripePaymentAdapter( secret_key=settings.stripe_secret_key, webhook_secret=settings.stripe_webhook_secret, ) raise ProviderUnavailableError( f"PAYMENT_PROVIDER='{provider}' پشتیبانی نمی‌شود. مقادیر مجاز: mock | stripe" ) @lru_cache def get_storage(): return mock.InMemoryStorageAdapter() @lru_cache def get_vector_store(): return mock.InMemoryVectorStoreAdapter() @lru_cache def get_notifier(): return mock.ConsoleNotificationAdapter() def reset_adapters() -> None: """Drop cached singletons. Used by tests that need a clean slate.""" for fn in ( get_model_provider, get_git_provider, get_domain_router, get_sandbox_provider, get_payment_provider, get_storage, get_vector_store, get_notifier, ): fn.cache_clear()