Files
DocsGPT/docsgpt/core/settings/events.py
T
arc53-machine c17b23378e refactor(settings): split Settings into per-domain modules
docsgpt/core/settings.py had grown to 258 fields in one 600-line class,
touched by about two commits a week, with related settings scattered
(GitHub ingest caps inside the embeddings block, API keys in four places,
the OpenAI Responses knobs 100 lines from the other OpenAI fields).

It is now a package: one module per domain (auth, llm, embeddings,
retrieval, vectorstores, database, workers, ingestion, ocr, storage,
connectors, server, events, agents, guardrails, scheduler, sandbox,
speech), each a SettingsGroup owning its fields and validators, composed
by multiple inheritance into the same flat Settings class. Every
attribute name, type, default, alias and constraint is unchanged, so
settings.NAME reads, .env files and test monkeypatches all keep working;
the import path docsgpt.core.settings is the package. Settings.normalize_api_key
is kept as a classmethod for callers that reuse it.

The comment above or beside each field became its Field(description=...),
so the definitions are visible to tooling; the next commit generates the
docs reference from them.

Pitfall recorded for future groups: pydantic collects validators by
method name across the MRO, so two groups naming a validator the same
would silently keep only one. Each group's validator has a unique name.
2026-09-17 11:04:01 +01:00

87 lines
3.9 KiB
Python

"""Server-sent events, replay journal and remote-device sessions."""
from __future__ import annotations
from pydantic import Field
from docsgpt.core.settings._shared import SettingsGroup
class EventsSettings(SettingsGroup):
"""The internal push channel (notifications and durable replay) and the Redis pool behind it."""
ENABLE_SSE_PUSH: bool = Field(
default=True,
description=(
"Internal SSE push channel (notifications and durable replay journal). False makes /api/events emit "
'"push_disabled" and return; clients fall back to polling.'
),
)
EVENTS_STREAM_MAXLEN: int = Field(
default=1000, description="Per-user durable backlog cap in entries; ~24h of replay at typical rates."
)
SSE_KEEPALIVE_SECONDS: int = Field(default=15, ge=1, description="Interval between SSE keepalive comments.")
SSE_MAX_CONCURRENT_PER_USER: int = Field(
default=8,
description=(
"Simultaneous SSE connections per user; each holds a pooled async Redis connection for its lifetime. "
"8 covers multi-tab use without one user starving the pool. 0 disables."
),
)
ASYNC_REDIS_MAX_CONNECTIONS: int = Field(
default=2000,
ge=1,
description=(
"Pool size of the async Redis client behind the event-loop routes, per process. Every open "
"notification tab, chat reconnect and device session holds one connection, so this caps concurrent "
"streams per worker (redis-py's own default is 100). Keep the total across workers below the Redis "
"server's maxclients (10000 by default)."
),
)
EVENTS_REPLAY_MAX_PER_REQUEST: int = Field(
default=200,
description=(
"Backlog entries XRANGE returns per /api/events snapshot. Bounds what one replay moves from Redis to "
"the wire: a client looping Last-Event-ID reconnects enumerates at most this many per round-trip."
),
)
EVENTS_REPLAY_MAX_AGE_HOURS: int = Field(default=48, description="Oldest backlog entry a replay will return.")
EVENTS_REPLAY_BUDGET_REQUESTS_PER_WINDOW: int = Field(
default=30,
description=(
"Sliding-window cap on snapshot replays per user; exhausting it returns 429 with the cursor pinned "
"so the client backs off until the window rolls over."
),
)
EVENTS_REPLAY_BUDGET_WINDOW_SECONDS: int = Field(default=60, description="Length of the replay budget window.")
MESSAGE_EVENTS_RETENTION_DAYS: int = Field(
default=14,
description=(
"Retention for the message_events journal, enforced by the cleanup_message_events beat task. Replay "
"only needs streams a client could still be tailing."
),
)
# Remote Device feature.
REMOTE_DEVICE_SESSION_IDLE_SECONDS: int = Field(
default=60, description="Seconds without a heartbeat before a remote-device session is considered idle."
)
REMOTE_DEVICE_REQUIRE_SIGNATURE: bool = Field(
default=False, description="Require signed commands from remote devices."
)
REMOTE_DEVICE_PAIRING_TTL_SECONDS: int = Field(default=600, description="Lifetime of a pairing code.")
REMOTE_DEVICE_CMD_QUEUE_TTL_SECONDS: int = Field(
default=900,
description=(
"Redis TTL of the per-device command queue, routing invocations cross-process so a scheduled run "
"reaches the web-held device session. Must exceed the max drain deadline (605s) so a command for a "
"briefly-offline device isn't evicted before its own drain gives up."
),
)
REMOTE_DEVICE_INVOCATION_TTL_SECONDS: int = Field(
default=900, description="Redis TTL of a pending remote-device invocation."
)
REMOTE_DEVICE_OUTPUT_STREAM_MAXLEN: int = Field(
default=10_000, description="Cap on buffered output entries per remote-device invocation stream."
)