路径
GET /stream/:taskId
公网入口 https://agentapi.kira.art/stream/:taskId。响应 Content-Type: text/event-stream。
认证
eitherAuth():Authorization: Bearer <Supabase JWT> 或 X-Internal-Key: <INTERNAL_KEY>。校验 task.userId === jwtPayload.sub,不匹配返 403。
请求
| 来源 | 字段 | 必填 | 说明 |
|---|---|---|---|
| 路径参数 | taskId | 是 | 由 POST /task 返回 |
| Header | Last-Event-ID | 否 | 续读游标。重连时带上最后收到的 id,服务端从该 Redis stream entry 之后重放 |
续读用 SSE 协议标准的
Last-Event-ID header,而不是 query string。浏览器 EventSource 自动带;用 fetch 手动解析时需自行携带。帧格式
每条 chunk batch 与终止哨兵都是一条 Redis stream entry,转成 SSE 帧(writeEntry,src/hono/agent/index.ts:402):
- 普通帧无
event:字段,data是 chunk JSON 数组本身(AI SDK UI chunks),前端JSON.parse(data)后直接 iterate。 event: done帧data为空字符串。event: error帧data是错误字符串(非 JSON 对象)。- 每帧都带
id(Redis entry ID),即续读游标。 - 服务端每次 BLOCK 超时会写一条 SSE comment 心跳
: keepalive,客户端忽略,防 CF / fly-proxy / Bun 任意层 idle 断连。 - 若 task HASH 已被 GC(TTL 过期)且无新帧,服务端发
event: error+data: {"message":"stream expired"}并关闭。
event: done 或 event: error 即关闭连接。
续读语义
Block 等新帧
blockingReadChunksAfter(XREAD BLOCK STREAMS agent:stream:{taskId} <cursor>)循环等待新 chunk,逐帧写出并推进 cursor。状态码
| 码 | 含义 |
|---|---|
200 | 建立 SSE 流(text/event-stream) |
401 | 缺少有效 JWT / jwtPayload.sub |
403 | task 属于其他用户 |
404 | task 不存在(从未创建或 TTL 已过期) |
示例
源
src/hono/agent/index.ts:338(handler)、writeEntry at :402、src/lib/agentTask.ts(readChunksFrom / blockingReadChunksAfter)