# ジョブと実行

> TypeScript から、Nodaro の実行をポーリング、一覧表示、キャンセル、削除します。client.jobs は単体の生成を、client.executions はワークフロー全体の実行を追跡します。

Source: https://nodaro.ai/ja/docs/developers/sdk/jobs-and-executions

**ジョブ**は、1 枚の画像、1 本の動画のレンダリング、1 つのナレーションなど、Nodaro での生成 1 件です。**実行**は、ワークフロー全体を 1 回実行したものです。**`client.jobs`** はジョブの読み取り、一覧表示、キャンセル、削除を行い、**`client.executions`** はワークフローの実行の読み取り、一覧表示、キャンセルを行います。**`client.videoPro`** は、長時間の**動画生成 Pro**（Generate Video Pro）の実行を停止または継続します。これらのメソッドは、[ジョブ](https://nodaro.ai/docs/developers/api/jobs)と[実行](https://nodaro.ai/docs/developers/api/executions)の REST API と同じエンドポイントを呼び出します。

## メソッド
| メソッド | 内容 |
| --- | --- |
| [`executions.get(id)`](#executionsgetid) | ワークフローの実行を、すべてのノードの状態とともに読み取ります |
| [`executions.listForWorkflow(workflowId, params?)`](#executionslistforworkflowworkflowid-params) | ワークフローの実行を一覧表示します |
| [`executions.cancel(id, params?)`](#executionscancelid-params) | ワークフローの実行を停止します |
| [`jobs.get(id)`](#jobsgetid) | ジョブを、その入力と出力とともに読み取ります |
| [`jobs.list(params?)`](#jobslistparams) | 自分のジョブを、新しい順に一覧表示します |
| [`jobs.getStatus(id)`](#jobsgetstatusid) | ポーリング用に、ジョブのステータスを読み取ります |
| [`jobs.cancel(id)`](#jobscancelid) | ジョブを停止し、確保されていたクレジットを返還します |
| [`jobs.delete(id)`](#jobsdeleteid) | ジョブと、それが生成したメディアを削除します |
| [`videoPro.stop(jobId)`](#videoprostopjobid) | 動画生成 Pro の実行を停止し、完成した部分を残します |
| [`videoPro.continueRun(jobId, opts?)`](#videoprocontinuerunjobid-opts) | 動画生成 Pro の実行を、新しいジョブとして継続します |

## ステータス
ジョブは、次のステータスを移り変わります。

| ジョブのステータス | 意味 |
| --- | --- |
| `pending`、`queued` | ワーカーを待っています。 |
| `processing` | 実行中です。モデルが報告する場合、`progress` が 0 から 100 まで変化します。 |
| `pending_review` | このデプロイ環境のコンテンツポリシーにより、人によるレビューのために保留されています。終了状態ではありません。 |
| `completed` | 完了しました。結果は `output_data` にあります。 |
| `failed` | 失敗しました。理由は `error_message` に示され、`error_hint` が分類する場合もあります。 |
| `cancelled` | キャンセルによって停止しました。 |

実行には、独自のステータスがあります。`pending`、`running`、`completed`、`failed`、`cancelled`、`stopping`、`timed_out`、`discarded` です。実行内の各ノードは、`pending`、`running`、`completed`、`failed`、`skipped` のいずれかです。

## client.executions
実行は、ワークフローを 1 回実行したものです。AI ノードごとの 1 つのジョブと、すべてのノードの状態をまとめます。

### executions.get(id)
ワークフローの実行を、すべてのノードの状態のマップを含めて読み取ります。id がワークフローの実行ではなく単体のノードのジョブを指す場合、サーバーは、その 1 つのノードについて同じ形式で応答します。

```ts
get(id: string): Promise<{ data: WorkflowExecution }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "client.workflows.run() が返した実行の id です。" },
}}
/>

```ts
const { data } = await client.executions.get(executionId)
console.log(data.status, `${data.completedNodes}/${data.totalNodes}`, data.totalCreditsUsed)
```

`WorkflowExecution` は、`id`、`workflowId`、`status`、`triggerType`（`manual`、`webhook`、`schedule`、`app_run`、`single-node` のいずれか）、`nodeStates`、`totalNodes`、`completedNodes`、`failedNodes`、`totalCreditsUsed`、`errorMessage`、`startedAt`、`completedAt`、`createdAt`、`updatedAt` を持ちます。

**ノードの出力を読み取る。**`nodeStates[nodeId].output` は、ノードが `completed` のときに存在します。3D シーンのノードのように、ノードが `failed` でも、実行が使える結果を残す場合には存在することもあります。ステータスではなくこのフィールドを確認し、`output` が存在することを成功と見なさないでください。

```ts

const node = data.nodeStates["scene-1"]
if (node.status === "failed") {
console.error(node.error) // the failure stands
if (node.output?.plan) {
// the draft the run kept is still here, and it was billed
}
}
nodeStateMayCarryOutput(node.status) // true for "completed" and "failed"
```

この 2 つのステータスの集合である `OUTPUT_BEARING_NODE_STATUSES` も、エクスポートされています。

### executions.listForWorkflow(workflowId, params?)
1 つのワークフローの実行を、新しい順にページ単位で一覧表示します。この一覧には、そのワークフロー上で開始された単体ノードの実行も含まれます。

```ts
listForWorkflow(workflowId: string, params?: ListExecutionsForWorkflowParams): Promise<{
data: WorkflowExecutionSummary[]
nextCursor?: string
}>
```

<TypeTable
type={{
workflowId: { type: 'string', required: true, description: "ワークフローの id です。" },
limit: { type: 'number', description: "ページのサイズです。" },
cursor: { type: 'string', description: "前のページの nextCursor です。" },
status: { type: 'string', description: "カンマ区切りのステータスのリストです。たとえば pending,running です。" },
source: { type: '"editor" | "all"', description: "editor では、アプリ、Webhook、スケジュールが開始した実行を除きます。" },
}}
/>

```ts
const { data: runs, nextCursor } = await client.executions.listForWorkflow(workflowId, {
limit: 20,
status: "completed",
})
```

### executions.cancel(id, params?)
ワークフローの実行を停止します。停止のしかたは、次の 3 通りです。

- **即時**（デフォルト）：実行中のジョブがキャンセルされ、確保されていたクレジットが返還され、ステータスは `cancelled` になります。
- **`mode: "after_current"`**：ステータスは `stopping` になります。実行中のノードは完了してキャンバスとライブラリに反映され、その後で実行が停止します。
- **`mode: "discard"`**：新しいノードは開始しませんが、実行中のジョブはキャンセルされません。外部のモデルの呼び出しは、途中で止められないためです。それらは完了してライブラリに保存されますが、結果はキャンバスには戻りません。ステータスは `discarded` になります。ジョブは完了しているため、返還は行われません。

```ts
cancel(id: string, params?: { mode?: "after_current" | "discard" }): Promise<{ success: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "実行の id です。" },
mode: { type: '"after_current" | "discard"', description: "停止のしかたです。省略すると、即時に停止します。" },
}}
/>

```ts
await client.executions.cancel(executionId, { mode: "after_current" })
```

## client.jobs
ジョブは、1 枚の画像、1 本の動画のレンダリング、1 つのナレーションなど、生成 1 件です。ワークフローの実行は AI ノードごとに 1 つのジョブを作り、単体のノードの実行もそれぞれ 1 つのジョブを作ります。ジョブのフィールドは、API が送るとおりのスネークケースです。

### jobs.get(id)
ジョブを、その入力と出力を含めて読み取ります。

```ts
get(id: string): Promise<{ data: Job }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ジョブの id です。" },
}}
/>

```ts
const { data: job } = await client.jobs.get(jobId)
if (job.status === "completed") console.log(job.output_data)
```

`Job` には、次のフィールドがあります。

| フィールド | 説明 |
| --- | --- |
| `id`、`status`、`progress` | ジョブの id、ステータス、0 から 100 までの進行状況です。 |
| `input_data`、`output_data` | リクエストの内容と結果です。どちらからも、サーバー内部専用の値は取り除かれます。 |
| `error_message` | ジョブが失敗した理由、または `null` です。 |
| `error_hint` | 一部の失敗について、構造化された理由を示します。下記を参照してください。 |
| `credits` | ジョブのために確保されたクレジット、または `null` です。 |
| `credit_status` | `reserved`、`committed`、`refunded` のいずれかで、クレジットの状態を示します。課金がないジョブでは `null` です。 |
| `job_type` | ジョブの種類です。 |
| `created_at`、`started_at`、`completed_at` | タイムスタンプです。 |
| `user_id` | 所有者です。 |
| `source`、`source_detail` | ジョブの発信元です。`sdk`、`cli`、`mcp`、`app`、`web`、`api` などがあり、`sdk/2.17.0` のような詳細が付きます。 |
| `recovering` | モデルが結果を返した後にワーカーが停止したジョブを、プラットフォームが復旧している間 `true` になります。 |

**`error_hint`** には、2 種類あります。まず `kind` で絞り込んでから、残りを読んでください。

- **`safety-block`**：モデル自身の安全フィルターが、出力を拒否しました。`class` は `copyright`、`likeness`、`safety` のいずれかです。`retried` は、Nodaro がすでに 1 回再試行したかどうかを示し、`suggestedProvider` は、存在する場合、同じリクエストを再試行できるモデルの名前を示します。[モデルがプロンプトをブロックした場合](https://nodaro.ai/docs/nodes/image/generate-image#when-a-model-blocks-the-prompt)を参照してください。
- **`policy-block`**：このデプロイ環境のコンテンツポリシーが、リクエストまたは結果を拒否しました。`reason` はユーザー向けに書かれているため、そのまま表示してください。

### jobs.list(params?)
自分のジョブを、新しい順にページ単位で一覧表示します（`GET /v1/jobs`）。

```ts
list(params?: { type?: string; origin?: string; limit?: number; cursor?: string }): Promise<{
data: Job[]
next: string | null
}>
```

<TypeTable
type={{
type: { type: 'string', description: "llm-structured や video-analysis など、このルートが作成したジョブだけに絞ります。完全一致です。" },
origin: { type: 'string', description: "リクエストにこの origin の値が含まれるジョブだけに絞ります。ジョブを送ったアプリの名前です。完全一致です。" },
limit: { type: 'number', default: '50', description: "ページのサイズで、1〜100 です。" },
cursor: { type: 'string', description: "前のページの next の値です。" },
}}
/>

```ts
let cursor: string | undefined
do {
const page = await client.jobs.list({ type: "llm-structured", origin: "my-app", cursor })
for (const job of page.data) console.log(job.id, job.status)
cursor = page.next ?? undefined
} while (cursor)
```

ページに含まれる行は `limit` より少ないことがあり、0 行でも `next` が付くことがあります。行数ではなく、`next` を基準にページを進めてください。

### jobs.getStatus(id)
ジョブのステータスだけを読み取ります。`id`、`status`、`progress`、`output_data`、`error_message`、`error_hint`、`credit_status` です（`GET /v1/jobs/:id/status`）。リクエストのデータと料金のフィールドを省くため、`get()` よりもずっと軽量です。ポーリングのループで使ってください。

```ts
getStatus(id: string): Promise<{ data: JobStatusResult }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ジョブの id です。" },
}}
/>

```ts
async function waitForJob(jobId: string) {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") return data.output_data
if (data.status === "failed" || data.status === "cancelled") {
throw new Error(data.error_message ?? data.status)
}
await new Promise((resolve) => setTimeout(resolve, 2_000))
}
}
```

[`client.nodes.runAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes#runandwaittype-params-opts) は、進行状況、キャンセル、型付きエラーとともに、このループを代わりに実行します。

### jobs.cancel(id)
ジョブをキャンセルし、確保されていたクレジットを返還します。レビューのために保留されているジョブも、キャンセルできます。

```ts
cancel(id: string): Promise<{ success: true; cancelled: number }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ジョブの id です。" },
}}
/>

```ts
const { cancelled } = await client.jobs.cancel(jobId)
```

### jobs.delete(id)
ジョブと、それが生成した非公開のメディアを削除します（`DELETE /v1/jobs/:id`）。削除できるのは、ジョブの所有者だけです。実行中のジョブはそのまま削除されるため、ワーカーを止めたい場合は、先にキャンセルしてください。

```ts
delete(id: string): Promise<{ success: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ジョブの id です。" },
}}
/>

```ts
await client.jobs.cancel(jobId)
await client.jobs.delete(jobId)
```

## client.videoPro
長い動画をセグメントごとにレンダリングするノードである[動画生成 Pro](https://nodaro.ai/docs/nodes/video/generate-video-pro) の実行制御です。Nodaro Cloud 上で動作します。ほかのノードの実行と同じように実行を開始し、そのジョブに対してこれらのメソッドを使います。

### videoPro.stop(jobId)
実行中の動画生成 Pro のジョブを、安全に停止します。処理中のセグメントは破棄されますが、料金は請求され、残りのセグメントはスキップされます。完成したセグメントは、ジョブの最終的な動画としてつなぎ合わされ、使われなかった確保分は返還されます。まだ開始していないジョブは、全額返還のうえキャンセルされます。

```ts
stop(jobId: string): Promise<StopVideoProResult>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "動画生成 Pro のジョブの id です。" },
}}
/>

```ts
await client.videoPro.stop(jobId)
const { data } = await client.jobs.getStatus(jobId) // becomes completed, with the partial video
```

ジョブのポーリングを続けてください。`output_data.pro.stopped` が `true` に設定された状態で完了します。

### videoPro.continueRun(jobId, opts?)
停止、失敗、完了のいずれかの実行を、**新しいジョブ**として継続します。`fromSegment` より前のセグメントは再利用され、それ以降のすべてのセグメントが再生成されます。支払うのは、再生成されるセグメントの分と、Pro の固定料金だけです。

```ts
continueRun(jobId: string, opts?: { fromSegment?: number }): Promise<ContinueVideoProResult>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "継続するジョブです。" },
fromSegment: { type: 'number', description: "再生成する最初のセグメントで、1 から数えます。デフォルトは、納品されなかった最初のセグメントです。" },
}}
/>

```ts
const { jobId: newJobId, fromSegment } = await client.videoPro.continueRun(jobId, { fromSegment: 4 })
```

結果には、ポーリングする新しい `jobId` が含まれ、`continuedFromJobId`、`fromSegment`、`segmentCount`、`deduped` が含まれることもあります。

## Frequently asked questions

### ジョブと実行は、どう違うのですか？

ジョブは、1 枚の画像や 1 本の動画など、生成 1 件を指します。実行は、ワークフロー全体を 1 回実行したもので、AI ノードごとの 1 つのジョブと、すべてのノードの状態をまとめたものです。

### ポーリングのループは、どのメソッドを呼び出せばよいですか？

client.jobs.getStatus(jobId) です。ステータス、進行状況、出力、エラーだけを返すため、client.jobs.get よりもずっと軽量です。

### ジョブをキャンセルすると、クレジットは返還されますか？

はい。client.jobs.cancel は、ジョブを止め、確保されていたクレジットを返還します。実行を即座にキャンセルした場合も、実行中の各ジョブに対して同じことが行われます。

### pending_review というステータスは、どういう意味ですか？

このデプロイ環境のコンテンツポリシーが、人によるレビューのために結果を保留していることを意味します。終了状態ではありません。ジョブは後で completed、failed、cancelled のいずれかになります。待ち続けて、リクエストを再実行しないでください。
