2026-02-20 20:17:00 +00:00
|
|
|
"""Configuration loaded from environment variables."""
|
|
|
|
|
|
2026-02-23 22:18:28 +01:00
|
|
|
import logging
|
2026-02-20 20:17:00 +00:00
|
|
|
import os
|
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-02-23 22:18:28 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-02-20 20:17:00 +00:00
|
|
|
try:
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
load_dotenv()
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass # python-dotenv is optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Config:
|
2026-02-23 22:18:28 +01:00
|
|
|
# Primary model for conversation with parents — litellm format.
|
2026-02-21 07:35:37 +00:00
|
|
|
# The matching API key must be set as an env var (ANTHROPIC_API_KEY, OPENAI_API_KEY, …).
|
2026-02-23 22:18:28 +01:00
|
|
|
# Example: "anthropic/claude-opus-4-6", "google/gemini-2.0-flash", "openai/gpt-4o"
|
2026-02-21 07:35:37 +00:00
|
|
|
ai_model: str = "anthropic/claude-opus-4-6"
|
2026-02-20 20:17:00 +00:00
|
|
|
|
2026-02-23 22:18:28 +01:00
|
|
|
# Lightweight model for simple tasks such as email-label translation.
|
|
|
|
|
# Can be from a different provider than ai_model.
|
|
|
|
|
# If not configured (SIMPLE_MODEL env var unset), falls back to ai_model with a warning.
|
|
|
|
|
# Example: "anthropic/claude-haiku-4-5-20251001", "openai/gpt-4o-mini"
|
|
|
|
|
simple_model: str = ""
|
|
|
|
|
|
2026-02-20 20:17:00 +00:00
|
|
|
# Email — IMAP (receiving)
|
|
|
|
|
imap_host: str = ""
|
|
|
|
|
imap_port: int = 993
|
|
|
|
|
imap_username: str = ""
|
|
|
|
|
imap_password: str = ""
|
|
|
|
|
imap_use_ssl: bool = True
|
|
|
|
|
|
|
|
|
|
# Email — SMTP (sending)
|
|
|
|
|
smtp_host: str = ""
|
|
|
|
|
smtp_port: int = 587
|
|
|
|
|
smtp_use_tls: bool = True
|
|
|
|
|
|
|
|
|
|
# Registration email address shown to parents
|
|
|
|
|
registration_email: str = ""
|
|
|
|
|
|
2026-02-21 21:31:49 +00:00
|
|
|
# Admin notification routing.
|
|
|
|
|
# Each leader receives mail only when a day in their group is booked.
|
|
|
|
|
# For testing, point all three to your own address.
|
|
|
|
|
admin_email_indoor: str = "" # Indoor leader (Andrea Sigrist) — To when indoor booked
|
|
|
|
|
admin_email_outdoor: str = "" # Outdoor leader (Barbara Gross) — To when outdoor booked
|
|
|
|
|
admin_email_cc: str = "" # Always Cc'd (Markus Graf / admin); comma-separated if multiple
|
2026-02-21 21:23:51 +00:00
|
|
|
|
2026-02-20 20:17:00 +00:00
|
|
|
# Storage
|
|
|
|
|
data_dir: Path = field(default_factory=lambda: Path("data"))
|
|
|
|
|
knowledge_base_dir: Path = field(
|
|
|
|
|
default_factory=lambda: Path(
|
|
|
|
|
"openspec/changes/define-project-scope/content/knowledge-base"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Polling interval in seconds
|
|
|
|
|
poll_interval: int = 60
|
|
|
|
|
|
2026-02-22 19:55:23 +00:00
|
|
|
# Extended thinking — Anthropic models only.
|
|
|
|
|
# When set, enables the thinking phase before the LLM replies.
|
|
|
|
|
# Recommended value: 8000 (tokens). Set to None/unset to disable.
|
|
|
|
|
thinking_budget: int | None = None
|
|
|
|
|
|
2026-02-20 20:17:00 +00:00
|
|
|
@classmethod
|
|
|
|
|
def from_env(cls) -> "Config":
|
2026-02-23 22:18:28 +01:00
|
|
|
ai_model = os.getenv("AI_MODEL", "anthropic/claude-opus-4-6")
|
|
|
|
|
simple_model = os.getenv("SIMPLE_MODEL", "")
|
|
|
|
|
if not simple_model:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"SIMPLE_MODEL not configured — falling back to AI_MODEL (%s) for simple tasks "
|
|
|
|
|
"(set SIMPLE_MODEL to a cheaper model, e.g. anthropic/claude-haiku-4-5-20251001)",
|
|
|
|
|
ai_model,
|
|
|
|
|
)
|
|
|
|
|
simple_model = ai_model
|
2026-02-20 20:17:00 +00:00
|
|
|
return cls(
|
2026-02-23 22:18:28 +01:00
|
|
|
ai_model=ai_model,
|
|
|
|
|
simple_model=simple_model,
|
2026-02-20 20:17:00 +00:00
|
|
|
imap_host=os.getenv("IMAP_HOST", ""),
|
|
|
|
|
imap_port=int(os.getenv("IMAP_PORT", "993")),
|
|
|
|
|
imap_username=os.getenv("IMAP_USERNAME", ""),
|
|
|
|
|
imap_password=os.getenv("IMAP_PASSWORD", ""),
|
|
|
|
|
imap_use_ssl=os.getenv("IMAP_USE_SSL", "true").lower() == "true",
|
|
|
|
|
smtp_host=os.getenv("SMTP_HOST", ""),
|
|
|
|
|
smtp_port=int(os.getenv("SMTP_PORT", "587")),
|
|
|
|
|
smtp_use_tls=os.getenv("SMTP_USE_TLS", "true").lower() == "true",
|
|
|
|
|
registration_email=os.getenv("REGISTRATION_EMAIL", ""),
|
2026-02-21 21:31:49 +00:00
|
|
|
admin_email_indoor=os.getenv("ADMIN_EMAIL_INDOOR", ""),
|
|
|
|
|
admin_email_outdoor=os.getenv("ADMIN_EMAIL_OUTDOOR", ""),
|
|
|
|
|
admin_email_cc=os.getenv("ADMIN_EMAIL_CC", ""),
|
2026-02-20 20:17:00 +00:00
|
|
|
data_dir=Path(os.getenv("DATA_DIR", "data")),
|
|
|
|
|
knowledge_base_dir=Path(
|
|
|
|
|
os.getenv(
|
|
|
|
|
"KNOWLEDGE_BASE_DIR",
|
|
|
|
|
"openspec/changes/define-project-scope/content/knowledge-base",
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
poll_interval=int(os.getenv("POLL_INTERVAL", "60")),
|
2026-02-22 19:55:23 +00:00
|
|
|
thinking_budget=(
|
|
|
|
|
int(os.getenv("THINKING_BUDGET"))
|
|
|
|
|
if os.getenv("THINKING_BUDGET")
|
|
|
|
|
else None
|
|
|
|
|
),
|
2026-02-20 20:17:00 +00:00
|
|
|
)
|