From 0e454b889bca07c836147c36d087a6df29399e04 Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 21 Apr 2026 00:08:41 +0800 Subject: [PATCH] docs: add backend sse interface baseline --- .../01_M0_M2_FOUNDATION_AND_AUTH.md | 3 +- .../M0_SSE_INTERFACE_BASELINE_2026-04-20.md | 300 ++++++++++++++++++ backend-rewrite-tasklist/README.md | 1 + 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md diff --git a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md index 35865c61..01ed1d54 100644 --- a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md +++ b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md @@ -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、错误格式 diff --git a/backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md b/backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md new file mode 100644 index 00000000..fc4a252a --- /dev/null +++ b/backend-rewrite-tasklist/M0_SSE_INTERFACE_BASELINE_2026-04-20.md @@ -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 实时链路设计时,可以直接引用本文,不再靠人工回忆事件名。 diff --git a/backend-rewrite-tasklist/README.md b/backend-rewrite-tasklist/README.md index 61cd66c9..da855a86 100644 --- a/backend-rewrite-tasklist/README.md +++ b/backend-rewrite-tasklist/README.md @@ -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 回归。 ## 维护规则