Skip to content

Commit 2527428

Browse files
authored
[ISSUE #5259] Add A2A Gateway: REST API, SSE streaming, Task lifecycle, Java SDK and tests (#5260)
* [EVENTMESH-xxx] Add A2A Gateway: REST API, SSE streaming, Task lifecycle, Java SDK and tests ## What's New ### A2A Gateway Runtime (eventmesh-runtime) - A2AGatewayServer: standalone HTTP server entry point - A2AGatewayService: core gateway logic with task timeout, agent validation, cancel notification, and concurrent-safe status subscribers - A2AGatewayHttpHandler: Netty-based REST handler with CORS, SSE streaming, health check, task list pagination, and precise error codes - TaskRegistry: in-memory task lifecycle management with TTL cleanup, CopyOnWriteArrayList for parent-child concurrency safety - InMemoryA2AMessageTransport: default transport implementation - A2ACardHttpHandler: enhanced with CORS headers ### A2A Protocol Plugin (eventmesh-protocol-a2a) - A2AClient: typed Java SDK with sync/async task send, SSE streaming (streamTaskStatus), agent discovery, heartbeat, and SSE-aware socket timeout - A2AMessageTransport: transport abstraction interface - A2ATopicFactory: A2A topic convention standardization - A2ATopicFactoryTest: unit tests ### Examples - A2AGatewayDemo: end-to-end HTTP client demo ### Tests (all passing) - A2AGatewayServiceTest: 15+ tests covering timeout, agent validation, cancel, SSE, pagination - TaskRegistryTest: TTL, concurrent child creation, lifecycle transitions - A2AClientServerIntegrationTest: full client-server integration with SSE streaming, CORS, health, heartbeat - A2AGatewayEndToEndTest: end-to-end pipeline verification - InMemoryA2AMessageTransportTest: transport unit tests ### Documentation (7 files updated) - eventmesh-a2a-design.md, ARCHITECTURE.md, IMPLEMENTATION_SUMMARY.md, IMPLEMENTATION_SUMMARY_EN.md, TEST_RESULTS.md, README.md, README_EN.md ### Key Engineering Decisions - Task timeout: auto-fail non-terminal tasks after configurable threshold - SSE: heartbeat mechanism + socketTimeout(0) for long-lived connections - Concurrency: CopyOnWriteArrayList + ConcurrentHashMap throughout - CORS: Access-Control-Allow-Origin on all A2A endpoints - Agent validation: reject tasks for unregistered agents * fix: fix Javadoc bad use of '>' in A2AGatewayDemo flow diagram Replace '-->' arrow notation with HTML entity '&gt;' in Javadoc <pre> block to fix javadoc compilation error: 'bad use of ">". The '>' character inside <pre> blocks in Javadoc must be escaped as '&gt;' when it appears after '-' characters (forming '-->' which Java parser misinterprets as an HTML comment end marker). Fixes: CodeQL Analyze (java) and all Build CI failures in PR #5260. * fix: escape '>' in TaskRegistry Javadoc state machine diagram Replace '->' with '-&gt;' in Javadoc <pre> block to avoid javadoc 'bad use of >' compilation error. The Java parser interprets '>' in <pre> blocks as HTML tag closers. Fixes: remaining CI build failure in PR #5260 after first fix. * fix: resolve all checkstyle violations in A2A module checkstyleMain (4 files, 8 violations): - Remove unused imports (ArrayList, Objects) - Fix import ordering per project convention: static first, then org.apache.eventmesh, org.apache, java, io, org.junit, com - Replace Unicode escape (\u00b0) with literal degree symbol - Add missing Javadoc for StatusSubscriber interface checkstyleTest (5 files, 9 violations): - Move static imports to top (option=top in checkstyle config) - Fix import group ordering (io before org per groups config) - Remove unused import (assertNull) - Fix VariableDeclarationUsageDistance by moving declarations closer to first usage Fixes: third round of CI failures in PR #5260 after javadoc fixes. * fix: correct import group ordering in A2A module checkstyle groups order: org.apache.eventmesh,org.apache,java,javax,org,io,net,junit,com,lombok - Move org.junit.jupiter.* (org group) before io.cloudevents.* (io group) in test files - Separate com.fasterxml and lombok into their own groups with blank lines in A2AGatewayService Fixes: 4th round checkstyle violations in PR #5260 * fix: stabilize flaky testHeartbeatRefreshesTtl test Increase TTL from 200ms to 500ms and cleanup interval from 100ms to 250ms to prevent race condition on slow CI runners (macOS). Add assertion messages for better debugging. The test was flaky because Thread.sleep(100) could exceed 100ms on overloaded CI runners, causing the cleanup to expire the card before heartbeat check. Fixes: 5th round CI failure on macOS in PR #5260 * fix: resolve checkstyle violations in protocol-a2a module - A2AMessageTransport.java: add Javadoc for MessageCallback interface - A2AClient.java: remove unused java.util.function.Consumer import - A2AClient.java: fix import order (org.apache.eventmesh, org.apache, java, org, io, com per checkstyle groups) - A2AClient.java: add Javadoc for RequestHandler interface Fixes: 6th round CI failure (protocol-a2a checkstyle) in PR #5260 * fix: static import ordering in A2ATopicFactoryTest - Move static imports to top (option=top in checkstyle) - Fix alphabetical order: assertFalse before assertNotNull Fixes: 7th round CI failure (protocol-a2a checkstyleTest) in PR #5260
1 parent 7cfbede commit 2527428

27 files changed

Lines changed: 6084 additions & 37 deletions

File tree

docs/a2a-protocol/ARCHITECTURE.md

Lines changed: 164 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,80 @@ graph TD
6464

6565
### 3.2 Component Design (`eventmesh-protocol-a2a`)
6666

67-
The core logic resides in the `eventmesh-protocol-plugin` module.
67+
The core protocol logic resides in the `eventmesh-protocol-plugin` module.
6868

6969
* **`EnhancedA2AProtocolAdaptor`**: The central brain of the protocol.
7070
* **Intelligent Parsing**: Automatically detects message format (MCP vs. Raw CloudEvent).
7171
* **Protocol Delegation**: Delegates to `CloudEvents` or `HTTP` adaptors when necessary.
7272
* **Semantic Mapping**: Transforms JSON-RPC methods and IDs into CloudEvent attributes.
7373
* **`A2AProtocolConstants`**: Defines standard operations like `task/get`, `message/sendStream`.
7474
* **`JsonRpc*` Models**: Strictly typed POJOs for JSON-RPC 2.0 compliance.
75+
* **`AgentCard` / `AgentSkill` / `AgentInterface`**: Agent capability discovery models.
76+
* **`A2ATopicFactory`**: Topic naming and parsing utility for request/response/status topics.
77+
* **`A2AClient`**: Java SDK for agent developers — AgentCard registration, task submission (sync/async), task status query, heartbeat, and transport-based request handling. Returns typed `TaskResult` objects.
78+
* **`A2AMessageTransport`**: Transport-agnostic pub/sub interface (InMemory implementation for dev/testing).
7579

76-
### 3.3 Asynchronous RPC Mapping ( The "Async Bridge" )
80+
### 3.3 Gateway Runtime Architecture (`eventmesh-runtime`)
81+
82+
The Gateway runtime provides a standalone Netty HTTP server bridging external clients to the A2A event bus.
83+
84+
```mermaid
85+
graph TD
86+
Client["Client / A2AClient SDK"] -- "HTTP REST" --> Server["A2AGatewayServer<br/>(Netty HTTP)"]
87+
Server --> Handler["A2AGatewayHttpHandler"]
88+
Handler --> GwService["A2AGatewayService"]
89+
GwService --> Registry["TaskRegistry<br/>(state machine + TTL)"]
90+
GwService --> Transport["InMemoryA2AMessageTransport"]
91+
GwService --> PubSub["A2APublishSubscribeService<br/>(AgentCard discovery)"]
92+
Transport -- "publish/subscribe" --> Agent["Target Agent"]
93+
Agent -- "response event" --> Transport
94+
Transport --> GwService
95+
96+
style Server fill:#f9f,stroke:#333,stroke-width:2px
97+
style Registry fill:#cfc,stroke:#333
98+
style Transport fill:#ccf,stroke:#333
99+
```
100+
101+
#### Core Components
102+
103+
| Component | Module | Responsibility |
104+
| :--- | :--- | :--- |
105+
| `A2AGatewayServer` | runtime | Standalone Netty HTTP server entry point. Pre-registers mock agents, wires all components. |
106+
| `A2AGatewayHttpHandler` | runtime | HTTP request router. Maps REST endpoints to service calls. Supports SSE streaming. |
107+
| `A2AGatewayService` | runtime | Core orchestration: task submission, response handling, status subscription, SSE push. |
108+
| `TaskRegistry` | runtime | In-memory task lifecycle state machine with TTL auto-cleanup. |
109+
| `A2APublishSubscribeService` | runtime | AgentCard registration, discovery, and heartbeat management. |
110+
| `InMemoryA2AMessageTransport` | runtime | In-memory pub/sub (replaceable by EventMesh broker). |
111+
| `A2ACardHttpHandler` | runtime | AgentCard CRUD REST endpoints (`/a2a/cards/*`). |
112+
| `A2AClient` | protocol-a2a | Java SDK for agent developers (HTTP + transport). |
113+
114+
#### Task Lifecycle State Machine
115+
116+
```
117+
SUBMITTED → WORKING → COMPLETED
118+
↘ FAILED
119+
↘ CANCELLED
120+
```
121+
122+
* **TTL Auto-Cleanup**: Terminal-state tasks are automatically removed after a configurable TTL (default: 5 minutes). A daemon thread runs cleanup every 60 seconds.
123+
* **Race Condition Prevention**: `pendingTasks.put(taskId, future)` is called **before** `transport.publish()` to ensure the future is registered before any synchronous delivery could trigger `handleResponse()`.
124+
125+
#### REST API
126+
127+
| Method | Path | Description |
128+
| :--- | :--- | :--- |
129+
| `POST` | `/a2a/tasks?mode=sync` | Submit task synchronously (wait for result) |
130+
| `POST` | `/a2a/tasks?mode=async` | Submit task asynchronously (return taskId immediately) |
131+
| `GET` | `/a2a/tasks/{taskId}` | Get task status and result |
132+
| `DELETE` | `/a2a/tasks/{taskId}` | Cancel a task |
133+
| `GET` | `/a2a/tasks/{taskId}/wait` | Long-poll wait for task result |
134+
| `GET` | `/a2a/tasks/{taskId}/stream` | **SSE** stream of task status updates |
135+
| `GET` | `/a2a/agents` | List all registered agents |
136+
| `POST` | `/a2a/heartbeat` | Agent heartbeat |
137+
| `GET` | `/a2a/cards/list` | List all AgentCards |
138+
| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | Register an AgentCard |
139+
140+
### 3.4 Asynchronous RPC Mapping ( The "Async Bridge" )
77141

78142
To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchronous events:
79143

@@ -113,6 +177,18 @@ To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchron
113177
* **Operation**: `message/sendStream`
114178
* **Mechanism**: Maps to `.stream` event type and preserves sequence order via `seq` extension attribute.
115179

180+
#### D. SSE Task Streaming (Gateway)
181+
* **Endpoint**: `GET /a2a/tasks/{taskId}/stream`
182+
* **Mechanism**: Server-Sent Events (`text/event-stream`) pushes real-time task state transitions.
183+
* **Flow**: Initial state → WORKING updates → terminal state → connection close.
184+
* **Implementation**: Handler writes `DefaultHttpContent` chunks directly to the Netty channel, returning `null` to skip the standard `FullHttpResponse` path.
185+
186+
#### E. Task TTL Auto-Cleanup (Gateway)
187+
* Terminal-state tasks are automatically removed by a daemon scheduler after a configurable TTL (default: 5 minutes, cleanup interval: 60 seconds), preventing memory leaks.
188+
189+
#### F. AgentCard Discovery & Heartbeat (Gateway)
190+
* AgentCards expire after 60 seconds without heartbeat. `POST /a2a/heartbeat` refreshes the last-seen timestamp.
191+
116192
## 5. Usage Examples
117193

118194
### 5.1 Sending a Tool Call (Request)
@@ -149,7 +225,92 @@ To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchron
149225
* `subject`: `market.crypto.btc`
150226
* `targetagent`: (Empty)
151227

228+
### 5.3 Gateway REST API (HTTP)
229+
230+
The A2A Gateway provides a REST API for external clients and non-Java agents.
231+
232+
#### 5.3.1 Submit Task (Sync)
233+
234+
```bash
235+
curl -X POST 'http://localhost:10105/a2a/tasks?mode=sync' \
236+
-H 'Content-Type: application/json' \
237+
-d '{"targetAgent":"weather-agent","message":"Beijing"}'
238+
```
239+
240+
Response:
241+
```json
242+
{
243+
"taskId": "task-a1b2c3d4",
244+
"state": "COMPLETED",
245+
"data": "The weather in Beijing is sunny, 25°C"
246+
}
247+
```
248+
249+
#### 5.3.2 Submit Task (Async)
250+
251+
```bash
252+
curl -X POST 'http://localhost:10105/a2a/tasks?mode=async' \
253+
-H 'Content-Type: application/json' \
254+
-d '{"targetAgent":"weather-agent","message":"Shanghai"}'
255+
```
256+
257+
#### 5.3.3 SSE Stream
258+
259+
```bash
260+
curl -N http://localhost:10105/a2a/tasks/{taskId}/stream
261+
```
262+
263+
Response (`text/event-stream`):
264+
```
265+
data: {"taskId":"task-a1b2c3d4","state":"SUBMITTED"}
266+
267+
data: {"taskId":"task-a1b2c3d4","state":"WORKING","data":"processing..."}
268+
269+
data: {"taskId":"task-a1b2c3d4","state":"completed","data":"result..."}
270+
```
271+
272+
#### 5.3.4 List Agents
273+
274+
```bash
275+
curl http://localhost:10105/a2a/agents
276+
```
277+
278+
### 5.4 A2AClient SDK (Java)
279+
280+
```java
281+
A2AClient client = A2AClient.builder()
282+
.gatewayUrl("http://localhost:10105")
283+
.namespace("global")
284+
.agentName("my-agent")
285+
.agentCard(card)
286+
.heartbeatInterval(30_000)
287+
.build();
288+
289+
client.start();
290+
291+
// Synchronous task (returns typed TaskResult)
292+
TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
293+
294+
// Asynchronous task (returns taskId immediately)
295+
String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
296+
297+
// Poll status
298+
TaskResult status = client.getTaskStatus(taskId);
299+
300+
// Cancel
301+
boolean cancelled = client.cancelTask(taskId);
302+
303+
// List registered agents (typed List<String>)
304+
List<String> agents = client.listAgents();
305+
306+
client.shutdown();
307+
```
308+
152309
## 6. Future Roadmap
153310

311+
* **EventMesh Broker Integration**: Replace `InMemoryA2AMessageTransport` with the real EventMesh broker for production deployment.
154312
* **Schema Registry**: Implement dynamic discovery of Agent capabilities via `methods/list`.
155-
* **Sidecar Injection**: Fully integrate the adaptor into the EventMesh Sidecar.
313+
* **Sidecar Injection**: Fully integrate the adaptor into the EventMesh Sidecar.
314+
* **WebSocket Streaming**: Extend SSE to bidirectional WebSocket for real-time agent dialogue.
315+
* **Task Persistence**: Persist `TaskRegistry` state to a durable store for crash recovery.
316+
* **Authentication**: Add API key / JWT authentication to the Gateway REST API.

docs/a2a-protocol/IMPLEMENTATION_SUMMARY.md

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A2A 协议已成功重构为采用 **MCP (Model Context Protocol)** 架构,将
1313
- **请求 (Requests)** 映射为 `*.req` 事件,属性 `mcptype=request`
1414
- **响应 (Responses)** 映射为 `*.resp` 事件,属性 `mcptype=response`
1515
- **关联 (Correlation)** 通过将 JSON-RPC `id` 映射到 CloudEvent `collaborationid` 来处理。
16-
- **路由优化**: 实现了深度内容路由提取
16+
- **路由优化**: 实现了"深度内容路由提取"
1717
- `params._agentId` -> CloudEvent 扩展属性 `targetagent` (P2P)。
1818
- `params._topic` -> CloudEvent Subject (Pub/Sub)。
1919

@@ -24,14 +24,80 @@ A2A 协议已成功重构为采用 **MCP (Model Context Protocol)** 架构,将
2424
### 3. 标准化与兼容性
2525
- **数据模型**: 定义了符合 JSON-RPC 2.0 规范的 `JsonRpcRequest``JsonRpcResponse``JsonRpcError` POJO 对象。
2626
- **方法定义**: 引入了 `McpMethods` 常量,支持标准操作如 `tools/call``resources/read`
27+
- **AgentCard 模型**: 实现了 `AgentCard``AgentSkill``AgentInterface``AgentCapabilities` 等完整的 Agent 能力描述模型。
2728

28-
### 4. 测试与质量
29-
- **单元测试**: 在 `EnhancedA2AProtocolAdaptorTest` 中实现了对请求/响应循环、错误处理、通知和批处理的全面覆盖。
30-
- **集成演示**: `McpIntegrationDemoTest` 模拟了 P2P RPC 闭环。
31-
- **模式测试**: `McpPatternsIntegrationTest` 模拟了 Pub/Sub 和 Streaming 流程。
29+
### 4. Gateway 运行时架构 (`eventmesh-runtime`)
30+
31+
完整的独立 HTTP Gateway 服务,桥接外部客户端到 A2A 事件总线。
32+
33+
#### 核心组件
34+
35+
| 组件 | 职责 |
36+
| :--- | :--- |
37+
| `A2AGatewayServer` | Netty HTTP 服务器入口,预注册 mock agent,组装所有组件 |
38+
| `A2AGatewayHttpHandler` | HTTP 请求路由,支持 SSE 流式响应 |
39+
| `A2AGatewayService` | 核心编排:任务提交、响应处理、状态订阅、SSE 推送 |
40+
| `TaskRegistry` | 内存任务状态机 + TTL 自动清理 |
41+
| `A2APublishSubscribeService` | AgentCard 注册、发现、心跳管理 |
42+
| `InMemoryA2AMessageTransport` | 内存 pub/sub 实现(可替换为 EventMesh broker) |
43+
| `A2ACardHttpHandler` | AgentCard CRUD REST 端点 |
44+
| `A2AClient` | Java SDK,提供类型化 API |
45+
46+
#### REST API
47+
48+
| 方法 | 路径 | 说明 |
49+
| :--- | :--- | :--- |
50+
| `POST` | `/a2a/tasks?mode=sync` | 同步提交任务 |
51+
| `POST` | `/a2a/tasks?mode=async` | 异步提交任务 |
52+
| `GET` | `/a2a/tasks/{taskId}` | 查询任务状态 |
53+
| `DELETE` | `/a2a/tasks/{taskId}` | 取消任务 |
54+
| `GET` | `/a2a/tasks/{taskId}/wait` | 长轮询等待结果 |
55+
| `GET` | `/a2a/tasks/{taskId}/stream` | SSE 流式推送状态更新 |
56+
| `GET` | `/a2a/agents` | 列出已注册 agents |
57+
| `POST` | `/a2a/heartbeat` | Agent 心跳 |
58+
| `GET` | `/a2a/cards/list` | 列出所有 AgentCard |
59+
| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | 注册 AgentCard |
60+
61+
### 5. 关键改进
62+
63+
#### 5.1 TaskRegistry TTL 自动清理
64+
- **问题**: 终态任务(COMPLETED/FAILED/CANCELLED)无限累积导致内存泄漏。
65+
- **方案**: 守护线程 `ScheduledExecutorService` 每 60 秒扫描一次,清理超过 TTL(默认 5 分钟)的终态任务。
66+
- **配置**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` 构造函数支持自定义调优。
67+
68+
#### 5.2 竞态条件修复
69+
- **问题**: `InMemoryTransport` 同步投递消息,若 `transport.publish()``pendingTasks.put()` 之前执行,`handleResponse()` 会先于 `put()` 运行,导致 future 永不完成。
70+
- **方案**: 严格保证 `pendingTasks.put(taskId, future)``transport.publish()` 之前执行,并添加注释说明顺序重要性。
71+
72+
#### 5.3 A2AClient 类型化返回
73+
- **改进**: `getTaskStatus()` 返回 `TaskResult` 对象(而非原始 JSON 字符串),`listAgents()` 返回 `List<String>`(而非原始 JSON)。
74+
- **兼容**: `TaskResult.data` 字段使用 `@JsonAlias("result")` 注解,兼容服务端 `result` 字段名。
75+
76+
#### 5.4 SSE 流式响应
77+
- **端点**: `GET /a2a/tasks/{taskId}/stream`
78+
- **实现**: Handler 直接写入 Netty channel(`DefaultHttpContent` chunks),返回 `null` 跳过标准 `FullHttpResponse` 路径。通过 `StatusSubscriber` 回调实时推送状态变更。
79+
80+
#### 5.5 使用文档
81+
- 新建 `eventmesh-examples/.../demo/README.md`,包含架构图、API 表、curl 示例、SDK 用法、运行方式。
82+
83+
### 6. 测试与质量
84+
- **协议层单元测试**: `EnhancedA2AProtocolAdaptorTest` 覆盖请求/响应循环、错误处理、通知和批处理。
85+
- **Topic 工具测试**: `A2ATopicFactoryTest` 覆盖 topic 生成与解析。
86+
- **Gateway 运行时测试**:
87+
- `TaskRegistryTest` — 任务状态机 + TTL 清理验证
88+
- `InMemoryA2AMessageTransportTest` — 内存传输投递
89+
- `A2AGatewayServiceTest` — Gateway 服务层
90+
- `A2AGatewayEndToEndTest` — 进程内全链路
91+
- `A2AClientServerIntegrationTest` — 真实 HTTP 客户端-服务端集成测试
92+
- **集成演示**: `McpIntegrationDemoTest``McpPatternsIntegrationTest``McpComprehensiveDemoTest``CloudEventsComprehensiveDemoTest`
93+
- **总计**: 73 个测试场景,全部通过。
3294

3395
## 下一步计划
3496

35-
1. **路由集成**: 更新 EventMesh Runtime Router,利用新的 `targetagent``a2amethod` 扩展属性实现高级路由规则。
36-
2. **Schema 注册中心**: 实现一个“注册中心智能体 (Registry Agent)”,允许智能体动态发布其 MCP 能力 (`methods/list`)。
37-
3. **Sidecar 支持**: 将 A2A 适配器逻辑暴露在 Sidecar 代理中,允许非 Java 智能体 (Python, Node.js) 通过简单的 HTTP/JSON 进行交互。
97+
1. **EventMesh Broker 集成**: 用真实 EventMesh broker 替换 `InMemoryA2AMessageTransport`,实现生产级部署。
98+
2. **路由集成**: 更新 EventMesh Runtime Router,利用 `targetagent``a2amethod` 扩展属性实现高级路由规则。
99+
3. **Schema 注册中心**: 实现"注册中心智能体 (Registry Agent)",允许智能体动态发布 MCP 能力 (`methods/list`)。
100+
4. **Sidecar 支持**: 将 A2A 适配器逻辑暴露在 Sidecar 代理中,允许非 Java 智能体通过 HTTP/JSON 交互。
101+
5. **WebSocket 流式**: 将 SSE 扩展为双向 WebSocket,支持实时 agent 对话。
102+
6. **任务持久化**: 将 `TaskRegistry` 状态持久化到 Redis/DB,支持崩溃恢复。
103+
7. **认证授权**: 为 Gateway REST API 添加 API Key / JWT 认证。

0 commit comments

Comments
 (0)