1251 lines
36 KiB
Go
1251 lines
36 KiB
Go
package sdk
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"crypto/hmac"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
|
||
"synapse/pkg/types"
|
||
)
|
||
|
||
// =============================================================================
|
||
// 协议类型
|
||
// =============================================================================
|
||
|
||
// Protocol 协议类型
|
||
type Protocol string
|
||
|
||
const (
|
||
ProtoP2A Protocol = "P2A" // 外部调用协议
|
||
ProtoA2A Protocol = "A2A" // 点对点通信协议
|
||
ProtoA2N Protocol = "A2N" // 群组协作协议
|
||
)
|
||
|
||
// =============================================================================
|
||
// 客户端配置
|
||
// =============================================================================
|
||
|
||
// Config 客户端配置
|
||
type Config struct {
|
||
Protocol Protocol // 协议类型,决定客户端行为
|
||
|
||
// P2A 协议字段
|
||
BaseURL string // Agent 服务地址
|
||
APIKey string // API Key
|
||
|
||
// A2A / A2N 协议字段
|
||
AgentEndpoint types.AgentEndpoint // 本 Agent 端点
|
||
AuthSecret string // HMAC 认证密钥
|
||
CallbackPort int // 回调监听端口
|
||
|
||
// 通用字段
|
||
Timeout time.Duration // HTTP 超时(默认 30s)
|
||
MaxRetries int // 最大重试次数
|
||
}
|
||
|
||
// =============================================================================
|
||
// 客户端
|
||
// =============================================================================
|
||
|
||
// Client 灵隙 SDK 客户端
|
||
// 通过 Protocol 类型区分三种协议行为
|
||
type Client struct {
|
||
config Config
|
||
|
||
// 通用
|
||
httpClient *http.Client
|
||
|
||
// A2A 任务状态
|
||
tasks map[string]*P2PTaskState
|
||
tasksMu sync.RWMutex
|
||
|
||
// A2N 群组状态
|
||
groups map[string]*GroupState
|
||
groupsMu sync.RWMutex
|
||
|
||
// 事件回调
|
||
eventHandler A2NEventHandler
|
||
|
||
// A2A 消息回调
|
||
messageHandler A2AMessageHandler
|
||
}
|
||
|
||
// P2PTaskState P2P 任务状态
|
||
type P2PTaskState struct {
|
||
TaskID string
|
||
Status types.P2PTaskStatus
|
||
Progress int
|
||
Message string
|
||
Result any
|
||
Error *types.SynapseError
|
||
}
|
||
|
||
// GroupState 群组状态
|
||
type GroupState struct {
|
||
GroupID string
|
||
Name string
|
||
Topology types.GroupTopology
|
||
Status types.GroupStatus
|
||
Members []types.GroupMember
|
||
Role string
|
||
}
|
||
|
||
// =============================================================================
|
||
// 构造函数
|
||
// =============================================================================
|
||
|
||
// NewClient 创建 SDK 客户端
|
||
// 通过 cfg.Protocol 区分协议行为:
|
||
// - ProtoP2A:外部调用,需要 BaseURL
|
||
// - ProtoA2A:点对点通信,需要 AgentEndpoint
|
||
// - ProtoA2N:群组协作,需要 AgentEndpoint
|
||
func NewClient(cfg Config) *Client {
|
||
if cfg.Timeout == 0 {
|
||
cfg.Timeout = 30 * time.Second
|
||
}
|
||
|
||
return &Client{
|
||
config: cfg,
|
||
httpClient: &http.Client{Timeout: cfg.Timeout},
|
||
tasks: make(map[string]*P2PTaskState),
|
||
groups: make(map[string]*GroupState),
|
||
}
|
||
}
|
||
|
||
// Protocol 返回客户端协议类型
|
||
func (c *Client) Protocol() Protocol {
|
||
return c.config.Protocol
|
||
}
|
||
|
||
// Config 返回客户端配置
|
||
func (c *Client) Config() Config {
|
||
return c.config
|
||
}
|
||
|
||
// =============================================================================
|
||
// 事件处理器设置
|
||
// =============================================================================
|
||
|
||
// A2AMessageHandler A2A 消息回调接口
|
||
type A2AMessageHandler interface {
|
||
HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
|
||
HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error
|
||
HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error
|
||
HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error
|
||
HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error
|
||
}
|
||
|
||
// SetA2AMessageHandler 设置 A2A 消息回调处理器
|
||
func (c *Client) SetA2AMessageHandler(handler A2AMessageHandler) {
|
||
c.messageHandler = handler
|
||
}
|
||
|
||
// A2NEventHandler A2N 群组事件接口
|
||
type A2NEventHandler interface {
|
||
OnGroupCreated(ctx context.Context, groupID string) error
|
||
OnGroupDisbanded(ctx context.Context, groupID string) error
|
||
OnMemberJoined(ctx context.Context, groupID string, member types.GroupMember) error
|
||
OnMemberLeft(ctx context.Context, groupID, agentID string) error
|
||
OnTaskSplit(ctx context.Context, groupID, originalTaskID string, subtasks []types.Subtask) error
|
||
OnSubtaskProgress(ctx context.Context, groupID, subtaskID string, progress int) error
|
||
OnTaskMerged(ctx context.Context, groupID, originalTaskID string, result any) error
|
||
OnVoteInitiated(ctx context.Context, groupID string, vote types.GroupVotePayload) error
|
||
OnVoteResult(ctx context.Context, groupID, voteID, option string, passed bool) error
|
||
OnEscalation(ctx context.Context, groupID, escalationID string, level int) error
|
||
OnBroadcast(ctx context.Context, groupID string, sender types.AgentEndpoint, message []byte) error
|
||
}
|
||
|
||
// SetA2NEventHandler 设置 A2N 群组事件处理器
|
||
func (c *Client) SetA2NEventHandler(handler A2NEventHandler) {
|
||
c.eventHandler = handler
|
||
}
|
||
|
||
// =============================================================================
|
||
// P2A 协议方法 — 外部调用
|
||
// =============================================================================
|
||
|
||
// =============================================================================
|
||
// P2A 数据结构
|
||
// =============================================================================
|
||
|
||
// TaskRequest 任务请求
|
||
type TaskRequest struct {
|
||
TaskType string `json:"task_type"`
|
||
Payload map[string]any `json:"payload"`
|
||
ExternalTaskID string `json:"external_task_id,omitempty"`
|
||
Priority string `json:"priority,omitempty"`
|
||
CallbackURL string `json:"callback_url,omitempty"`
|
||
CallbackToken string `json:"callback_token,omitempty"`
|
||
}
|
||
|
||
// TaskResponse 任务提交响应
|
||
type TaskResponse struct {
|
||
Success bool `json:"success"`
|
||
TaskID string `json:"task_id"`
|
||
Status string `json:"status"`
|
||
Message string `json:"message,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// TaskInfo 任务详情
|
||
type TaskInfo struct {
|
||
ID string `json:"id"`
|
||
Type string `json:"type"`
|
||
Status string `json:"status"`
|
||
Progress int `json:"progress"`
|
||
Message string `json:"message,omitempty"`
|
||
CreatedAt string `json:"created_at"`
|
||
UpdatedAt string `json:"updated_at"`
|
||
CompletedAt string `json:"completed_at,omitempty"`
|
||
ETA string `json:"eta,omitempty"`
|
||
Result map[string]any `json:"result,omitempty"`
|
||
Error *TaskError `json:"error,omitempty"`
|
||
ExternalTaskID string `json:"external_task_id,omitempty"`
|
||
}
|
||
|
||
// TaskError 任务错误
|
||
type TaskError struct {
|
||
Code string `json:"code"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// TaskDetailResponse 任务详情响应
|
||
type TaskDetailResponse struct {
|
||
Success bool `json:"success"`
|
||
Task TaskInfo `json:"task"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// TaskStatusResponse 任务状态响应
|
||
type TaskStatusResponse struct {
|
||
Success bool `json:"success"`
|
||
TaskID string `json:"task_id"`
|
||
Status string `json:"status"`
|
||
Progress int `json:"progress"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// TaskListResponse 任务列表响应
|
||
type TaskListResponse struct {
|
||
Success bool `json:"success"`
|
||
Tasks []TaskInfo `json:"tasks"`
|
||
Total int `json:"total"`
|
||
Limit int `json:"limit"`
|
||
Offset int `json:"offset"`
|
||
HasMore bool `json:"has_more"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// CapabilitiesResponse 能力查询响应
|
||
type CapabilitiesResponse struct {
|
||
Success bool `json:"success"`
|
||
Capabilities CapabilitiesInfo `json:"capabilities"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// CapabilitiesInfo Agent 能力信息
|
||
type CapabilitiesInfo struct {
|
||
AgentID string `json:"agent_id"`
|
||
AgentName string `json:"agent_name"`
|
||
SystemID string `json:"system_id"`
|
||
ProtocolVersion string `json:"protocol_version"`
|
||
SupportedTaskTypes []SupportedTaskType `json:"supported_task_types"`
|
||
SupportedTransportModes []string `json:"supported_transport_modes"`
|
||
MaxFileSize int64 `json:"max_file_size"`
|
||
MaxConcurrentTasks int `json:"max_concurrent_tasks"`
|
||
SupportProgressUpdates bool `json:"support_progress_updates"`
|
||
SupportStreaming bool `json:"support_streaming"`
|
||
SupportHiL bool `json:"support_hil"`
|
||
SupportGroups bool `json:"support_groups"`
|
||
}
|
||
|
||
// SupportedTaskType 支持的任务类型
|
||
type SupportedTaskType struct {
|
||
Type string `json:"type"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
EstimatedTime string `json:"estimated_time"`
|
||
InputSchema string `json:"input_schema,omitempty"`
|
||
OutputSchema string `json:"output_schema,omitempty"`
|
||
}
|
||
|
||
// FileParam 上传文件参数
|
||
type FileParam struct {
|
||
FieldName string
|
||
Filename string
|
||
Data []byte
|
||
MimeType string
|
||
}
|
||
|
||
// CallbackRequest 回调请求结构
|
||
type CallbackRequest struct {
|
||
TaskID string `json:"task_id"`
|
||
Status string `json:"status"`
|
||
Progress int `json:"progress"`
|
||
Message string `json:"message,omitempty"`
|
||
Result map[string]any `json:"result,omitempty"`
|
||
Error *TaskError `json:"error,omitempty"`
|
||
Timestamp string `json:"timestamp"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// P2A 方法
|
||
// =============================================================================
|
||
|
||
// apiURL 构造 API URL
|
||
func (c *Client) apiURL(path string) string {
|
||
return c.config.BaseURL + path
|
||
}
|
||
|
||
// setAuthHeaders 设置认证头
|
||
func (c *Client) setAuthHeaders(req *http.Request) {
|
||
if c.config.APIKey != "" {
|
||
req.Header.Set("X-API-Key", c.config.APIKey)
|
||
}
|
||
req.Header.Set("Accept", "application/json")
|
||
}
|
||
|
||
// SubmitTask 提交任务(JSON 方式)
|
||
func (c *Client) SubmitTask(ctx context.Context, req *TaskRequest) (*TaskResponse, error) {
|
||
body, err := json.Marshal(req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||
}
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.apiURL("/api/v1/synapse/task"), bytes.NewReader(body))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// SubmitTaskWithFiles 提交带文件的任务
|
||
func (c *Client) SubmitTaskWithFiles(ctx context.Context, req *TaskRequest, files []FileParam) (*TaskResponse, error) {
|
||
var buf bytes.Buffer
|
||
writer := multipart.NewWriter(&buf)
|
||
|
||
_ = writer.WriteField("task_type", req.TaskType)
|
||
|
||
payloadJSON, _ := json.Marshal(req.Payload)
|
||
_ = writer.WriteField("payload", string(payloadJSON))
|
||
|
||
if req.ExternalTaskID != "" {
|
||
_ = writer.WriteField("external_task_id", req.ExternalTaskID)
|
||
}
|
||
if req.Priority != "" {
|
||
_ = writer.WriteField("priority", req.Priority)
|
||
}
|
||
if req.CallbackURL != "" {
|
||
_ = writer.WriteField("callback_url", req.CallbackURL)
|
||
}
|
||
if req.CallbackToken != "" {
|
||
_ = writer.WriteField("callback_token", req.CallbackToken)
|
||
}
|
||
|
||
for _, f := range files {
|
||
var part io.Writer
|
||
var err error
|
||
if f.MimeType != "" {
|
||
part, err = writer.CreatePart(map[string][]string{
|
||
"Content-Disposition": {fmt.Sprintf(`form-data; name="%s"; filename="%s"`, f.FieldName, f.Filename)},
|
||
"Content-Type": {f.MimeType},
|
||
})
|
||
} else {
|
||
part, err = writer.CreateFormFile(f.FieldName, f.Filename)
|
||
}
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create form file: %w", err)
|
||
}
|
||
_, _ = part.Write(f.Data)
|
||
}
|
||
|
||
_ = writer.Close()
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.apiURL("/api/v1/synapse/task/multipart"), &buf)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
httpReq.Header.Set("Content-Type", writer.FormDataContentType())
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// GetTask 获取任务详情
|
||
func (c *Client) GetTask(ctx context.Context, taskID string) (*TaskDetailResponse, error) {
|
||
httpReq, err := http.NewRequestWithContext(ctx, "GET", c.apiURL("/api/v1/synapse/task/"+taskID), nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskDetailResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// GetTaskStatus 获取任务状态
|
||
func (c *Client) GetTaskStatus(ctx context.Context, taskID string) (*TaskStatusResponse, error) {
|
||
httpReq, err := http.NewRequestWithContext(ctx, "GET", c.apiURL("/api/v1/synapse/task/"+taskID+"/status"), nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskStatusResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// ListTasks 获取任务列表
|
||
func (c *Client) ListTasks(ctx context.Context, status string, limit, offset int) (*TaskListResponse, error) {
|
||
if limit <= 0 {
|
||
limit = 20
|
||
}
|
||
url := fmt.Sprintf("%s/api/v1/synapse/tasks?status=%s&limit=%d&offset=%d",
|
||
c.config.BaseURL, status, limit, offset)
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskListResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// CancelTask 取消任务
|
||
func (c *Client) CancelTask(ctx context.Context, taskID string) (*TaskResponse, error) {
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.apiURL("/api/v1/synapse/task/"+taskID+"/cancel"), nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result TaskResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// GetCapabilities 获取 Agent 能力清单
|
||
func (c *Client) GetCapabilities(ctx context.Context) (*CapabilitiesResponse, error) {
|
||
httpReq, err := http.NewRequestWithContext(ctx, "GET", c.apiURL("/api/v1/synapse/capabilities"), nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
c.setAuthHeaders(httpReq)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
var result CapabilitiesResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return &result, fmt.Errorf("API error: %s", result.Error)
|
||
}
|
||
|
||
return &result, nil
|
||
}
|
||
|
||
// WaitForTaskP2A 等待任务完成(轮询方式)
|
||
func (c *Client) WaitForTaskP2A(ctx context.Context, taskID string, interval time.Duration) (*TaskInfo, error) {
|
||
if interval == 0 {
|
||
interval = 5 * time.Second
|
||
}
|
||
|
||
ticker := time.NewTicker(interval)
|
||
defer ticker.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
case <-ticker.C:
|
||
resp, err := c.GetTask(ctx, taskID)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
switch resp.Task.Status {
|
||
case "completed", "failed", "cancelled", "rejected":
|
||
return &resp.Task, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// CreateAndWait 创建任务并等待完成
|
||
func (c *Client) CreateAndWait(ctx context.Context, taskType string, payload map[string]any, interval time.Duration) (*TaskInfo, error) {
|
||
req := &TaskRequest{
|
||
TaskType: taskType,
|
||
Payload: payload,
|
||
Priority: "normal",
|
||
}
|
||
|
||
resp, err := c.SubmitTask(ctx, req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if !resp.Success {
|
||
return nil, fmt.Errorf("task submission failed: %s", resp.Message)
|
||
}
|
||
|
||
return c.WaitForTaskP2A(ctx, resp.TaskID, interval)
|
||
}
|
||
|
||
// CreateWithCallback 创建任务并指定回调
|
||
func (c *Client) CreateWithCallback(ctx context.Context, taskType string, payload map[string]any, callbackURL, callbackToken string) (*TaskResponse, error) {
|
||
req := &TaskRequest{
|
||
TaskType: taskType,
|
||
Payload: payload,
|
||
Priority: "normal",
|
||
CallbackURL: callbackURL,
|
||
CallbackToken: callbackToken,
|
||
}
|
||
|
||
return c.SubmitTask(ctx, req)
|
||
}
|
||
|
||
// VerifyCallback 验证回调请求
|
||
func (c *Client) VerifyCallback(r *http.Request, callbackToken string) bool {
|
||
if callbackToken == "" {
|
||
return true
|
||
}
|
||
return r.Header.Get("X-Callback-Token") == callbackToken
|
||
}
|
||
|
||
// =============================================================================
|
||
// A2A 协议方法 — 点对点通信
|
||
// =============================================================================
|
||
|
||
// signMessage HMAC 签消息
|
||
func (c *Client) signMessage(msg *types.UnifiedSynapseMessage) string {
|
||
if c.config.AuthSecret == "" {
|
||
return ""
|
||
}
|
||
h := hmac.New(sha256.New, []byte(c.config.AuthSecret))
|
||
h.Write([]byte(msg.ID + "." + string(msg.Payload)))
|
||
return hex.EncodeToString(h.Sum(nil))
|
||
}
|
||
|
||
// VerifySignature 验证消息签名
|
||
func (c *Client) VerifySignature(msg *types.UnifiedSynapseMessage, secret string) bool {
|
||
if secret == "" {
|
||
return true
|
||
}
|
||
if msg.Signature == "" {
|
||
return false
|
||
}
|
||
h := hmac.New(sha256.New, []byte(secret))
|
||
h.Write([]byte(msg.ID + "." + string(msg.Payload)))
|
||
expected := hex.EncodeToString(h.Sum(nil))
|
||
return hmac.Equal([]byte(msg.Signature), []byte(expected))
|
||
}
|
||
|
||
// ackMessage 生成 ACK 响应
|
||
func (c *Client) ackMessage(msg *types.UnifiedSynapseMessage) *types.UnifiedSynapseMessage {
|
||
return &types.UnifiedSynapseMessage{
|
||
ID: types.GenerateMessageID(),
|
||
Version: msg.Version,
|
||
Type: string(types.P2PAck),
|
||
Sender: c.config.AgentEndpoint,
|
||
Receiver: &msg.Sender,
|
||
ReplyTo: msg.ID,
|
||
Payload: []byte("{}"),
|
||
Timestamp: types.NowTimestamp(),
|
||
}
|
||
}
|
||
|
||
// getTaskState 获取任务状态
|
||
func (c *Client) getTaskState(taskID string) (*P2PTaskState, bool) {
|
||
c.tasksMu.RLock()
|
||
defer c.tasksMu.RUnlock()
|
||
state, ok := c.tasks[taskID]
|
||
return state, ok
|
||
}
|
||
|
||
// listTasks 列出所有任务
|
||
func (c *Client) listTasks() map[string]*P2PTaskState {
|
||
c.tasksMu.RLock()
|
||
defer c.tasksMu.RUnlock()
|
||
result := make(map[string]*P2PTaskState, len(c.tasks))
|
||
for k, v := range c.tasks {
|
||
result[k] = v
|
||
}
|
||
return result
|
||
}
|
||
|
||
// setTaskState 设置任务状态
|
||
func (c *Client) setTaskState(taskID string, status types.P2PTaskStatus, progress int, message string) {
|
||
c.tasksMu.Lock()
|
||
defer c.tasksMu.Unlock()
|
||
if _, ok := c.tasks[taskID]; !ok {
|
||
c.tasks[taskID] = &P2PTaskState{TaskID: taskID}
|
||
}
|
||
c.tasks[taskID].Status = status
|
||
c.tasks[taskID].Progress = progress
|
||
c.tasks[taskID].Message = message
|
||
}
|
||
|
||
func (c *Client) setTaskResult(taskID string, result any) {
|
||
c.tasksMu.Lock()
|
||
defer c.tasksMu.Unlock()
|
||
if state, ok := c.tasks[taskID]; ok {
|
||
state.Result = result
|
||
}
|
||
}
|
||
|
||
func (c *Client) setTaskError(taskID string, err *types.SynapseError) {
|
||
c.tasksMu.Lock()
|
||
defer c.tasksMu.Unlock()
|
||
if state, ok := c.tasks[taskID]; ok {
|
||
state.Error = err
|
||
}
|
||
}
|
||
|
||
// sendMessage 发送 P2P 消息
|
||
func (c *Client) sendMessage(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
|
||
msg.Signature = c.signMessage(msg)
|
||
|
||
body, err := json.Marshal(msg)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to marshal message: %w", err)
|
||
}
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
httpReq.Header.Set("X-Synapse-Signature", msg.Signature)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode >= 400 {
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
return fmt.Errorf("remote error: %s", string(respBody))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// sendRequest 发送消息并等待响应
|
||
func (c *Client) sendRequest(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
|
||
msg.Signature = c.signMessage(msg)
|
||
|
||
body, err := json.Marshal(msg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to marshal message: %w", err)
|
||
}
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
httpReq.Header.Set("X-Synapse-Signature", msg.Signature)
|
||
|
||
resp, err := c.httpClient.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return nil, fmt.Errorf("remote error: %s", string(respBody))
|
||
}
|
||
|
||
var respMsg types.UnifiedSynapseMessage
|
||
if err := json.Unmarshal(respBody, &respMsg); err != nil {
|
||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||
}
|
||
|
||
return &respMsg, nil
|
||
}
|
||
|
||
// SendTaskRequest 发送任务请求
|
||
func (c *Client) SendTaskRequest(ctx context.Context, target types.AgentEndpoint, taskType string, payload map[string]any) (string, error) {
|
||
taskID := types.GenerateTaskID()
|
||
|
||
taskReq := types.TaskRequestPayload{}
|
||
taskReq.Task.ID = taskID
|
||
taskReq.Task.Type = taskType
|
||
taskReq.Task.Priority = types.PriorityNormal
|
||
taskReq.Task.Payload = payload
|
||
|
||
payloadBytes, _ := json.Marshal(taskReq)
|
||
|
||
msg := &types.UnifiedSynapseMessage{
|
||
ID: types.GenerateMessageID(),
|
||
Version: "1.0.0",
|
||
Type: string(types.P2PTaskRequest),
|
||
Sender: c.config.AgentEndpoint,
|
||
Receiver: &target,
|
||
Payload: payloadBytes,
|
||
Priority: types.PriorityNormal,
|
||
Timestamp: types.NowTimestamp(),
|
||
}
|
||
|
||
c.setTaskState(taskID, types.P2PStatusPending, 0, "任务已发送")
|
||
|
||
err := c.sendMessage(ctx, target.Endpoint, msg)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to send task request: %w", err)
|
||
}
|
||
|
||
return taskID, nil
|
||
}
|
||
|
||
// SendTaskCancel 发送取消
|
||
func (c *Client) SendTaskCancel(ctx context.Context, target types.AgentEndpoint, taskID, reason string) error {
|
||
cancelPayload := types.TaskCancelPayload{
|
||
TaskID: taskID,
|
||
Reason: reason,
|
||
CancelledBy: c.config.AgentEndpoint.AgentID,
|
||
}
|
||
|
||
payloadBytes, _ := json.Marshal(cancelPayload)
|
||
|
||
msg := &types.UnifiedSynapseMessage{
|
||
ID: types.GenerateMessageID(),
|
||
Version: "1.0.0",
|
||
Type: string(types.P2PTaskCancel),
|
||
Sender: c.config.AgentEndpoint,
|
||
Receiver: &target,
|
||
Payload: payloadBytes,
|
||
Timestamp: types.NowTimestamp(),
|
||
}
|
||
|
||
c.setTaskState(taskID, types.P2PStatusCancelled, 0, "任务已取消")
|
||
|
||
return c.sendMessage(ctx, target.Endpoint, msg)
|
||
}
|
||
|
||
// SendHumanDecision 发送人类决策
|
||
func (c *Client) SendHumanDecision(ctx context.Context, target types.AgentEndpoint, taskID string, decision types.HumanDecisionPayload) error {
|
||
payloadBytes, _ := json.Marshal(decision)
|
||
|
||
msg := &types.UnifiedSynapseMessage{
|
||
ID: types.GenerateMessageID(),
|
||
Version: "1.0.0",
|
||
Type: string(types.P2PHumanDecision),
|
||
Sender: c.config.AgentEndpoint,
|
||
Receiver: &target,
|
||
Payload: payloadBytes,
|
||
Timestamp: types.NowTimestamp(),
|
||
}
|
||
|
||
return c.sendMessage(ctx, target.Endpoint, msg)
|
||
}
|
||
|
||
// SendCapabilityQueryA2A 查询对方能力
|
||
func (c *Client) SendCapabilityQueryA2A(ctx context.Context, target types.AgentEndpoint) ([]string, error) {
|
||
msg := &types.UnifiedSynapseMessage{
|
||
ID: types.GenerateMessageID(),
|
||
Version: "1.0.0",
|
||
Type: string(types.P2PCapabilityQuery),
|
||
Sender: c.config.AgentEndpoint,
|
||
Receiver: &target,
|
||
Payload: []byte("{}"),
|
||
Timestamp: types.NowTimestamp(),
|
||
}
|
||
|
||
respMsg, err := c.sendRequest(ctx, target.Endpoint, msg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to query capabilities: %w", err)
|
||
}
|
||
|
||
var resp struct {
|
||
Capabilities []string `json:"capabilities"`
|
||
}
|
||
if err := json.Unmarshal(respMsg.Payload, &resp); err != nil {
|
||
return nil, fmt.Errorf("failed to parse capabilities: %w", err)
|
||
}
|
||
|
||
return resp.Capabilities, nil
|
||
}
|
||
|
||
// WaitForTaskA2A 等待任务完成
|
||
func (c *Client) WaitForTaskA2A(ctx context.Context, taskID string, timeout time.Duration) (*P2PTaskState, error) {
|
||
deadline := time.Now().Add(timeout)
|
||
ticker := time.NewTicker(1 * time.Second)
|
||
defer ticker.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
case <-ticker.C:
|
||
if time.Now().After(deadline) {
|
||
return nil, fmt.Errorf("task timeout")
|
||
}
|
||
|
||
state, ok := c.getTaskState(taskID)
|
||
if !ok {
|
||
continue
|
||
}
|
||
|
||
switch state.Status {
|
||
case types.P2PStatusCompleted, types.P2PStatusFailed, types.P2PStatusCancelled, types.P2PStatusRejected:
|
||
return state, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// HandleA2AMessage 处理收到的 A2A 消息
|
||
func (c *Client) HandleA2AMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
|
||
if c.config.AuthSecret != "" && !c.VerifySignature(msg, c.config.AuthSecret) {
|
||
return nil, fmt.Errorf("invalid signature")
|
||
}
|
||
|
||
switch types.P2PMessageType(msg.Type) {
|
||
case types.P2PTaskRequest:
|
||
if c.messageHandler != nil {
|
||
return c.messageHandler.HandleTaskRequest(ctx, msg)
|
||
}
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskConfirm:
|
||
var payload types.TaskConfirmPayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PStatusConfirmed, 0, "任务已确认")
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskReject:
|
||
var payload types.TaskRejectPayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PStatusRejected, 0, payload.Reason.Message)
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskStatusUpdate:
|
||
var payload types.TaskStatusUpdatePayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PTaskStatus(payload.Status), payload.Progress, payload.Message)
|
||
if c.messageHandler != nil {
|
||
_ = c.messageHandler.HandleTaskStatusUpdate(ctx, msg)
|
||
}
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskComplete:
|
||
var payload types.TaskCompletePayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PStatusCompleted, 100, payload.Summary)
|
||
c.setTaskResult(payload.TaskID, payload.Result.Output)
|
||
if c.messageHandler != nil {
|
||
_ = c.messageHandler.HandleTaskComplete(ctx, msg)
|
||
}
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskFailed:
|
||
var payload types.TaskFailedPayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PStatusFailed, 0, payload.Error.Message)
|
||
c.setTaskError(payload.TaskID, types.NewSynapseError(
|
||
types.SynapseErrorCode(0),
|
||
payload.Error.Message,
|
||
nil,
|
||
))
|
||
if c.messageHandler != nil {
|
||
_ = c.messageHandler.HandleTaskFailed(ctx, msg)
|
||
}
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PTaskNeedHuman:
|
||
var payload types.TaskNeedHumanPayload
|
||
_ = json.Unmarshal(msg.Payload, &payload)
|
||
c.setTaskState(payload.TaskID, types.P2PStatusNeedHuman, 0, payload.Reason.Message)
|
||
if c.messageHandler != nil {
|
||
_ = c.messageHandler.HandleNeedHuman(ctx, msg)
|
||
}
|
||
return c.ackMessage(msg), nil
|
||
|
||
case types.P2PCapabilityQuery:
|
||
return nil, nil
|
||
|
||
default:
|
||
return c.ackMessage(msg), nil
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// A2N 协议方法 — 群组协作
|
||
// =============================================================================
|
||
|
||
// CreateGroup 创建群组
|
||
func (c *Client) CreateGroup(ctx context.Context, name string, topology types.GroupTopology, config *types.GroupConfig, initialMembers []types.AgentEndpoint) (*GroupState, error) {
|
||
groupID := fmt.Sprintf("group-%d", time.Now().UnixNano())
|
||
|
||
if config == nil {
|
||
config = &types.GroupConfig{
|
||
AllowSelfLeave: true,
|
||
RequiresApproval: false,
|
||
ConsensusRequired: false,
|
||
}
|
||
}
|
||
|
||
members := make([]types.GroupMember, 0, len(initialMembers)+1)
|
||
for _, m := range initialMembers {
|
||
members = append(members, types.GroupMember{
|
||
AgentID: m.AgentID,
|
||
Role: "worker",
|
||
Status: types.GroupMemberActive,
|
||
})
|
||
}
|
||
|
||
groupState := &GroupState{
|
||
GroupID: groupID,
|
||
Name: name,
|
||
Topology: topology,
|
||
Status: types.GroupStatusActive,
|
||
Members: members,
|
||
Role: "coordinator",
|
||
}
|
||
|
||
c.groupsMu.Lock()
|
||
c.groups[groupID] = groupState
|
||
c.groupsMu.Unlock()
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnGroupCreated(ctx, groupID)
|
||
}
|
||
|
||
return groupState, nil
|
||
}
|
||
|
||
// JoinGroup 加入群组
|
||
func (c *Client) JoinGroup(ctx context.Context, groupID string) error {
|
||
c.groupsMu.Lock()
|
||
defer c.groupsMu.Unlock()
|
||
|
||
if _, ok := c.groups[groupID]; !ok {
|
||
c.groups[groupID] = &GroupState{
|
||
GroupID: groupID,
|
||
Status: types.GroupStatusActive,
|
||
Role: "worker",
|
||
}
|
||
}
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnMemberJoined(ctx, groupID, types.GroupMember{})
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// LeaveGroup 离开群组
|
||
func (c *Client) LeaveGroup(ctx context.Context, groupID string) error {
|
||
c.groupsMu.Lock()
|
||
delete(c.groups, groupID)
|
||
c.groupsMu.Unlock()
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnMemberLeft(ctx, groupID, "")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// DisbandGroup 解散群组
|
||
func (c *Client) DisbandGroup(ctx context.Context, groupID string) error {
|
||
c.groupsMu.Lock()
|
||
if group, ok := c.groups[groupID]; ok {
|
||
group.Status = types.GroupStatusDisbanded
|
||
}
|
||
c.groupsMu.Unlock()
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnGroupDisbanded(ctx, groupID)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetGroup 获取群组信息
|
||
func (c *Client) GetGroup(groupID string) (*GroupState, bool) {
|
||
c.groupsMu.RLock()
|
||
defer c.groupsMu.RUnlock()
|
||
group, ok := c.groups[groupID]
|
||
return group, ok
|
||
}
|
||
|
||
// ListGroups 列出所有群组
|
||
func (c *Client) ListGroups() []*GroupState {
|
||
c.groupsMu.RLock()
|
||
defer c.groupsMu.RUnlock()
|
||
result := make([]*GroupState, 0, len(c.groups))
|
||
for _, g := range c.groups {
|
||
result = append(result, g)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// SubmitGroupTask 提交群组任务
|
||
func (c *Client) SubmitGroupTask(ctx context.Context, groupID, taskType string, requirements map[string]any, mergeStrategy types.MergeStrategy) ([]types.Subtask, error) {
|
||
subtasks := []types.Subtask{
|
||
{
|
||
ID: fmt.Sprintf("sub-%d-1", time.Now().UnixNano()),
|
||
Description: "子任务 1",
|
||
AssignedTo: "",
|
||
Dependencies: []string{},
|
||
EstimatedDuration: 60,
|
||
Priority: 1,
|
||
},
|
||
}
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnTaskSplit(ctx, groupID, "", subtasks)
|
||
}
|
||
|
||
return subtasks, nil
|
||
}
|
||
|
||
// SendSubtaskResult 发送子任务结果
|
||
func (c *Client) SendSubtaskResult(ctx context.Context, groupID, subtaskID string, result any, err error) error {
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnTaskMerged(ctx, groupID, "", result)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// InitiateVote 发起投票
|
||
func (c *Client) InitiateVote(ctx context.Context, groupID, question string, options []string, voterIDs []string, threshold float64, deadline time.Duration) (string, error) {
|
||
voteID := fmt.Sprintf("vote-%d", time.Now().UnixNano())
|
||
|
||
vote := types.GroupVotePayload{
|
||
VoteID: voteID,
|
||
Question: question,
|
||
Options: options,
|
||
Voters: voterIDs,
|
||
Deadline: types.Timestamp(time.Now().Add(deadline).UTC().Format(time.RFC3339)),
|
||
Threshold: threshold,
|
||
Status: "open",
|
||
}
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnVoteInitiated(ctx, groupID, vote)
|
||
}
|
||
|
||
return voteID, nil
|
||
}
|
||
|
||
// SubmitVote 提交投票
|
||
func (c *Client) SubmitVote(ctx context.Context, groupID, voteID, option, reason string) error {
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnVoteResult(ctx, groupID, voteID, option, true)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Broadcast 广播消息
|
||
func (c *Client) Broadcast(ctx context.Context, groupID string, message []byte) error {
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnBroadcast(ctx, groupID, types.AgentEndpoint{}, message)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EscalateToHuman 升级到人类决策
|
||
func (c *Client) EscalateToHuman(ctx context.Context, groupID string, level int, reason string) (string, error) {
|
||
escalationID := fmt.Sprintf("esc-%d", time.Now().UnixNano())
|
||
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnEscalation(ctx, groupID, escalationID, level)
|
||
}
|
||
|
||
return escalationID, nil
|
||
}
|
||
|
||
// HandleA2NMessage 处理群组消息
|
||
func (c *Client) HandleA2NMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
|
||
groupID := msg.GroupID
|
||
|
||
switch types.GroupMessageType(msg.Type) {
|
||
case types.GroupCreate:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnGroupCreated(ctx, groupID)
|
||
}
|
||
|
||
case types.GroupDisband:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnGroupDisbanded(ctx, groupID)
|
||
}
|
||
|
||
case types.GroupMemberUpdate:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnMemberJoined(ctx, groupID, types.GroupMember{})
|
||
}
|
||
|
||
case types.GroupTaskSplit:
|
||
var payload types.GroupTaskSplitPayload
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnTaskSplit(ctx, groupID, payload.OriginalTask.ID, payload.Subtasks)
|
||
}
|
||
|
||
case types.GroupTaskProgress:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnSubtaskProgress(ctx, groupID, "", 0)
|
||
}
|
||
|
||
case types.GroupTaskMerge:
|
||
var payload types.GroupTaskMergePayload
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnTaskMerged(ctx, groupID, payload.OriginalTaskID, payload.MergedResult)
|
||
}
|
||
|
||
case types.GroupVote:
|
||
var payload types.GroupVotePayload
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnVoteInitiated(ctx, groupID, payload)
|
||
}
|
||
|
||
case types.GroupConsensus:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnVoteResult(ctx, groupID, "", "", true)
|
||
}
|
||
|
||
case types.GroupConsensusEscalate, types.GroupSubtaskEscalate:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnEscalation(ctx, groupID, "", 1)
|
||
}
|
||
|
||
case types.GroupBroadcast:
|
||
if c.eventHandler != nil {
|
||
_ = c.eventHandler.OnBroadcast(ctx, groupID, msg.Sender, msg.Payload)
|
||
}
|
||
}
|
||
|
||
return nil, nil
|
||
}
|