Files
synapse/internal/agent/p2p/protocol.go
2026-06-28 03:24:26 +08:00

596 lines
21 KiB
Go

package p2p
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
"synapse/pkg/types"
)
// =============================================================================
// P2P Protocol Handler
// =============================================================================
// TaskHandler is the interface for handling task execution
type TaskHandler interface {
// ExecuteTask executes a task and returns the result
ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error)
// ValidateTask validates if the task can be executed
ValidateTask(taskType string, payload map[string]interface{}) ([]types.MissingField, bool)
// GetCapabilities returns the agent's capabilities
GetCapabilities() []string
}
// MessageHandler is the interface for handling incoming messages
type MessageHandler interface {
// HandleMessage handles an incoming P2P message
HandleMessage(msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
}
// CallbackSender is the interface for sending callbacks to remote agents
type CallbackSender interface {
// SendCallback sends a message to a remote agent
SendCallback(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error
}
// =============================================================================
// P2PProtocol represents the P2P protocol handler
// =============================================================================
// P2PProtocol handles point-to-point Synapse communication
type P2PProtocol struct {
mu sync.RWMutex
// Agent identity
agentEndpoint types.AgentEndpoint
// Task state
tasks map[string]*types.P2PTaskStatus
// Handlers
taskHandler TaskHandler
callbackSender CallbackSender
// Configuration
config *P2PConfig
}
// P2PConfig holds P2P protocol configuration
type P2PConfig struct {
MaxRetries int // Max callback retries
RetryBaseDelay time.Duration // Base delay for retries
TaskTimeout time.Duration // Task execution timeout
HeartbeatInterval time.Duration // Heartbeat interval
}
// DefaultP2PConfig returns default P2P configuration
func DefaultP2PConfig() *P2PConfig {
return &P2PConfig{
MaxRetries: 5,
RetryBaseDelay: 2 * time.Second,
TaskTimeout: 5 * time.Minute,
HeartbeatInterval: 30 * time.Second,
}
}
// =============================================================================
// P2PTask represents an active task
// =============================================================================
type P2PTask struct {
ID string
Type string
Priority types.Priority
Status types.P2PTaskStatus
Payload map[string]interface{}
Sender types.AgentEndpoint
Receiver types.AgentEndpoint
CorrelationID string
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt *time.Time
Result any
Error *types.SynapseError
}
// =============================================================================
// NewP2PProtocol creates a new P2P protocol handler
// =============================================================================
func NewP2PProtocol(agentEndpoint types.AgentEndpoint, config *P2PConfig) *P2PProtocol {
if config == nil {
config = DefaultP2PConfig()
}
return &P2PProtocol{
agentEndpoint: agentEndpoint,
tasks: make(map[string]*types.P2PTaskStatus),
config: config,
}
}
// SetTaskHandler sets the task handler
func (p *P2PProtocol) SetTaskHandler(h TaskHandler) {
p.taskHandler = h
}
// SetCallbackSender sets the callback sender
func (p *P2PProtocol) SetCallbackSender(s CallbackSender) {
p.callbackSender = s
}
// AgentEndpoint returns the agent's endpoint
func (p *P2PProtocol) AgentEndpoint() types.AgentEndpoint {
return p.agentEndpoint
}
// =============================================================================
// HandleMessage processes an incoming P2P message
// =============================================================================
func (p *P2PProtocol) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
p.mu.Lock()
defer p.mu.Unlock()
// Validate message
if err := p.validateMessage(msg); err != nil {
a2aErr, ok := err.(*types.SynapseError)
if !ok {
a2aErr = types.NewSynapseError(types.ErrMessageFormatInvalid, err.Error(), nil)
}
return p.newNACK(msg, a2aErr)
}
// Route based on message type
switch types.P2PMessageType(msg.Type) {
case types.P2PTaskRequest:
return p.handleTaskRequest(ctx, msg)
case types.P2PTaskConfirm:
return p.handleTaskConfirm(ctx, msg)
case types.P2PTaskReject:
return p.handleTaskReject(ctx, msg)
case types.P2PTaskNeedInfo:
return p.handleTaskNeedInfo(ctx, msg)
case types.P2PTaskInfoProvided:
return p.handleTaskInfoProvided(ctx, msg)
case types.P2PTaskStatusUpdate:
return p.handleTaskStatusUpdate(ctx, msg)
case types.P2PTaskComplete:
return p.handleTaskComplete(ctx, msg)
case types.P2PTaskFailed:
return p.handleTaskFailed(ctx, msg)
case types.P2PTaskCancel:
return p.handleTaskCancel(ctx, msg)
case types.P2PTaskAck:
return p.handleTaskAck(ctx, msg)
case types.P2PTaskNeedHuman:
return p.handleTaskNeedHuman(ctx, msg)
case types.P2PHumanDecision:
return p.handleHumanDecision(ctx, msg)
case types.P2PAck:
return p.handleAck(ctx, msg)
case types.P2PNack:
return p.handleNack(ctx, msg)
case types.P2PHeartbeat:
return p.handleHeartbeat(ctx, msg)
default:
return p.newNACK(msg, types.NewSynapseError(
types.ErrMessageFormatInvalid,
fmt.Sprintf("Unknown message type: %s", msg.Type),
nil,
))
}
}
// =============================================================================
// Message Handlers
// =============================================================================
// handleTaskRequest handles incoming task requests
func (p *P2PProtocol) handleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskRequestPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(
types.ErrMessageFormatInvalid,
"Invalid task request payload",
err.Error(),
))
}
taskID := payload.Task.ID
log.Printf("[P2P] Task request received: %s (type: %s)", taskID, payload.Task.Type)
// Check if we have a task handler
if p.taskHandler == nil {
return p.newReject(msg, taskID, "agent_not_ready", "Task handler not configured", nil)
}
// Validate the task
missingFields, canHandle := p.taskHandler.ValidateTask(payload.Task.Type, payload.Task.Payload)
if !canHandle {
if len(missingFields) > 0 {
// Need more info
return p.newNeedInfo(msg, taskID, missingFields)
}
return p.newReject(msg, taskID, "capability_lacking", "Agent cannot handle this task type", nil)
}
// Create task context
taskCtx, cancel := context.WithTimeout(ctx, p.config.TaskTimeout)
defer cancel()
// Execute task asynchronously
go p.executeTask(taskCtx, msg, taskID, payload.Task.Type, payload.Task.Payload)
// Return immediate confirmation
return p.newAck(msg, taskID, "task_accepted")
}
// executeTask executes a task and handles callbacks
func (p *P2PProtocol) executeTask(ctx context.Context, origMsg *types.UnifiedSynapseMessage, taskID, taskType string, payload map[string]interface{}) {
// Send initial status update
p.sendStatusUpdate(ctx, taskID, types.P2PStatusExecuting, 0, "Task started")
// Execute the task
result, err := p.taskHandler.ExecuteTask(ctx, taskType, payload)
if err != nil {
// Task failed
p.sendTaskFailed(ctx, taskID, "execution_failed", err.Error(), true, nil)
return
}
// Task completed
p.sendTaskComplete(ctx, taskID, result)
}
// sendStatusUpdate sends a status update to the sender
func (p *P2PProtocol) sendStatusUpdate(ctx context.Context, taskID string, status types.P2PTaskStatus, progress int, message string) {
if p.callbackSender == nil {
return
}
payload := types.TaskStatusUpdatePayload{
TaskID: taskID,
Status: string(status),
Progress: progress,
Message: message,
}
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskStatusUpdate),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send status update: %v", err)
}
}
// sendTaskComplete sends task completion notification
func (p *P2PProtocol) sendTaskComplete(ctx context.Context, taskID string, result any) {
if p.callbackSender == nil {
return
}
payload := types.TaskCompletePayload{}
payload.TaskID = taskID
payload.Result.OutputType = "task_result"
payload.Result.Output = result
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskComplete),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send task complete: %v", err)
}
}
// sendTaskFailed sends task failure notification
func (p *P2PProtocol) sendTaskFailed(ctx context.Context, taskID, errorCode, errorMessage string, recoverable bool, partialResult any) {
if p.callbackSender == nil {
return
}
payload := types.TaskFailedPayload{}
payload.TaskID = taskID
payload.Error.Code = errorCode
payload.Error.Message = errorMessage
payload.Error.Recoverable = recoverable
payload.PartialResult = partialResult
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskFailed),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send task failed: %v", err)
}
}
// handleTaskConfirm handles task confirmation from receiver
func (p *P2PProtocol) handleTaskConfirm(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskConfirmPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid confirm payload", err.Error()))
}
log.Printf("[P2P] Task confirmed: %s (estimated: %ds)", payload.TaskID, payload.EstimatedDuration)
return p.newAck(msg, payload.TaskID, "confirmed")
}
// handleTaskReject handles task rejection from receiver
func (p *P2PProtocol) handleTaskReject(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskRejectPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid reject payload", err.Error()))
}
log.Printf("[P2P] Task rejected: %s (%s: %s)", payload.TaskID, payload.Reason.Code, payload.Reason.Message)
return p.newAck(msg, payload.TaskID, "rejected")
}
// handleTaskNeedInfo handles information request from receiver
func (p *P2PProtocol) handleTaskNeedInfo(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskNeedInfoPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid need info payload", err.Error()))
}
log.Printf("[P2P] Information requested: %s (missing %d fields)", payload.TaskID, len(payload.MissingFields))
// This would typically be handled by the application logic
// For now, just acknowledge
return p.newAck(msg, payload.TaskID, "info_request_received")
}
// handleTaskInfoProvided handles information provided by sender
func (p *P2PProtocol) handleTaskInfoProvided(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskInfoProvidedPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid info provided payload", err.Error()))
}
log.Printf("[P2P] Information provided: %s (%d fields)", payload.TaskID, len(payload.ProvidedInfo))
return p.newAck(msg, payload.TaskID, "info_received")
}
// handleTaskStatusUpdate handles status updates from receiver
func (p *P2PProtocol) handleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskStatusUpdatePayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid status update payload", err.Error()))
}
log.Printf("[P2P] Status update: %s - %s (progress: %d%%)", payload.TaskID, payload.Status, payload.Progress)
return p.newAck(msg, payload.TaskID, "status_received")
}
// handleTaskComplete handles task completion notification
func (p *P2PProtocol) handleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskCompletePayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid complete payload", err.Error()))
}
log.Printf("[P2P] Task completed: %s", payload.TaskID)
return p.newAck(msg, payload.TaskID, "completed")
}
// handleTaskFailed handles task failure notification
func (p *P2PProtocol) handleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskFailedPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid failed payload", err.Error()))
}
log.Printf("[P2P] Task failed: %s (%s: %s)", payload.TaskID, payload.Error.Code, payload.Error.Message)
return p.newAck(msg, payload.TaskID, "failure_noted")
}
// handleTaskCancel handles task cancellation
func (p *P2PProtocol) handleTaskCancel(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskCancelPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid cancel payload", err.Error()))
}
log.Printf("[P2P] Task cancelled: %s (by %s)", payload.TaskID, payload.CancelledBy)
return p.newAck(msg, payload.TaskID, "cancelled")
}
// handleTaskAck handles task-level acknowledgment
func (p *P2PProtocol) handleTaskAck(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] Task ACK received for message: %s", msg.ReplyTo)
return nil, nil // No response needed for ACK
}
// handleTaskNeedHuman handles human intervention request
func (p *P2PProtocol) handleTaskNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskNeedHumanPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid need human payload", err.Error()))
}
log.Printf("[P2P] Human intervention needed: %s (level: %d)", payload.TaskID, payload.EscalationLevel)
// Store the HiL request and wait for human decision
// This would typically be handled by a HiL manager
return p.newAck(msg, payload.TaskID, "human_intervention_requested")
}
// handleHumanDecision handles human decision response
func (p *P2PProtocol) handleHumanDecision(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.HumanDecisionPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid human decision payload", err.Error()))
}
log.Printf("[P2P] Human decision: %s (action: %s, by %s)", payload.TaskID, payload.Decision.Action, payload.DecidedBy.UserName)
// Process the human decision
// This would resume the task execution with the decision
return p.newAck(msg, payload.TaskID, "decision_received")
}
// handleAck handles generic ACK
func (p *P2PProtocol) handleAck(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] ACK received for: %s", msg.ReplyTo)
return nil, nil
}
// handleNack handles generic NACK
func (p *P2PProtocol) handleNack(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] NACK received for: %s", msg.ReplyTo)
return nil, nil
}
// handleHeartbeat handles heartbeat
func (p *P2PProtocol) handleHeartbeat(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
return p.newAck(msg, "", "alive")
}
// =============================================================================
// Response Builders
// =============================================================================
// newAck creates an ACK response
func (p *P2PProtocol) newAck(msg *types.UnifiedSynapseMessage, taskID, note string) (*types.UnifiedSynapseMessage, error) {
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PAck),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Timestamp: types.NowTimestamp(),
}, nil
}
// newNACK creates a NACK response
func (p *P2PProtocol) newNACK(msg *types.UnifiedSynapseMessage, err *types.SynapseError) (*types.UnifiedSynapseMessage, error) {
return nil, err
}
// newReject creates a task rejection response
func (p *P2PProtocol) newReject(msg *types.UnifiedSynapseMessage, taskID, reasonCode, reasonMessage string, alternatives *struct {
SuggestedAgentID string `json:"suggested_agent_id,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
PartialSupport bool `json:"partial_support,omitempty"`
}) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskRejectPayload{}
payload.TaskID = taskID
payload.Reason.Code = reasonCode
payload.Reason.Message = reasonMessage
if alternatives != nil {
payload.Alternatives = alternatives
}
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskReject),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}, nil
}
// newNeedInfo creates a need info response
func (p *P2PProtocol) newNeedInfo(msg *types.UnifiedSynapseMessage, taskID string, missingFields []types.MissingField) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskNeedInfoPayload{}
payload.TaskID = taskID
payload.MissingFields = missingFields
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskNeedInfo),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}, nil
}
// =============================================================================
// Validation
// =============================================================================
func (p *P2PProtocol) 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
}
// =============================================================================
// Task Management
// =============================================================================
// GetTaskStatus returns the status of a task
func (p *P2PProtocol) GetTaskStatus(taskID string) (types.P2PTaskStatus, bool) {
p.mu.RLock()
defer p.mu.RUnlock()
status, ok := p.tasks[taskID]
if !ok {
return "", false
}
return *status, true
}
// ListTasks returns all tasks
func (p *P2PProtocol) ListTasks() map[string]types.P2PTaskStatus {
p.mu.RLock()
defer p.mu.RUnlock()
result := make(map[string]types.P2PTaskStatus)
for k, v := range p.tasks {
result[k] = *v
}
return result
}