965cce7192
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
169 lines
4.3 KiB
Go
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]
|
|
}
|