625 lines
21 KiB
Go
625 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()
|
|
|
|
// Store task status
|
|
status := types.P2PStatusPending
|
|
p.tasks[taskID] = &status
|
|
|
|
// 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{}) {
|
|
// Update status to executing
|
|
p.mu.Lock()
|
|
execStatus := types.P2PStatusExecuting
|
|
p.tasks[taskID] = &execStatus
|
|
p.mu.Unlock()
|
|
|
|
// 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.mu.Lock()
|
|
failStatus := types.P2PStatusFailed
|
|
p.tasks[taskID] = &failStatus
|
|
p.mu.Unlock()
|
|
p.sendTaskFailed(ctx, taskID, "execution_failed", err.Error(), true, nil)
|
|
return
|
|
}
|
|
|
|
// Task completed
|
|
p.mu.Lock()
|
|
compStatus := types.P2PStatusCompleted
|
|
p.tasks[taskID] = &compStatus
|
|
p.mu.Unlock()
|
|
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) {
|
|
errBytes, _ := json.Marshal(err)
|
|
return &types.UnifiedSynapseMessage{
|
|
ID: types.GenerateMessageID(),
|
|
Version: "1.0.0",
|
|
Type: string(types.P2PNack),
|
|
Sender: p.agentEndpoint,
|
|
Receiver: &msg.Sender,
|
|
CorrelationID: msg.CorrelationID,
|
|
ReplyTo: msg.ID,
|
|
Payload: errBytes,
|
|
Timestamp: types.NowTimestamp(),
|
|
}, nil
|
|
}
|
|
|
|
// 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
|
|
}
|