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

# 流式補全

> 完整的 SSE 事件模式、傳輸格式與錯誤語義。

在任意 `POST /api/v1/completions` 請求中設置 `stream: true`，即可獲得 Server-Sent Events（SSE）流，而非單個 JSON 響應。

## 請求

```bash theme={null}
curl -N -X POST https://www.hitheo.ai/api/v1/completions \
  -H "Authorization: Bearer $THEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Write a haiku about code", "stream": true}'
```

需要通過 Bearer token 進行身份驗證。參見[身份驗證](/api-reference/authentication)。

## 傳輸格式

每個事件由兩行組成，後跟一個空行：

```
event: <event_name>
data: <json_payload>
```

調用方必須緩衝部分數據塊並按 `\n\n` 進行拆分——令牌事件每條可能只有幾十字節，且傳輸層不保證與事件邊界對齊。

## 事件類型（按到達順序）

服務器以固定順序發出事件：`thinking` → `meta` → (`skills`? / `genui_meta`?) → `tool`\* → `artifact`\* → `token`\* → `done`。若流中途出現失敗，將發出 `error` 事件並關閉該流。

| 事件 | 說明 |
| - | - |
| `thinking` | 心跳——單個字節，用於讓代理立即刷新響應。無 JSON 負載 |
| `meta` | 引擎信息、解析後的模式、工件、路由、`conversation_id`、`request_id` |
| `skills` | 本輪生效的技能（僅當應用了一個或多個技能時出現） |
| `genui_meta` | GenUI 組件庫信息（僅當 `resolved_mode === "genui"` 時出現） |
| `tool` | 表示一次工具調用（可能多次觸發） |
| `artifact` | 一個生成的文件（圖片、文檔、代碼、視頻）（可能多次觸發） |
| `token` | 一個文本片段（每個令牌觸發一次） |
| `done` | 最終負載：完整內容、後續建議、用量、`conversation_id`、`request_id` |
| `error` | 發生錯誤——負載與 REST 錯誤信封格式一致 |

## 負載模式

### `meta`

```typescript theme={null}
interface StreamMetaData {
  id: string;                       // completion id (cmpl_...)
  mode: ChatMode;                   // mode you requested
  resolved_mode: ChatMode;          // mode after intent classification
  model: {
    id: string;                     // theo-branded id (e.g. "theo-1-flash")
    label: string;                  // human-friendly label
    engine: string;                 // engine subsystem (e.g. "theo-core")
  };
  tools: Array<{ name: string; status: string; description?: string }>;
  artifacts: unknown[];             // pre-populated artifacts (images, docs)
  brand?: Record<string, unknown>;  // optional brand-soul overlay
  routing?: Record<string, unknown>;// routing-engine telemetry
  conversation_id: string | null;   // null for stateless callers
  request_id: string;               // mirror of X-Request-Id header
}
```

### `token`

```typescript theme={null}
interface StreamTokenData {
  token: string; // text chunk; concatenate in order to reconstruct the full content
}
```

### `tool`

```typescript theme={null}
interface StreamToolData {
  name: string;        // tool name (e.g. "Intent classifier", "browser_navigate")
  status: string;      // "pending" | "running" | "complete" | "error"
  description?: string;// short human-readable description
}
```

### `artifact`

負載取決於 `artifact.type`。通用字段：`id`、`type`、`title`、`providerId`、`modelId`。圖片類工件包含 `imageUrl`；視頻類工件包含 `videoUrl`；文檔類工件包含 `downloadUrl` 和 `sizeBytes`；代碼類工件包含 `code` 和 `language`。

### `genui_meta`

```typescript theme={null}
interface GenUIMetaEvent {
  library: string; // component library id (e.g. "theo")
  tools: string[]; // tool names the Renderer's toolProvider should expose
}
```

### `skills`

```typescript theme={null}
interface StreamSkillsData {
  active: Array<{
    id: string;
    slug: string;
    name: string;
    intensity?: number; // 0–100 when the skill supports intensity
  }>;
}
```

### `done`

```typescript theme={null}
interface StreamDoneData {
  id: string;
  content: string;                  // full text output (same as concatenated tokens)
  follow_ups: Array<{ label: string; prompt: string }>;
  structured_output?: {
    type: string;
    data: unknown;
    parse_error?: string;
  };
  skills_active?: Array<{ id: string; slug: string; name: string; intensity?: number }>;
  routing?: Record<string, unknown>;
  usage: {
    cost_cents: number;
    prompt_tokens: number;    // always 0 for non-text modes
    completion_tokens: number;// always 0 for non-text modes
    total_tokens: number;
    cached?: boolean;         // true when served from the semantic cache
  };
  conversation_id: string | null;
  request_id: string;
}
```

### `error`

`error` 事件的負載與 REST 錯誤信封格式一致（參見[概覽](/api-reference/overview)）：

```typescript theme={null}
interface StreamErrorData {
  error: {
    message: string;
    type: string;            // e.g. "rate_limit_error", "server_error"
    code: string;            // e.g. "stream_interrupted", "orchestration_failed"
    request_id: string | null;
  };
}
```

## 流示例

```
event: thinking
data: {"status":"processing"}

event: meta
data: {"id":"cmpl_abc123","mode":"auto","resolved_mode":"fast","model":{"id":"theo-1-flash","label":"Theo Flash","engine":"theo-core"},"tools":[],"artifacts":[],"conversation_id":null,"request_id":"req_9f2e1a"}

event: token
data: {"token":"Lines"}

event: token
data: {"token":" of"}

event: token
data: {"token":" code"}

event: done
data: {"id":"cmpl_abc123","content":"Lines of code","follow_ups":[],"usage":{"cost_cents":0.01,"prompt_tokens":8,"completion_tokens":3,"total_tokens":11},"conversation_id":null,"request_id":"req_9f2e1a"}
```

## 提取 `conversation_id`

`conversation_id` 同時出現在 `meta` 和 `done` 中。對於無狀態調用方（請求中未包含 `conversation_id`），其值為 `null`。對於在請求中傳入了 `conversation_id` 的調用方，該值會原樣回傳。SDK 在流結束後將其暴露為 `stream.conversationId`。

```typescript theme={null}
const stream = theo.stream({ prompt: "..." });
for await (const event of stream) {
  // consume events
}
console.log(stream.conversationId); // populated from meta / done
```

## 流中錯誤

當編排在 `meta` 已發出之後失敗時（例如上游 LLM 提供方超時，或觸發了某個模式下的速率限制），服務器會發出帶 REST 信封格式的 `error` 事件並關閉該流：

```
event: meta
data: {"id":"cmpl_abc123","mode":"auto","resolved_mode":"fast","model":{"id":"theo-1-flash","label":"Theo Flash","engine":"theo-core"},"tools":[],"artifacts":[],"conversation_id":null,"request_id":"req_9f2e1a"}

event: token
data: {"token":"Lines"}

event: error
data: {"error":{"message":"Rate limit exceeded for fast mode (120 RPM on 'free' tier).","type":"rate_limit_error","code":"mode_rate_limit_exceeded","request_id":"req_9f2e1a"}}
```

請使用與 REST 錯誤處理相同的代碼路徑：

```typescript theme={null}
for await (const event of stream) {
  if (event.type === "error") {
    const { message, type, code, request_id } = event.data.error;
    throw new Error(`[${type}/${code}] ${message} (request_id=${request_id})`);
  }
  // ...
}
```

## SDK 用法

SDK 返回一個 `TheoStream`——一個可異步迭代的對象，帶有用於中途取消的 `cancel()` 方法，以及最終的元數據屬性。

```typescript theme={null}
import { Theo } from "@hitheo/sdk";

const theo = new Theo({ apiKey: process.env.THEO_API_KEY! });
const stream = theo.stream({ prompt: "Explain DNS" });

// Graceful cancel after 3s (e.g. user clicked "Stop generating")
setTimeout(() => stream.cancel(), 3000);

for await (const event of stream) {
  switch (event.type) {
    case "meta":     console.log("Mode:", event.data.resolved_mode); break;
    case "token":    process.stdout.write(event.token); break;
    case "tool":     console.log("Tool:", event.data.name, event.data.status); break;
    case "artifact": console.log("Artifact:", event.data); break;
    case "error":    console.error("Stream error:", event.data.error); break;
    case "done":     console.log("\nCost:", event.data.usage.cost_cents); break;
  }
}

console.log("conversation_id:", stream.conversationId);
console.log("usage:", stream.usage);
console.log("request_id:", stream.requestId);
```

參見[流式](/core-concepts/streaming)獲取概念性指南。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.