From c93dc34a5ea1a5f8db1f871561622a879ca702b2 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 2 Apr 2026 15:12:45 +0530 Subject: [PATCH 1/5] Added support for Wan2.7 --- runware/types.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/runware/types.py b/runware/types.py index d1fdf3a..4dca6d4 100644 --- a/runware/types.py +++ b/runware/types.py @@ -810,6 +810,12 @@ def request_key(self) -> str: return "texSlat" +@dataclass +class IColorPaletteEntry(SerializableMixin): + hex: str + ratio: Optional[Union[str, float]] = None + + @dataclass class ISettings(SerializableMixin): # Image @@ -820,6 +826,10 @@ class ISettings(SerializableMixin): trueCFGScale: Optional[float] = None quality: Optional[str] = None promptExtend: Optional[bool] = None + editRegions: Optional[List[List[List[int]]]] = None + sequential: Optional[bool] = None + thinking: Optional[bool] = None + colorPalette: Optional[List[Union[IColorPaletteEntry, Dict[str, Any]]]] = None # 3D inference textureSize: Optional[int] = None decimationTarget: Optional[int] = None @@ -859,6 +869,11 @@ def __post_init__(self): self.shapeSlat = IShapeSlat(**self.shapeSlat) if self.texSlat is not None and isinstance(self.texSlat, dict): self.texSlat = ITexSlat(**self.texSlat) + if self.colorPalette is not None: + self.colorPalette = [ + IColorPaletteEntry(**item) if isinstance(item, dict) else item + for item in self.colorPalette + ] @property def request_key(self) -> str: @@ -891,6 +906,7 @@ class IInputs(SerializableMixin): references: Optional[List[Union[str, File]]] = None referenceImages: Optional[List[Union[str, File, IInputReference]]] = None image: Optional[Union[str, File]] = None + images: Optional[List[Union[str, File]]] = None mask: Optional[Union[str, File]] = None superResolutionReferences: Optional[List[Union[str, File]]] = None From 4fb8e39871c0a7707bba1c9b16b55c9344ff661b Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 2 Apr 2026 17:43:29 +0530 Subject: [PATCH 2/5] Fixed processing local images --- runware/base.py | 8 ++++++++ runware/utils.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/runware/base.py b/runware/base.py index b060f80..eca3de0 100644 --- a/runware/base.py +++ b/runware/base.py @@ -744,6 +744,14 @@ async def _imageInference( if isinstance(requestImage.inputs, dict): requestImage.inputs = IInputs(**requestImage.inputs) + if requestImage.inputs.image: + requestImage.inputs.image = await process_image(requestImage.inputs.image) + + if requestImage.inputs.images: + requestImage.inputs.images = await self._process_media_list( + requestImage.inputs.images + ) + if requestImage.inputs.referenceImages: requestImage.inputs.referenceImages = await self._process_media_list( requestImage.inputs.referenceImages, diff --git a/runware/utils.py b/runware/utils.py index 55661d2..34e017e 100644 --- a/runware/utils.py +++ b/runware/utils.py @@ -948,7 +948,11 @@ def instantiateDataclassList( return instances -def isLocalFile(file): +def isLocalFile(file: Union[str, File]) -> bool: + if isinstance(file, File): + return True + if not isinstance(file, str): + raise TypeError(f"Expected str or File, got {type(file).__name__}") if os.path.isfile(file): return True @@ -985,7 +989,7 @@ def isLocalFile(file): async def process_image( - image: Optional[Union[str, list, UploadImageType | None | File]], + image: Optional[Union[str, File, list, UploadImageType]], ) -> None | list[Any] | str: if image is None: return None @@ -996,6 +1000,8 @@ async def process_image( return images elif isinstance(image, UploadImageType): return image.imageUUID + if isinstance(image, File): + return await fileToBase64(image) if isLocalFile(image) and not image.startswith("http"): return await fileToBase64(image) return image \ No newline at end of file From 3a17c96fec7b9318b32bc8472dcfb72fb6fdb476 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 2 Apr 2026 17:48:28 +0530 Subject: [PATCH 3/5] Reverted isLocalFile, process_image to original version --- runware/types.py | 7 ++++++- runware/utils.py | 10 ++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/runware/types.py b/runware/types.py index 4dca6d4..9a1571e 100644 --- a/runware/types.py +++ b/runware/types.py @@ -118,6 +118,11 @@ class OperationState(Enum): IOutputType = Literal["base64Data", "dataURI", "URL"] IOutputFormat = Literal["JPG", "PNG", "WEBP", "SVG"] IAudioOutputFormat = Literal["wav", "mp3", "pcm", "opus", "aac", "flac", "MP3"] +# Image edit region types: +# IEditRegionBox: [x1, y1, x2, y2] absolute pixel rectangle +# IEditRegions: list of images -> list of boxes per image +IEditRegionBox = List[int] +IEditRegions = List[List[IEditRegionBox]] @dataclass @@ -826,7 +831,7 @@ class ISettings(SerializableMixin): trueCFGScale: Optional[float] = None quality: Optional[str] = None promptExtend: Optional[bool] = None - editRegions: Optional[List[List[List[int]]]] = None + editRegions: Optional[IEditRegions] = None sequential: Optional[bool] = None thinking: Optional[bool] = None colorPalette: Optional[List[Union[IColorPaletteEntry, Dict[str, Any]]]] = None diff --git a/runware/utils.py b/runware/utils.py index 34e017e..55661d2 100644 --- a/runware/utils.py +++ b/runware/utils.py @@ -948,11 +948,7 @@ def instantiateDataclassList( return instances -def isLocalFile(file: Union[str, File]) -> bool: - if isinstance(file, File): - return True - if not isinstance(file, str): - raise TypeError(f"Expected str or File, got {type(file).__name__}") +def isLocalFile(file): if os.path.isfile(file): return True @@ -989,7 +985,7 @@ def isLocalFile(file: Union[str, File]) -> bool: async def process_image( - image: Optional[Union[str, File, list, UploadImageType]], + image: Optional[Union[str, list, UploadImageType | None | File]], ) -> None | list[Any] | str: if image is None: return None @@ -1000,8 +996,6 @@ async def process_image( return images elif isinstance(image, UploadImageType): return image.imageUUID - if isinstance(image, File): - return await fileToBase64(image) if isLocalFile(image) and not image.startswith("http"): return await fileToBase64(image) return image \ No newline at end of file From e52f6a9ae38d16cd8cc1f12b8413a3c197162a7d Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 2 Apr 2026 18:59:04 +0530 Subject: [PATCH 4/5] Fixed validations --- runware/types.py | 23 +++++++++++++++++------ runware/utils.py | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/runware/types.py b/runware/types.py index 9a1571e..e5c087d 100644 --- a/runware/types.py +++ b/runware/types.py @@ -118,11 +118,6 @@ class OperationState(Enum): IOutputType = Literal["base64Data", "dataURI", "URL"] IOutputFormat = Literal["JPG", "PNG", "WEBP", "SVG"] IAudioOutputFormat = Literal["wav", "mp3", "pcm", "opus", "aac", "flac", "MP3"] -# Image edit region types: -# IEditRegionBox: [x1, y1, x2, y2] absolute pixel rectangle -# IEditRegions: list of images -> list of boxes per image -IEditRegionBox = List[int] -IEditRegions = List[List[IEditRegionBox]] @dataclass @@ -821,6 +816,14 @@ class IColorPaletteEntry(SerializableMixin): ratio: Optional[Union[str, float]] = None +@dataclass +class IEditRegion(SerializableMixin): + x1: int + y1: int + x2: int + y2: int + + @dataclass class ISettings(SerializableMixin): # Image @@ -831,7 +834,7 @@ class ISettings(SerializableMixin): trueCFGScale: Optional[float] = None quality: Optional[str] = None promptExtend: Optional[bool] = None - editRegions: Optional[IEditRegions] = None + editRegions: Optional[List[List[Union[IEditRegion, Dict[str, Any]]]]] = None sequential: Optional[bool] = None thinking: Optional[bool] = None colorPalette: Optional[List[Union[IColorPaletteEntry, Dict[str, Any]]]] = None @@ -874,6 +877,14 @@ def __post_init__(self): self.shapeSlat = IShapeSlat(**self.shapeSlat) if self.texSlat is not None and isinstance(self.texSlat, dict): self.texSlat = ITexSlat(**self.texSlat) + if self.editRegions is not None: + self.editRegions = [ + [ + IEditRegion(**item) if isinstance(item, dict) else item + for item in image_regions + ] + for image_regions in self.editRegions + ] if self.colorPalette is not None: self.colorPalette = [ IColorPaletteEntry(**item) if isinstance(item, dict) else item diff --git a/runware/utils.py b/runware/utils.py index 55661d2..d238e30 100644 --- a/runware/utils.py +++ b/runware/utils.py @@ -39,7 +39,7 @@ mimetypes.add_type("image/webp", ".webp") BASE_RUNWARE_URLS = { - Environment.PRODUCTION: "wss://ws-api.runware.ai/v1", + Environment.PRODUCTION: "wss://ws-api.runware.dev/v1", Environment.TEST: "ws://localhost:8080", } From aa1fe7402d984634259ba866a5ff3dd4e679ae17 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Thu, 2 Apr 2026 18:59:36 +0530 Subject: [PATCH 5/5] Removed debugging lines --- runware/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runware/utils.py b/runware/utils.py index d238e30..55661d2 100644 --- a/runware/utils.py +++ b/runware/utils.py @@ -39,7 +39,7 @@ mimetypes.add_type("image/webp", ".webp") BASE_RUNWARE_URLS = { - Environment.PRODUCTION: "wss://ws-api.runware.dev/v1", + Environment.PRODUCTION: "wss://ws-api.runware.ai/v1", Environment.TEST: "ws://localhost:8080", }