在 Cloudflare Agents 上构建 A2A 协议服务器:Agent Card 发现、JSON-RPC 传输与 SSE 流式示例全解析
2026/9/18 8:49:38 网站建设 项目流程

在 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提供的DefaultRequestHandlerJsonRpcTransportHandler组装协议处理链,开发者只需要关心两件事——"任务怎么执行"(实现AgentExecutor)和"任务状态存哪里"(实现TaskStore)。

该示例集中展示了以下五项能力(与 README 一一对应):

  • Agent Card 发现:在/.well-known/agent-card.json提供标准化的 Agent 元数据(协议版本、能力、技能、端点 URL 等),任何 A2A 客户端都能先发现、再交互;
  • JSON-RPC 传输:通过@a2a-js/sdk服务器端实现message/sendmessage/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 start

npm 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依次发布submittedworking→ (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 }] });

用户消息中的文本通过过滤partskind === "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 内部如何分发请求

MyA2AonRequest方法是协议入口,完整覆盖三条路径(见 server.ts):

  1. Agent Card 发现GET /.well-known/agent-card.json/.well-known/agent.json,返回handler.getAgentCard()
  2. CORS 预检OPTIONS请求返回允许POST, OPTIONSContent-Type头的响应头;
  3. JSON-RPC 端点POST请求交给transport.handle(body)

handle的返回值有两种情况,需要分别处理:

  • 非流式结果:直接Response.json(result)返回;
  • 异步可迭代结果message/stream的场景):将异步生成器逐个事件编码为 SSE 格式id: ...\ndata: ...\n\n,通过TransformStreamtext/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>;

getAgentByNameagents框架提供的路由助手(实现位于 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。流式事件中kindmessage(agent 回复)、status-update(状态变更,status.message可能携带最终回复)、task(完整任务对象)三种类型都会被消费,这也完整对应了服务器端ExecutionEventBus发布的事件种类。

配置解读:wrangler.jsonc 的四个关键点

示例的 Worker 配置集中在 wrangler.jsonc,理解它才能顺利部署到生产环境:

配置项作用
mainsrc/server.tsWorker 入口,即上面导出的fetchMyA2A
compatibility_date/compatibility_flags2026-06-11/["nodejs_compat"]兼容性日期与 Node.js 兼容层,@a2a-js/sdk等依赖可能用到 Node API
ai.bindingAIremote: true声明 Workers AI 绑定,对应env.AI(见 env.d.ts)
durable_objects.bindings类名与绑定名均为MyA2A注册 Durable Object 命名空间
migrationsnew_sqlite_classes: ["MyA2A"],tagv1声明 MyA2A 使用SQLite存储,这是ctx.storage.sql可用的前提
assetsSPA 模式 +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,业务方只实现AgentExecutorTaskStore两个接口即可获得完整的 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询