Files
tracer-cloud--opensre/gateway/config/get_gateway_settings.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

175 lines
5.6 KiB
Python

"""Telegram gateway configuration loaded from env and integration store."""
from __future__ import annotations
import logging
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Annotated, Any
from pydantic import Field, ValidationError, field_validator
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
from config.strict_config import StrictConfigModel
from integrations.messaging_security import MessagingIdentityPolicy, MessagingPlatform
from integrations.store import get_integration
logger = logging.getLogger(__name__)
class GatewayConfigurationError(RuntimeError):
"""Raised when Telegram gateway configuration is invalid."""
class GatewaySettings(StrictConfigModel):
"""Runtime settings for the Telegram gateway process."""
bot_token: str
allowed_user_ids: list[str] = Field(default_factory=list)
max_concurrent_turns: int = Field(default=4, ge=1)
stream_edit_interval_seconds: float = Field(default=1.5, gt=0)
auto_start_enabled: bool = True
class GatewayEnv(BaseSettings):
"""Environment-backed Telegram gateway settings."""
model_config = SettingsConfigDict(env_prefix="TELEGRAM_", extra="ignore")
bot_token: str = ""
# NoDecode keeps pydantic-settings from JSON-decoding the env value so the
# CSV validator below can parse "42,99" instead of raising a SettingsError.
allowed_users: Annotated[list[str], NoDecode] = Field(default_factory=list)
gateway_max_concurrent: int = Field(default=4, ge=1)
gateway_stream_edit_interval_seconds: float = Field(default=1.5, gt=0)
gateway_auto_start: bool = True
@field_validator("allowed_users", mode="before")
@classmethod
def parse_allowed_users(cls, value: Any) -> Any:
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
return value
@field_validator("gateway_auto_start", mode="before")
@classmethod
def parse_auto_start(cls, value: Any) -> bool:
if isinstance(value, bool):
return value
if isinstance(value, str):
return value.strip().lower() not in {"0", "false", "no", "off"}
return bool(value)
@dataclass(frozen=True)
class TelegramInboundMessage:
"""Normalized inbound Telegram DM text or callback."""
update_id: int
user_id: str
chat_id: str
message_id: str
text: str
def load_telegram_credentials() -> Mapping[str, Any]:
"""Load Telegram credentials from the integration store."""
try:
record = get_integration(MessagingPlatform.TELEGRAM.value)
except Exception as exc:
raise GatewayConfigurationError("Could not load Telegram integration") from exc
if not isinstance(record, Mapping):
logger.info("Telegram integration not configured; using env only")
return {}
credentials = record.get("credentials")
if not isinstance(credentials, Mapping):
logger.info("Telegram integration has no credentials; using env only")
return {}
return credentials
def store_bot_token(credentials: Mapping[str, Any]) -> str:
return str(credentials.get("bot_token") or "").strip()
def store_allowed_users(credentials: Mapping[str, Any]) -> list[str]:
raw_policy = credentials.get("identity_policy")
if not raw_policy:
return []
if not isinstance(raw_policy, Mapping):
raise GatewayConfigurationError("Telegram identity_policy must be an object")
try:
policy = MessagingIdentityPolicy.model_validate(raw_policy)
except ValidationError as exc:
raise GatewayConfigurationError("Invalid Telegram identity_policy") from exc
return list(policy.allowed_user_ids)
def choose_bot_token(env: GatewayEnv, credentials: Mapping[str, Any]) -> str:
token = env.bot_token or store_bot_token(credentials)
if not token:
raise GatewayConfigurationError(
"Telegram bot token is missing. Set TELEGRAM_BOT_TOKEN or configure the Telegram integration."
)
return token
def choose_authorized_users(env: GatewayEnv, credentials: Mapping[str, Any]) -> list[str]:
users = store_allowed_users(credentials) or env.allowed_users
if not users:
logger.warning("Telegram allowed users are not configured")
return users
def load_gateway_settings() -> GatewaySettings:
"""Load complete Telegram gateway settings."""
try:
env = GatewayEnv()
credentials = load_telegram_credentials()
return GatewaySettings(
bot_token=choose_bot_token(env, credentials),
allowed_user_ids=choose_authorized_users(env, credentials),
max_concurrent_turns=env.gateway_max_concurrent,
stream_edit_interval_seconds=env.gateway_stream_edit_interval_seconds,
auto_start_enabled=env.gateway_auto_start,
)
except ValidationError as exc:
raise GatewayConfigurationError("Invalid Telegram gateway configuration") from exc
def try_load_gateway_settings_for_startup(
*,
logger: logging.Logger,
respect_auto_start: bool = True,
) -> GatewaySettings | None:
"""Load gateway settings for optional background startup; return None when skipped."""
try:
settings = load_gateway_settings()
except GatewayConfigurationError as exc:
logger.debug("[telegram-gateway] startup skipped: %s", exc)
return None
if respect_auto_start and not settings.auto_start_enabled:
logger.debug("[telegram-gateway] auto-start disabled in config")
return None
if not settings.bot_token:
logger.warning("[telegram-gateway] no bot token configured")
return None
return settings