"""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. 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 from functools import lru_cache from app.core.config import settings from app.core.errors import ProviderUnavailableError from app.infrastructure.adapters import mock from app.infrastructure.adapters.openai_compatible import OpenAICompatibleModelAdapter @lru_cache def get_model_provider(): provider = settings.ai_default_provider.lower() if provider == "mock": return mock.MockModelProviderAdapter() if provider == "openai_compatible": return OpenAICompatibleModelAdapter( base_url=settings.openai_compatible_base_url, default_api_key=settings.openai_compatible_api_key, ) if provider == "agy": from app.infrastructure.adapters.agy import ( AgyModelAdapter, load_agy_accounts, report_agy_state, ) return AgyModelAdapter(loader=load_agy_accounts, report=report_agy_state) raise ProviderUnavailableError( f"AI_DEFAULT_PROVIDER='{provider}' پشتیبانی نمی‌شود. " "مقادیر مجاز: mock | openai_compatible | agy" ) @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()