mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 08:52:45 +00:00
197 lines
6.3 KiB
Python
197 lines
6.3 KiB
Python
import json
|
|
import re
|
|
from dataclasses import asdict, dataclass, field
|
|
from typing import Any, Dict, List, Optional, Union
|
|
|
|
if __package__ and "." in __package__:
|
|
from ..services.request_contracts import (
|
|
MAX_BODY_SIZE,
|
|
MAX_INPUT_STRING_LENGTH,
|
|
MAX_JOB_ID_LENGTH,
|
|
MAX_PROFILE_ID_LENGTH,
|
|
MAX_TEMPLATE_ID_LENGTH,
|
|
MAX_TRACE_ID_LENGTH,
|
|
SCHEMA_VERSION,
|
|
WEBHOOK_JOB_REQUEST_CONTRACT,
|
|
)
|
|
else: # pragma: no cover - top-level test import mode
|
|
from services.request_contracts import ( # type: ignore
|
|
MAX_BODY_SIZE,
|
|
MAX_INPUT_STRING_LENGTH,
|
|
MAX_JOB_ID_LENGTH,
|
|
MAX_PROFILE_ID_LENGTH,
|
|
MAX_TEMPLATE_ID_LENGTH,
|
|
MAX_TRACE_ID_LENGTH,
|
|
SCHEMA_VERSION,
|
|
WEBHOOK_JOB_REQUEST_CONTRACT,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Profile:
|
|
"""
|
|
Defines a generation profile (e.g., SDXL-v1, Flux-Dev).
|
|
This acts as a preset identifier for the planner.
|
|
"""
|
|
|
|
id: str
|
|
version: str
|
|
label: str
|
|
description: Optional[str] = None
|
|
model_config_data: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> "Profile":
|
|
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
|
|
|
|
@dataclass
|
|
class GenerationParams:
|
|
"""
|
|
The concrete generation parameters produced by the planner.
|
|
"""
|
|
|
|
width: int = 1024
|
|
height: int = 1024
|
|
steps: int = 20
|
|
cfg: float = 7.0
|
|
sampler_name: str = "euler"
|
|
scheduler: str = "normal"
|
|
seed: Optional[int] = None
|
|
extra: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
def __post_init__(self):
|
|
# Validation / Clamping logic
|
|
# Clamp ranges
|
|
self.width = max(256, min(4096, self.width))
|
|
self.height = max(256, min(4096, self.height))
|
|
self.steps = max(1, min(100, self.steps))
|
|
self.cfg = max(1.0, min(30.0, self.cfg))
|
|
|
|
# Round dimensions to nearest 8
|
|
self.width = (self.width // 8) * 8
|
|
self.height = (self.height // 8) * 8
|
|
|
|
def dict(self) -> Dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> "GenerationParams":
|
|
# Filter unrelated keys to avoid TypeError on init
|
|
valid_keys = cls.__annotations__.keys()
|
|
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
|
return cls(**filtered)
|
|
|
|
|
|
@dataclass
|
|
class JobSpec:
|
|
"""
|
|
A top-level wrapper identifying what needs to be done.
|
|
"""
|
|
|
|
positive_prompt: str
|
|
negative_prompt: str
|
|
params: GenerationParams
|
|
schema_version: str = SCHEMA_VERSION # Literal equivalent
|
|
metadata: Dict[str, Any] = field(
|
|
default_factory=dict
|
|
) # R25: Trace context, user tags, etc.
|
|
|
|
def to_json(self) -> str:
|
|
data = asdict(self)
|
|
# Ensure params is also dictified if not already (asdict handles nested dataclasses)
|
|
return json.dumps(data, indent=2)
|
|
|
|
|
|
@dataclass
|
|
class ParamPatch:
|
|
"""
|
|
For defining partial updates to GenerationParams (refine loop).
|
|
"""
|
|
|
|
target_field: str
|
|
value: Any
|
|
reason: Optional[str] = None
|
|
|
|
|
|
@dataclass
|
|
class WebhookJobRequest:
|
|
"""
|
|
Incoming webhook request schema (S2).
|
|
Strict validation with length limits.
|
|
"""
|
|
|
|
version: int
|
|
template_id: str
|
|
profile_id: str
|
|
inputs: Dict[str, Any] = field(default_factory=dict)
|
|
job_id: Optional[str] = None
|
|
trace_id: Optional[str] = None
|
|
callback: Optional[Dict[str, Any]] = None # F16: { url, method?, headers?, mode? }
|
|
|
|
def __post_init__(self):
|
|
# Version validation
|
|
if self.version != 1:
|
|
raise ValueError(f"Unsupported version: {self.version}")
|
|
|
|
# Length limits
|
|
if self.job_id and len(self.job_id) > MAX_JOB_ID_LENGTH:
|
|
raise ValueError(f"job_id exceeds max length ({MAX_JOB_ID_LENGTH})")
|
|
if self.trace_id and len(self.trace_id) > MAX_TRACE_ID_LENGTH:
|
|
raise ValueError(f"trace_id exceeds max length ({MAX_TRACE_ID_LENGTH})")
|
|
if self.trace_id and not re.match(r"^[a-zA-Z0-9_-]+$", self.trace_id):
|
|
raise ValueError("trace_id contains invalid characters")
|
|
if len(self.template_id) > MAX_TEMPLATE_ID_LENGTH:
|
|
raise ValueError(
|
|
f"template_id exceeds max length ({MAX_TEMPLATE_ID_LENGTH})"
|
|
)
|
|
if len(self.profile_id) > MAX_PROFILE_ID_LENGTH:
|
|
raise ValueError(f"profile_id exceeds max length ({MAX_PROFILE_ID_LENGTH})")
|
|
|
|
# Validate inputs (only allowed keys, string length limits)
|
|
allowed_input_keys = set(WEBHOOK_JOB_REQUEST_CONTRACT["allowed_input_keys"])
|
|
for key, value in self.inputs.items():
|
|
if key not in allowed_input_keys:
|
|
raise ValueError(f"Unknown input key: {key}")
|
|
if isinstance(value, str) and len(value) > MAX_INPUT_STRING_LENGTH:
|
|
raise ValueError(
|
|
f"Input '{key}' exceeds max length ({MAX_INPUT_STRING_LENGTH})"
|
|
)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> "WebhookJobRequest":
|
|
"""Parse and validate from dict."""
|
|
# 1. Check for unknown keys (Strict validation)
|
|
allowed_top_level = set(WEBHOOK_JOB_REQUEST_CONTRACT["allowed_top_level"])
|
|
unknown = set(data.keys()) - allowed_top_level
|
|
if unknown:
|
|
raise ValueError(f"Unknown fields: {unknown}")
|
|
|
|
# 2. Check required fields
|
|
required = set(WEBHOOK_JOB_REQUEST_CONTRACT["required_top_level"])
|
|
missing = required - set(data.keys())
|
|
if missing:
|
|
raise ValueError(f"Missing required fields: {missing}")
|
|
|
|
return cls(
|
|
version=data["version"],
|
|
template_id=data["template_id"],
|
|
profile_id=data["profile_id"],
|
|
inputs=data.get("inputs", {}),
|
|
job_id=data.get("job_id"),
|
|
trace_id=data.get("trace_id"),
|
|
callback=data.get("callback"),
|
|
)
|
|
|
|
def to_normalized(self) -> Dict[str, Any]:
|
|
"""Return normalized, validated representation."""
|
|
return {
|
|
"version": self.version,
|
|
"job_id": self.job_id,
|
|
"trace_id": self.trace_id,
|
|
"template_id": self.template_id,
|
|
"profile_id": self.profile_id,
|
|
"inputs": self.inputs,
|
|
"callback": self.callback,
|
|
}
|