> ## 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.

# 异步任务（研究与视频）

> 以正确的方式运行长任务：使用 research()/video() 入队，然后用 waitForJob() 轮询任务。

部分 Theo 能力**太慢，无法内联返回**。`research`（规划 → 多源网络搜索 → 综合）和 `video`（模型生成）以后台任务的形式运行：你先将其入队，立即获得一个 `job_id`，然后轮询直到任务完成。

<Warning>
  **不要**向 `/completions` 或 `theo.stream()` 发送 `mode: "research"` 或 `mode: "video"`。那样会让任务**内联**运行，从而超出请求超时时间。如果你这样做，SDK 会立即抛出 `TheoUsageError`。请使用下方的专用方法。
</Warning>

## 生命周期

1. **入队** — `POST /api/v1/research` 或 `/api/v1/video` 会返回 `202`，并带有 `{ id, job_id, status: "queued", poll_url }`。
2. **轮询** — `GET /api/v1/jobs/{job_id}` 返回当前 `status`：`queued` → `active` → `completed`（或 `failed`）。
3. **读取结果** — 当 `status === "completed"` 时，`result` 中保存着类型化的负载数据。

## SDK 用法

`waitForJob()` 会为你执行轮询循环，并支持可选的 `onProgress` 回调和 `AbortSignal`。

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

const theo = new Theo({ apiKey: process.env.THEO_API_KEY! });

const job = await theo.research({
  prompt: "Compare React, Vue, and Svelte for enterprise apps in 2026",
  depth: "advanced",
  max_sources: 10,
});

const final = await theo.waitForJob<ResearchJobResult>(job.job_id, {
  intervalMs: 3000,                       // poll cadence (default 2s)
  maxWaitMs: 180_000,                      // give up after 3 min (default 5 min)
  onProgress: (s) => console.log(`${s.status} ${s.progress}%`),
  signal: AbortSignal.timeout(180_000),   // optional hard cancel
});

if (final.status === "completed") {
  console.log(final.result?.report);
  console.log(`${final.result?.sourceCount} sources`);
}
```

Video 的用法与之相同，使用 `theo.video()` + `waitForJob<VideoJobResult>()`。

### 轮询间隔与限制

* 默认轮询间隔为 **2 秒**；`waitForJob` 默认在 **5 分钟**后放弃（`maxWaitMs`），并抛出 `TheoTimeoutError`。对于较长的视频渲染，可以调高 `maxWaitMs`。
* 不要以快于约 1 秒的频率轮询——这只会消耗速率限制额度，并不会让任务更快完成。

### 取消

传入一个 `AbortSignal`。当它触发时，`waitForJob` 会停止轮询，并以 `TheoCancelledError` 拒绝（它**不会**取消服务器端的任务——只是让你的客户端停止等待）。

```typescript theme={null}
const controller = new AbortController();
// e.g. user navigates away
document.addEventListener("stop", () => controller.abort());

await theo.waitForJob(job.job_id, { signal: controller.signal });
```

## 结果模式

`result` 通过 `job<T>()` / `waitForJob<T>()` 上的泛型进行类型化。这些结构与 REST 负载一致（参见[获取任务状态](/api-reference/jobs/get)）：

* **`ResearchJobResult`** — `{ report, sources: { title, url }[], queries, sourceCount }`
* **`VideoJobResult`** — `{ videoUrl, model, engine, durationMs }`
* **`ImageJobResult`** — `{ imageUrl, model, engine }`
* **`DocumentJobResult`** — `{ title, format, downloadUrl, sizeBytes }`
  任何 `model` / `engine` 字段都会以 Theo 品牌呈现——原始的上游标识符永远不会出现。

## 成本与用量

任务结果目前尚不携带按任务计算的 `cost_cents` 字段。要归因 research/video 任务的支出，请查看聚合的[用量报告](/api-reference/usage/get)（`theo.usage()`），其中包含覆盖 `research` 和 `video` 的 `by_mode` 明细。

## 失败

当 `status === "failed"` 时，`result` 为 `null`，`error` 中保存着失败原因字符串。入队调用本身也可能在任务队列暂时不可用时返回 `503 queue_unavailable`——请稍后重试。


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