Files
Cyrene/backend/platform-bridge/internal/bridge/router.go
T

169 lines
4.3 KiB
Go

package bridge
import (
"fmt"
"sync"
"github.com/yourname/cyrene-ai/platform-bridge/internal/permissions"
)
// PlatformRouter manages all platform adapters and routes messages.
type PlatformRouter struct {
mu sync.RWMutex
adapters map[string]PlatformAdapter
mapper *IdentityMapper
checker *permissions.Checker
handler MessageHandler
// Conversational context per channel.
contexts map[string]*ChannelContext // channelKey -> context
}
// ChannelContext stores the active conversation state for a channel.
type ChannelContext struct {
Platform string
ChannelID string
ChannelType string
LastUserMsg string
MessageCount int
}
func NewPlatformRouter(mapper *IdentityMapper, checker *permissions.Checker) *PlatformRouter {
return &PlatformRouter{
adapters: make(map[string]PlatformAdapter),
mapper: mapper,
checker: checker,
contexts: make(map[string]*ChannelContext),
}
}
// RegisterAdapter adds a platform adapter.
func (r *PlatformRouter) RegisterAdapter(a PlatformAdapter) {
r.mu.Lock()
defer r.mu.Unlock()
r.adapters[a.PlatformName()] = a
}
// GetAdapter returns the adapter for a platform.
func (r *PlatformRouter) GetAdapter(platform string) (PlatformAdapter, error) {
r.mu.RLock()
defer r.mu.RUnlock()
a, ok := r.adapters[platform]
if !ok {
return nil, fmt.Errorf("no adapter for platform: %s", platform)
}
return a, nil
}
// ListAdapters returns all registered adapter names.
func (r *PlatformRouter) ListAdapters() []string {
r.mu.RLock()
defer r.mu.RUnlock()
names := make([]string, 0, len(r.adapters))
for name := range r.adapters {
names = append(names, name)
}
return names
}
// SetMessageHandler sets the callback for processing unified messages.
func (r *PlatformRouter) SetMessageHandler(h MessageHandler) {
r.handler = h
}
// RouteMessage converts a platform message to unified, checks permissions, and dispatches.
func (r *PlatformRouter) RouteMessage(platform string, rawMsg interface{}) (*UnifiedResponse, error) {
a, err := r.GetAdapter(platform)
if err != nil {
return nil, err
}
unified, err := a.ToUnified(rawMsg)
if err != nil {
return nil, fmt.Errorf("convert to unified: %w", err)
}
// Resolve identity.
identity, err := r.mapper.Resolve(platform, unified.SenderID)
if err != nil {
return nil, fmt.Errorf("identity not found: %w", err)
}
// Merge identity info into the unified message.
unified.SenderID = identity.CyreneUser
unified.SenderName = identity.Nickname
// Apply permission-based filtering.
_ = identity // used by permission checks on tools
// Update channel context.
r.updateContext(unified)
if r.handler == nil {
return nil, fmt.Errorf("no message handler configured")
}
response, err := r.handler(unified)
if err != nil {
return nil, err
}
response.Platform = platform
response.PlatformHints = r.platformHints(platform)
return response, nil
}
// SendResponse converts and sends a unified response through the platform adapter.
func (r *PlatformRouter) SendResponse(response *UnifiedResponse) ([]PlatformMessage, error) {
a, err := r.GetAdapter(response.Platform)
if err != nil {
return nil, err
}
return a.FromUnified(response)
}
func (r *PlatformRouter) platformHints(platform string) PlatformHints {
cap := PlatformCapabilities{}
if a, err := r.GetAdapter(platform); err == nil {
cap = a.Capabilities()
}
return PlatformHints{
TypingIndicator: cap.SupportsTypingHint,
BurstMode: cap.RecommendBurstMax > 1,
}
}
func (r *PlatformRouter) channelKey(platform, channelID string) string {
return platform + ":" + channelID
}
func (r *PlatformRouter) updateContext(msg *UnifiedMessage) {
key := r.channelKey(msg.Platform, msg.ChannelID)
r.mu.Lock()
defer r.mu.Unlock()
ctx, ok := r.contexts[key]
if !ok {
ctx = &ChannelContext{
Platform: msg.Platform,
ChannelID: msg.ChannelID,
ChannelType: msg.ChannelType,
}
r.contexts[key] = ctx
}
ctx.LastUserMsg = msg.Content
ctx.MessageCount++
}
// ListAllIdentities returns all registered identity mappings.
func (r *PlatformRouter) ListAllIdentities() map[string][]permissions.PlatformIdentity {
return r.mapper.ListAll()
}
// GetContext returns the channel context.
func (r *PlatformRouter) GetContext(platform, channelID string) *ChannelContext {
r.mu.RLock()
defer r.mu.RUnlock()
return r.contexts[platform+":"+channelID]
}