From 812f68d5aaae69d3e975af98d2877f3dd6d2329c Mon Sep 17 00:00:00 2001 From: lychang <632945494@qq.com> Date: Sun, 28 Jun 2026 03:24:26 +0800 Subject: [PATCH] =?UTF-8?q?=E7=81=B5=E9=9A=99(synapse)=20v1.0.0=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 21 + configs/.gitkeep | 0 docs/v1/00-index.md | 113 -- docs/v1/00_index.md | 107 ++ ...{01-raw-protocol.md => 01_raw-protocol.md} | 0 ...{02-a2a-protocol.md => 02_a2a-protocol.md} | 2 +- ...{03-a2n-protocol.md => 03_a2n-protocol.md} | 2 +- ...{04-p2a-protocol.md => 04_p2a-protocol.md} | 2 +- ...draft.md => 05_identity-protocol-draft.md} | 4 +- docs/v1/capabilities-system.md | 480 +++++++ docs/v1/components.md | 517 +++++++ docs/v1/content-system.md | 427 ++++++ docs/v1/error-codes.md | 408 ++++++ docs/v1/grpc-transport.md | 521 +++++++ docs/v1/hil-mechanism.md | 1010 +++++++++++++ docs/v1/message-extensions.md | 201 +++ docs/v1/transport-modes.md | 579 ++++++++ {group => internal/agent/group}/protocol.go | 2 +- {p2p => internal/agent/p2p}/protocol.go | 2 +- {p2p => internal/agent/p2p}/protocol_test.go | 2 +- .../api/externalapi}/handler.go | 2 +- {grpcapi => internal/api/grpcapi}/client.go | 0 {grpcapi => internal/api/grpcapi}/server.go | 2 +- .../api/grpcapi}/synapse.pb.go | 0 .../api/grpcapi}/synapse_grpc.pb.go | 0 .../capabilities}/provider.go | 2 +- .../components/harbor}/harbor.go | 2 +- {libra => internal/components/libra}/libra.go | 2 +- .../components/meridian}/meridian.go | 2 +- .../components/panorama}/panorama.go | 2 +- {pivot => internal/components/pivot}/pivot.go | 2 +- {sigil => internal/components/sigil}/sigil.go | 2 +- .../components/speculum}/speculum.go | 0 {hil => internal/hil}/protocol.go | 2 +- {routing => internal/routing}/router.go | 8 +- .../transport}/multipart_polling.go | 2 +- .../transport}/transport.go | 2 +- pkg/sdk/client.go | 1250 +++++++++++++++++ pkg/sdk/doc.go | 21 + {types => pkg/types}/content.go | 0 {types => pkg/types}/errors.go | 0 {types => pkg/types}/types.go | 0 {types => pkg/types}/types_test.go | 0 sdk/a2a/client.go | 459 ------ sdk/a2n/client.go | 346 ----- sdk/doc.go | 10 - sdk/p2a/client.go | 547 -------- test/.gitkeep | 0 48 files changed, 5566 insertions(+), 1499 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 configs/.gitkeep delete mode 100644 docs/v1/00-index.md create mode 100644 docs/v1/00_index.md rename docs/v1/{01-raw-protocol.md => 01_raw-protocol.md} (100%) rename docs/v1/{02-a2a-protocol.md => 02_a2a-protocol.md} (99%) rename docs/v1/{03-a2n-protocol.md => 03_a2n-protocol.md} (99%) rename docs/v1/{04-p2a-protocol.md => 04_p2a-protocol.md} (99%) rename docs/v1/{05-identity-protocol-draft.md => 05_identity-protocol-draft.md} (99%) create mode 100644 docs/v1/capabilities-system.md create mode 100644 docs/v1/components.md create mode 100644 docs/v1/content-system.md create mode 100644 docs/v1/error-codes.md create mode 100644 docs/v1/grpc-transport.md create mode 100644 docs/v1/hil-mechanism.md create mode 100644 docs/v1/message-extensions.md create mode 100644 docs/v1/transport-modes.md rename {group => internal/agent/group}/protocol.go (99%) rename {p2p => internal/agent/p2p}/protocol.go (99%) rename {p2p => internal/agent/p2p}/protocol_test.go (99%) rename {externalapi => internal/api/externalapi}/handler.go (99%) rename {grpcapi => internal/api/grpcapi}/client.go (100%) rename {grpcapi => internal/api/grpcapi}/server.go (99%) rename {grpcapi => internal/api/grpcapi}/synapse.pb.go (100%) rename {grpcapi => internal/api/grpcapi}/synapse_grpc.pb.go (100%) rename {capabilities => internal/capabilities}/provider.go (99%) rename {harbor => internal/components/harbor}/harbor.go (99%) rename {libra => internal/components/libra}/libra.go (99%) rename {meridian => internal/components/meridian}/meridian.go (99%) rename {panorama => internal/components/panorama}/panorama.go (99%) rename {pivot => internal/components/pivot}/pivot.go (99%) rename {sigil => internal/components/sigil}/sigil.go (99%) rename {speculum => internal/components/speculum}/speculum.go (100%) rename {hil => internal/hil}/protocol.go (99%) rename {routing => internal/routing}/router.go (99%) rename {transport => internal/transport}/multipart_polling.go (99%) rename {transport => internal/transport}/transport.go (99%) create mode 100644 pkg/sdk/client.go create mode 100644 pkg/sdk/doc.go rename {types => pkg/types}/content.go (100%) rename {types => pkg/types}/errors.go (100%) rename {types => pkg/types}/types.go (100%) rename {types => pkg/types}/types_test.go (100%) delete mode 100644 sdk/a2a/client.go delete mode 100644 sdk/a2n/client.go delete mode 100644 sdk/doc.go delete mode 100644 sdk/p2a/client.go create mode 100644 test/.gitkeep diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c6769fa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# 变更记录 + +## [1.0.0] - 2026-06-27 + +### Feat + +- 初始化灵隙 Synapse 协议栈项目 +- 实现 A2A(点对点)、A2N(群组协作)、P2A(外部调用)三大子协议 +- 实现七大组件层:灵脉、灵玺、灵鉴、灵泊、灵瞰、灵衡、灵枢 +- 实现完整 SDK(p2a/a2a/a2n 三合一) +- 实现 gRPC 双传输协议 +- 实现 HiL 三级升级机制 +- 实现数字分段错误码系统 +- 协议文档 v1 重构(抽象层 + 实现层分离) + +### Refactor + +- 项目结构按标准化规范重组(cmd/internal/pkg/docs/configs/test) +- SDK 三个子包合并为单包,通过 Protocol 参数区分 +- 协议文档移除编号前缀 +- 所有 import 路径对齐新目录结构 diff --git a/configs/.gitkeep b/configs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/v1/00-index.md b/docs/v1/00-index.md deleted file mode 100644 index 164fdeb..0000000 --- a/docs/v1/00-index.md +++ /dev/null @@ -1,113 +0,0 @@ -# 灵隙 Synapse 协议栈 v1 - -> 版本:1.0.0 -> 更新日期:2026-06-27 - ---- - -## 协议结构 - -本版本协议按抽象层设计,只定义"实现什么功能"和"数据格式",不涉及具体实现细节。 - -``` -docs/v1/ -├── 00-index.md # 本文件(协议索引) -├── 01-raw-protocol.md # 原始协议(基础层) -├── 02-a2a-protocol.md # A2A 协议(点对点) -├── 03-a2n-protocol.md # A2N 协议(群组协作) -└── 04-p2a-protocol.md # P2A 协议(外部调用) -``` - ---- - -## 协议概览 - -### 层级关系 - -``` - ┌─────────────────────────────────────┐ - │ P2A 协议(外部调用入口) │ - │ 04-p2a-protocol.md │ - └─────────────────────────────────────┘ - │ - │ 封装 - ▼ -┌─────────────────────────────────────────────────────┐ -│ A2A 协议(点对点任务协作) │ -│ 02-a2a-protocol.md │ -└─────────────────────────────────────────────────────┘ - │ - │ 复用单播 - ▼ -┌─────────────────────────────────────────────────────┐ -│ A2N 协议(群组协作) │ -│ 03-a2n-protocol.md │ -└─────────────────────────────────────────────────────┘ - │ - │ 继承 - ▼ -┌─────────────────────────────────────────────────────┐ -│ 原始协议(公共基础) │ -│ 01-raw-protocol.md │ -│ - 统一消息信封 │ -│ - 身份标识 │ -│ - 签名机制 │ -│ - 时间格式 │ -└─────────────────────────────────────────────────────┘ -``` - ---- - -## 协议对比 - -| 协议 | 代号 | 参与方 | 核心功能 | -|------|------|--------|---------| -| 原始协议 | Raw | - | 消息信封、身份、签名 | -| A2A 协议 | P2P | Agent ↔ Agent | 任务提交、进度同步、信息补全、HiL | -| A2N 协议 | Group | 1 Coordinator + N Worker | 群组管理、任务拆分合并、投票共识 | -| P2A 协议 | Caller | 外部 → Agent | REST API、回调/轮询 | - ---- - -## 消息类型前缀 - -| 前缀 | 协议 | 说明 | -|------|------|------| -| `p2p:` | A2A | 点对点消息(17 种) | -| `group:` | A2N | 群组内消息(20+ 种) | -| `inter_group:` | A2N | 跨群组消息(升级) | -| `caller:` | P2A | 外部调用消息(内部转换) | - ---- - -## 与 v0 版本的差异 - -| 对比项 | v0 版本 | v1 版本 | -|--------|---------|---------| -| 内容定位 | 混合抽象层和实现层 | 纯抽象层 | -| 数据格式 | 嵌入大量 JSON 示例 | 表格化字段定义 | -| 实现细节 | 包含 Go struct、算法实现 | 仅定义接口要求 | -| 七大组件 | 无协议规范 | 待补充独立文档 | -| 文件数量 | 8 个 | 4 个核心协议 | - ---- - -## 后续补充 - -以下内容待补充为独立实现文档(不属于协议抽象层): - -- 七大组件详细规范(灵脉、灵玺、灵鉴、灵泊、灵瞰、灵衡、灵枢) -- 内容类型系统(TextContent、FileContent 等) -- 能力声明系统 -- gRPC 传输协议 -- HiL 三级升级机制 -- 数字分段错误码完整定义 - ---- - -## 版本历史 - -| 版本 | 日期 | 变更说明 | -|------|------|---------| -| v1.0.0 | 2026-06-27 | 重构为抽象层协议,分离实现细节 | -| v0.x | 2026-06-27 | 初始版本(混合抽象层与实现层) | \ No newline at end of file diff --git a/docs/v1/00_index.md b/docs/v1/00_index.md new file mode 100644 index 0000000..8c91069 --- /dev/null +++ b/docs/v1/00_index.md @@ -0,0 +1,107 @@ +# 灵隙 Synapse 协议栈 v1 + +> 版本:1.0.0 +> 更新日期:2026-06-27 + +--- + +## 协议结构 + +本版本协议按抽象层设计,只定义"实现什么功能"和"数据格式",不涉及具体实现细节。 + +``` +docs/v1/ +├── index.md # 本文件(协议索引) +├── raw-protocol.md # 原始协议(基础层) +├── a2a-protocol.md # A2A 协议(点对点) +├── a2n-protocol.md # A2N 协议(群组协作) +├── p2a-protocol.md # P2A 协议(外部调用) +├── identity-protocol-draft.md # 去中心化身份与寻址(草案) +│ +├── components.md # 七大组件层(灵脉/灵玺/灵鉴/灵泊/灵瞰/灵衡/灵枢) +├── content-system.md # 内容类型系统 +├── capabilities-system.md # 能力声明系统 +├── grpc-transport.md # gRPC 传输协议 +├── hil-mechanism.md # HiL 三级升级机制 +├── error-codes.md # 数字分段错误码系统 +├── message-extensions.md # 消息扩展字段 +└── transport-modes.md # 传输模式定义 +``` + +--- + +## 文档分类 + +v1 版本文档分为三层: + +### 协议层(抽象层) + +定义"实现什么功能"和"数据格式",不涉及具体实现细节: + +| 编号 | 文档 | 说明 | +|----|----------------------------------------------------------------|---------------| +| 00 | [00_index.md](00_index.md) | 协议索引 | +| 01 | [01_raw-protocol.md](01_raw-protocol.md) | 原始协议(公共基础) | +| 02 | [02_a2a-protocol.md](02_a2a-protocol.md) | A2A 协议(点对点) | +| 03 | [03_a2n-protocol.md](03_a2n-protocol.md) | A2N 协议(群组协作) | +| 04 | [04_p2a-protocol.md](04_p2a-protocol.md) | P2A 协议(外部调用) | +| 05 | [05_identity-protocol-draft.md](05_identity-protocol-draft.md) | 去中心化身份与寻址(草案) | + +### 实现层 + +每个文档对应一个独立的功能模块: + +| 文档 | 对应模块 | +|--------------------------------------------------|-----------------------------| +| [components.md](components.md) | 七大组件层(灵脉/灵玺/灵鉴/灵泊/灵瞰/灵衡/灵枢) | +| [content-system.md](content-system.md) | 内容类型系统 | +| [capabilities-system.md](capabilities-system.md) | 能力声明系统 | +| [grpc-transport.md](grpc-transport.md) | gRPC 传输协议 | +| [hil-mechanism.md](hil-mechanism.md) | HiL 三级升级机制 | +| [error-codes.md](error-codes.md) | 数字分段错误码系统 | +| [message-extensions.md](message-extensions.md) | 消息扩展字段 | +| [transport-modes.md](transport-modes.md) | 传输模式定义 | + +--- + +## 协议对比 + +| 协议 | 代号 | 参与方 | 核心功能 | +|--------|--------|--------------------------|--------------------| +| 原始协议 | Raw | - | 消息信封、身份、签名 | +| A2A 协议 | P2P | Agent ↔ Agent | 任务提交、进度同步、信息补全、HiL | +| A2N 协议 | Group | 1 Coordinator + N Worker | 群组管理、任务拆分合并、投票共识 | +| P2A 协议 | Caller | 外部 → Agent | REST API、回调/轮询 | + +--- + +## 消息类型前缀 + +| 前缀 | 协议 | 说明 | +|----------------|-----|--------------| +| `p2p:` | A2A | 点对点消息(17 种) | +| `group:` | A2N | 群组内消息(20+ 种) | +| `inter_group:` | A2N | 跨群组消息(升级) | +| `caller:` | P2A | 外部调用消息(内部转换) | + +--- + +## 与 v0 版本的差异 + +| 对比项 | v0 版本 | v1 版本 | +|------|-------------------|--------------------------| +| 内容定位 | 混合抽象层和实现层 | 协议层 + 独立实现层 | +| 数据格式 | 嵌入大量 JSON 示例 | 表格化字段定义 | +| 实现细节 | 包含 Go struct、算法实现 | 独立实现层文档 | +| 七大组件 | 一个文档混合包含 | 独立为组件层规范 | +| SDK | 三个子包(p2a/a2a/a2n) | 单包,通过 Protocol 参数区分 | +| 文件数量 | 8 个 | 14 个(5 协议 + 8 实现 + 1 草案) | + +--- + +## 版本历史 + +| 版本 | 日期 | 变更说明 | +|--------|------------|-------------------------------| +| v1.0.0 | 2026-06-27 | 重构为抽象层协议 + 独立实现层文档,继承 v0 全部内容 | +| v0.x | 2026-06-27 | 初始版本(混合抽象层与实现层) | \ No newline at end of file diff --git a/docs/v1/01-raw-protocol.md b/docs/v1/01_raw-protocol.md similarity index 100% rename from docs/v1/01-raw-protocol.md rename to docs/v1/01_raw-protocol.md diff --git a/docs/v1/02-a2a-protocol.md b/docs/v1/02_a2a-protocol.md similarity index 99% rename from docs/v1/02-a2a-protocol.md rename to docs/v1/02_a2a-protocol.md index 165a5bb..f9f1f1b 100644 --- a/docs/v1/02-a2a-protocol.md +++ b/docs/v1/02_a2a-protocol.md @@ -3,7 +3,7 @@ > 版本:1.0.0 > 状态:规范 > 层级:子协议层 -> 基于:[灵隙原始协议](01-raw-protocol.md) +> 基于:[灵隙原始协议](01_raw-protocol.md) > 前缀:`p2p:` --- diff --git a/docs/v1/03-a2n-protocol.md b/docs/v1/03_a2n-protocol.md similarity index 99% rename from docs/v1/03-a2n-protocol.md rename to docs/v1/03_a2n-protocol.md index 1027996..7a649be 100644 --- a/docs/v1/03-a2n-protocol.md +++ b/docs/v1/03_a2n-protocol.md @@ -3,7 +3,7 @@ > 版本:1.0.0 > 状态:规范 > 层级:子协议层 -> 基于:[灵隙原始协议](01-raw-protocol.md)、[A2A 协议](02-a2a-protocol.md) +> 基于:[灵隙原始协议](01_raw-protocol.md)、[A2A 协议](02_a2a-protocol.md) > 前缀:`group:` / `inter_group:` --- diff --git a/docs/v1/04-p2a-protocol.md b/docs/v1/04_p2a-protocol.md similarity index 99% rename from docs/v1/04-p2a-protocol.md rename to docs/v1/04_p2a-protocol.md index 17e3170..76cd89a 100644 --- a/docs/v1/04-p2a-protocol.md +++ b/docs/v1/04_p2a-protocol.md @@ -3,7 +3,7 @@ > 版本:1.0.0 > 状态:规范 > 层级:子协议层 -> 基于:[灵隙原始协议](01-raw-protocol.md) +> 基于:[灵隙原始协议](01_raw-protocol.md) > 前缀:`caller:`(内部转换) --- diff --git a/docs/v1/05-identity-protocol-draft.md b/docs/v1/05_identity-protocol-draft.md similarity index 99% rename from docs/v1/05-identity-protocol-draft.md rename to docs/v1/05_identity-protocol-draft.md index dbb350d..ddd9e46 100644 --- a/docs/v1/05-identity-protocol-draft.md +++ b/docs/v1/05_identity-protocol-draft.md @@ -3,7 +3,7 @@ > 版本:0.1(草案) > 状态:草案 > 层级:基础设施层 -> 依赖:[灵隙原始协议](01-raw-protocol.md) +> 依赖:[灵隙原始协议](01_raw-protocol.md) --- @@ -782,7 +782,7 @@ func (a *AntiReplay) CheckAndAdd(nonce string, timestamp int64) bool { ### 8.1 与原始协议的整合 -本草案扩展 [01-raw-protocol.md](01-raw-protocol.md) 中的 `AgentEndpoint`: +本草案扩展 [raw-protocol.md](01_raw-protocol.md) 中的 `AgentEndpoint`: ```go type AgentEndpoint struct { diff --git a/docs/v1/capabilities-system.md b/docs/v1/capabilities-system.md new file mode 100644 index 0000000..be23c87 --- /dev/null +++ b/docs/v1/capabilities-system.md @@ -0,0 +1,480 @@ +# 灵隙协议实现规范:能力声明系统 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 目录 + +1. [核心接口 CapabilitiesProvider](#1-核心接口-capabilitiesprovider) +2. [能力描述结构 AgentCapabilities](#2-能力描述结构-agentcapabilities) +3. [SupportedTaskType 结构](#3-supportedtasktype-结构) +4. [接口分类](#4-接口分类) +5. [合规级别](#5-合规级别) +6. [标准接口注册表](#6-标准接口注册表) + +--- + +## 1. 核心接口 CapabilitiesProvider + +每个 Agent 必须实现 `CapabilitiesProvider` 接口,以便其他 Agent 查询其能力。该接口是所有能力声明系统的入口点,定义了三个核心方法: + +### 1.1 接口定义 + +```go +type CapabilitiesProvider interface { + GetCapabilities(ctx context.Context) (*AgentCapabilities, error) + GetSupportedInterfaces() []ImplementedInterface + GetTaskTypeCapabilities(taskType string) (*SupportedTaskType, error) +} +``` + +### 1.2 方法说明 + +| 方法 | 返回值 | 说明 | +|------|--------|------| +| `GetCapabilities(ctx)` | `*AgentCapabilities` | 返回 Agent 的完整能力描述,包括身份信息、支持的任务类型、内容类型、传输模式、资源限制以及各项特性支持标志。这是能力查询的核心方法。 | +| `GetSupportedInterfaces()` | `[]ImplementedInterface` | 返回 Agent 实现的标准接口列表。每个条目包含接口名称、分类、合规级别和版本号,用于快速判断 Agent 是否支持某项协议功能。 | +| `GetTaskTypeCapabilities(taskType)` | `*SupportedTaskType` | 返回指定任务类型的详细能力描述,包括输入输出 Schema、预估耗时、关联能力标签等。若 Agent 不支持该任务类型,应返回 `nil`。 | + +### 1.3 ImplementedInterface 结构 + +```go +type ImplementedInterface struct { + Name string `json:"name"` + Category InterfaceCategory `json:"category"` + ComplianceLevel Compliance `json:"compliance_level"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} +``` + +### 1.4 调用约定 + +- `GetCapabilities` 应当在 Agent 启动时被注册中心调用,用于填充全局能力目录。 +- `GetSupportedInterfaces` 应当在 P2P 握手阶段被对端 Agent 调用,用于协商交互协议。 +- `GetTaskTypeCapabilities` 应当在任务提交前被调用,用于验证任务类型是否受支持以及获取输入输出约束。 +- 所有方法必须保持幂等性,多次调用应返回一致的结果。 + +--- + +## 2. 能力描述结构 AgentCapabilities + +`AgentCapabilities` 是 Agent 能力声明的核心数据结构,包含了 Agent 的身份信息、支持的任务类型、内容类型、传输模式以及资源限制和特性支持标志。 + +### 2.1 完整结构定义 + +```go +type AgentCapabilities 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"` + SupportedContentTypes []string `json:"supported_content_types"` + SupportedTransportModes []TransportMode `json:"supported_transport_modes"` + + // ── 资源限制 ── + MaxFileSize int64 `json:"max_file_size"` + MaxConcurrentTasks int `json:"max_concurrent_tasks"` + MaxTaskDuration int `json:"max_task_duration_seconds"` + + // ── 特性支持 ── + SupportProgressUpdates bool `json:"support_progress_updates"` + SupportStreaming bool `json:"support_streaming"` + SupportHiL bool `json:"support_hil"` + SupportGroups bool `json:"support_groups"` +} +``` + +### 2.2 字段详解 + +#### 身份信息 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `AgentID` | `string` | Agent 唯一标识符。在整个生态系统中必须全局唯一,通常由系统 ID 和 Agent 名称组合生成。格式示例:`sys_alpha_agent_01`。 | +| `AgentName` | `string` | Agent 的人类可读名称。用于日志和界面展示,如"文本生成助手"。 | +| `SystemID` | `string` | 所属系统 ID。标识该 Agent 归属于哪个系统或组织,用于跨系统路由和权限控制。 | +| `ProtocolVersion` | `string` | 协议版本号。遵循语义化版本规范,格式为 `major.minor.patch`,如 `"1.0.0"`。用于协议兼容性协商。 | + +#### 能力清单 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `SupportedTaskTypes` | `[]SupportedTaskType` | 支持的任务类型列表。每个条目描述一种任务类型的详细能力(见第 3 章)。 | +| `SupportedContentTypes` | `[]string` | 支持的内容类型列表。使用 MIME 类型标识,如 `["text/plain", "image/png", "application/json"]`。 | +| `SupportedTransportModes` | `[]TransportMode` | 支持的传输模式列表。详见 `TransportMode` 枚举定义。 | + +##### TransportMode 枚举 + +```go +type TransportMode string + +const ( + TransportModeCallback TransportMode = "callback" // 异步回调(Webhook) + TransportModePolling TransportMode = "polling" // 异步轮询 + TransportModeSync TransportMode = "sync" // 同步等待 + TransportModeHybrid TransportMode = "hybrid" // 回调 + 轮询兜底 +) +``` + +#### 资源限制 + +| 字段 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `MaxFileSize` | `int64` | 单文件最大字节数。超过此大小的文件应当使用 URL 或流式传输。 | 10MB(10485760) | +| `MaxConcurrentTasks` | `int` | 最大并发任务数。用于流量控制和负载均衡,超过此数的任务请求将被拒绝(错误码 4004)。 | 10 | +| `MaxTaskDuration` | `int` | 单个任务最大持续时间(秒)。超过此时间的任务将被自动终止(错误码 2004)。 | 3600(1 小时) | + +#### 特性支持标志 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `SupportProgressUpdates` | `bool` | 是否支持进度更新推送。若为 `true`,Agent 将在任务执行过程中发送 `TaskEvent` 类型为 `progress` 的更新。 | +| `SupportStreaming` | `bool` | 是否支持流式传输。若为 `true`,Agent 可以接收和发送 `StreamChunk` 格式的流数据。 | +| `SupportHiL` | `bool` | 是否支持人工介入(Human-in-the-Loop)。若为 `true`,Agent 可以在必要时发起 HiL 升级请求(见第 5 章)。 | +| `SupportGroups` | `bool` | 是否支持群组协作。若为 `true`,Agent 可以参与群组任务、投票和消息广播。 | + +### 2.3 默认值工厂函数 + +```go +func DefaultAgentCapabilities() *AgentCapabilities { + return &AgentCapabilities{ + ProtocolVersion: "1.0.0", + MaxFileSize: 10 * 1024 * 1024, // 10MB + MaxConcurrentTasks: 10, + MaxTaskDuration: 3600, + SupportProgressUpdates: true, + SupportStreaming: false, + SupportHiL: false, + SupportGroups: false, + } +} +``` + +### 2.4 序列化示例 + +```json +{ + "agent_id": "agent_text_gen_01", + "agent_name": "文本生成助手", + "system_id": "system_alpha", + "protocol_version": "1.0.0", + "supported_task_types": [ + { + "type": "text_generation", + "name": "文本生成", + "description": "根据输入提示生成自然语言文本", + "estimated_time": "5s-30s", + "input_schema": "...", + "output_schema": "...", + "capabilities": ["creative_writing", "summarization"] + } + ], + "supported_content_types": [ + "text/plain", + "text/markdown", + "application/json" + ], + "supported_transport_modes": [ + "callback", + "polling", + "sync" + ], + "max_file_size": 10485760, + "max_concurrent_tasks": 10, + "max_task_duration_seconds": 3600, + "support_progress_updates": true, + "support_streaming": false, + "support_hil": true, + "support_groups": false +} +``` + +--- + +## 3. SupportedTaskType 结构 + +`SupportedTaskType` 描述 Agent 支持的单一任务类型的详细信息,包括类型标识、名称、描述、预估耗时、输入输出 Schema 以及关联能力标签。 + +### 3.1 结构定义 + +```go +type SupportedTaskType struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + EstimatedTime string `json:"estimated_time"` // 如 "30s-2min" + InputSchema string `json:"input_schema,omitempty"` + OutputSchema string `json:"output_schema,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` +} +``` + +### 3.2 字段说明 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `Type` | `string` | 是 | 任务类型标识符。采用蛇形命名法(snake_case),如 `text_generation`、`image_analysis`、`code_review`。该标识在协议消息的 `TaskType` 字段中使用。 | +| `Name` | `string` | 是 | 任务类型的人类可读名称,如"文本生成"、"图片分析"、"代码审查"。用于界面展示。 | +| `Description` | `string` | 是 | 任务类型的详细描述,说明该任务的输入、输出和适用场景。 | +| `EstimatedTime` | `string` | 是 | 预估完成时间范围。格式为字符串,如 `"5s-30s"`、`"30s-2min"`、`"1min-5min"`。用于任务调度和超时设置参考。 | +| `InputSchema` | `string` | 否 | 输入数据的 JSON Schema。用于验证任务提交时的 payload 格式。格式为 JSON Schema Draft-07 字符串。 | +| `OutputSchema` | `string` | 否 | 输出数据的 JSON Schema。用于解析任务结果时的格式校验。 | +| `Capabilities` | `[]string` | 否 | 该任务类型所依赖的细粒度能力标签列表。用于按能力搜索 Agent。 | + +### 3.3 预定义任务类型 + +| 类型标识 | 名称 | 说明 | +|---------|------|------| +| `text_generation` | 文本生成 | 根据提示生成自然语言文本 | +| `text_analysis` | 文本分析 | 对文本进行分类、情感分析、实体识别等 | +| `summarization` | 摘要生成 | 将长文本总结为简短摘要 | +| `translation` | 翻译 | 在不同语言之间进行翻译 | +| `code_generation` | 代码生成 | 根据需求生成源代码 | +| `code_review` | 代码审查 | 对代码进行审查、发现潜在问题 | +| `image_generation` | 图片生成 | 根据文本描述生成图片 | +| `image_analysis` | 图片分析 | 对图片进行内容识别和分析 | +| `audio_transcription` | 语音转文字 | 将音频转换为文本 | +| `audio_generation` | 语音合成 | 将文本合成为语音 | +| `video_generation` | 视频生成 | 根据描述生成视频内容 | +| `data_analysis` | 数据分析 | 对结构化数据进行分析和可视化 | +| `search_retrieval` | 搜索检索 | 从知识库或互联网检索信息 | +| `planning` | 任务规划 | 将复杂任务分解为可执行的子任务 | +| `reasoning` | 逻辑推理 | 执行多步逻辑推理和决策 | + +### 3.4 示例 + +```go +textGenTaskType := &SupportedTaskType{ + Type: "text_generation", + Name: "文本生成", + Description: "根据输入提示生成自然语言文本,支持创意写作、商业文案、技术文档等", + EstimatedTime: "5s-30s", + InputSchema: `{ + "type": "object", + "properties": { + "prompt": {"type": "string", "description": "输入提示"}, + "max_tokens": {"type": "integer", "minimum": 1, "maximum": 4096}, + "temperature": {"type": "number", "minimum": 0, "maximum": 2} + }, + "required": ["prompt"] + }`, + OutputSchema: `{ + "type": "object", + "properties": { + "text": {"type": "string", "description": "生成的文本"}, + "tokens_used": {"type": "integer", "description": "使用的 token 数量"} + }, + "required": ["text"] + }`, + Capabilities: []string{"creative_writing", "summarization", "paraphrasing"}, +} +``` + +--- + +## 4. 接口分类 + +系统中的标准接口按照功能领域划分为七大分类。每个分类代表协议栈中的一个特定功能维度。 + +### 4.1 分类枚举 + +```go +type InterfaceCategory string + +const ( + CategoryCore InterfaceCategory = "core" // 核心接口 + CategoryP2P InterfaceCategory = "p2p" // P2P 接口 + CategoryGroup InterfaceCategory = "group" // 群组接口 + CategoryHiL InterfaceCategory = "hil" // HiL 接口 + CategoryTransport InterfaceCategory = "transport" // 传输接口 + CategorySecurity InterfaceCategory = "security" // 安全接口 + CategoryManagement InterfaceCategory = "management" // 管理接口 +) +``` + +### 4.2 分类详解 + +| 分类 | 枚举值 | 定位 | 职责范围 | 典型接口 | +|------|--------|------|----------|---------| +| 核心接口 | `core` | 协议基石 | Agent 身份标识、消息收发、能力声明等所有 Agent 最基本的生存能力 | AgentIdentity、MessageHandler、CapabilitiesProvider | +| P2P 接口 | `p2p` | 点对点协作 | Agent 之间的任务提交、处理、回调与状态查询 | P2PTaskHandler、CallbackSender、CallbackReceiver、TaskStatusProvider | +| 群组接口 | `group` | 群组协同 | 多 Agent 组成群组后的任务分发、投票决策与管理 | GroupTaskHandler、GroupManager、VotingHandler | +| HiL 接口 | `hil` | 人工介入 | 将决策升级到人类用户,处理人工审批、驳回与修改 | HILHandler、EscalationHandler | +| 传输接口 | `transport` | 数据传输 | 定义 Agent 间数据传输的网络协议和模式 | HTTPTransport、MultipartTransport、HybridTransport、StreamingTransport | +| 安全接口 | `security` | 安全保障 | 身份认证、消息签名、幂等性保障 | Authenticator、IdempotencyHandler | +| 管理接口 | `management` | 运维管理 | Agent 配置、健康监控、日志查询 | ConfigProvider、HealthReporter、MetricCollector | + +### 4.3 分类依赖关系 + +``` +core ──→ 所有分类的前置依赖,任何 Agent 必须实现 + │ + ├──→ security ──→ transport ──→ p2p ──→ group + │ │ + │ └──→ hil + │ + └──→ management(可选,与所有分类正交) +``` + +- `core` 是基础层,所有其他分类都依赖 `core` 接口。 +- `security` 在 `transport` 之上提供安全层。 +- `p2p` 依赖 `transport` 和 `security`。 +- `group` 和 `hil` 在 `p2p` 基础上构建更高级的交互模式。 +- `management` 与所有分类正交,提供运维支持。 + +--- + +## 5. 合规级别 + +每个标准接口定义了一个合规级别,用于标识该接口在协议实现中的重要性等级。 + +### 5.1 级别枚举 + +```go +const ( + ComplianceBasic Compliance = "basic" // 基本级 - 必须实现 + ComplianceComplete Compliance = "complete" // 完整级 - 推荐实现 + ComplianceAdvanced Compliance = "advanced" // 进阶级 - 可选实现 +) +``` + +### 5.2 级别说明 + +| 级别 | 约束 | 说明 | +|------|------|------| +| **basic(基本级)** | **必须实现** | 所有 Agent 都必须实现此接口,否则无法完成基本的协议握手与通信。这是协议兼容性的最低门槛。未实现 basic 级别接口的 Agent 将被注册中心拒绝注册。 | +| **complete(完整级)** | **推荐实现** | 建议 Agent 实现此接口以获得完整的协议功能体验。未实现时,Agent 仍然可以正常工作,但部分高级交互模式(如异步回调、进度更新)将不可用。 | +| **advanced(进阶级)** | **可选实现** | Agent 可根据自身场景和能力选择实现。这些接口通常对应特定场景的高级功能(如流式传输、群组协作)。未实现不影响基本通信能力。 | + +### 5.3 合规矩阵 + +| 场景 | Basic 接口 | Complete 接口 | Advanced 接口 | +|------|-----------|--------------|--------------| +| 最小可行 Agent | 全部实现 | 可不实现 | 可不实现 | +| 标准 P2P Agent | 全部实现 | 推荐全部实现 | 按需实现 | +| 群组协作 Agent | 全部实现 | 全部实现 | 推荐全部实现 | +| 全功能 Agent | 全部实现 | 全部实现 | 全部实现 | + +### 5.4 合规级别声明 + +Agent 通过 `GetSupportedInterfaces()` 方法声明其实现的接口及对应的合规级别。注册中心可以根据这些信息对 Agent 进行合规性评估: + +```go +type ComplianceEvaluation struct { + AgentID string `json:"agent_id"` + BasicImplemented int `json:"basic_implemented"` // 已实现的 basic 接口数 + BasicRequired int `json:"basic_required"` // 需要实现的 basic 接口数 + CompleteImplemented int `json:"complete_implemented"` // 已实现的 complete 接口数 + CompleteTotal int `json:"complete_total"` // complete 接口总数 + AdvancedImplemented int `json:"advanced_implemented"` // 已实现的 advanced 接口数 + AdvancedTotal int `json:"advanced_total"` // advanced 接口总数 + Compliant bool `json:"compliant"` // 是否合规(所有 basic 接口均已实现) + MissingBasics []string `json:"missing_basics,omitempty"` // 缺失的 basic 接口列表 +} +``` + +--- + +## 6. 标准接口注册表 + +系统预定义了 18 个标准接口,涵盖协议栈的所有功能维度。以下表格详细列出每个接口的名称、分类、合规级别和说明。 + +### 6.1 接口注册表 + +| 序号 | 接口名 | 分类 | 合规级别 | 说明 | +|------|--------|------|---------|------| +| 1 | `AgentIdentity` | `core` | basic | Agent 身份信息。提供 Agent 的 ID、名称、公钥等基础身份数据。是所有交互的起点,注册中心以此识别 Agent。 | +| 2 | `MessageHandler` | `core` | basic | 消息处理。接收并处理来自其他 Agent 或外部调用方的 `UnifiedSynapseMessage` 消息。Agent 之间通信的核心入口。 | +| 3 | `CapabilitiesProvider` | `core` | basic | 能力声明。提供 Agent 的能力描述(AgentCapabilities),供注册中心和其他 Agent 查询。 | +| 4 | `P2PTaskHandler` | `p2p` | basic | P2P 任务处理。接收并处理来自其他 Agent 的点对点任务请求,执行任务并返回结果。 | +| 5 | `CallbackReceiver` | `p2p` | basic | 回调接收端点。对外暴露回调 URL,用于接收其他 Agent 异步执行完成后的任务结果回调。 | +| 6 | `CallbackSender` | `p2p` | complete | 回调发送。在异步任务完成后,主动向请求方注册的回调端点发送结果。 | +| 7 | `TaskStatusProvider` | `p2p` | complete | 任务状态查询。提供任务执行状态的查询接口,支持按任务 ID 查询当前进度和状态。用于轮询模式。 | +| 8 | `GroupTaskHandler` | `group` | advanced | 群组任务处理。接收群组内分发的子任务,执行并将结果报告给群组协调者。 | +| 9 | `GroupManager` | `group` | advanced | 群组管理。创建、解散群组,管理成员加入/离开,维护群组角色和权限。 | +| 10 | `VotingHandler` | `group` | advanced | 投票处理。发起投票、提交投票、统计结果,支持多种共识类型(多数、绝对多数、一致同意、加权、排序)。 | +| 11 | `HILHandler` | `hil` | complete | HiL 决策处理。接收人工介入请求,将决策上下文呈现给人类用户,处理用户的批准/驳回/修改响应。 | +| 12 | `EscalationHandler` | `hil` | advanced | 升级处理。在 HiL 超时或 Level 1 无法解决时,自动将决策升级到更高级别(Level 2/3)。 | +| 13 | `HTTPTransport` | `transport` | basic | HTTP 传输。支持通过 HTTP/HTTPS 协议进行同步请求-响应通信。所有 Agent 必须支持 HTTP 作为基础传输方式。 | +| 14 | `MultipartTransport` | `transport` | complete | Multipart 文件传输。支持通过 multipart/form-data 进行文件上传,适用于大文件传输场景。 | +| 15 | `HybridTransport` | `transport` | complete | 混合传输。同时支持回调(Webhook)和轮询两种异步传输模式,以回调为主、轮询为兜底。 | +| 16 | `StreamingTransport` | `transport` | advanced | 流式传输。支持 Server-Sent Events(SSE)或 WebSocket 流式数据传输,适用于实时性要求高的场景。 | +| 17 | `Authenticator` | `security` | basic | 认证签名。提供消息签名、验签、令牌颁发与验证功能。所有消息必须经过签名认证。 | +| 18 | `IdempotencyHandler` | `security` | basic | 幂等性处理。通过幂等键(Idempotency-Key)确保任务提交和消息发送的幂等性,防止重复处理。 | + +### 6.2 按合规级别汇总 + +| 合规级别 | 接口数量 | 接口列表 | +|---------|---------|---------| +| **basic(必须实现)** | 7 | AgentIdentity、MessageHandler、CapabilitiesProvider、P2PTaskHandler、CallbackReceiver、HTTPTransport、Authenticator、IdempotencyHandler | +| **complete(推荐实现)** | 4 | CallbackSender、TaskStatusProvider、HILHandler、MultipartTransport、HybridTransport | +| **advanced(可选实现)** | 5 | GroupTaskHandler、GroupManager、VotingHandler、EscalationHandler、StreamingTransport | + +> 注:basic 接口共 8 个,complete 共 5 个,advanced 共 5 个,总计 18 个标准接口。 + +### 6.3 按分类汇总 + +| 分类 | 接口数量 | 接口列表 | +|------|---------|---------| +| core | 3 | AgentIdentity、MessageHandler、CapabilitiesProvider | +| p2p | 4 | P2PTaskHandler、CallbackReceiver、CallbackSender、TaskStatusProvider | +| group | 3 | GroupTaskHandler、GroupManager、VotingHandler | +| hil | 2 | HILHandler、EscalationHandler | +| transport | 4 | HTTPTransport、MultipartTransport、HybridTransport、StreamingTransport | +| security | 2 | Authenticator、IdempotencyHandler | +| management | 0 | (由具体实现自行定义,协议层面不强制) | + +### 6.4 接口注册与发现流程 + +``` +请求方 Agent 注册中心 目标 Agent + │ │ │ + │ 1. 注册并上报能力 │ │ + │ ────────────────────────────→ │ │ + │ │ 2. 存储能力信息到目录 │ + │ │ ──────────────── │ + │ │ │ + │ 3. 按能力查询 Agent │ │ + │ ────────────────────────────→ │ │ + │ │ 4. 返回匹配的 Agent 列表 │ + │ ←──────────────────────────── │ │ + │ │ │ + │ 5. P2P 握手 │ │ + │ ──────────────────────────────────────────────────────────→ │ + │ │ 6. 返回支持接口列表 │ + │ ←────────────────────────────────────────────────────────── │ + │ │ │ + │ 7. 按协商接口通信 │ │ + │ ──────────────────────────────────────────────────────────→ │ +``` + +**流程说明**: + +1. Agent 启动时向注册中心注册,并上报 `AgentCapabilities`。 +2. 注册中心将能力信息存入全局目录,验证 basic 接口合规性。 +3. 请求方通过 `FindByCapability` 查询具备特定能力的 Agent。 +4. 注册中心返回匹配的 Agent 端点列表。 +5. 请求方与目标 Agent 直接建立 P2P 连接,进行握手。 +6. 目标 Agent 返回 `GetSupportedInterfaces()` 结果,协商接口兼容性。 +7. 双方按协商结果使用对应接口进行通信。 + +### 6.5 接口版本兼容规则 + +- Agent 必须实现当前协议版本所要求的所有 basic 接口。 +- 接口版本遵循语义化版本规范: + - **MAJOR** 版本不匹配时,接口不兼容,无法通信。 + - **MINOR** 版本不同时,以较低版本为兼容基准。 + - **PATCH** 版本不同时,完全兼容。 +- 新增接口字段必须使用 `omitempty` 标记,确保向后兼容。 + +--- + +> 本文档基于 v0 实现细节文档第 3 章扩展编写,是灵隙协议能力声明系统的完整实现规范。 +> 配套文档:(接口实现指南) diff --git a/docs/v1/components.md b/docs/v1/components.md new file mode 100644 index 0000000..b9b59c6 --- /dev/null +++ b/docs/v1/components.md @@ -0,0 +1,517 @@ +# 灵隙协议组件实现规范:七大组件层 + +> 版本:1.0.0 | 层级:实现层 | 依赖:v1 各核心协议 + +根据 [v0/06-implementation-details.md](file:///e:/code/go/synapse/docs/v0/06-implementation-details.md) 的第1章内容,分别创建以下7个组件的规范(每组包含:定位说明、核心功能表、数据结构定义)。 + +--- + +## 1. 灵脉 Meridian - 寻址与路由层 + +### 定位说明 + +Agent 之间虽有空隙(灵隙),但暗中有能量流动的轨迹(脉)。此层负责维护全局路由表。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| Agent 注册 | `Register(endpoint)` | Agent "通脉"入网 | +| Agent 注销 | `Unregister(agentID)` | Agent "断脉"离场 | +| Agent 查询 | `GetAgent(agentID)` | 获取 Agent 端点信息 | +| Agent 列表 | `ListAgents()` | 列出所有活跃 Agent | +| 心跳保活 | `Heartbeat(agentID)` | 刷新 Agent TTL | +| 按能力查找 | `FindByCapability(capability)` | 查找具备特定能力的 Agent | + +### 数据结构定义 + +```go +// AgentEndpoint - Agent 端点信息 +type AgentEndpoint struct { + AgentID string // Agent 唯一标识 + AgentName string // Agent 名称 + SystemID string // 所属系统 ID + Endpoint string // 回调端点 URL + Version string // Agent 版本 + Capabilities []string // 能力标签列表 + Metadata map[string]interface{} // 扩展元数据 +} + +// RegistryConfig - 注册表配置 +type RegistryConfig struct { + DefaultTTL time.Duration // 默认存活时间(5分钟) +} +``` + +### 拓扑管理 + +```go +// 拓扑类型 +type TopologyType string +const ( + TopologyStar = "star" // 星型拓扑(中心协调) + TopologyMesh = "mesh" // 网状拓扑(全互联) + TopologyRing = "ring" // 环形拓扑(环形传递) + TopologyTree = "tree" // 树形拓扑(层级管理) + TopologyHybrid = "hybrid" // 混合拓扑 +) + +// 拓扑管理接口 +type TopologyManager interface { + CreateTopology(id, name string, topologyType TopologyType) *NetworkTopology + AddNode(topologyID, nodeID string) error + AddEdge(topologyID, from, to string) error + GetNeighbors(topologyID, nodeID string) ([]string, error) +} +``` + +--- + +## 2. 灵玺 Sigil - 身份与凭证层 + +### 定位说明 + +皇帝的玉玺是最高凭证。每个 Agent 的初始身份都需经过"铸玺"过程,每一次交互都要"验玺"。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 创建身份 | `CreateIdentity(agentID, ...)` | "铸玺" - 创建身份凭证 | +| 获取身份 | `GetIdentity(agentID)` | 获取 Agent 身份信息 | +| 撤销身份 | `RevokeIdentity(agentID)` | "废玺" - 撤销身份 | +| 检查撤销 | `IsRevoked(agentID)` | 检查身份是否已撤销 | +| 签名消息 | `SignMessage(msg)` | 对消息签名 | +| 验证签名 | `VerifySignature(msg)` | 验证消息签名 | +| 颁发令牌 | `IssueToken(agentID, ...)` | 颁发认证令牌 | +| 验证令牌 | `ValidateToken(signedToken)` | 验证令牌有效性 | + +### 数据结构定义 + +```go +// AgentIdentity - Agent 身份凭证 +type AgentIdentity struct { + AgentID string // Agent 唯一标识 + AgentName string // Agent 名称 + SystemID string // 所属系统 ID + PublicKey string // 公钥(可选) + PrivateKey string // 私钥(本地保存) + AuthSecret string // HMAC 密钥 + Roles []string // 角色列表 + Permissions []string // 权限列表 + CreatedAt time.Time // 创建时间 + ExpiresAt *time.Time // 过期时间 + RevokedAt *time.Time // 撤销时间 +} + +// AuthToken - 认证令牌 +type AuthToken struct { + TokenID string // 令牌 ID + AgentID string // 所属 Agent + SystemID string // 所属系统 + Roles []string // 角色列表 + IssuedAt time.Time // 颁发时间 + ExpiresAt time.Time // 过期时间 +} +``` + +### 签名算法 + +```go +// 支持的签名算法 +type SignatureAlgorithm string +const ( + AlgHMACSHA256 SignatureAlgorithm = "HMAC-SHA256" // 默认 + AlgHMACSHA512 SignatureAlgorithm = "HMAC-SHA512" // 可选 +) + +// 签名内容格式 +// signature_content = sender_id | receiver_id | timestamp | payload_json +``` + +--- + +## 3. 灵鉴 Speculum - 共识与裁决层 + +### 定位说明 + +古有"以史为鉴",今有"以智为鉴"。此层通过对比推理链的严密性来达成共识,如青天明镜。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 发起投票 | `InitiateVote(...)` | 创建投票议题 | +| 提交投票 | `SubmitVote(voteID, voterID, optionID, reasoning)` | 提交投票 | +| 关闭投票 | `CloseVote(voteID)` | 关闭并统计结果 | +| 获取投票 | `GetVote(voteID)` | 获取投票信息 | +| 仲裁争议 | `Arbitrate(ctx, dispute)` | 仲裁争议 | +| 提交推理链 | `SubmitReasoning(chain)` | 提交推理链到镜面 | +| 比较推理链 | `CompareReasonings(chainIDs)` | 比较推理链严密性 | + +### 共识类型 + +```go +type ConsensusType string +const ( + ConsensusMajority = "majority" // 多数投票(>50%) + ConsensusSupermajority = "supermajority" // 绝对多数(>67%) + ConsensusUnanimity = "unanimity" // 一致同意(100%) + ConsensusWeighted = "weighted" // 加权投票 + ConsensusRanked = "ranked" // 排序投票 +) +``` + +### 数据结构定义 + +```go +// Vote - 投票 +type Vote struct { + VoteID string // 投票 ID + Question string // 投票问题 + Options []VoteOption // 投票选项 + Type ConsensusType // 共识类型 + Initiator string // 发起者 + Participants []string // 参与者列表 + Votes map[string]string // voter_id -> option_id + Weights map[string]float64 // voter_id -> 权重 + Status VoteStatus // 投票状态 + Deadline time.Time // 截止时间 + Result *VoteResult // 投票结果 + Reasoning map[string]string // voter_id -> 推理理由 +} + +// VoteResult - 投票结果 +type VoteResult struct { + Winner string // 获胜选项 + WinOption VoteOption // 获胜选项详情 + VoteCounts map[string]int // 选项 -> 票数 + Passed bool // 是否通过 + Margin float64 // 得票率 +} +``` + +### 推理镜面 + +```go +// ReasoningChain - 推理链 +type ReasoningChain struct { + ChainID string // 推理链 ID + AgentID string // Agent ID + Topic string // 推理主题 + Steps []ReasoningStep // 推理步骤 + Score float64 // 推理得分 + SubmittedAt time.Time // 提交时间 +} + +// ReasoningStep - 推理步骤 +type ReasoningStep struct { + StepID string // 步骤 ID + Description string // 步骤描述 + Evidence []string // 证据列表 + Confidence float64 // 置信度(0-1) + Next []string // 下一步 ID 列表 +} +``` + +--- + +## 4. 灵泊 Harbor - 异步队列与暂存层 + +### 定位说明 + +"泊"为船舶停靠。数据包遇风浪(网络抖动)时暂泊于此,待风平浪静再启航。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 消息入队 | `Enqueue(msg, target, priority)` | 消息入队(支持优先级) | +| 消息出队 | `Dequeue()` | 消息出队 | +| 标记送达 | `MarkDelivered(msgID)` | 标记消息已送达 | +| 标记失败 | `MarkFailed(msgID, err)` | 标记消息失败(触发重试) | +| 缓冲消息 | `BufferMessage(agentID, msg)` | 为离线 Agent 缓冲消息 | +| 清空缓冲 | `FlushBuffer(agentID)` | Agent 上线后清空缓冲 | +| 添加重试 | `AddRetry(msg, target)` | 添加重试条目 | + +### 数据结构定义 + +```go +// QueueConfig - 队列配置 +type QueueConfig struct { + MaxSize int // 最大队列大小(10000) + DefaultMaxRetries int // 默认最大重试次数(5) + DefaultRetryDelay time.Duration // 默认重试延迟(30s) +} + +// 队列消息状态 +type QueuedStatus string +const ( + QueuedPending = "pending" // 待处理 + QueuedProcessing = "processing" // 处理中 + QueuedDelivered = "delivered" // 已送达 + QueuedFailed = "failed" // 失败 + QueuedDead = "dead" // 死信 +) +``` + +### 重试机制 + +```go +// RetryConfig - 重试配置 +type RetryConfig struct { + MaxRetries int // 最大重试次数(5) + BaseDelay time.Duration // 基础延迟(1s) + MaxDelay time.Duration // 最大延迟(5min) + JitterFactor float64 // 抖动因子(0.1) +} + +// 重试延迟算法:指数退避 + 抖动 +// delay = min(BaseDelay * 2^(retryCount-1), MaxDelay) * (1 ± JitterFactor) +``` + +### 缓冲配置 + +```go +// BufferConfig - 缓冲配置 +type BufferConfig struct { + MaxPerAgent int // 每个 Agent 最大缓冲数(1000) + MaxAge time.Duration // 消息最大存活时间(24h) + CleanupInterval time.Duration // 清理间隔(1h) +} +``` + +--- + +## 5. 灵瞰 Panorama - 可观测与度量层 + +### 定位说明 + +居高临下,俯瞰全局。如"天眼"般记录每一帧交互的耗时、置信度与资源消耗。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 开始追踪 | `StartTrace(traceID, name, agentID)` | 创建追踪链路 | +| 开始跨度 | `StartSpan(traceID, parentSpanID, name, agentID)` | 创建子跨度 | +| 结束跨度 | `EndSpan(traceID, spanID, status, err)` | 结束跨度 | +| 添加日志 | `AddSpanLog(traceID, spanID, message, fields)` | 添加跨度日志 | +| 记录指标 | `RecordMetric(name, type, value, labels)` | 记录指标 | +| 递增计数 | `IncrementCounter(name, labels, delta)` | 递增计数器 | +| 设置计量 | `SetGauge(name, labels, value)` | 设置计量值 | +| 健康检查 | `CheckAll(ctx)` | 检查所有组件健康状态 | + +### 数据结构定义 + +```go +// Trace - 追踪链路 +type Trace struct { + TraceID string // 追踪 ID + RootSpan *Span // 根跨度 + Spans map[string]*Span // 跨度集合 + StartTime time.Time // 开始时间 + EndTime *time.Time // 结束时间 + Metadata map[string]interface{} // 元数据 +} + +// Span - 追踪跨度 +type Span struct { + SpanID string // 跨度 ID + TraceID string // 所属追踪 ID + ParentID string // 父跨度 ID + Name string // 跨度名称 + Service string // 服务名 + AgentID string // Agent ID + StartTime time.Time // 开始时间 + EndTime *time.Time // 结束时间 + Duration time.Duration // 持续时间 + Status SpanStatus // 跨度状态 + Tags map[string]string // 标签 + Logs []SpanLog // 日志 + Attributes map[string]interface{} // 属性 +} + +// SpanStatus - 跨度状态 +type SpanStatus string +const ( + SpanStatusPending = "pending" // 待定 + SpanStatusRunning = "running" // 运行中 + SpanStatusSuccess = "success" // 成功 + SpanStatusFailed = "failed" // 失败 + SpanStatusCancelled = "cancelled" // 已取消 +) +``` + +### 指标类型 + +```go +type MetricType string +const ( + MetricCounter = "counter" // 计数器(累加) + MetricGauge = "gauge" // 计量器(瞬时值) + MetricHistogram = "histogram" // 直方图(分布) +) +``` + +### 健康状态 + +```go +type HealthStatus string +const ( + HealthStatusHealthy = "healthy" // 健康 + HealthStatusDegraded = "degraded" // 降级 + HealthStatusUnhealthy = "unhealthy" // 不健康 + HealthStatusUnknown = "unknown" // 未知 +) +``` + +--- + +## 6. 灵衡 Libra - 价值结算与激励层 + +### 定位说明 + +权衡利弊,计量公平。将 Agent 间的"智力贡献"与"资源消耗"通证化,动态定价。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 创建账户 | `CreateAccount(agentID)` | 为 Agent 创建账户 | +| 获取余额 | `GetBalance(agentID)` | 查询账户余额 | +| 转账 | `Transfer(from, to, amount, ...)` | 执行转账 | +| 设置价格 | `SetPrice(taskType, capability, price)` | 设置任务价格 | +| 获取价格 | `GetPrice(taskType)` | 获取任务价格 | +| 计算成本 | `CalculateCost(taskType, complexity, duration)` | 计算任务成本 | +| 结算任务 | `SettleTask(taskID, ...)` | 结算单个任务 | +| 结算群组任务 | `SettleGroupTask(taskID, ...)` | 结算群组任务(分摊费用) | +| 奖励 | `Reward(agentID, amount, reason)` | 发放奖励 | + +### 数据结构定义 + +```go +// Account - 账户 +type Account struct { + AgentID string // Agent ID + Balance map[string]float64 // 余额(currency -> amount) + CreatedAt time.Time // 创建时间 + Metadata map[string]interface{} // 元数据 +} + +// Transaction - 交易记录 +type Transaction struct { + TxID string // 交易 ID + Type TransactionType // 交易类型 + FromAgentID string // 发送方 + ToAgentID string // 接收方 + Amount float64 // 金额 + Currency string // 货币("SYN") + Description string // 描述 + TaskID string // 关联任务 ID + Timestamp time.Time // 时间戳 + Status TransactionStatus // 交易状态 +} + +// TransactionType - 交易类型 +type TransactionType string +const ( + TxTaskExecution = "task_execution" // 任务执行 + TxDataSharing = "data_sharing" // 数据共享 + TxAPIInvocation = "api_invocation" // API 调用 + TxGroupContribution = "group_contribution" // 群组贡献 + TxDeposit = "deposit" // 充值 + TxWithdraw = "withdraw" // 提现 + TxReward = "reward" // 奖励 + TxPenalty = "penalty" // 惩罚 +) +``` + +### 定价规则 + +```go +// 基础价格(默认) +var BasePrice = map[string]float64{ + "text_generation": 1.0, + "image_generation": 5.0, + "video_generation": 20.0, + "audio_generation": 3.0, + "analysis": 2.0, + "translation": 1.5, +} + +// 成本计算公式 +// cost = basePrice * complexityFactor + durationFactor +// complexityFactor = complexity * 0.5 +// durationFactor = duration_seconds * 0.01 +``` + +--- + +## 7. 灵枢 Pivot - 紧急制动与治理层 + +### 定位说明 + +取自《灵枢经》。这是整个协议的"总闸"与"方向盘",不轻易触发,但触发时能以最小代价重置网络状态。 + +### 核心功能 + +| 功能 | 方法 | 说明 | +|-----|------|------| +| 允许请求 | `Allow()` | 检查熔断器是否允许请求 | +| 记录成功 | `RecordSuccess()` | 记录成功(用于恢复) | +| 记录失败 | `RecordFailure()` | 记录失败(触发熔断) | +| 获取状态 | `State()` | 获取熔断器状态 | +| 重置 | `Reset()` | 重置熔断器 | +| 设置治理级别 | `SetLevel(level)` | 设置治理级别 | +| 封禁 Agent | `BanAgent(agentID, reason)` | 封禁 Agent | +| 解封 Agent | `UnbanAgent(agentID)` | 解封 Agent | +| 紧急停止 | `EmergencyStop(reason)` | 一键熔断所有 | +| 恢复正常 | `Resume()` | 恢复正常模式 | +| 应用升级 | `ApplyUpgrade(version)` | 应用协议升级 | +| 回滚升级 | `RollbackUpgrade(version)` | 回滚协议升级 | + +### 数据结构定义 + +```go +// CircuitBreaker - 熔断器 +type CircuitBreaker struct { + name string // 熔断器名称 + state CircuitState // 状态 + failureCount int // 失败计数 + successCount int // 成功计数(半开状态) + threshold int // 失败阈值(5) + timeout time.Duration // 熔断超时(30s) +} + +// CircuitState - 熔断器状态 +type CircuitState string +const ( + CircuitClosed = "closed" // 关闭:正常流通 + CircuitOpen = "open" // 打开:熔断,拒绝请求 + CircuitHalfOpen = "half_open" // 半开:尝试恢复 +) + +// 状态转换规则: +// Closed -> Open :失败次数 >= threshold +// Open -> HalfOpen :超过 timeout 时间 +// HalfOpen -> Closed:连续成功 3 次 +// HalfOpen -> Open :再次失败 +``` + +### 治理级别 + +```go +type GovernanceLevel int +const ( + GovLevelNormal = iota // 正常运行 + GovLevelWatch // 观察模式(记录但不限制) + GovLevelRestricted // 受限模式(限流) + GovLevelEmergency // 紧急模式(仅白名单) + GovLevelShutdown // 停机模式(全部拒绝) +) +``` + +--- + +> 本文档对应 [v0/06-implementation-details.md](file:///e:/code/go/synapse/docs/v0/06-implementation-details.md) 第1章"七大组件层详细规范",是 v1 协议栈组件实现的参考依据。 diff --git a/docs/v1/content-system.md b/docs/v1/content-system.md new file mode 100644 index 0000000..7856816 --- /dev/null +++ b/docs/v1/content-system.md @@ -0,0 +1,427 @@ +# 灵隙协议实现规范:内容类型系统 + +> 版本:1.0.0 | 层级:实现层 + +根据 [v0/06-implementation-details.md](../v0/06-implementation-details.md) 的第2章内容,本文档定义 Synapse 协议内容类型系统的完整规范,涵盖传输模式、内容编码、结构化内容类型定义及 MIME 类型常量。 + +--- + +## 1. 传输模式 + +传输模式决定内容数据在 Agent 之间的传递方式,根据数据大小和实时性需求选择最佳模式。 + +```go +type TransferMode string + +const ( + TransferInline TransferMode = "inline" // 内嵌传输(base64 编码) + TransferURL TransferMode = "url" // URL 引用传输 + TransferStreaming TransferMode = "streaming" // 流式传输 + TransferMultipart TransferMode = "multipart" // Multipart 表单传输 +) +``` + +### 1.1 文件大小阈值 + +| 模式 | 适用范围 | 说明 | +|------|---------|------| +| `inline` | ≤ 1MB | 数据直接嵌入消息体,适用于小文件 | +| `url` | > 1MB | 通过 URL 引用下载,适用于中等文件 | +| `streaming` | > 1MB | 流式分片传输,适用于大文件或实时数据流 | +| `multipart` | 任意大小 | 表单形式上传,适用于文件上传场景 | + +``` +// 文件大小阈值判定逻辑 +// size <= 1MB → inline +// size > 1MB → url 或 streaming +``` + +### 1.2 ContentBase + +所有内容类型共享的基础结构: + +```go +type ContentBase struct { + Type TransferMode `json:"type"` + ContentType string `json:"content_type"` // MIME 类型 + Encoding ContentEncoding `json:"encoding,omitempty"` // 内容编码 + Size int64 `json:"size,omitempty"` // 原始大小(字节) + EncodedSize int64 `json:"encoded_size,omitempty"` // 编码后大小(字节) + Checksum string `json:"checksum,omitempty"` // SHA-256 校验和 + Metadata map[string]any `json:"metadata,omitempty"` // 扩展元数据 +} +``` + +--- + +## 2. 内容编码 + +内容编码定义了原始数据在传输前的编码/压缩方式。 + +```go +type ContentEncoding string + +const ( + EncodingNone ContentEncoding = "none" // 无编码 + EncodingBase64 ContentEncoding = "base64" // Base64 编码 + EncodingGzip ContentEncoding = "gzip" // Gzip 压缩 + EncodingDeflate ContentEncoding = "deflate" // Deflate 压缩 +) +``` + +| 编码 | 适用场景 | 说明 | +|------|---------|------| +| `none` | 纯文本、JSON | 无需额外编码,直接传输原始数据 | +| `base64` | 二进制数据 inline 传输 | 将二进制数据编码为 ASCII 字符串 | +| `gzip` | 大文本、代码、JSON | 高压缩比,适合网络传输 | +| `deflate` | 通用压缩场景 | 基于 DEFLATE 算法,兼容性好 | + +--- + +## 3. 内容类型定义 + +### 3.1 文本内容(TextContent) + +纯文本内容,支持字符集和语言声明。 + +```go +type TextContent struct { + ContentBase + Charset string `json:"charset"` // 字符集(默认 "utf-8") + Language string `json:"language,omitempty"` // 语言代码(如 "zh-CN", "en-US") + Data string `json:"data"` // 文本数据 +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:----:|------| +| `charset` | string | ✓ | 字符集编码,默认 `"utf-8"` | +| `language` | string | - | 语言代码,遵循 BCP 47 标准 | +| `data` | string | ✓ | 文本内容数据 | + +**示例**: + +```json +{ + "type": "inline", + "content_type": "text/plain", + "encoding": "none", + "charset": "utf-8", + "language": "zh-CN", + "data": "你好,世界!" +} +``` + +--- + +### 3.2 JSON 内容(JSONContent) + +结构化 JSON 数据,支持 Schema 验证。 + +```go +type JSONContent struct { + ContentBase + Schema string `json:"schema,omitempty"` // Schema 标识(如 JSON Schema URI) + SchemaURL string `json:"schema_url,omitempty"` // Schema 外部引用 URL + Data any `json:"data"` // JSON 数据(任意合法 JSON 值) +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:----:|------| +| `schema` | string | - | Schema 标识符,用于接收方识别数据结构 | +| `schema_url` | string | - | Schema 的外部访问地址,可在线获取 Schema 定义 | +| `data` | any | ✓ | 任意合法的 JSON 值(object、array、string、number、boolean) | + +**示例**: + +```json +{ + "type": "inline", + "content_type": "application/json", + "encoding": "none", + "schema": "https://schema.synapse.com/v1/agent-info", + "data": { + "agent_id": "agent-001", + "name": "智能助手", + "version": "1.2.0" + } +} +``` + +--- + +### 3.3 代码内容(CodeContent) + +源代码或脚本内容,支持多文件依赖声明。 + +```go +type CodeContent struct { + ContentBase + Language string `json:"language"` // 编程语言(如 "go", "python", "javascript") + Version string `json:"version,omitempty"` // 语言版本(如 "3.11", "1.21") + Dependencies []string `json:"dependencies,omitempty"` // 依赖列表(包名或模块名) + Data string `json:"data"` // 源代码内容 +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:----:|------| +| `language` | string | ✓ | 编程语言标识 | +| `version` | string | - | 语言运行时版本 | +| `dependencies` | array[string] | - | 外部依赖列表,辅助接收方环境准备 | +| `data` | string | ✓ | 源代码文本 | + +**示例**: + +```json +{ + "type": "inline", + "content_type": "text/plain", + "encoding": "none", + "language": "python", + "version": "3.11", + "dependencies": ["requests>=2.28", "pydantic>=2.0"], + "data": "def hello(name: str) -> str:\n return f\"Hello, {name}!\"" +} +``` + +--- + +### 3.4 文件内容(FileContent) + +通用文件内容,支持 inline、url、streaming 三种传输模式。 + +```go +type FileContent struct { + ContentBase + + // 文件标识 + Name string `json:"name"` // 文件名(含扩展名) + Role string `json:"role,omitempty"` // 角色("reference", "result", "attachment") + Description string `json:"description,omitempty"` // 文件描述 + + // Inline 传输(≤ 1MB) + Data string `json:"data,omitempty"` // base64 编码的文件数据 + + // URL 传输(> 1MB) + URL string `json:"url,omitempty"` // 文件下载 URL + URLExpiresAt *Timestamp `json:"url_expires_at,omitempty"` // URL 过期时间 + DownloadMethod string `json:"download_method,omitempty"` // 下载方法(GET / POST / signed_url) + RangeSupported bool `json:"range_supported,omitempty"` // 是否支持 HTTP Range 请求 + + // 流式传输 + StreamingSessionID string `json:"streaming_session_id,omitempty"` // 流式会话 ID + TotalChunks int64 `json:"total_chunks,omitempty"` // 总块数 + ChunkSize int64 `json:"chunk_size,omitempty"` // 每块大小 + + // 元数据 + Width int `json:"width,omitempty"` // 宽度(图片/视频,单位:像素) + Height int `json:"height,omitempty"` // 高度(图片/视频,单位:像素) + Duration float64 `json:"duration,omitempty"` // 时长(音视频,单位:秒) + Resolution string `json:"resolution,omitempty"` // 分辨率描述(如 "1920x1080") + ThumbnailURL string `json:"thumbnail_url,omitempty"` // 缩略图 URL +} +``` + +**角色定义**: + +| 角色 | 说明 | +|------|------| +| `reference` | 参考材料,辅助执行方理解上下文 | +| `result` | 执行结果输出文件 | +| `attachment` | 附件,与任务相关但不参与核心逻辑的文件 | + +**传输模式选择**: + +| 模式 | 条件 | 填充字段 | +|------|------|---------| +| `inline` | 文件大小 ≤ 1MB | `data`(base64 编码) | +| `url` | 文件大小 > 1MB | `url` + `url_expires_at` + `download_method` + `range_supported` | +| `streaming` | 大文件或实时流 | `streaming_session_id` + `total_chunks` + `chunk_size` | + +**示例(inline 传输)**: + +```json +{ + "type": "inline", + "content_type": "image/png", + "encoding": "base64", + "size": 524288, + "name": "screenshot.png", + "role": "reference", + "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" +} +``` + +**示例(url 传输)**: + +```json +{ + "type": "url", + "content_type": "video/mp4", + "encoding": "none", + "size": 15728640, + "name": "demo.mp4", + "role": "result", + "url": "https://storage.synapse.com/files/demo.mp4", + "url_expires_at": "2026-06-30T23:59:59Z", + "download_method": "GET", + "range_supported": true, + "width": 1920, + "height": 1080, + "duration": 30.5, + "resolution": "1920x1080" +} +``` + +--- + +### 3.5 内容包(ContentBundle + ContentBundleItem) + +用于传输多个相关联的内容对象,支持分组和角色标注。 + +```go +type ContentBundle struct { + ID string `json:"id"` // 包唯一标识 + Name string `json:"name,omitempty"` // 包名称 + Description string `json:"description,omitempty"` // 包描述 + Items []ContentBundleItem `json:"items"` // 内容项列表 + TotalSize int64 `json:"total_size,omitempty"` // 总大小(字节) + CreatedAt Timestamp `json:"created_at"` // 创建时间 +} + +type ContentBundleItem struct { + ID string `json:"id"` // 项唯一标识 + Role string `json:"role"` // 角色("main", "reference", "attachment", "thumbnail") + Content any `json:"content"` // 内容对象(TextContent / JSONContent / CodeContent / FileContent 之一) +} +``` + +**项角色定义**: + +| 角色 | 说明 | +|------|------| +| `main` | 主要内容项,通常为任务的主输出 | +| `reference` | 参考材料项,辅助理解上下文 | +| `attachment` | 附件项,补充信息 | +| `thumbnail` | 缩略图项,用于预览展示 | + +**示例**: + +```json +{ + "id": "bundle-001", + "name": "分析报告", + "description": "包含分析结果、图表和原始数据", + "items": [ + { + "id": "item-001", + "role": "main", + "content": { + "type": "inline", + "content_type": "text/markdown", + "encoding": "none", + "charset": "utf-8", + "language": "zh-CN", + "data": "# 分析报告\n\n本次分析共发现 **3** 个关键问题..." + } + }, + { + "id": "item-002", + "role": "reference", + "content": { + "type": "url", + "content_type": "image/png", + "encoding": "none", + "size": 204800, + "name": "chart.png", + "role": "reference", + "url": "https://storage.synapse.com/reports/chart.png" + } + }, + { + "id": "item-003", + "role": "attachment", + "content": { + "type": "url", + "content_type": "application/json", + "encoding": "gzip", + "size": 1048576, + "name": "raw_data.json.gz", + "role": "attachment", + "url": "https://storage.synapse.com/reports/raw_data.json.gz" + } + } + ], + "total_size": 1253376, + "created_at": "2026-06-28T10:00:00Z" +} +``` + +--- + +## 4. MIME 类型常量表 + +```go +const ( + // 文本类型 + MIMETextPlain = "text/plain" // 纯文本 + MIMETextMarkdown = "text/markdown" // Markdown 文档 + MIMETextHTML = "text/html" // HTML 文档 + MIMETextCSV = "text/csv" // CSV 表格数据 + MIMETextCSS = "text/css" // 样式表 + MIMETextXML = "text/xml" // XML 文本 + + // 应用程序类型 + MIMEJSON = "application/json" // JSON 数据 + MIMEXML = "application/xml" // XML 数据 + MIMEPDF = "application/pdf" // PDF 文档 + MIMEZip = "application/zip" // ZIP 压缩包 + MIMEGZip = "application/gzip" // GZip 压缩文件 + MIMEOctetStream = "application/octet-stream" // 二进制流(通用) + MIMEMultipartForm = "multipart/form-data" // Multipart 表单数据 + + // 图片类型 + MIMEImageJPEG = "image/jpeg" // JPEG 图片 + MIMEImagePNG = "image/png" // PNG 图片 + MIMEImageWebP = "image/webp" // WebP 图片 + MIMEImageGIF = "image/gif" // GIF 图片 + MIMEImageSVG = "image/svg+xml" // SVG 矢量图 + MIMEImageBMP = "image/bmp" // BMP 位图 + MIMEImageTIFF = "image/tiff" // TIFF 图片 + MIMEImageICO = "image/x-icon" // 图标文件 + + // 视频类型 + MIMEVideoMP4 = "video/mp4" // MP4 视频 + MIMEVideoWebM = "video/webm" // WebM 视频 + MIMEVideoAVI = "video/x-msvideo" // AVI 视频 + MIMEVideoMOV = "video/quicktime" // MOV 视频 + MIMEVideoMKV = "video/x-matroska" // MKV 视频 + + // 音频类型 + MIMEAudioMP3 = "audio/mpeg" // MP3 音频 + MIMEAudioWAV = "audio/wav" // WAV 音频 + MIMEAudioAAC = "audio/aac" // AAC 音频 + MIMEAudioOGG = "audio/ogg" // OGG 音频 + MIMEAudioFLAC = "audio/flac" // FLAC 无损音频 + MIMEAudioWebM = "audio/webm" // WebM 音频 +) +``` + +### 分类汇总 + +| 类别 | 数量 | 常量列表 | +|------|:----:|---------| +| 文本类型 | 6 | `MIMETextPlain`, `MIMETextMarkdown`, `MIMETextHTML`, `MIMETextCSV`, `MIMETextCSS`, `MIMETextXML` | +| 应用程序类型 | 7 | `MIMEJSON`, `MIMEXML`, `MIMEPDF`, `MIMEZip`, `MIMEGZip`, `MIMEOctetStream`, `MIMEMultipartForm` | +| 图片类型 | 8 | `MIMEImageJPEG`, `MIMEImagePNG`, `MIMEImageWebP`, `MIMEImageGIF`, `MIMEImageSVG`, `MIMEImageBMP`, `MIMEImageTIFF`, `MIMEImageICO` | +| 视频类型 | 5 | `MIMEVideoMP4`, `MIMEVideoWebM`, `MIMEVideoAVI`, `MIMEVideoMOV`, `MIMEVideoMKV` | +| 音频类型 | 6 | `MIMEAudioMP3`, `MIMEAudioWAV`, `MIMEAudioAAC`, `MIMEAudioOGG`, `MIMEAudioFLAC`, `MIMEAudioWebM` | + +--- + +**文档结束** + +> 本文档对应 v0/06-implementation-details.md 第2章「内容类型系统」, +> 是 Synapse 协议内容子系统在 v1 层级中的独立规范。 diff --git a/docs/v1/error-codes.md b/docs/v1/error-codes.md new file mode 100644 index 0000000..c5ab30d --- /dev/null +++ b/docs/v1/error-codes.md @@ -0,0 +1,408 @@ +# 灵隙协议实现规范:数字分段错误码系统 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 1. 错误码分段总览 + +灵隙协议将错误码按照功能领域划分为 9 个分段,每个分段占用 1000 个编码空间(即一个十进制千位段),便于程序化路由、国际化映射和监控告警分类。 + +| 分段范围 | 类别 | 常量标识 | 说明 | +|---------|------|---------|------| +| 1000-1999 | SYSTEM_ERROR | 系统错误 | 服务端基础设施类错误,包括内部错误、维护状态等 | +| 2000-2999 | TASK_ERROR | 任务错误 | 任务生命周期相关错误,包括查找、取消、超时等 | +| 3000-3999 | INFO_ERROR | 信息错误 | 请求信息/数据类错误,包括字段缺失、格式错误等 | +| 4000-4999 | CAPABILITY_ERROR | 能力错误 | Agent 能力相关错误,包括不支持的任务类型、资源繁忙等 | +| 5000-5999 | SECURITY_ERROR | 安全错误 | 认证与授权类错误,包括签名验证、权限不足等 | +| 6000-6999 | COMMUNICATION_ERROR | 通信错误 | 网络通信类错误,包括端点不可达、回调失败等 | +| 7000-7999 | GROUP_ERROR | 群组错误 | 群组协作类错误,包括成员管理、共识失败等 | +| 8000-8999 | HIL_ERROR | HiL 错误 | 人工介入(Human-in-the-Loop)相关错误 | +| 9000-9999 | VALIDATION_ERROR | 验证错误 | 通用参数验证错误 | + +分类映射逻辑由 `SynapseErrorCode.Category()` 方法实现,通过错误码范围自动推导所属类别。 + +--- + +## 2. 系统错误(1000-1999) + +系统错误表示服务端基础设施层面的异常,通常需要运维人员介入处理。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 1000 | ErrSystemError | 通用系统错误,无法归类的系统级异常 | +| 1001 | ErrInternalError | 内部错误,服务端内部处理逻辑异常 | +| 1002 | ErrMaintenance | 系统维护中,服务暂不可用 | + +**适用场景**: +- 数据库连接失败、磁盘写入异常等基础设施故障 +- 未捕获的运行时异常 +- 计划内维护窗口或突发性服务不可用 + +--- + +## 3. 任务错误(2000-2999) + +任务错误表示与任务生命周期管理相关的异常,涵盖任务的创建、执行、完成和取消等阶段。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 2001 | ErrTaskNotFound | 任务不存在,指定的任务 ID 无效或已过期 | +| 2002 | ErrTaskAlreadyCompleted | 任务已完成,无法对已完成的任务执行变更操作 | +| 2003 | ErrTaskCancelled | 任务已取消,任务已被发起方或系统取消 | +| 2004 | ErrTaskTimeout | 任务超时,执行时长超过最大允许时间 | +| 2005 | ErrTaskInvalidParams | 任务参数无效,提交任务时提供的参数不符合要求 | + +**适用场景**: +- 查询不存在的任务状态 +- 对已结束的任务发起取消或修改 +- 任务执行超过最大持续时间阈值 +- 提交任务时缺少必要参数或参数格式错误 + +--- + +## 4. 信息错误(3000-3999) + +信息错误表示请求中携带的信息不完整或不符合规范,通常由调用方修正后重试。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 3001 | ErrRequiredFieldMissing | 必填字段缺失,请求中缺少协议要求的必需字段 | +| 3002 | ErrInvalidFieldFormat | 字段格式无效,字段值不符合预期的格式规范 | +| 3003 | ErrInfoNotProvided | 信息未提供,需要的信息未被提供 | + +**适用场景**: +- 消息中缺少 `sender`、`receiver` 等必填字段 +- 邮箱、URL、时间戳等字段格式校验失败 +- 上下文信息不足以完成任务处理 + +--- + +## 5. 能力错误(4000-4999) + +能力错误表示接收方 Agent 无法处理请求所要求的能力或资源不可用。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 4001 | ErrUnsupportedTaskType | 不支持的任务类型,Agent 未声明支持该类型的任务 | +| 4002 | ErrCapabilityLacking | 能力不足,Agent 不具备执行请求所需的某项能力 | +| 4003 | ErrResourceBusy | 资源繁忙,Agent 当前正在处理其他任务,无法接受新请求 | +| 4004 | ErrQuotaExceeded | 配额超限,请求超出 Agent 设定的资源配额限制 | + +**适用场景**: +- 向只支持文本生成的 Agent 提交图像生成任务 +- 调用未在 `AgentCapabilities` 中声明的能力 +- Agent 已达到 `MaxConcurrentTasks` 上限 +- 文件大小超过 `MaxFileSize` 或任务时长超过 `MaxTaskDuration` + +--- + +## 6. 安全错误(5000-5999) + +安全错误表示认证、授权或安全策略相关的异常,涉及身份验证和访问控制。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 5001 | ErrAuthFailed | 认证失败,身份凭证验证未通过 | +| 5002 | ErrSignatureInvalid | 签名无效,消息签名验证失败,内容可能被篡改 | +| 5003 | ErrPermissionDenied | 权限不足,当前身份不具备执行操作所需的权限 | +| 5004 | ErrPolicyViolation | 策略违规,请求违反系统安全策略或合规要求 | + +**适用场景**: +- 令牌过期、密钥错误等认证失败 +- HMAC 签名不匹配或签名算法不一致 +- 角色(Role)不具备操作所需的权限(Permission) +- 违反数据访问策略、IP 白名单限制等安全规则 + +--- + +## 7. 通信错误(6000-6999) + +通信错误表示网络传输层面的异常,通常涉及 Agent 之间的消息投递和回调机制。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 6001 | ErrEndpointUnreachable | 端点不可达,目标 Agent 的回调端点无法连接 | +| 6002 | ErrCallbackFailed | 回调失败,回调请求发送成功但返回错误响应 | +| 6003 | ErrMessageFormatInvalid | 消息格式无效,接收到的消息不符合协议格式要求 | +| 6004 | ErrTTLExpired | TTL 过期,消息的存活时间已过期,已被丢弃 | + +**适用场景**: +- 目标 Agent 离线或网络不可达 +- 回调端点返回非 2xx 状态码 +- 消息结构解析失败、JSON 格式错误 +- 消息在灵泊(Harbor)队列中停留超过 TTL + +--- + +## 8. 群组错误(7000-7999) + +群组错误表示群组协作场景中的异常,涵盖群组生命周期管理和成员操作。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 7001 | ErrGroupNotFound | 群组不存在,指定的群组 ID 无效或已解散 | +| 7002 | ErrGroupNotActive | 群组未激活,群组当前处于非活跃状态 | +| 7003 | ErrMemberNotFound | 成员不存在,指定的成员不在群组中 | +| 7004 | ErrNotGroupMember | 不是群组成员,当前 Agent 不属于该群组 | +| 7005 | ErrConsensusNotReached | 未达成共识,群组投票未达到共识阈值 | +| 7006 | ErrRoleNotFound | 角色不存在,指定的角色在群组中未定义 | +| 7007 | ErrAlreadyMember | 已是成员,Agent 已经加入该群组 | +| 7008 | ErrGroupFull | 群组已满,群组成员数量已达上限 | + +**适用场景**: +- 查询或操作不存在的群组 +- 向未激活的群组发送消息 +- 查询群组中不存在的成员信息 +- 非群组成员尝试执行仅成员可用的操作 +- 投票结果未达到 `ConsensusMajority` 或 `ConsensusSupermajority` 阈值 +- 重复加入同一群组 +- 群组人数超过 `MaxMembers` 限制 + +--- + +## 9. HiL 错误(8000-8999) + +HiL(Human-in-the-Loop)错误表示人工介入流程中的异常情况,涵盖人工决策超时、拒绝和升级级别错误。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 8001 | ErrHumanTimeout | 人工决策超时,在规定时间内未收到人工响应 | +| 8002 | ErrHumanDeclined | 人工拒绝,人工决策者明确拒绝了请求 | +| 8003 | ErrEscalationLevel | 升级级别错误,指定的升级级别无效或不适用 | + +**适用场景**: +- Level 1/2/3 超时未响应(超时时间依据级别分别为 24h/12h/72h) +- 人工批复结果为 `rejected` +- 升级级别超出定义范围(1-3)或与升级原因不匹配 + +**级别超时默认值**: + +| 级别 | 默认超时 | 紧急超时 | +|-----|---------|---------| +| Level 1 | 24 小时 | — | +| Level 2 | 12 小时 | 2 小时 | +| Level 3 | 72 小时 | — | + +--- + +## 10. 验证错误(9000-9999) + +验证错误表示通用参数验证未通过,适用于多个层级的输入校验。 + +| 错误码 | 常量名称 | 说明 | +|-------|---------|------| +| 9001 | ErrInvalidParams | 参数无效,通用参数校验失败 | + +**适用场景**: +- 请求参数类型不匹配 +- 枚举值超出允许范围 +- 数值超出允许的上下界 +- 字符串长度超出限制 + +--- + +## 11. 错误响应结构 + +灵隙协议定义了两层错误响应结构:`SynapseError` 承载单一错误的完整信息,`ErrorResponse` 作为统一的 API 响应信封。 + +### 11.1 SynapseError + +`SynapseError` 是协议中所有错误的标准载体,包含错误码、类别、人类可读消息以及扩展信息。 + +```go +type SynapseError struct { + Code SynapseErrorCode `json:"code"` // 数字错误码,如 1000、2001 + Category string `json:"category"` // 错误类别,如 "SYSTEM_ERROR"、"TASK_ERROR" + Message string `json:"message"` // 错误描述,人类可读的英文消息 + Details any `json:"details,omitempty"` // 错误详情,可选,可包含结构化补充信息 + RetryAfter *int `json:"retry_after,omitempty"` // 建议重试等待时间(秒),可选 + Timestamp Timestamp `json:"timestamp"` // 错误发生时间戳 +} +``` + +**字段说明**: + +| 字段 | 类型 | 必填 | 说明 | +|-----|------|-----|------| +| `code` | SynapseErrorCode | 是 | 数字错误码,属于预定义的 9 个分段之一 | +| `category` | string | 是 | 由 `SynapseErrorCode.Category()` 自动推导的类别常量 | +| `message` | string | 是 | 面向开发者的英文错误描述,便于日志和调试 | +| `details` | any | 否 | 可选的扩展信息,可包含字段级别的验证错误等结构化数据 | +| `retry_after` | *int | 否 | 对可重试错误,建议调用方等待的秒数(如资源繁忙时) | +| `timestamp` | Timestamp | 是 | 错误生成的时间戳,用于追踪和时序分析 | + +### 11.2 ErrorResponse + +`ErrorResponse` 是所有 API 响应的统一错误信封,通过 `success` 字段标识请求处理状态。 + +```go +type ErrorResponse struct { + Success bool `json:"success"` // 固定为 false + Error *SynapseError `json:"error"` // 错误详情 +} +``` + +**JSON 序列化示例**: + +```json +{ + "success": false, + "error": { + "code": 4003, + "category": "CAPABILITY_ERROR", + "message": "Agent is currently busy processing other tasks", + "details": { + "current_tasks": 5, + "max_concurrent": 5 + }, + "retry_after": 30, + "timestamp": "2026-06-28T10:30:00Z" + } +} +``` + +### 11.3 ToMap() 方法 + +`SynapseError` 提供 `ToMap() map[string]any` 方法,将错误结构转换为扁平化的 map 结构,便于在不同上下文中进行 JSON 序列化或格式转换。 + +**转换规则**: + +| 源字段 | map 中的 key | 条件 | +|-------|-------------|------| +| `Code` | `"code"` | 始终包含 | +| `Category` | `"category"` | 始终包含 | +| `Message` | `"message"` | 始终包含 | +| `Timestamp` | `"timestamp"` | 始终包含 | +| `Details` | `"details"` | 仅当 `Details != nil` 时包含 | +| `RetryAfter` | `"retry_after"` | 仅当 `RetryAfter != nil` 时包含 | + +**方法签名**: + +```go +func (e *SynapseError) ToMap() map[string]any +``` + +**Go 实现**: + +```go +func (e *SynapseError) ToMap() map[string]any { + m := map[string]any{ + "code": e.Code, + "category": e.Category, + "message": e.Message, + "timestamp": e.Timestamp, + } + if e.Details != nil { + m["details"] = e.Details + } + if e.RetryAfter != nil { + m["retry_after"] = *e.RetryAfter + } + return m +} +``` + +### 11.4 工厂函数 + +协议提供两个工厂函数用于构造 `SynapseError`: + +| 函数 | 说明 | +|-----|------| +| `NewSynapseError(code, message, details)` | 创建标准错误,自动填充 `Category` 和 `Timestamp` | +| `NewSynapseErrorWithRetry(code, message, details, retryAfter)` | 创建带重试提示的错误 | + +### 11.5 预定义错误实例 + +`types/errors.go` 中定义了一系列预构建的错误实例,供实现直接引用: + +| 变量名 | 对应错误码 | 默认消息 | +|-------|-----------|---------| +| `ErrTaskNotFoundError` | 2001 | "Task not found" | +| `ErrTaskAlreadyCompletedError` | 2002 | "Task has already been completed" | +| `ErrTaskCancelledError` | 2003 | "Task has been cancelled" | +| `ErrTaskTimeoutError` | 2004 | "Task execution timed out" | +| `ErrRequiredFieldMissingError` | 3001 | "Required field is missing" | +| `ErrUnsupportedTaskTypeError` | 4001 | "Task type is not supported by this agent" | +| `ErrCapabilityLackingError` | 4002 | "Agent lacks the required capability" | +| `ErrResourceBusyError` | 4003 | "Agent is currently busy processing other tasks" | +| `ErrAuthFailedError` | 5001 | "Authentication failed" | +| `ErrSignatureInvalidError` | 5002 | "Message signature verification failed" | +| `ErrPermissionDeniedError` | 5003 | "Permission denied" | +| `ErrEndpointUnreachableError` | 6001 | "Callback endpoint is unreachable" | +| `ErrMessageFormatInvalidError` | 6003 | "Message format is invalid" | +| `ErrGroupNotFoundError` | 7001 | "Group not found" | +| `ErrNotGroupMemberError` | 7004 | "Agent is not a member of this group" | +| `ErrGroupNotActiveError` | 7002 | "Group is not active" | +| `ErrAlreadyMemberError` | 7007 | "Agent is already a member of this group" | +| `ErrGroupFullError` | 7008 | "Group has reached maximum capacity" | +| `ErrMemberNotFoundError` | 7003 | "Member not found in group" | +| `ErrInvalidParamsError` | 9001 | "Invalid parameters" | +| `ErrHumanTimeoutError` | 8001 | "Human decision timed out" | + +--- + +## 附录:错误码常量定义 + +```go +type SynapseErrorCode int + +// 系统错误 +const ErrSystemError SynapseErrorCode = 1000 +const ErrInternalError SynapseErrorCode = 1001 +const ErrMaintenance SynapseErrorCode = 1002 + +// 任务错误 +const ErrTaskNotFound SynapseErrorCode = 2001 +const ErrTaskAlreadyCompleted SynapseErrorCode = 2002 +const ErrTaskCancelled SynapseErrorCode = 2003 +const ErrTaskTimeout SynapseErrorCode = 2004 +const ErrTaskInvalidParams SynapseErrorCode = 2005 + +// 信息错误 +const ErrRequiredFieldMissing SynapseErrorCode = 3001 +const ErrInvalidFieldFormat SynapseErrorCode = 3002 +const ErrInfoNotProvided SynapseErrorCode = 3003 + +// 能力错误 +const ErrUnsupportedTaskType SynapseErrorCode = 4001 +const ErrCapabilityLacking SynapseErrorCode = 4002 +const ErrResourceBusy SynapseErrorCode = 4003 +const ErrQuotaExceeded SynapseErrorCode = 4004 + +// 安全错误 +const ErrAuthFailed SynapseErrorCode = 5001 +const ErrSignatureInvalid SynapseErrorCode = 5002 +const ErrPermissionDenied SynapseErrorCode = 5003 +const ErrPolicyViolation SynapseErrorCode = 5004 + +// 通信错误 +const ErrEndpointUnreachable SynapseErrorCode = 6001 +const ErrCallbackFailed SynapseErrorCode = 6002 +const ErrMessageFormatInvalid SynapseErrorCode = 6003 +const ErrTTLExpired SynapseErrorCode = 6004 + +// 群组错误 +const ErrGroupNotFound SynapseErrorCode = 7001 +const ErrGroupNotActive SynapseErrorCode = 7002 +const ErrMemberNotFound SynapseErrorCode = 7003 +const ErrNotGroupMember SynapseErrorCode = 7004 +const ErrConsensusNotReached SynapseErrorCode = 7005 +const ErrRoleNotFound SynapseErrorCode = 7006 +const ErrAlreadyMember SynapseErrorCode = 7007 +const ErrGroupFull SynapseErrorCode = 7008 + +// HiL 错误 +const ErrHumanTimeout SynapseErrorCode = 8001 +const ErrHumanDeclined SynapseErrorCode = 8002 +const ErrEscalationLevel SynapseErrorCode = 8003 + +// 验证错误 +const ErrInvalidParams SynapseErrorCode = 9001 +``` + +--- + +> 本文档对应源码:[types/errors.go](file:///e:/code/go/synapse/types/errors.go) +> +> 参考文档:[06-implementation-details.md](file:///e:/code/go/synapse/docs/v0/06-implementation-details.md) 第 6 章 diff --git a/docs/v1/grpc-transport.md b/docs/v1/grpc-transport.md new file mode 100644 index 0000000..9db53b7 --- /dev/null +++ b/docs/v1/grpc-transport.md @@ -0,0 +1,521 @@ +# 灵隙协议实现规范:gRPC 传输协议 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 1. Proto 定义位置说明 + +Synapse 协议的 gRPC 服务定义统一存放于以下位置: + +``` +proto/ +├── synapse.proto # 主 proto 文件,包含所有服务与消息定义 +├── synapse_common.proto # 公共消息类型(可选拆分) +├── google/ +│ └── api/ +│ ├── annotations.proto # Google HTTP 注解依赖 +│ └── http.proto +└── protoc-gen-openapiv2/ + └── options/ + └── annotations.proto # OpenAPI v2 注解依赖(可选) +``` + +主定义文件 [`synapse.proto`](file:///proto/synapse.proto) 包含: + +- 5 个 gRPC 服务(TaskService、SynapseMessageService、CapabilitiesService、GroupService、HealthService) +- 20 个 RPC 方法 +- 对应的请求/响应消息类型 +- `google.api.http` 注解,用于通过 gRPC Gateway 生成 HTTP REST 端点 + +通过 `buf` 或 `protoc` 工具链编译生成: + +| 生成产物 | 说明 | +|---------|------| +| `synapse.pb.go` | gRPC 服务端/客户端桩代码 | +| `synapse.pb.gw.go` | gRPC Gateway 反向代理代码 | +| `synapse.swagger.json` | OpenAPI v2 规范文档(可选) | + +--- + +## 2. 服务定义 + +### 2.1 TaskService(任务服务) + +任务服务是协议核心,负责任务的提交、查询、取消与状态流式推送。 + +```protobuf +service TaskService { + // 提交任务(JSON payload) + rpc SubmitTask(SubmitTaskRequest) returns (SubmitTaskResponse); + + // 提交任务(含文件附件,Multipart) + rpc SubmitTaskWithFiles(SubmitTaskWithFilesRequest) returns (SubmitTaskResponse); + + // 获取任务完整信息 + rpc GetTask(GetTaskRequest) returns (TaskStatusResponse); + + // 获取任务状态(轻量查询) + rpc GetTaskStatus(GetTaskRequest) returns (TaskStatusResponse); + + // 获取任务列表(支持分页和过滤) + rpc ListTasks(ListTasksRequest) returns (ListTasksResponse); + + // 取消任务 + rpc CancelTask(CancelTaskRequest) returns (CancelTaskResponse); + + // 流式监听任务状态变化(双向流) + rpc StreamTaskStatus(stream TaskStatusStreamRequest) returns (stream TaskStatusUpdate); +} +``` + +**RPC 方法列表:** + +| # | 方法名 | 请求类型 | 响应类型 | 流式 | +|---|--------|---------|---------|------| +| 1 | `SubmitTask` | `SubmitTaskRequest` | `SubmitTaskResponse` | 否 | +| 2 | `SubmitTaskWithFiles` | `SubmitTaskWithFilesRequest` | `SubmitTaskResponse` | 否 | +| 3 | `GetTask` | `GetTaskRequest` | `TaskStatusResponse` | 否 | +| 4 | `GetTaskStatus` | `GetTaskRequest` | `TaskStatusResponse` | 否 | +| 5 | `ListTasks` | `ListTasksRequest` | `ListTasksResponse` | 否 | +| 6 | `CancelTask` | `CancelTaskRequest` | `CancelTaskResponse` | 否 | +| 7 | `StreamTaskStatus` | `stream TaskStatusStreamRequest` | `stream TaskStatusUpdate` | 双向流 | + +### 2.2 SynapseMessageService(消息服务) + +消息服务负责 Agent 间的异步消息传递、流式对话以及回调推送。 + +```protobuf +service SynapseMessageService { + // 发送单条消息 + rpc SendMessage(SendMessageRequest) returns (SendMessageResponse); + + // 流式消息交换(双向流,适用于多轮对话) + rpc StreamMessages(stream SynapseMessage) returns (stream SynapseMessage); + + // 接收服务端推送的回调消息(服务器推送流) + rpc ReceiveCallback(google.protobuf.Empty) returns (stream SynapseMessage); +} +``` + +**RPC 方法列表:** + +| # | 方法名 | 请求类型 | 响应类型 | 流式 | +|---|--------|---------|---------|------| +| 8 | `SendMessage` | `SendMessageRequest` | `SendMessageResponse` | 否 | +| 9 | `StreamMessages` | `stream SynapseMessage` | `stream SynapseMessage` | 双向流 | +| 10 | `ReceiveCallback` | `google.protobuf.Empty` | `stream SynapseMessage` | 服务端流 | + +### 2.3 CapabilitiesService(能力服务) + +能力服务提供 Agent 能力查询与发现机制。 + +```protobuf +service CapabilitiesService { + // 获取当前 Agent 自身的能力声明 + rpc GetCapabilities(google.protobuf.Empty) returns (CapabilitiesResponse); + + // 查询指定 Agent 的能力 + rpc QueryAgent(QueryAgentRequest) returns (CapabilitiesResponse); + + // 列出网络中所有活跃 Agent + rpc ListAgents(google.protobuf.Empty) returns (ListAgentsResponse); +} +``` + +**RPC 方法列表:** + +| # | 方法名 | 请求类型 | 响应类型 | 流式 | +|---|--------|---------|---------|------| +| 11 | `GetCapabilities` | `google.protobuf.Empty` | `CapabilitiesResponse` | 否 | +| 12 | `QueryAgent` | `QueryAgentRequest` | `CapabilitiesResponse` | 否 | +| 13 | `ListAgents` | `google.protobuf.Empty` | `ListAgentsResponse` | 否 | + +### 2.4 GroupService(群组服务) + +群组服务支持 Agent 群的创建、成员管理与群内消息流。 + +```protobuf +service GroupService { + // 创建群组 + rpc CreateGroup(CreateGroupRequest) returns (CreateGroupResponse); + + // 加入群组 + rpc JoinGroup(JoinGroupRequest) returns (JoinGroupResponse); + + // 离开群组 + rpc LeaveGroup(LeaveGroupRequest) returns (LeaveGroupResponse); + + // 解散群组(仅群主/管理员) + rpc DisbandGroup(DisbandGroupRequest) returns (DisbandGroupResponse); + + // 列出群组成员 + rpc ListGroupMembers(ListGroupMembersRequest) returns (ListGroupMembersResponse); + + // 流式收发群组消息(双向流) + rpc StreamGroupMessages(stream SynapseMessage) returns (stream SynapseMessage); +} +``` + +**RPC 方法列表:** + +| # | 方法名 | 请求类型 | 响应类型 | 流式 | +|---|--------|---------|---------|------| +| 14 | `CreateGroup` | `CreateGroupRequest` | `CreateGroupResponse` | 否 | +| 15 | `JoinGroup` | `JoinGroupRequest` | `JoinGroupResponse` | 否 | +| 16 | `LeaveGroup` | `LeaveGroupRequest` | `LeaveGroupResponse` | 否 | +| 17 | `DisbandGroup` | `DisbandGroupRequest` | `DisbandGroupResponse` | 否 | +| 18 | `ListGroupMembers` | `ListGroupMembersRequest` | `ListGroupMembersResponse` | 否 | +| 19 | `StreamGroupMessages` | `stream SynapseMessage` | `stream SynapseMessage` | 双向流 | + +### 2.5 HealthService(健康服务) + +健康服务提供存活探针与健康状态检查。 + +```protobuf +service HealthService { + // 健康检查 + rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse); +} +``` + +**RPC 方法列表:** + +| # | 方法名 | 请求类型 | 响应类型 | 流式 | +|---|--------|---------|---------|------| +| 20 | `HealthCheck` | `google.protobuf.Empty` | `HealthCheckResponse` | 否 | + +--- + +## 3. HTTP 路由映射表 + +通过 `google.api.http` 注解,每个 gRPC 方法映射为对应的 HTTP REST 端点。gRPC Gateway 在启动时读取这些注解,自动生成反向代理,使同一套后端可同时服务于 gRPC 和 HTTP 客户端。 + +### 3.1 完整映射表 + +| # | gRPC 方法 | HTTP 方法 | HTTP 路径 | 说明 | +|---|-----------|----------|----------|------| +| 1 | `SubmitTask` | `POST` | `/api/v1/synapse/task` | 提交任务 | +| 2 | `SubmitTaskWithFiles` | `POST` | `/api/v1/synapse/task/multipart` | 提交含附件的任务 | +| 3 | `GetTask` | `GET` | `/api/v1/synapse/task/{task_id}` | 获取任务详情 | +| 4 | `GetTaskStatus` | `GET` | `/api/v1/synapse/task/{task_id}/status` | 获取任务状态 | +| 5 | `ListTasks` | `GET` | `/api/v1/synapse/tasks` | 列出任务(支持分页) | +| 6 | `CancelTask` | `POST` | `/api/v1/synapse/task/{task_id}/cancel` | 取消任务 | +| 7 | `StreamTaskStatus` | `GET` | `/api/v1/synapse/task/{task_id}/stream` | SSE 流式监听任务状态 | +| 8 | `SendMessage` | `POST` | `/api/v1/synapse/message` | 发送消息 | +| 9 | `StreamMessages` | `GET` | `/api/v1/synapse/messages/stream` | WebSocket/SSE 流式消息交换 | +| 10 | `ReceiveCallback` | `GET` | `/api/v1/synapse/messages/callback` | SSE 接收服务端回调推送 | +| 11 | `GetCapabilities` | `GET` | `/api/v1/synapse/capabilities` | 获取自身能力声明 | +| 12 | `QueryAgent` | `GET` | `/api/v1/synapse/agents/{agent_id}` | 查询指定 Agent 能力 | +| 13 | `ListAgents` | `GET` | `/api/v1/synapse/agents` | 列出所有活跃 Agent | +| 14 | `CreateGroup` | `POST` | `/api/v1/synapse/group` | 创建群组 | +| 15 | `JoinGroup` | `POST` | `/api/v1/synapse/group/{group_id}/join` | 加入群组 | +| 16 | `LeaveGroup` | `POST` | `/api/v1/synapse/group/{group_id}/leave` | 离开群组 | +| 17 | `DisbandGroup` | `POST` | `/api/v1/synapse/group/{group_id}/disband` | 解散群组 | +| 18 | `ListGroupMembers` | `GET` | `/api/v1/synapse/group/{group_id}/members` | 列出群组成员 | +| 19 | `StreamGroupMessages` | `GET` | `/api/v1/synapse/group/{group_id}/messages/stream` | WebSocket/SSE 流式群组消息 | +| 20 | `HealthCheck` | `GET` | `/api/v1/synapse/health` | 健康检查 | + +### 3.2 路由特性说明 + +**路径参数绑定**: + +| 路径参数 | 绑定字段 | 示例值 | +|---------|---------|-------| +| `{task_id}` | `GetTaskRequest.task_id` | `"task-abc123"` | +| `{agent_id}` | `QueryAgentRequest.agent_id` | `"agent-xyz789"` | +| `{group_id}` | `JoinGroupRequest.group_id` | `"group-demo-001"` | + +**请求体映射规则**: + +- **POST 请求**:除路径参数外,剩余字段映射为 HTTP 请求体(JSON) +- **GET 请求**:非路径参数字段映射为 URL 查询参数(query string) + +**流式端点说明**: + +| 流式 RPC | HTTP 协议映射 | 说明 | +|---------|-------------|------| +| `StreamTaskStatus` | SSE(Server-Sent Events) | 服务端单向推送任务状态变更 | +| `StreamMessages` | WebSocket | 全双工流式消息交换 | +| `ReceiveCallback` | SSE(Server-Sent Events) | 服务端单向推送回调消息 | +| `StreamGroupMessages` | WebSocket | 全双工群组消息流 | + +对于双向流 RPC(`StreamMessages`、`StreamGroupMessages`),HTTP REST 映射通过 WebSocket Upgrade 实现;对于服务端流 RPC(`StreamTaskStatus`、`ReceiveCallback`),通过 SSE(`text/event-stream`)实现。 + +### 3.3 路由前缀约定 + +所有 Synapse 协议 REST API 遵循以下 URL 前缀约定: + +``` +/api/v1/synapse/ +``` + +- `api`:标识为 API 请求 +- `v1`:API 版本号 +- `synapse`:命名空间,区分 Synapse 协议与其他业务 API + +版本升级时(如 v1 → v2),路径前缀同步更新为 `/api/v2/synapse/`,新旧版本可并行部署。 + +### 3.4 HTTP 状态码与 gRPC 状态码映射 + +| HTTP 状态码 | gRPC 状态码 | 说明 | +|------------|-------------|------| +| `200 OK` | `OK` | 请求成功 | +| `201 Created` | `OK` | 资源创建成功(SubmitTask、CreateGroup) | +| `400 Bad Request` | `InvalidArgument` | 请求参数无效 | +| `401 Unauthorized` | `Unauthenticated` | 认证失败 | +| `403 Forbidden` | `PermissionDenied` | 权限不足 | +| `404 Not Found` | `NotFound` | 资源不存在 | +| `409 Conflict` | `AlreadyExists` / `Aborted` | 资源冲突 | +| `429 Too Many Requests` | `ResourceExhausted` | 限流触发 | +| `500 Internal Server Error` | `Internal` | 服务端内部错误 | +| `503 Service Unavailable` | `Unavailable` | 服务不可用 | + +gRPC Gateway 自动完成上述状态码转换,客户端无需额外处理。 + +--- + +## 4. 协议缓冲区消息结构概览 + +以下列出各服务核心 RPC 的请求与响应消息结构(仅示意字段分类,完整定义以 `.proto` 文件为准): + +### 4.1 任务相关消息 + +```protobuf +// SubmitTask 请求 +message SubmitTaskRequest { + string task_type = 1; // 任务类型 + string payload_json = 2; // JSON 负载 + string callback_url = 3; // 回调地址(可选) + int32 ttl_seconds = 4; // 任务 TTL(可选) + string idempotency_key = 5; // 幂等键(可选) +} + +// SubmitTask 响应 +message SubmitTaskResponse { + string task_id = 1; // 任务 ID + string status = 2; // 初始状态 + string message = 3; // 附加信息 +} + +// 任务状态查询响应(通用) +message TaskStatusResponse { + string task_id = 1; + string status = 2; + int32 progress = 3; // 0-100 + string result_json = 4; // 执行结果(JSON) + string error_message = 5; + string created_at = 6; + string updated_at = 7; + string etag = 8; // 用于增量查询 +} + +// 任务流式更新 +message TaskStatusUpdate { + string task_id = 1; + string event_type = 2; // status_update / progress / log / error / complete + string data_json = 3; +} +``` + +### 4.2 消息相关消息 + +```protobuf +// SendMessage 请求 +message SendMessageRequest { + string receiver_id = 1; + string content_type = 2; // text / json / code / file + string payload_json = 3; + string correlation_id = 4; // 关联 ID(可选) + string conversation_id = 5; // 会话 ID(可选) +} + +// SendMessage 响应 +message SendMessageResponse { + string message_id = 1; + string status = 2; +} + +// 统一消息信封(用于流式传输) +message SynapseMessage { + string id = 1; + string sender_id = 2; + string receiver_id = 3; + string content_type = 4; + string payload_json = 5; + string correlation_id = 6; + string conversation_id = 7; + int64 timestamp = 8; +} +``` + +### 4.3 能力相关消息 + +```protobuf +message CapabilitiesResponse { + string agent_id = 1; + string agent_name = 2; + string protocol_version = 3; + repeated string supported_task_types = 4; + repeated string supported_content_types = 5; + int64 max_file_size = 6; + int32 max_concurrent_tasks = 7; + bool support_streaming = 8; + bool support_hil = 9; + bool support_groups = 10; +} + +message QueryAgentRequest { + string agent_id = 1; +} + +message ListAgentsResponse { + repeated AgentInfo agents = 1; + int32 total = 2; +} + +message AgentInfo { + string agent_id = 1; + string agent_name = 2; + string endpoint = 3; + repeated string capabilities = 4; + string status = 5; // online / offline / busy +} +``` + +### 4.4 群组相关消息 + +```protobuf +message CreateGroupRequest { + string name = 1; + string description = 2; + repeated string initial_members = 3; +} + +message CreateGroupResponse { + string group_id = 1; + string status = 2; +} + +message JoinGroupRequest { + string group_id = 1; + string agent_id = 2; +} + +message LeaveGroupRequest { + string group_id = 1; + string agent_id = 2; +} + +message DisbandGroupRequest { + string group_id = 1; + string agent_id = 2; // 需群主或管理员权限 +} + +message ListGroupMembersRequest { + string group_id = 1; +} + +message ListGroupMembersResponse { + string group_id = 1; + repeated GroupMember members = 2; +} + +message GroupMember { + string agent_id = 1; + string role = 2; // owner / admin / member + string joined_at = 3; +} +``` + +### 4.5 健康检查消息 + +```protobuf +message HealthCheckResponse { + string status = 1; // SERVING / NOT_SERVING + string version = 2; + int64 uptime_seconds = 3; +} +``` + +--- + +## 5. 传输配置与使用指引 + +### 5.1 gRPC 客户端连接配置 + +```go +// 推荐 gRPC 客户端配置 +conn, err := grpc.Dial( + target, + grpc.WithInsecure(), // 开发环境;生产环境使用 TLS + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(10*1024*1024), // 最大接收消息 10MB + grpc.MaxCallSendMsgSize(10*1024*1024), // 最大发送消息 10MB + ), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: 30 * time.Second, // 每 30s 发送心跳 + Timeout: 10 * time.Second, // 心跳超时 10s + PermitWithoutStream: true, // 无活跃流时也发送心跳 + }), +) +``` + +### 5.2 HTTP REST 客户端调用示例 + +```bash +# SubmitTask +curl -X POST http://localhost:8080/api/v1/synapse/task \ + -H "Content-Type: application/json" \ + -d '{"task_type":"text_generation","payload_json":"...","callback_url":"..."}' + +# GetTask +curl http://localhost:8080/api/v1/synapse/task/task-abc123 + +# 流式监听任务状态(SSE) +curl -N http://localhost:8080/api/v1/synapse/task/task-abc123/stream + +# HealthCheck +curl http://localhost:8080/api/v1/synapse/health +``` + +### 5.3 幂等性支持 + +对于 `SubmitTask` 和 `CreateGroup` 等写操作,建议通过 `idempotency_key` 字段实现幂等: + +- 客户端为每个写操作生成唯一幂等键(如 UUID v4) +- 服务端缓存幂等键及其响应,相同键的重复请求返回缓存结果 +- 幂等键缓存 TTL 默认为 24 小时 + +--- + +## 附录:gRPC Gateway 集成方式 + +``` +┌─────────────────────────────────────────────────────┐ +│ 外部客户端 │ +│ gRPC Client HTTP Client (REST) │ +└────────┬───────────────────────────────┬────────────┘ + │ gRPC (h2) │ HTTP/1.1 + ▼ ▼ +┌─────────────────┐ ┌──────────────────────┐ +│ gRPC Server │ │ gRPC Gateway │ +│ (直接服务) │◄────────►│ (反向代理 / 协议转换) │ +└────────┬────────┘ └──────────────────────┘ + │ 内部 gRPC 调用 + ▼ +┌─────────────────────────────────────────────────────┐ +│ Synapse 业务逻辑层 │ +│ TaskService / MessageService / Capabilities ... │ +└─────────────────────────────────────────────────────┘ +``` + +推荐集成方式: + +1. 在 Go 服务端使用 `grpc-gateway` 库,通过一行代码注册反向代理 +2. 启动时同时监听两个端口(例:gRPC 端口 `9090`,HTTP 端口 `8080`) +3. HTTP 端口同时提供 REST API 和 Swagger UI(开发环境) + +--- + +**文档结束** diff --git a/docs/v1/hil-mechanism.md b/docs/v1/hil-mechanism.md new file mode 100644 index 0000000..10cb138 --- /dev/null +++ b/docs/v1/hil-mechanism.md @@ -0,0 +1,1010 @@ +# 灵隙协议实现规范:人工介入(HiL)三级升级机制 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 目录 + +1. [升级级别定义](#1-升级级别定义) +2. [HiL 状态流转](#2-hil-状态流转) +3. [HiL 请求结构](#3-hil-请求结构) +4. [升级判定规则](#4-升级判定规则) +5. [人工响应结构](#5-人工响应结构) +6. [超时与降级策略](#6-超时与降级策略) +7. [通知渠道规范](#7-通知渠道规范) +8. [审计与日志](#8-审计与日志) + +--- + +## 1. 升级级别定义 + +HiL(Human-in-the-Loop)三级升级机制根据决策的重要性、紧急程度和风险等级,将人工介入分为三个级别。 + +### 1.1 级别总览 + +| 级别 | 名称 | 默认超时 | 紧急超时 | 决策者 | 适用场景 | +|-----|------|---------|---------|-------|---------| +| **Level 1** | Agent 建议 + 人确认 | 24 小时 | — | 任务发起人 / 值班人 | 信息缺失、创意方向、用户主动请求 | +| **Level 2** | 必须人工决策 | 12 小时 | 2 小时 | 领域负责人 / 审核专员 | 品牌风格、合规审查、能力不足、高风险操作 | +| **Level 3** | 专家 / 管理层决策 | 72 小时 | 24 小时 | 专家委员会 / 管理层 | 战略决策、重大争议、跨系统冲突 | + +### 1.2 Level 1:Agent 建议 + 人确认 + +**定位**:低风险、低成本的快速确认。Agent 提供建议方案,人类只需确认或微调。 + +**触发条件**: + +| 触发代码 | 场景描述 | 示例 | +|---------|---------|------| +| `INFO_MISSING` | Agent 缺少关键信息,需要人类补充 | 客户未提供明确的需求规格 | +| `CREATIVE_DIRECTION` | 创意方向需人类确认 | Logo 设计方案初选 | +| `USER_REQUESTED` | 用户主动要求人工介入 | 用户明确要求"请人工审核" | +| `CONTENT_REVIEW` | 内容发布前快速确认 | 推文、公告文案确认 | +| `PREFERENCE_CONFIRM` | 偏好确认 | 颜色、字体等视觉偏好选择 | + +**行为规范**: + +- Agent 必须提供明确的建议值(`SuggestedValue`) +- 人类可执行的操作:**批准**、**修改后批准**、**拒绝并说明原因** +- 超时默认策略:**自动以 Agent 建议值继续执行** +- 升级频率限制:同一任务单日不超过 5 次 Level 1 升级 + +### 1.3 Level 2:必须人工决策 + +**定位**:中高风险,必须由具备相应领域知识的人类做出决策,Agent 不可自行决定。 + +**触发条件**: + +| 触发代码 | 场景描述 | 示例 | +|---------|---------|------| +| `BRAND_STYLE` | 品牌风格相关决策 | 品牌色调、VI 元素使用 | +| `COMPLIANCE` | 合规要求判定 | 数据隐私法规、行业合规 | +| `CAPABILITY_LACKING` | Agent 能力不足以完成任务 | 需要特定领域专家的判断 | +| `HIGH_COST_RISK` | 高成本或高风险操作 | 预算超过阈值、涉及敏感操作 | +| `RESOURCE_DEADLOCK` | 资源死锁或竞争 | 多个 Agent 争夺同一资源 | +| `SENSITIVE_CONTENT` | 敏感内容判定 | 涉及政治、色情、暴力等内容 | +| `THIRD_PARTY_ESCALATION` | 第三方系统接入审批 | 外部 API 调用、数据交换审批 | + +**行为规范**: + +- Agent 必须提供完整的决策上下文(`Context`),包括推理过程和依据 +- Agent 应提供至少 2 个可选方案(`Options`),并分析各自的优缺点 +- 人类可执行的操作:**批准某一方案**、**驳回并要求补充信息**、**修改方案后执行** +- 超时默认策略:**升级至 Level 3**(不可自动通过或拒绝) +- 紧急模式下(`HIGH_COST_RISK`、`SENSITIVE_CONTENT`),超时缩短为 **2 小时** + +### 1.4 Level 3:专家 / 管理层决策 + +**定位**:最高级别的决策层级,涉及战略方向、重大争议和跨系统冲突,必须由专家委员会或管理层做出最终裁决。 + +**触发条件**: + +| 触发代码 | 场景描述 | 示例 | +|---------|---------|------| +| `STRATEGIC_DECISION` | 战略级决策 | 产品方向、市场策略、合作伙伴选择 | +| `MAJOR_DISPUTE` | 重大争议 | Agent 之间无法达成共识,且影响重大 | +| `CROSS_SYSTEM_CONFLICT` | 跨系统冲突 | 两个独立系统间的数据或资源冲突 | +| `LEVEL2_TIMEOUT_ESCALATED` | Level 2 超时升级 | Level 2 超时后自动升级 | +| `POLICY_EXCEPTION` | 策略例外申请 | 需要突破既定策略的特殊情况 | +| `CRISIS_RESPONSE` | 危机响应 | 安全事件、数据泄露、服务中断 | + +**行为规范**: + +- Agent 必须提供完整的背景报告,包括事件时间线、影响范围、可选方案及风险评估 +- 需要至少 **2 位** 具备相应权限的决策者参与 +- 决策方式:**投票制**(简单多数或加权投票,根据场景配置) +- 超时默认策略:**任务挂起**(不自动执行任何操作),等待人工跟进 +- 所有 Level 3 决策必须记录完整的审计日志 + +### 1.5 级别间关系 + +``` +Level 1 ──超时──→ 自动以建议值执行 + │ + ├──复杂情况──→ Level 2 + │ +Level 2 ──超时──→ Level 3 + │ + ├──驳回并要求补充──→ Level 1 / Level 2(重新提交) + │ +Level 3 ──超时──→ 任务挂起(等待人工) + │ + └──决策完成──→ 任务继续执行或终止 +``` + +--- + +## 2. HiL 状态流转 + +### 2.1 状态定义 + +```go +type HILStatus string + +const ( + // 初始状态:请求已创建,尚未进入人工队列 + HILStatusPending HILStatus = "pending" + + // 等待状态:请求已进入人工队列,等待分配决策者 + HILStatusWaiting HILStatus = "waiting" + + // 决策中:决策者正在审阅中,尚未给出结论 + HILStatusDeciding HILStatus = "deciding" + + // 已批准:决策者批准了 Agent 的建议或某一方案 + HILStatusApproved HILStatus = "approved" + + // 已拒绝:决策者拒绝了所有方案 + HILStatusRejected HILStatus = "rejected" + + // 超时:在截止时间前未收到人工响应 + HILStatusTimeout HILStatus = "timeout" + + // 已升级:当前级别无法处理,升级至更高级别 + HILStatusEscalated HILStatus = "escalated" +) +``` + +### 2.2 状态流转图 + +``` + ┌─────────────────────────────────────┐ + │ 创建请求 │ + │ HILStatusPending │ + └────────────────┬────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ 进入人工队列 │ + │ HILStatusWaiting │ + └────────────────┬────────────────────┘ + │ + ┌───────────┴───────────┐ + │ 分配决策者 │ + ▼ ▼ + ┌────────────────────────┐ ┌────────────────────┐ + │ 决策者开始审阅 │ │ 超时未分配 │ + │ HILStatusDeciding │ │ HILStatusTimeout │ + └──────────┬─────────────┘ └─────────┬──────────┘ + │ │ + ┌────────────┼────────────┬─────────────┘ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────────┐ + │ 批准 │ │ 拒绝 │ │ 升级 │ + │ Approved │ │ Rejected │ │ Escalated │ + └──────────┘ └──────────┘ └──────┬───────┘ + │ + ▼ + ┌────────────────────┐ + │ 进入更高级别流程 │ + │ Pending/Waiting │ + └────────────────────┘ +``` + +### 2.3 状态转移矩阵 + +| 当前状态 \ 目标状态 | pending | waiting | deciding | approved | rejected | timeout | escalated | +|-------------------|---------|---------|----------|----------|----------|---------|-----------| +| **pending** | — | ✔️ 入队 | ❌ | ❌ | ❌ | ❌ | ❌ | +| **waiting** | ❌ | — | ✔️ 分配决策者 | ❌ | ❌ | ✔️ 超时未分配 | ✔️ 手动升级 | +| **deciding** | ❌ | ❌ | — | ✔️ 批准方案 | ✔️ 拒绝方案 | ✔️ 决策超时 | ✔️ 无法决策 | +| **approved** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | +| **rejected** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | +| **timeout** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ✔️ 超时升级 | +| **escalated** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | + +### 2.4 状态流转 Hook + +```go +// HILStatusHook - 状态变更回调接口 +type HILStatusHook interface { + // OnStatusChanged - 状态变更时触发 + OnStatusChanged(ctx context.Context, req *HILRequest, oldStatus, newStatus HILStatus) error + + // OnEscalated - 升级时触发 + OnEscalated(ctx context.Context, req *HILRequest, fromLevel, toLevel int) error + + // OnTimeout - 超时时触发 + OnTimeout(ctx context.Context, req *HILRequest) error + + // OnDecisionMade - 决策完成时触发 + OnDecisionMade(ctx context.Context, req *HILRequest, resp *HILResponse) error +} +``` + +--- + +## 3. HiL 请求结构 + +### 3.1 HILRequest + +```go +type HILRequest struct { + // 基础标识 + ID string `json:"id"` // 请求唯一 ID(UUID) + TaskID string `json:"task_id"` // 关联任务 ID + ParentRequestID string `json:"parent_request_id,omitempty"` // 父请求 ID(升级时关联) + + // 升级信息 + EscalationType EscalationType `json:"escalation_type"` // 升级类型 + EscalationLevel int `json:"escalation_level"` // 升级级别(1/2/3) + SourceSystemID string `json:"source_system_id"` // 发起方系统 ID + SourceAgentID string `json:"source_agent_id"` // 发起方 Agent ID + + // 决策内容 + Reason HILReason `json:"reason"` // 介入原因 + Context HILContext `json:"context"` // 决策上下文 + Options []HILOption `json:"options"` // 可选方案(至少 1 个) + SuggestedValue any `json:"suggested_value"` // Agent 建议值 + + // 时间与状态 + CreatedAt time.Time `json:"created_at"` // 创建时间 + Deadline time.Time `json:"deadline"` // 截止时间 + RespondedAt *time.Time `json:"responded_at,omitempty"` // 响应时间 + Status HILStatus `json:"status"` // 当前状态 + StatusHistory []StatusEntry `json:"status_history"` // 状态变更历史 + + // 响应 + Response *HILResponse `json:"response,omitempty"` // 人工响应 + AssignedTo []string `json:"assigned_to,omitempty"` // 指定决策者 ID 列表 + AssignedRoles []string `json:"assigned_roles,omitempty"` // 指定决策角色 + + // 元数据 + Priority int `json:"priority"` // 优先级(1-5,5 最高) + Tags map[string]string `json:"tags,omitempty"` // 自定义标签 + Notified bool `json:"notified"` // 是否已发送通知 +} + +// EscalationType - 升级类型 +type EscalationType string + +const ( + EscalationP2P EscalationType = "P2P" // 点对点升级 + EscalationGROUP EscalationType = "GROUP" // 群组内升级 + EscalationInterGROUP EscalationType = "INTER_GROUP" // 跨群组升级 + EscalationSYSTEM EscalationType = "SYSTEM" // 系统级升级 +) + +// StatusEntry - 状态变更记录 +type StatusEntry struct { + FromStatus HILStatus `json:"from_status"` // 原状态 + ToStatus HILStatus `json:"to_status"` // 新状态 + ChangedBy string `json:"changed_by"` // 变更者 + Reason string `json:"reason"` // 变更原因 + Timestamp time.Time `json:"timestamp"` // 变更时间 +} +``` + +### 3.2 HILReason + +```go +type HILReason struct { + Code HILReasonCode `json:"code"` // 原因代码 + Message string `json:"message"` // 人类可读的描述 + Detail string `json:"detail"` // 详细说明 + Confidence int `json:"confidence"` // Agent 置信度(0-100) + TriggerEvent string `json:"trigger_event,omitempty"` // 触发事件标识 +} + +// HILReasonCode - 原因代码枚举 +type HILReasonCode string + +const ( + // Level 1 原因 + ReasonInfoMissing HILReasonCode = "INFO_MISSING" // 信息缺失 + ReasonCreativeDirection HILReasonCode = "CREATIVE_DIRECTION" // 创意方向 + ReasonUserRequested HILReasonCode = "USER_REQUESTED" // 用户请求 + ReasonContentReview HILReasonCode = "CONTENT_REVIEW" // 内容审核 + ReasonPreferenceConfirm HILReasonCode = "PREFERENCE_CONFIRM" // 偏好确认 + + // Level 2 原因 + ReasonBrandStyle HILReasonCode = "BRAND_STYLE" // 品牌风格 + ReasonCompliance HILReasonCode = "COMPLIANCE" // 合规要求 + ReasonCapabilityLacking HILReasonCode = "CAPABILITY_LACKING" // 能力不足 + ReasonHighCostRisk HILReasonCode = "HIGH_COST_RISK" // 高成本风险 + ReasonResourceDeadlock HILReasonCode = "RESOURCE_DEADLOCK" // 资源死锁 + ReasonSensitiveContent HILReasonCode = "SENSITIVE_CONTENT" // 敏感内容 + ReasonThirdPartyEsc HILReasonCode = "THIRD_PARTY_ESCALATION" // 第三方介入 + + // Level 3 原因 + ReasonStrategicDecision HILReasonCode = "STRATEGIC_DECISION" // 战略决策 + ReasonMajorDispute HILReasonCode = "MAJOR_DISPUTE" // 重大争议 + ReasonCrossSystemConflict HILReasonCode = "CROSS_SYSTEM_CONFLICT" // 跨系统冲突 + ReasonLevel2TimeoutEsc HILReasonCode = "LEVEL2_TIMEOUT_ESCALATED" // Level 2 超时升级 + ReasonPolicyException HILReasonCode = "POLICY_EXCEPTION" // 策略例外 + ReasonCrisisResponse HILReasonCode = "CRISIS_RESPONSE" // 危机响应 +) +``` + +### 3.3 HILContext + +```go +type HILContext struct { + // 任务上下文 + TaskType string `json:"task_type"` // 任务类型 + TaskDescription string `json:"task_description"` // 任务描述 + TaskPayload any `json:"task_payload"` // 任务载荷摘要 + + // 决策上下文 + CurrentState any `json:"current_state"` // 当前状态 + AffectedFields []string `json:"affected_fields"` // 受影响字段列表 + Constraints map[string]interface{} `json:"constraints"` // 约束条件 + + // Agent 推理 + ReasoningChain string `json:"reasoning_chain"` // Agent 推理链摘要 + ConfidenceScore float64 `json:"confidence_score"` // 整体置信度(0.0-1.0) + Alternatives []string `json:"alternatives"` // 已排除的替代方案 + + // 风险信息 + RiskLevel string `json:"risk_level"` // 风险等级(low/mid/high/critical) + RiskAssessment string `json:"risk_assessment"` // 风险评估 + ImpactScope string `json:"impact_scope"` // 影响范围 + + // 附件 + Attachments []ContextAttachment `json:"attachments,omitempty"` // 附件列表 +} + +// ContextAttachment - 上下文附件 +type ContextAttachment struct { + Name string `json:"name"` // 附件名 + Type string `json:"type"` // 附件类型(image/document/data) + URL string `json:"url"` // 附件 URL + Description string `json:"description"` // 附件描述 + Size int64 `json:"size"` // 附件大小(字节) +} +``` + +### 3.4 HILOption + +```go +type HILOption struct { + // 选项标识 + ID string `json:"id"` // 选项 ID + Field string `json:"field"` // 对应字段名 + + // 选项内容 + Value any `json:"value"` // 候选值 + Label string `json:"label"` // 人类可读的显示标签 + Description string `json:"description"` // 详细描述 + + // 分析信息 + ProsCons string `json:"pros_cons"` // 优缺点分析 + Confidence int `json:"confidence"` // Agent 置信度(0-100) + EstimatedCost string `json:"estimated_cost,omitempty"` // 预估成本 + RiskNote string `json:"risk_note,omitempty"` // 风险提示 + + // 元数据 + IsDefault bool `json:"is_default"` // 是否为默认推荐 + Source string `json:"source"` // 来源(agent/human/auto) +} + +// HILOptionSet - 选项集合(工具方法) +type HILOptionSet struct { + Options []HILOption `json:"options"` +} + +func (s *HILOptionSet) GetDefault() *HILOption { + for _, opt := range s.Options { + if opt.IsDefault { + return &opt + } + } + if len(s.Options) > 0 { + return &s.Options[0] + } + return nil +} + +func (s *HILOptionSet) GetByID(id string) *HILOption { + for _, opt := range s.Options { + if opt.ID == id { + return &opt + } + } + return nil +} +``` + +### 3.5 请求结构示例 + +```json +{ + "id": "hil-a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "task_id": "task-20260628-001", + "parent_request_id": "", + "escalation_type": "P2P", + "escalation_level": 2, + "source_system_id": "sys-creative-001", + "source_agent_id": "agent-designer-v3", + + "reason": { + "code": "BRAND_STYLE", + "message": "品牌风格决策:需要确认视觉元素是否符合品牌规范", + "detail": "Agent 在生成 banner 设计稿时,对主色调使用存在不确定性。当前候选色 #3366FF 与品牌指南中规定的 Primary-500 存在偏差(偏差值 #2244DD)。", + "confidence": 72, + "trigger_event": "brand_style_check_failed" + }, + + "context": { + "task_type": "banner_design", + "task_description": "为夏季促销活动生成一张 1200x628 的社交媒体 banner", + "task_payload": { "campaign": "summer_sale_2026", "format": "social_media" }, + "affected_fields": ["color_primary", "color_secondary", "typography"], + "constraints": { "budget_level": "standard" }, + "reasoning_chain": "1. 分析品牌指南 V3.2 → 2. 检测到主色偏差 → 3. 计算偏差值 → 4. 需要人工确认", + "confidence_score": 0.72, + "risk_level": "mid", + "risk_assessment": "颜色偏差不会导致功能问题,但可能影响品牌一致性" + }, + + "options": [ + { + "id": "opt-1", + "field": "color_primary", + "value": "#3366FF", + "label": "推荐方案:使用 #3366FF", + "description": "基于夏季活力主题的微调版本,与品牌色系相近", + "pros_cons": "优点:更符合夏季活动调性;缺点:与品牌指南有微小偏差", + "confidence": 72, + "is_default": true + }, + { + "id": "opt-2", + "field": "color_primary", + "value": "#2244DD", + "label": "保守方案:严格遵循品牌色 #2244DD", + "description": "100% 符合品牌指南 V3.2 规定的 Primary-500", + "pros_cons": "优点:完全合规;缺点:与夏季活力主题契合度较低", + "confidence": 95, + "is_default": false + } + ], + + "suggested_value": { "color_primary": "#3366FF" }, + + "status": "pending", + "priority": 3, + "assigned_roles": ["brand_manager", "creative_director"] +} +``` + +--- + +## 4. 升级判定规则 + +### 4.1 需要人工介入的代码映射表 + +```go +// hilRequiredCodes - 需要人工介入的原因代码映射表 +// true = 必须人工介入,Agent 不可自行决策 +// false = 可由 Agent 自行决策(默认值) +var hilRequiredCodes = map[HILReasonCode]bool{ + // Level 1 + ReasonInfoMissing: true, + ReasonCreativeDirection: true, + ReasonUserRequested: true, + ReasonContentReview: true, + ReasonPreferenceConfirm: true, + + // Level 2 + ReasonBrandStyle: true, + ReasonCompliance: true, + ReasonCapabilityLacking: true, + ReasonHighCostRisk: true, + ReasonResourceDeadlock: true, + ReasonSensitiveContent: true, + ReasonThirdPartyEsc: true, + + // Level 3 + ReasonStrategicDecision: true, + ReasonMajorDispute: true, + ReasonCrossSystemConflict: true, + ReasonLevel2TimeoutEsc: true, + ReasonPolicyException: true, + ReasonCrisisResponse: true, +} +``` + +### 4.2 升级级别映射表 + +```go +// levelMap - 原因代码到升级级别的映射 +// 决定升级到 Level 1 / 2 / 3 +var levelMap = map[HILReasonCode]int{ + // ── Level 1 ── + ReasonInfoMissing: 1, + ReasonCreativeDirection: 1, + ReasonUserRequested: 1, + ReasonContentReview: 1, + ReasonPreferenceConfirm: 1, + + // ── Level 2 ── + ReasonBrandStyle: 2, + ReasonCompliance: 2, + ReasonCapabilityLacking: 2, + ReasonHighCostRisk: 2, + ReasonResourceDeadlock: 2, + ReasonSensitiveContent: 2, + ReasonThirdPartyEsc: 2, + + // ── Level 3 ── + ReasonStrategicDecision: 3, + ReasonMajorDispute: 3, + ReasonCrossSystemConflict: 3, + ReasonLevel2TimeoutEsc: 3, + ReasonPolicyException: 3, + ReasonCrisisResponse: 3, +} +``` + +### 4.3 完整判定规则表 + +| 原因代码 | 中文说明 | 是否必须 HiL | 升级级别 | 默认超时 | 超时策略 | +|---------|---------|:----------:|:-------:|---------|---------| +| `INFO_MISSING` | 信息缺失 | ✔️ | 1 | 24h | 以建议值继续 | +| `CREATIVE_DIRECTION` | 创意方向 | ✔️ | 1 | 24h | 以建议值继续 | +| `USER_REQUESTED` | 用户请求 | ✔️ | 1 | 24h | 以建议值继续 | +| `CONTENT_REVIEW` | 内容审核 | ✔️ | 1 | 24h | 以建议值继续 | +| `PREFERENCE_CONFIRM` | 偏好确认 | ✔️ | 1 | 24h | 以建议值继续 | +| `BRAND_STYLE` | 品牌风格 | ✔️ | 2 | 12h | 升级至 Level 3 | +| `COMPLIANCE` | 合规要求 | ✔️ | 2 | 12h | 升级至 Level 3 | +| `CAPABILITY_LACKING` | 能力不足 | ✔️ | 2 | 12h | 升级至 Level 3 | +| `HIGH_COST_RISK` | 高成本风险 | ✔️ | 2 | 2h(紧急) | 升级至 Level 3 | +| `RESOURCE_DEADLOCK` | 资源死锁 | ✔️ | 2 | 12h | 升级至 Level 3 | +| `SENSITIVE_CONTENT` | 敏感内容 | ✔️ | 2 | 2h(紧急) | 升级至 Level 3 | +| `THIRD_PARTY_ESCALATION` | 第三方介入 | ✔️ | 2 | 12h | 升级至 Level 3 | +| `STRATEGIC_DECISION` | 战略决策 | ✔️ | 3 | 72h | 任务挂起 | +| `MAJOR_DISPUTE` | 重大争议 | ✔️ | 3 | 72h | 任务挂起 | +| `CROSS_SYSTEM_CONFLICT` | 跨系统冲突 | ✔️ | 3 | 72h | 任务挂起 | +| `LEVEL2_TIMEOUT_ESCALATED` | Level 2 超时 | ✔️ | 3 | 72h | 任务挂起 | +| `POLICY_EXCEPTION` | 策略例外 | ✔️ | 3 | 72h | 任务挂起 | +| `CRISIS_RESPONSE` | 危机响应 | ✔️ | 3 | 24h(紧急) | 任务挂起 | + +### 4.4 动态判定函数 + +```go +// DetermineHiLLevel - 根据原因代码和上下文判定升级级别 +// 返回:是否需要 HiL、升级级别、建议超时时间 +func DetermineHiLLevel(reasonCode HILReasonCode, ctx context.Context) (required bool, level int, timeout time.Duration) { + // 是否必须 HiL + required = hilRequiredCodes[reasonCode] + + if !required { + return false, 0, 0 + } + + // 判定升级级别 + level = levelMap[reasonCode] + if level == 0 { + level = 1 // 默认 Level 1 + } + + // 根据上下文调整超时 + timeout = getDefaultTimeout(level) + + // 紧急模式判定:高风险场景缩短超时 + if isEmergencyMode(ctx, reasonCode) { + timeout = getEmergencyTimeout(level) + } + + return required, level, timeout +} + +func getDefaultTimeout(level int) time.Duration { + switch level { + case 1: + return 24 * time.Hour + case 2: + return 12 * time.Hour + case 3: + return 72 * time.Hour + default: + return 24 * time.Hour + } +} + +func getEmergencyTimeout(level int) time.Duration { + switch level { + case 2: + return 2 * time.Hour + case 3: + return 24 * time.Hour + default: + return getDefaultTimeout(level) + } +} + +// isEmergencyMode - 判断是否为紧急模式 +func isEmergencyMode(ctx context.Context, reasonCode HILReasonCode) bool { + emergencyCodes := map[HILReasonCode]bool{ + ReasonHighCostRisk: true, + ReasonSensitiveContent: true, + ReasonCrisisResponse: true, + } + return emergencyCodes[reasonCode] +} +``` + +### 4.5 阈值配置表 + +| 配置项 | 默认值 | 说明 | +|-------|-------|------| +| `hil.max_level1_per_task` | 5 | 单任务最大 Level 1 升级次数 | +| `hil.max_level2_per_task` | 3 | 单任务最大 Level 2 升级次数 | +| `hil.max_level3_per_task` | 1 | 单任务最大 Level 3 升级次数 | +| `hil.cost_threshold_high` | 1000 | 高成本阈值(SYN 代币) | +| `hil.confidence_threshold` | 60 | Agent 置信度阈值(低于此值建议升级) | +| `hil.max_pending_requests` | 50 | 最大待处理请求数(超出后拒绝新请求) | +| `hil.emergency_mode_enabled` | true | 是否启用紧急模式 | +| `hil.auto_approve_on_timeout_level1` | true | Level 1 超时是否自动以建议值通过 | + +--- + +## 5. 人工响应结构 + +### 5.1 HILResponse + +```go +type HILResponse struct { + // 决策标识 + DecisionID string `json:"decision_id"` // 决策唯一 ID + RequestID string `json:"request_id"` // 关联请求 ID + + // 决策者信息 + UserID string `json:"user_id"` // 决策用户 ID + UserName string `json:"user_name"` // 用户名 + UserRole string `json:"user_role"` // 用户角色 + UserAvatar string `json:"user_avatar,omitempty"` // 用户头像 URL + + // 决策内容 + Action DecisionAction `json:"action"` // 决策动作 + Decision HILDecision `json:"decision"` // 决策内容 + Notes string `json:"notes"` // 备注说明 + + // 元数据 + Timestamp time.Time `json:"timestamp"` // 决策时间 + Duration time.Duration `json:"duration"` // 决策耗时 + Source string `json:"source"` // 响应渠道(web/mobile/api/email) +} + +// DecisionAction - 决策动作枚举 +type DecisionAction string + +const ( + ActionApprove DecisionAction = "approve" // 批准建议值或默认方案 + ActionReject DecisionAction = "reject" // 拒绝所有方案 + ActionModify DecisionAction = "modify" // 修改后批准 + ActionSelect DecisionAction = "select" // 选择某一方案 + ActionDefer DecisionAction = "defer" // 暂缓决策,需要更多信息 + ActionEscalate DecisionAction = "escalate" // 手动升级 +) +``` + +### 5.2 HILDecision + +```go +type HILDecision struct { + // 批准的字段(Action = approve 时使用) + // key = 字段名, value = 批准的值 + ApprovedFields map[string]interface{} `json:"approved_fields,omitempty"` + + // 修改的字段(Action = modify 时使用) + // 与 ApprovedFields 的区别:ModifiedFields 包含人类手动调整的值 + ModifiedFields map[string]interface{} `json:"modified_fields,omitempty"` + + // 选中的选项 ID(Action = select 时使用) + SelectedOption string `json:"selected_option,omitempty"` + + // 拒绝原因(Action = reject 时使用) + RejectionReason string `json:"rejection_reason,omitempty"` + + // 附加指令(Action = modify/defer 时使用) + Instructions string `json:"instructions,omitempty"` + + // 决策影响评估 + Impact *DecisionImpact `json:"impact,omitempty"` +} + +// DecisionImpact - 决策影响评估 +type DecisionImpact struct { + EstimatedCost float64 `json:"estimated_cost"` // 预估成本 + EstimatedTime string `json:"estimated_time"` // 预估耗时 + AffectedSystems []string `json:"affected_systems"` // 受影响系统 + RollbackPossible bool `json:"rollback_possible"` // 是否可回滚 + RollbackPlan string `json:"rollback_plan,omitempty"` // 回滚方案 +} +``` + +### 5.3 决策动作对照表 + +| 动作 | 适用级别 | 含义 | 后续流程 | +|------|---------|------|---------| +| `approve` | 1 / 2 / 3 | 批准 Agent 的建议值或默认方案 | 任务继续执行 | +| `reject` | 1 / 2 / 3 | 拒绝所有方案 | 任务终止或返回修改 | +| `modify` | 1 / 2 | 修改建议值后批准 | 按修改后的值执行 | +| `select` | 2 / 3 | 从多个方案中选择一个 | 按选中方案执行 | +| `defer` | 2 / 3 | 暂缓决策,要求补充信息 | 返回等待状态,Agent 补充信息 | +| `escalate` | 1 / 2 | 手动升级至更高级别 | 进入更高级别的流程 | + +### 5.4 响应处理函数 + +```go +// ProcessHILResponse - 处理人工响应 +// 返回:是否需要触发后续操作、错误信息 +func ProcessHILResponse(ctx context.Context, resp *HILResponse) (bool, error) { + switch resp.Action { + case ActionApprove: + return handleApprove(ctx, resp) + case ActionReject: + return handleReject(ctx, resp) + case ActionModify: + return handleModify(ctx, resp) + case ActionSelect: + return handleSelect(ctx, resp) + case ActionDefer: + return handleDefer(ctx, resp) + case ActionEscalate: + return handleEscalate(ctx, resp) + default: + return false, fmt.Errorf("unknown decision action: %s", resp.Action) + } +} + +func handleApprove(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 更新请求状态为 approved + // 2. 应用 approved_fields 到任务 + // 3. 触发任务继续执行 + // 4. 记录审计日志 + return true, nil +} + +func handleModify(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 更新请求状态为 approved + // 2. 将 modified_fields 合并到任务参数 + // 3. 记录原始值与修改值到审计日志 + // 4. 触发任务继续执行 + return true, nil +} + +func handleSelect(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 验证 selected_option 存在 + // 2. 更新请求状态为 approved + // 3. 提取选中选项的值并应用到任务 + // 4. 触发任务继续执行 + return true, nil +} + +func handleReject(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 更新请求状态为 rejected + // 2. 记录拒绝原因 + // 3. 通知任务发起方 + // 4. 根据策略决定任务终止或重新提交 + return true, nil +} + +func handleDefer(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 更新请求状态为 pending(重新进入队列) + // 2. 记录 instructions 为补充信息要求 + // 3. 通知 Agent 补充信息 + // 4. 更新 deadline + return true, nil +} + +func handleEscalate(ctx context.Context, resp *HILResponse) (bool, error) { + // 1. 更新当前请求状态为 escalated + // 2. 创建新的升级请求(level + 1) + // 3. 关联 parent_request_id + // 4. 通知更高级别的决策者 + return true, nil +} +``` + +### 5.5 响应示例 + +```json +{ + "decision_id": "dec-9876-5432-1098", + "request_id": "hil-a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "user_id": "user-zhangsan-001", + "user_name": "张三", + "user_role": "brand_manager", + "action": "modify", + "decision": { + "modified_fields": { + "color_primary": "#3377FF", + "color_secondary": "#FF7722" + }, + "instructions": "主色调调整为 #3377FF(介于推荐和保守方案之间),辅助色使用 #FF7722 突出促销氛围。请按此配色重新生成设计稿。", + "impact": { + "estimated_cost": 0, + "estimated_time": "15min", + "affected_systems": ["creative-engine", "brand-assets"], + "rollback_possible": true, + "rollback_plan": "恢复至修改前的配色方案即可" + } + }, + "notes": "颜色调整合理,注意保持整体的视觉平衡。", + "timestamp": "2026-06-28T14:30:00+08:00", + "duration": 1800000000000, + "source": "web" +} +``` + +--- + +## 6. 超时与降级策略 + +### 6.1 超时处理矩阵 + +| 级别 | 超时时间 | 超时动作 | 说明 | +|-----|---------|---------|------| +| Level 1 | 24h | 自动批准(以 Agent 建议值执行) | 低风险场景,Agent 建议值视为默认 | +| Level 1(紧急) | 4h | 自动批准 | 任务优先级高时缩短超时 | +| Level 2 | 12h | 升级至 Level 3 | 不可自动决策,必须由更高级别介入 | +| Level 2(紧急) | 2h | 升级至 Level 3 | 高风险场景快速升级 | +| Level 3 | 72h | 任务挂起 | 不做任何自动操作,等待人工 | +| Level 3(紧急) | 24h | 任务挂起 + 通知管理层 | 危机场景需尽快响应 | + +### 6.2 超时监控 + +```go +// HILTimeoutWatcher - 超时监控器 +type HILTimeoutWatcher struct { + mu sync.RWMutex + timers map[string]*time.Timer + onTimeout func(ctx context.Context, req *HILRequest) +} + +func NewHILTimeoutWatcher(onTimeout func(ctx context.Context, req *HILRequest)) *HILTimeoutWatcher { + return &HILTimeoutWatcher{ + timers: make(map[string]*time.Timer), + onTimeout: onTimeout, + } +} + +func (w *HILTimeoutWatcher) Register(req *HILRequest) { + w.mu.Lock() + defer w.mu.Unlock() + + timeout := time.Until(req.Deadline) + timer := time.AfterFunc(timeout, func() { + ctx := context.Background() + w.onTimeout(ctx, req) + }) + w.timers[req.ID] = timer +} + +func (w *HILTimeoutWatcher) Cancel(reqID string) { + w.mu.Lock() + defer w.mu.Unlock() + + if timer, ok := w.timers[reqID]; ok { + timer.Stop() + delete(w.timers, reqID) + } +} +``` + +### 6.3 超时通知策略 + +| 级别 | 超时前提醒 | 提醒方式 | 超时后通知 | +|-----|-----------|---------|-----------| +| Level 1 | 超时前 4h | 站内通知 | 通知请求发起人 | +| Level 2 | 超时前 2h、30min | 站内通知 + 邮件 | 通知请求发起人 + 升级接收人 | +| Level 3 | 超时前 24h、4h、30min | 站内通知 + 邮件 + 短信 | 通知管理层 + 升级委员会 | + +--- + +## 7. 通知渠道规范 + +### 7.1 通知渠道配置 + +```go +type HILNotificationConfig struct { + // 渠道启用状态 + InAppEnabled bool `json:"in_app_enabled"` // 站内通知 + EmailEnabled bool `json:"email_enabled"` // 邮件通知 + SMSEnabled bool `json:"sms_enabled"` // 短信通知 + WebhookEnabled bool `json:"webhook_enabled"` // Webhook 通知 + + // 各渠道的级别阈值 + InAppLevel int `json:"in_app_level"` // 站内通知最低级别(1) + EmailLevel int `json:"email_level"` // 邮件通知最低级别(2) + SMSLevel int `json:"sms_level"` // 短信通知最低级别(3) + WebhookURL string `json:"webhook_url,omitempty"` // Webhook URL + + // 通知模板 + TitleTemplate string `json:"title_template"` // 标题模板 + BodyTemplate string `json:"body_template"` // 内容模板 +} + +// HILNotification - 通知消息结构 +type HILNotification struct { + ID string `json:"id"` + RequestID string `json:"request_id"` + Channels []string `json:"channels"` // 通知渠道列表 + Title string `json:"title"` + Body string `json:"body"` + Level int `json:"level"` + Recipients []string `json:"recipients"` + Status string `json:"status"` // sent/delivered/failed + CreatedAt time.Time `json:"created_at"` +} +``` + +### 7.2 渠道-级别对照表 + +| 级别 | 站内通知 | 邮件 | 短信 | Webhook | +|-----|:-------:|:----:|:----:|:-------:| +| Level 1 | ✔️ | ❌ | ❌ | 可选 | +| Level 2 | ✔️ | ✔️ | ❌ | 可选 | +| Level 3 | ✔️ | ✔️ | ✔️(紧急) | ✔️ | +| 紧急模式 | ✔️ | ✔️ | ✔️ | ✔️ | + +--- + +## 8. 审计与日志 + +### 8.1 审计记录结构 + +```go +type HILAuditLog struct { + LogID string `json:"log_id"` // 审计日志 ID + RequestID string `json:"request_id"` // 关联请求 ID + TaskID string `json:"task_id"` // 关联任务 ID + + // 事件信息 + EventType string `json:"event_type"` // 事件类型 + EventData map[string]interface{} `json:"event_data"` // 事件数据 + Timestamp time.Time `json:"timestamp"` // 事件时间 + + // 操作者 + ActorID string `json:"actor_id"` // 操作者 ID + ActorType string `json:"actor_type"` // 操作者类型(agent/human/system) + ActorRole string `json:"actor_role"` // 操作者角色 + + // 变更记录 + Changes []FieldChange `json:"changes"` // 字段变更记录 + + // 完整性 + Checksum string `json:"checksum"` // 日志校验和 + Signature string `json:"signature"` // 数字签名 +} + +type FieldChange struct { + Field string `json:"field"` // 字段名 + OldValue interface{} `json:"old_value"` // 旧值 + NewValue interface{} `json:"new_value"` // 新值 + Reason string `json:"reason"` // 变更原因 +} +``` + +### 8.2 审计事件类型 + +| 事件类型 | 说明 | 记录内容 | +|---------|------|---------| +| `hil.request.created` | 请求创建 | 请求完整内容、发起方信息 | +| `hil.request.assigned` | 分配决策者 | 决策者 ID、角色 | +| `hil.request.status_changed` | 状态变更 | 原状态、新状态、变更原因 | +| `hil.request.notified` | 发送通知 | 通知渠道、接收人列表 | +| `hil.request.timeout` | 超时触发 | 超时级别、触发策略 | +| `hil.request.escalated` | 升级触发 | 原级别、新级别、升级原因 | +| `hil.decision.submitted` | 决策提交 | 决策完整内容、决策者信息 | +| `hil.decision.applied` | 决策应用 | 应用字段、变更值 | +| `hil.request.completed` | 请求完成 | 最终状态、耗时 | + +### 8.3 审计日志保留策略 + +| 级别 | 保留期限 | 存储方式 | +|-----|---------|---------| +| Level 1 | 90 天 | 热存储 | +| Level 2 | 180 天 | 热存储 | +| Level 3 | 365 天 | 热存储 + 冷备份 | +| 紧急模式 | 730 天 | 热存储 + 冷备份 + 加密归档 | + +--- + +> 本文档基于 [06-implementation-details.md](../v0/06-implementation-details.md#5-人工介入hil三级升级机制) 第5章扩展编写,是灵隙协议实现规范的一部分。 +> +> 相关文档:[raw-protocol.md](01_raw-protocol.md) | [a2a-protocol.md](02_a2a-protocol.md) | [a2n-protocol.md](03_a2n-protocol.md) diff --git a/docs/v1/message-extensions.md b/docs/v1/message-extensions.md new file mode 100644 index 0000000..7905d00 --- /dev/null +++ b/docs/v1/message-extensions.md @@ -0,0 +1,201 @@ +# 灵隙协议实现规范:消息扩展字段 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 1. 消息扩展字段总览 + +统一消息信封 `UnifiedSynapseMessage` 在原始协议基础字段之上,扩充了以下扩展字段,用于支持会话关联、传输控制、群组精准投递及安全声明等高级功能。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ConversationID` | `string` | 会话 ID,用于多轮对话关联 | +| `TTL` | `int` | 消息存活时间,单位秒;超时后由灵泊(Harbor)自动清理 | +| `Broadcast` | `bool` | 是否广播;为 `true` 时消息将发送至群组所有成员 | +| `Encrypt` | `bool` | 标识 payload 是否已加密;接收方据此决定是否解密 | +| `TargetType` | `GroupTargetType` | 群组目标类型,定义消息在群组内的投递范围 | +| `TargetIDs` | `[]string` | 目标 ID 列表,与 `TargetType` 配合使用 | +| `SignatureAlgorithm` | `string` | 签名算法声明,接收方据此选择验证方式 | + +### 代码定义 + +```go +type UnifiedSynapseMessage struct { + // 基础字段(协议定义) + ID string `json:"id"` + Version string `json:"version"` + Type string `json:"type"` + Sender AgentEndpoint `json:"sender"` + Receiver *AgentEndpoint `json:"receiver,omitempty"` + CorrelationID string `json:"correlation_id,omitempty"` + ReplyTo string `json:"reply_to,omitempty"` + GroupID string `json:"group_id,omitempty"` + Payload json.RawMessage `json:"payload"` + Timestamp Timestamp `json:"timestamp"` + Signature string `json:"signature,omitempty"` + + // 扩展字段(实现补充) + ConversationID string `json:"conversation_id,omitempty"` + TTL int `json:"ttl,omitempty"` + Broadcast bool `json:"broadcast,omitempty"` + Encrypt bool `json:"encrypt,omitempty"` + TargetType GroupTargetType `json:"target_type,omitempty"` + TargetIDs []string `json:"target_ids,omitempty"` + SignatureAlgorithm string `json:"signature_algorithm,omitempty"` +} +``` + +--- + +## 2. 群组目标类型 + +`GroupTargetType` 枚举定义了消息在群组内的三种目标选择策略,用于精确控制消息的投递范围,避免向无关成员发送无关消息。 + +```go +type GroupTargetType string + +const ( + TargetTypeAll GroupTargetType = "all" // 所有成员 + TargetTypeRole GroupTargetType = "role" // 按角色 + TargetTypeSpecific GroupTargetType = "specific" // 指定成员 +) +``` + +### 字段说明表 + +| 值 | 常量名 | 含义 | TargetIDs 用法 | +|----|--------|------|----------------| +| `"all"` | `TargetTypeAll` | 发送给群组内所有成员 | 忽略 `TargetIDs`,即使有值也不参与路由 | +| `"role"` | `TargetTypeRole` | 按角色筛选成员 | `TargetIDs` 填入角色名称列表(如 `["admin","operator"]`),仅匹配这些角色的成员会收到消息 | +| `"specific"` | `TargetTypeSpecific` | 发送给指定的若干成员 | `TargetIDs` 填入成员 AgentID 列表,精确投递 | + +### 使用约束 + +- 仅在 `GroupID` 非空时生效。若 `GroupID` 为空,`TargetType` 和 `TargetIDs` 将被忽略。 +- 默认值为空字符串,此时行为等同于 `"all"`(全量投递)。 +- `TargetType` 与 `Broadcast` 的关系:当 `Broadcast` 为 `true` 时,`TargetType` 仍可进一步缩小广播范围;两者协同工作而非互斥。 + +--- + +## 3. 各字段用途说明表 + +| 字段名 | 类型 | 用途 | 使用场景 | 示例值 | +|--------|------|------|----------|--------| +| `ConversationID` | `string` | 关联同一会话的多条消息,形成对话上下文 | 多轮问答、连续任务编排、长对话跟踪 | `"conv-a1b2c3d4"` | +| `TTL` | `int` | 消息的存活时长,超时后由灵泊组件自动丢弃 | 临时通知、时效性指令、过期任务取消 | `300`(5 分钟) | +| `Broadcast` | `bool` | 标记消息是否为广播,指示接收方是否需要扩散 | 群组公告、系统通知、状态广播 | `true` | +| `Encrypt` | `bool` | 标记 payload 是否已加密,接收方据此触发解密流程 | 包含敏感数据的消息、跨信任域通信 | `true` | +| `TargetType` | `GroupTargetType` | 群组内的目标选择策略 | 按角色下发命令、定向通知指定成员 | `"role"` | +| `TargetIDs` | `[]string` | 配合 `TargetType` 提供目标标识列表 | 指定角色名列表或成员 ID 列表 | `["admin","operator"]` | +| `SignatureAlgorithm` | `string` | 声明消息签名所使用的算法,接收方选择对应验证逻辑 | 多算法共存场景、算法升级过渡期 | `"HMAC-SHA256"` | + +--- + +## 4. 扩展字段与基础字段关系图 + +下图展示了包含扩展字段在内的完整 `UnifiedSynapseMessage` 结构层次。 + +``` +┌─────────────────────────────────────────────────────────┐ +│ UnifiedSynapseMessage │ +│ │ +│ ┌─────────────────────────────────────────────────────┐│ +│ │ 基础字段 ││ +│ │ ││ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ ││ +│ │ │ ID │ │ Version │ │ Type │ ││ +│ │ └──────────┘ └──────────┘ └──────────────────┘ ││ +│ │ ││ +│ │ ┌────────────────────────────────────────────────┐ ││ +│ │ │ Sender (AgentEndpoint) │ ││ +│ │ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │ ││ +│ │ │ │AgentID │ │AgentName │ │ Endpoint │ │ ││ +│ │ │ └─────────┘ └──────────┘ └────────────────┘ │ ││ +│ │ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │ ││ +│ │ │ │SystemID │ │Version │ │ Capabilities │ │ ││ +│ │ │ └─────────┘ └──────────┘ └────────────────┘ │ ││ +│ │ └────────────────────────────────────────────────┘ ││ +│ │ ││ +│ │ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ││ +│ │ │ Receiver │ │CorrelationID │ │ ReplyTo │ ││ +│ │ │(optional)│ │ │ │ │ ││ +│ │ └──────────┘ └──────────────┘ └──────────────┘ ││ +│ │ ││ +│ │ ┌──────────┐ ┌──────────────────────────────────┐ ││ +│ │ │ GroupID │ │ Payload │ ││ +│ │ │ │ │ (json.RawMessage) │ ││ +│ │ └──────────┘ └──────────────────────────────────┘ ││ +│ │ ││ +│ │ ┌──────────┐ ┌──────────┐ ││ +│ │ │Timestamp │ │Signature │ ││ +│ │ └──────────┘ └──────────┘ ││ +│ └─────────────────────────────────────────────────────┘│ +│ │ +│ ┌─────────────────────────────────────────────────────┐│ +│ │ 扩展字段 ││ +│ │ ││ +│ │ ┌────────────────┐ ┌──────┐ ┌──────────┐ ││ +│ │ │ ConversationID │ │ TTL │ │ Broadcast│ ││ +│ │ └────────────────┘ └──────┘ └──────────┘ ││ +│ │ ││ +│ │ ┌──────────┐ ┌──────────────────────────────────┐ ││ +│ │ │ Encrypt │ │ TargetType + TargetIDs │ ││ +│ │ │ │ │ (群组目标选择) │ ││ +│ │ └──────────┘ └──────────────────────────────────┘ ││ +│ │ ││ +│ │ ┌────────────────────────────────────────────────┐ ││ +│ │ │ SignatureAlgorithm │ ││ +│ │ │ (签名算法声明) │ ││ +│ │ └────────────────────────────────────────────────┘ ││ +│ └─────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────┘ +``` + +### 层次说明 + +| 层级 | 包含字段 | 职责 | +|------|----------|------| +| **标识层** | `ID`, `Version`, `Type` | 消息唯一标识与协议版本声明 | +| **源端层** | `Sender` (AgentEndpoint) | 发送方端点信息,含身份、能力与路由地址 | +| **目标层** | `Receiver`, `GroupID`, `TargetType`, `TargetIDs` | 点对点或群组目标寻址 | +| **控制层** | `CorrelationID`, `ReplyTo`, `ConversationID` | 消息关联、回复与会话跟踪 | +| **数据层** | `Payload` | 业务负载,JSON 格式 | +| **传输层** | `TTL`, `Broadcast`, `Priority`, `ExpiresAt` | 传输行为控制与生命周期管理 | +| **安全层** | `Signature`, `SignatureAlgorithm`, `Encrypt` | 完整性保护、算法声明与负载加密 | +| **时间层** | `Timestamp` | 消息产生时间,ISO 8601 格式 | + +### 扩展字段与基础字段的协作关系 + +``` +发送流程 接收流程 +───────── ───────── + + 发方构造消息 收方解析消息 + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ 填写基础字段 │ │ 校验基础字段 │ + │ ID, Sender, │─────────▶ │ ID, Type, │ + │ Type, Payload│ │ Signature │ + └──────────────┘ └──────────────┘ + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ 设置扩展字段 │ │ 读取扩展字段 │ + │ │ │ │ + │ • ConversationID ──────▶ │ • 恢复会话 │ + │ • TTL ──────▶ │ • 设置超时 │ + │ • Broadcast ──────▶ │ • 判断扩散 │ + │ • Encrypt ──────▶ │ • 触发解密 │ + │ • TargetType ──────▶ │ • 筛选成员 │ + │ • TargetIDs ──────▶ │ • 精确投递 │ + │ • SigAlgo ──────▶ │ • 选择验证 │ + └──────────────┘ └──────────────┘ +``` + +--- + +**参考来源** +- [v0 实现细节 - 第 7 章:消息扩展字段](file:///e:/code/go/synapse/docs/v0/06-implementation-details.md#1088) +- [UnifiedSynapseMessage 类型定义](file:///e:/code/go/synapse/pkg/types/types.go#L197-L236) diff --git a/docs/v1/transport-modes.md b/docs/v1/transport-modes.md new file mode 100644 index 0000000..6d63ae5 --- /dev/null +++ b/docs/v1/transport-modes.md @@ -0,0 +1,579 @@ +# 灵隙协议实现规范:传输模式定义 + +> 版本:1.0.0 | 层级:实现层 + +--- + +## 1. 传输模式枚举 + +传输模式(TransportMode)定义了任务通信的传输方式。Synapse 协议支持四种传输模式,适配不同的网络环境和业务场景。 + +### 1.1 模式定义 + +```go +// TransportMode 定义任务通信的传输模式 +type TransportMode string + +const ( + // TransportModeCallback - 异步回调模式 + // 任务完成后通过 Webhook 回调通知结果 + TransportModeCallback TransportMode = "callback" + + // TransportModePolling - 异步轮询模式 + // 调用方主动轮询获取任务状态和结果 + TransportModePolling TransportMode = "polling" + + // TransportModeSync - 同步模式 + // 调用方阻塞等待任务完成,直接返回结果 + TransportModeSync TransportMode = "sync" + + // TransportModeHybrid - 混合模式 + // 优先使用回调,回调失败时启用轮询作为兜底 + TransportModeHybrid TransportMode = "hybrid" +) +``` + +### 1.2 模式对比 + +| 模式 | 枚举值 | 通信方向 | 实时性 | 适用场景 | +|------|--------|----------|--------|----------| +| 回调(Callback) | `callback` | 服务端 → 调用方 | 高 | 网络可达、期望实时通知 | +| 轮询(Polling) | `polling` | 调用方 → 服务端 | 中 | 调用方主动拉取、服务端不主动推送 | +| 同步(Sync) | `sync` | 双向阻塞 | 最高 | 短耗时任务、需要即时响应 | +| 混合(Hybrid) | `hybrid` | 双向 + 兜底 | 高 | 生产环境、容错要求高的场景 | + +### 1.3 模式选择建议 + +| 条件 | 推荐模式 | +|------|----------| +| 任务耗时 < 5s | `sync` — 直接同步等待 | +| 任务耗时 5s~5min,回调端点可达 | `callback` — 异步回调 | +| 任务耗时 5s~5min,回调端点不可达 | `polling` — 轮询拉取 | +| 生产环境,需要高可靠性 | `hybrid` — 回调 + 轮询兜底 | +| 任务耗时 > 5min | `polling` 或 `hybrid` — 长时间异步 | + +--- + +## 2. 传输偏好配置 + +### 2.1 传输偏好结构 + +```go +// TransportPreferences 定义任务的传输偏好配置 +type TransportPreferences struct { + // Preferred - 首选传输模式 + Preferred TransportMode `json:"preferred"` + + // Fallback - 兜底传输模式,当首选模式不可用时使用 + Fallback TransportMode `json:"fallback,omitempty"` + + // CallbackEndpoint - 回调端点 URL + // 仅当 Preferred 或 Fallback 为 callback/hybrid 时生效 + CallbackEndpoint string `json:"callback_endpoint,omitempty"` + + // PollingInterval - 轮询间隔(秒) + // 仅当 Preferred 或 Fallback 为 polling/hybrid 时生效 + PollingInterval int `json:"polling_interval,omitempty"` + + // ProgressUpdates - 是否接收进度更新 + // 为 true 时,服务端会在任务执行过程中推送进度事件 + ProgressUpdates bool `json:"progress_updates"` + + // ProgressInterval - 进度更新最小间隔(秒) + // 控制进度通知的频率,避免频繁推送 + ProgressInterval int `json:"progress_interval,omitempty"` +} +``` + +### 2.2 字段说明 + +| 字段 | 类型 | 必需 | 默认值 | 说明 | +|------|------|------|--------|------| +| `preferred` | `TransportMode` | 是 | `callback` | 首选传输模式 | +| `fallback` | `TransportMode` | 否 | `polling` | 兜底模式,首选不可用时回退 | +| `callback_endpoint` | `string` | 条件必填 | `""` | 回调 URL,callback/hybrid 模式必需 | +| `polling_interval` | `int` | 否 | `5` | 轮询间隔(秒),最小 1 | +| `progress_updates` | `bool` | 否 | `true` | 是否启用进度推送 | +| `progress_interval` | `int` | 否 | `10` | 进度更新间隔(秒) | + +### 2.3 默认配置 + +```go +// DefaultTransportPreferences 返回默认传输偏好配置 +// +// 默认值说明: +// - 首选回调模式(callback),兜底轮询(polling) +// - 轮询间隔 5 秒 +// - 启用进度更新,最小间隔 10 秒 +func DefaultTransportPreferences() *TransportPreferences { + return &TransportPreferences{ + Preferred: TransportModeCallback, + Fallback: TransportModePolling, + PollingInterval: 5, + ProgressUpdates: true, + ProgressInterval: 10, + } +} +``` + +### 2.4 配置示例 + +**纯回调模式:** + +```go +prefs := &TransportPreferences{ + Preferred: TransportModeCallback, + CallbackEndpoint: "https://my-service.com/callback", + ProgressUpdates: true, + ProgressInterval: 5, +} +``` + +**纯轮询模式:** + +```go +prefs := &TransportPreferences{ + Preferred: TransportModePolling, + PollingInterval: 3, + ProgressUpdates: false, +} +``` + +**混合模式(生产推荐):** + +```go +prefs := &TransportPreferences{ + Preferred: TransportModeCallback, + Fallback: TransportModePolling, + CallbackEndpoint: "https://my-service.com/callback", + PollingInterval: 5, + ProgressUpdates: true, + ProgressInterval: 10, +} +``` + +**同步模式:** + +```go +prefs := &TransportPreferences{ + Preferred: TransportModeSync, +} +``` + +--- + +## 3. 轮询 API 类型 + +轮询 API 为调用方提供主动拉取任务状态的能力。所有 Agent 端必须实现以下类型以支持轮询传输模式。 + +### 3.1 任务状态响应 + +```go +// TaskStatusResponse 任务状态轮询响应 +// +// 调用方通过此结构获取单个任务的当前状态。 +// 成功时 Success 为 true,Task 字段包含任务详情; +// 失败时 Success 为 false,Error 字段包含错误信息。 +type TaskStatusResponse struct { + Success bool `json:"success"` + Task *TaskInfo `json:"task,omitempty"` + Error *SynapseError `json:"error,omitempty"` +} +``` + +### 3.2 任务信息 + +```go +// TaskInfo 任务详细信息 +// +// 描述一个异步任务的完整状态,包括进度、结果和 +// 错误信息。ETag 字段用于增量查询,避免重复传输 +// 未变更的数据。 +type TaskInfo struct { + // ID - 任务唯一标识 + ID string `json:"id"` + + // Type - 任务类型,如 "text_generation", "image_analysis" + Type string `json:"type"` + + // Status - 任务状态 + // 可选值: pending, running, completed, failed, cancelled + Status string `json:"status"` + + // Progress - 任务进度百分比(0-100) + Progress int `json:"progress"` + + // Message - 当前状态的人类可读描述 + Message string `json:"message,omitempty"` + + // CreatedAt - 任务创建时间 + CreatedAt Timestamp `json:"created_at"` + + // UpdatedAt - 任务最后更新时间 + UpdatedAt Timestamp `json:"updated_at"` + + // ETA - 预计完成时间(可选) + ETA *Timestamp `json:"eta,omitempty"` + + // Result - 任务执行结果 + // 当 Status 为 completed 时此字段有效 + Result any `json:"result,omitempty"` + + // Error - 任务执行错误 + // 当 Status 为 failed 时此字段有效 + Error *SynapseError `json:"error,omitempty"` + + // ETag - 实体标签,用于增量查询和缓存 + // 客户端可在后续请求中携带此值以获取变更数据 + ETag string `json:"etag,omitempty"` +} +``` + +### 3.3 任务状态枚举 + +| 状态值 | 说明 | 可转换至 | +|--------|------|----------| +| `pending` | 任务已提交,等待执行 | `running` | +| `running` | 任务正在执行中 | `completed`, `failed`, `cancelled` | +| `completed` | 任务执行成功,Result 可用 | —(终态) | +| `failed` | 任务执行失败,Error 可用 | —(终态) | +| `cancelled` | 任务已被取消 | —(终态) | + +### 3.4 任务列表响应 + +```go +// TaskListResponse 任务列表响应 +// +// 支持分页查询,调用方可使用 Limit 和 Offset +// 参数控制返回结果的数量和偏移量。 +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"` +} +``` + +**分页参数说明:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | `int` | 符合条件的任务总数 | +| `limit` | `int` | 本次请求的限制数量 | +| `offset` | `int` | 本次请求的偏移量 | +| `has_more` | `bool` | 是否还有更多数据 | + +### 3.5 任务事件 + +```go +// TaskEvent 任务事件 +// +// 表示任务执行过程中产生的事件,用于事件流推送 +// 和增量状态更新。 +type TaskEvent struct { + // ID - 事件唯一标识 + ID string `json:"id"` + + // Type - 事件类型 + // 可选值: status_update, progress, log, error, complete + Type string `json:"type"` + + // Timestamp - 事件发生时间 + Timestamp Timestamp `json:"timestamp"` + + // Data - 事件数据 + // 不同类型的事件携带不同的数据结构 + Data any `json:"data"` +} +``` + +### 3.6 事件类型说明 + +| 事件类型 | 触发时机 | Data 内容 | +|----------|----------|-----------| +| `status_update` | 任务状态变更 | 包含新状态和状态变更原因 | +| `progress` | 进度更新 | 包含当前进度值(0-100)和描述 | +| `log` | 任务产生日志输出 | 日志文本内容 | +| `error` | 任务执行出错 | 错误代码和错误消息 | +| `complete` | 任务完成 | 任务执行结果 | + +### 3.7 任务事件流响应 + +```go +// TaskEventsResponse 任务事件流响应 +// +// 支持基于游标的分页,调用方可使用 Next 游标 +// 持续拉取后续事件。 +type TaskEventsResponse struct { + Success bool `json:"success"` + Since string `json:"since"` + Events []TaskEvent `json:"events"` + HasMore bool `json:"has_more"` + Next string `json:"next_cursor,omitempty"` +} +``` + +**游标使用流程:** + +1. 首次请求不带 `since` 参数,获取当前所有事件 +2. 响应中返回 `next_cursor` 作为下一页游标 +3. 后续请求携带 `since=next_cursor` 获取后续事件 +4. 当 `has_more` 为 `false` 时,表示已无新事件 + +### 3.8 轮询 API 端点约定 + +| 端点 | 方法 | 请求参数 | 响应类型 | 说明 | +|------|------|----------|----------|------| +| `/v1/tasks/{taskID}` | GET | — | `TaskStatusResponse` | 查询单个任务状态 | +| `/v1/tasks` | GET | `limit, offset, status` | `TaskListResponse` | 查询任务列表 | +| `/v1/tasks/{taskID}/events` | GET | `since` | `TaskEventsResponse` | 查询任务事件流 | + +--- + +## 4. 流式传输类型 + +流式传输用于实时传输大文件或持续生成的数据,如音频流、视频流、长文本生成等场景。 + +### 4.1 流数据块 + +```go +// StreamChunk 流式传输数据块 +// +// 每次传输一个数据块,Sequence 字段保证数据块 +// 的顺序。当 Final 为 true 时,表示流传输结束, +// 客户端可开始组装完整数据。 +type StreamChunk struct { + // TaskID - 任务 ID,标识所属任务 + TaskID string `json:"task_id"` + + // Sequence - 块序列号,从 1 开始递增 + // 用于客户端排序和校验丢包 + Sequence int64 `json:"sequence"` + + // ChunkType - 数据块类型 + // 可选值: text, binary, audio, video + ChunkType string `json:"chunk_type"` + + // Data - 数据块内容 + // 二进制数据使用 base64 编码传输 + Data string `json:"data"` + + // Final - 是否为最后一个数据块 + // 为 true 时表示流传输结束 + Final bool `json:"final"` + + // Timestamp - 数据块时间戳 + Timestamp Timestamp `json:"timestamp"` +} +``` + +### 4.2 数据块类型说明 + +| ChunkType | 说明 | Data 编码 | 适用场景 | +|-----------|------|-----------|----------| +| `text` | 文本数据 | UTF-8 字符串 | 流式文本生成、日志输出 | +| `binary` | 二进制数据 | Base64 | 文件传输、二进制协议数据 | +| `audio` | 音频数据 | Base64 | 实时语音、音频流处理 | +| `video` | 视频数据 | Base64 | 实时视频、视频流处理 | + +### 4.3 流完成通知 + +```go +// StreamComplete 流传输完成通知 +// +// 当所有数据块传输完毕后发送,包含完整数据的 +// 校验和,供客户端验证数据完整性。 +type StreamComplete struct { + // TaskID - 任务 ID + TaskID string `json:"task_id"` + + // TotalChunks - 总数据块数 + // 客户端可据此验证是否收到所有数据块 + TotalChunks int64 `json:"total_chunks"` + + // FullContentChecksum - 完整数据校验和 + // 格式: "sha256:",用于完整性校验 + FullContentChecksum string `json:"full_content_checksum"` + + // Result - 任务执行结果(可选) + // 流传输完成后携带的最终结果 + Result any `json:"result,omitempty"` +} +``` + +### 4.4 流式传输生命周期 + +``` +调用方 服务端 + | | + |-- POST /v1/tasks/submit --->| + | (TransportMode: sync) | 启动异步任务 + | | + |<--- StreamChunk{seq:1} -----| 数据块 1 + |<--- StreamChunk{seq:2} -----| 数据块 2 + |<--- StreamChunk{seq:3} -----| 数据块 3 + |<--- StreamChunk{seq:4} -----| 数据块 4(Final=true) + |<--- StreamComplete ---------| 完成通知 + | | + +客户端收到 StreamComplete 后: + 1. 确认 TotalChunks == 实际收到的块数 + 2. 计算完整数据的 SHA256 校验和 + 3. 对比 FullContentChecksum 验证完整性 + 4. 读取 Result 获取任务执行结果 +``` + +### 4.5 校验和验证 + +```go +// ValidateStreamChecksum 验证流式传输数据的完整性 +// +// 客户端在收到 StreamComplete 后应调用此函数 +// 验证收到的数据是否完整。 +func ValidateStreamChecksum(receivedChecksum, computedChecksum string) bool { + return receivedChecksum == computedChecksum +} +``` + +### 4.6 传输模式与流式传输的组合规则 + +| 传输模式 | 是否支持流式 | 说明 | +|----------|-------------|------| +| `callback` | 否 | 回调模式仅通知结果,不承载流数据 | +| `polling` | 否 | 轮询模式通过分块获取,非实时流 | +| `sync` | 是 | 同步模式下可实时流式返回数据 | +| `hybrid` | 是(回调阶段) | 混合模式中回调路径支持流式 | + +--- + +## 5. 传输模式交互流程 + +### 5.1 回调模式(Callback) + +``` +调用方 Agent + | | + |--- SubmitTask ------------>| 提交任务 + | (callback_endpoint: URL) | + | | Agent 处理任务 + | |---> HTTP POST --->| 调用方回调端点 + | (异步回调) |<--- 200 OK -------| 回调响应 + | | + | [处理完成] | +``` + +### 5.2 轮询模式(Polling) + +``` +调用方 Agent + | | + |--- SubmitTask ------------>| 提交任务 + | | + |--- GET /v1/tasks/{id} ---->| 轮询状态(间隔 5s) + |<--- TaskInfo(status=running)| + |--- GET /v1/tasks/{id} ---->| + |<--- TaskInfo(status=running)| + |--- GET /v1/tasks/{id} ---->| + |<--- TaskInfo(status=completed, result=...) + | | +``` + +### 5.3 同步模式(Sync) + +``` +调用方 Agent + | | + |--- SubmitTask + Sync ----->| 提交任务 + | | Agent 立即处理 + |<-- Result (同步返回) -------| 直接返回结果 + | | +``` + +### 5.4 混合模式(Hybrid) + +``` +调用方 Agent + | | + |--- SubmitTask ------------>| 提交任务 + | (preferred: callback, | + | fallback: polling) | + | | Agent 处理任务 + | | + | [回调成功] | + |<-- AsyncCallback -----------| 回调通知结果 + | | + | [回调失败] | + |--- GET /v1/tasks/{id} ---->| 轮询兜底 + |<-- TaskInfo(result=...) ----| 获取结果 + | | +``` + +--- + +## 6. 错误处理 + +### 6.1 SynapseError 结构 + +```go +// SynapseError 协议错误信息 +type SynapseError struct { + Code int `json:"code"` + Message string `json:"message"` +} +``` + +### 6.2 传输模式相关错误码 + +| 错误码 | 名称 | 说明 | +|--------|------|------| +| `4001` | `CALLBACK_FAILED` | 回调端点不可达或返回非 2xx | +| `4002` | `POLLING_TIMEOUT` | 轮询超时,任务未在预期时间内完成 | +| `4003` | `INVALID_CALLBACK_ENDPOINT` | 回调端点 URL 格式无效 | +| `4004` | `MODE_NOT_SUPPORTED` | Agent 不支持请求的传输模式 | +| `4005` | `STREAM_SEQUENCE_GAP` | 流式传输出现序列号跳变 | + +--- + +## 附录:Go 类型汇总 + +```go +// 传输模式枚举 +type TransportMode string +const ( + TransportModeCallback TransportMode = "callback" + TransportModePolling TransportMode = "polling" + TransportModeSync TransportMode = "sync" + TransportModeHybrid TransportMode = "hybrid" +) + +// 传输偏好配置 +type TransportPreferences struct { + Preferred TransportMode `json:"preferred"` + Fallback TransportMode `json:"fallback,omitempty"` + CallbackEndpoint string `json:"callback_endpoint,omitempty"` + PollingInterval int `json:"polling_interval,omitempty"` + ProgressUpdates bool `json:"progress_updates"` + ProgressInterval int `json:"progress_interval,omitempty"` +} + +func DefaultTransportPreferences() *TransportPreferences { ... } + +// 轮询 API 类型 +type TaskStatusResponse struct { ... } +type TaskInfo struct { ... } +type TaskListResponse struct { ... } +type TaskEvent struct { ... } +type TaskEventsResponse struct { ... } + +// 流式传输类型 +type StreamChunk struct { ... } +type StreamComplete struct { ... } +``` + +--- + +> 本文档对应代码实现在 [types/content.go](file:///e:/code/go/synapse/types/content.go) 中。 +> +> 参考源文档:[06-implementation-details.md §8](file:///e:/code/go/synapse/docs/v0/06-implementation-details.md#L1144) diff --git a/group/protocol.go b/internal/agent/group/protocol.go similarity index 99% rename from group/protocol.go rename to internal/agent/group/protocol.go index eb7ca5b..aa4b864 100644 --- a/group/protocol.go +++ b/internal/agent/group/protocol.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/p2p/protocol.go b/internal/agent/p2p/protocol.go similarity index 99% rename from p2p/protocol.go rename to internal/agent/p2p/protocol.go index b479639..b3dc85f 100644 --- a/p2p/protocol.go +++ b/internal/agent/p2p/protocol.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/p2p/protocol_test.go b/internal/agent/p2p/protocol_test.go similarity index 99% rename from p2p/protocol_test.go rename to internal/agent/p2p/protocol_test.go index eb387c9..7db486d 100644 --- a/p2p/protocol_test.go +++ b/internal/agent/p2p/protocol_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/externalapi/handler.go b/internal/api/externalapi/handler.go similarity index 99% rename from externalapi/handler.go rename to internal/api/externalapi/handler.go index e0a9f94..56b3519 100644 --- a/externalapi/handler.go +++ b/internal/api/externalapi/handler.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/grpcapi/client.go b/internal/api/grpcapi/client.go similarity index 100% rename from grpcapi/client.go rename to internal/api/grpcapi/client.go diff --git a/grpcapi/server.go b/internal/api/grpcapi/server.go similarity index 99% rename from grpcapi/server.go rename to internal/api/grpcapi/server.go index 87a67a5..981dc47 100644 --- a/grpcapi/server.go +++ b/internal/api/grpcapi/server.go @@ -18,7 +18,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/grpcapi/synapse.pb.go b/internal/api/grpcapi/synapse.pb.go similarity index 100% rename from grpcapi/synapse.pb.go rename to internal/api/grpcapi/synapse.pb.go diff --git a/grpcapi/synapse_grpc.pb.go b/internal/api/grpcapi/synapse_grpc.pb.go similarity index 100% rename from grpcapi/synapse_grpc.pb.go rename to internal/api/grpcapi/synapse_grpc.pb.go diff --git a/capabilities/provider.go b/internal/capabilities/provider.go similarity index 99% rename from capabilities/provider.go rename to internal/capabilities/provider.go index 5718a87..52e5c41 100644 --- a/capabilities/provider.go +++ b/internal/capabilities/provider.go @@ -5,7 +5,7 @@ import ( "encoding/json" "net/http" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/harbor/harbor.go b/internal/components/harbor/harbor.go similarity index 99% rename from harbor/harbor.go rename to internal/components/harbor/harbor.go index b726494..b10c348 100644 --- a/harbor/harbor.go +++ b/internal/components/harbor/harbor.go @@ -14,7 +14,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/libra/libra.go b/internal/components/libra/libra.go similarity index 99% rename from libra/libra.go rename to internal/components/libra/libra.go index 6fefec9..15b5543 100644 --- a/libra/libra.go +++ b/internal/components/libra/libra.go @@ -14,7 +14,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/meridian/meridian.go b/internal/components/meridian/meridian.go similarity index 99% rename from meridian/meridian.go rename to internal/components/meridian/meridian.go index 5d79519..db83240 100644 --- a/meridian/meridian.go +++ b/internal/components/meridian/meridian.go @@ -15,7 +15,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/panorama/panorama.go b/internal/components/panorama/panorama.go similarity index 99% rename from panorama/panorama.go rename to internal/components/panorama/panorama.go index 9aeee4c..93262b7 100644 --- a/panorama/panorama.go +++ b/internal/components/panorama/panorama.go @@ -13,7 +13,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/pivot/pivot.go b/internal/components/pivot/pivot.go similarity index 99% rename from pivot/pivot.go rename to internal/components/pivot/pivot.go index b793c62..a093efd 100644 --- a/pivot/pivot.go +++ b/internal/components/pivot/pivot.go @@ -15,7 +15,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/sigil/sigil.go b/internal/components/sigil/sigil.go similarity index 99% rename from sigil/sigil.go rename to internal/components/sigil/sigil.go index edd11ca..fa672c1 100644 --- a/sigil/sigil.go +++ b/internal/components/sigil/sigil.go @@ -17,7 +17,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/speculum/speculum.go b/internal/components/speculum/speculum.go similarity index 100% rename from speculum/speculum.go rename to internal/components/speculum/speculum.go diff --git a/hil/protocol.go b/internal/hil/protocol.go similarity index 99% rename from hil/protocol.go rename to internal/hil/protocol.go index 4411e62..69f0733 100644 --- a/hil/protocol.go +++ b/internal/hil/protocol.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/routing/router.go b/internal/routing/router.go similarity index 99% rename from routing/router.go rename to internal/routing/router.go index e7b18cc..3de46da 100644 --- a/routing/router.go +++ b/internal/routing/router.go @@ -8,10 +8,10 @@ import ( "strings" "sync" - "synapse/group" - "synapse/hil" - "synapse/p2p" - "synapse/types" + "synapse/internal/agent/group" + "synapse/internal/agent/p2p" + "synapse/internal/hil" + "synapse/pkg/types" ) // ============================================================================= diff --git a/transport/multipart_polling.go b/internal/transport/multipart_polling.go similarity index 99% rename from transport/multipart_polling.go rename to internal/transport/multipart_polling.go index 446e264..e5c1ba9 100644 --- a/transport/multipart_polling.go +++ b/internal/transport/multipart_polling.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/transport/transport.go b/internal/transport/transport.go similarity index 99% rename from transport/transport.go rename to internal/transport/transport.go index 1e136b9..c071bb4 100644 --- a/transport/transport.go +++ b/internal/transport/transport.go @@ -15,7 +15,7 @@ import ( "sync" "time" - "synapse/types" + "synapse/pkg/types" ) // ============================================================================= diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go new file mode 100644 index 0000000..88974a5 --- /dev/null +++ b/pkg/sdk/client.go @@ -0,0 +1,1250 @@ +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 +} diff --git a/pkg/sdk/doc.go b/pkg/sdk/doc.go new file mode 100644 index 0000000..29a341a --- /dev/null +++ b/pkg/sdk/doc.go @@ -0,0 +1,21 @@ +// Package sdk 灵隙(Synapse)协议 SDK +// +// 单包 SDK,通过 Protocol 类型区分三种协议客户端: +// +// - ProtoP2A(协议前缀 "P2A"):外部调用方,使用 REST API +// - ProtoA2A(协议前缀 "A2A"):Agent 点对点通信,消息级交互 +// - ProtoA2N(协议前缀 "A2N"):Agent 群组协作,管理群组与任务 +// +// 用法示例: +// +// p2a := sdk.NewClient(sdk.ProtoP2A, sdk.Config{BaseURL: "http://...", APIKey: "..."}) +// task, err := p2a.SubmitTask(ctx, req) +// +// a2a := sdk.NewClient(sdk.ProtoA2A, sdk.Config{...}) +// taskID, err := a2a.SendTaskRequest(ctx, target, "text_gen", payload) +// +// a2n := sdk.NewClient(sdk.ProtoA2N, sdk.Config{...}) +// group, err := a2n.CreateGroup(ctx, name, topology, nil, members) +// +// 协议版本:1.0.0 +package sdk diff --git a/types/content.go b/pkg/types/content.go similarity index 100% rename from types/content.go rename to pkg/types/content.go diff --git a/types/errors.go b/pkg/types/errors.go similarity index 100% rename from types/errors.go rename to pkg/types/errors.go diff --git a/types/types.go b/pkg/types/types.go similarity index 100% rename from types/types.go rename to pkg/types/types.go diff --git a/types/types_test.go b/pkg/types/types_test.go similarity index 100% rename from types/types_test.go rename to pkg/types/types_test.go diff --git a/sdk/a2a/client.go b/sdk/a2a/client.go deleted file mode 100644 index 93ca186..0000000 --- a/sdk/a2a/client.go +++ /dev/null @@ -1,459 +0,0 @@ -package a2a - -import ( - "bytes" - "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "sync" - "time" - - "synapse/types" -) - -// ============================================================================= -// Synapse P2P SDK (灵隙协议) -// Agent 间点对点通信客户端 SDK -// 基于 A2A 协议:两个 Agent 之间进行点对点任务协作 -// ============================================================================= - -// MessageCallbackHandler 消息回调处理器接口 -type MessageCallbackHandler 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 -} - -// Client 灵隙 点对点通信客户端 -type Client struct { - agentEndpoint types.AgentEndpoint - authSecret string - callbackHandler MessageCallbackHandler - tasks map[string]*P2PTaskState - tasksMu sync.RWMutex - httpClient *http.Client -} - -// ClientConfig 灵隙 客户端配置 -type ClientConfig struct { - AgentEndpoint types.AgentEndpoint - AuthSecret string - CallbackPort int - Timeout time.Duration - MaxRetries int -} - -// P2PTaskState P2P 任务状态 -type P2PTaskState struct { - TaskID string - Status types.P2PTaskStatus - Progress int - Message string - Result any - Error *types.SynapseError -} - -// NewClient 创建新的 灵隙 客户端 -func NewClient(config *ClientConfig) (*Client, error) { - if config.Timeout == 0 { - config.Timeout = 30 * time.Second - } - - return &Client{ - agentEndpoint: config.AgentEndpoint, - authSecret: config.AuthSecret, - tasks: make(map[string]*P2PTaskState), - httpClient: &http.Client{Timeout: config.Timeout}, - }, nil -} - -// SetMessageHandler 设置消息回调处理器 -func (c *Client) SetMessageHandler(handler MessageCallbackHandler) { - c.callbackHandler = handler -} - -// ============================================================================= -// 任务状态管理 -// ============================================================================= - -// 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 -} - -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 - } -} - -// ============================================================================= -// 消息发送 -// ============================================================================= - -// 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.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.agentEndpoint.AgentID, - } - - payloadBytes, _ := json.Marshal(cancelPayload) - - msg := &types.UnifiedSynapseMessage{ - ID: types.GenerateMessageID(), - Version: "1.0.0", - Type: string(types.P2PTaskCancel), - Sender: c.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.agentEndpoint, - Receiver: &target, - Payload: payloadBytes, - Timestamp: types.NowTimestamp(), - } - - return c.sendMessage(ctx, target.Endpoint, msg) -} - -// SendCapabilityQuery 查询对方能力 -func (c *Client) SendCapabilityQuery(ctx context.Context, target types.AgentEndpoint) ([]string, error) { - msg := &types.UnifiedSynapseMessage{ - ID: types.GenerateMessageID(), - Version: "1.0.0", - Type: string(types.P2PCapabilityQuery), - Sender: c.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 -} - -// ============================================================================= -// 消息签名与验证 -// ============================================================================= - -func (c *Client) signMessage(msg *types.UnifiedSynapseMessage) string { - if c.authSecret == "" { - return "" - } - - h := hmac.New(sha256.New, []byte(c.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)) -} - -// ============================================================================= -// 消息发送底层 -// ============================================================================= - -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 -} - -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 -} - -// ============================================================================= -// 等待任务完成 -// ============================================================================= - -// WaitForTask 等待任务完成(轮询方式,通过回调更新状态) -func (c *Client) WaitForTask(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 - } - } - } -} - -// ============================================================================= -// 回调处理 -// ============================================================================= - -// HandleMessage 处理收到的消息(供 HTTP 服务调用) -func (c *Client) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { - if c.authSecret != "" && !c.VerifySignature(msg, c.authSecret) { - return nil, fmt.Errorf("invalid signature") - } - - switch types.P2PMessageType(msg.Type) { - case types.P2PTaskRequest: - if c.callbackHandler != nil { - return c.callbackHandler.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.callbackHandler != nil { - _ = c.callbackHandler.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.callbackHandler != nil { - _ = c.callbackHandler.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.callbackHandler != nil { - _ = c.callbackHandler.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.callbackHandler != nil { - _ = c.callbackHandler.HandleNeedHuman(ctx, msg) - } - return c.ackMessage(msg), nil - - case types.P2PCapabilityQuery: - return nil, nil - - default: - return c.ackMessage(msg), nil - } -} - -func (c *Client) ackMessage(msg *types.UnifiedSynapseMessage) *types.UnifiedSynapseMessage { - return &types.UnifiedSynapseMessage{ - ID: types.GenerateMessageID(), - Version: msg.Version, - Type: string(types.P2PAck), - Sender: c.agentEndpoint, - Receiver: &msg.Sender, - ReplyTo: msg.ID, - Payload: []byte("{}"), - Timestamp: types.NowTimestamp(), - } -} diff --git a/sdk/a2n/client.go b/sdk/a2n/client.go deleted file mode 100644 index 31c8811..0000000 --- a/sdk/a2n/client.go +++ /dev/null @@ -1,346 +0,0 @@ -package a2n - -import ( - "context" - "fmt" - "sync" - "time" - - "synapse/types" -) - -// ============================================================================= -// Synapse Group SDK (A2N Protocol) -// 多 Agent 群组协作客户端 SDK -// 基于 A2N 协议:多个 Agent 组成群组进行协同工作 -// ============================================================================= - -// GroupEventHandler 群组事件处理器接口 -type GroupEventHandler 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 -} - -// Client A2N 群组协作客户端 -type Client struct { - a2aClient *a2aClient - groups map[string]*GroupState - groupsMu sync.RWMutex - eventHandler GroupEventHandler -} - -// a2aClient 是 P2P 客户端的简化接口 -type a2aClient interface { - SendMessage(ctx context.Context, target types.AgentEndpoint, msg *types.UnifiedSynapseMessage) error - AgentEndpoint() types.AgentEndpoint -} - -// ClientConfig A2N 客户端配置 -type ClientConfig struct { - P2PClient *a2aClient - EventHandler GroupEventHandler -} - -// GroupState 群组状态 -type GroupState struct { - GroupID string - Name string - Topology types.GroupTopology - Status types.GroupStatus - Members []types.GroupMember - Role string -} - -// NewClient 创建新的 A2N 群组客户端 -func NewClient(a2aClient interface{}, eventHandler GroupEventHandler) (*Client, error) { - return &Client{ - groups: make(map[string]*GroupState), - eventHandler: eventHandler, - }, nil -} - -// SetEventHandler 设置群组事件处理器 -func (c *Client) SetEventHandler(handler GroupEventHandler) { - c.eventHandler = handler -} - -// ============================================================================= -// 群组管理 -// ============================================================================= - -// 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, - ConsensusThreshold: 0.5, - BroadcastMode: "all", - } - } - - members := make([]types.GroupMember, 0, len(initialMembers)+1) - for _, m := range initialMembers { - members = append(members, types.GroupMember{ - AgentID: m.AgentID, - AgentName: m.AgentName, - Role: "worker", - JoinedAt: types.NowTimestamp(), - Status: types.GroupMemberActive, - Capabilities: []string{}, - LoadFactor: 0, - }) - } - - 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 提交群组任务(作为 coordinator 拆分任务) -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 -} - -// ============================================================================= -// 消息处理 -// ============================================================================= - -// HandleMessage 处理群组消息 -func (c *Client) HandleMessage(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 -} diff --git a/sdk/doc.go b/sdk/doc.go deleted file mode 100644 index 0b33fe6..0000000 --- a/sdk/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package sdk 灵隙(Synapse)协议 SDK -// -// 灵隙协议族(Synapse Protocol Suite)Go 语言 SDK,包含三个子包: -// -// - sdk/p2a: P2A 协议 - 非 Agent 调用方通过 REST API 调用 Agent 能力 -// - sdk/a2a: A2A 协议 - Agent 之间的点对点任务协作通信 -// - sdk/a2n: A2N 协议 - 多 Agent 群组协作(群组管理、任务拆分合并、投票) -// -// 协议版本:1.0.0 -package sdk diff --git a/sdk/p2a/client.go b/sdk/p2a/client.go deleted file mode 100644 index 1bafa60..0000000 --- a/sdk/p2a/client.go +++ /dev/null @@ -1,547 +0,0 @@ -package p2a - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "time" -) - -// ============================================================================= -// Synapse Caller SDK (P2A Protocol) -// 非Agent调用方使用的客户端SDK -// 基于 P2A 协议:非 Agent 调用方通过 REST API 调用 Agent 能力 -// ============================================================================= - -// Client 是 Synapse P2A 服务的外部调用客户端 -type Client struct { - baseURL string - apiKey string - httpClient *http.Client -} - -// ClientConfig 客户端配置 -type ClientConfig struct { - BaseURL string - APIKey string - Timeout time.Duration - MaxRetries int -} - -// NewClient 创建新的 Synapse Caller 客户端 -func NewClient(config ClientConfig) *Client { - if config.Timeout == 0 { - config.Timeout = 30 * time.Second - } - return &Client{ - baseURL: config.BaseURL, - apiKey: config.APIKey, - httpClient: &http.Client{Timeout: config.Timeout}, - } -} - -// ============================================================================= -// 通用响应结构 -// ============================================================================= - -// APIResponse 通用 API 响应 -type APIResponse struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Error string `json:"error,omitempty"` -} - -// TaskResponse 任务提交响应 -type TaskResponse struct { - APIResponse - TaskID string `json:"task_id"` - Status string `json:"status"` - Message string `json:"message,omitempty"` -} - -// TaskStatusResponse 任务状态响应(简化版) -type TaskStatusResponse struct { - APIResponse - TaskID string `json:"task_id"` - Status string `json:"status"` - Progress int `json:"progress"` -} - -// TaskDetailResponse 任务详情响应 -type TaskDetailResponse struct { - APIResponse - Task TaskInfo `json:"task"` -} - -// TaskListResponse 任务列表响应 -type TaskListResponse struct { - APIResponse - Tasks []TaskInfo `json:"tasks"` - Total int `json:"total"` - Limit int `json:"limit"` - Offset int `json:"offset"` - HasMore bool `json:"has_more"` -} - -// 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"` -} - -// 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"` -} - -// FileParam 上传文件参数 -type FileParam struct { - FieldName string - Filename string - Data []byte - MimeType string -} - -// ============================================================================= -// 能力查询 -// ============================================================================= - -// CapabilitiesResponse 能力查询响应 -type CapabilitiesResponse struct { - APIResponse - Capabilities CapabilitiesInfo `json:"capabilities"` -} - -// 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"` -} - -// GetCapabilities 获取 Agent 能力清单 -func (c *Client) GetCapabilities(ctx context.Context) (*CapabilitiesResponse, error) { - url := fmt.Sprintf("%s/api/v1/synapse/capabilities", c.baseURL) - - 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 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 -} - -// ============================================================================= -// 任务提交 -// ============================================================================= - -// SubmitTask 提交任务(JSON 方式) -func (c *Client) SubmitTask(ctx context.Context, req *TaskRequest) (*TaskResponse, error) { - url := fmt.Sprintf("%s/api/v1/synapse/task", c.baseURL) - - body, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %w", err) - } - - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, 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 提交带文件的任务(multipart/form-data 方式) -func (c *Client) SubmitTaskWithFiles(ctx context.Context, req *TaskRequest, files []FileParam) (*TaskResponse, error) { - url := fmt.Sprintf("%s/api/v1/synapse/task/multipart", c.baseURL) - - 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", url, &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) { - url := fmt.Sprintf("%s/api/v1/synapse/task/%s", c.baseURL, taskID) - - 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 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) { - url := fmt.Sprintf("%s/api/v1/synapse/task/%s/status", c.baseURL, taskID) - - 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 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.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) { - url := fmt.Sprintf("%s/api/v1/synapse/task/%s/cancel", c.baseURL, taskID) - - httpReq, err := http.NewRequestWithContext(ctx, "POST", 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 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 -} - -// ============================================================================= -// 等待任务完成 -// ============================================================================= - -// WaitForTask 等待任务完成(轮询方式) -func (c *Client) WaitForTask(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 - } - } - } -} - -// ============================================================================= -// 回调验证 -// ============================================================================= - -// 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"` -} - -// VerifyCallback 验证回调请求签名 -func (c *Client) VerifyCallback(r *http.Request, callbackToken string) bool { - if callbackToken == "" { - return true - } - token := r.Header.Get("X-Callback-Token") - return token == callbackToken -} - -// ============================================================================= -// 高阶 API: 快速创建任务并等待 -// ============================================================================= - -// 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.WaitForTask(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) -} - -// ============================================================================= -// 辅助方法 -// ============================================================================= - -func (c *Client) setAuthHeaders(req *http.Request) { - if c.apiKey != "" { - req.Header.Set("X-API-Key", c.apiKey) - } - req.Header.Set("Accept", "application/json") -} diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29