A2A
这一页说明怎么接一个 A2A(JSON-RPC over SSE)的 Agent:每个函数收到什么、要返回什么,以及这个协议要注意的地方。
结论
- 对话、停止、续接、插话的请求内容是适配器填好的 JSON-RPC 信封,接入方原样发出。
- 本 SDK 的 sessions 和按会话返回 tasks 的 history 是接入方约定,须由后端按后端约定另外提供。
- 本 SDK 从 SSE id 或异步序列外层 eventId 取续接游标。tasks/resubscribe 请求体没有游标字段,接入方必须另行约定重放位置。
- 仅在后端使用本文所述 ADK 事件排列时选择 profile: adk;不能只凭服务端名称选择。
最小接入示例
只给 run,直连本机的 Agent:
adapter.ts
import { createA2aAdapter } from '@volcengine/chatkit/adapters/a2a';
// 直连本机 Agent 的 JSON-RPC 端点,只用于本地开发
const RPC = 'http://localhost:9999/';
export const adapter = createA2aAdapter({
// 请求内容是适配器填好的 JSON-RPC 信封,原样发出
run: ({ body }) =>
fetch(RPC, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify(body),
}),
});
适配器把 sessionId 编到 message.contextId,不会主动建会话。后端是否接受客户端给定 contextId、是否按它保存上下文,需要确认;适配器不会把服务端另发的 contextId 自动替换为当前 sessionId。
走自己的代理
生产环境的请求发给自己的服务,由它带上凭据转发给 Agent:
adapter.ts
import { createA2aAdapter } from '@volcengine/chatkit/adapters/a2a';
// 请求发给自己的服务,由它带上凭据转发给 Agent
const rpc = (body: unknown) =>
fetch('/api/a2a', {
method: 'POST',
credentials: 'include', // 带上登录态,服务端据此知道用户是谁
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify(body),
});
export const adapter = createA2aAdapter({
run: ({ body }) => rpc(body),
abort: ({ body }) => rpc(body),
});
全部能力
adapter.ts
import { createA2aAdapter } from '@volcengine/chatkit/adapters/a2a';
const RPC = '/api/a2a';
const API = '/api/chat';
const rpc = (body: unknown, headers: Record<string, string> = {}) =>
fetch(RPC, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream', ...headers },
body: JSON.stringify(body),
});
const send = (method: string, path: string, body?: unknown) =>
fetch(`${API}${path}`, {
method,
headers: { 'Content-Type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
const query = (cursor?: string) => (cursor ? `?pageToken=${encodeURIComponent(cursor)}` : '');
export const adapter = createA2aAdapter({
// 以下是协议里有的方法:请求内容是适配器填好的 JSON-RPC 信封,原样发出
run: ({ body }) => rpc(body),
abort: ({ body }) => rpc(body),
steer: ({ body }) => rpc(body),
// 最后收到的事件编号放进请求头,后端只发它之后的事件
resume: ({ body, eventId }) => rpc(body, { 'Last-Event-ID': eventId }),
// 以下要后端按约定另外提供,地址和请求头只是一种排法
regenerate: ({ targetRunId, body }) => rpc(body, { 'X-Replace-From': targetRunId }),
edit: ({ targetRunId, body }) => rpc(body, { 'X-Replace-From': targetRunId }),
history: ({ sessionId, cursor }) => send('GET', `/contexts/${sessionId}/tasks${query(cursor)}`),
});
注释里标了「另外提供」的函数,地址和请求头只是一种排法,以和后端团队商定的为准。
每个函数收到什么、要返回什么
表中“事件流”指未配置 subscribe 时的 Response 或异步事件序列。配置 subscribe 后,run / resume / regenerate / edit 只返回受理 Response,输出全部走订阅;其余能力仍返回 Response。
steer 和 abort 的响应和别的协议不同:状态码成功、内容里又没有 error 才算成功。
响应的格式和例子见后端约定 · A2A。
编号
业务状态与内容扩展
聊天里的业务字段通过 transformOutput 转成内容变更。独立业务状态由应用维护,再交给插槽组件;适配器不提供业务状态通知或累积状态 getter。
输入怎么放进请求
后端认别的字段时用 transformInput 换掉:
adapter.ts
import { createA2aAdapter } from '@volcengine/chatkit/adapters/a2a';
export const adapter = createA2aAdapter({
run: ({ body }) =>
fetch('/api/a2a', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify(body),
}),
// 默认协议编码完成后,返回完整请求体。
transformInput: (input, { defaultInput }) => {
if (defaultInput.method === 'tasks/resubscribe') return defaultInput;
return {
...defaultInput,
params: {
...defaultInput.params,
message: {
...defaultInput.params.message,
metadata: { ...defaultInput.params.message.metadata, tenant: input?.metadata?.tenant },
},
},
};
},
});
注意点
选择事件翻译配置
这是本 SDK 的两种翻译配置,请求相同,事件解读不同;不等于自动识别官方协议版本:
选错了的表现:
重新生成与编辑后重发
本适配器没有编码标准化的作废请求。是否提供这两个函数,取决于后端如何管理上下文:
任务的结束状态
流中途断开
连接状态与执行结果分开:断线或缺少终态只表示结果未知。给了 history 时刷新消息与轮次结论;有已收到的游标与 resume 时续流。恢复不会重新调用 run。停止后仍接收输出,远端受理取消不等于已经停止。详见断线恢复与停止确认。
历史里的顺序
任务快照不记过程消息和产出之间的先后。历史里过程消息在前、产出在后。
一轮里正文和工具调用交替出现时,刷新后的顺序可能和当时看到的不一样。
错误
会话业务
会话目录、创建、删除、改名和 fork 由应用调用业务 API,取得真实 sessionId 后传给 useChatSessions。Adapter 不提供 sessions 配置。历史仍由 history 能力函数转换。详见会话契约。
事件形状与状态边界
HTTP run/resume 的 SSE data 通常是 JSON-RPC 信封;异步序列的 data 可直接给 result 内的 A2A 事件,也支持保留已有信封。直接流和独立订阅、本地发送和被动接收都使用同一个解码入口,不会二次包装。事件带 contextId 时必须与当前 sessionId 一致;错会话事件不写入消息或恢复游标。见订阅接入边界。
完成由任务 completed 状态确定;final: true 本身只关闭订阅读取器,不把 working 转成完成。SDK 也接受直接 HTTP 流的 [DONE] 作为完成标记,这属于兼容行为,不能据此要求所有 A2A 服务发送。input-required / auth-required 在 ChatKit 映射为 interrupted;这不表示后端任务永久终止,也不自动提供审批或补充输入工作流。
本适配器的 history 将未识别或缺失的任务状态也作为非终态候选,可能选择它为 activeRun。后端应返回准确状态,未知任务不应附带可恢复游标;SDK 不保证它实际仍在执行。
工具展示中的 adk_type、adk_thought 等 metadata 是本适配器识别的扩展,不是通用 A2A 工具调用保证。上述 JSON-RPC 方法、字段和 profile 描述本适配器的行为,不声明兼容所有或最新 A2A 规范版本。