platforms.base module
Abstract base types for platform adapters.
Every chat platform (Matrix, Discord, Slack, …) implements
PlatformAdapter. Incoming events are normalised into
IncomingMessage and forwarded to the message_handler
callback supplied at construction time. In the gateway service that
callback publishes the message onto the sg:stream:inbound Redis
stream (via RedisEventBus); the inference
worker later reconstructs the IncomingMessage and feeds it to
MessageProcessor.
- class platforms.base.Attachment(data, mimetype, filename, source_url='')[source]
Bases:
objectA downloaded media attachment, already materialized as raw bytes.
Represents a single piece of inbound media (image, audio, video, or generic file) that a platform adapter has already fetched off the wire so downstream code never has to re-download it. Adapters populate one per attachment while converting a native event into an
IncomingMessage, carrying the rawdatatogether with itsmimetypeandfilenameso the inference worker’s media pipeline can route it correctly. Thesource_urlpreserves the original location (a Matrixmxc://URI, a Discord CDN link, etc.) for re-fetching, caching, or display.Constructed by the platform adapters in
platforms/discord.py,platforms/matrix.py,platforms/discord_self.py, andplatforms/emoji_resolver.py, and reconstructed from the inbound Redis envelope ininference_main.pyandmessage_queue.py. Holds bytes in memory only; it performs no I/O of its own.
- class platforms.base.IncomingMessage(platform, channel_id, user_id, user_name, text, is_addressed, attachments=<factory>, channel_name='', timestamp=<factory>, message_id='', reply_to_id='', extra=<factory>, reactions='', unified_user_id=None, user_aliases=<factory>)[source]
Bases:
objectPlatform-agnostic representation of an incoming chat message.
Platform adapters construct one of these for every event and pass it to the message_handler callback (which, in the gateway service, publishes it onto the
sg:stream:inboundRedis stream for the inference worker’sMessageProcessor).- Parameters:
- attachments: list[Attachment]
Media attachments that have already been downloaded.
- __post_init__()[source]
Seed a default alias set when none was supplied.
Dataclass post-initialization hook that runs automatically after
IncomingMessagefield assignment. Whenuser_aliasesis left empty by the constructing platform adapter, it backfills a single canonical"{platform}:{user_id}"alias so that downstream identity resolution (the inference worker reconstructing this message from thesg:stream:inboundRedis stream and looking up aunified_user_id) always has at least one stable handle to key on. Mutates onlyself.user_aliasesand performs no I/O.It is invoked implicitly by the dataclass machinery on every
IncomingMessageinstantiation, so it has no explicit internal callers.- Return type:
- class platforms.base.HistoricalMessage(user_id, user_name, text, timestamp, message_id='', is_bot=False, reply_to_id='', reactions='')[source]
Bases:
objectLightweight representation of a message fetched from platform history.
Used by
PlatformAdapter.fetch_history()to return recent messages for backfilling conversation context after downtime.- Parameters:
- class platforms.base.PlatformAdapter(message_handler)[source]
Bases:
ABCInterface that every platform must implement.
Subclasses wire up their SDK’s event loop, convert native events to
IncomingMessage, and forward them to the message_handler callback supplied at construction time.- Parameters:
message_handler (MessageHandler)
- __init__(message_handler)[source]
Store the inbound callback and initialize optional-handler slots.
Base constructor every concrete adapter chains up to via
super(). It captures the message_handler that the host service supplies so each adapter has exactly one place to deliver a normalizedIncomingMessageonce a native event arrives. In the gateway service that handler publishes the message onto thesg:stream:inboundRedis stream (viaRedisEventBus) for the inference worker to pick up; in tests it may be a plain coroutine that collects messages. The remaining attributes are nulled out here and only wired up later by the host (edit/delete handlers and the cancel/revoke/purge/toggle button callbacks), so an adapter that never receives them simply has no interactive affordances.Invoked indirectly whenever a concrete adapter is instantiated — e.g. when
gateway_main.pybuilds its platform adapters at startup.- Parameters:
message_handler (
Callable[[IncomingMessage,PlatformAdapter],Awaitable[None]]) – Coroutine the adapter awaits for every inbound message; receives theIncomingMessageand the adapter instance itself so the host can reply on the right platform.- Return type:
None
- abstract property name: str
Return the short lowercase platform identifier for this adapter.
Abstract read-only property each concrete adapter must implement to report a stable slug such as
"matrix","discord", or"webchat". This string is the canonical key the rest of the system uses to identify the platform: it becomes theplatformfield stamped onto outbound envelopes ingateway_main.py, the key for the adapter registry inbackground_tasks.py, the value rendered in the web admin status payload inweb/bot_admin.py, and the running-status entries inweb/platforms_api.py. Must match the slug used onIncomingMessageso routing stays consistent.- Returns:
The lowercase platform slug.
- abstract property is_running: bool
Report whether the adapter’s connection / event loop is live.
Abstract read-only property every adapter implements to expose its connection state, typically tracking a flag toggled in
start()andstop(). Callers treat it as the readiness signal for the platform:web/deps.pyandbackground_tasks.pygate work on it before touching an adapter,web/platforms_api.pyandweb/bot_admin.pysurface it as the per-platform running indicator, andweb/bot_admin.pyalso uses it to decide whether the underlying client is safe to query. Should not perform I/O — a cheap state read.- Returns:
Truewhile the platform event loop is connected and listening,Falsebefore startup or after shutdown.
- property bot_identity: dict[str, str]
Return the bot’s own identity on this platform.
Returns a dict with at minimum
platformanduser_id. Adapters should override to providedisplay_nameandmentionwhere available. The default returns an emptyuser_id(safe to call before login completes).
- abstractmethod async start()[source]
Connect to the platform, authenticate, and begin consuming events.
Abstract lifecycle entry point each adapter implements to spin up its SDK’s event loop, log in, and start dispatching native events through the stored message_handler. Implementations are expected to flip the state backing
is_runningtoTrueonce listening, and may open network sockets, background tasks, and platform sessions as side effects.Awaited once per adapter during gateway boot —
GatewayServicecallsadapter.start()for every created adapter ingateway_main.py(deliberately after the outbound consumer is up, percore/outbound_consumer.py), and tests invoke it directly.- Return type:
- abstractmethod async stop()[source]
Gracefully disconnect from the platform and release its resources.
Abstract counterpart to
start()that every adapter implements to tear down its connection: cancel background tasks, close the SDK client and sockets, and flip the state backingis_runningtoFalse. Should be idempotent and safe to call even if startup never fully completed.Awaited on shutdown by
GatewayService.on_stop()ingateway_main.pyand when an operator stops a platform throughweb/platforms_api.py(note callers usestop()rather than a rawclose()).- Return type:
- abstractmethod async send(channel_id, text)[source]
Send a plain-text message to channel_id.
Returns the platform message ID of the sent message, or
""if the send failed or the platform does not expose one.
- abstractmethod async send_file(channel_id, data, filename, mimetype='application/octet-stream')[source]
Send a file/media attachment to channel_id.
- Parameters:
channel_id (
str) – Platform-specific channel / room identifier.data (
bytes) – Raw file bytes.filename (
str) – Suggested filename for the attachment.mimetype (
str) – MIME type of the file (used for determining how to present the attachment on platforms that distinguish images, audio, video, and generic files).
- Returns:
A platform-specific content URL for the uploaded file (
mxc://on Matrix, CDN URL on Discord), orNoneif the upload failed.- Return type:
- async send_with_buttons(channel_id, text, view=None)[source]
Send a message with interactive buttons attached.
- Parameters:
- Returns:
The platform message ID, or
""on failure. The default implementation falls back tosend().- Return type:
- async edit_message(channel_id, message_id, new_text)[source]
Edit an existing message sent by the bot.
- Parameters:
channel_id (
str) – Platform-specific channel / room identifier.message_id (
str) – Platform-specific ID of the message to edit.new_text (
str) – Replacement text content.success (Returns True on)
not (False if the platform does)
default (support editing or the operation failed. The)
False. (implementation is a no-op that returns)
- Return type:
- async start_typing(channel_id)[source]
Begin showing a typing indicator in channel_id.
Implementations should spawn a background task that periodically refreshes the indicator until
stop_typing()is called. The default implementation is a no-op.
- async stop_typing(channel_id)[source]
Stop showing the typing indicator in channel_id.
Must be safe to call even if
start_typing()was never called for the given channel. The default is a no-op.
- async set_presence(text, emoji=None)[source]
Set the bot’s platform presence/status to text.
Global (non-channel) action driven by
StatusManager. The default implementation is a no-op; platform adapters that support presence (e.g. Discord) should override it.
- async fetch_history(channel_id, limit=100)[source]
Fetch recent messages from the platform for channel_id.
Returns up to limit messages in chronological order (oldest first). The default implementation returns an empty list; platform adapters should override when the underlying SDK supports history retrieval.
- Return type:
- Parameters:
- async get_channel_webhooks(channel_id)[source]
Return webhooks configured for channel_id.
The default implementation returns an empty list. Platform adapters that support webhooks (e.g. Discord) should override.
- async should_skip_channel_heartbeat(channel_id)[source]
Return True to skip background channel heartbeat (no LLM) for channel_id.
Used by
execute_channel_heartbeat()before building context. Override on platforms where DMs or tiny Matrix rooms should not receive periodic nudges.
- async list_servers_and_channels()[source]
Return all servers/guilds and their channels.
Each platform adapter should override this to return a list of dicts describing servers/guilds (or rooms) the bot is active in, along with their channels. The format is platform-specific but should include at minimum
server_name,server_id, andchannels(a list of channel dicts).The default implementation returns an empty list.
- async get_guild_members(guild_id)[source]
Return all members of guild_id with role information.
The default implementation returns an empty list. Platform adapters with guild/server membership APIs (e.g. Discord) should override and implement caching.
- async is_channel_valid(channel_id)[source]
Return True if the channel/room is valid and active on the platform.
If a channel has been deleted or is no longer accessible to the bot, this method should return False. The default implementation returns True.
- async add_reaction(channel_id, message_id, emoji)[source]
Add an emoji reaction to a message.
Counterpart to
core.proxy_adapter.ProxyPlatformAdapter.add_reaction, which publishes atype: "reaction"outbound envelope that the gateway replays by calling this method on the live adapter. The default is a no-op; platforms that support reactions (Discord, Matrix) override it.