"""Shared runtime assembly for CLI and desktop interfaces.""" from __future__ import annotations from dataclasses import dataclass from javis.config.settings import Settings from javis.core.chat_service import ChatService from javis.core.provider_router import ApprovalCallback, HybridProvider, NoticeCallback from javis.memory.sqlite_store import SQLiteSessionStore from javis.memory.usage_store import SQLiteUsageStore, UsageStoreError from javis.providers.base import MissingApiKeyError from javis.providers.gemini import GeminiProvider from javis.providers.ollama import OllamaProvider from javis.security.privacy import PrivacyRouter from javis.security.secrets import SecretProvider, SecretStoreError @dataclass(frozen=True, slots=True) class RuntimeStatus: ollama_reachable: bool local_model_available: bool local_model: str gemini_key_available: bool gemini_configured: bool free_only: bool provider_mode: str cloud_requests_today: int | None local_fallbacks_today: int | None data_dir: str def cli_lines(self) -> list[str]: cloud_requests = ( self.cloud_requests_today if self.cloud_requests_today is not None else "unbekannt" ) local_fallbacks = ( self.local_fallbacks_today if self.local_fallbacks_today is not None else "unbekannt" ) return [ f"Ollama erreichbar: {'ja' if self.ollama_reachable else 'nein'}", f"Lokales Modell vorhanden: {'ja' if self.local_model_available else 'nein'}", f"Gemini-Schlüssel vorhanden: {'ja' if self.gemini_key_available else 'nein'}", f"Gemini konfiguriert: {'ja' if self.gemini_configured else 'nein'}", f"Nullkostenmodus: {'aktiv' if self.free_only else 'inaktiv'}", f"Aktiver Providermodus: {self.provider_mode}", f"Heutige Cloudanfragen: {cloud_requests}", f"Heutige lokale Fallbacks: {local_fallbacks}", ] @dataclass(slots=True) class JavisRuntime: settings: Settings service: ChatService local_provider: OllamaProvider hybrid_provider: HybridProvider usage_store: SQLiteUsageStore secrets: SecretProvider def status(self) -> RuntimeStatus: ollama = self.local_provider.probe() try: key_available = self.secrets.gemini_key_available() except SecretStoreError: key_available = False try: cloud_requests: int | None = self.usage_store.cloud_requests_on() local_fallbacks: int | None = self.usage_store.local_fallbacks_on() except UsageStoreError: cloud_requests = local_fallbacks = None return RuntimeStatus( ollama_reachable=ollama.reachable, local_model_available=ollama.model_available, local_model=self.local_provider.model, gemini_key_available=key_available, gemini_configured=( self.settings.gemini_enabled and self.settings.free_only and self.settings.billing_confirmed_disabled ), free_only=self.settings.free_only, provider_mode=self.hybrid_provider.mode, cloud_requests_today=cloud_requests, local_fallbacks_today=local_fallbacks, data_dir=str(self.settings.data_dir), ) def build_javis_runtime( settings: Settings, *, approval_callback: ApprovalCallback, notice_callback: NoticeCallback, secret_provider: SecretProvider | None = None, ) -> JavisRuntime: """Assemble the existing core once for any user interface.""" store = SQLiteSessionStore(settings.database_path) usage_store = SQLiteUsageStore(settings.usage_database_path) secrets = secret_provider or SecretProvider() local = OllamaProvider( settings.model, settings.ollama_base_url, settings.timeout_seconds, ) def create_cloud_provider() -> GeminiProvider: try: key = secrets.get_gemini_key() except SecretStoreError as exc: raise MissingApiKeyError( "Der Gemini-Schlüssel konnte nicht sicher geladen werden." ) from exc return GeminiProvider( model=settings.gemini_model, api_key=key, timeout_seconds=settings.gemini_timeout_seconds, max_output_tokens=settings.max_cloud_output_tokens, max_retries=settings.gemini_max_retries, ) hybrid = HybridProvider( local_provider=local, cloud_provider_factory=create_cloud_provider, cloud_model=settings.gemini_model, privacy_router=PrivacyRouter(), usage_store=usage_store, approval_callback=approval_callback, notice_callback=notice_callback, mode=settings.provider_mode, cloud_enabled=settings.gemini_enabled, billing_confirmed_disabled=settings.billing_confirmed_disabled, free_only=settings.free_only, max_cloud_requests_per_day=settings.max_cloud_requests_per_day, max_cloud_input_chars=settings.max_cloud_input_chars, max_cloud_context_messages=settings.max_cloud_context_messages, ) return JavisRuntime( settings=settings, service=ChatService(store, hybrid), local_provider=local, hybrid_provider=hybrid, usage_store=usage_store, secrets=secrets, )