SSE 事件流

更新时间:
复制为 MD 格式

Qoder Cloud Agents 使用 Server-Sent Events (SSE) 协议将 Agent 的实时执行状态推送到客户端。你无需轮询——建立一次连接即可持续接收所有事件。

连接 URL

GET https://api.qoder.com.cn/api/v1/cloud/sessions/{session_id}/events/stream

请求头:

Authorization: Bearer $QODER_PAT
Accept: text/event-stream

SSE 格式

每个事件由三行组成,以空行分隔:

id: evt_01abc123
event: agent.message
data: {"content":[{"type":"text","text":"Hello, I'm ready to help."}]}

字段

说明

id

事件唯一标识,用于断线重连

event

事件类型,决定 data 的结构

data

JSON 格式的事件负载

完整事件类型

事件类型

含义

触发时机

data 结构

user.message

用户发送消息

客户端 POST 事件后

{"content": [{"type":"text","text":"..."}]}

user.interrupt

用户中断当前执行

客户端发送中断事件

{}

user.define_outcome

用户定义预期结果

客户端设定目标

{"outcome": "..."}

session.status_running

Session 开始执行

收到消息后开始处理

{"status": "running"}

span.model_request_start

模型请求开始

Agent 发起 LLM 调用

{"model": "...", "span_id": "..."}

agent.thinking

Agent 思考中

模型推理过程中

{"content": "..."}

agent.message

Agent 回复消息

模型产出文本

{"content": [{"type":"text","text":"..."}]}

agent.tool_use

Agent 发起的工具调用(Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch)

模型决定调用工具

见下文「工具调用对」

agent.tool_result

工具执行回执,通过 tool_use_id 与 tool_use 配对

工具执行完成后

见下文「工具调用对」

session.status_idle

Session 进入空闲

一轮对话完成

见下文「status_idle 完整 schema」

span.model_request_end

模型请求结束

LLM 调用完成

{"span_id": "...", "usage": {...}}

session.error

Session 出错

运行时异常

{"error": "...", "code": "..."}

terminated

Session 终止

Session 被关闭或超时

{"reason": "..."}

每个事件的 data JSON 除上表列出的特有字段外,还包含以下通用字段:idtypeturn_iduser.define_outcome 不含此字段)、session_idcreated_atprocessed_atschema_version

典型事件生命周期

一次完整的对话轮次,事件按以下顺序触发:

user.message                  ← 用户输入
session.status_running    ← 开始执行
span.model_request_start      ← 模型请求
agent.thinking                ← 推理中(可选)
agent.message                 ← 回复内容(可能多次 delta)
session.status_idle           ← 空闲,等待下一轮
span.model_request_end        ← 模型请求收尾

工具调用对(pair)

每个 agent.tool_use 必有同 turn_idagent.tool_result 配对,靠 tool_use_id 关联。

agent.tool_use 关键字段:

{
  "type": "agent.tool_use",
  "id": "evt_1d4c9b3c873b...",
  "name": "Bash",
  "input": { "command": "...", "description": "..." },
  "tool_use_id": "toolu_bdrk_01Kj...",
  "turn_id": "turn_...",
  "session_id": "sess_...",
  "requires_confirmation": false
}

此事件的 id 与其他事件一样使用 evt_ 前缀;tool_use_id 字段使用外部模型方格式 toolu_bdrk_<24>name 首字母大写。

agent.tool_result 关键字段:

{
  "type": "agent.tool_result",
  "id": "evt_<32hex>",
  "name": "Bash",
  "content": [{ "type": "text", "text": "..." }],
  "tool_use_id": "toolu_bdrk_01Kj...",
  "turn_id": "turn_...",
  "is_error": false
}

排序建议:按 created_at → 类型(tool_use 在 tool_result 前)→ id 三级排序。不要单靠 id 排序——平台生成的 tool_result id 可能字典序小于 tool_use id。

status_idle 完整 schema

session.status_idle 事件除 status 外还携带 stop_reasonusageturn_idsession_id

{
  "type": "session.status_idle",
  "id": "evt_840b8dc4b1534e52a2b858bf26b7de00",
  "status": "idle",
  "stop_reason": { "type": "end_turn" },
  "usage": {
    "input_tokens": 23487,
    "output_tokens": 1024,
    "cache_read_input_tokens": 11223,
    "cache_creation_input_tokens": 0
  },
  "turn_id": "turn_...",
  "session_id": "sess_..."
}

stop_reason.type 当前观察值为 end_turn;其他枚举值(cancel / max_turns / error 等)按实际场景出现。

断线重连与 after_id

SSE 连接建立后,默认从头重放所有历史事件。若只需增量事件,传入 after_id 参数:

GET /sessions/{session_id}/events/stream?after_id=evt_01abc123

浏览器原生 EventSource 会在断线重连时自动携带 Last-Event-ID 请求头,服务端据此返回增量事件。

长时间保持连接时建议客户端记录最后收到的 id,断线后用 after_id 恢复。

delta_flush_interval_ms

通过查询参数控制 agent.message delta 事件的刷新间隔:

GET /sessions/{session_id}/events/stream?delta_flush_interval_ms=100

默认值为 50ms。增大该值可减少事件频率、降低客户端渲染压力。

curl 示例

curl -N \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Accept: text/event-stream" \
  "https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream"

after_id 的增量连接:

curl -N \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Accept: text/event-stream" \
  "https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream?after_id=evt_01abc123"

JavaScript EventSource 示例

const url = new URL('https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream');
// 如需增量:url.searchParams.set('after_id', lastEventId);

const es = new EventSource(url, {
  headers: { 'Authorization': `Bearer ${QODER_PAT}` }
});

es.addEventListener('agent.message', (e) => {
  const data = JSON.parse(e.data);
  console.log('Agent:', data.content);
});

es.addEventListener('session.status_idle', () => {
  console.log('轮次完成,等待下一轮输入');
});

es.addEventListener('session.error', (e) => {
  const data = JSON.parse(e.data);
  console.error('错误:', data.error);
  es.close();
});

es.onerror = () => {
  console.warn('连接断开,浏览器将自动重连');
};

客户端实现建议

  1. 记录 last event id —— 每收到事件就更新,用于重连恢复。

  2. 处理 delta 拼接 —— agent.message 可能以流式 delta 到达,客户端需拼接为完整文本。

  3. 监听 terminated —— 收到后主动关闭连接,不再重连。

  4. 超时重试 —— 若 30 秒无事件,主动断开并重连(避免静默断连)。

  5. 过滤事件 —— 仅监听业务关心的事件类型,忽略 span 级别事件可减少处理开销。

常见问题

Q:连接后收到大量历史事件怎么办? A:传入 after_id 参数,指向最后处理过的事件 ID,服务端只返回该 ID 之后的事件。

Q:SSE 连接会自动关闭吗? A:Session 终止时服务端发送 terminated 事件后关闭连接。空闲超时由 Session 配置决定。

Q:能否用 WebSocket 替代? A:当前仅支持 SSE。SSE 对单向推送场景更轻量,且天然支持断线重连。

Q:delta_flush_interval_ms 设太大会丢事件吗? A:不会丢失,只是批量合并后发送。最终内容完整性不受影响。