452 lines
12 KiB
Go
452 lines
12 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
|
|
"synapse/group"
|
|
"synapse/hil"
|
|
"synapse/p2p"
|
|
"synapse/types"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Message Router
|
|
// =============================================================================
|
|
|
|
// Router handles routing of Synapse messages
|
|
type Router struct {
|
|
mu sync.RWMutex
|
|
|
|
// Protocols
|
|
p2pProtocol *p2p.P2PProtocol
|
|
groupProtocol *group.GroupProtocol
|
|
hilProtocol *hil.HILProtocol
|
|
|
|
// External routing
|
|
externalRoutes map[string]string // system_id -> gateway endpoint
|
|
|
|
// Configuration
|
|
config *RouterConfig
|
|
}
|
|
|
|
// RouterConfig holds router configuration
|
|
type RouterConfig struct {
|
|
MaxHops int
|
|
EnableCaching bool
|
|
}
|
|
|
|
// DefaultRouterConfig returns default router configuration
|
|
func DefaultRouterConfig() *RouterConfig {
|
|
return &RouterConfig{
|
|
MaxHops: 10,
|
|
EnableCaching: true,
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// NewRouter creates a new message router
|
|
// =============================================================================
|
|
|
|
func NewRouter(config *RouterConfig) *Router {
|
|
if config == nil {
|
|
config = DefaultRouterConfig()
|
|
}
|
|
return &Router{
|
|
externalRoutes: make(map[string]string),
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// SetP2PProtocol sets the P2P protocol handler
|
|
func (r *Router) SetP2PProtocol(p *p2p.P2PProtocol) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.p2pProtocol = p
|
|
}
|
|
|
|
// SetGroupProtocol sets the Group protocol handler
|
|
func (r *Router) SetGroupProtocol(g *group.GroupProtocol) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.groupProtocol = g
|
|
}
|
|
|
|
// SetHILProtocol sets the HiL protocol handler
|
|
func (r *Router) SetHILProtocol(h *hil.HILProtocol) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.hilProtocol = h
|
|
}
|
|
|
|
// RegisterExternalRoute registers an external system gateway
|
|
func (r *Router) RegisterExternalRoute(systemID, endpoint string) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.externalRoutes[systemID] = endpoint
|
|
}
|
|
|
|
// =============================================================================
|
|
// RouteMessage routes an incoming message to the appropriate handler
|
|
// =============================================================================
|
|
|
|
func (r *Router) RouteMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
// Validate message
|
|
if err := r.validateMessage(msg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Route based on message category
|
|
if isGroupMessage(msg.Type) {
|
|
if r.groupProtocol == nil {
|
|
return nil, fmt.Errorf("group protocol not configured")
|
|
}
|
|
return r.groupProtocol.HandleMessage(ctx, msg)
|
|
}
|
|
|
|
if isP2PMessage(msg.Type) {
|
|
if r.p2pProtocol == nil {
|
|
return nil, fmt.Errorf("p2p protocol not configured")
|
|
}
|
|
return r.p2pProtocol.HandleMessage(ctx, msg)
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown message type: %s", msg.Type)
|
|
}
|
|
|
|
// =============================================================================
|
|
// SendMessage sends a message to a destination
|
|
// =============================================================================
|
|
|
|
// MessageSender is the interface for sending messages
|
|
type MessageSender interface {
|
|
Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error
|
|
}
|
|
|
|
// TransportSender is used by the router to send messages
|
|
var _ MessageSender = (*TransportSender)(nil)
|
|
|
|
// TransportSender is a placeholder for actual transport implementation
|
|
type TransportSender struct {
|
|
httpClient *HTTPClient
|
|
}
|
|
|
|
// HTTPClient is a simple HTTP client for sending messages
|
|
type HTTPClient struct {
|
|
baseURL string
|
|
timeoutMs int
|
|
}
|
|
|
|
// Send sends a message via HTTP
|
|
func (t *TransportSender) Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
|
|
if endpoint == "" {
|
|
return fmt.Errorf("endpoint is required")
|
|
}
|
|
|
|
_, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal message: %w", err)
|
|
}
|
|
|
|
// In a real implementation, this would use net/http to send the message
|
|
log.Printf("[Router] Sending message to %s: type=%s, id=%s", endpoint, msg.Type, msg.ID)
|
|
|
|
return nil // Placeholder - actual HTTP call would go here
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateOutboundMessage creates a message for sending to a remote agent
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateOutboundMessage(
|
|
msgType string,
|
|
sender types.AgentEndpoint,
|
|
receiver *types.AgentEndpoint,
|
|
payload any,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
|
}
|
|
|
|
msg := &types.UnifiedSynapseMessage{
|
|
ID: types.GenerateMessageID(),
|
|
Version: "1.0.0",
|
|
Type: msgType,
|
|
Sender: sender,
|
|
Receiver: receiver,
|
|
CorrelationID: correlationID,
|
|
Payload: payloadBytes,
|
|
Timestamp: types.NowTimestamp(),
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateTaskRequest creates a task request message
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateTaskRequest(
|
|
sender types.AgentEndpoint,
|
|
receiver types.AgentEndpoint,
|
|
taskID string,
|
|
taskType string,
|
|
taskPayload map[string]interface{},
|
|
priority types.Priority,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payload := types.TaskRequestPayload{}
|
|
payload.Task.ID = taskID
|
|
payload.Task.Type = taskType
|
|
payload.Task.Priority = priority
|
|
payload.Task.Payload = taskPayload
|
|
|
|
return r.CreateOutboundMessage(
|
|
string(types.P2PTaskRequest),
|
|
sender,
|
|
&receiver,
|
|
payload,
|
|
correlationID,
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateTaskNeedHuman creates a need human message
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateTaskNeedHuman(
|
|
sender types.AgentEndpoint,
|
|
receiver types.AgentEndpoint,
|
|
taskID string,
|
|
reasonCode, reasonMessage string,
|
|
context struct {
|
|
TaskSummary string
|
|
CurrentProgress int
|
|
BlockedReason string
|
|
RelevantHistory []any
|
|
},
|
|
options []struct {
|
|
Field string
|
|
Value any
|
|
ProsCons string
|
|
Confidence int
|
|
},
|
|
suggestedValue any,
|
|
escalationLevel int,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payload := types.TaskNeedHumanPayload{}
|
|
payload.TaskID = taskID
|
|
payload.Reason.Code = reasonCode
|
|
payload.Reason.Message = reasonMessage
|
|
payload.Context.TaskSummary = context.TaskSummary
|
|
payload.Context.CurrentProgress = context.CurrentProgress
|
|
payload.Context.BlockedReason = context.BlockedReason
|
|
payload.Context.RelevantHistory = context.RelevantHistory
|
|
|
|
// Copy options
|
|
payload.Options = make([]struct {
|
|
Field string `json:"field"`
|
|
Value any `json:"value"`
|
|
ProsCons string `json:"pros_cons,omitempty"`
|
|
Confidence int `json:"confidence,omitempty"`
|
|
}, len(options))
|
|
for i, opt := range options {
|
|
payload.Options[i].Field = opt.Field
|
|
payload.Options[i].Value = opt.Value
|
|
payload.Options[i].ProsCons = opt.ProsCons
|
|
payload.Options[i].Confidence = opt.Confidence
|
|
}
|
|
|
|
payload.SuggestedValue = suggestedValue
|
|
payload.EscalationLevel = escalationLevel
|
|
|
|
return r.CreateOutboundMessage(
|
|
string(types.P2PTaskNeedHuman),
|
|
sender,
|
|
&receiver,
|
|
payload,
|
|
correlationID,
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateGroupTaskSplit creates a group task split message
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateGroupTaskSplit(
|
|
sender types.AgentEndpoint,
|
|
groupID string,
|
|
originalTask struct {
|
|
ID string
|
|
Type string
|
|
Requirements map[string]interface{}
|
|
Deadline *types.Timestamp
|
|
},
|
|
subtasks []types.Subtask,
|
|
mergeStrategy types.MergeStrategy,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payload := types.GroupTaskSplitPayload{}
|
|
payload.OriginalTask.ID = originalTask.ID
|
|
payload.OriginalTask.Type = originalTask.Type
|
|
payload.OriginalTask.Requirements = originalTask.Requirements
|
|
payload.OriginalTask.Deadline = originalTask.Deadline
|
|
payload.Subtasks = subtasks
|
|
payload.MergeStrategy = mergeStrategy
|
|
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
|
}
|
|
|
|
msg := &types.UnifiedSynapseMessage{
|
|
ID: types.GenerateMessageID(),
|
|
Version: "1.0.0",
|
|
Type: string(types.GroupTaskSplit),
|
|
Sender: sender,
|
|
GroupID: groupID,
|
|
Broadcast: false,
|
|
CorrelationID: correlationID,
|
|
Payload: payloadBytes,
|
|
Timestamp: types.NowTimestamp(),
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateGroupBroadcast creates a group broadcast message
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateGroupBroadcast(
|
|
sender types.AgentEndpoint,
|
|
groupID string,
|
|
msgType string,
|
|
payload any,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
|
}
|
|
|
|
msg := &types.UnifiedSynapseMessage{
|
|
ID: types.GenerateMessageID(),
|
|
Version: "1.0.0",
|
|
Type: msgType,
|
|
Sender: sender,
|
|
GroupID: groupID,
|
|
Broadcast: true,
|
|
CorrelationID: correlationID,
|
|
Payload: payloadBytes,
|
|
Timestamp: types.NowTimestamp(),
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// CreateGroupVote creates a group vote message
|
|
// =============================================================================
|
|
|
|
func (r *Router) CreateGroupVote(
|
|
sender types.AgentEndpoint,
|
|
groupID string,
|
|
voteID string,
|
|
question string,
|
|
options []string,
|
|
voters []string,
|
|
threshold float64,
|
|
deadline types.Timestamp,
|
|
correlationID string,
|
|
) (*types.UnifiedSynapseMessage, error) {
|
|
payload := types.GroupVotePayload{}
|
|
payload.VoteID = voteID
|
|
payload.Question = question
|
|
payload.Options = options
|
|
payload.Voters = voters
|
|
payload.Threshold = threshold
|
|
payload.Deadline = deadline
|
|
payload.Status = "open"
|
|
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
|
}
|
|
|
|
msg := &types.UnifiedSynapseMessage{
|
|
ID: types.GenerateMessageID(),
|
|
Version: "1.0.0",
|
|
Type: string(types.GroupVote),
|
|
Sender: sender,
|
|
GroupID: groupID,
|
|
Broadcast: true,
|
|
CorrelationID: correlationID,
|
|
Payload: payloadBytes,
|
|
Timestamp: types.NowTimestamp(),
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Validation
|
|
// =============================================================================
|
|
|
|
func (r *Router) validateMessage(msg *types.UnifiedSynapseMessage) error {
|
|
if msg.ID == "" {
|
|
return types.ErrMessageFormatInvalidError
|
|
}
|
|
if msg.Sender.AgentID == "" {
|
|
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender agent_id", nil)
|
|
}
|
|
if msg.Sender.Endpoint == "" {
|
|
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender endpoint", nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Functions
|
|
// =============================================================================
|
|
|
|
func isP2PMessage(msgType string) bool {
|
|
return strings.HasPrefix(msgType, "p2p:")
|
|
}
|
|
|
|
func isGroupMessage(msgType string) bool {
|
|
return strings.HasPrefix(msgType, "group:") || strings.HasPrefix(msgType, "inter_group:")
|
|
}
|
|
|
|
// GetRoute determines the route for a message
|
|
func (r *Router) GetRoute(msg *types.UnifiedSynapseMessage) (routeType string, target string, err error) {
|
|
// P2P messages
|
|
if msg.Receiver != nil {
|
|
if msg.Receiver.SystemID != r.p2pProtocol.AgentEndpoint().SystemID {
|
|
// External system
|
|
if endpoint, ok := r.externalRoutes[msg.Receiver.SystemID]; ok {
|
|
return "external", endpoint, nil
|
|
}
|
|
return "unknown", "", fmt.Errorf("no route for system: %s", msg.Receiver.SystemID)
|
|
}
|
|
return "local", msg.Receiver.AgentID, nil
|
|
}
|
|
|
|
// Group messages
|
|
if msg.GroupID != "" {
|
|
return "group", msg.GroupID, nil
|
|
}
|
|
|
|
return "unknown", "", fmt.Errorf("no route found for message")
|
|
}
|