Files
beaver_project/app-instance/backend/beaver/interfaces/channels/external_connector.py

98 lines
3.3 KiB
Python

"""Generic runtime channel backed by an external connector sidecar."""
from __future__ import annotations
import hashlib
from typing import Any
from beaver.foundation.events import OutboundMessage
from beaver.interfaces.channels.connections.sidecar_client import ConnectorSidecarClient
class ExternalConnectorChannel:
def __init__(
self,
*,
channel_id: str,
platform_kind: str,
connection_id: str,
account_id: str,
display_name: str,
sidecar_client: ConnectorSidecarClient | Any,
) -> None:
self.channel_id = channel_id
self.kind = "external_connector"
self.mode = "http"
self.platform_kind = platform_kind
self.connection_id = connection_id
self.account_id = account_id
self.display_name = display_name or channel_id
self.sidecar_client = sidecar_client
self.started = False
async def start(self) -> None:
self.started = True
async def stop(self) -> None:
self.started = False
async def send(self, message: OutboundMessage) -> None:
identity = message.channel_identity
if identity is None:
raise ValueError("channel_identity is required for external connector sends")
metadata = {
"inboundMessageId": identity.message_id,
"sessionId": message.session_id,
}
context_token = _context_token(message)
if context_token:
metadata["contextToken"] = context_token
payload = {
"requestId": _request_id(message),
"connectionId": self.connection_id,
"channelId": self.channel_id,
"kind": self.platform_kind,
"target": {
"peerId": identity.peer_id,
"peerType": identity.peer_type,
"threadId": identity.thread_id,
},
"content": message.content,
"metadata": metadata,
}
await self.sidecar_client.send(payload)
def _request_id(message: OutboundMessage) -> str:
identity = message.channel_identity
channel = message.channel or (identity.channel_id if identity else "unknown")
session_id = message.session_id or (identity.session_id() if identity else "unknown")
message_id = str(message.message_id or "").strip()
if not message_id:
basis = "|".join(
[
message.content,
identity.message_id if identity and identity.message_id else "",
identity.peer_id if identity else "",
message.finish_reason,
]
)
message_id = hashlib.sha256(basis.encode("utf-8")).hexdigest()[:24]
return f"out_{channel}:{session_id}:{message_id}"
def _context_token(message: OutboundMessage) -> str | None:
inbound_metadata = message.metadata.get("inbound_metadata")
if isinstance(inbound_metadata, dict):
value = _clean_optional(inbound_metadata.get("contextToken") or inbound_metadata.get("context_token"))
if value:
return value
return _clean_optional(message.metadata.get("contextToken") or message.metadata.get("context_token"))
def _clean_optional(value: Any) -> str | None:
if value is None:
return None
text = str(value).strip()
return text or None