872 lines
25 KiB
Go
872 lines
25 KiB
Go
package grpcapi
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"synapse/pkg/types"
|
|
)
|
|
|
|
// =============================================================================
|
|
// gRPC Server Implementation
|
|
// =============================================================================
|
|
|
|
// GRPCServer wraps the gRPC server and gateway
|
|
type GRPCServer struct {
|
|
grpcServer *grpc.Server
|
|
gateway *http.Server
|
|
mu sync.RWMutex
|
|
|
|
// Services
|
|
taskService *TaskServiceImpl
|
|
messageService *MessageServiceImpl
|
|
capabilitiesService *CapabilitiesServiceImpl
|
|
groupService *GroupServiceImpl
|
|
healthService *HealthServiceImpl
|
|
|
|
// Dependencies
|
|
taskExecutor TaskExecutor
|
|
msgRouter MessageRouter
|
|
capsProvider CapabilitiesProvider
|
|
groupManager GroupManager
|
|
}
|
|
|
|
// TaskExecutor interface for task operations
|
|
type TaskExecutor interface {
|
|
SubmitTask(ctx context.Context, taskType string, payload map[string]any, externalTaskID, priority, callbackURL, callbackToken string) (string, string, error)
|
|
GetTaskStatus(ctx context.Context, taskID string) (*types.TaskInfo, error)
|
|
ListTasks(ctx context.Context, status string, limit, offset int) ([]types.TaskInfo, int, error)
|
|
CancelTask(ctx context.Context, taskID string) error
|
|
}
|
|
|
|
type MessageRouter interface {
|
|
RouteMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
|
|
}
|
|
|
|
type CapabilitiesProvider interface {
|
|
GetCapabilities(ctx context.Context) (*types.AgentCapabilities, error)
|
|
}
|
|
|
|
type GroupManager interface {
|
|
CreateGroup(ctx context.Context, name, description, coordinatorID string, initialMembers []string) (string, error)
|
|
JoinGroup(ctx context.Context, groupID string, agent *types.AgentEndpoint) error
|
|
LeaveGroup(ctx context.Context, groupID, agentID string) error
|
|
DisbandGroup(ctx context.Context, groupID string) error
|
|
ListMembers(ctx context.Context, groupID string) ([]types.GroupMember, error)
|
|
|
|
// Voting operations
|
|
InitiateVote(ctx context.Context, groupID, initiatorID, question string, options []string, threshold float64, timeoutSeconds int32) (string, error)
|
|
SubmitVote(ctx context.Context, voteID, agentID, option, reason string) error
|
|
GetVoteStatus(ctx context.Context, voteID string) (*VoteStatus, error)
|
|
|
|
// Task split and merge
|
|
SplitTask(ctx context.Context, groupID, taskID, taskType, requirements string, strategy SplitStrategy) ([]*SubtaskInfo, error)
|
|
MergeResults(ctx context.Context, groupID, parentTaskID string, strategy MergeStrategy) (*anypb.Any, string, error)
|
|
}
|
|
|
|
// VoteStatus represents the status of a vote
|
|
type VoteStatus struct {
|
|
VoteID string
|
|
Status string // open, decided, expired
|
|
Question string
|
|
TotalVotes int
|
|
RequiredVotes int
|
|
WinningOption string
|
|
Votes map[string]string // agentID -> option
|
|
}
|
|
|
|
// =============================================================================
|
|
// Server Constructor
|
|
// =============================================================================
|
|
|
|
// NewGRPCServer creates a new gRPC server
|
|
func NewGRPCServer(taskExec TaskExecutor, msgRouter MessageRouter, capsProvider CapabilitiesProvider, groupManager GroupManager) *GRPCServer {
|
|
return &GRPCServer{
|
|
taskExecutor: taskExec,
|
|
msgRouter: msgRouter,
|
|
capsProvider: capsProvider,
|
|
groupManager: groupManager,
|
|
taskService: &TaskServiceImpl{
|
|
taskExecutor: taskExec,
|
|
streamClients: make(map[string]chan *TaskStatusUpdate),
|
|
},
|
|
messageService: &MessageServiceImpl{
|
|
msgRouter: msgRouter,
|
|
},
|
|
capabilitiesService: &CapabilitiesServiceImpl{
|
|
provider: capsProvider,
|
|
},
|
|
groupService: &GroupServiceImpl{
|
|
groupManager: groupManager,
|
|
},
|
|
healthService: &HealthServiceImpl{},
|
|
}
|
|
}
|
|
|
|
// Start starts both gRPC and gateway servers
|
|
func (s *GRPCServer) Start(grpcPort, gatewayPort int) error {
|
|
// Start gRPC server
|
|
go func() {
|
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort))
|
|
if err != nil {
|
|
log.Fatalf("[gRPC] Failed to listen: %v", err)
|
|
}
|
|
|
|
// Create gRPC server with interceptors
|
|
opts := []grpc.ServerOption{
|
|
grpc.UnaryInterceptor(s.unaryInterceptor),
|
|
grpc.StreamInterceptor(s.streamInterceptor),
|
|
}
|
|
s.grpcServer = grpc.NewServer(opts...)
|
|
|
|
// Register services using ServiceDesc
|
|
s.grpcServer.RegisterService(&TaskService_ServiceDesc, s.taskService)
|
|
s.grpcServer.RegisterService(&A2AMessageService_ServiceDesc, s.messageService)
|
|
s.grpcServer.RegisterService(&CapabilitiesService_ServiceDesc, s.capabilitiesService)
|
|
s.grpcServer.RegisterService(&GroupService_ServiceDesc, s.groupService)
|
|
s.grpcServer.RegisterService(&HealthService_ServiceDesc, s.healthService)
|
|
|
|
log.Printf("[gRPC] Server started on port %d", grpcPort)
|
|
if err := s.grpcServer.Serve(lis); err != nil {
|
|
log.Fatalf("[gRPC] Failed to serve: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Start gRPC-Gateway
|
|
go func() {
|
|
_ = context.Background() // ctx reserved for future gateway handler registration
|
|
mux := http.NewServeMux()
|
|
|
|
// Register gateway handlers
|
|
// Note: These would be generated from proto by grpc-gateway
|
|
// err := RegisterTaskServiceHandlerFromEndpoint(ctx, mux, fmt.Sprintf("localhost:%d", grpcPort), opts)
|
|
// ... etc
|
|
|
|
s.gateway = &http.Server{
|
|
Addr: fmt.Sprintf(":%d", gatewayPort),
|
|
Handler: mux,
|
|
}
|
|
|
|
log.Printf("[Gateway] HTTP server started on port %d", gatewayPort)
|
|
if err := s.gateway.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("[Gateway] Failed to serve: %v", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully stops both servers
|
|
func (s *GRPCServer) Stop() {
|
|
if s.grpcServer != nil {
|
|
s.grpcServer.GracefulStop()
|
|
}
|
|
if s.gateway != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
s.gateway.Shutdown(ctx)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Interceptors
|
|
// =============================================================================
|
|
|
|
func (s *GRPCServer) unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
// Extract metadata
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
if ok {
|
|
// Check API key
|
|
apiKeys := md.Get("x-api-key")
|
|
if len(apiKeys) > 0 {
|
|
// Validate API key
|
|
log.Printf("[gRPC] API Key: %s", apiKeys[0])
|
|
}
|
|
}
|
|
|
|
// Call handler
|
|
return handler(ctx, req)
|
|
}
|
|
|
|
func (s *GRPCServer) streamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
// Extract metadata
|
|
md, ok := metadata.FromIncomingContext(ss.Context())
|
|
if ok {
|
|
apiKeys := md.Get("x-api-key")
|
|
if len(apiKeys) > 0 {
|
|
log.Printf("[gRPC Stream] API Key: %s", apiKeys[0])
|
|
}
|
|
}
|
|
|
|
return handler(srv, ss)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Task Service Implementation
|
|
// =============================================================================
|
|
|
|
type TaskServiceImpl struct {
|
|
UnimplementedTaskServiceServer
|
|
taskExecutor TaskExecutor
|
|
mu sync.RWMutex
|
|
streamClients map[string]chan *TaskStatusUpdate
|
|
}
|
|
|
|
func (s *TaskServiceImpl) SubmitTask(ctx context.Context, req *SubmitTaskRequest) (*SubmitTaskResponse, error) {
|
|
// Convert payload
|
|
payload := make(map[string]any)
|
|
if req.Payload != nil {
|
|
// Unpack Any to map
|
|
// This would use protojson or similar
|
|
}
|
|
|
|
taskID, status, err := s.taskExecutor.SubmitTask(
|
|
ctx,
|
|
req.TaskType,
|
|
payload,
|
|
req.ExternalTaskId,
|
|
req.Priority,
|
|
req.CallbackUrl,
|
|
req.CallbackToken,
|
|
)
|
|
if err != nil {
|
|
return &SubmitTaskResponse{
|
|
Success: false,
|
|
ErrorCode: "TASK_SUBMISSION_FAILED",
|
|
Message: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &SubmitTaskResponse{
|
|
Success: true,
|
|
TaskId: taskID,
|
|
Status: parseTaskStatus(status),
|
|
Message: "任务已接收",
|
|
}, nil
|
|
}
|
|
|
|
func (s *TaskServiceImpl) SubmitTaskWithFiles(ctx context.Context, req *SubmitTaskWithFilesRequest) (*SubmitTaskResponse, error) {
|
|
// Handle file uploads
|
|
// Convert files and call SubmitTask
|
|
return s.SubmitTask(ctx, req.Request)
|
|
}
|
|
|
|
func (s *TaskServiceImpl) GetTask(ctx context.Context, req *GetTaskRequest) (*TaskStatusResponse, error) {
|
|
task, err := s.taskExecutor.GetTaskStatus(ctx, req.TaskId)
|
|
if err != nil {
|
|
return &TaskStatusResponse{
|
|
Success: false,
|
|
ErrorCode: "TASK_NOT_FOUND",
|
|
ErrorMessage: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &TaskStatusResponse{
|
|
Success: true,
|
|
Task: convertTaskInfo(task),
|
|
}, nil
|
|
}
|
|
|
|
func (s *TaskServiceImpl) GetTaskStatus(ctx context.Context, req *GetTaskRequest) (*TaskStatusResponse, error) {
|
|
return s.GetTask(ctx, req)
|
|
}
|
|
|
|
func (s *TaskServiceImpl) ListTasks(ctx context.Context, req *ListTasksRequest) (*ListTasksResponse, error) {
|
|
tasks, total, err := s.taskExecutor.ListTasks(ctx, req.Status, int(req.Limit), int(req.Offset))
|
|
if err != nil {
|
|
return &ListTasksResponse{
|
|
Success: false,
|
|
}, err
|
|
}
|
|
|
|
var protoTasks []*TaskInfo
|
|
for _, t := range tasks {
|
|
protoTasks = append(protoTasks, convertTaskInfo(&t))
|
|
}
|
|
|
|
return &ListTasksResponse{
|
|
Success: true,
|
|
Tasks: protoTasks,
|
|
Total: int32(total),
|
|
Limit: req.Limit,
|
|
Offset: req.Offset,
|
|
HasMore: int(req.Offset+req.Limit) < total,
|
|
}, nil
|
|
}
|
|
|
|
func (s *TaskServiceImpl) CancelTask(ctx context.Context, req *CancelTaskRequest) (*CancelTaskResponse, error) {
|
|
if err := s.taskExecutor.CancelTask(ctx, req.TaskId); err != nil {
|
|
return &CancelTaskResponse{
|
|
Success: false,
|
|
Message: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &CancelTaskResponse{
|
|
Success: true,
|
|
TaskId: req.TaskId,
|
|
Status: TaskStatus_TASK_STATUS_CANCELLED,
|
|
Message: "任务已取消",
|
|
}, nil
|
|
}
|
|
|
|
// StreamTaskStatus implements bidirectional streaming for task status updates
|
|
func (s *TaskServiceImpl) StreamTaskStatus(stream TaskService_StreamTaskStatusServer) error {
|
|
// Register client
|
|
clientID := fmt.Sprintf("stream-%d", time.Now().UnixNano())
|
|
statusChan := make(chan *TaskStatusUpdate, 100)
|
|
|
|
s.mu.Lock()
|
|
s.streamClients[clientID] = statusChan
|
|
s.mu.Unlock()
|
|
|
|
defer func() {
|
|
s.mu.Lock()
|
|
delete(s.streamClients, clientID)
|
|
s.mu.Unlock()
|
|
close(statusChan)
|
|
}()
|
|
|
|
// Goroutine to send status updates
|
|
go func() {
|
|
for update := range statusChan {
|
|
if err := stream.Send(update); err != nil {
|
|
log.Printf("[gRPC Stream] Send error: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Receive subscription requests
|
|
for {
|
|
req, err := stream.Recv()
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if req.Subscribe {
|
|
log.Printf("[gRPC Stream] Client %s subscribed to task %s", clientID, req.TaskId)
|
|
// Start watching task status
|
|
go s.watchTaskStatus(req.TaskId, statusChan)
|
|
} else {
|
|
log.Printf("[gRPC Stream] Client %s unsubscribed from task %s", clientID, req.TaskId)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *TaskServiceImpl) watchTaskStatus(taskID string, ch chan *TaskStatusUpdate) {
|
|
ticker := time.NewTicker(2 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
task, err := s.taskExecutor.GetTaskStatus(context.Background(), taskID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
ch <- &TaskStatusUpdate{
|
|
TaskId: taskID,
|
|
Status: parseTaskStatus(task.Status),
|
|
Progress: int32(task.Progress),
|
|
Message: task.Message,
|
|
}
|
|
|
|
// Stop if terminal state
|
|
if task.Status == "completed" || task.Status == "failed" || task.Status == "cancelled" {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Message Service Implementation
|
|
// =============================================================================
|
|
|
|
type MessageServiceImpl struct {
|
|
UnimplementedA2AMessageServiceServer
|
|
msgRouter MessageRouter
|
|
}
|
|
|
|
func (s *MessageServiceImpl) SendMessage(ctx context.Context, req *SendMessageRequest) (*SendMessageResponse, error) {
|
|
// Convert proto message to internal format
|
|
msg := convertProtoToInternalMessage(req.Message)
|
|
|
|
resp, err := s.msgRouter.RouteMessage(ctx, msg)
|
|
if err != nil {
|
|
return &SendMessageResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &SendMessageResponse{
|
|
Success: true,
|
|
MessageId: resp.ID,
|
|
}, nil
|
|
}
|
|
|
|
// StreamMessages implements bidirectional streaming for Synapse messages
|
|
func (s *MessageServiceImpl) StreamMessages(stream A2AMessageService_StreamMessagesServer) error {
|
|
ctx := stream.Context()
|
|
clientID := fmt.Sprintf("msg-stream-%d", time.Now().UnixNano())
|
|
|
|
log.Printf("[gRPC Stream] New message stream: %s", clientID)
|
|
|
|
// Goroutine to receive messages
|
|
go func() {
|
|
for {
|
|
msg, err := stream.Recv()
|
|
if err == io.EOF {
|
|
return
|
|
}
|
|
if err != nil {
|
|
log.Printf("[gRPC Stream] Receive error: %v", err)
|
|
return
|
|
}
|
|
|
|
internalMsg := convertProtoToInternalMessage(msg)
|
|
resp, err := s.msgRouter.RouteMessage(ctx, internalMsg)
|
|
if err != nil {
|
|
log.Printf("[gRPC Stream] Route error: %v", err)
|
|
continue
|
|
}
|
|
|
|
protoResp := convertInternalToProtoMessage(resp)
|
|
if err := stream.Send(protoResp); err != nil {
|
|
log.Printf("[gRPC Stream] Send error: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Keep stream alive
|
|
<-ctx.Done()
|
|
log.Printf("[gRPC Stream] Message stream closed: %s", clientID)
|
|
return nil
|
|
}
|
|
|
|
func (s *MessageServiceImpl) ReceiveCallback(req *emptypb.Empty, stream A2AMessageService_ReceiveCallbackServer) error {
|
|
// This would be used by agents to receive callback messages via gRPC stream
|
|
// Implementation depends on callback routing mechanism
|
|
return status.Error(codes.Unimplemented, "not implemented")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Capabilities Service Implementation
|
|
// =============================================================================
|
|
|
|
type CapabilitiesServiceImpl struct {
|
|
UnimplementedCapabilitiesServiceServer
|
|
provider CapabilitiesProvider
|
|
}
|
|
|
|
func (s *CapabilitiesServiceImpl) GetCapabilities(ctx context.Context, req *emptypb.Empty) (*CapabilitiesResponse, error) {
|
|
caps, err := s.provider.GetCapabilities(ctx)
|
|
if err != nil {
|
|
return &CapabilitiesResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &CapabilitiesResponse{
|
|
Success: true,
|
|
Capabilities: convertAgentCapabilities(caps),
|
|
}, nil
|
|
}
|
|
|
|
func (s *CapabilitiesServiceImpl) QueryAgent(ctx context.Context, req *QueryAgentRequest) (*CapabilitiesResponse, error) {
|
|
// Query specific agent's capabilities
|
|
return s.GetCapabilities(ctx, nil)
|
|
}
|
|
|
|
func (s *CapabilitiesServiceImpl) ListAgents(ctx context.Context, req *emptypb.Empty) (*ListAgentsResponse, error) {
|
|
return &ListAgentsResponse{
|
|
Success: true,
|
|
Agents: []*AgentInfo{},
|
|
}, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Group Service Implementation
|
|
// =============================================================================
|
|
|
|
type GroupServiceImpl struct {
|
|
UnimplementedGroupServiceServer
|
|
groupManager GroupManager
|
|
}
|
|
|
|
func (s *GroupServiceImpl) CreateGroup(ctx context.Context, req *CreateGroupRequest) (*CreateGroupResponse, error) {
|
|
groupID, err := s.groupManager.CreateGroup(ctx, req.Name, req.Description, req.CoordinatorId, req.InitialMembers)
|
|
if err != nil {
|
|
return &CreateGroupResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &CreateGroupResponse{
|
|
Success: true,
|
|
GroupId: groupID,
|
|
Group: &GroupInfo{
|
|
Id: groupID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
CoordinatorId: req.CoordinatorId,
|
|
Status: "active",
|
|
CreatedAt: timestamppb.Now(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (s *GroupServiceImpl) JoinGroup(ctx context.Context, req *JoinGroupRequest) (*JoinGroupResponse, error) {
|
|
err := s.groupManager.JoinGroup(ctx, req.GroupId, convertProtoEndpoint(req.Agent))
|
|
if err != nil {
|
|
return &JoinGroupResponse{Success: false, Error: err.Error()}, nil
|
|
}
|
|
return &JoinGroupResponse{Success: true}, nil
|
|
}
|
|
|
|
func (s *GroupServiceImpl) LeaveGroup(ctx context.Context, req *LeaveGroupRequest) (*LeaveGroupResponse, error) {
|
|
err := s.groupManager.LeaveGroup(ctx, req.GroupId, req.AgentId)
|
|
if err != nil {
|
|
return &LeaveGroupResponse{Success: false, Error: err.Error()}, nil
|
|
}
|
|
return &LeaveGroupResponse{Success: true}, nil
|
|
}
|
|
|
|
func (s *GroupServiceImpl) DisbandGroup(ctx context.Context, req *DisbandGroupRequest) (*DisbandGroupResponse, error) {
|
|
err := s.groupManager.DisbandGroup(ctx, req.GroupId)
|
|
if err != nil {
|
|
return &DisbandGroupResponse{Success: false, Error: err.Error()}, nil
|
|
}
|
|
return &DisbandGroupResponse{Success: true}, nil
|
|
}
|
|
|
|
func (s *GroupServiceImpl) ListGroupMembers(ctx context.Context, req *ListGroupMembersRequest) (*ListGroupMembersResponse, error) {
|
|
members, err := s.groupManager.ListMembers(ctx, req.GroupId)
|
|
if err != nil {
|
|
return &ListGroupMembersResponse{Success: false}, nil
|
|
}
|
|
|
|
var protoMembers []*GroupMember
|
|
for _, m := range members {
|
|
protoMembers = append(protoMembers, &GroupMember{
|
|
AgentId: m.AgentID,
|
|
AgentName: m.AgentName,
|
|
Role: m.Role,
|
|
Status: string(m.Status),
|
|
JoinedAt: timestamppb.Now(),
|
|
})
|
|
}
|
|
|
|
return &ListGroupMembersResponse{
|
|
Success: true,
|
|
Members: protoMembers,
|
|
}, nil
|
|
}
|
|
|
|
// StreamGroupMessages implements bidirectional streaming for group messages
|
|
func (s *GroupServiceImpl) StreamGroupMessages(stream GroupService_StreamGroupMessagesServer) error {
|
|
ctx := stream.Context()
|
|
clientID := fmt.Sprintf("group-stream-%d", time.Now().UnixNano())
|
|
|
|
log.Printf("[gRPC Stream] New group stream: %s", clientID)
|
|
|
|
go func() {
|
|
for {
|
|
msg, err := stream.Recv()
|
|
if err == io.EOF {
|
|
return
|
|
}
|
|
if err != nil {
|
|
log.Printf("[gRPC Stream] Receive error: %v", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("[gRPC Stream] Group message received: group=%s, type=%v", msg.GroupId, msg.Type)
|
|
|
|
// Echo back for now
|
|
if err := stream.Send(msg); err != nil {
|
|
log.Printf("[gRPC Stream] Send error: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
log.Printf("[gRPC Stream] Group stream closed: %s", clientID)
|
|
return nil
|
|
}
|
|
|
|
// InitiateVote initiates a voting process in the group
|
|
func (s *GroupServiceImpl) InitiateVote(ctx context.Context, req *VoteRequest) (*VoteResponse, error) {
|
|
voteID, err := s.groupManager.InitiateVote(ctx, req.GroupId, req.InitiatorId, req.Question, req.Options, req.Threshold, req.TimeoutSeconds)
|
|
if err != nil {
|
|
return &VoteResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &VoteResponse{
|
|
Success: true,
|
|
VoteId: voteID,
|
|
}, nil
|
|
}
|
|
|
|
// SubmitVote submits a vote in an ongoing voting process
|
|
func (s *GroupServiceImpl) SubmitVote(ctx context.Context, req *SubmitVoteRequest) (*SubmitVoteResponse, error) {
|
|
err := s.groupManager.SubmitVote(ctx, req.VoteId, req.AgentId, req.Option, req.Reason)
|
|
if err != nil {
|
|
return &SubmitVoteResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &SubmitVoteResponse{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// GetVoteStatus gets the current status of a vote
|
|
func (s *GroupServiceImpl) GetVoteStatus(ctx context.Context, req *GetVoteStatusRequest) (*VoteStatusResponse, error) {
|
|
status, err := s.groupManager.GetVoteStatus(ctx, req.VoteId)
|
|
if err != nil {
|
|
return &VoteStatusResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &VoteStatusResponse{
|
|
Success: true,
|
|
VoteId: status.VoteID,
|
|
Status: status.Status,
|
|
Question: status.Question,
|
|
TotalVotes: int32(status.TotalVotes),
|
|
RequiredVotes: int32(status.RequiredVotes),
|
|
WinningOption: status.WinningOption,
|
|
}, nil
|
|
}
|
|
|
|
// SplitTask splits a task into subtasks across group members
|
|
func (s *GroupServiceImpl) SplitTask(ctx context.Context, req *SplitTaskRequest) (*SplitTaskResponse, error) {
|
|
subtasks, err := s.groupManager.SplitTask(ctx, req.GroupId, req.TaskId, req.TaskType, req.Requirements, req.Strategy)
|
|
if err != nil {
|
|
return &SplitTaskResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &SplitTaskResponse{
|
|
Success: true,
|
|
GroupId: req.GroupId,
|
|
ParentTaskId: req.TaskId,
|
|
Subtasks: subtasks,
|
|
}, nil
|
|
}
|
|
|
|
// MergeResults merges results from subtasks
|
|
func (s *GroupServiceImpl) MergeResults(ctx context.Context, req *MergeResultsRequest) (*MergeResultsResponse, error) {
|
|
mergedResult, summary, err := s.groupManager.MergeResults(ctx, req.GroupId, req.ParentTaskId, req.Strategy)
|
|
if err != nil {
|
|
return &MergeResultsResponse{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
return &MergeResultsResponse{
|
|
Success: true,
|
|
ParentTaskId: req.ParentTaskId,
|
|
MergedResult: mergedResult,
|
|
Summary: summary,
|
|
}, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Health Service Implementation
|
|
// =============================================================================
|
|
|
|
type HealthServiceImpl struct {
|
|
UnimplementedHealthServiceServer
|
|
}
|
|
|
|
func (s *HealthServiceImpl) HealthCheck(ctx context.Context, req *emptypb.Empty) (*HealthCheckResponse, error) {
|
|
return &HealthCheckResponse{
|
|
Status: "healthy",
|
|
Version: "1.0.0",
|
|
UptimeSeconds: 3600,
|
|
ActiveTasks: 0,
|
|
}, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Conversion Helpers
|
|
// =============================================================================
|
|
|
|
func parseTaskStatus(status string) TaskStatus {
|
|
switch status {
|
|
case "received":
|
|
return TaskStatus_TASK_STATUS_RECEIVED
|
|
case "processing":
|
|
return TaskStatus_TASK_STATUS_PROCESSING
|
|
case "completed":
|
|
return TaskStatus_TASK_STATUS_COMPLETED
|
|
case "failed":
|
|
return TaskStatus_TASK_STATUS_FAILED
|
|
case "cancelled":
|
|
return TaskStatus_TASK_STATUS_CANCELLED
|
|
case "rejected":
|
|
return TaskStatus_TASK_STATUS_REJECTED
|
|
case "waiting":
|
|
return TaskStatus_TASK_STATUS_WAITING
|
|
default:
|
|
return TaskStatus_TASK_STATUS_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func convertTaskInfo(task *types.TaskInfo) *TaskInfo {
|
|
if task == nil {
|
|
return nil
|
|
}
|
|
|
|
var result *anypb.Any
|
|
if task.Result != nil {
|
|
result, _ = anypb.New(&anypb.Any{Value: []byte(fmt.Sprintf("%v", task.Result))})
|
|
}
|
|
|
|
return &TaskInfo{
|
|
Id: task.ID,
|
|
Type: task.Type,
|
|
Status: parseTaskStatus(task.Status),
|
|
Progress: int32(task.Progress),
|
|
Message: task.Message,
|
|
CreatedAt: timestamppb.Now(),
|
|
UpdatedAt: timestamppb.Now(),
|
|
Result: result,
|
|
}
|
|
}
|
|
|
|
func convertAgentCapabilities(caps *types.AgentCapabilities) *AgentCapabilities {
|
|
if caps == nil {
|
|
return nil
|
|
}
|
|
|
|
var taskTypes []*SupportedTaskType
|
|
for _, tt := range caps.SupportedTaskTypes {
|
|
taskTypes = append(taskTypes, &SupportedTaskType{
|
|
Type: tt.Type,
|
|
Name: tt.Name,
|
|
Description: tt.Description,
|
|
EstimatedTime: tt.EstimatedTime,
|
|
InputSchema: tt.InputSchema,
|
|
OutputSchema: tt.OutputSchema,
|
|
Capabilities: tt.Capabilities,
|
|
})
|
|
}
|
|
|
|
var transportModes []TransportMode
|
|
for _, tm := range caps.SupportedTransportModes {
|
|
transportModes = append(transportModes, TransportMode(TransportMode_value[string(tm)]))
|
|
}
|
|
|
|
return &AgentCapabilities{
|
|
AgentId: caps.AgentID,
|
|
AgentName: caps.AgentName,
|
|
SystemId: caps.SystemID,
|
|
ProtocolVersion: caps.ProtocolVersion,
|
|
SupportedTaskTypes: taskTypes,
|
|
SupportedContentTypes: caps.SupportedContentTypes,
|
|
SupportedTransportModes: transportModes,
|
|
MaxFileSize: caps.MaxFileSize,
|
|
MaxConcurrentTasks: int32(caps.MaxConcurrentTasks),
|
|
MaxTaskDurationSeconds: int32(caps.MaxTaskDuration),
|
|
SupportProgressUpdates: caps.SupportProgressUpdates,
|
|
SupportStreaming: caps.SupportStreaming,
|
|
SupportHil: caps.SupportHiL,
|
|
SupportGroups: caps.SupportGroups,
|
|
}
|
|
}
|
|
|
|
func convertProtoToInternalMessage(msg *A2AMessage) *types.UnifiedSynapseMessage {
|
|
if msg == nil {
|
|
return nil
|
|
}
|
|
|
|
var payload []byte
|
|
if msg.Payload != nil {
|
|
payload = msg.Payload.Value
|
|
}
|
|
|
|
return &types.UnifiedSynapseMessage{
|
|
ID: msg.Id,
|
|
Version: msg.Version,
|
|
Type: string(MessageType_name[int32(msg.Type)]),
|
|
Sender: types.AgentEndpoint{AgentID: msg.Sender.AgentId},
|
|
CorrelationID: msg.CorrelationId,
|
|
ReplyTo: msg.ReplyTo,
|
|
GroupID: msg.GroupId,
|
|
Timestamp: types.Timestamp(msg.Timestamp.AsTime().Format(time.RFC3339)),
|
|
Payload: payload,
|
|
Signature: msg.Signature,
|
|
}
|
|
}
|
|
|
|
func convertInternalToProtoMessage(msg *types.UnifiedSynapseMessage) *A2AMessage {
|
|
if msg == nil {
|
|
return nil
|
|
}
|
|
|
|
var payload *anypb.Any
|
|
if len(msg.Payload) > 0 {
|
|
payload = &anypb.Any{Value: msg.Payload}
|
|
}
|
|
|
|
return &A2AMessage{
|
|
Id: msg.ID,
|
|
Version: msg.Version,
|
|
Type: MessageType(MessageType_value[msg.Type]),
|
|
Sender: &AgentEndpoint{AgentId: msg.Sender.AgentID},
|
|
CorrelationId: msg.CorrelationID,
|
|
ReplyTo: msg.ReplyTo,
|
|
GroupId: msg.GroupID,
|
|
Timestamp: timestamppb.Now(),
|
|
Payload: payload,
|
|
Signature: msg.Signature,
|
|
}
|
|
}
|
|
|
|
func convertProtoEndpoint(endpoint *AgentEndpoint) *types.AgentEndpoint {
|
|
if endpoint == nil {
|
|
return nil
|
|
}
|
|
return &types.AgentEndpoint{
|
|
AgentID: endpoint.AgentId,
|
|
AgentName: endpoint.AgentName,
|
|
SystemID: endpoint.SystemId,
|
|
Endpoint: endpoint.Endpoint,
|
|
Version: endpoint.Version,
|
|
}
|
|
}
|