Agent Run 通过 Server-Sent Events(SSE)输出公开进度、回复文本、工具输入、积分结算、预览地址和交互请求。
所需权限:
Token 必须放在 Authorization Header 中;不要放入 URL Query。
Write/Edit 正文随事件流默认发送,不需要额外 Query 参数或 Scope。Read 文件名、Skill 名称及工具正文均使用普通
每个业务事件帧由
OpenAPI 的 Bash 工具调用仍使用
Bash 在参数生成完成后首次发送
以上产物 URL 仅为结构示例,实际使用事件返回的完整 HTTPS URL。素材调用成功且有可公开产物时,完成事件可携带
Agent 主循环结算,或 Run 内图片、视频、语音、音乐的独立计量成功并返回有效积分时,事件流会发送一条
默认会收到两类工具输入事件。
服务端从持久化消息生成的
客户端维护每个 Run 的单一文本缓冲区:
只有内部 dev server 状态精确变为
网络断开时:
可回复的交互事件包含
如果事件没有
所需权限:
只有
成功响应是恢复后的 Run 对象。继续消费原 Run 的 SSE;如果原连接已关闭,则重新订阅该
Action 回复没有通用
订阅事件
agent.read
agent.read 授权;客户端必须忽略未知事件及新增字段。
SSE 帧
每个业务事件帧由 id、event 和 JSON data 组成:
id只在本次连接内从 1 单调递增。- 心跳使用 SSE 注释帧,不包含业务数据,也没有
id。 - 服务端不支持
Last-Event-ID断点续传。 - 首个终态事件之后,服务端关闭连接。
事件语义
| 事件 | 客户端处理 |
|---|---|
run.working | 标记 Run 正在处理;只包含 run_id 和 status |
tool.call | 按 tool_call_id 展示工具 started / completed 进度 |
tool.input.delta | 按 tool_call_id 和 field 追加 Write/Edit 白名单字段的文本增量 |
tool.input.snapshot | 整体替换同一工具调用当前公开输入的全量状态 |
message.delta | 把 delta 追加到当前回复末尾 |
message.snapshot | 用 content 整段替换当前回复;存在 tool_calls 时同时整体替换当前轮公开工具状态 |
preview.ready | 使用短时 Preview Link 打开当前可用预览 |
run.input_required | 展示 Action,收集输入后调用回复接口 |
run.usage | 按 usage_id 去重后,将本次结算的 credits_used 累加到 Run 总消耗 |
run.completed | 正常终态 |
run.failed | 失败终态 |
run.canceled | 取消终态 |
run.interrupted | 中断终态 |
run.superseded | 当前 Run 被取代;保留旧 Run 的真实终态,并切换到可选 current_run_id |
tool.call 的已知 phase 为 analyzing、building、validating、coordinating、finalizing。未知 phase 仍按进行中展示。
tool.call 的基础字段包含安全的工具名、状态、阶段和展示文案,不包含原始参数、目录路径或原始执行结果。支持以下结构化扩展:
- Read 可携带
file_name,只含安全 basename,不含目录、读取内容、offset、limit 或 URL 查询信息。 - Skill 可携带
skill,其中skill_name为服务端解析的 canonical 名称,display_name为可选展示名;展示时使用display_name ?? skill_name。不公开技能正文、配置、凭证或加载结果。 status: "completed"可携带outcome(succeeded、failed、canceled)。completed仅表示调用结束,实际结果以outcome为准。
Bash 类型与素材生成
OpenAPI 的 Bash 工具调用仍使用 tool_name: "Bash",通过可选的 sub_type 区分普通命令与素材生成:
| sub_type | 含义 | 成功时可返回的 result |
|---|---|---|
shell | 普通 Shell 命令 | 不公开原始命令输出 |
image_generate | 生成图片 | {type: "image", images: [HTTPS URL]} |
video_generate | 生成视频 | {type: "video", video: HTTPS URL} |
speech_generate | 生成语音 | {type: "audio", audio: HTTPS URL} |
music_generate | 生成音乐 | {type: "audio", audio: HTTPS URL} |
status: "started",此时才分类 sub_type;同一次调用的 completed 保留同一分类。started 不表示素材已经生成完成,completed 也只表示调用结束,实际结果以 outcome 为准。
sub_type 为可选字段。客户端遇到缺省或未知值时,应按通用工具进度展示,不应报错或推断生成成功。不要把 sub_type 当作新的工具名,也不要根据展示文案或命令关键词自行分类。
例如,一次生图调用可以产生以下两个事件:
result;失败、取消、没有可公开 URL 或达到投影限制时可能省略。图片最多返回 64 张,过滤或截断时可能带 result.truncated: true。不公开提示词、原始命令、普通 Bash 输出或内部返回体;产物 URL 可能包含签名,不要改写其参数或写入公开日志。
积分结算
Agent 主循环结算,或 Run 内图片、视频、语音、音乐的独立计量成功并返回有效积分时,事件流会发送一条 run.usage:
credits_used是这一次结算的积分,不是整个 Run 的累计值;它使用非负十进制整数字符串,"0"是有效值。- 同一个 Run 在阻塞后恢复、发生 Session 轮换或生成素材时可能有多次结算,因此可能收到多条
run.usage。素材积分可以在 Run 仍在执行时到达,不必等到整个 Run 结束。 run.usage当前不携带tool_call_id、sub_type或素材类型。不要按到达顺序把某笔积分直接绑定到某个工具调用;本事件用于累计 Run 消耗。- 重连时缓存窗口内的结算事件可能重放。
usage_id对同一次结算保持稳定,客户端必须先按它去重,再对credits_used求和。 - 计量结算失败或没有返回有效积分时不会伪造
run.usage。 未收到run.usage不代表零消耗;事件用于本次运行的结算展示,不应把缺失事件伪装为零值。 - 最终一条
run.usage(若有)会先于run.completed、run.failed、run.canceled或run.interrupted;这些正常终态是该连接最后一个业务事件。
200,再收到视频结算 1050,最后收到主 Agent 结算 23,去重后合计 1273。这些数字仅示意多笔累计,不代表固定价格或必须按此顺序到达;写代码不会保证产生一条独立的工具级积分事件。
结算重放受缓存和 Run 状态限制,不是长期账单查询能力。Run 结束较久后重新订阅,可能只返回终态;接入方应按 Run 保存已处理的 usage_id 与累计值,不能依赖历史消息或重新订阅补齐全部历史积分。
一个简单的累计器可以写成:
Write/Edit 输入流
默认会收到两类工具输入事件。tool.input.delta 使用追加语义:Write 只产生 field: "content";Edit 只产生 field: "old_string" 或 field: "new_string"。
tool.input.snapshot 是该 tool_call_id 的权威全量状态,必须替换本地工具输入,而不是继续追加:
- Write 的
input只使用content;Edit 使用old_string、new_string,最终快照还可能带replace_all。 - Edit 删除内容时
new_string可以是空字符串,且可能没有对应 delta;以最终 snapshot 为准。 file_name只含 basename,可能在无法安全解析时省略;同名文件仍必须用tool_call_id区分。complete: false表示参数仍在生成;complete: true只表示参数生成完成,不表示工具执行成功,随后应读取tool.call.outcome。truncated: true表示公开投影达到大小或并发限制;Agent 内部仍使用完整参数执行。- 不保证
file_name先于内容 delta 到达;客户端必须容忍字段顺序和事件交错。
tool_call_id 分别维护状态:delta 追加到对应 field,snapshot 整体替换 file_name、input、complete 和 truncated。断线重连时缓存窗口内可能先收到 snapshot,用它覆盖重连前的本地累计值。
最终与重连工具快照
服务端从持久化消息生成的 message.snapshot 会附带 tool_calls,它是当前轮 Read、Write、Edit、Skill 以及图片、视频、语音、音乐素材 Bash 调用公开状态的权威全量快照。普通 Shell 命令不进入 tool_calls。实时文本更新产生的瞬态 message.snapshot 可能省略该字段:字段存在时整体替换本地工具列表,字段缺省时保留已有工具列表。
- 快照只包含当前轮,即最近一条公开用户消息之后的工具调用,并按调用顺序排列。
- Read 仍然只有 basename 文件名;Write/Edit 使用与实时事件完全相同的白名单正文;Skill 名称仍由服务端目录解析。
- 素材 Bash 快照保留
sub_type、状态及可公开的result,用于恢复图片、视频、语音或音乐结果卡;result的可选性与实时完成事件一致。 status: "completed"表示找到了配对的持久化工具结果,outcome是安全归一化状态。原始工具结果不会进入快照。- 最多返回 64 个工具调用,Write/Edit 正文继续受实时投影相同的字段和 Run 总大小限制;达到限制时可能带
truncated: true。 - 如果消息存储暂不可用,服务端退化为实时事件或纯文本快照,不会阻塞 Run 终态。
文本 reducer
客户端维护每个 Run 的单一文本缓冲区:
message.snapshot 可能在首次连接、重连和终态前出现。content 始终是当前完整文本;tool_calls 存在时是当前轮完整公开工具状态。两者使用替换语义;缺省的 tool_calls 不应清空已有工具列表。
预览事件
running,并且短时 Preview Link 签发成功时,服务端才发送 preview.ready。starting、无效内部地址或签发失败都不发送 preview.ready,也绝不会向 Open API 客户端暴露 raw Sandbox URL。
url 是调用方应打开的短时 opaque Preview Link,不要依赖 host 或 path 形状;expires_at 是必填的 Unix 毫秒过期时间。
链接过期或需要续期时,第三方后端使用受保护的 POST /projects/{project_id}/agent/preview-links 重新签发;恢复仍在进行时接口返回 202,按 Retry-After 重试。不要等待 SSE 一定再次产生预览事件,也不要把 opaque Ticket 写入日志或长期存储。公开的 GET /open/preview/{preview_ticket} 只是 HTML 壳页面,不接受 API 凭证,也不会恢复 Sandbox。
断线重连
网络断开时:
- 保留当前
run_id、已展示文本和 UI 状态。 - 使用有限次数、带抖动的指数退避重新 GET 同一事件端点。
- 不发送
Last-Event-ID,也不使用重复 start 代替重连。 - 收到
message.snapshot后整段替换已有文本;若存在tool_calls,同时整体替换当前轮工具状态及素材结果卡,缺省则保留已有工具状态。素材调用以tool_call_id区分,使用sub_type选择展示类型。 - 对重放的
run.usage按usage_id去重,再累计字符串形式的credits_used,不要重复计费展示。 - 收到终态后停止重连。
429 时,Retry-After: 60 是固定窗口的安全等待上界,不是剩余 TTL;等待后增加少量随机抖动再重连。
页面刷新且不知道当前 Run 时,先调用 GET .../agent/runs/current,再决定是否订阅,详见 会话与恢复。
run.input_required
可回复的交互事件包含 action:
| 字段 | 说明 |
|---|---|
action_id | 短期签名 Token;原样回传,不得解析或修改 |
kind | answers 或 confirmation |
prompt | 向用户展示的问题或确认说明 |
input_schema | response 必须满足的 JSON Schema |
expires_at | Action 过期时间,Unix 毫秒 |
action,而是 detail: "unsupported_action",表示当前阻塞工具不支持通过 OpenAPI 回复。客户端应停止自动提交,并引导用户到 Meoo Web 处理。
只要阻塞工具仍有效,每次建连或重连都会签发新的 action_id 和 expires_at;长连接存续时,服务端会在当前 Token 半 TTL 时续签。客户端必须用最新事件替换旧 Token。
回复 Action
agent.run
action_id 与项目和 Run 绑定,且只能在有效期和正确状态下使用。
问答回复
kind=answers 时,answers 包含 1~4 项;键长 1~1000,值为 1~10000 字符的非空字符串。服务端会先 trim 每个 key 和 value,再校验并传给 Agent;trim 后为空或产生重复 key 会返回 400。
确认回复
kind=confirmation 时,decision 为 approve 或 reject:
action.input_schema 声明允许 comment 时才能附带非空 comment:
run_id。
Action 错误处理
| HTTP | 场景 | 处理 |
|---|---|---|
400 | Token 或 response 形状不合法 | 按 input_schema 修正输入 |
404 | Action 与项目不匹配,或资源不可访问 | 不枚举项目/Run,刷新当前状态 |
409 action_expired | Action Token 已过期 | 重新订阅 Run,等待新的 Action Token |
409 action_already_responded | Action 已回复 | 恢复 Run 状态,不重复提交 |
409 run_conflict | Run generation 或状态已变化 | 查询 current 或重新订阅 |
Idempotency-Key。请求结果不确定时,先恢复 Run 状态;不要用新的 start 请求模拟 Action 回复。
