> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberun.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# 订阅任务事件流（SSE）

> 通过 Server-Sent Events 端点实时跟随任务进度。

`GET /api/v1/r/tasks/{taskId}/events` 打开一个 **Server-Sent Events**
流，每次状态变化或进度更新都会推送一个事件。连接保持开启直到任务
进入终态（`completed`、`failed`、`cancelled`），然后关闭。

请优先使用它，而非轮询 `GET /r/tasks/{taskId}` —— 延迟更低、请求更少。

## 建立连接

使用 Bearer 令牌（JWT、`sk-` 或 `dk-`）：

```bash theme={null}
curl -N \
  -H "Authorization: Bearer sk-..." \
  https://core.cyberun.cloud/api/v1/r/tasks/{taskId}/events
```

浏览器内建的 `EventSource` **不**支持自定义请求头，意味着它无法
携带 `Authorization` 头。服务端运行时（Node `fetch`、Python
`requests`、`httpx`）、React Native、以及 `eventsource` 这类库都
可以正常使用。在浏览器中请通过你自己的后端中转。

## 事件类型

| 事件          | 含义                                                       |
| ----------- | -------------------------------------------------------- |
| `status`    | 任务状态变化（例如 `pending` → `waiting` → `queued` → `running`）。 |
| `progress`  | 执行进度，包含 `progress`、`step_current`、`step_total`、当前节点。     |
| `completed` | 任务完成。包含输出对象。                                             |
| `failed`    | 任务失败。包含错误字符串。                                            |
| `cancelled` | 任务在进入终态前被取消。                                             |
| `error`     | 服务端读取任务状态时出错。流可能仍然继续。                                    |

### `status`

```
event: status
data: {"task_id":"...","status":"running","timestamp":"2026-02-01T14:30:05Z"}
```

### `progress`

```
event: progress
data: {
  "task_id":"...",
  "progress":0.35,
  "current_node":"3",
  "step_current":7,
  "step_total":20,
  "timestamp":"2026-02-01T14:30:10Z"
}
```

Nerfstudio 任务额外携带 `stage`（`download`、`process_data`、`train`、
`export`、`upload`）以及人类可读的 `message` 字段。

| 字段             | 类型      | 含义                             |
| -------------- | ------- | ------------------------------ |
| `progress`     | number  | `0.0`–`1.0` 的整体进度。             |
| `step_current` | integer | 已完成的步数（运行时报告时）。                |
| `step_total`   | integer | 本次运行的总步数。                      |
| `current_node` | string  | 运行时的当前节点标识（ComfyUI 工作流为节点 ID）。 |
| `stage`        | string  | 仅 Nerfstudio。粗粒度阶段名称。          |
| `message`      | string  | 仅 Nerfstudio。自由格式状态文本。         |

### 终态事件

```
event: completed
data: {"task_id":"...","status":"completed","completed_at":"...","output":{...}}

event: failed
data: {"task_id":"...","status":"failed","error":"ComfyUI execution error: ..."}

event: cancelled
data: {"task_id":"...","status":"cancelled"}
```

这三种事件之后服务器会关闭连接。

### `error`

```
event: error
data: {"message":"failed to fetch task status"}
```

服务端读取任务状态的瞬时问题。视为可恢复 —— 任务本身可能仍在运行。
如果流也关闭，请改为轮询（见下方）。

## 完整事件流示例

一个典型的 ComfyUI 任务从头到尾：

```
event: status
data: {"task_id":"...","status":"waiting","timestamp":"..."}

event: status
data: {"task_id":"...","status":"queued","timestamp":"..."}

event: status
data: {"task_id":"...","status":"running","timestamp":"..."}

event: progress
data: {"task_id":"...","progress":0.15,"step_current":3,"step_total":20,"current_node":"3"}

event: progress
data: {"task_id":"...","progress":0.5,"step_current":10,"step_total":20,"current_node":"3"}

event: progress
data: {"task_id":"...","progress":1.0,"step_current":20,"step_total":20,"current_node":"3"}

event: completed
data: {"task_id":"...","status":"completed","output":{...}}
```

## 轮询兜底

如果网络无法承载 SSE，或你不想长连接，可改为轮询
`GET /r/tasks/{taskId}` 直到 `task_status` 为
`completed`、`failed`、`cancelled` 之一。轮询间隔 3 秒是个合理默认值。

```bash theme={null}
curl -s \
  -H "Authorization: Bearer sk-..." \
  https://core.cyberun.cloud/api/v1/r/tasks/{taskId}
```

当任务为 `completed` 时，通过 `GET /r/tasks/{taskId}/result`
获取产物链接。

## 消费事件流

<CodeGroup>
  ```python Python theme={null}
  import json, requests

  API_KEY = "sk-..."
  BASE = "https://core.cyberun.cloud/api/v1/r"

  with requests.get(
      f"{BASE}/tasks/{task_id}/events",
      headers={"Authorization": f"Bearer {API_KEY}"},
      stream=True,
  ) as stream:
      event_type = None
      for line in stream.iter_lines(decode_unicode=True):
          if not line:
              continue
          if line.startswith("event:"):
              event_type = line[6:].strip()
          elif line.startswith("data:"):
              data = json.loads(line[5:].strip())
              if event_type == "progress":
                  pct = int(data["progress"] * 100)
                  print(f"{pct}% ({data['step_current']}/{data['step_total']})")
              elif event_type in ("completed", "failed", "cancelled"):
                  print(event_type, data)
                  break
  ```

  ```ts Node.js theme={null}
  const API_KEY = process.env.CYBERUN_KEY!;
  const BASE = 'https://core.cyberun.cloud/api/v1/r';

  const resp = await fetch(`${BASE}/tasks/${taskId}/events`, {
  	headers: { Authorization: `Bearer ${API_KEY}` }
  });

  const reader = resp.body!.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let eventType = '';

  while (true) {
  	const { done, value } = await reader.read();
  	if (done) break;
  	buffer += decoder.decode(value, { stream: true });
  	const lines = buffer.split('\n');
  	buffer = lines.pop() ?? '';

  	for (const line of lines) {
  		if (line.startsWith('event:')) {
  			eventType = line.slice(6).trim();
  		} else if (line.startsWith('data:')) {
  			const data = JSON.parse(line.slice(5).trim());
  			if (eventType === 'progress') {
  				console.log(`${Math.round(data.progress * 100)}%`);
  			} else if (eventType === 'completed' || eventType === 'failed' || eventType === 'cancelled') {
  				console.log(eventType, data);
  				return;
  			}
  		}
  	}
  }
  ```
</CodeGroup>

## 相关

* [生成 API 密钥](/zh/cloud/guides/api-key) —— 签发 `sk-` 凭证。
* [API 参考](/zh/api-reference/introduction) —— 完整端点列表。
* [Webhooks](/zh/cloud/guides/webhooks) —— 以推送方式接收终态事件，
  无需保持流式连接。
