feat: implement channel runtime connectors

This commit is contained in:
2026-06-03 16:22:44 +08:00
parent ee972441f5
commit c3d84b904a
105 changed files with 15621 additions and 322 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from contextlib import suppress
from beaver.foundation.events import MessageBus, OutboundMessage
@ -20,13 +21,17 @@ class ChannelManager:
self.started = False
def register(self, channel: ChannelAdapter) -> None:
if self.started:
raise RuntimeError("Cannot register channels after ChannelManager.start()")
if channel.name in self.channels:
raise ValueError(f"Channel already registered: {channel.name}")
if channel.bus is not self.bus:
raise ValueError("Channel must share the same MessageBus as ChannelManager")
self.channels[channel.name] = channel
if channel.channel_id in self.channels:
raise ValueError(f"Channel already registered: {channel.channel_id}")
self.channels[channel.channel_id] = channel
def unregister(self, channel_id: str) -> ChannelAdapter | None:
return self.channels.pop(channel_id, None)
def replace_registered(self, channel: ChannelAdapter) -> ChannelAdapter | None:
old = self.channels.get(channel.channel_id)
self.channels[channel.channel_id] = channel
return old
async def start(self) -> None:
started: list[ChannelAdapter] = []
@ -53,7 +58,13 @@ class ChannelManager:
if errors:
raise RuntimeError(f"Failed to stop {len(errors)} channel(s)") from errors[0]
async def dispatch_outbound(self, stop_event: asyncio.Event) -> None:
async def dispatch_outbound(
self,
stop_event: asyncio.Event,
*,
on_delivered: Callable[[OutboundMessage], Awaitable[None]] | None = None,
on_failed: Callable[[OutboundMessage, Exception | None], Awaitable[None]] | None = None,
) -> None:
"""Route bus outbound messages until stopped and the queue is drained."""
while True:
@ -68,9 +79,16 @@ class ChannelManager:
channel = self.channels.get(message.channel)
if channel is None:
self.undeliverable.append(message)
if on_failed is not None:
await on_failed(message, None)
continue
try:
await channel.send(message)
except Exception: # pragma: no cover - defensive channel isolation
except Exception as exc: # pragma: no cover - defensive channel isolation
self.undeliverable.append(message)
if on_failed is not None:
await on_failed(message, exc)
else:
if on_delivered is not None:
await on_delivered(message)