Initial commit
This commit is contained in:
129
server/config.py
Normal file
129
server/config.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
config.py — Configuration loading and validation.
|
||||
|
||||
Loaded once at startup. Fails fast if required variables are missing.
|
||||
All other modules import `settings` from here.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env from the project root (one level above server/)
|
||||
_env_path = Path(__file__).parent.parent / ".env"
|
||||
load_dotenv(_env_path)
|
||||
|
||||
|
||||
_PROJECT_ROOT = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def _extract_agent_name(fallback: str = "Jarvis") -> str:
|
||||
"""Read agent name from SOUL.md. Looks for 'You are **Name**', then the # heading."""
|
||||
try:
|
||||
soul = (_PROJECT_ROOT / "SOUL.md").read_text(encoding="utf-8")
|
||||
except FileNotFoundError:
|
||||
return fallback
|
||||
# Primary: "You are **Name**"
|
||||
m = re.search(r"You are \*\*([^*]+)\*\*", soul)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
# Fallback: first "# Name" heading, dropping anything after " — "
|
||||
for line in soul.splitlines():
|
||||
if line.startswith("# "):
|
||||
name = line[2:].split("—")[0].strip()
|
||||
if name:
|
||||
return name
|
||||
return fallback
|
||||
|
||||
|
||||
def _require(key: str) -> str:
|
||||
"""Get a required environment variable, fail fast if missing."""
|
||||
value = os.getenv(key)
|
||||
if not value:
|
||||
print(f"[aide] FATAL: Required environment variable '{key}' is not set.", file=sys.stderr)
|
||||
print(f"[aide] Copy .env.example to .env and fill in your values.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return value
|
||||
|
||||
|
||||
def _optional(key: str, default: str = "") -> str:
|
||||
return os.getenv(key, default)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
# Required
|
||||
db_master_password: str
|
||||
|
||||
# AI provider selection — keys are stored in the DB, not here
|
||||
default_provider: str = "anthropic" # "anthropic", "openrouter", or "openai"
|
||||
default_model: str = "" # Empty = use provider's default model
|
||||
|
||||
# Optional with defaults
|
||||
port: int = 8080
|
||||
max_tool_calls: int = 20
|
||||
max_autonomous_runs_per_hour: int = 10
|
||||
timezone: str = "Europe/Oslo"
|
||||
|
||||
# Agent identity — derived from SOUL.md at startup, fallback if file absent
|
||||
agent_name: str = "Jarvis"
|
||||
|
||||
# Model selection — empty list triggers auto-discovery at runtime
|
||||
available_models: list[str] = field(default_factory=list)
|
||||
default_chat_model: str = ""
|
||||
|
||||
# Database
|
||||
aide_db_url: str = ""
|
||||
|
||||
|
||||
def _load() -> Settings:
|
||||
master_password = _require("DB_MASTER_PASSWORD")
|
||||
|
||||
default_provider = _optional("DEFAULT_PROVIDER", "anthropic").lower()
|
||||
default_model = _optional("DEFAULT_MODEL", "")
|
||||
|
||||
_known_providers = {"anthropic", "openrouter", "openai"}
|
||||
if default_provider not in _known_providers:
|
||||
print(f"[aide] FATAL: Unknown DEFAULT_PROVIDER '{default_provider}'. Use 'anthropic', 'openrouter', or 'openai'.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
port = int(_optional("PORT", "8080"))
|
||||
max_tool_calls = int(_optional("MAX_TOOL_CALLS", "20"))
|
||||
max_runs = int(_optional("MAX_AUTONOMOUS_RUNS_PER_HOUR", "10"))
|
||||
timezone = _optional("TIMEZONE", "Europe/Oslo")
|
||||
|
||||
def _normalize_model(m: str) -> str:
|
||||
"""Prepend default_provider if model has no provider prefix."""
|
||||
parts = m.split(":", 1)
|
||||
if len(parts) == 2 and parts[0] in _known_providers:
|
||||
return m
|
||||
return f"{default_provider}:{m}"
|
||||
|
||||
available_models: list[str] = [] # unused; kept for backward compat
|
||||
default_chat_model_raw = _optional("DEFAULT_CHAT_MODEL", "")
|
||||
default_chat_model = _normalize_model(default_chat_model_raw) if default_chat_model_raw else ""
|
||||
|
||||
aide_db_url = _require("AIDE_DB_URL")
|
||||
|
||||
return Settings(
|
||||
agent_name=_extract_agent_name(),
|
||||
db_master_password=master_password,
|
||||
default_provider=default_provider,
|
||||
default_model=default_model,
|
||||
port=port,
|
||||
max_tool_calls=max_tool_calls,
|
||||
max_autonomous_runs_per_hour=max_runs,
|
||||
timezone=timezone,
|
||||
available_models=available_models,
|
||||
default_chat_model=default_chat_model,
|
||||
aide_db_url=aide_db_url,
|
||||
)
|
||||
|
||||
|
||||
# Module-level singleton — import this everywhere
|
||||
settings = _load()
|
||||
Reference in New Issue
Block a user