OmniRoute A2A Server 接入指南:通过 Agent-to-Agent 协议 v0.3 将智能路由、配额与健康能力开放给任意 Agent
【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150+ free), 1200+ models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline & Copilot. Quota-aware auto-fallback, RTK+Caveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550+ contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute
本文基于 docs/i18n/pl/docs/frameworks/A2A-SERVER.md 编写,并对照其英文权威版 docs/frameworks/A2A-SERVER.md 与仓库源码核实细节。
OmniRoute 的 A2A Server 把整个 AI 网关包装成一个"一等公民 Agent":任何支持 Agent-to-Agent Protocol(A2A)v0.3 的编排器(LangChain、CrewAI、AutoGen 或自定义 Agent),都可以通过一个POST /a2a的 JSON-RPC 2.0 端点,把任务委托给 OmniRoute 的智能路由引擎、配额管理、成本分析等技能,并拿到带routing_explanation、cost_envelope、resilience_trace、policy_verdict的可观测元数据。读完本文你将掌握:如何发现 OmniRoute Agent(Agent Card)、如何调用四个 JSON-RPC 方法、六大内置技能的使用方式、任务生命周期与 TTL、错误码语义,以及如何扩展一个新的 A2A Skill。
A2A 表面的"两张脸":JSON-RPC 与 REST
A2A 能力面由两个互补的接口组成:
- JSON-RPC 2.0入口:
POST /a2a,是 A2A 的规范(canonical)入口点,负责技能执行的全部语义。路由实现在 src/app/a2a/route.ts,支持四个方法:message/send(同步执行)、message/stream(SSE 流式)、tasks/get(查询任务)、tasks/cancel(取消任务)。 - REST 辅助入口:
/api/a2a/*,为仪表盘与外部工具提供状态查询、任务列表、任务取消等辅助能力,见 src/app/api/a2a/tasks/route.ts 与 src/app/api/a2a/status/route.ts。
在底层,任务由A2ATaskManager(src/lib/a2a/taskManager.ts)统一跟踪,默认 TTL 为 5 分钟;技能通过 src/lib/a2a/taskExecution.ts 中的A2A_SKILL_HANDLERS注册表分发。整体架构(发现 → 委托 → 执行 → 内部网关转发)可参考 src/lib/a2a/README.md 中给出的架构图。
Agent 发现:读取 Agent Card
A2A 协议要求每个 Agent 在/.well-known/agent.json暴露一张Agent Card,描述自身能力、技能与认证要求。OmniRoute 默认监听http://localhost:20128:
curl http://localhost:20128/.well-known/agent.json返回的 Agent Card 包含name、description、url(指向/a2a)、version、capabilities(streaming: true、pushNotifications: false)、skills数组以及authentication(schemes: ["api-key"],apiKeyHeader: "Authorization")。
Agent Card 是动态生成的,实现见 src/app/.well-known/agent.json/route.ts:
version字段直接取自process.env.npm_package_version(回退值为"1.8.1"),因此每次发布时都会与package.json自动保持同步;skills数组在 6 个内置技能基础上,还会通过getFleetSkills()追加 OmniConductor 编排集群的 fleet 技能(集群未配置或离线时为空数组,卡片依然有效);- 响应带有
Cache-Control: public, max-age=3600,即公开缓存 1 小时; - 顺带说明:Agent Card 的描述文本中仍写有 "36+ 个提供者"、
list-capabilities描述写有 "42 个技能" 等旧数值,这与当前运行时注册表的真实目录规模存在已知漂移(文档中已记录为待单独更新的 TODO,当前源码里 6 个内置技能 + fleet 技能的形态以 taskExecution.ts 为准)。
认证与启用开关
认证
所有/a2a请求都要求在Authorization头携带 API Key:
Authorization: Bearer YOUR_OMNIROUTE_API_KEY认证逻辑集中在 src/lib/a2a/authenticate.ts 的authenticateA2ARequest(),JSON-RPC 与 REST 任务面共用同一实现,避免两处逻辑漂移。其判定顺序:
- 若开启了
REQUIRE_API_KEY特性开关:必须提供有效 API Key,否则拒绝(作为例外,仪表盘会话 Cookie 可被放行,以支持 A2A Playground); - 若未开启强制开关但配置了
OMNIROUTE_API_KEY:用timingSafeEqual做常量时间比较,防止时序侧信道泄露密钥(对应测试见 tests/unit/a2a-auth-timing-safe.test.ts); - 若服务端既未要求也未配置 API Key:直接放行(keyless local-first 默认姿态),即文档所说"未配置 API Key 时认证被绕过"。
认证失败时,JSON-RPC 返回错误码-32600(Unauthorized)。
启用开关
A2A 默认是关闭的,由Endpoints → A2A开关控制(存储于settings.a2aEnabled)。未启用时:
GET /api/a2a/status返回status: "disabled"与online: false;- 对
POST /a2a的 JSON-RPC 调用返回HTTP 503与 JSON-RPC 错误码-32000,错误信息提示到 Endpoints 页面开启。
对应的守卫逻辑在 src/app/a2a/route.ts 的rejectIfA2ADisabled()中,启用状态由 src/app/api/a2a/status/route.ts 读取(settings.a2aEnabled === true时为"ok",并附带任务统计与 Agent Card 摘要)。
JSON-RPC 2.0 方法详解
所有方法都走POST /a2a,请求体为标准的 JSON-RPC 2.0 信封(jsonrpc: "2.0"+id+method+params)。路由的完整处理流程(见 src/app/a2a/route.ts)为:认证 → 解析 JSON(失败返回-32700)→ 校验jsonrpc/method(失败返回-32600)→ 检查启用开关(失败返回-32000/503)→ 解析调用者 owner → 分发到具体方法。
协议兼容层:
/a2a还内置了 A2A 1.0 兼容层——1.0 将方法改名为SendMessage/SendStreamingMessage并改变了同步响应结构,OmniRoute 通过V1_METHOD_ALIASES将这两个 1.0 方法名映射回message/send/message/stream,并把 v0.3 的顶层artifacts/metadata重排成 1.0 客户端期望的task.status.message.parts[].text。v0.3 客户端不受影响。相关测试见 tests/unit/a2a-v1-compat-10839.test.ts。
message/send— 同步执行
发送消息给某个技能,并等待完整响应:
curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_KEY" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "message/send", "params": { "skill": "smart-routing", "messages": [{"role": "user", "content": "Write a hello world in Python"}], "metadata": {"model": "auto", "combo": "fast-coding"} } }'响应示例:
{ "jsonrpc": "2.0", "id": "1", "result": { "task": { "id": "uuid", "state": "completed" }, "artifacts": [{ "type": "text", "content": "..." }], "metadata": { "routing_explanation": "Selected claude-sonnet via provider \"anthropic\" (latency: 1200ms, cost: $0.003)", "cost_envelope": { "estimated": 0.005, "actual": 0.003, "currency": "USD" }, "resilience_trace": [ { "event": "primary_selected", "provider": "anthropic", "timestamp": "..." } ], "policy_verdict": { "allowed": true, "reason": "within budget and quota limits" } } } }几个值得注意的路由行为(源码佐证):
- 若
params.skill缺省,默认使用smart-routing; messages兼容两种形态:规范的messages[]数组,或 legacy 的message.content/message.parts(见toMessageArray()的归一化逻辑);- 消息缺失时返回
-32602(Invalid params); - 技能不存在时返回
-32601(Method or skill not found); - 技能执行抛错时,任务被标记为
failed,响应返回-32603(Internal error); - 对于
smart-routing,成功后会额外调用logRoutingDecision()(src/lib/a2a/routingLogger.ts)把路由决策写入统计,供仪表盘分析。
message/stream— SSE 流式执行
与message/send相同,但以 Server-Sent Events 返回实时流:
curl -N -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_KEY" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "message/stream", "params": { "skill": "smart-routing", "messages": [{"role": "user", "content": "Explain quantum computing"}] } }'SSE 事件序列:
data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}} : heartbeat 2026-03-03T17:00:00Z data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}}SSE 的实现位于 src/lib/a2a/streaming.ts:
- 心跳:每 15 秒发送一条
: heartbeat <ISO时间>注释行,保持连接存活; - 分块:技能产出以
working状态的chunk事件逐条发出(对非流式技能做模拟分片); - 完成:最后一个事件为
completed状态并携带metadata; - 失败/取消:以
failed状态事件携带metadata.error结束; - 响应头为
text/event-stream、Cache-Control: no-cache, no-transform、Connection: keep-alive、X-Accel-Buffering: no; - 流式任务通过
beginStream()/endStream()计入activeStreams统计;客户端中断(AbortSignal)会立即发出取消失败事件并关闭流。
tasks/get— 查询任务状态
curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_KEY" \ -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"TASK_UUID"}}'任务不存在时返回-32601(Task not found);taskId缺失时返回-32602。
tasks/cancel— 取消任务
curl -X POST http://localhost:20128/a2a \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_KEY" \ -d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}'安全提示:调用者 owner 作用域
从 taskManager.ts 与 authenticate.ts 可以看到(对应安全修复 GHSA-jcm5-6wpp-wjj8):
- 每个任务携带
owner:API Key 的SHA-256 哈希前 32 位十六进制,仪表盘会话调用者为"dashboard",keyless 调用者为undefined; - 带 owner 的任务只对同 owner 可见/可取消/可列出;
tasks/get、tasks/cancel与 REST 列表都会按 owner 过滤,防止 IDOR 探测(取消他人任务与任务不存在返回相同的 "not found" 错误);相关测试见 tests/unit/a2a-task-owner-idor.test.ts。
内置技能(Available Skills)
OmniRoute 暴露 6 个 A2A 技能,全部在 src/lib/a2a/taskExecution.ts 的A2A_SKILL_HANDLERS中注册,每个技能的模块位于 src/lib/a2a/skills/,通过动态import()按需加载:
| Skill | ID | 描述 | Tags | 示例 |
|---|---|---|---|---|
| Smart Routing | smart-routing | 使用 OmniRoute 的 combo 引擎 + 评分体系,将 prompt 路由到最优 provider/combo | routing, providers | "Route this prompt via the best model" |
| Quota Management | quota-management | 报告各 provider 的配额状态,帮助调用方决定何时限流/切换 | quota, providers | "Check quota for anthropic" |
| Provider Discovery | provider-discovery | 列出已安装的 provider,含能力、free-tier 标记与 OAuth 状态 | providers, discovery | "What providers are available?" |
| Cost Analysis | cost-analysis | 基于价格目录 + 近期 usage 估算单次请求/会话成本 | cost, usage | "Estimate cost for this conversation" |
| Health Report | health-report | 聚合各 provider 的 circuit breaker、cooldown、lockout 状态 | health, resilience | "Show health status of all providers" |
| List Capabilities | list-capabilities | 以 Markdown 表格返回完整 Agent Skills 目录,附 SKILL.md 原始 URL 供上下文注入 | catalog, discovery, skills | "List all OmniRoute capabilities" |
关于技能数量与 provider 数量的数值漂移:文档表格与 Agent Card 描述文本中写有 "42 个技能"、"36+ providers" 等旧值;而当前源码 listCapabilities.ts 中的目录覆盖为23 API + 21 CLI + 1 config 共 45 项(
metadata.coverage硬编码这些总数)。文档已明确记录这是待更新项,引用时请以运行时目录为准。
smart-routing技能的 metadata 参数与返回值
smart-routing是使用最频繁的技能。其metadata支持以下参数(见 src/lib/a2a/skills/smartRouting.ts 与 src/lib/a2a/README.md):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model | string | "auto" | 目标模型(如claude-sonnet-4、gpt-4o,或auto交给路由引擎) |
combo | string | 当前激活 combo | 指定要走某个 combo(通过x-combo头传给内部/v1/chat/completions) |
budget | number | 无 | 本次请求的成本上限(USD),超过则policy_verdict.allowed=false |
role | string | 无 | 任务角色提示:coding、review、planning、analysis、debugging、documentation |
返回字段:
| 字段 | 说明 |
|---|---|
artifacts[].content | LLM 响应文本 |
metadata.routing_explanation | 人类可读的路由决策说明(选中模型、provider、延迟、成本) |
metadata.cost_envelope | 预估 vs 实际成本(estimated/actual/currency) |
metadata.resilience_trace | 事件数组(primary_selected,触发回退时追加fallback_needed) |
metadata.policy_verdict | 请求是否被允许及原因(预算/配额检查) |
从源码看,smartRouting.ts内部会把请求转发给 OmniRoute 自身的/v1/chat/completions,携带 30 秒超时与内部 API Key,返回的provider、cost、fallbacksTriggered等字段被整理进上述元数据。
list-capabilities技能详解
对于需要在发送 API 调用之前先探测 OmniRoute 能力的外部 Agent,list-capabilities特别有用。它会返回一张结构化 Markdown 表格制品:
| ID | Name | Category | Area | Endpoints/Commands | Raw URL | | --- | --- | --- | --- | --- | --- | | omni-auth | Auth & Sessions | api | auth | POST /api/auth/login, ... | https://raw.githubusercontent.com/... | ...每行包含rawUrl列,Agent 拿到后可以立即拉取完整的 SKILL.md 注入上下文;metadata.totalSkills与目录大小保持一致。实现见 src/lib/a2a/skills/listCapabilities.ts,目录数据来自 src/lib/agentSkills/catalog.ts。技能目录的完整说明参见 AGENT-SKILLS.md。
辅助 REST API
JSON-RPC 端点/a2a是 A2A 的规范入口,下面的 REST 端点则面向仪表盘与外部工具提供辅助访问:
| Endpoint | Method | 描述 | 认证 |
|---|---|---|---|
/api/a2a/status | GET | 服务器状态、已注册技能 | (公开) |
/api/a2a/tasks | GET | 任务列表,支持过滤 | management |
/api/a2a/tasks/[id] | GET | 按 ID 获取任务 | management |
/api/a2a/tasks/[id]/cancel | POST | 取消运行中的任务 | management |
/.well-known/agent.json | GET | Agent Card(A2A 发现) | (公开,缓存 3600s) |
/api/a2a/tasks | POST | 向 OmniConductor 集群入站委托(Conductor PRD RF5) | Bearer vsOMNIROUTE_API_KEY+a2aEnabled |
关于 REST 任务列表(GET /api/a2a/tasks):支持state(仅接受五个合法状态值)、skill、limit(1–200,默认 50)、offset(默认 0)查询参数,返回{ tasks, total, limit, offset };management 或 keyless 姿态可见全部任务,裸 API Key 必须有效且按 owner 过滤,见 src/app/api/a2a/tasks/route.ts。
关于入站委托(POST /api/a2a/tasks):外部 A2A Agent 可通过 OmniRoute 把编码类工作委托给 OmniConductor 编排集群。请求体形如{ skill: "conductor" | "conductor-cli-<profile>", messages: [{role, content}], metadata: { conductor: { repo: { url, base_ref? }, mode?, cli?, model? } } }——只有 Agent Card 上公布的 Conductor fleet 技能可被委托,且metadata.conductor.repo.url必填(集群基于 git 仓库工作)。该路由使用服务端CONDUCTOR_ORCHESTRATOR_TOKEN(回退CONDUCTOR_HUB_TOKEN)转调到 hub 的POST /v1/tasks,返回201 { conductor_task_id, state: "submitted" };任务状态经 SSE→A2A 镜像回流,可通过GET /api/a2a/tasks?skill=conductor查询。
任务生命周期与 TTL
任务状态机(src/lib/a2a/taskManager.ts 的VALID_TRANSITIONS严格约束):
submitted → working → completed → failed → cancelled关键行为:
- 默认 TTL 5 分钟:任务在
ttlMinutes(构造A2ATaskManager时传入,默认 5)后过期。submitted/working状态的任务过期后会被自动标记为failed(消息为 "TTL expired"); - 后台清理:每 60 秒扫一次过期任务(
cleanupExpired());终态任务在超过2×TTL后从内存移除; - 终态不可转移:
completed、failed、cancelled均为终态,任何非法迁移都会抛错; - 事件日志:每次状态迁移都会写入
events数组(含时间戳、状态、可选消息),并发布agent.task.updated事件总线消息(供编排画布订阅,best-effort 不阻塞写入路径); - 历史持久化:任务状态可写入 SQLite 历史表(best-effort),保留天数由
OMNIROUTE_A2A_HISTORY_RETENTION_DAYS控制,默认 30 天,每日最多清理一次; - 任务 ID:UUID v4(
randomUUID())。
若要自定义 TTL,可以 forkA2ATaskManager的实例化并传入不同值,例如new A2ATaskManager(15)表示 15 分钟 TTL(同时可注入自定义持久化层)。单例入口getTaskManager()使用默认 5 分钟。
错误码
| 代码 | 含义 |
|---|---|
| -32700 | 解析错误(JSON 无效) |
| -32600 | 无效请求 / 未授权 |
| -32601 | 未找到方法或技能 |
| -32602 | 无效参数 |
| -32603 | 内部错误 |
| -32000 | A2A 端点已禁用 |
注意 HTTP 状态码的映射并非固定:-32600→ 400、-32601→ 404、-32603→ 500、其余为 200(错误体现在 JSON-RPCerror字段中);而-32000(禁用)固定返回 HTTP 503。此外 src/lib/a2a/README.md 还扩展记录了-32001(任务未找到)到-32005(无可用 provider)等业务错误码。
集成示例
Python(requests)
import requests resp = requests.post("http://localhost:20128/a2a", json={ "jsonrpc": "2.0", "id": "1", "method": "message/send", "params": { "skill": "smart-routing", "messages": [{"role": "user", "content": "Hello"}] } }, headers={"Authorization": "Bearer YOUR_KEY"}) result = resp.json()["result"] print(result["artifacts"][0]["content"]) print(result["metadata"]["routing_explanation"])TypeScript(fetch)
const resp = await fetch("http://localhost:20128/a2a", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_KEY", }, body: JSON.stringify({ jsonrpc: "2.0", id: "1", method: "message/send", params: { skill: "smart-routing", messages: [{ role: "user", content: "Hello" }], }, }), }); const { result } = await resp.json(); console.log(result.metadata.routing_explanation);更完整的场景化客户端(含 Agent 发现、budget约束、SSE 流式解析、任务轮询取消、LangChain 自定义 LLM 包装、Go 客户端等)见 src/lib/a2a/README.md。
扩展指南:添加一个新的 A2A Skill
按以下 5 步即可为 OmniRoute 增加一个新技能(这也是A2A_SKILL_HANDLERS与 Agent Card 的扩展路径):
1. 创建技能文件:src/lib/a2a/skills/<your-skill>.ts
导出异步函数(task: A2ATask) => Promise<{ artifacts, metadata }>,参照现有技能(如smartRouting.ts)的形状:
export async function executeYourSkill(task: A2ATask) { // 读取 task.input.messages / task.input.metadata // 产出 artifacts: [{ type: "text", content }] 与 metadata return { artifacts, metadata }; }2. 注册 handler:在src/lib/a2a/taskExecution.ts的A2A_SKILL_HANDLERS中追加条目(采用动态 import 保持按需加载):
export const A2A_SKILL_HANDLERS = { // ...existing skills "your-skill": async (task) => { const skillModule = await import("./skills/yourSkill"); return skillModule.executeYourSkill(task); }, };3. 暴露到 Agent Card:在 src/app/.well-known/agent.json/route.ts 的skills数组追加:
{ "id": "your-skill", "name": "Your Skill", "description": "Brief, intent-focused description", "tags": ["routing", "quota"], "examples": ["Sample natural-language invocation"] }4. 编写测试:创建tests/unit/a2a-<your-skill>.test.ts,覆盖 happy path 与 error path(仓库现有 A2A 测试可作参考,如 tests/unit/a2a-enabled-route.test.ts、tests/unit/a2a-route-require-api-key.test.ts、tests/unit/a2a-memory-hits.test.ts)。
5. 更新文档:在本文档(及 src/lib/a2a/README.md)的 Available Skills 表格中登记新技能。
补充:任务执行中的内存检索观测
从 src/lib/a2a/taskExecution.ts 可以看到一个值得注意的机制:任务执行前会做一次纯观测用途的记忆检索(collectMemoryHits,对应 Orchestration Canvas 任务 C2)——检索到的记忆从不注入技能 prompt,只镜像到task.metadata.memoryHits与memory_hits历史事件,供仪表盘展示"该任务参考了哪些记忆"。可用OMNIROUTE_A2A_MEMORY_HITS=0完全关闭;检索有 1500ms 截止时间(MEMORY_RECALL_TIMEOUT_MS),超时或失败均静默降级为[],绝不拖垮主任务。A2A 技能通过/v1/chat/completions等内部端点与 OmniRoute 网关联动,形成"外部 Agent → A2A 技能 → 网关路由引擎 → 最优 provider"的完整链路。
【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150+ free), 1200+ models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline & Copilot. Quota-aware auto-fallback, RTK+Caveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550+ contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考