comfyui: satisfy provider contract review items

This commit is contained in:
calesthio
2026-04-23 17:49:04 -07:00
committed by Alastair Beal
parent 4c62186c95
commit 7c4bb08890
9 changed files with 1152 additions and 71 deletions
+15 -4
View File
@@ -140,10 +140,15 @@ class ComfyUIClient:
json={"prompt": workflow},
timeout=30,
)
resp.raise_for_status()
data = resp.json()
try:
data = resp.json()
except ValueError:
data = {}
if data.get("node_errors"):
raise ComfyUIError(f"Node errors: {json.dumps(data['node_errors'])}")
if data.get("error"):
raise ComfyUIError(f"Prompt error: {json.dumps(data['error'])}")
resp.raise_for_status()
prompt_id = data.get("prompt_id")
if not prompt_id:
raise ComfyUIError(f"No prompt_id in response: {data}")
@@ -181,6 +186,7 @@ class ComfyUIClient:
filename: str,
subfolder: str,
dest: Path,
folder_type: str = "output",
) -> Path:
"""Download an output artifact from the ComfyUI server."""
resp = requests.get(
@@ -188,7 +194,7 @@ class ComfyUIClient:
params={
"filename": filename,
"subfolder": subfolder,
"type": "output",
"type": folder_type,
},
timeout=120,
)
@@ -246,7 +252,12 @@ class ComfyUIClient:
target = dest
else:
target = dest.with_stem(f"{dest.stem}_{i:03d}").with_suffix(suffix)
self.download(item["filename"], item.get("subfolder", ""), target)
self.download(
item["filename"],
item.get("subfolder", ""),
target,
item.get("type", "output"),
)
paths.append(target)
return paths
+222
View File
@@ -0,0 +1,222 @@
"""Shared metadata helpers for ComfyUI provider tools."""
from __future__ import annotations
import hashlib
import json
from typing import Any
COMFYUI_SETUP_OFFER: dict[str, Any] = {
"kind": "local_server",
"fix_complexity": "1-minute env-var if ComfyUI is already running; otherwise local install",
"env_var": "COMFYUI_SERVER_URL",
"default_url": "http://localhost:8188",
"health_check": "GET /system_stats",
"what_it_unlocks": [
"free local image generation through ComfyUI workflows",
"free local video generation through ComfyUI workflows",
"community workflow_json/workflow_path execution",
],
}
BUNDLED_MODEL_STACKS: dict[str, list[dict[str, Any]]] = {
"flux2-txt2img": [
{
"role": "diffusion_model",
"name": "flux2-dev-nvfp4.safetensors",
"quantization": "NVFP4",
"destination_hint": "ComfyUI/models/diffusion_models/",
"download_url": (
"https://huggingface.co/black-forest-labs/FLUX.2-dev-NVFP4"
),
},
{
"role": "text_encoder",
"name": "mistral_3_small_flux2_fp4_mixed.safetensors",
"quantization": "FP4 mixed",
"destination_hint": "ComfyUI/models/text_encoders/",
"download_url": (
"https://huggingface.co/Comfy-Org/flux2-dev/tree/main/"
"split_files/text_encoders"
),
},
{
"role": "vae",
"name": "flux2-vae.safetensors",
"destination_hint": "ComfyUI/models/vae/",
"download_url": (
"https://huggingface.co/Comfy-Org/flux2-dev/blob/main/"
"split_files/vae/flux2-vae.safetensors"
),
},
],
"wan22-t2v-4step": [
{
"role": "text_encoder",
"name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/text_encoders/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/text_encoders"
),
},
{
"role": "diffusion_model_high_noise",
"name": "wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/diffusion_models/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"blob/main/split_files/diffusion_models/"
"wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors"
),
},
{
"role": "diffusion_model_low_noise",
"name": "wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/diffusion_models/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/diffusion_models"
),
},
{
"role": "vae",
"name": "wan2.2_vae.safetensors",
"destination_hint": "ComfyUI/models/vae/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/vae"
),
},
{
"role": "lora",
"name": "wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise.safetensors",
"strength_model": 1.0,
"destination_hint": "ComfyUI/models/loras/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/loras"
),
},
{
"role": "lora",
"name": "wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise.safetensors",
"strength_model": 1.0,
"destination_hint": "ComfyUI/models/loras/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/loras"
),
},
],
"wan22-i2v-4step": [
{
"role": "text_encoder",
"name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/text_encoders/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/text_encoders"
),
},
{
"role": "diffusion_model_high_noise",
"name": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/diffusion_models/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"blob/main/split_files/diffusion_models/"
"wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors"
),
},
{
"role": "diffusion_model_low_noise",
"name": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
"quantization": "FP8",
"destination_hint": "ComfyUI/models/diffusion_models/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/diffusion_models"
),
},
{
"role": "vae",
"name": "wan_2.1_vae.safetensors",
"destination_hint": "ComfyUI/models/vae/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/"
"tree/main/split_files/vae"
),
},
{
"role": "lora",
"name": "wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors",
"strength_model": 1.0,
"destination_hint": "ComfyUI/models/loras/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/loras"
),
},
{
"role": "lora",
"name": "wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors",
"strength_model": 1.0,
"destination_hint": "ComfyUI/models/loras/",
"download_url": (
"https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/"
"tree/main/split_files/loras"
),
},
],
}
def workflow_hash(workflow: dict[str, Any]) -> str:
"""Return a stable hash of the final workflow JSON submitted to ComfyUI."""
payload = json.dumps(workflow, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def model_stack(workflow_key: str | None, inputs: dict[str, Any]) -> list[dict[str, Any]]:
"""Return bundled or caller-supplied model stack metadata."""
if workflow_key:
return [dict(item) for item in BUNDLED_MODEL_STACKS[workflow_key]]
stack = inputs.get("workflow_model_stack")
return stack if isinstance(stack, list) else []
def missing_models_payload(
missing: list[str],
*,
workflow_key: str,
workflow_name: str,
operation: str | None = None,
) -> dict[str, Any]:
"""Build a machine-readable missing-model error payload."""
stack_by_name = {
item["name"]: item for item in BUNDLED_MODEL_STACKS.get(workflow_key, [])
}
items = []
for name in missing:
meta = dict(stack_by_name.get(name, {}))
meta.setdefault("name", name)
meta.setdefault("role", "unknown")
meta.setdefault("destination_hint", "ComfyUI/models/ matching the workflow node")
meta.setdefault("download_url", None)
items.append(meta)
return {
"provider": "comfyui",
"workflow": workflow_name,
"operation": operation,
"missing_models": items,
"setup_offer": COMFYUI_SETUP_OFFER,
}