feat: prepare ARR for controlled public deployment
This commit is contained in:
116
agent_integration/config.py
Normal file
116
agent_integration/config.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Environment-backed configuration for the Open Agent API integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Mapping, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "https://superagent.nianxx.cn"
|
||||
DEFAULT_SESSION_DB = "runtime/agent_sessions.sqlite3"
|
||||
SUPPORTED_AUTH_MODES = {"bearer", "x-api-key"}
|
||||
PLACEHOLDER_API_KEYS = {"changeme", "df_open_replace_me", "df_open_xxx"}
|
||||
LOCAL_HTTP_HOSTS = {"localhost", "127.0.0.1", "::1"}
|
||||
|
||||
|
||||
class AgentConfigurationError(ValueError):
|
||||
"""Raised when required Agent integration configuration is invalid."""
|
||||
|
||||
|
||||
def _positive_float(name: str, raw_value: str) -> float:
|
||||
try:
|
||||
value = float(raw_value)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise AgentConfigurationError(f"{name} must be a number") from exc
|
||||
if value <= 0:
|
||||
raise AgentConfigurationError(f"{name} must be greater than zero")
|
||||
return value
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentConfig:
|
||||
"""Validated runtime configuration with no implicit dotenv loading."""
|
||||
|
||||
base_url: str = DEFAULT_BASE_URL
|
||||
api_key: Optional[str] = None
|
||||
auth_mode: str = "bearer"
|
||||
timeout_seconds: float = 60.0
|
||||
connect_timeout_seconds: float = 10.0
|
||||
session_db: Path = Path(DEFAULT_SESSION_DB)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
base_url = self.base_url.strip().rstrip("/")
|
||||
parsed = urlparse(base_url)
|
||||
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
||||
raise AgentConfigurationError("DEERFLOW_BASE_URL must be an absolute http(s) URL")
|
||||
if parsed.scheme == "http" and parsed.hostname not in LOCAL_HTTP_HOSTS:
|
||||
raise AgentConfigurationError(
|
||||
"DEERFLOW_BASE_URL must use HTTPS unless it points to localhost"
|
||||
)
|
||||
|
||||
auth_mode = self.auth_mode.strip().lower()
|
||||
if auth_mode not in SUPPORTED_AUTH_MODES:
|
||||
raise AgentConfigurationError("DEERFLOW_AUTH_MODE must be 'bearer' or 'x-api-key'")
|
||||
|
||||
api_key = self.api_key.strip() if self.api_key else None
|
||||
if self.timeout_seconds <= 0:
|
||||
raise AgentConfigurationError("DEERFLOW_TIMEOUT_SECONDS must be greater than zero")
|
||||
if self.connect_timeout_seconds <= 0:
|
||||
raise AgentConfigurationError("DEERFLOW_CONNECT_TIMEOUT_SECONDS must be greater than zero")
|
||||
|
||||
object.__setattr__(self, "base_url", base_url)
|
||||
object.__setattr__(self, "auth_mode", auth_mode)
|
||||
object.__setattr__(self, "api_key", api_key)
|
||||
object.__setattr__(self, "session_db", Path(self.session_db))
|
||||
|
||||
@classmethod
|
||||
def from_env(
|
||||
cls,
|
||||
env: Optional[Mapping[str, str]] = None,
|
||||
*,
|
||||
require_api_key: bool = True,
|
||||
) -> "AgentConfig":
|
||||
source = os.environ if env is None else env
|
||||
api_key = source.get("DEERFLOW_OPEN_API_KEY", "").strip() or None
|
||||
if require_api_key and not cls._is_real_api_key(api_key):
|
||||
raise AgentConfigurationError(
|
||||
"DEERFLOW_OPEN_API_KEY is required and must replace the example placeholder"
|
||||
)
|
||||
|
||||
return cls(
|
||||
base_url=source.get("DEERFLOW_BASE_URL", DEFAULT_BASE_URL),
|
||||
api_key=api_key,
|
||||
auth_mode=source.get("DEERFLOW_AUTH_MODE", "bearer"),
|
||||
timeout_seconds=_positive_float(
|
||||
"DEERFLOW_TIMEOUT_SECONDS",
|
||||
source.get("DEERFLOW_TIMEOUT_SECONDS", "60"),
|
||||
),
|
||||
connect_timeout_seconds=_positive_float(
|
||||
"DEERFLOW_CONNECT_TIMEOUT_SECONDS",
|
||||
source.get("DEERFLOW_CONNECT_TIMEOUT_SECONDS", "10"),
|
||||
),
|
||||
session_db=Path(source.get("DEERFLOW_SESSION_DB", DEFAULT_SESSION_DB)),
|
||||
)
|
||||
|
||||
def redacted_summary(self) -> Dict[str, Any]:
|
||||
"""Return configuration diagnostics without exposing the credential."""
|
||||
|
||||
return {
|
||||
"base_url": self.base_url,
|
||||
"auth_mode": self.auth_mode,
|
||||
"api_key_configured": self.api_key_configured,
|
||||
"timeout_seconds": self.timeout_seconds,
|
||||
"connect_timeout_seconds": self.connect_timeout_seconds,
|
||||
"session_db": str(self.session_db),
|
||||
}
|
||||
|
||||
@property
|
||||
def api_key_configured(self) -> bool:
|
||||
return self._is_real_api_key(self.api_key)
|
||||
|
||||
@staticmethod
|
||||
def _is_real_api_key(api_key: Optional[str]) -> bool:
|
||||
return bool(api_key and api_key.strip().lower() not in PLACEHOLDER_API_KEYS)
|
||||
Reference in New Issue
Block a user