Files
Stillfront-comfyui-sf-nodes/py/wavespeed_api/v3.py
T
Ivor StrelarandClaude Opus 5.5 9212fa484e feat: add SF WaveSpeed GPT Image 2.5 node
Mirrors ComfyUI's "OpenAI GPT Image 2.5" partner node but bills a WaveSpeed
key via SF WaveSpeed Client. Built on the same V3 components the partner node
uses: a DynamicCombo model picker whose options carry their own controls, and
Autogrow reference-image slots, so connecting images switches to WaveSpeed's
/edit endpoint and leaving them empty uses /text-to-image.

Follows WaveSpeed's actual schemas rather than OpenAI's: gpt-image-1 is
dropped (not offered), 2 and 2.5 take aspect_ratio plus a 1k/2k/4k resolution
instead of pixel sizes, background exists only on 1.5, and 1.5 caps
references at 10. There is no mask input, as no WaveSpeed edit endpoint
accepts one. WaveSpeed has no `n`, so n requests are submitted together and
returned as one batch.

Copies the partner node's behaviour where it applies: references are
downscaled to 2048x2048 pixels before upload, batches are flattened, and
outputs keep alpha when a transparent background was produced.

Also moves v3 result polling and the 401 key-source message into a shared
wavespeed_api/v3.py, and switches the Tripo P2 node to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:10:38 +02:00

48 lines
1.8 KiB
Python

# ABOUTME: Shared helpers for nodes that call WaveSpeed's v3 prediction API directly
# ABOUTME: Result polling with interrupt support, and readable errors for a rejected API key
import time
RESULT_PATH = "/api/v3/predictions/{id}/result"
TERMINAL_FAILURES = {"failed", "cancelled", "timeout", "deleted"}
def check_interrupted():
"""Raise if the user pressed Cancel in ComfyUI; a no-op outside ComfyUI."""
try:
import comfy.model_management as mm
except ImportError:
return
mm.throw_exception_if_processing_interrupted()
def wait_for_result(api, task_id, timeout_seconds, label, interval=2):
"""Poll a prediction until it completes. Returns the result dict."""
started = time.time()
while True:
check_interrupted()
result = api.get(RESULT_PATH.format(id=task_id))
status = result.get("status")
if status == "completed":
return result
if status in TERMINAL_FAILURES:
raise RuntimeError(f"{label} task {status}: {result.get('error') or result}")
if time.time() - started > timeout_seconds:
raise RuntimeError(f"{label} task {task_id} still '{status}' after {timeout_seconds}s.")
time.sleep(interval)
def call(client, action):
"""Run an API call, turning a 401 into an error that says which key was used."""
try:
return action()
except Exception as e:
if "401" in str(e) or "Unauthorized" in str(e):
source = client.get("source", "the SF WaveSpeed Client node")
raise RuntimeError(
f"WaveSpeed rejected the API key, which came from {source}. "
f"Check it on wavespeed.ai under API Keys, or clear the api_key field "
f"on SF WaveSpeed Client to fall back to a key saved elsewhere."
) from e
raise