199 lines
9.5 KiB
Markdown
199 lines
9.5 KiB
Markdown
# csc serve 事件标准化 — 实施任务清单
|
||
|
||
> 关联文档:`docs/serve/serve-event-standardization-proposal.md`
|
||
> 原则:**不做双写**,csc serve 直接输出 canonical 格式,旧 cs-cloud passthrough 兼容
|
||
|
||
---
|
||
|
||
## Phase 1:sessionMessageRouter 路由增强
|
||
|
||
### 1.1 新建 StreamStateTracker
|
||
|
||
- [x] 创建 `src/server/streamStateTracker.ts`
|
||
- [x] 实现 `SessionStreamState` 状态结构(messageID、activeBlocks、usage、stopReason)
|
||
- [x] 实现 `processStreamEvent()` — Anthropic stream_event → canonical 事件转换
|
||
- [x] `message_start` → `message.updated` + `step-start` part
|
||
- [x] `content_block_start` (text) → `message.part.updated { type: "text" }`
|
||
- [x] `content_block_start` (thinking) → `message.part.updated { type: "reasoning" }`
|
||
- [x] `content_block_start` (redacted_thinking) → `message.part.updated { type: "reasoning", redacted: true }`
|
||
- [x] `content_block_start` (tool_use) → `message.part.updated { type: "tool", status: "pending" }`
|
||
- [x] `content_block_delta` (text_delta) → `message.part.delta { field: "text" }`
|
||
- [x] `content_block_delta` (thinking_delta) → `message.part.delta { field: "text" }`
|
||
- [x] `content_block_delta` (input_json_delta) → `message.part.delta { field: "input" }`
|
||
- [x] `content_block_stop` (tool_use) → `message.part.updated { status: "running" }`
|
||
- [x] `content_block_stop` (text/thinking) → finalize timing
|
||
- [x] `message_delta` → 提取 usage.outputTokens + stopReason(不输出事件)
|
||
- [x] `message_stop` → `message.part.updated { type: "step-finish" }` + reset state
|
||
- [x] 工具名规范化函数(`Bash` → `bash`、`Read` → `read` 等,复用现有 `toPermissionKey` 映射)
|
||
- [x] tool input JSON 累积 + try/catch 解析 fallback
|
||
- [x] 多 step(tool_use 循环)场景:每次 `message_start` 生成新 messageID
|
||
|
||
### 1.2 改造 stream_event 路由
|
||
|
||
- [x] `sessionMessageRouter.ts` — `case 'stream_event'` 改用 StreamStateTracker
|
||
- [x] 移除旧 `ctx.emitEvent('stream_event', msg)` — 不再做 raw 转发
|
||
- [x] Tracker 产出的 canonical 事件通过 `ctx.emitOpencodeEvent()` 发出
|
||
|
||
### 1.3 system 子类型分发
|
||
|
||
- [x] `case 'system'` — 替换当前的统一 `ctx.emitEvent('message', msg)`
|
||
- [x] `task_notification` → `ctx.emitOpencodeEvent('task.completed', { taskID, status, summary, usage, ... })`
|
||
- [x] `task_started` → `ctx.emitOpencodeEvent('task.started', { taskID, description, taskType, ... })`
|
||
- [x] `task_progress` → `ctx.emitOpencodeEvent('task.progress', { taskID, usage, summary, workflowProgress, ... })`
|
||
- [x] `api_error` / `api_retry` → `ctx.emitOpencodeEvent('session.error', { error: { subtype, message, retryInMs, ... } })`
|
||
- [x] `compact_boundary` / `microcompact_boundary` → `ctx.emitOpencodeEvent('message.part.updated', { part: { type: "compaction", auto, overflow } })`
|
||
- [x] `stop_hook_summary` → `ctx.emitOpencodeEvent('session.hook_summary', { ... })`
|
||
- [x] `turn_duration` → `ctx.emitOpencodeEvent('session.metrics', { ... })`
|
||
- [x] `cache_warning` → `ctx.emitOpencodeEvent('session.warning', { ... })`
|
||
- [x] `informational` → `ctx.emitOpencodeEvent('session.info', { ... })`
|
||
- [x] `post_turn_summary` → `ctx.emitOpencodeEvent('session.info', { ... })`
|
||
- [x] `session_state_changed` → `ctx.emitOpencodeEvent('session.status', { ... })`
|
||
- [x] `status` → `ctx.emitOpencodeEvent('session.status', { ... })`
|
||
- [x] `default` → `ctx.emitOpencodeEvent('session.info', { subtype, ...msg })`
|
||
|
||
### 1.4 新增 attachment 路由
|
||
|
||
- [x] `case 'attachment'` — 当前 default:break 丢弃
|
||
- [x] 转发为 `ctx.emitOpencodeEvent('message.attachment', { attachmentType, attachment })`
|
||
- [x] 关键 attachment 子类型至少覆盖:
|
||
- [x] `hook_success` / `hook_error` / `hook_cancelled`
|
||
- [x] `relevant_memories` / `nested_memory`
|
||
- [x] `task_status` / `task_reminder`
|
||
- [x] `diagnostics`
|
||
- [x] `token_usage` / `budget_usd`
|
||
- [x] `invoked_skills`
|
||
|
||
> 注:attachment 路由不区分子类型,统一通过 `attachmentType` 字段透传。消费端按需过滤。
|
||
|
||
### 1.5 新增 progress 路由
|
||
|
||
- [x] `case 'progress'` — 当前 default:break 丢弃
|
||
- [x] 转发为 `ctx.emitOpencodeEvent('tool.progress', { toolUseID, parentToolUseID, data })`
|
||
|
||
### 1.6 新增 tombstone 路由
|
||
|
||
- [x] `src/QueryEngine.ts` — serve 模式下将 tombstone 消息 yield 到 stdout
|
||
- [x] `sessionMessageRouter.ts` — `case 'tombstone'`
|
||
- [x] 转发为 `ctx.emitOpencodeEvent('message.removed', { messageID })`
|
||
|
||
### 1.7 handleResultMessage 增强
|
||
|
||
- [x] 提取 `stop_reason` 传播到 `session.result` 的 reason 字段
|
||
- [x] 提取 `usage` / `cost_usd` 透传到 `session.result` 事件
|
||
- [x] `subtype !== 'success'` 时发射 `session.error` 事件
|
||
- [x] 区分 result 子类型:`success` / `error_max_turns` / `error_max_budget` / `error_doom_loop` / `error`
|
||
|
||
### 1.8 handleAssistantMessage 增强(非流式 / 历史加载场景)
|
||
|
||
- [x] 完整 assistant 消息到达时,发射 `message.updated` 事件(含 role/modelID/cost/tokens/parentID)
|
||
- [x] 遍历 content blocks,为每个 block 发射 `message.part.updated`
|
||
- [x] `text` block → text part
|
||
- [x] `thinking` block → reasoning part
|
||
- [x] `redacted_thinking` block → reasoning part (redacted)
|
||
- [x] `tool_use` block → tool part (status: running)
|
||
|
||
---
|
||
|
||
## Phase 2:REST API 适配
|
||
|
||
### 2.1 消息历史端点标准化
|
||
|
||
- [x] `GET /session/{id}/message` — 返回 parts-based 格式(对标 opencode)
|
||
- [x] assistant 消息:content blocks 分解为 parts 数组
|
||
- [x] user 消息:text/tool_result 分解为 parts
|
||
- [x] 新增 `format=parts` query parameter 消费端按需选择
|
||
- [ ] system 消息:compaction_boundary → compaction part
|
||
- [ ] attachment 消息:保留原始数据但增加 part 包装
|
||
- [ ] `GET /session/{id}/todo` — 适配响应格式
|
||
|
||
---
|
||
|
||
## Phase 3:EventBus / SSE 层调整
|
||
|
||
### 3.1 emitEvent → emitOpencodeEvent 统一
|
||
|
||
- [x] 审计 `sessionHandle.ts` 中所有 `ctx.emitEvent()` 调用
|
||
- [x] `emitEvent('message', ...)` → `emitOpencodeEvent('message.updated', ...)`
|
||
- [x] `emitEvent('result', ...)` → `emitOpencodeEvent('session.result', ...)`
|
||
- [x] `emitEvent('ready', ...)` → `emitOpencodeEvent('session.updated', ...)`
|
||
- [x] `emitEvent('deleted', ...)` → `emitOpencodeEvent('session.deleted', ...)`
|
||
- [x] `emitEvent('stream_event', ...)` → 移除(Phase 1.2 已由 StreamStateTracker 替代)
|
||
- [x] `emitEvent('control_request', ...)` → 保留(已有独立 opencode 事件)
|
||
- [x] `emitEvent('permission_replied', ...)` / `emitEvent('question_replied', ...)` → 保留(低级别确认)
|
||
|
||
### 3.2 SSE 事件名映射
|
||
|
||
- [x] 确认 `emitOpencodeEvent` 输出的 SSE event name 格式
|
||
- [x] 全部统一为 opencode 风格(无 `session.` 前缀)
|
||
|
||
---
|
||
|
||
## Phase 4:测试
|
||
|
||
### 4.1 单元测试
|
||
|
||
- [x] `src/server/__tests__/streamStateTracker.test.ts` (18 tests, 35 assertions)
|
||
- [x] message_start → message.updated + step-start
|
||
- [x] text content_block 完整流程 (start → delta → stop)
|
||
- [x] thinking content_block 完整流程
|
||
- [x] redacted_thinking 处理
|
||
- [x] tool_use 完整流程 (start → input_delta → stop → running)
|
||
- [x] message_delta 提取 usage/stopReason
|
||
- [x] message_stop → step-finish + reset
|
||
- [x] 多 step 场景(tool_use 循环产生多个 message_start/stop)
|
||
- [x] input_json_delta 累积 + 损坏 JSON fallback
|
||
- [x] `src/server/__tests__/sessionMessageRouter.test.ts` (29 tests, 57 assertions)
|
||
- [x] system.task_notification → task.completed
|
||
- [x] system.task_started → task.started
|
||
- [x] system.task_progress → task.progress
|
||
- [x] system.api_error → session.error
|
||
- [x] system.compact_boundary → compaction part
|
||
- [x] attachment 路由
|
||
- [x] progress 路由
|
||
- [x] tombstone → message.removed
|
||
- [x] result 子类型区分
|
||
- [x] assistant 非流式消息 → parts 分解
|
||
- [x] init → session.updated
|
||
- [x] user → message.updated
|
||
|
||
### 4.2 集成测试
|
||
|
||
- [ ] 启动 csc serve,发送 prompt,验证 SSE 流包含完整 canonical 事件序列
|
||
- [ ] 后台 agent 场景:验证 task.started → task.progress → task.completed
|
||
- [ ] API 错误场景:验证 session.error 事件
|
||
- [ ] 压缩场景:验证 compaction part
|
||
- [ ] 历史消息加载:验证 parts-based 格式
|
||
|
||
### 4.3 兼容性测试
|
||
|
||
- [ ] 旧版 cs-cloud + 新 csc serve:确认旧 adapter 正常处理保留的事件名
|
||
- [ ] 旧版 cs-cloud passthrough 新事件:消费端不报错
|
||
- [ ] 新版 cs-cloud + 新 csc serve:确认 adapter 切换到新事件后功能不降级
|
||
|
||
---
|
||
|
||
## Phase 5:cs-cloud Adapter 精简(cs-cloud 侧)
|
||
|
||
### 5.1 切换到新事件源
|
||
|
||
- [ ] cs-cloud adapter 优先消费 `message.part.updated` / `message.part.delta` 而非 `session.stream_event`
|
||
- [ ] cs-cloud adapter 消费 `task.started` / `task.progress` / `task.completed` 而非从 `session.message` 推断
|
||
- [ ] cs-cloud adapter 消费 `session.error` 而非丢弃 system 子类型
|
||
|
||
### 5.2 移除适配代码
|
||
|
||
- [ ] 移除 `adapter_sse_stream.go`(StreamStateTracker 已在 csc 侧完成)
|
||
- [ ] 移除 `adapter_sse_message.go`(parts 分解已在 csc 侧完成)
|
||
- [ ] 移除 `adapter_parts.go`(part 构建已在 csc 侧完成)
|
||
- [ ] 保留 `adapter_json.go`(REST API 响应仍需适配,直到 Phase 2 完成)
|
||
- [ ] adapter 降级为 thin proxy(仅 SSE passthrough + REST 路由)
|
||
|
||
---
|
||
|
||
## 不在本次范围
|
||
|
||
- Event Sourcing / CQRS(架构变更过大)
|
||
- 多 Workspace 实例隔离(cs-cloud 多进程模型已解决)
|
||
- File snapshot / patch 追踪(依赖文件系统监控基础设施)
|
||
- Session fork / share 功能实现(需要 csc 核心逻辑支持)
|