"""Application configuration loaded from environment variables. Settings are read from `.env` at process start. The Anthropic API key and the session-cookie signing secret are the only required values; everything else has a sensible default so the app can boot in dev without ceremony. """ from __future__ import annotations import secrets from pydantic import Field, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore") # Required secrets -- wrapped in SecretStr so accidental logging or repr # does not leak them. Access the raw value with `.get_secret_value()`. anthropic_api_key: SecretStr # Signing key for the server-issued session cookie. A fresh random value is # generated at import if none is configured -- this means sessions do not # survive a process restart in dev, which is the desired behavior until a # real secret is set in the environment. session_secret: SecretStr = Field(default_factory=lambda: SecretStr(secrets.token_urlsafe(32))) anthropic_model: str = "claude-sonnet-4-5" max_tokens: int = 1024 # Upper bound on the Anthropic HTTP call. A stuck request must not hold a # worker thread forever -- see the tool-use loop cap in agent.py for the # paired total-work bound. anthropic_timeout_seconds: float = 30.0 server_host: str = "127.0.0.1" server_port: int = 8014 # Session store bounds. Protects against a trivial DoS that opens many # sessions or drives a single session to unbounded history length. session_store_max_entries: int = 10_000 session_idle_ttl_seconds: int = 1800 # 30 minutes max_turns_per_session: int = 40 # Hard cap on iterations of the tool-use loop within a single turn. The # model should never legitimately need this many tool calls for a support # conversation -- the cap exists to stop a runaway loop. max_tool_use_iterations: int = 8 # Per-minute sliding-window rate limits. Enforced by a tiny in-memory # limiter in server.py; suitable for a single-process demo deployment. rate_limit_per_ip_per_minute: int = 30 rate_limit_per_session_per_minute: int = 20 # Session cookie configuration. session_cookie_name: str = "bookly_session" session_cookie_secure: bool = False # Flip to True behind HTTPS. session_cookie_max_age_seconds: int = 60 * 60 * 8 # 8 hours # The type ignore is needed because pydantic-settings reads `anthropic_api_key` # and `session_secret` from environment / .env at runtime, but mypy sees them as # required constructor arguments and has no way to know about that. settings = Settings() # type: ignore[call-arg]