package routing import ( "context" "encoding/json" "fmt" "log" "strings" "sync" "synapse/internal/agent/group" "synapse/internal/agent/p2p" "synapse/internal/hil" "synapse/pkg/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") }