API 参考
示例使用本地运行时,其中 productId 是设备智能体 ID,deviceId 是真实设备 ID,HTTP 路径里的 products 按设备智能体理解。
选择协议
| 使用场景 | 推荐协议 | 说明 |
|---|---|---|
| 真实设备长期在线、上报状态、接收命令 | MQTT | 适合设备端连接、状态同步和命令响应。 |
| 其他智能体发现并调用已启用 A2A 的设备智能体 | A2A over MQTT | 使用 MQTT v5 的发现和请求/回复主题。 |
| 业务系统创建或执行多智能体场景与一次性任务 | HTTP + SSE | 使用 HTTP 提交持久化 Run,再通过 SSE 回放并持续接收事件。 |
| 业务系统、控制台扩展或自动化脚本调用 Device Agent | HTTP | 适合一次性请求、查询和命令下发。 |
| 实时语音交互 | WebSocket | 适合连续音频输入、实时识别结果和语音合成输出。 |
| 浏览器或设备端通过 WebSocket 连接 MQTT Broker | MQTT over WebSocket | 这是 MQTT 的传输方式,不是 Device Agent 的语音 WebSocket。 |
地址与认证
HTTP: http://127.0.0.1:3000
Voice WebSocket: ws://127.0.0.1:3001/ws/voice启用直接 HTTPS 后,主 HTTP 地址使用 https://。MQTT Broker 和语音服务需单独配置 TLS,浏览器连接时使用 wss://。使用私有 CA 时,向客户端提供 CA 文件并保留证书校验。
本地 Gateway 的主 HTTP 和 SSE 入口支持可选的服务 Bearer Token 和提供商控制台 SSO。语音 WebSocket 不继承这些认证,除设备或平台绑定校验外仍需另行保护。本地运行时不提供通用的多租户隔离、会话归属或资源级授权;共享或公网接入需在入口校验用户与资源权限。HTTPS/WSS 只保护传输。认证配置见环境变量。
配置 AGENT_GATEWAY_API_TOKEN 后,服务客户端在请求头中传入:
Authorization: Bearer <AGENT_GATEWAY_API_TOKEN>配置控制台 SSO 后,也可使用有效的登录会话 Cookie。认证失败返回 401 和 { "error": "Unauthorized" }。/health、/livez 和 /readyz 免鉴权;/metrics 使用已配置的服务或控制台认证。
MQTT
设备通过 MQTT 上线、上报状态和事件、接收命令并返回结果。服务地址、凭证、主题和消息体由当前 IoT 平台接入方式决定;从控制台获取所选设备的协议。
由 Device Agent 直接管理的设备使用 MQTT 接入中的完整协议:
- 主题、上报消息体、校验规则与命令响应。
- 通过 MQTT 对话:请求和回复示例、处理进度、请求关联、中断与清空。
- 时间同步与 MQTTX 验证。
A2A over MQTT
A2A 客户端通过 MQTT v5 发现并调用实时网络中已启用 A2A 的设备智能体:
| 用途 | 主题 |
|---|---|
| 发现智能体卡片 | $a2a/v1/discovery/{org_id}/{unit_id}/{agent_id} |
| 发现当前组织 | $a2a/v1/discovery/{org_id}/+/+ |
| 发现全部组织 | $a2a/v1/discovery/+/+/+ |
| 发送请求 | $a2a/v1/request/{org_id}/{unit_id}/{agent_id} |
| 接收回复 | 通过 MQTT v5 responseTopic 指定,通常位于 $a2a/v1/reply/... 下 |
智能体卡片使用 QoS 1 的 retained 消息;空 retained 消息表示移除卡片。Device Agent 处理 JSON-RPC SendMessage 请求。请求使用 QoS 1,并且必须携带 responseTopic;可选的 correlationData 会原样返回。需要延续同一上下文时使用 params.message.contextId;需要指定设备时使用 params.metadata.deviceId。
请求示例:
{
"jsonrpc": "2.0",
"id": "task-001",
"method": "SendMessage",
"params": {
"message": {
"role": "user",
"parts": [
{
"type": "text",
"text": "检查当前温度。如果温度过高,把空调切换到制冷模式。"
}
],
"taskId": "task-001",
"contextId": "server-room-session"
},
"metadata": {
"sender": "my-a2a-client",
"deviceId": "air-conditioner-01"
}
}
}回复使用 QoS 1,并发送到 responseTopic:
| JSON-RPC 结果 | 内容 |
|---|---|
result.statusUpdate | taskId、可选 contextId,以及 status.state、status.timestamp、可选 status.message。 |
result.artifactUpdate | taskId、可选 contextId,以及 artifact.artifactId、artifact.parts、append、lastChunk。 |
完整文本先通过 artifactUpdate.artifact.parts[].text 返回,再以 TASK_STATE_COMPLETED 结束;失败以 TASK_STATE_FAILED 结束。客户端应校验相同的 JSON-RPC id 和 correlationData,并为等待回复设置超时。
启用智能体、使用任务与场景以及排错方式见 A2A 多智能体协作。
HTTP
除健康检查和指标接口外,HTTP API 路径均以 /api 开头,没有版本号。平台的 /api/chat 返回持久化 Agent Run 事件流;A2A 提交接口返回 JSON,并提供独立事件流。其他接口使用 JSON。升级前请查看发布历史。
GET /health 只表示 HTTP 进程可响应,不检查大模型、MQTT 或设备是否可用。
JSON 错误响应包含 error,可选字段为 code、message、details 和外部平台错误码 providerCode。按 HTTP 状态码和返回的 code 处理错误,并检查可选字段是否存在。
对话和视觉
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /health | 检查 HTTP 进程是否可用。 |
GET | /livez | 检查进程存活状态。 |
GET | /readyz | 查询就绪检查;就绪返回 200,未就绪返回 503。 |
GET | /metrics | 获取 Prometheus 文本格式指标。 |
GET | /api/capabilities | 查询当前分角色部署能力清单及不可用原因。 |
POST | /api/chat | 发起文本对话,必须设置 stream: true。 |
POST | /api/cloud/runs | 提交持久化平台 Agent Run。 |
GET | /api/cloud/runs/:runId | 查询持久化平台 Agent Run。 |
POST | /api/cloud/runs/:runId/cancel | 取消排队中或运行中的平台 Agent Run。 |
GET | /api/cloud/runs/:runId/events | 订阅持久化 Run 事件,支持 Last-Event-ID。 |
GET | /api/sessions/:sessionId/history | 查询会话历史。 |
GET | /api/sessions/:sessionId/artifacts | 列出 Session 工作区生成的持久化文件。 |
GET | /api/sessions/:sessionId/artifacts/:encodedPath | 下载列表中的一份 Artifact,支持 ETag 和字节范围。 |
POST | /api/sessions/:sessionId/interrupt | 中断当前会话。 |
DELETE | /api/sessions/:sessionId | 归档当前对话并清空上下文;之后仍可使用相同的 sessionId。 |
POST | /api/sessions/:sessionId/tool-approvals | 授权或撤销高风险工具。仅限可信管理客户端。 |
POST | /api/vision/frames | 上传视觉帧,供后续对话使用。 |
部署能力清单
开放平台功能前,调用 GET /api/capabilities 确认部署能力,而非根据 API 进程环境推断其他角色。响应包含版本、manifest digest、配置版本与摘要、API/Worker/Ingress 健康状态、可用 Feature、Tool、Skill、主动 IM Channel 及机器可读的禁用原因。角色报告缺失、过期或不一致时返回 503 和 CLOUD_DEPLOYMENT_CAPABILITIES_UNAVAILABLE。
Run 固定接收时的 manifest;滚动升级期间,只能由报告完全兼容 manifest digest 的 Worker 执行。
文本对话
| 请求字段 | 要求 |
|---|---|
message | 必填,非空文本。 |
stream | 必须为 true。 |
sessionId | 终端接入建议自行生成并保存;历史、中断、清空和工具授权使用同一 ID。 |
metadata.productId、metadata.deviceId | 需要设备上下文时传入。 |
visionRefs | 可选,引用同一会话上传的视觉帧。 |
对话示例:
$ curl -N http://127.0.0.1:3000/api/chat \
-H 'Content-Type: application/json' \
-H 'Accept: text/event-stream' \
-d '{
"message": "查看当前温度,并把目标温度设置为 24 度",
"stream": true,
"sessionId": "demo-session",
"metadata": {
"productId": "thermostat",
"deviceId": "thermostat-001"
}
}'SSE 事件:
| 事件 | 数据 | 是否结束 |
|---|---|---|
status | type 为 start、thinking、thinking_delta、text_block、tool_start、tool_end、complete、error、compaction_start 或 compaction_end。 | 否 |
message | { "text", "sessionId", "timestamp" } | 是 |
error | { "error", "sessionId", "timestamp?" } | 是 |
本地运行时中,同一 sessionId 同时只能有一个对话请求,否则返回 429。平台将请求存入持久化队列,同一会话内串行执行。客户端断开只会关闭事件投递,Run 会继续执行。
平台对话的重试与重连规则:
- 保存
sessionId,每轮请求使用一个Idempotency-Key请求头;重试时复用相同的 POST 请求体和 Key。内容不同返回409,省略 Key 则每次重试都创建新请求。 - 重连时传入
Last-Event-ID或?after=<最后的 id>,回放最后收到的 SSEid之后的事件。 /api/chat不返回 Run ID;中断会话使用POST /api/sessions/:sessionId/interrupt。
提交平台 Run
需要 Run ID 以查询状态、接收事件或取消执行时,使用 POST /api/cloud/runs。此接口供可信业务系统调用,需通过控制台会话或服务认证,并在集成入口校验资源权限;它不执行下文的外部用户设备绑定校验。请求使用 prompt 和顶层设备标识:
$ export BASE=https://agent.example.com
$ curl "$BASE/api/cloud/runs" \
-H 'Content-Type: application/json' \
-d '{
"sessionId": "demo-session",
"idempotencyKey": "temperature-query-001",
"prompt": "查看当前温度",
"productId": "thermostat",
"deviceId": "thermostat-001"
}'sessionId、idempotencyKey 和非空 prompt 必填。新请求返回 201 { "run": {...}, "created": true };相同请求重试返回 200、原 Run 和 created: false。
保存 run.id,按上表查询、订阅事件或取消 Run。事件流支持 Last-Event-ID 或 after。取消不能撤销已完成的设备或外部操作,再次提交前先核对结果。
会话历史与上下文
GET /api/sessions/:sessionId/history 从最新消息向前分页。limit 默认为 20,范围为 1–100。返回 sessionId、messages、messageOffset、nextCursor,上下文用量可用时还包含 contextUsage。
向 before 原样传入上一页的 nextCursor 以读取更早内容,nextCursor: null 表示结束。分页保留用户轮次边界,消息数可能超过 limit。清空会话或改变历史分支可能使游标失效;收到 400 时重新加载最新页。
contextUsage 包含 tokens、contextWindow 和 percent。刚恢复或整理后的用量可能尚不可用,应等待后续模型响应,不能把未知值显示为 0。对话 status 事件可携带上下文用量,compaction_start 与 compaction_end 用于显示整理进度;这些事件不是最终回答。
清空会归档当前对话并重置上下文,不删除长期记忆或设备。界面说明见对话与记忆。
耐久 Session 控制
平台的 POST /api/sessions/:sessionId/interrupt 和 DELETE /api/sessions/:sessionId 使用 Idempotency-Key 请求头支持安全重试:
- Key 长度为 1 至 256 个字符,不含 ASCII 控制字符。每次 Stop 或 Clear 使用新 Key,同一操作的重试复用原 Key。
- 重试只回放首次接受的目标和结果,不影响之后创建的工作。不同 Session、操作类型或目标不能共用 Key。
- 兼容请求可以省略该头,但每次重试都成为新控制操作,不能回放原结果。
Stop 或 Clear 可能先于某轮对话到达时,另用 X-Device-Agent-Target-Idempotency-Key 传入该轮的 idempotencyKey。匹配请求即使稍后到达,也会被标记为取消而不执行。相同目标 Key 对应不同内容返回 409;没有待接收的对话请求时省略此头。
Interrupt 会立即取消排队中的 Run,并请求取消运行中的 Run;响应分别返回 pendingAdmissionCancelled、queuedCancelled、runningCancellationRequested 和 runningAwaitingAcknowledgement。存在排队或运行中的 Run 时,Clear 返回 409 AGENT_SESSION_ACTIVE;Stop 完成后,使用相同操作 Key 和目标 Key 重试 Clear。
操作回放与延迟请求拦截仅在 DEVICE_AGENT_RUN_RETENTION_HOURS 配置的 Agent Run 明细保留期内有效。客户端重试期限须更短;记录过期后,新的用户操作使用新 Key。
Session Artifact
GET /api/sessions/:sessionId/artifacts 只列出 Agent Session 工作区中的文件,并返回每个文件的 path、不透明 encodedPath、字节数和 SHA-256 摘要。下载时必须原样使用响应里的 encodedPath,不要自行编码路径。Session 历史和内部 JSONL 文件不会作为 Artifact 暴露。
Artifact 下载使用 application/octet-stream、ETag: "sha256:<digest>"、Accept-Ranges: bytes 和 Cache-Control: private, no-cache。传 If-None-Match 可获得 304;传一个 Range: bytes=<start>-<end> 区间可获得 206。无效范围返回 416,未知 Session 或 Artifact 返回 404。
耐久产品事件流
| 路径 | SSE 事件 | 内容 |
|---|---|---|
/api/device-events/stream?namespace=&productId=&deviceId= | device | 持久化 Provider 与浏览器模拟器设备事件;三个筛选项都可省略。 |
/api/timers/events | timer | 持久化 Timer changed、fired、complete 和 error 转换。 |
/api/agent-replies/events | agent_reply | 可信 Workflow、Timer、A2A、IM 和 MQTT 调度的持久化回复;普通对话和 PromptTask 不会广播到这里。 |
事件流的游标和错误规则:
- 不传游标时只接收新事件。回放时将最后收到的 SSE
id传给Last-Event-ID或?after=<cursor>,Header 优先。 - 首个事件为
ready,携带{ "ok": true };keepalive 注释不推进游标。 - 非法游标返回
400;游标超出已保留的历史边界返回409并给出恢复边界;存储预检失败返回503。 - 流启动后存储不可用时,服务端发送不含敏感信息的
error事件并关闭连接。
平台 PromptTask
平台只接受 POST /api/chat 或 POST /api/cloud/runs 顶层 promptTask 对象表示的公开 PromptTask:
{
"promptTask": { "kind": "device.modeling" }
}支持的 kind 为 device.modeling、device.simulator.behavior、device.icon.select 和 device.event.payload.generate。不要把 taskKind 或 taskScope 放入 metadata;平台会以 400 和 PROMPT_TASK_CONTRACT_REQUIRED 拒绝该旧格式。
PromptTask 使用专用系统提示词和独立临时 Session,禁用工具,不读写普通 Agent Session。sdk.bundle.enhance 和未知 kind 返回 400 和 PROMPT_TASK_UNSUPPORTED,不会作为普通对话执行。持久化请求在 /api/cloud/runs 中使用稳定的 sessionId 与 idempotencyKey。
工具授权
平台模式的 approval_required SSE 事件包含持久化 approvalId、toolName、toolCallId、精确 target、提示信息和过期时间。可信管理客户端核对这些字段后,在 5 分钟内批准操作:
{
"approvalId": "approval-id-from-sse",
"decision": "approve",
"scope": "once"
}decision 支持 approve、reject 和 revoke。只有 approve 可以携带 scope;scope 支持 once、session,默认 once。服务端会把决策绑定到已存储的会话、工具调用、输入摘要和目标;客户端不需要重新提交或修改 target。
平台会为 write_file、execute_command、mqtt_publish、webhook 和 send_im_message 请求授权。授权被批准、拒绝、过期、消费或撤销时,SSE 会发送 approval_decision。优先使用单次授权。通过 use_command、update_device_state 和 use_device_job 执行的模型化设备操作仍由持久化执行栅栏避免重复执行,但不要求单独授权。
Standalone 仍使用本地 action、toolName 和精确 target 请求契约,只为 write_file 和 execute_command 授权;它不使用平台持久化 approvalId。
视觉帧
视觉帧先通过 /api/vision/frames 上传,再把返回的 frameId 和 capturedAt 作为 visionRefs 传给 /api/chat。mimeType 支持 image/jpeg、image/png 和 image/webp。
$ curl http://127.0.0.1:3000/api/vision/frames \
-H 'Content-Type: application/json' \
-d '{
"sessionId": "demo-session",
"deviceId": "thermostat-001",
"mimeType": "image/png",
"imageBase64": "<base64>",
"source": "camera"
}'上传成功返回 201,包含 frameId、capturedAt、source 和 mimeType;请求无效返回 400,视觉帧存储不可用返回 503。imageBase64 字符串最大 6 MiB;每个会话作用域最多保留 20 帧,本地部署 5 分钟后过期,平台部署默认 15 分钟后过期。
平台部署会把每一帧绑定到服务端确定的租户、主体、命名空间、会话、产品和设备作用域。引用帧时必须使用相同的 sessionId、productId 和 deviceId。每个请求可以引用 1 至 3 帧。帧不存在、已过期、重复或作用域不匹配时,接口返回 400 和 VISION_FRAME_REFERENCE_INVALID。
带视觉帧发起对话:
{
"message": "根据这张图判断设备屏幕是否异常",
"stream": true,
"sessionId": "demo-session",
"metadata": {
"productId": "thermostat",
"deviceId": "thermostat-001"
},
"visionRefs": [
{
"frameId": "frame-001",
"capturedAt": "2026-05-11T10:00:00.000Z",
"source": "camera"
}
]
}持久化平台 Agent Run 也在顶层接受相同的 visionRefs:
$ curl http://127.0.0.1:3000/api/cloud/runs \
-H 'Content-Type: application/json' \
-d '{
"sessionId": "demo-session",
"idempotencyKey": "inspect-frame-001",
"prompt": "使用这张图片检查设备屏幕是否异常",
"productId": "thermostat",
"deviceId": "thermostat-001",
"visionRefs": [
{
"frameId": "frame-001",
"capturedAt": "2026-05-11T10:00:00.000Z",
"source": "camera"
}
]
}'平台在 Run 入队前解析全部引用,并把不可变图片字节写入持久化请求。因此,帧过期、API 重启、租约丢失或 Worker 替换都不会影响该 Run。使用同一 idempotencyKey 提交不同帧内容时,接口返回 409,不会修改已有 Run。
平台 Skill 与动态 Tool
平台把租户的用户 Skill 和动态 Tool 扩展保存为不可变版本。动态 Tool 源码在 Worker 中执行,以下管理接口只接受可信服务端内容。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/skills | 查询当前用户 Skill 及其启用状态。 |
POST | /api/skills/import | 从 multipart 字段 file 导入 Skill ZIP。 |
GET | /api/skills/:name/export | 把当前 Skill 版本导出为 ZIP。 |
PATCH | /api/skills/:name | 使用 { "enabled": true|false } 启用或禁用 Skill。 |
DELETE | /api/skills/:name | 从当前目录中移除 Skill。 |
GET | /api/tools | 查询当前动态 Tool、描述符、版本和启用状态。 |
PATCH | /api/tools/:name | 使用 { "enabled": true|false } 启用或禁用一个动态 Tool。 |
GET | /api/tool-editor/extensions | 查询当前 Tool 扩展包。 |
GET | /api/tool-editor/extensions/:name/source | 读取一个当前 Tool 扩展的源码。 |
POST | /api/tool-editor/extensions/save | 校验并保存一个不可变 Tool 扩展版本。 |
DELETE | /api/tool-editor/extensions/:name | 从当前目录中移除 Tool 扩展。 |
Skill ZIP 必须包含一个 <skill-folder>/SKILL.md,YAML frontmatter 声明 name 和 description;可包含 references/、templates/ 和 assets/。压缩后最大 5 MiB、最多 256 个文件条目、解压后最大 25 MiB。
$ curl "$BASE/api/skills/import" \
-F 'file=@customer-diagnostics.zip'
$ curl "$BASE/api/skills/customer-diagnostics" \
-X PATCH \
-H 'Content-Type: application/json' \
-d '{"enabled":false}'Tool 扩展导出默认注册函数,使用系统提供的 registerTool 和 Type。每个 Tool 必须声明执行描述符:只读操作使用 { "execution": "read", "approval": "none" };需要审批的副作用操作声明 approval: "required",审批请求只包含允许公开的 values 和 SHA-256 digests。缺少描述符、与静态 Tool 或扩展内 Tool 重名、Schema 无效或导入不支持的 npm 包时,返回 400,不创建版本。
{
"name": "customer-tools",
"code": "export default function ({ registerTool, Type }) { registerTool({ name: 'customer_lookup', description: 'Look up one customer.', parameters: Type.Object({ customerId: Type.String() }), async execute(_id, params) { return { content: [{ type: 'text', text: params.customerId }], details: {} }; } }, { execution: 'read', approval: 'none' }); }"
}向 POST /api/tool-editor/extensions/save 提交上述请求体,响应包含不可变的 revision 和 contentDigest。
Agent Run 固定接收时启用的 Skill 和 Tool 版本,目录变更只影响新 Run。Worker 替换或幂等重试仍使用原版本;Worker 校验发现版本、摘要或 Tool 策略不匹配时,Run 失败。
平台 MQTT 操作与 Zero EMQX
平台 MQTT 订阅作为持久化操作由 Ingress 执行,API 不另开 Broker 连接;已接收的请求在 API 或 Ingress 替换后保留。
| 方法 | 路径 | 用途 |
|---|---|---|
POST | /api/mqtt/subscriptions | 启动一次有边界的 MQTT 订阅。 |
GET | /api/mqtt/subscriptions/:operationId | 查询持久化订阅的状态或结果。 |
POST | /api/mqtt/subscriptions/:operationId/cancel | 请求取消并从 Broker 退订。 |
GET、PUT | /api/agent-config | 查询或替换带 revision 的非敏感平台运行配置。 |
POST | /api/agent-config/zero-emqx | 创建并应用一个托管的 Zero EMQX 实例。 |
GET | /api/agent-config/zero-emqx/operations/:operationId | 查询持久化创建操作的状态或结果。 |
GET | /api/agent-config/mqtt-status | 查询当前由 Ingress 持有的 Broker 连接状态。 |
订阅和 Zero EMQX 创建接口均要求 Idempotency-Key 请求头,长度为 1 至 200 个字符。相同 Key 与请求返回原操作;相同 Key 携带不同内容返回 409 和 CLOUD_MQTT_OPERATION_CONFLICT。
使用主题过滤器和明确的资源上限启动订阅:
$ curl "$BASE/api/mqtt/subscriptions" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: collect-demo-events-001' \
-d '{
"topic": "demo/+/events",
"qos": 1,
"duration": 10,
"maxMessages": 20,
"maxBytes": 65536
}'订阅参数:
qos:默认0。duration:默认 10 秒,范围 1 至 60。maxMessages:默认 100,范围 1 至 100。maxBytes:默认 262,144,范围 1 至 1,048,576。- MQTT 5 可选
subscriptionIdentifier(1 至 268,435,455)及最多 32 个userProperties;属性名和值各不超过 512 个 UTF-8 字节。
操作状态为 pending、running、succeeded、failed、cancelled 或 uncertain。新操作在短暂等待内完成时返回 201,否则返回 202 和 Location,继续查询该地址直至终态。成功结果包含 messages、messageCount、totalBytes 和 limitReached。
仅 cancelled 表示取消完成。uncertain 表示 Broker 清理结果未知;检查 Broker 状态后再决定是否用新 Key 重试。
只有在用户明确确认后才能创建 Zero EMQX:
$ curl "$BASE/api/agent-config/zero-emqx" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: provision-demo-broker-001' \
-d '{"confirmed":true,"tag":"customer-demo"}'tag 可省略,长度为 1 至 63 个字符。部署需配置 Zero EMQX Manager,并有就绪的 Ingress。新凭据加密保存,与非敏感配置一同提交;Ingress 按该版本重连。API 不返回 Broker 用户名或密码,成功结果包含 instanceId、不带凭据的 brokerUrl 和 wsUrl、expiresAt、configRevision、applyStatus: "applied"。
实例可能已创建但回复丢失时,操作以 uncertain 和 ZERO_EMQX_PROVISION_UNCERTAIN 结束,不自动重试。
使用状态接口确认 Ingress 当前实际持有的配置:
$ curl "$BASE/api/agent-config/mqtt-status"响应包含 state、connected、不带凭据的 brokerUrl、revision、activeConfigRevision、restartRequired、updatedAt 和可选的脱敏 lastError,不返回 MQTT 凭据。connected: true 表示连接与必需订阅均就绪;restartRequired: true 表示保存的版本比 Ingress 当前使用的版本更新。
GET /api/agent-config 返回 x-device-agent-config-revision、x-device-agent-restart-required 和 x-device-agent-restart-sections。最后一个头以逗号分隔需要重启的进程缓存配置节,无需重启时为空。需要重启时,受影响角色平滑退出,由部署监管器按新 revision 替换。
更新时,将读取到的 revision 放入 PUT /api/agent-config 的 x-device-agent-config-revision 请求头;缺失或无效返回 400,过期返回 409。接口保存非 Secret 配置,掩码响应中的部署 Secret,并拒绝请求体中的部署 Secret 明文。启动环境变量仅初始化 revision 1,后续通过此接口更新。
设备、命令和事件
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/products | 查询设备智能体列表。 |
GET | /api/products/:productId | 查询设备智能体定义,包括属性、命令、参数和事件。 |
GET | /api/products/:productId/devices | 查询设备列表;支持 status 和 tags 筛选。 |
GET | /api/products/:productId/devices/:deviceId | 查询设备详情。 |
POST | /api/products/:productId/devices/:deviceId/commands | 向在线设备下发命令。 |
GET | /api/products/:productId/devices/:deviceId/events | 查询设备事件。 |
先查询真实 ID 和产品定义,不要自行拼接命令名或参数:
$ export BASE=http://127.0.0.1:3000
$ curl "$BASE/api/products"
$ export PRODUCT_ID=your_product_id
$ curl "$BASE/api/products/$PRODUCT_ID"
$ curl "$BASE/api/products/$PRODUCT_ID/devices?status=online&tags=site:lab"
$ export DEVICE_ID=your_device_id设备列表返回 status、state、tags、modelStatus 和 lastSeenAt。status 支持 online、offline、error、all;多个 tags=key:value,key:value 条件需同时匹配。
命令名和参数必须来自产品定义:
$ curl "$BASE/api/products/$PRODUCT_ID/devices/$DEVICE_ID/commands" \
-H 'Content-Type: application/json' \
-d '{
"command": "set_drive_mode",
"params": {
"mode": "eco"
},
"timeoutMs": 30000
}'timeoutMs 默认 30,000,最大 120,000。命令响应:
| HTTP | 结果 |
|---|---|
200 | { "result": <设备响应> } |
400 | COMMAND_VALIDATION_FAILED |
404 | DEVICE_NOT_FOUND |
409 | DEVICE_NOT_ONLINE 或 DEVICE_COMMAND_FAILED |
504 | COMMAND_TIMEOUT |
收到 504 时,设备仍可能稍后执行命令;确认设备状态后再决定是否重试。
设备事件按新到旧返回,limit 默认 50、最大 200。下一页把本页最后一条事件的 id 作为 beforeId。
产品 A2A 卡片生命周期
产品 A2A 卡片接口用于发布或删除根据产品运行时模型生成的 retained 发现卡片。
| 方法 | 路径 | 用途 |
|---|---|---|
POST | /api/products/:productId/a2a-card | 发布产品 Agent Card。 |
DELETE | /api/products/:productId/a2a-card | 删除 retained 产品 Agent Card。 |
GET | /api/products/:productId/a2a-card/operations/:operationId | 查询一条平台发布操作。 |
DELETE | /api/products/:productId | 删除产品及其 retained Agent Card。 |
发布请求体支持 publishToA2ARegistry,默认值为 false:
$ curl "$BASE/api/products/$PRODUCT_ID/a2a-card" \
-X POST \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: publish-product-card-001' \
-d '{"publishToA2ARegistry":true}'平台 Ingress 在等待时间内完成操作时,发布和取消发布返回 200,产品删除返回 204。否则返回 202、{ "operationId": "...", "status": "pending" } 和指向产品操作 URL 的 Location。查询该 URL,直到 status 为 applied 或 failed:
{
"operationId": "operation-001",
"productId": "thermostat",
"action": "unpublish",
"status": "applied",
"desiredRevision": 2,
"appliedRevision": 2,
"createdAt": "2026-08-20T01:00:00.000Z",
"updatedAt": "2026-08-20T01:00:01.000Z"
}同一变更复用 Idempotency-Key,返回原操作,不创建新的产品 revision。响应不暴露 Broker 凭据、连接错误或已存储的 Agent Card。
外部平台用户绑定
ThingsNeural 接入的下列设备入口会通过 Client API 校验用户与设备的绑定关系,结果短暂缓存。
外部调用方须用 X-External-User-Id 请求头传入平台用户 ID,缺失或为空返回 401。对话和 Vision 上传可通过有效的控制台会话跳过此校验;其他 IoT 平台不要求该头。
| 入口 | 校验时机 |
|---|---|
| 设备、命令和事件接口 | 每次请求;设备列表只返回该用户已绑定的设备。 |
POST /api/chat | metadata 携带 deviceId 时;只带 productId 不带设备的请求会被拒绝。 |
POST /api/vision/frames | 请求体携带 deviceId 时。 |
/ws/voice | 产品为 ThingsNeural 时,URL 必须携带 ?userId=,见下方 WebSocket 部分。 |
| HTTP | 错误码 | 含义 |
|---|---|---|
401 | EXTERNAL_IDENTITY_REQUIRED | 缺少外部用户 ID 或设备标识。 |
403 | EXTERNAL_DEVICE_NOT_BOUND | 该用户未绑定此设备,或请求只引用受守卫的产品而未指定设备。 |
503 | EXTERNAL_BINDING_UNAVAILABLE | 绑定查询暂时不可用,请稍后重试。 |
「设备不存在」和「未绑定」均返回 403,不对外区分。
多智能体编排
平台将 A2A 场景创建、执行和一次性任务作为持久化 Run 交给 Worker。调用前配置可用的大模型,从 /api/a2a/agents 返回的 cards 中选取 status: online 的 agentId。visibility 支持 public、private、all,默认 all。需要本地设备时,用卡片顶层 productId 查询 deviceId,不要自行拼接标识。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/a2a/agents?visibility=all | 查询当前 A2A 组织和单元中的智能体卡片及在线状态。 |
GET | /api/a2a/scenes | 查询已保存场景。 |
POST | /api/a2a/scenes | 提交持久化场景创建和流程生成。 |
GET | /api/a2a/scenes/:sceneId | 查询场景详情和生成状态。 |
POST | /api/a2a/scenes/:sceneId/input | 为处于 needs_input 的场景提交持久化输入。 |
POST | /api/a2a/scenes/:sceneId/recreate | 提交持久化的场景替换和重新生成。 |
DELETE | /api/a2a/scenes/:sceneId | 删除场景。 |
POST | /api/a2a/chat | 提交持久化的全局场景对话或执行。 |
POST | /api/a2a/scenes/:sceneId/chat | 提交指定已就绪场景的持久化对话或执行。 |
POST | /api/a2a/tasks | 提交持久化的一次性多智能体任务。 |
GET | /api/a2a/runs/:runId | 查询一条持久化编排 Run。 |
POST | /api/a2a/runs/:runId/cancel | 请求取消一条 Run。 |
GET | /api/a2a/runs/:runId/events | 回放并持续接收一条 Run 的持久化 SSE 事件。 |
提交并监控 Run
上表的 A2A 提交 POST 均要求 Idempotency-Key 请求头(POST /api/a2a/runs/:runId/cancel 除外)。相同 Key 与请求返回原 Run,不同内容返回 409。
$ curl -i "$BASE/api/a2a/scenes" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: create-server-room-scene-001' \
-d '{
"name": "机房响应",
"instructions": "检查机房温湿度,超过阈值时调节空调并返回处理报告",
"agentIds": ["<sensor-agent-id>", "<air-conditioner-agent-id>"]
}'所有提交返回 202、含 run 的 JSON 和 Location: /api/a2a/runs/<runId>;场景生成另含 sceneId 与场景 status。保存 Run 和场景标识。
创建和重新创建使用 name、instructions、agentIds;场景输入使用 { "answers": "..." };场景和全局对话使用各自的对话输入;一次性 Task 使用 text,可带 devices。
轮询 GET /api/a2a/runs/:runId;pending 和 running 表示未结束,终态为 input_required、succeeded、failed、cancelled 或 uncertain。
input_required 结束当前 Run。需要绑定设备时,终态 SSE 提供一次性 continuationId;将它与设备绑定选择、一个新的 Idempotency-Key 提交到对应场景、全局对话或 Task 接口。继续查询返回的新 Run,原 Run 不会恢复。场景生成结束后还需查询场景:
status | 处理方式 |
|---|---|
creating | 继续等待并查询。创建和重新生成都使用此状态。 |
needs_input | 读取 pendingInput,提交补充信息后继续查询。 |
ready | 场景可以执行。 |
failed | 读取 failureReason;根据原因处理后调用 recreate。 |
使用 curl -N "$BASE/api/a2a/runs/$RUN_ID/events" 接收事件,重连时传入递增的 SSE id:Last-Event-ID: <last-id> 或 ?after=<last-id>。断开流只取消订阅;停止执行需调用 cancel 接口。uncertain 表示外部动作可能已完成,提交新请求前先核对目标状态。
控制台操作见 A2A 多智能体协作。
工作流
HTTP 接口管理工作流及运行记录;对话中的工作流工具也支持创建和完整替换。使用方式见 工作流。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/workflow-capabilities | 查询工作流可用工具、技能和 IM 通道。 |
GET | /api/tool-catalog | 查询当前已启用工具的名称与描述。 |
GET | /api/workflows | 查询已保存工作流。 |
PUT | /api/workflows/:workflowId | 创建或完整替换一份工作流定义。 |
PATCH | /api/workflows/:workflowId | 启用或停用工作流。请求体为 { "enabled": true } 或 { "enabled": false }。 |
DELETE | /api/workflows/:workflowId | 删除工作流。 |
GET | /api/workflow-runs | 查询工作流运行记录,支持 limit、offset 和 q。 |
GET | /api/workflows/:workflowId/runs | 查询指定工作流的运行记录。 |
GET | /api/workflow-runs/:runId | 查询一次运行及各步骤结果。 |
PUT 替换完整定义,须包含所有要保留的字段。请求体的 id 可省略,传入时须与路径一致;省略 enabled 则设为 true,包括替换已有工作流。创建返回 201,替换返回 200。
$ export WORKFLOW_ID=battery-low-alert
$ curl "$BASE/api/workflows/$WORKFLOW_ID" \
-X PUT \
-H 'Content-Type: application/json' \
-d '{
"trigger": {
"kind": "device_event",
"eventName": "battery_low_warning"
},
"actions": [
{
"type": "log_event",
"eventType": "device_event"
}
]
}'无效 JSON 对象或工作流定义返回 400,错误码为 WORKFLOW_DEFINITION_INVALID。请求体中的 id 与路径不一致时返回 400,错误码为 WORKFLOW_ID_MISMATCH。
列表返回 { "workflows": [...] };修改返回 { "workflow": {...} };删除成功返回 204。运行列表返回 runs、total、limit 和 offset;传入 q 时还会返回 query。limit 默认 50、最大 100。
定时任务
定时任务通过设备智能体对话创建。HTTP 可以查询、暂停、恢复、修改、取消任务,并读取运行历史。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/timers?status=active&limit=50 | 按 active、paused、running、completed 或 deleted 查询任务。 |
PATCH | /api/timers/:timerId | 设置 enabled,或替换 instruction,也可以同时修改。 |
DELETE | /api/timers/:timerId | 取消任务。 |
GET | /api/timers/:timerId/runs?limit=25&offset=0 | 分页查询该任务的运行历史。 |
对话工具 update_scheduled_task 可修改计划类型、规则与时区,保留任务 ID 和目标范围;HTTP PATCH 仅支持指令与启停。更换设备范围须取消并重新创建任务。
任务列表返回 { "timers": [...] },默认只查询 active,limit 最大 100;修改返回 { "timer": {...} },取消成功返回 204。运行历史返回 { "runs", "total", "limit", "offset" }。
控制台中的创建和管理流程见 定时任务。
Webhook
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/webhooks | 查询已保存连接,不返回 URL、Header 或签名密钥。 |
POST | /api/webhooks | 创建连接。 |
PATCH | /api/webhooks/:webhookId | 修改名称、启用状态、凭据、消息体模板或响应条件。 |
DELETE | /api/webhooks/:webhookId | 删除未被使用的连接;仍被工作流引用时返回 409。 |
POST | /api/webhooks/:webhookId/test | 发送测试消息并等待投递结果。 |
创建一个基础自定义连接:
$ curl "$BASE/api/webhooks" \
-H 'Content-Type: application/json' \
-d '{
"name": "operations-alerts",
"enabled": true,
"credential": {
"url": "https://hooks.example.com/device-agent"
},
"preset": "custom",
"bodyTemplate": {
"message": "{{message}}"
}
}'通过 POST /api/webhooks/:webhookId/test 和 { "message": "Device Agent test" } 测试连接。
JSON 请求体最大 64 KiB。创建成功返回 201 { "webhook": {...} },修改返回 200 { "webhook": {...} },删除返回 204,测试成功返回 200 { "ok": true, "httpStatus": <状态码> }。
预设、签名、模板、投递限制和工作流用法见 Webhook。
平台监控、额度与授权
以下接口仅用于平台部署,使用交付地址与部署约定的认证方式。界面操作见监控、设备 AI 额度和授权管理。
| 方法 | 路径 | 用途与主要参数 |
|---|---|---|
GET | /api/monitoring | 平台指标;必填 section,可选 range。 |
GET | /api/monitoring/ai-usage/devices | 搜索设备;q 为关键词,limit 为 1–20。也可传入 productId 和 deviceId 查询指定设备。 |
GET | /api/monitoring/ai-usage | 指定设备的用量;必填 productId、deviceId、from、to。 |
GET | /api/monitoring/ai-usage/cumulative | 累计用量;可选 from 和成对的 productId、deviceId。 |
GET | /api/monitoring/voice | 语音指标;可选 range 和成对的设备标识。 |
GET | /api/monitoring/voice/turns | 设备逐轮语音指标;必填设备标识、from、to,可选 cursor。 |
GET | /api/monitoring/voice/turn | 一轮语音详情;必填设备标识与 UUID 格式的 id。 |
GET | /api/ai-credits/accounts | 读取设备余额;必填 productId、deviceId。 |
POST | /api/ai-credits/adjustments | 增加或扣减指定设备的人工余额。 |
GET | /api/cloud/license | 读取当前部署的授权、用量、排队数量和有效期。 |
POST | /api/cloud/license | 导入新的授权文件;需要独立的授权管理令牌。 |
监控查询
/api/monitoring 的 section 支持 overview、usage、cluster、connections、latency;range 支持 1h、24h、7d、30d,默认为 24h。返回所选分区的快照、时间窗口和采集状态。未配置监控或暂时无法查询时返回 503,分别使用 CLOUD_MONITORING_NOT_CONFIGURED 或 CLOUD_MONITORING_UNAVAILABLE。
curl 'https://agent.example.com/api/monitoring?section=overview&range=24h'设备用量查询的 from、to 为带时区的 ISO 时间,范围向前且最长 31 天。按调用结束时间统计,包含 summary、byModel 和 series。可选筛选参数为 kind(llm、asr、tts)、provider、model、source 和 outcome(completed、failed、cancelled);直接传入这些参数,不使用 filters 查询参数。
累计查询不限制为 31 天,按调用开始时间统计。省略设备标识时汇总当前部署作用域内已记录的设备调用,提供设备标识时只汇总该设备;省略 from 时从首条记录开始。费用为已计价调用的估算,未计价调用不表示免费。
语音汇总的 range 支持 1h、24h、7d,默认 24h。逐轮查询返回 turns 与 nextCursor,翻页时复用原设备和时间范围,并把返回的游标传入 cursor。语音逐轮指标保留 7 天,详情不存在时返回 404。
设备余额调整
余额快照包含 currency、limit、paidBalance、allowanceBalance、totalBalance 和 used。查询现有设备的余额不会创建持久额度账户。
调整请求的 JSON 格式如下:
{
"productId": "thermostat",
"deviceId": "device-001",
"amount": 10,
"reason": "补充试用额度",
"idempotencyKey": "credit-adjustment-example-001"
}调整规则:
amount使用当前结算货币,非零且最多 6 位小数;正数增加、负数扣减余额。reason必填,最长 512 个字符。- 同一调整复用
idempotencyKey和请求内容,新调整使用新 Key。Key 按设备账户隔离,同一账户内内容不同返回409。 - 成功返回更新后的余额快照。
统一额度和计费规则通过已有的 GET / PUT /api/agent-config 管理 aiCredits 配置节,更新时保留完整配置并携带读取到的 revision。已有额度账户时不能更改结算货币。
导入平台授权
GET /api/cloud/license 返回当前授权状态、授权内容和用量:used 为最近 60 秒已启动的请求数,hardLimit 为包含弹性额度的每分钟请求上限,queued 为排队请求数。
导入时,POST 请求体为完整授权文件文本,Content-Type 使用 text/plain,并提供 X-Device-Agent-License-Admin-Token 请求头。该令牌与日常登录和服务凭据独立;普通登录或服务认证仍需满足部署要求。仅从可信管理端调用,令牌不应写入浏览器公开配置。
成功返回新的授权快照,当前部署无需重启。令牌缺失或错误、文件不匹配、过期或尚未生效时返回 403;授权版本冲突返回 409。导入失败保留已安装的授权。License 不包含模型和语音等第三方服务额度。
平台 运行日志
平台部署会把 API、Worker 和 Ingress 三个角色产生的脱敏 JSON 日志持久化归档到 PostgreSQL。以下接口不会暴露 Pod 文件路径,仅供可信的服务端管理客户端使用。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/logs/files?role=<角色>&instanceId=<实例>&limit=200&cursor=<游标> | 使用稳定快照游标查询按天归档的日志文件。两个筛选项都可省略,但传 instanceId 时必须同时传 role;limit 范围为 1–500。 |
GET | /api/logs/tail?role=<角色>&instanceId=<实例>&file=<文件>&lines=200&before=<游标> | 每次读取最多 1,000 条日志,并使用 nextBeforeOffset 向前翻页。 |
GET | /api/logs/download?role=<角色>&instanceId=<实例>&file=<文件> | 把一份精确归档下载为换行分隔的 JSON。 |
role 支持 api、worker 和 ingress;单进程 cloud-all 开发运行时使用 all。应先查询可用的角色、实例和文件,不要自行拼接实例 ID。归档文件名使用 UTC 日期,格式为 device-agent-YYYY-MM-DD.log。
$ curl "$BASE/api/logs/files?role=worker&limit=200"
$ curl "$BASE/api/logs/tail?role=worker&instanceId=worker-a&file=device-agent-2026-08-20.log&lines=200"分页与错误处理:
- 文件列表的
nextCursor原样传入下一次请求的cursor,直到null。游标固定首批归档快照,避免角色替换或新日志导致漏页。 - 每页 tail 记录按旧到新排列,
nextBeforeOffset不为空时传入before读取更早记录;读完返回200和空records。 - 归档不存在返回
404;下载超过 64 MiB 返回413和CLOUD_LOG_DOWNLOAD_TOO_LARGE;归档暂不可用返回503和CLOUD_LOG_ARCHIVE_UNAVAILABLE。
默认每进程缓冲最多 2,000 条、每批写入 200 条、保留 14 天。角色在启动标记归档后就绪;瞬时失败会重试,写入超时未确认、持续失败或累计丢弃 100 条时标为不健康,由部署替换。
可通过 DEVICE_AGENT_CLOUD_LOG_BUFFER_RECORDS、DEVICE_AGENT_CLOUD_LOG_BATCH_SIZE、DEVICE_AGENT_CLOUD_LOG_FLUSH_INTERVAL_MS、DEVICE_AGENT_CLOUD_LOG_RETENTION_DAYS、DEVICE_AGENT_CLOUD_LOG_PRUNE_INTERVAL_MS 和 DEVICE_AGENT_CLOUD_LOG_PRUNE_BATCH_SIZE 调整策略,滚动升级时全部角色保持一致。
WebSocket
语音通道使用 /ws/voice。连接时可带 Header:
| Header | 说明 |
|---|---|
Protocol-Version | 协议版本,当前为 3。 |
Device-Id | 当前设备 ID。 |
Client-Id | 客户端 ID,不传时默认使用设备 ID。 |
使用提供用户绑定校验的接入方式时,外部平台用户在 URL 携带 userId;hello 传入 userId 时必须与 URL 一致。服务端校验设备绑定,连接期间周期性复查,绑定解除后以 1008 关闭。
提供设备凭证认证的接入方式,也支持设备端在 URL 传入 productKey、deviceKey、sign。这三个值由对应平台设备凭证规则生成,hello.deviceId 必须与 deviceKey 一致,hello.productId 使用 Device Agent 中的设备智能体 ID。设备认证与外部用户绑定是两种接入路径,按部署提供的认证方式使用。不要将平台管理凭证当作设备签名。认证未通过返回 1008,认证服务不可用返回 1011。
语音连接建立后,先发送 hello:
{
"type": "hello",
"version": 3,
"audio_params": {
"format": "pcm",
"sample_rate": 16000,
"channels": 1
},
"sessionId": "demo-session",
"productId": "thermostat",
"deviceId": "thermostat-001",
"provider": "aliyun"
}JSON 控制消息可使用 WebSocket 文本帧,也可放入类型 1 的二进制帧。音频使用类型 0 的二进制帧,格式由 audio_params 约定。二进制帧头固定为 4 字节:
| 字节 | 内容 |
|---|---|
0 | 类型:0 音频,1 UTF-8 JSON。 |
1 | 保留位,设为 0。 |
2..3 | payload 长度,big-endian uint16。 |
16 位 PCM payload 应保持偶数字节,单帧不超过 65,534 字节。客户端应为每轮生成 taskId,并在 listen、stop 和 abort 中使用同一值。
一次语音交互的消息顺序通常是:
| 方向 | 消息 | 说明 |
|---|---|---|
| 客户端 -> Device Agent | hello | 建立语音会话,提交音频参数、设备上下文和语音服务商。 |
| Device Agent -> 客户端 | hello | 返回会话 ID 和服务端语音输出参数。 |
| 客户端 -> Device Agent | listen | 发送 `mode: auto |
| 客户端 -> Device Agent | 音频二进制帧 | 发送语音数据。 |
| Device Agent -> 客户端 | asr | 返回 text、definite、utterances 和 taskId。 |
| 客户端 -> Device Agent | stop | 发送同一 taskId,可携带 visionRefs。 |
| Device Agent -> 客户端 | agent_reply | 返回 text 和 taskId;streaming: true 表示增量文本。 |
| Device Agent -> 客户端 | 语音合成音频二进制帧 | 播放语音回复。 |
| Device Agent -> 客户端 | tts_complete | 表示本轮完成或已中断,可带 taskId。 |
| 客户端 -> Device Agent | abort | 使用当前 taskId 中断本轮。 |
| 客户端 -> Device Agent | goodbye | 携带服务端 hello 返回的 session_id。 |
服务端 hello.audio_params 约定下行 TTS 格式,按其中的 sample_rate 播放 PCM。即使 listen.mode 为 auto,每轮录音结束也须发送 stop。
关闭码:1000 为正常关闭;1008 为绑定校验失败(userId 缺失、不一致或未绑定);1011 为语音服务或绑定查询不可用;1012 为配置已重载,可重新连接。