HarmonyOS A2A 协议服务端:从请求接入到任务产物返回
华为HarmonyOS的Agent Framework Kit为A2A协议提供了服务端实现,但真正的难点在于任务状态管理和产物返回。本文深入解析其API设计,从创建Server到处理回调,带你理解智能体通信的完整链路。

如果把 A2A(Agent to Agent)理解成“智能体之间的一次调用”,很容易只盯着消息怎么发,却忽略了真正难处理的部分:一次请求可能要持续一段时间,任务需要有明确状态,执行过程中还可能产出文件、文本或其他资源,客户端也需要拿到这些变化。
华为 HarmonyOS 的 Agent Framework Kit 给出的这组 A2A 服务端 API,处理的就是这条链路。服务端负责接收 A2A 客户端请求、触发智能体执行、更新任务状态,并把执行结果作为 Artifact 返回给客户端。
先看结论:它解决什么问题
官方文档将这个模块定义为 A2A 协议的服务端实现,主要包括:
- 创建 A2A Server 实例。
- 接收并处理 A2A 客户端的消息请求。
- 处理客户端的密钥协商请求。
- 通过回调触发智能体执行。
- 管理任务状态。
- 管理任务执行产生的 Artifact。
- 从请求上下文中读取智能体、会话、消息、任务和元数据等信息。
从工程视角看,最重要的对象可以这样对应:

版本和运行约束
这部分不要省略。页面中列出的 A2A 相关接口有统一的使用约束:
- 所有接口仅可在 Stage 模型下使用。
- 系统能力为 SystemCapability.AI.Agent.AgentKit。
- 起始版本为 26.0.0。
- 页面上的接口和数据类型均标记为 Beta。
- 页面能力面向 Phone 和 Tablet。
因此,接入前至少要确认当前工程使用的是 Stage 模型,并且目标设备和 SDK 版本满足页面声明的起始版本要求。代码能写出来,不代表运行环境一定具备这项能力。
导入模块
官方页面给出的导入方式如下:
import {
Role,
TaskState,
Part,
Message,
Artifact,
TaskStatus,
Task,
OnDataCallback,
createA2AServer,
ProxySender,
Server,
AgentOperation,
RequestContext,
TaskArtifactParam
} from ‘@kit.AgentFrameworkKit’;
页面的导入示例覆盖了服务端创建、回调、上下文、任务状态、消息和产物相关类型。实际代码中可以按使用情况导入,但名称必须来自页面列出的接口。
一次请求的完整链路
从服务端代码的职责划分看,一次 A2A 请求大致经过以下步骤:
- 在 AgentExtensionAbility 中使用 createA2AServer 创建 Server。
- 将 AgentCard 和 OnDataCallback 传入服务端。
- 启动 Server。
- 收到客户端消息后,调用 onMessage。
- 通过 OnDataCallback 的 method 判断本次操作类型。
- 从 RequestContext 获取请求中的消息、任务、会话和元数据。
- 业务执行过程中使用 updateStatus 更新任务状态。
- 有执行产物时使用 addArtifact 添加产物信息。
- 不再提供服务时调用 stop。
需要注意,页面没有提供一个“自动执行全部业务逻辑”的高阶方法。智能体具体做什么,仍然由传入的 OnDataCallback 业务回调定义。
创建 A2A Server
API 签名
createA2AServer(
agentCard: common.AgentCard,
onData: OnDataCallback,
want?: Want
): Server
这个方法创建并返回 Server 实例,用于处理 A2A 协议通信。
参数含义:

返回值是创建的 A2A Server 实例。
页面列出的错误码如下:

官方创建示例
官方示例把服务端放在 AgentExtensionAbility 中创建:
import { Want, AgentExtensionAbility } from ‘@kit.AbilityKit’;
import {
createA2AServer,
Server,
RequestContext,
AgentOperation,
OnDataCallback
} from ‘@kit.AgentFrameworkKit’;
export default class MyAgentExtensionAbility extends AgentExtensionAbility {
private server: Server | null = null;
private agentOnData: OnDataCallback = (
method: AgentOperation,
context: RequestContext
) => {
// Agent的业务处理定义
}
async onCreate(want: Want) {
try {
const card = this.context.agentCard;
this.server = createA2AServer(card, this.agentOnData, want = want);
} catch (error) {
console.error(`Failed to create server: ${error}`);
}
}
}
这个示例表达了三个关键点:
- 服务端实例类型是 Server。
- 业务入口是 OnDataCallback。
- createA2AServer 的第一个参数来自 this.context.agentCard。
这里保留了官方示例中的 want = want 写法。落地时应以当前 SDK 的 ArkTS 编译结果和官方签名为准,不要因为示例看起来像赋值表达式,就自行替换成页面没有声明的其他参数形式。
操作回调:OnDataCallback
API 签名
type OnDataCallback = (
method: AgentOperation,
context: RequestContext
) => void
当 A2A Server 收到客户端请求后,会触发这个事件回调,交给智能体执行。
回调参数:

工程上可以把它看成一个“操作分发入口”:method 说明发生了什么,context 提供本次请求的上下文。
页面列出了四种 AgentOperation:

因此,业务回调至少要有这四类操作的分支意识。页面没有给出某个具体业务的处理实现,具体逻辑需要由应用自己定义。
Server 的生命周期和通信方法
Server 是服务端实例,负责智能体通信和任务生命周期管理。页面提供了以下方法。
启动和停止
// server对象通过createA2AServer接口创建
server.start();
start() 用于启动 A2A 服务端实例。
服务不再需要时:
// server对象通过createA2AServer接口创建
server.stop();
stop() 用于停止服务器实例。
接收消息并发送响应
// server对象通过createA2AServer接口创建
// proxy提供外部定义的消息发送接口
server.onMessage(data, (response: string) => {
proxy.sendData(response);
});
API 签名:
onMessage(data: string, sender: ProxySender): void
参数含义:

ProxySender 的定义是:
type ProxySender = (data: string) => void
也就是说,服务端收到的是 JSON 字符串,响应也通过一个接收 string 的回调交给外部发送接口。页面示例中的 proxy.sendData(response) 是外部定义的消息发送接口,并不是 A2A 页面另外提供的 API。
处理密钥协商
onAuth(data: string): string
onAuth 用于处理来自 A2A 客户端的密钥协商请求。
参数 data 是客户端的密钥协商请求,其中包含客户端公钥信息。返回值是密钥协商信息,其中包含服务端公钥。
官方页面只说明了请求和返回值的类型及含义,没有给出密钥算法、密钥格式或更具体的协议字段,因此接入文章不能把这些内容延伸成额外的实现约定。
RequestContext:从请求中拿到什么
RequestContext 提供传入请求的上下文信息。它不是一个任务对象本身,而是当前请求读取相关信息的入口。
智能体和客户端会话
const agentId: string = context.getAgentId() ?? “”;
const clientSessionId: string = context.getClientSessionId() ?? “”;
对应方法:
getAgentId(): string | undefined
getClientSessionId(): string | undefined
- getAgentId() 获取智能体 ID;如果不存在,返回 undefined。
- getClientSessionId() 获取客户端会话 ID;如果不存在,返回 undefined。
页面将这两个返回值都定义为 string | undefined。因此业务代码不应该默认它们一定存在。
用户输入
const userInput: string = context.getUserInput(“,”);
API 签名:
getUserInput(delimiter?: string): string
delimiter 是可选分隔符,用于分隔多条用户输入。返回值是用户输入内容。
当前消息
const message: Message = context.getMessage();
API 签名:
getMessage(): Message | undefined
这个方法获取 A2A 客户端请求中的消息体对象。如果请求中不存在消息体,返回 undefined。
关联任务和当前任务
const relatedTasks: Task[] = context.getRelatedTasks();
const currentTask: Task = context.getCurrentTask();
对应方法:
getRelatedTasks(): Task[]
getCurrentTask(): Task | undefined
- getRelatedTasks() 获取关联任务列表。
- getCurrentTask() 获取当前任务;如果不存在,返回 undefined。
文档核对提醒: 官方页面中 getCurrentTask() 的示例代码写成了:
const currentTask: Task = context.getRelatedTasks();
这与该 API 的方法名和返回值说明不一致。按照页面列出的接口签名,读取当前任务应调用 context.getCurrentTask();上面的写法是官方示例中的原文不一致点,不建议照抄。
任务 ID、上下文 ID 和元数据
const taskId: string = context.getTaskId() ?? “”;
const contextId: string = context.getContextId() ?? “”;
const metadata: object = context.getMetadata();
对应方法:
getTaskId(): string | undefined
getContextId(): string | undefined
getMetadata(): object
- getTaskId() 获取任务 ID,不存在时返回 undefined。
- getContextId() 获取上下文 ID,不存在时返回 undefined。
- getMetadata() 获取客户端请求的元数据,用于附带额外请求信息。
任务状态:让客户端知道任务走到哪了
Task 和 TaskStatus
Task 表示一个 A2A 任务,包含任务状态、产物和历史消息:

TaskStatus 表示任务当前状态:

TaskState 的完整枚举
页面给出的任务状态包括:

状态设计上,SUBMITTED、WORKING 和 COMPLETED 能覆盖最基本的提交、执行和完成流程;INPUT_REQUIRED、AUTH_REQUIRED、CANCELED、FAILED 和 REJECTED 则对应任务无法直接继续或已经结束的分支。
更新任务状态
API 签名:
updateStatus(taskId: string, status: TaskStatus): void
参数:

页面明确给出的 UUID 格式为:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
官方示例:
server.updateStatus(taskId, {
state: TaskState.COMPLETED,
message: {
messageId: ‘msg1’,
role: Role.AGENT,
parts: [{
text: ‘Task completed’
}]
}
});
这个例子不仅更新了 TaskState,还在状态更新中附带了一条 Message。消息的发送者角色是 Role.AGENT,内容由一个 Part 组成。
Artifact:任务结果不是只有一段文本
Artifact 数据结构
Artifact 表示智能体任务产生的资源:

添加产物
API 签名:
addArtifact(
taskId: string,
taskArtifactParam: TaskArtifactParam
): void
参数:

TaskArtifactParam 的字段如下:

其中两个字段对长任务尤其关键:
- append 为 true 时,把当前 parts 追加到现有相同 ID 的产物;为 false 时,在产物列表中新增一项。默认值为 false。
- lastChunk 为 true 时表示当前是最后一个数据块;为 false 时表示还不是最后一个数据块。默认值为 false。
这意味着一个任务可以分多次调用 addArtifact,逐步补充同一个 Artifact。页面没有给出更高层的流式封装,分块行为由 append 和 lastChunk 这两个参数表达。
Message、Role 和 Part
Role
Role 表示消息发送者角色:

Message
Message 表示 A2A 通信中的消息单元:

Part
Part 是消息内容部件,支持文本、原始数据、URL、媒体等内容类型:

页面给出的 URL 示例包括:
- file://
- https://
- http://
页面给出的 MIME 类型示例包括:
- text/plain
- application/json
- image/png
页面将这些字段都定义为可选字段。具体使用哪个字段,取决于要表达的内容类型。比如纯文本可以使用 text,文件地址可以使用 url 并配合 filename 和 mediaType。
使用场景
下面这些场景都能直接落到页面列出的能力上。
1. 处理一条普通客户端消息
客户端发送 JSON 字符串后,服务端通过 onMessage 接收,再由 OnDataCallback 根据 AgentOperation.EXECUTE 进入业务处理。业务代码可以从 RequestContext 取出 Message、用户输入、任务 ID 和元数据,然后更新任务状态。
这个场景对应的是最基本的 A2A 请求执行链路。
2. 需要较长时间才能完成的任务
任务不是收到请求后立刻返回最终结果时,可以用 TaskState.SUBMITTED 表示已提交,用 TaskState.WORKING 表示处理中,完成后使用 TaskState.COMPLETED。
页面给出的 Task、TaskStatus 和 updateStatus 正好提供了这套状态表达能力。
3. 任务需要用户补充信息
当任务不能继续执行,需要用户输入时,可以使用 TaskState.INPUT_REQUIRED。RequestContext.getUserInput(delimiter?) 可用于读取用户输入内容。
这里要区分两件事:INPUT_REQUIRED 是任务状态枚举,getUserInput 是读取请求上下文的方法。页面没有提供“自动弹出输入界面”的 API。
4. 任务产出文件或媒体
Part 支持 url、mediaType、filename 等字段,Artifact 用于表示任务产生的资源。这个组合适合表达文件 URL、媒体类型和文件名等信息。
页面没有规定文件内容一定要通过本地路径、网络地址或某一种具体存储方式提供,只规定 url 必须符合标准 URL 规范。
5. 产物分块返回
对于需要分段生成的产物,可以使用 TaskArtifactParam.append 和 TaskArtifactParam.lastChunk:
- 中间数据块追加到相同 Artifact。
- 最后一块通过 lastChunk: true 标记。
这个机制适合长文本、逐步生成的资源或其他需要多次更新的任务产物。具体产物内容仍然通过 Part[] 表达。
6. 取消、清理上下文和推荐操作
AgentOperation 还提供:
- CANCEL:收到取消任务请求时触发。
- CLEAR_CONTEXT:清除上下文操作。
- PERCEPTION_SUGGEST:小艺 OnApp Chips 推荐操作。
页面只定义了这些操作类型和触发说明,没有为每种操作提供额外的专用处理接口。因此,应用应在自己的 OnDataCallback 中基于 method 做分支处理。
7. 客户端与服务端密钥协商
服务端可以通过 onAuth(data: string): string 处理客户端的密钥协商请求:
- 输入是包含客户端公钥信息的字符串。
- 返回值是包含服务端公钥的密钥协商信息。
官方页面没有继续规定具体算法和字段,接入时不能仅凭这个 API 签名推导出额外的密钥协议。
一个更接近落地的回调分发骨架
页面明确给出了回调类型和四种操作枚举,但没有给出完整的业务分发实现。下面这个骨架只使用页面已列出的类型和方法,用来说明代码组织方式:
import {
AgentOperation,
OnDataCallback,
RequestContext,
TaskState,
Task
} from ‘@kit.AgentFrameworkKit’;
const agentOnData: OnDataCallback = (
method: AgentOperation,
context: RequestContext
) => {
switch (method) {
case AgentOperation.EXECUTE: {
const taskId: string = context.getTaskId() ?? “”;
const message = context.getMessage();
const userInput: string = context.getUserInput(“,”);
const metadata: object = context.getMetadata();
break;
}
case AgentOperation.CANCEL: {
const currentTask: Task | undefined = context.getCurrentTask();
break;
}
case AgentOperation.CLEAR_CONTEXT: {
break;
}
case AgentOperation.PERCEPTION_SUGGEST: {
break;
}
default: {
break;
}
}
};
这段代码只演示回调分发和上下文读取,没有虚构某个具体的 Agent 业务,也没有把页面没有提供的响应对象或客户端调用加进来。
接入时容易忽略的点
1. Beta 和起始版本不能当成普通备注
页面上的接口均标记为 Beta,并且起始版本为 26.0.0。如果项目需要兼容更低版本,不能仅凭 TypeScript 类型检查通过就认为运行时可用。
2. taskId 和多个 ID 字段有格式要求
页面明确要求任务 ID 使用 UUID 格式,Message.messageId、Task.id、Artifact.artifactId 和上下文相关 ID 也都按 UUID 说明。业务侧生成或传递这些字段时,需要保持格式一致。
3. 任务状态和消息是两个层次
TaskStatus.state 表示任务状态,TaskStatus.message 是状态更新时附加的消息。不要把一段文本直接当成任务状态;状态需要使用 TaskState 枚举。
4. Artifact 至少要有一个 Part
Artifact.parts 是必选字段,而且页面明确要求至少包含一个部件。TaskArtifactParam.parts 也是必选字段。
5. 可选返回值要处理 undefined
以下方法的返回值可能是 undefined:
- getAgentId()
- getClientSessionId()
- getMessage()
- getCurrentTask()
- getTaskId()
- getContextId()
官方示例对字符串 ID 使用了 ?? “”,这说明调用侧不能把它们都当作必然存在的字符串。
6. append 的默认值是 false
如果希望把新的 parts 追加到已有相同 ID 的 Artifact,需要显式考虑 append。页面说明默认值为 false,默认行为是在产物列表中新增一项。
7. 不要把页面中的外部对象误认成 Kit API
onMessage 示例中的 proxy.sendData(response) 里的 proxy,是示例注释所说的“外部定义的消息发送接口”。它不属于页面列出的 @kit.AgentFrameworkKit 接口。
API 速查
创建和回调
createA2AServer(
agentCard: common.AgentCard,
onData: OnDataCallback,
want?: Want
): Server
type OnDataCallback = (
method: AgentOperation,
context: RequestContext
) => void
type ProxySender = (data: string) => void
Server
start(): void
stop(): void
onMessage(data: string, sender: ProxySender): void
onAuth(data: string): string
updateStatus(taskId: string, status: TaskStatus): void
addArtifact(taskId: string, taskArtifactParam: TaskArtifactParam): void
RequestContext
getAgentId(): string | undefined
getClientSessionId(): string | undefined
getUserInput(delimiter?: string): string
getMessage(): Message | undefined
getRelatedTasks(): Task[]
getCurrentTask(): Task | undefined
getTaskId(): string | undefined
getContextId(): string | undefined
getMetadata(): object
枚举
enum AgentOperation {
EXECUTE = 0,
CANCEL = 1,
CLEAR_CONTEXT = 2,
PERCEPTION_SUGGEST = 3
}
enum TaskState {
SUBMITTED = 0,
WORKING = 1,
INPUT_REQUIRED = 2,
COMPLETED = 3,
CANCELED = 4,
FAILED = 5,
REJECTED = 6,
AUTH_REQUIRED = 7,
UNSPECIFIED = 8
}
enum Role {
AGENT = 0,
USER = 1,
UNSPECIFIED = 2
}
核心数据类型
interface Message {
messageId: string;
role: Role;
parts: Part[];
contextId?: string;
extensions?: string[];
metadata?: object;
referenceTaskIds?: string[];
taskId?: string;
}
interface Part {
text?: string;
raw?: string;
url?: string;
data?: object | string | number | boolean;
mediaType?: string;
filename?: string;
metadata?: object;
}
interface Task {
id: string;
contextId: string;
status: TaskStatus;
artifacts?: Artifact[];
history?: Message[];
metadata?: object;
}
interface TaskStatus {
state: TaskState;
timestamp?: string;
message?: Message;
}
interface Artifact {
artifactId: string;
parts: Part[];
description?: string;
extensions?: string[];
metadata?: object;
name?: string;
}
interface TaskArtifactParam {
parts: Part[];
artifactId?: string;
name?: string;
metadata?: object;
append?: boolean;
lastChunk?: boolean;
extensions?: string[];
}
上面的类型结构用于速查,实际开发应以当前版本 SDK 的导出定义和官方 API 文档为准。尤其是页面标记为 Beta 的接口,不应脱离目标版本和运行环境单独判断。
最后总结
HarmonyOS A2A 服务端 API 的核心不是“多了一个消息回调”,而是把一次智能体间通信拆成了几个能落地管理的对象:
- Server 管通信入口和任务生命周期。
- OnDataCallback 根据 AgentOperation 进入业务处理。
- RequestContext 提供请求上下文。
- TaskState 表达任务进度和结束状态。
- Message 和 Part 表达消息内容。
- Artifact 表达任务产生的资源。
- append 和 lastChunk 支持产物分块更新。
真正接入时,建议先把操作分发、任务状态和产物更新三条线跑通,再往具体智能体业务里填内容。这样代码边界比较清楚,也能避免把“收到请求”“任务处理中”和“已经有最终产物”混成一个状态。
本文由 @纯爱掌门人 授权发布于人人都是产品经理。未经作者许可,禁止转载
题图来自Unsplash,基于CC0协议
该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务
- 目前还没评论,等你发挥!

起点课堂会员权益



