订阅事件
agent.read
agent.read 授权;客户端必须忽略未知事件及新增字段。
SSE 帧
每个业务事件帧由id、event 和 JSON data 组成:
id只在本次连接内从 1 单调递增。- 心跳使用 SSE 注释帧,不包含业务数据,也没有
id。 - 服务端不支持
Last-Event-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为准。
积分结算
每次 Agent 计量 END 成功并返回有效积分时,事件流会发送一条run.usage:
credits_used是这一次结算的积分,不是整个 Run 的累计值;它使用非负十进制整数字符串,"0"是有效值。- 同一个 Run 在阻塞后恢复或发生 Session 轮换时可能有多次结算,因此可能收到多条
run.usage。 - 重连时缓存窗口内的结算事件可能重放。
usage_id对同一次结算保持稳定,客户端必须先按它去重,再对credits_used求和。 - 计量 END 失败或没有返回有效积分时不会伪造
run.usage。 未收到run.usage不代表零消耗;事件用于本次运行的结算展示,不应把缺失事件伪装为零值。 - 最终一条
run.usage(若有)会先于run.completed、run.failed、run.canceled或run.interrupted;这些正常终态是该连接最后一个业务事件。
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 公开状态的权威全量快照。实时文本更新产生的瞬态 message.snapshot 可能省略该字段:字段存在时整体替换本地工具列表,字段缺省时保留已有工具列表。
- 快照只包含当前轮,即最近一条公开用户消息之后的工具调用,并按调用顺序排列。
- Read 仍然只有 basename 文件名;Write/Edit 使用与实时事件完全相同的白名单正文;Skill 名称仍由服务端目录解析。
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,同时整体替换当前轮工具状态,缺省则保留已有工具状态。 - 对重放的
run.usage按usage_id去重,再累计字符串形式的credits_used,不要重复计费展示。 - 收到终态后停止重连。
429 时,Retry-After: 60 是固定窗口的安全等待上界,不是剩余 TTL;等待后增加少量随机抖动再重连。
页面刷新且不知道当前 Run 时,先调用 GET .../agent/runs/current,再决定是否订阅,详见 会话与恢复。
run.input_required
可回复的交互事件包含 action:
如果事件没有
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 错误处理
Action 回复没有通用
Idempotency-Key。请求结果不确定时,先恢复 Run 状态;不要用新的 start 请求模拟 Action 回复。
