在 Cloudflare Agents 上构建 A2A 协议服务器:Agent Card 发现、JSON-RPC 传输与 SSE 流式示例全解析
【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents
本教程以examples/a2a示例为蓝本,讲解如何把一个基于 Cloudflare Agents 框架构建的 AI Agent 暴露为标准 A2A(Agent-to-Agent)协议服务器,并配套一个浏览器端的 A2A 客户端 UI。读完本文,你将掌握 A2A Agent Card 发现机制、基于@a2a-js/sdk的 JSON-RPC 传输与 SSE 流式推送、以及用 Durable Object SQLite 持久化任务状态的完整实战方案。
示例概览:Cloudflare Agent 如何成为 A2A 服务器
examples/a2a是 Cloudflare Agents 仓库中一个端到端的 A2A 示例,它的核心思路非常清晰:复用 SDK 而不是手写协议。服务器端使用@a2a-js/sdk提供的DefaultRequestHandler与JsonRpcTransportHandler组装协议处理链,开发者只需要关心两件事——"任务怎么执行"(实现AgentExecutor)和"任务状态存哪里"(实现TaskStore)。
该示例集中展示了以下五项能力(与 README 一一对应):
- Agent Card 发现:在
/.well-known/agent-card.json提供标准化的 Agent 元数据(协议版本、能力、技能、端点 URL 等),任何 A2A 客户端都能先发现、再交互; - JSON-RPC 传输:通过
@a2a-js/sdk服务器端实现message/send与message/stream两种方法; - SSE 流式推送:
message/stream的返回以text/event-stream形式实时推送任务状态更新,用户能看到"提交 → 工作中 → 完成"的全过程; - DO-backed TaskStore:使用 Durable Object SQLite 作为任务持久化存储,任务状态跨请求可恢复;
- Workers AI 真实推理:AI 响应由 Cloudflare Workers AI 提供(Agent Card 描述中标注为 GLM 4.7 Flash,而
server.ts源码实际调用的是@cf/moonshotai/kimi-k2.7-code模型)。
快速运行:三行命令跑通完整链路
在examples/a2a目录下执行:
npm install npm startnpm start实际运行的是vite dev(见 package.json),通过@cloudflare/vite-plugin在本地同时拉起 Worker 与前端静态资源。启动后打开 http://localhost:5173 即可使用聊天 UI。
任何 A2A 客户端都可以先通过 Agent Card 发现这个 Agent:
curl http://localhost:5173/.well-known/agent-card.json该端点同时支持.well-known/agent-card.json与.well-known/agent.json两个路径(见 server.ts),返回的 Agent Card 带有Access-Control-Allow-Origin: *头,便于跨域客户端读取。Agent Card 中的url字段指向http://localhost:5173/a2a,即 JSON-RPC 端点。
生产部署使用npm run deploy,等价于vite build && wrangler deploy。
关键模式:DefaultRequestHandler + AgentExecutor,不手写协议
这是整个示例最值得借鉴的设计。A2A 协议本身包含 JSON-RPC 信封、方法分发、参数校验、SSE 流式封装等一系列样板逻辑,手写很容易出错。示例直接使用 SDK 的服务器端组件把协议层与业务层解耦:
const handler = new DefaultRequestHandler(agentCard, taskStore, executor); const transport = new JsonRpcTransportHandler(handler);DefaultRequestHandler接收三个参数:Agent Card(协议元数据)、TaskStore(任务持久化实现)、AgentExecutor(实际执行任务的业务逻辑)。它负责解析 JSON-RPC 请求、分发到message/send/message/stream/message/cancel等方法;JsonRpcTransportHandler负责处理 JSON-RPC 2.0 信封与序列化;- 业务方只需实现
AgentExecutor接口,通过ExecutionEventBus发布生命周期事件。
在 MyA2A 构造函数 中可以看到三者的组装方式:
export class MyA2A extends Agent<Env> { constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); const taskStore = new DurableObjectTaskStore(ctx.storage.sql); const executor = new AIAgentExecutor(() => this.env); this.handler = new DefaultRequestHandler(agentCard, taskStore, executor); this.transport = new JsonRpcTransportHandler(this.handler); } }MyA2A继承自agents框架的Agent<Env>基类,因此天然具备 Durable Object 的持久化能力,ctx.storage.sql可以直接拿来做任务存储。
AgentExecutor:用事件总线表达任务生命周期
AIAgentExecutor完整展示了 A2A 任务的状态机流转。它利用ExecutionEventBus依次发布submitted→working→ (AI 响应消息)→completed事件:
class AIAgentExecutor implements AgentExecutor { async execute(ctx: RequestContext, bus: ExecutionEventBus) { // 新任务先发布 submitted 状态 if (!task) { bus.publish({ id: taskId, contextId, history: [userMessage], kind: "task", status: { state: "submitted", timestamp: ... } }); } // 发布 working 状态 bus.publish({ kind: "status-update", status: { state: "working", ... }, ... }); // 调用 Workers AI const result = await generateText({ model, messages }); // 发布 agent 回复消息 bus.publish({ kind: "message", role: "agent", parts: [{ kind: "text", text: result.text }], ... }); // 发布 completed 状态(final: true,携带回复消息) bus.publish({ kind: "status-update", final: true, status: { state: "completed", message: responseMessage }, ... }); bus.finished(); } cancelTask = async (): Promise<void> => {}; }要点在于:status-update事件的final: true标记任务终结,同时可携带message字段把最终回复一并推送;bus.finished()通知执行流结束。cancelTask在示例中是空实现,生产环境中应在此实现任务取消逻辑。
AI 调用:workers-ai-provider + Vercel AI SDK
AI 推理部分没有直接调用 Workers AI 的 REST API,而是用workers-ai-provider把它适配为 Vercel AI SDK 的模型接口,再交给generateText:
const workersai = createWorkersAI({ binding: this.getEnv().AI }); const result = await generateText({ model: workersai("@cf/moonshotai/kimi-k2.7-code"), instructions: "You are a helpful AI assistant. Keep responses concise and clear.", messages: [{ role: "user", content: userText }] });用户消息中的文本通过过滤parts中kind === "text"的部分拼接而成(见 server.ts),这也体现了 A2AMessage多模态 parts 结构的使用方式。
任务持久化:Durable Object SQLite 版 TaskStore
A2A 协议要求任务状态可查询、可恢复,因此TaskStore必须有真实的存储后端。示例给出了一个仅 20 余行的最小实现DurableObjectTaskStore(见 server.ts),直接构建在 Durable Object 的 SQLite 之上:
class DurableObjectTaskStore implements TaskStore { constructor(private sql: SqlStorage) { this.sql.exec(` CREATE TABLE IF NOT EXISTS a2a_tasks ( id TEXT PRIMARY KEY, data TEXT NOT NULL ) `); } async save(task: Task): Promise<void> { this.sql.exec( "INSERT OR REPLACE INTO a2a_tasks (id, data) VALUES (?, ?)", task.id, JSON.stringify(task) ); } async load(taskId: string): Promise<Task | undefined> { const rows = [...this.sql.exec("SELECT data FROM a2a_tasks WHERE id = ?", taskId)]; if (rows.length === 0) return undefined; return JSON.parse(rows[0].data as string) as Task; } }整个 Task 对象被整体序列化为 JSON 存入单表,id为主键、INSERT OR REPLACE实现幂等更新。schema 在构造函数中通过CREATE TABLE IF NOT EXISTS自举创建,无需额外的迁移脚本。得益于 Durable Object 的持久化特性,即使 Worker 重启,任务状态也能从 SQLite 中恢复——这正是 Agent Card 中stateTransitionHistory: true能力声明的基础。
路由与传输:Agent 内部如何分发请求
MyA2A的onRequest方法是协议入口,完整覆盖三条路径(见 server.ts):
- Agent Card 发现:
GET /.well-known/agent-card.json或/.well-known/agent.json,返回handler.getAgentCard(); - CORS 预检:
OPTIONS请求返回允许POST, OPTIONS与Content-Type头的响应头; - JSON-RPC 端点:
POST请求交给transport.handle(body)。
handle的返回值有两种情况,需要分别处理:
- 非流式结果:直接
Response.json(result)返回; - 异步可迭代结果(
message/stream的场景):将异步生成器逐个事件编码为 SSE 格式id: ...\ndata: ...\n\n,通过TransformStream以text/event-stream响应头返回,并设置Cache-Control: no-cache防止中间层缓存。
顶层 Worker 入口则负责把 A2A 相关路径路由到 Durable Object 实例:
export default { async fetch(request: Request, env: Env) { const url = new URL(request.url); if (url.pathname.startsWith("/.well-known/") || url.pathname === "/a2a") { const agent = await getAgentByName(env.MyA2A, "default"); return agent.fetch(request); } return new Response("Not found", { status: 404 }); } } satisfies ExportedHandler<Env>;getAgentByName是agents框架提供的路由助手(实现位于 packages/agents/src/agent-routing.ts),它根据命名空间与实例名返回初始化后的 Agent stub,这里把 "default" 单例实例作为 A2A 服务器的承载者。其余请求直接 404,静态资源则由 Vite 插件托管。
浏览器端 A2A 客户端:零 SDK 依赖的轻量实现
client.tsx展示了一个不打包 SDK、直接用 fetch 实现的 A2A 客户端,非常适合理解协议本质。整个客户端分为三层:
1. Agent Card 发现
组件挂载后先请求同源的/.well-known/agent-card.json:
async function fetchAgentCard(baseUrl: string): Promise<AgentCard> { const res = await fetch(`${baseUrl}/.well-known/agent-card.json`); if (!res.ok) throw new Error(`Failed to fetch agent card: ${res.status}`); return res.json(); }拿到 Agent Card 后,UI 会渲染协议版本徽章、Streaming 能力徽章、skills 列表以及 JSON-RPC 端点 URL(见 client.tsx)。
2. 非流式发送 message/send
sendMessage构造标准 JSON-RPC 2.0 信封,调用message/send方法:
const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: crypto.randomUUID(), method: "message/send", params: { message } }) });3. 流式接收 message/stream
streamMessage是一个异步生成器,负责解析 SSE 流:按\n\n切分事件块、提取data:前缀的 JSON 负载,并将result字段逐个yield出去。由于 SSE 事件可能被 TCP 分片截断,实现里维护了一个buffer变量处理粘包(见 client.tsx)。
客户端根据 Agent Card 的capabilities.streaming决定走哪条路径:支持流式时先用占位气泡展示 "Thinking..." 状态,再随事件流不断刷新文本与状态徽章;不支持时回退到message/send。流式事件中kind为message(agent 回复)、status-update(状态变更,status.message可能携带最终回复)、task(完整任务对象)三种类型都会被消费,这也完整对应了服务器端ExecutionEventBus发布的事件种类。
配置解读:wrangler.jsonc 的四个关键点
示例的 Worker 配置集中在 wrangler.jsonc,理解它才能顺利部署到生产环境:
| 配置项 | 值 | 作用 |
|---|---|---|
main | src/server.ts | Worker 入口,即上面导出的fetch与MyA2A类 |
compatibility_date/compatibility_flags | 2026-06-11/["nodejs_compat"] | 兼容性日期与 Node.js 兼容层,@a2a-js/sdk等依赖可能用到 Node API |
ai.binding | AI,remote: true | 声明 Workers AI 绑定,对应env.AI(见 env.d.ts) |
durable_objects.bindings | 类名与绑定名均为MyA2A | 注册 Durable Object 命名空间 |
migrations | new_sqlite_classes: ["MyA2A"],tagv1 | 声明 MyA2A 使用SQLite存储,这是ctx.storage.sql可用的前提 |
assets | SPA 模式 +run_worker_first: ["/.well-known/*", "/a2a"] | 静态资源走单页应用回退,同时保证 A2A 相关路径优先由 Worker(Durable Object)处理 |
assets.run_worker_first尤其关键:它保证/.well-known/*与/a2a的请求先进入 Worker 路由,而不会命中前端 SPA 的回退页面。vite.config.ts中的cloudflare()插件(见 vite.config.ts)正是依赖这份配置在本地模拟上述运行时环境。
与其他示例的衔接
该示例与仓库中另一个 AI Chat 示例 形成对照:后者使用@cloudflare/ai-chat构建同类 AI Agent,而 A2A 示例强调的是协议互操作性——遵循 A2A 标准的任意客户端(不仅是本示例自带的 UI)都能发现并调用该 Agent。当你需要把自己的 Agent 接入更大的 Agent 生态时,本文的DefaultRequestHandler + AgentExecutor + DO TaskStore模式就是一条经过验证的实现路径。
小结
回顾整个示例,值得沉淀的三个工程要点是:第一,协议逻辑交给 SDK,业务方只实现AgentExecutor与TaskStore两个接口即可获得完整的 A2A 服务器能力;第二,任务生命周期通过事件总线显式表达,submitted → working → completed的状态流转天然适配 SSE 实时推送;第三,Durable Object SQLite 作为默认持久层,让任务状态具备跨请求的可恢复性,同时new_sqlite_classes迁移声明让整个过程零额外基础设施。
【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考