# ChatSession & Message API(MVP-1) 基地址:`http://localhost:8000/api`(FrankenPHP 容器 8000 端口) 认证方式:JWT,`Authorization: Bearer {token}` 自然语言:中文 ## 变更记录 - 2025-02-14:新增 ChatSession 创建、消息追加、增量查询接口;支持状态门禁与 dedupe 幂等。 - 2025-02-14:MVP-1.1 增加会话列表、会话更新(重命名/状态变更),列表附带最后一条消息摘要。 ## 领域模型 - `ChatSession`:`session_id`(UUID)、`session_name`、`status`(`OPEN`/`LOCKED`/`CLOSED`)、`last_seq` - `Message`:`message_id`(UUID)、`session_id`、`role`(`USER`/`AGENT`/`TOOL`/`SYSTEM`)、`type`(字符串)、`content`、`payload`(json)、`seq`(会话内递增)、`reply_to`(UUID)、`dedupe_key` - 幂等:`UNIQUE (session_id, dedupe_key)`;同一 dedupe_key 返回已有消息。 - 状态门禁:`CLOSED` 禁止追加,例外 `role=SYSTEM && type in [run.status, error]`;`LOCKED` 禁止 `role=USER && type=user.prompt`。 - 会话缓存:`chat_sessions.last_message_id` 记录最后一条消息;`appendMessage` 事务内同步更新 `last_seq`、`last_message_id`、`updated_at`。 ## 接口 ### 创建会话 - `POST /sessions` - 请求体字段 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | session_name | 否 | string(≤255) | 会话名称 | - 响应 201(JSON) | 字段 | 类型 | 说明 | | --- | --- | --- | | session_id | uuid | 主键 | | session_name | string|null | 会话名 | | status | enum | `OPEN|LOCKED|CLOSED` | | last_seq | int | 当前最大 seq | | last_message_id | uuid|null | 最后一条消息 | | created_at, updated_at | datetime | 时间戳 | - 错误:401 未授权 ### 追加消息 - `POST /sessions/{session_id}/messages` - 请求体字段 | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | role | 是 | enum | `USER|AGENT|TOOL|SYSTEM` | | type | 是 | string(≤64) | 如 `user.prompt`/`agent.message` 等 | | content | 否 | string | 文本内容 | | payload | 否 | object | jsonb 结构 | | reply_to | 否 | uuid | 引用消息 | | dedupe_key | 否 | string(≤128) | 幂等键 | - 响应 201(JSON) 字段:`message_id, session_id, seq, role, type, content, payload, reply_to, dedupe_key, created_at` - 幂等:同 session + dedupe_key 返回已存在的消息(同 `message_id/seq`)。 - 错误:401 未授权;403 违反状态门禁(CLOSED 禁止,LOCKED 禁止 user.prompt);404 session 不存在;422 校验失败。 ### 按序增量查询 - `GET /sessions/{session_id}/messages?after_seq=0&limit=50` - 查询参数 | 参数 | 默认 | 类型 | 说明 | | --- | --- | --- | --- | | after_seq | 0 | int | 仅返回 seq 大于该值 | | limit | 50 | int(≤200) | 返回数量上限 | - 响应 200:`data` 数组,元素字段同“追加消息”响应。 - 错误:401/404/422 ### 会话列表 - `GET /sessions?page=1&per_page=15&status=OPEN&q=keyword` - 查询参数 | 参数 | 默认 | 类型 | 说明 | | --- | --- | --- | --- | | page | 1 | int | 分页页码 | | per_page | 15 | int(≤100) | 分页大小 | | status | - | enum | 过滤 `OPEN|LOCKED|CLOSED` | | q | - | string | ILIKE 模糊匹配 session_name | - 响应 200:分页结构(`data/links/meta`),`data` 每项字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | session_id | uuid | 会话主键 | | session_name | string|null | 名称 | | status | enum | `OPEN|LOCKED|CLOSED` | | last_seq | int | 当前最大 seq | | last_message_id | uuid|null | 最后一条消息 | | last_message_at | datetime|null | 最后一条消息时间 | | last_message_preview | string | content 截断 120,空内容返回空字符串 | | last_message_role | string|null | 最后消息角色 | | last_message_type | string|null | 最后消息类型 | | created_at, updated_at | datetime | 时间戳 | - 排序:`updated_at` DESC - 错误:401/422 ### 会话更新 - `PATCH /sessions/{session_id}` - 请求体(至少一项,否则 422) | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | session_name | 否 | string 1..255 | 自动 trim | | status | 否 | enum | `OPEN|LOCKED|CLOSED` | - 规则: - `CLOSED` 不可改回 `OPEN`(返回 403)。 - 任意更新都会刷新 `updated_at`。 - 响应 200:字段同“会话列表”项。 - 错误:401 未授权;403 状态门禁;404 session 不存在;422 校验失败。 ### 获取会话详情 - `GET /sessions/{session_id}` - 响应 200:字段同“会话列表”项。 - 错误:401 未授权;404 session 不存在。 ### 归档会话(Archive) - `POST /sessions/{session_id}/archive` - 行为:将 `status` 置为 `CLOSED`,更新 `updated_at`,幂等(重复归档返回当前状态)。 - 响应 200:字段同“会话列表”项(status=CLOSED)。 - 错误:401 未授权;404 session 不存在。 ### 获取单条消息(带会话校验) - `GET /sessions/{session_id}/messages/{message_id}` - 行为:校验 `message.session_id` 与路径参数一致,否则 404。 - 响应 200:字段同“追加消息”响应。 - 错误:401 未授权;404 不存在或不属于该会话。 ### SSE 实时增量 - `GET /sessions/{session_id}/sse?after_seq=123` - 头部:`Accept: text/event-stream`,可带 `Last-Event-ID`(优先于 query)用于断线续传。 - 查询参数 | 参数 | 默认 | 类型 | 说明 | | --- | --- | --- | --- | | after_seq | 0 | int | backlog 起始 seq(若有 Last-Event-ID 则覆盖) | | limit | 200 | int(≤500) | backlog 最多条数 | - SSE 输出格式: ``` id: {seq} event: message data: {...message json...} ``` - `id` 为消息 `seq`,便于续传;`data` 为消息 JSON(同追加消息响应字段)。 - Backlog:建立连接后先补发 `seq > after_seq` 的消息(order asc,最多 `limit` 条),再进入实时订阅。 - 实时:Redis channel `session:{session_id}:messages` 发布消息 ID,SSE 侧读取后按 seq 去重、推送。 - 心跳:周期输出 `: ping` 保活(生产环境)。 - 错误:401 未授权;404 session 不存在。 ## cURL 示例 ```bash # 创建会话 SESSION_ID=$(curl -s -X POST http://localhost:8000/api/sessions \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"session_name":"Demo"}' | jq -r '.data.session_id') # 追加消息(支持 dedupe_key 幂等) curl -s -X POST http://localhost:8000/api/sessions/$SESSION_ID/messages \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"role":"USER","type":"user.prompt","content":"hello","dedupe_key":"k1"}' # 增量查询 curl -s "http://localhost:8000/api/sessions/$SESSION_ID/messages?after_seq=0&limit=50" \ -H "Authorization: Bearer $TOKEN" # 归档 curl -X POST http://localhost:8000/api/sessions/$SESSION_ID/archive \ -H "Authorization: Bearer $TOKEN" # 获取单条消息 curl -s http://localhost:8000/api/sessions/$SESSION_ID/messages/{message_id} \ -H "Authorization: Bearer $TOKEN" # SSE(断线续传:可带 Last-Event-ID) curl -N http://localhost:8000/api/sessions/$SESSION_ID/sse?after_seq=10 \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: text/event-stream" ```