# 事件流说明（轮询返回 events）

`GET /api/v1/tasks/{task_id}` 返回的 `events` 字段，是一个按时间顺序（`idx` 递增）的**事件流**。

本项目的目标是：**与网页端 WebSocket 事件保持同一粒度**。因此你会看到：

- token 级别的 `text_chunk`/`thinking_chunk`
- 工具调用生命周期事件（准备/意图/开始/状态更新）
- 工具执行结果的状态更新与系统消息

> 提示：客户端应只依赖 `type` 字符串与 `data` 字段，并对未知事件类型保持兼容（忽略或记录）。

## 1. 事件统一包裹格式（envelope）

每个事件的结构：

```json
{
  "idx": 12,
  "type": "text_chunk",
  "data": { "content": "你好", "index": 5, "elapsed": 0.003 },
  "ts": 1769182968.154797
}
```

字段含义：

- `idx`：事件序号（从 0 开始递增），用于轮询 offset 与去重
- `type`：事件类型（字符串）
- `data`：事件载荷（不同 type 不同结构）
- `ts`：服务端记录的 UNIX 时间戳（秒，float）

## 2. offset 与去重

轮询接口支持 `from` 参数：

- `GET /api/v1/tasks/{task_id}?from=0`：从头拉取
- `GET /api/v1/tasks/{task_id}?from=next_offset`：增量拉取
（路径里的 `{task_id}` 为任务 id）

正确做法：

1. 客户端本地保存 `offset`（初始 0）
2. 轮询请求带上 `from=offset`
3. 处理返回 `events`
4. 将 `offset` 更新为返回的 `next_offset`

## 3. 常见事件类型与载荷

以下为**常见**事件类型（实际可能随版本增加更多 type）。

### 3.1 AI 消息生命周期

#### `ai_message_start`

AI 新一轮回复开始（一次用户消息可能触发多次迭代，但 UI 通常以此作为“助手消息开始”的信号）。

```json
{ "type": "ai_message_start", "data": {} }
```

### 3.2 思考（reasoning）

#### `thinking_start`

```json
{ "type": "thinking_start", "data": {} }
```

#### `thinking_chunk`

逐段思考内容（细粒度流式）。

```json
{ "type": "thinking_chunk", "data": { "content": "..." } }
```

#### `thinking_end`

思考结束（带完整思考文本）。

```json
{ "type": "thinking_end", "data": { "full_content": "..." } }
```

建议客户端处理方式：

- 若你想还原“实时思考流”，就持续 append `thinking_chunk.data.content`；
- 若你只想最终值，可忽略 chunk，只在 `thinking_end` 读取 `full_content`。

### 3.3 正文输出（token 级）

#### `text_start`

```json
{ "type": "text_start", "data": {} }
```

#### `text_chunk`

逐 token/子串输出。字段：

- `content`：本次增量内容
- `index`：从 1 开始的 chunk 序号（仅用于调试/对齐）
- `elapsed`：与上一 chunk 的时间间隔（秒）

```json
{
  "type": "text_chunk",
  "data": { "content": "你好", "index": 5, "elapsed": 0.003 }
}
```

#### `text_end`

正文结束，包含 `full_content`（完整文本）。

```json
{ "type": "text_end", "data": { "full_content": "..." } }
```

建议客户端处理方式：

- 实时显示：append `text_chunk.data.content`
- 最终落盘：以 `text_end.data.full_content` 为准（如果你做了实时 append，最终可用 full_content 校验/纠偏）

> 注意：若模型在工具调用后没有再次输出总结文本，则可能出现“最后一轮事件是工具相关，没有 text_end”的情况；这属于模型行为/迭代次数限制导致，并不代表任务异常。

### 3.4 工具调用（tool）相关

工具链路通常会出现如下事件：

1) `tool_preparing`（模型开始输出 tool_calls 时）  
2) `tool_intent`（从增量 arguments 中抽取 intent 字段）  
3) `tool_start`（真正执行工具时）  
4) `update_action`（执行进度与结果状态更新）  

#### `tool_preparing`

```json
{
  "type": "tool_preparing",
  "data": {
    "id": "web_search:0",
    "name": "web_search",
    "message": "准备调用 web_search...",
    "intent": "搜索明日方舟终末地游戏信息",
    "conversation_id": "conv_..."
  }
}
```

#### `tool_intent`

```json
{
  "type": "tool_intent",
  "data": {
    "id": "web_search:0",
    "name": "web_search",
    "intent": "搜索明日方舟终末地游戏信息",
    "conversation_id": "conv_..."
  }
}
```

#### `tool_start`

```json
{
  "type": "tool_start",
  "data": {
    "id": "tool_0_web_search_...",
    "name": "web_search",
    "arguments": { "...": "..." },
    "preparing_id": "web_search:0",
    "monitor_snapshot": null,
    "conversation_id": "conv_..."
  }
}
```

说明：

- `preparing_id` 用于把“模型输出的 tool_call”与“真实执行的工具卡片”关联起来
- `arguments` 为工具参数（通常是 JSON 对象）

#### `update_action`

该事件用于更新工具卡片（或其他 action）的状态。字段随工具不同可能略有差异，但通常包含：

- `id`：与 `tool_start.data.id` 对应
- `status`：`running/completed/failed/...`
- `message`：可读描述
- 可能还带 `result/summary/monitor_snapshot/...`

客户端建议：

- 把 `update_action` 当作通用的“状态更新”事件处理；
- 对未知字段保持兼容（直接展示或忽略）。

### 3.5 系统消息与错误

#### `system_message`

```json
{ "type": "system_message", "data": { "content": "..." } }
```

#### `error`

任务内错误（不同于 HTTP 层错误）。出现后通常任务会进入 `failed`。

```json
{ "type": "error", "data": { "message": "..." } }
```

### 3.6 任务结束

#### `task_complete`

```json
{
  "type": "task_complete",
  "data": {
    "total_iterations": 2,
    "total_tool_calls": 1,
    "auto_fix_attempts": 0
  }
}
```

> 结束条件以轮询返回的 `status != running` 为准；`task_complete` 是一个“完成事件”，但客户端仍应读取 `status`。

## 4. 事件缓冲与性能建议

- 服务端每个任务保存最近 **1000** 条事件（队列会丢弃更早的数据）
- 建议轮询间隔 `0.5s ~ 2s`
- 长任务建议客户端把 `text_chunk` 与关键工具事件持久化到本地，避免错过
