docs: add backend sse interface baseline
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
交付物:[M0_ROUTE_MIGRATION_MATRIX_2026-04-20.md](./M0_ROUTE_MIGRATION_MATRIX_2026-04-20.md)
|
||||
- [x] 整理当前 12 个内部模块并锁定迁移归属
|
||||
交付物:[M0_MODULE_MIGRATION_BASELINE_2026-04-20.md](./M0_MODULE_MIGRATION_BASELINE_2026-04-20.md)
|
||||
- [ ] 整理当前所有 SSE 接口与事件格式
|
||||
- [x] 整理当前所有 SSE 接口与事件格式
|
||||
交付物:[M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md)
|
||||
- [ ] 整理当前所有 `/generated-*` 静态资源前缀
|
||||
- [ ] 整理当前前端直接依赖的响应头、envelope、错误格式
|
||||
|
||||
|
||||
300
backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md
Normal file
300
backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# M0:SSE 接口与事件格式冻结基线
|
||||
|
||||
日期:`2026-04-20`
|
||||
|
||||
依据来源:
|
||||
|
||||
- [../docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md](../docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md)
|
||||
- [../server-node/manifests/backend-capability-index.json](../server-node/manifests/backend-capability-index.json)
|
||||
- `server-node/src/http.ts`
|
||||
- `server-node/src/routes/runtimeRoutes.ts`
|
||||
- `server-node/src/routes/customWorldAgent.ts`
|
||||
- `server-node/src/modules/ai/chatOrchestrator.ts`
|
||||
- `server-node/src/services/customWorldAgentOrchestrator.ts`
|
||||
- `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于完成 `M0` 的第四条任务:
|
||||
|
||||
- 整理当前所有 SSE 接口与事件格式
|
||||
|
||||
这里的“整理”不是只记住有几条 `stream` 路由,而是要求后续 Axum 重写必须先冻结:
|
||||
|
||||
1. 当前到底有哪几条 SSE 路由。
|
||||
2. 每条路由是“透传上游流”还是“项目自定义事件流”。
|
||||
3. 每条路由的事件名、结束标记、错误帧和头部约束是什么。
|
||||
4. 哪些流的 `payload` 是增量文本,哪些其实是“累计文本”。
|
||||
|
||||
## 2. 冻结结论
|
||||
|
||||
当前 Node 后端正式登记的 SSE 接口固定为以下 `6` 条:
|
||||
|
||||
| 路由 ID | 方法/路径 | 当前实现入口 | 协议类型 | 成功结束标记 | 鉴权 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `runtime.characterReplyStream` | `POST /api/runtime/chat/character/reply/stream` | `runtimeRoutes.ts -> streamCharacterChatReplyFromOrchestrator` | 上游透传 SSE | 上游 `data: [DONE]` | JWT |
|
||||
| `runtime.npcDialogueStream` | `POST /api/runtime/chat/npc/dialogue/stream` | `runtimeRoutes.ts -> streamNpcChatDialogueFromOrchestrator` | 上游透传 SSE | 上游 `data: [DONE]` | JWT |
|
||||
| `runtime.npcRecruitStream` | `POST /api/runtime/chat/npc/recruit/stream` | `runtimeRoutes.ts -> streamNpcRecruitDialogueFromOrchestrator` | 上游透传 SSE | 上游 `data: [DONE]` | JWT |
|
||||
| `runtime.npcTurnStream` | `POST /api/runtime/chat/npc/turn/stream` | `runtimeRoutes.ts -> streamNpcChatTurnFromOrchestrator` | 项目自定义 SSE | `event: complete` 后追加 `data: [DONE]` | JWT |
|
||||
| `runtime.customWorldSessionGenerateStream` | `GET /api/runtime/custom-world/sessions/:sessionId/generate/stream` | `runtimeRoutes.ts` 内联实现 | 项目自定义 SSE | `event: done`,无 `[DONE]` | JWT |
|
||||
| `runtime.customWorldAgentStreamMessage` | `POST /api/runtime/custom-world/agent/sessions/:sessionId/messages/stream` | `customWorldAgent.ts -> customWorldAgentOrchestrator.streamMessage` | 项目自定义 SSE | `event: done`,无 `[DONE]` | JWT |
|
||||
|
||||
冻结总数:
|
||||
|
||||
1. SSE 接口:`6`
|
||||
2. 上游透传型:`3`
|
||||
3. 本地自定义事件流:`3`
|
||||
|
||||
## 3. 全部 SSE 接口共享的响应头约束
|
||||
|
||||
当前所有项目内主动准备 SSE 响应的接口,都经过 `prepareEventStreamResponse(...)`,因此至少冻结以下头部行为:
|
||||
|
||||
| 响应头 | 当前值/规则 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `Content-Type` | 默认 `text/event-stream; charset=utf-8` | 透传型接口可被上游 `content-type` 覆盖,但仍保持 SSE。 |
|
||||
| `Cache-Control` | `no-cache` | 禁止中间层缓存流式结果。 |
|
||||
| `Connection` | `keep-alive` | 保持 SSE 长连接。 |
|
||||
| `X-Accel-Buffering` | `no` | 禁止代理层缓冲。 |
|
||||
| `x-request-id` | 透传当前请求 ID | 所有 SSE 都要带请求追踪头。 |
|
||||
| `x-api-version` | 当前 API 版本号 | 与普通 JSON 接口一致。 |
|
||||
| `x-route-version` | 当前路由版本号 | 与普通 JSON 接口一致。 |
|
||||
| `x-response-time-ms` | 当前已耗时毫秒数 | 在准备响应头时写入。 |
|
||||
|
||||
额外冻结约束:
|
||||
|
||||
1. `SSE` 接口当前也保留普通 API 元数据头,不能因为换成 Axum 就丢掉。
|
||||
2. 这 `6` 条流式接口都在 `requireAuth` 之后注册,因此第一阶段默认仍需要 `Bearer JWT`。
|
||||
|
||||
## 4. 协议分型
|
||||
|
||||
### 4.1 上游透传型 SSE(3 条)
|
||||
|
||||
包含:
|
||||
|
||||
1. `POST /api/runtime/chat/character/reply/stream`
|
||||
2. `POST /api/runtime/chat/npc/dialogue/stream`
|
||||
3. `POST /api/runtime/chat/npc/recruit/stream`
|
||||
|
||||
当前实现特征:
|
||||
|
||||
1. 路由不自己重写事件名,直接把上游模型返回的 SSE 原样管道转发给前端。
|
||||
2. 本地只负责:
|
||||
- 发起上游流式请求
|
||||
- 准备 SSE 头部
|
||||
- 处理中断时的请求 abort
|
||||
3. 从 `llmClient.streamMessageContent(...)` 的解析逻辑可以反推,当前上游 SSE 采用 OpenAI 风格:
|
||||
- 多个 `data: {...}` JSON chunk
|
||||
- 最终 `data: [DONE]`
|
||||
|
||||
冻结要求:
|
||||
|
||||
1. 第一阶段 Axum 仍要保持这三条接口的“上游透传”语义。
|
||||
2. 不要在未发版变更协议前,擅自把它们改成项目自定义 `event: reply_delta` 格式。
|
||||
|
||||
### 4.2 项目自定义 SSE(3 条)
|
||||
|
||||
包含:
|
||||
|
||||
1. `POST /api/runtime/chat/npc/turn/stream`
|
||||
2. `GET /api/runtime/custom-world/sessions/:sessionId/generate/stream`
|
||||
3. `POST /api/runtime/custom-world/agent/sessions/:sessionId/messages/stream`
|
||||
|
||||
当前实现特征:
|
||||
|
||||
1. 路由或 orchestrator 自己写 `event:` 与 `data:`。
|
||||
2. 事件名不是上游协议,而是项目本地约定。
|
||||
3. 这三条流的结束方式并不一致,必须分别兼容。
|
||||
|
||||
## 5. 各接口事件格式冻结
|
||||
|
||||
### 5.1 `runtime.characterReplyStream`
|
||||
|
||||
路径:
|
||||
|
||||
- `POST /api/runtime/chat/character/reply/stream`
|
||||
|
||||
冻结格式:
|
||||
|
||||
1. 当前为上游透传流。
|
||||
2. 本地不保证固定 `event` 名。
|
||||
3. 前端实际收到的是上游 `data: {...}` chunk 与最终 `data: [DONE]`。
|
||||
4. 失败时当前实现也不是本地 `event: error`,而是由上游失败或 Express 错误链决定。
|
||||
|
||||
### 5.2 `runtime.npcDialogueStream`
|
||||
|
||||
路径:
|
||||
|
||||
- `POST /api/runtime/chat/npc/dialogue/stream`
|
||||
|
||||
冻结格式:
|
||||
|
||||
1. 当前为上游透传流。
|
||||
2. 协议特征与 `runtime.characterReplyStream` 相同。
|
||||
3. 第一阶段不能私自改成项目自定义事件名。
|
||||
|
||||
### 5.3 `runtime.npcRecruitStream`
|
||||
|
||||
路径:
|
||||
|
||||
- `POST /api/runtime/chat/npc/recruit/stream`
|
||||
|
||||
冻结格式:
|
||||
|
||||
1. 当前为上游透传流。
|
||||
2. 协议特征与前两条透传 SSE 相同。
|
||||
3. 结束标记仍依赖上游 `data: [DONE]`。
|
||||
|
||||
### 5.4 `runtime.npcTurnStream`
|
||||
|
||||
路径:
|
||||
|
||||
- `POST /api/runtime/chat/npc/turn/stream`
|
||||
|
||||
成功事件序列:
|
||||
|
||||
1. `event: reply_delta`
|
||||
2. `event: reply_delta`
|
||||
3. `...`
|
||||
4. `event: complete`
|
||||
5. `data: [DONE]`
|
||||
|
||||
错误事件:
|
||||
|
||||
1. `event: error`
|
||||
2. `data: {"message":"..."}`
|
||||
3. 之后直接 `response.end()`,不会再补 `complete`
|
||||
|
||||
冻结 payload 规则:
|
||||
|
||||
| 事件名 | payload 结构 | 关键说明 |
|
||||
| --- | --- | --- |
|
||||
| `reply_delta` | `{ "text": string }` | `text` 实际是“累计文本”,不是单 token 增量。 |
|
||||
| `complete` | `{ "npcReply": string, "affinityDelta": number, "affinityText": string, "suggestions": string[], "pendingQuestOffer": object \| null, "chatDirective": object \| null }` | 最终一次性返回业务结算数据。 |
|
||||
| `error` | `{ "message": string }` | 仅错误消息,无额外状态。 |
|
||||
|
||||
补充冻结点:
|
||||
|
||||
1. `reply_delta.text` 每次都是当前累计回复全文。
|
||||
2. `complete.suggestions` 在强制收束场景下可能是空数组。
|
||||
3. `complete.chatDirective` 当前至少可能包含:
|
||||
- `turnLimit`
|
||||
- `remainingTurns`
|
||||
- `forceExit`
|
||||
- `closingMode`
|
||||
4. `complete.pendingQuestOffer` 当前可能包含:
|
||||
- `quest`
|
||||
- `introText`
|
||||
|
||||
### 5.5 `runtime.customWorldSessionGenerateStream`
|
||||
|
||||
路径:
|
||||
|
||||
- `GET /api/runtime/custom-world/sessions/:sessionId/generate/stream`
|
||||
|
||||
成功事件序列:
|
||||
|
||||
1. `event: progress`,payload:`{ "phase": "preparing", "progress": 10 }`
|
||||
2. `event: progress`,payload:`{ "phase": "requesting_llm", "progress": 45 }`
|
||||
3. `event: progress`,payload:`CustomWorldGenerationProgress`
|
||||
4. `...`
|
||||
5. `event: progress`,payload:`{ "phase": "completed", "progress": 100 }`
|
||||
6. `event: result`
|
||||
7. `event: done`
|
||||
|
||||
错误事件:
|
||||
|
||||
1. `event: error`
|
||||
2. `data: {"message":"..."}`
|
||||
3. 之后直接结束,不会再发 `done`
|
||||
|
||||
冻结 payload 规则:
|
||||
|
||||
| 事件名 | payload 结构 | 关键说明 |
|
||||
| --- | --- | --- |
|
||||
| `progress` | 兼容两种结构 | 这是当前最容易踩坑的混合协议。 |
|
||||
| `result` | `{ "profile": object }` | 返回完整世界 profile。 |
|
||||
| `done` | `{ "ok": true }` | 当前没有 `[DONE]` 字符串终止帧。 |
|
||||
| `error` | `{ "message": string }` | 当前也没有额外错误码。 |
|
||||
|
||||
`progress` 事件的两种冻结结构:
|
||||
|
||||
1. 启动/收尾帧:
|
||||
- `{ "phase": "preparing", "progress": 10 }`
|
||||
- `{ "phase": "requesting_llm", "progress": 45 }`
|
||||
- `{ "phase": "completed", "progress": 100 }`
|
||||
2. 编排器进度帧 `CustomWorldGenerationProgress`:
|
||||
- `phaseId`
|
||||
- `phaseLabel`
|
||||
- `phaseDetail`
|
||||
- `overallProgress`
|
||||
- `completedWeight`
|
||||
- `totalWeight`
|
||||
- `elapsedMs`
|
||||
- `estimatedRemainingMs`
|
||||
- `activeStepIndex`
|
||||
- `steps`
|
||||
|
||||
补充冻结点:
|
||||
|
||||
1. 当前 `progress` 不是单一 schema,而是混合 schema。
|
||||
2. 当前实现会在客户端断开时触发 `AbortController`,这条流具备显式中断处理。
|
||||
|
||||
### 5.6 `runtime.customWorldAgentStreamMessage`
|
||||
|
||||
路径:
|
||||
|
||||
- `POST /api/runtime/custom-world/agent/sessions/:sessionId/messages/stream`
|
||||
|
||||
成功事件序列:
|
||||
|
||||
1. `event: reply_delta`
|
||||
2. `event: reply_delta`
|
||||
3. `...`
|
||||
4. `event: session`
|
||||
5. `event: done`
|
||||
|
||||
错误事件:
|
||||
|
||||
1. `event: error`
|
||||
2. `data: {"message":"..."}`
|
||||
3. 之后直接结束,不会再补 `done`
|
||||
|
||||
冻结 payload 规则:
|
||||
|
||||
| 事件名 | payload 结构 | 关键说明 |
|
||||
| --- | --- | --- |
|
||||
| `reply_delta` | `{ "text": string }` | 当前也是累计文本,不是 diff patch。 |
|
||||
| `session` | `{ "session": CustomWorldAgentSessionSnapshot }` | 完整会话快照一次性回推。 |
|
||||
| `done` | `{ "ok": true }` | 当前没有 `[DONE]`。 |
|
||||
| `error` | `{ "message": string }` | 仅错误消息。 |
|
||||
|
||||
补充冻结点:
|
||||
|
||||
1. 这条流当前不会在成功结尾补发最终文本帧,只会发 `session` 快照。
|
||||
2. `reply_delta.text` 同样是“到当前为止的完整回复”。
|
||||
3. 当前实现没有像 `customWorldSessionGenerateStream` 那样显式挂请求断开 abort。
|
||||
|
||||
## 6. 第一阶段 Axum 重写必须兼容的硬约束
|
||||
|
||||
后续重写中,不允许出现以下情况:
|
||||
|
||||
1. 把当前 `6` 条 SSE 路由减少、合并或改掉方法类型。
|
||||
2. 把透传型 `3` 条流直接改写成自定义事件名,而前端却不知情。
|
||||
3. 把 `npcTurnStream` 的 `reply_delta` 从“累计文本”改成“真正 delta”,导致前端拼接方式失效。
|
||||
4. 把 `customWorldSessionGenerateStream` 的混合 `progress` schema 静默改成新格式,却没有版本门禁。
|
||||
5. 把 `customWorldAgentStreamMessage` 的 `session` 终帧改成局部 patch,而前端仍按完整快照消费。
|
||||
6. 丢失 `x-request-id`、`x-api-version`、`x-route-version`、`x-response-time-ms` 等当前前端与联调用到的头。
|
||||
|
||||
## 7. 本任务完成定义
|
||||
|
||||
当以下条件成立时,这条任务视为完成:
|
||||
|
||||
1. 当前 `6` 条 SSE 接口已经有书面冻结清单。
|
||||
2. 每条 SSE 都已明确:
|
||||
- 方法与路径
|
||||
- 协议类型
|
||||
- 事件名
|
||||
- 成功结束标记
|
||||
- 错误事件
|
||||
- 关键 payload 结构
|
||||
3. 后续 Axum SSE 落地、前端 contract 回归、SpacetimeDB 实时链路设计时,可以直接引用本文,不再靠人工回忆事件名。
|
||||
@@ -17,6 +17,7 @@
|
||||
- [M0_CAPABILITY_SURFACE_BASELINE_2026-04-20.md](./M0_CAPABILITY_SURFACE_BASELINE_2026-04-20.md):当前 Node 后端 `6` 个挂载面的冻结基线,用于后续接口映射、模块迁移与验收对照。
|
||||
- [M0_ROUTE_MIGRATION_MATRIX_2026-04-20.md](./M0_ROUTE_MIGRATION_MATRIX_2026-04-20.md):当前 `96` 条后端路由的“旧接口 -> 新实现”迁移矩阵,用于 Axum 路由树和 application service 落位。
|
||||
- [M0_MODULE_MIGRATION_BASELINE_2026-04-20.md](./M0_MODULE_MIGRATION_BASELINE_2026-04-20.md):当前 `12` 个内部模块的迁移归属基线,用于锁定 Rust crate、SpacetimeDB bounded context 与 Axum/application 分工。
|
||||
- [M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md):当前 `6` 条 SSE 接口及其事件格式冻结基线,用于 Axum SSE 兼容和前端 contract 回归。
|
||||
|
||||
## 维护规则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user