Spaces:
Sleeping
Sleeping
| """Flat application settings schema loaded by Pydantic Settings.""" | |
| from __future__ import annotations | |
| from functools import lru_cache | |
| from typing import Any | |
| from pydantic import Field, field_validator, model_validator | |
| from pydantic_settings import BaseSettings, SettingsConfigDict | |
| from .constants import HTTP_CONNECT_TIMEOUT_DEFAULT | |
| from .env_files import ( | |
| ANTHROPIC_AUTH_TOKEN_ENV, | |
| env_file_override, | |
| settings_env_files, | |
| ) | |
| from .nim import NimSettings | |
| from .provider_catalog import BEDROCK_DEFAULT_BASE, SUPPORTED_PROVIDER_IDS | |
| from .reasoning import ReasoningPreference | |
| class Settings(BaseSettings): | |
| """Application settings loaded from environment variables.""" | |
| # ==================== Azure OpenAI ==================== | |
| azure_openai_api_key: str = Field( | |
| default="", validation_alias="AZURE_OPENAI_API_KEY" | |
| ) | |
| azure_openai_base_url: str = Field( | |
| default="", validation_alias="AZURE_OPENAI_BASE_URL" | |
| ) | |
| # ==================== OpenRouter Config ==================== | |
| open_router_api_key: str = Field(default="", validation_alias="OPENROUTER_API_KEY") | |
| # ==================== Mistral La Plateforme ==================== | |
| mistral_api_key: str = Field(default="", validation_alias="MISTRAL_API_KEY") | |
| # ==================== Mistral Codestral (codestral.mistral.ai) ==================== | |
| codestral_api_key: str = Field(default="", validation_alias="CODESTRAL_API_KEY") | |
| # ==================== DeepSeek Config ==================== | |
| deepseek_api_key: str = Field(default="", validation_alias="DEEPSEEK_API_KEY") | |
| # ==================== Kimi Config ==================== | |
| kimi_api_key: str = Field(default="", validation_alias="KIMI_API_KEY") | |
| # ==================== Kimi Code Subscription ==================== | |
| kimi_code_api_key: str = Field(default="", validation_alias="KIMI_CODE_API_KEY") | |
| # ==================== Wafer Config ==================== | |
| wafer_api_key: str = Field(default="", validation_alias="WAFER_API_KEY") | |
| # ==================== MiniMax Config ==================== | |
| minimax_api_key: str = Field(default="", validation_alias="MINIMAX_API_KEY") | |
| # ==================== OpenCode Zen / OpenCode Go ==================== | |
| # Same key from opencode.ai/auth; zen uses prefix ``opencode/``, Go uses ``opencode_go/``. | |
| opencode_api_key: str = Field(default="", validation_alias="OPENCODE_API_KEY") | |
| # ==================== Vercel AI Gateway ==================== | |
| vercel_ai_gateway_api_key: str = Field( | |
| default="", validation_alias="AI_GATEWAY_API_KEY" | |
| ) | |
| # ==================== Amazon Bedrock Mantle ==================== | |
| bedrock_api_key: str = Field( | |
| default="", validation_alias="AWS_BEARER_TOKEN_BEDROCK" | |
| ) | |
| bedrock_base_url: str = Field( | |
| default=BEDROCK_DEFAULT_BASE, | |
| validation_alias="BEDROCK_BASE_URL", | |
| ) | |
| # ==================== Hugging Face Inference Providers ==================== | |
| huggingface_api_key: str = Field(default="", validation_alias="HUGGINGFACE_API_KEY") | |
| # ==================== Cohere Compatibility API ==================== | |
| cohere_api_key: str = Field(default="", validation_alias="COHERE_API_KEY") | |
| # ==================== GitHub Models ==================== | |
| github_models_token: str = Field(default="", validation_alias="GITHUB_MODELS_TOKEN") | |
| # ==================== SambaNova Cloud ==================== | |
| sambanova_api_key: str = Field(default="", validation_alias="SAMBANOVA_API_KEY") | |
| # ==================== Kilo.ai Config ==================== | |
| kilo_api_key: str = Field(default="", validation_alias="KILO_API_KEY") | |
| # ==================== Z.ai Config ==================== | |
| zai_api_key: str = Field(default="", validation_alias="ZAI_API_KEY") | |
| # ==================== Fireworks AI Config ==================== | |
| fireworks_api_key: str = Field(default="", validation_alias="FIREWORKS_API_KEY") | |
| # ==================== Cloudflare Workers AI Config ==================== | |
| cloudflare_api_token: str = Field( | |
| default="", validation_alias="CLOUDFLARE_API_TOKEN" | |
| ) | |
| cloudflare_account_id: str = Field( | |
| default="", validation_alias="CLOUDFLARE_ACCOUNT_ID" | |
| ) | |
| # ==================== Google Gemini (Google AI Studio) ==================== | |
| gemini_api_key: str = Field(default="", validation_alias="GEMINI_API_KEY") | |
| # ==================== Google Vertex AI ==================== | |
| vertex_project_id: str = Field(default="", validation_alias="VERTEX_PROJECT_ID") | |
| vertex_location: str = Field(default="global", validation_alias="VERTEX_LOCATION") | |
| # ==================== Groq (OpenAI-compatible) ==================== | |
| groq_api_key: str = Field(default="", validation_alias="GROQ_API_KEY") | |
| # ==================== Cerebras Inference (OpenAI-compatible) ==================== | |
| cerebras_api_key: str = Field(default="", validation_alias="CEREBRAS_API_KEY") | |
| # ==================== Ollama Cloud ==================== | |
| ollama_api_key: str = Field(default="", validation_alias="OLLAMA_API_KEY") | |
| # ==================== Messaging Platform Selection ==================== | |
| # Valid: "telegram" | "discord" | "none" | |
| messaging_platform: str = Field( | |
| default="discord", validation_alias="MESSAGING_PLATFORM" | |
| ) | |
| messaging_rate_limit: int = Field( | |
| default=1, validation_alias="MESSAGING_RATE_LIMIT" | |
| ) | |
| messaging_rate_window: float = Field( | |
| default=1.0, validation_alias="MESSAGING_RATE_WINDOW" | |
| ) | |
| # ==================== NVIDIA NIM Config ==================== | |
| nvidia_nim_api_key: str = "" | |
| # ==================== LM Studio Config ==================== | |
| lm_studio_base_url: str = Field( | |
| default="http://localhost:1234/v1", | |
| validation_alias="LM_STUDIO_BASE_URL", | |
| ) | |
| # ==================== Llama.cpp Config ==================== | |
| llamacpp_base_url: str = Field( | |
| default="http://localhost:8080/v1", | |
| validation_alias="LLAMACPP_BASE_URL", | |
| ) | |
| # ==================== Ollama Config ==================== | |
| ollama_base_url: str = Field( | |
| default="http://localhost:11434", | |
| validation_alias="OLLAMA_BASE_URL", | |
| ) | |
| # ==================== Model ==================== | |
| # All Claude model requests are mapped to this single model (fallback) | |
| # Format: provider_type/model/name | |
| model: str = "nvidia_nim/nvidia/nemotron-3-super-120b-a12b" | |
| # Per-model overrides (optional, falls back to MODEL) | |
| # Each can use a different provider | |
| model_fable: str | None = Field(default=None, validation_alias="MODEL_FABLE") | |
| model_opus: str | None = Field(default=None, validation_alias="MODEL_OPUS") | |
| model_sonnet: str | None = Field(default=None, validation_alias="MODEL_SONNET") | |
| model_haiku: str | None = Field(default=None, validation_alias="MODEL_HAIKU") | |
| # ==================== Per-Provider Proxy ==================== | |
| openai_proxy: str = Field(default="", validation_alias="OPENAI_PROXY") | |
| azure_openai_proxy: str = Field(default="", validation_alias="AZURE_OPENAI_PROXY") | |
| nvidia_nim_proxy: str = Field(default="", validation_alias="NVIDIA_NIM_PROXY") | |
| open_router_proxy: str = Field(default="", validation_alias="OPENROUTER_PROXY") | |
| mistral_proxy: str = Field(default="", validation_alias="MISTRAL_PROXY") | |
| codestral_proxy: str = Field(default="", validation_alias="CODESTRAL_PROXY") | |
| lmstudio_proxy: str = Field(default="", validation_alias="LMSTUDIO_PROXY") | |
| llamacpp_proxy: str = Field(default="", validation_alias="LLAMACPP_PROXY") | |
| kimi_proxy: str = Field(default="", validation_alias="KIMI_PROXY") | |
| kimi_code_proxy: str = Field(default="", validation_alias="KIMI_CODE_PROXY") | |
| wafer_proxy: str = Field(default="", validation_alias="WAFER_PROXY") | |
| minimax_proxy: str = Field(default="", validation_alias="MINIMAX_PROXY") | |
| opencode_proxy: str = Field(default="", validation_alias="OPENCODE_PROXY") | |
| opencode_go_proxy: str = Field(default="", validation_alias="OPENCODE_GO_PROXY") | |
| vercel_ai_gateway_proxy: str = Field( | |
| default="", validation_alias="VERCEL_AI_GATEWAY_PROXY" | |
| ) | |
| bedrock_proxy: str = Field(default="", validation_alias="BEDROCK_PROXY") | |
| huggingface_proxy: str = Field(default="", validation_alias="HUGGINGFACE_PROXY") | |
| cohere_proxy: str = Field(default="", validation_alias="COHERE_PROXY") | |
| github_models_proxy: str = Field(default="", validation_alias="GITHUB_MODELS_PROXY") | |
| sambanova_proxy: str = Field(default="", validation_alias="SAMBANOVA_PROXY") | |
| kilo_proxy: str = Field(default="", validation_alias="KILO_PROXY") | |
| zai_proxy: str = Field(default="", validation_alias="ZAI_PROXY") | |
| fireworks_proxy: str = Field(default="", validation_alias="FIREWORKS_PROXY") | |
| cloudflare_proxy: str = Field(default="", validation_alias="CLOUDFLARE_PROXY") | |
| gemini_proxy: str = Field(default="", validation_alias="GEMINI_PROXY") | |
| vertex_proxy: str = Field(default="", validation_alias="VERTEX_PROXY") | |
| groq_proxy: str = Field(default="", validation_alias="GROQ_PROXY") | |
| cerebras_proxy: str = Field(default="", validation_alias="CEREBRAS_PROXY") | |
| ollama_cloud_proxy: str = Field(default="", validation_alias="OLLAMA_CLOUD_PROXY") | |
| # ==================== Provider Rate Limiting ==================== | |
| provider_rate_limit: int = Field(default=40, validation_alias="PROVIDER_RATE_LIMIT") | |
| provider_rate_window: int = Field( | |
| default=60, validation_alias="PROVIDER_RATE_WINDOW" | |
| ) | |
| provider_max_concurrency: int = Field( | |
| default=5, validation_alias="PROVIDER_MAX_CONCURRENCY" | |
| ) | |
| reasoning_policy: ReasoningPreference = Field( | |
| default=ReasoningPreference.CLIENT, | |
| validation_alias="REASONING_POLICY", | |
| ) | |
| reasoning_fable: ReasoningPreference = Field( | |
| default=ReasoningPreference.INHERIT, | |
| validation_alias="REASONING_FABLE", | |
| ) | |
| reasoning_opus: ReasoningPreference = Field( | |
| default=ReasoningPreference.INHERIT, | |
| validation_alias="REASONING_OPUS", | |
| ) | |
| reasoning_sonnet: ReasoningPreference = Field( | |
| default=ReasoningPreference.INHERIT, | |
| validation_alias="REASONING_SONNET", | |
| ) | |
| reasoning_haiku: ReasoningPreference = Field( | |
| default=ReasoningPreference.INHERIT, | |
| validation_alias="REASONING_HAIKU", | |
| ) | |
| # ==================== HTTP Client Timeouts ==================== | |
| http_read_timeout: float = Field( | |
| default=120.0, validation_alias="HTTP_READ_TIMEOUT" | |
| ) | |
| http_write_timeout: float = Field( | |
| default=10.0, validation_alias="HTTP_WRITE_TIMEOUT" | |
| ) | |
| http_connect_timeout: float = Field( | |
| default=HTTP_CONNECT_TIMEOUT_DEFAULT, | |
| validation_alias="HTTP_CONNECT_TIMEOUT", | |
| ) | |
| # ==================== Fast Prefix Detection ==================== | |
| fast_prefix_detection: bool = True | |
| # ==================== Optimizations ==================== | |
| enable_network_probe_mock: bool = True | |
| enable_title_generation_skip: bool = True | |
| enable_suggestion_mode_skip: bool = True | |
| enable_filepath_extraction_mock: bool = True | |
| # ==================== Local web server tools (web_search / web_fetch) ==================== | |
| # Off by default: these tools perform outbound HTTP from the proxy (SSRF risk). | |
| enable_web_server_tools: bool = Field( | |
| default=False, validation_alias="ENABLE_WEB_SERVER_TOOLS" | |
| ) | |
| # Comma-separated URL schemes allowed for web_fetch (default: http,https). | |
| web_fetch_allowed_schemes: str = Field( | |
| default="http,https", validation_alias="WEB_FETCH_ALLOWED_SCHEMES" | |
| ) | |
| # When true, skip private/loopback/link-local IP blocking for web_fetch (lab only). | |
| web_fetch_allow_private_networks: bool = Field( | |
| default=False, validation_alias="WEB_FETCH_ALLOW_PRIVATE_NETWORKS" | |
| ) | |
| # ==================== Debug / diagnostic logging (avoid sensitive content) ==================== | |
| # Minimum log level for the JSON file sink (DEBUG, INFO, WARNING, ERROR, CRITICAL). | |
| log_level: str = Field(default="INFO", validation_alias="LOG_LEVEL") | |
| # When false (default), API and SSE helpers log only metadata (counts, lengths, ids). | |
| log_raw_api_payloads: bool = Field( | |
| default=False, validation_alias="LOG_RAW_API_PAYLOADS" | |
| ) | |
| log_raw_sse_events: bool = Field( | |
| default=False, validation_alias="LOG_RAW_SSE_EVENTS" | |
| ) | |
| # When false (default), unhandled exceptions log only type + route metadata (no message/traceback). | |
| log_api_error_tracebacks: bool = Field( | |
| default=False, validation_alias="LOG_API_ERROR_TRACEBACKS" | |
| ) | |
| # When false (default), messaging logs omit text/transcription previews (metadata only). | |
| log_raw_messaging_content: bool = Field( | |
| default=False, validation_alias="LOG_RAW_MESSAGING_CONTENT" | |
| ) | |
| # When true, log full Claude CLI stderr, non-JSON lines, and parser error text. | |
| log_raw_cli_diagnostics: bool = Field( | |
| default=False, validation_alias="LOG_RAW_CLI_DIAGNOSTICS" | |
| ) | |
| # When true, log exception text / CLI error strings in messaging (may leak user content). | |
| log_messaging_error_details: bool = Field( | |
| default=False, validation_alias="LOG_MESSAGING_ERROR_DETAILS" | |
| ) | |
| debug_platform_edits: bool = Field( | |
| default=False, validation_alias="DEBUG_PLATFORM_EDITS" | |
| ) | |
| debug_subagent_stack: bool = Field( | |
| default=False, validation_alias="DEBUG_SUBAGENT_STACK" | |
| ) | |
| # ==================== NIM Settings ==================== | |
| nim: NimSettings = Field(default_factory=NimSettings) | |
| # ==================== Voice Note Transcription ==================== | |
| voice_note_enabled: bool = Field( | |
| default=True, validation_alias="VOICE_NOTE_ENABLED" | |
| ) | |
| # Device: "cpu" | "cuda" | "nvidia_nim" | |
| # - "cpu"/"cuda": local Whisper (requires voice_local extra: uv sync --extra voice_local) | |
| # - "nvidia_nim": NVIDIA NIM Whisper API (requires voice extra: uv sync --extra voice) | |
| whisper_device: str = Field(default="cpu", validation_alias="WHISPER_DEVICE") | |
| # Whisper model ID or short name (for local Whisper) or NVIDIA NIM model (for nvidia_nim) | |
| # Local Whisper: "tiny", "base", "small", "medium", "large-v2", "large-v3", "large-v3-turbo" | |
| # NVIDIA NIM: "nvidia/parakeet-ctc-1.1b-asr", "openai/whisper-large-v3", etc. | |
| whisper_model: str = Field(default="base", validation_alias="WHISPER_MODEL") | |
| # ==================== Bot Wrapper Config ==================== | |
| telegram_bot_token: str | None = None | |
| allowed_telegram_user_id: str | None = None | |
| telegram_proxy_url: str = Field(default="", validation_alias="TELEGRAM_PROXY_URL") | |
| discord_bot_token: str | None = Field( | |
| default=None, validation_alias="DISCORD_BOT_TOKEN" | |
| ) | |
| allowed_discord_channels: str | None = Field( | |
| default=None, validation_alias="ALLOWED_DISCORD_CHANNELS" | |
| ) | |
| allowed_dir: str = "" | |
| max_message_log_entries_per_chat: int | None = Field( | |
| default=None, validation_alias="MAX_MESSAGE_LOG_ENTRIES_PER_CHAT" | |
| ) | |
| # ==================== Server ==================== | |
| host: str = "0.0.0.0" | |
| port: int = 8082 | |
| open_admin_browser: bool = Field(default=True, validation_alias="FCC_OPEN_BROWSER") | |
| # Optional proxy bearer token protecting public API endpoints. | |
| # Set via env `ANTHROPIC_AUTH_TOKEN`. When empty, no auth is required. | |
| anthropic_auth_token: str = Field( | |
| default="", validation_alias="ANTHROPIC_AUTH_TOKEN" | |
| ) | |
| # Handle empty strings for optional string fields | |
| def parse_optional_str(cls, v: Any) -> Any: | |
| if v == "": | |
| return None | |
| return v | |
| def parse_optional_log_cap(cls, v: Any) -> Any: | |
| if v == "" or v is None: | |
| return None | |
| return v | |
| def validate_log_level(cls, v: str) -> str: | |
| valid = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} | |
| upper = v.upper() | |
| if upper not in valid: | |
| raise ValueError(f"LOG_LEVEL must be one of {sorted(valid)}, got {v!r}") | |
| return upper | |
| def validate_root_reasoning_policy( | |
| cls, value: ReasoningPreference | |
| ) -> ReasoningPreference: | |
| if value is ReasoningPreference.INHERIT: | |
| raise ValueError("REASONING_POLICY cannot inherit") | |
| return value | |
| def validate_whisper_device(cls, v: str) -> str: | |
| if v not in ("cpu", "cuda", "nvidia_nim"): | |
| raise ValueError( | |
| f"whisper_device must be 'cpu', 'cuda', or 'nvidia_nim', got {v!r}" | |
| ) | |
| return v | |
| def validate_messaging_platform(cls, v: str) -> str: | |
| if v not in ("telegram", "discord", "none"): | |
| raise ValueError( | |
| f"messaging_platform must be 'telegram', 'discord', or 'none', got {v!r}" | |
| ) | |
| return v | |
| def validate_messaging_rate_limit(cls, v: int) -> int: | |
| if v <= 0: | |
| raise ValueError("messaging_rate_limit must be > 0") | |
| return v | |
| def validate_messaging_rate_window(cls, v: float) -> float: | |
| if v <= 0: | |
| raise ValueError("messaging_rate_window must be > 0") | |
| return float(v) | |
| def validate_web_fetch_allowed_schemes(cls, v: str) -> str: | |
| schemes = [part.strip().lower() for part in v.split(",") if part.strip()] | |
| if not schemes: | |
| raise ValueError("web_fetch_allowed_schemes must list at least one scheme") | |
| for scheme in schemes: | |
| if not scheme.isascii() or not scheme.isalpha(): | |
| raise ValueError( | |
| f"Invalid URL scheme in web_fetch_allowed_schemes: {scheme!r}" | |
| ) | |
| return ",".join(schemes) | |
| def validate_model_format(cls, v: str | None) -> str | None: | |
| if v is None: | |
| return None | |
| if "/" not in v: | |
| raise ValueError( | |
| f"Model must be prefixed with provider type. " | |
| f"Valid providers: {', '.join(SUPPORTED_PROVIDER_IDS)}. " | |
| f"Format: provider_type/model/name" | |
| ) | |
| provider = v.split("/", 1)[0] | |
| if provider not in SUPPORTED_PROVIDER_IDS: | |
| supported = ", ".join(f"'{p}'" for p in SUPPORTED_PROVIDER_IDS) | |
| raise ValueError(f"Invalid provider: '{provider}'. Supported: {supported}") | |
| return v | |
| def check_nvidia_nim_api_key(self) -> Settings: | |
| if ( | |
| self.voice_note_enabled | |
| and self.whisper_device == "nvidia_nim" | |
| and not self.nvidia_nim_api_key.strip() | |
| ): | |
| raise ValueError( | |
| "NVIDIA_NIM_API_KEY is required when WHISPER_DEVICE is 'nvidia_nim'. " | |
| "Set it in your .env file." | |
| ) | |
| return self | |
| def prefer_dotenv_anthropic_auth_token(self) -> Settings: | |
| """Let explicit .env auth config override stale shell/client tokens.""" | |
| dotenv_value = env_file_override(self.model_config, ANTHROPIC_AUTH_TOKEN_ENV) | |
| if dotenv_value is not None: | |
| self.anthropic_auth_token = dotenv_value | |
| return self | |
| model_config = SettingsConfigDict( | |
| env_file=settings_env_files(), | |
| env_file_encoding="utf-8", | |
| extra="ignore", | |
| ) | |
| def get_settings() -> Settings: | |
| """Get cached settings instance.""" | |
| return Settings() | |