AI活用

ComfyUIをTypeScriptから実行する|公式SDKでWorkflowを呼び出す方法

ComfyUIをTypeScriptから動かすには、公式SDK「@comfyorg/sdk」(Comfy API v2用、Beta 0.1.x)を使い、ローカルはcomfy-api-proxy経由で接続します。最小プロジェクト、Workflowと入力値の分離、型付きの結果取得、失敗とTimeoutの区別、SDK更新に強いラッパー層の作り方を解説します。

この記事の目次
  1. 結論:公式SDKは実在するが Beta。ラッパー層を挟んで使う
  2. TypeScript連携でSDKを使う理由は何か
  3. SDKの現行状態(2026年8月22日確認)
  4. 最小プロジェクトをどう作るか
  5. Workflowと入力値をどう分けて渡すか
  6. 結果をどう型付きで扱うか
  7. 再現実験:最小Workflowを2回実行して保存する
  8. 失敗とTimeoutをどう区別するか
  9. SDKの更新に強い構成をどう作るか
  10. よくある質問
  11. ブラウザから直接@comfyorg/sdkを使えますか?
  12. プロキシを立てずにSDKで8188に直接つなげますか?
  13. Comfy Cloudに移すときコードはどれくらい変わりますか?
  14. まとめ

ComfyUIをTypeScriptから動かすには、Comfy-Orgが公開している公式SDK「@comfyorg/sdk」を使い、ローカルのComfyUIにはcomfy-api-proxy経由で接続します。2026年8月22日時点でSDKはBeta(0.1.x)で、APIの形は今後変わり得ると公式が明記しています。最小プロジェクトから、入力値の分離、型付きの結果取得、失敗の区別、更新で壊れにくいラッパー層までを順に作ります。

情報確認日:2026年8月22日(日本時間)

結論:公式SDKは実在するが Beta。ラッパー層を挟んで使う

責任範囲

  • 公式TypeScript SDKの現行状態(npm名・版・要件・Beta)を確認した結果を示す
  • Node 22以上でSDKを入れ、ローカルComfyUIにプロキシ経由でつなぐ最小プロジェクトを作る
  • Workflow定義と可変値(Prompt・Seed)を分け、結果を型付きで受け取る
  • Queue失敗・Network失敗・Timeoutを分けて扱い、UIからSDKを直接触らない層を設計する
  • API経路の全体像とServer APIの直接呼び出しはComfyUI APIの使い方に任せる
スポンサーリンク

TypeScript連携でSDKを使う理由は何か

ComfyUIはSDKがなくてもfetchで動かせます。それでもSDKを選ぶ理由は、入力画像の重複アップロード回避、Queue満杯時の再試行、完了判定のポーリング、出力のダウンロード、エラーの型分けといった周辺の面倒をSDKが引き受けるからです。生fetchはServer API(8188)に追加プロセスなしで直接つなげる代わりに、互換性の約束がなく、完了判定もエラー判定も自分のコードになります。

SDKの現行状態(2026年8月22日確認)

npmに@comfyorg/sdkが存在し、最新は0.1.7(2026年8月13日公開)、リポジトリはComfy-Org配下のcomfy-typescript-sdkです。公式ドキュメントは「Beta。0.1.xの間はAPIの形が変わり得る」としています。「公式SDKはあるが、来年も同じコードが動く保証はない」状態で、これが最後の節のラッパー層の理由です。

SDKが包んでいるのはAPI v2:SDKはServer API(8188)を直接呼びません。ローカルで使うときはcomfy-api-proxyをComfyUIの前に立て、SDKはそのプロキシ(8189)と話します。8188と8189は別物です。

最小プロジェクトをどう作るか

固定するのはNodeのバージョン、SDKの版、接続先の環境変数の三つです。SDKはNode 22以上を要求し、COMFY_BASE_URLが未設定だとComfy Cloudに向くので、ローカルでは必ず設定します。

# 1. ローカルComfyUI(127.0.0.1:8188)を起動済みの前提
# 2. プロキシを入れて起動する(別ターミナルで常駐)
pip install comfy-api-proxy
comfy-api-proxy            # 既定で http://127.0.0.1:8189 が開く

# 3. TypeScriptプロジェクト
mkdir comfy-ts && cd comfy-ts
npm init -y
npm i @comfyorg/sdk@0.1.7  # 確認日時点の版に固定
npm i -D typescript tsx @types/node
npx tsc --init
# .env の代わりにシェルで設定(ローカルプロキシ向け)
export COMFY_BASE_URL="http://127.0.0.1:8189"

セルフホストではapiKeyを渡しません。READMEが「渡さないこと」と明記しており、キーが必要なのはComfy Cloudとserverlessだけです。

// src/hello.ts — 接続確認だけを行う
import { Comfy } from "@comfyorg/sdk";

const client = new Comfy(); // COMFY_BASE_URL を読む。ローカルでは apiKey なし
const wf = await client.workflows.fromFile("workflow_api.json");
const job = await client.run(wf); // 送信して完了までポーリング
console.log(job.status, job.outputs.length);

workflow_api.jsonは現行UI(2026年8月時点)のFile → Export Workflow (API)で書き出したものです。GUI保存用JSONを渡すとWorkflowFormatUiエラーになります。

Workflowと入力値をどう分けて渡すか

Workflow定義(JSON)は固定し、変わる値だけをコードから差し込みます。SDKにはwf.setInput(nodeId, inputName, value)があり、ノードのキーと入力名を指定して値を上書きします。

// src/generate.ts
import { Comfy } from "@comfyorg/sdk";

type GenerateParams = {
  prompt: string;
  seed: number;
};

// 自分の workflow_api.json に合わせて変える箇所はここだけ
const NODE = {
  positive: "6",  // CLIPTextEncode(正のPrompt)
  sampler: "3",   // KSampler
  save: "9",      // SaveImage
} as const;

export async function generate(params: GenerateParams) {
  const client = new Comfy();
  const wf = await client.workflows.fromFile("workflow_api.json");

  wf.setInput(NODE.positive, "text", params.prompt);
  wf.setInput(NODE.sampler, "seed", params.seed);

  const job = await client.run(wf, { timeoutMs: 120_000 });
  return job.getOutputs(NODE.save);
}

ノードのキー("6"など)は書き出したJSONごとに違い、入力名(textseed)は各ノードのinputsにあるキーそのものです。迷ったらJSONでclass_typeを探し、そのinputsの名前を使います。

入力画像も同じ形で、client.assets.fromFile("photo.png")のハンドルをsetInputに渡します。

結果をどう型付きで扱うか

run()が返すjobは、statusoutputsを持ちます。outputsは出力ハンドルの配列で、getOutputs(nodeId)でSaveImageなど特定ノードの分に絞れます。各ハンドルはtoFiletoBytesgetDownloadUrlと、namejobIdを持ちます。seedやpromptは入っていないので、再現に必要な値は自分のコードで出力と一緒に記録します。

再現実験:最小Workflowを2回実行して保存する

Seedを固定して「同じ入力なら同じ出力になるか」を確かめます。結果は環境ごとに違うため、手順と記録表だけを示します。

  1. GUIで動くWorkflowをFile → Export Workflow (API)で書き出す
  2. 上のgenerateを、同じpromptと同じseedで2回呼ぶ
  3. 2回ともjob.statussucceededになり、out/に画像が保存されることを確認する
  4. 2枚の画像のハッシュ(shasum -a 256など)を比較する
prompt seed job.status 保存ファイル sha256(先頭8桁) 所要時間
1
2

ハッシュが一致すればSDK経由でも決定論的に生成できています。一致しないときは、seed以外に毎回変わる入力がないかを先に疑います。

失敗とTimeoutをどう区別するか

「動かない」には、Queueに入らなかった、実行で失敗した、ネットワークが切れた、待ちきれなかった、の違いがあります。SDKは型付きエラーで分けているのでinstanceofで振り分けます。

状況 SDKが投げるもの 取るべき行動
JSONがUI形式だった WorkflowFormatUiInvalidWorkflowの一種) 書き出し直す。再試行しても直らない
Queueが満杯 QueueFullsubmit()は自動で再試行) 自動再試行の後も出るなら間隔を空ける
実行中にノードが失敗 JobFailederr.errorにnode_id・class_type) 該当ノードをGUIで確認。同じ入力で再試行しない
待ち時間超過 run({ timeoutMs })の期限、またはAbortSignal jobは続いている可能性がある。idで状態を確認する
プロキシに届かない SDK外の接続エラー(ComfyErrorではない) プロキシとComfyUIの起動を確認

Timeoutは「待つのをやめた」だけで、サーバー側のjobは続いているかもしれません。時間制限を付けるならsubmit()job.idを先に得て、期限後に状態を見直すかキャンセルするかを決めます。submitwaitcancelAbortSignalを受け取ります。

SDKの更新に強い構成をどう作るか

SDKがBetaである以上、メソッド名が変わる前提で設計します。方針は、@comfyorg/sdkimportするファイルをsrc/comfy/client.ts一つに限り、UIやユースケースはそのファイルが公開する自分の型だけを見る分離です。

// src/comfy/types.ts — アプリが依存してよい型はこれだけ
export type GenerateRequest = { prompt: string; seed: number };
export type GenerateResult =
  | { ok: true; files: string[]; jobId: string }
  | { ok: false; reason: "invalid_workflow" | "job_failed" | "timeout" | "unavailable"; detail?: string };

// src/comfy/client.ts — SDKの名前が変わっても直すのはこのファイル
import { Comfy, JobFailed, InvalidWorkflow } from "@comfyorg/sdk";
import type { GenerateRequest, GenerateResult } from "./types.js";

export async function generateImage(req: GenerateRequest): Promise<GenerateResult> {
  try {
    const client = new Comfy();
    const wf = await client.workflows.fromFile("workflow_api.json");
    wf.setInput("6", "text", req.prompt);
    wf.setInput("3", "seed", req.seed);
    const job = await client.run(wf, { timeoutMs: 120_000 });
    const files: string[] = [];
    for (const [i, out] of job.getOutputs("9").entries()) {
      const path = `out/${job.id}-${i}.png`;
      await out.toFile(path);
      files.push(path);
    }
    return { ok: true, files, jobId: job.id };
  } catch (err) {
    if (err instanceof InvalidWorkflow) return { ok: false, reason: "invalid_workflow" };
    if (err instanceof JobFailed) return { ok: false, reason: "job_failed", detail: err.error?.message };
    return { ok: false, reason: "unavailable", detail: String(err) };
  }
}

SDKを上げて名前が変わっても、直すのはclient.tsの数行です。例外を自分のreasonに変換するのも同じ理由です。

進捗を画面に出すためのjob.events()(SSE)もありますが、READMEは「再接続時に見逃した分は再送されないので完了判定はポーリングが正」としています。進捗表示の設計は別記事で扱います。

よくある質問

ブラウザから直接@comfyorg/sdkを使えますか?

READMEは「ブラウザ対応はv1の範囲外」としています。Node側で使い、ブラウザには自分のAPI経由で結果を渡してください。

プロキシを立てずにSDKで8188に直接つなげますか?

つなげません。SDKはAPI v2の形で話し、Server API(8188)はその形を持たないため、ローカルではcomfy-api-proxyが必要です。プロキシを増やしたくないなら、Server APIをfetchで呼ぶ選択になります。

Comfy Cloudに移すときコードはどれくらい変わりますか?

COMFY_BASE_URLを外し、new Comfy({ apiKey })でキーを渡す差分です。課金や保存期間は移す前に公式で確認してください。

まとめ

TypeScriptからComfyUIを動かす公式SDKは@comfyorg/sdkとして実在し、ローカルではcomfy-api-proxyを挟んで使います。Workflow JSONは固定し、PromptやseedはsetInputで差し込み、出力はgetOutputsから型付きで受け取ります。

SDKはBetaなので、版を固定し、SDKを触るファイルを一つに限り、エラーを自分の型に変換する層を作ってください。API経路の選び方と、SDKを使わないServer APIの最小実行はComfyUI APIの使い方を参照してください。

スポンサーリンク