Files
synapse/pkg/sdk/client.go
2026-06-28 03:24:26 +08:00

1251 lines
36 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}