| """Execution boundary for published Koolook workflow setups.""" |
| from __future__ import annotations |
|
|
| from copy import deepcopy |
| from dataclasses import dataclass |
| from typing import Any |
|
|
| try: |
| from .koolook_setups import PublishedSetupRegistry |
| except ImportError: |
| from koolook_setups import PublishedSetupRegistry |
|
|
|
|
| class SetupRunError(Exception): |
| """API-facing setup runner error with stable HTTP status semantics.""" |
|
|
| def __init__(self, status_code: int, errors: list[str]) -> None: |
| super().__init__("; ".join(errors)) |
| self.status_code = status_code |
| self.errors = errors |
|
|
|
|
| @dataclass(frozen=True) |
| class RunRecord: |
| run_id: str |
| setup_id: str |
| prompt_id: str |
| status: str |
| inputs: dict[str, Any] |
|
|
|
|
| class InMemorySetupRunStore: |
| """Small run-id mapping store for a running ComfyUI process.""" |
|
|
| def __init__(self) -> None: |
| self._next_id = 1 |
| self._records: dict[str, RunRecord] = {} |
|
|
| def create(self, *, setup_id: str, prompt_id: str, inputs: dict[str, Any] | None = None) -> RunRecord: |
| run_id = f"run-{self._next_id:06d}" |
| self._next_id += 1 |
| record = RunRecord( |
| run_id=run_id, |
| setup_id=setup_id, |
| prompt_id=prompt_id, |
| status="queued", |
| inputs=deepcopy(inputs) if isinstance(inputs, dict) else {}, |
| ) |
| self._records[run_id] = record |
| return record |
|
|
| def get(self, run_id: str) -> RunRecord | None: |
| return self._records.get(run_id) |
|
|
|
|
| class AiohttpComfyClient: |
| """HTTP adapter for the managed ComfyUI server.""" |
|
|
| def __init__(self, base_url: str) -> None: |
| self._base_url = base_url.rstrip("/") |
|
|
| async def queue_prompt(self, prompt: dict) -> dict: |
| import aiohttp |
|
|
| async with aiohttp.ClientSession() as session: |
| async with session.post(f"{self._base_url}/prompt", json={"prompt": prompt}) as response: |
| try: |
| payload = await response.json() |
| except Exception: |
| payload = {"error": await response.text()} |
| if response.status >= 400: |
| detail = payload.get("error") if isinstance(payload, dict) else payload |
| raise SetupRunError( |
| 502, |
| [f"ComfyUI queue request failed with HTTP {response.status}: {detail}"], |
| ) |
| return payload if isinstance(payload, dict) else {} |
|
|
| async def get_history(self, prompt_id: str) -> dict: |
| import aiohttp |
|
|
| async with aiohttp.ClientSession() as session: |
| async with session.get(f"{self._base_url}/history/{prompt_id}") as response: |
| try: |
| payload = await response.json() |
| except Exception: |
| payload = {"error": await response.text()} |
| if response.status >= 400: |
| detail = payload.get("error") if isinstance(payload, dict) else payload |
| raise SetupRunError( |
| 502, |
| [f"ComfyUI history request failed with HTTP {response.status}: {detail}"], |
| ) |
| return payload if isinstance(payload, dict) else {} |
|
|
| async def get_queue(self) -> dict: |
| import aiohttp |
|
|
| async with aiohttp.ClientSession() as session: |
| async with session.get(f"{self._base_url}/queue") as response: |
| try: |
| payload = await response.json() |
| except Exception: |
| payload = {"error": await response.text()} |
| if response.status >= 400: |
| detail = payload.get("error") if isinstance(payload, dict) else payload |
| raise SetupRunError( |
| 502, |
| [f"ComfyUI queue status request failed with HTTP {response.status}: {detail}"], |
| ) |
| return payload if isinstance(payload, dict) else {} |
|
|
|
|
| class PublishedSetupRunner: |
| """Runs callable published setups without exposing ComfyUI internals.""" |
|
|
| def __init__( |
| self, |
| registry: PublishedSetupRegistry, |
| comfy_client: Any, |
| run_store: InMemorySetupRunStore | None = None, |
| ) -> None: |
| self._registry = registry |
| self._comfy_client = comfy_client |
| self._run_store = run_store or InMemorySetupRunStore() |
|
|
| async def runSetup(self, setup_id: str, inputs: dict[str, Any]) -> dict[str, Any]: |
| setup = self._registry.getSetup(setup_id) |
| if setup is None: |
| raise SetupRunError(404, [f"Published setup '{setup_id}' not found."]) |
| if not isinstance(inputs, dict): |
| raise SetupRunError(400, ["inputs must be a JSON object"]) |
|
|
| api_prompt = setup.get("apiPrompt") |
| if not isinstance(api_prompt, dict) or setup.get("validation", {}).get("status") != "valid": |
| raise SetupRunError(400, [f"Published setup '{setup_id}' is not callable."]) |
|
|
| prompt = deepcopy(api_prompt) |
| input_fields = _declared_input_fields(setup) |
| errors = _validate_run_inputs(input_fields, inputs) |
| errors.extend(_validate_execution_map_inputs(setup, inputs)) |
| if errors: |
| raise SetupRunError(400, errors) |
|
|
| for key, value in inputs.items(): |
| field = input_fields[key] |
| target = field["target"] |
| prompt[str(target["node"])]["inputs"][str(target["input"])] = _prompt_value_for_field(field, value) |
|
|
| mapped_prompt = _prune_prompt_for_execution_map(setup, prompt, inputs) |
| prompt = ( |
| mapped_prompt |
| if mapped_prompt is not None |
| else _prune_prompt_for_selected_app_results(setup, prompt, inputs) |
| ) |
|
|
| try: |
| queued = await self._comfy_client.queue_prompt(prompt) |
| except SetupRunError: |
| raise |
| except Exception as exc: |
| raise SetupRunError(502, [f"ComfyUI queue request failed: {exc}"]) from exc |
|
|
| prompt_id = queued.get("prompt_id") if isinstance(queued, dict) else None |
| if not isinstance(prompt_id, str) or not prompt_id: |
| raise SetupRunError(502, ["ComfyUI queue response did not include prompt_id"]) |
|
|
| record = self._run_store.create(setup_id=setup_id, prompt_id=prompt_id, inputs=inputs) |
| return {"runId": record.run_id, "promptId": record.prompt_id, "status": record.status} |
|
|
| async def getRun(self, run_id: str) -> dict[str, Any]: |
| record = self._run_store.get(run_id) |
| if record is None: |
| raise SetupRunError(404, [f"Koolook run '{run_id}' not found."]) |
|
|
| setup = self._registry.getSetup(record.setup_id) |
| if setup is None: |
| raise SetupRunError(404, [f"Published setup '{record.setup_id}' not found."]) |
|
|
| try: |
| history = await self._comfy_client.get_history(record.prompt_id) |
| except SetupRunError: |
| raise |
| except Exception as exc: |
| raise SetupRunError(502, [f"ComfyUI history request failed: {exc}"]) from exc |
|
|
| history_entry = history.get(record.prompt_id) if isinstance(history, dict) else None |
| if isinstance(history_entry, dict): |
| status = _status_from_history(history_entry) |
| return { |
| "runId": record.run_id, |
| "setupId": record.setup_id, |
| "promptId": record.prompt_id, |
| "status": status, |
| "comfyStatus": history_entry.get("status", {}), |
| "outputs": _summarize_outputs( |
| setup, |
| history_entry.get("outputs", {}), |
| record.inputs, |
| _history_prompt(history_entry), |
| ), |
| } |
|
|
| try: |
| queue = await self._comfy_client.get_queue() |
| except SetupRunError: |
| raise |
| except Exception as exc: |
| raise SetupRunError(502, [f"ComfyUI queue status request failed: {exc}"]) from exc |
| return { |
| "runId": record.run_id, |
| "setupId": record.setup_id, |
| "promptId": record.prompt_id, |
| "status": _status_from_queue(record.prompt_id, queue), |
| "outputs": [], |
| } |
|
|
|
|
| def _declared_input_fields(setup: dict[str, Any]) -> dict[str, dict[str, Any]]: |
| fields: dict[str, dict[str, Any]] = {} |
| for field in setup.get("inputContract", {}).get("inputs", []): |
| if not isinstance(field, dict): |
| continue |
| key = field.get("key") |
| target = field.get("target") |
| if isinstance(key, str) and key and isinstance(target, dict): |
| fields[key] = field |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if isinstance(app, dict): |
| for field in app.get("inputs", []): |
| if not isinstance(field, dict): |
| continue |
| key = field.get("key") |
| target = field.get("target") |
| if isinstance(key, str) and key and isinstance(target, dict): |
| fields.setdefault(key, field) |
| for field in app.get("outputs", []): |
| if not isinstance(field, dict): |
| continue |
| key = field.get("key") |
| target = field.get("target") |
| if isinstance(key, str) and key and isinstance(target, dict): |
| fields.setdefault(key, field) |
| switch = app.get("switch") |
| if isinstance(switch, dict): |
| key = switch.get("key") |
| target = switch.get("target") |
| if isinstance(key, str) and key and isinstance(target, dict): |
| fields.setdefault(key, switch) |
| return fields |
|
|
|
|
| def _validate_run_inputs(input_fields: dict[str, dict[str, Any]], inputs: dict[str, Any]) -> list[str]: |
| errors: list[str] = [] |
| for key in inputs: |
| if key not in input_fields: |
| errors.append(f"input '{key}' is not declared by this setup") |
| continue |
| field = input_fields[key] |
| if field.get("key") == "switch" and _switch_option_value(field, inputs[key]) is None: |
| errors.append(_switch_value_error(key, field)) |
| for key, field in input_fields.items(): |
| if field.get("required") is True and key not in inputs: |
| errors.append(f"required input '{key}' is missing") |
| return errors |
|
|
|
|
| def _prompt_value_for_field(field: dict[str, Any], value: Any) -> Any: |
| if field.get("key") != "switch": |
| return value |
| selected_value = _switch_option_value(field, value) |
| for option in field.get("options", []): |
| if not isinstance(option, dict): |
| continue |
| if option.get("value") == selected_value and isinstance(option.get("label"), str): |
| return option["label"] |
| return value |
|
|
|
|
| def _switch_option_value(switch: dict[str, Any], selected: Any) -> int | None: |
| if isinstance(selected, bool): |
| return None |
| for option in switch.get("options", []): |
| if not isinstance(option, dict): |
| continue |
| value = option.get("value") |
| if isinstance(value, bool) or not isinstance(value, int): |
| continue |
| if selected == value: |
| return value |
| if isinstance(selected, str) and (selected == str(value) or selected == option.get("label")): |
| return value |
| return None |
|
|
|
|
| def _switch_value_error(key: str, switch: dict[str, Any]) -> str: |
| choices: list[str] = [] |
| for option in switch.get("options", []): |
| if not isinstance(option, dict): |
| continue |
| value = option.get("value") |
| label = option.get("label") |
| if isinstance(value, bool) or not isinstance(value, int): |
| continue |
| choices.append(f"{value} ({label})" if isinstance(label, str) and label else str(value)) |
| if choices: |
| return f"input '{key}' must be one of: {', '.join(choices)}" |
| return f"input '{key}' has no valid switch options" |
|
|
|
|
| def _status_from_history(history_entry: dict[str, Any]) -> str: |
| status = history_entry.get("status") |
| if not isinstance(status, dict): |
| return "succeeded" |
| status_text = str(status.get("status_str", "")).lower() |
| if status.get("completed") is True and status_text not in {"error", "failed", "failure"}: |
| return "succeeded" |
| return "failed" |
|
|
|
|
| def _status_from_queue(prompt_id: str, queue: Any) -> str: |
| if not isinstance(queue, dict): |
| return "lost" |
| if _queue_contains_prompt(queue.get("queue_running", []), prompt_id): |
| return "running" |
| if _queue_contains_prompt(queue.get("queue_pending", []), prompt_id): |
| return "queued" |
| return "lost" |
|
|
|
|
| def _queue_contains_prompt(entries: Any, prompt_id: str) -> bool: |
| if not isinstance(entries, list): |
| return False |
| for entry in entries: |
| if isinstance(entry, dict) and entry.get("prompt_id") == prompt_id: |
| return True |
| if isinstance(entry, list) and prompt_id in entry: |
| return True |
| return False |
|
|
|
|
| def _summarize_outputs( |
| setup: dict[str, Any], |
| raw_outputs: Any, |
| run_inputs: dict[str, Any] | None = None, |
| history_prompt: dict[str, Any] | None = None, |
| ) -> list[dict[str, Any]]: |
| output_items = _flatten_history_outputs(raw_outputs) |
| summaries = [] |
| for output in setup.get("outputContract", {}).get("outputs", []): |
| if not isinstance(output, dict): |
| continue |
| summaries.append( |
| { |
| "key": output.get("key", ""), |
| "label": output.get("label", output.get("key", "")), |
| "type": output.get("type", ""), |
| "items": [ |
| item |
| for item in output_items |
| if _history_output_matches_contract(item, str(output.get("type", ""))) |
| ], |
| } |
| ) |
| summaries.extend(_summarize_app_surface_outputs(setup, output_items, run_inputs or {}, history_prompt)) |
| return summaries |
|
|
|
|
| def _summarize_app_surface_outputs( |
| setup: dict[str, Any], |
| output_items: list[dict[str, Any]], |
| run_inputs: dict[str, Any], |
| history_prompt: dict[str, Any] | None = None, |
| ) -> list[dict[str, Any]]: |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if not isinstance(app, dict): |
| return [] |
| summaries: list[dict[str, Any]] = [] |
| for field_type, fields in (("output", app.get("outputs")), ("result", app.get("results"))): |
| if not isinstance(fields, list): |
| continue |
| for field in fields: |
| if not isinstance(field, dict): |
| continue |
| target = field.get("target") |
| target_node = str(target.get("node")) if isinstance(target, dict) else "" |
| items = [ |
| item |
| for item in output_items |
| if target_node and item.get("nodeId") == target_node |
| ] |
| if field_type == "result" and not items: |
| items = _selected_switch_result_items(setup, field, output_items, run_inputs) |
| if field_type == "result" and not items and history_prompt is not None: |
| items = _execution_map_writer_result_items(setup, history_prompt, run_inputs) |
| summary = { |
| "key": field.get("key", ""), |
| "label": field.get("label", field.get("key", "")), |
| "type": field_type, |
| "visible": field.get("visible", True), |
| "target": target if isinstance(target, dict) else {}, |
| "default": field.get("default"), |
| "items": items, |
| } |
| summaries.append(summary) |
| return summaries |
|
|
|
|
| def _history_prompt(history_entry: dict[str, Any]) -> dict[str, Any] | None: |
| prompt_record = history_entry.get("prompt") |
| if ( |
| isinstance(prompt_record, list) |
| and len(prompt_record) >= 3 |
| and isinstance(prompt_record[2], dict) |
| ): |
| return prompt_record[2] |
| return None |
|
|
|
|
| def _execution_map_writer_result_items( |
| setup: dict[str, Any], |
| prompt: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> list[dict[str, Any]]: |
| writer_nodes = _selected_execution_map_writer_nodes(setup, run_inputs) |
| items: list[dict[str, Any]] = [] |
| for writer_node in writer_nodes: |
| node = prompt.get(writer_node) |
| if not isinstance(node, dict): |
| continue |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| continue |
| value = _resolve_prompt_value(prompt, inputs.get("filepath")) |
| if isinstance(value, str) and value: |
| items.append({"nodeId": writer_node, "kind": "text", "value": value}) |
| return items |
|
|
|
|
| def _selected_execution_map_writer_nodes( |
| setup: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> list[str]: |
| execution_map = setup.get("executionMap") |
| if not isinstance(execution_map, dict) or execution_map.get("version") != 1: |
| return [] |
| routers = execution_map.get("routers") |
| if not isinstance(routers, list): |
| return [] |
| out: list[str] = [] |
| for router in routers: |
| if not isinstance(router, dict): |
| continue |
| selected_value = _execution_map_selected_value(setup, router, run_inputs) |
| branches = router.get("branches") |
| branch = branches.get(str(selected_value)) if isinstance(branches, dict) else None |
| writer_nodes = branch.get("writerNodes") if isinstance(branch, dict) else None |
| if isinstance(writer_nodes, list): |
| out.extend(str(node_id) for node_id in writer_nodes) |
| return list(dict.fromkeys(out)) |
|
|
|
|
| def _selected_switch_result_items( |
| setup: dict[str, Any], |
| result_field: dict[str, Any], |
| output_items: list[dict[str, Any]], |
| run_inputs: dict[str, Any], |
| ) -> list[dict[str, Any]]: |
| selected_branch = _selected_result_branch_ref(setup, result_field, run_inputs) |
| if selected_branch is None: |
| return [] |
| selected_node = str(selected_branch[0]) |
| return [item for item in output_items if item.get("nodeId") == selected_node] |
|
|
|
|
| def _prune_prompt_for_execution_map( |
| setup: dict[str, Any], |
| prompt: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> dict[str, Any] | None: |
| execution_map = setup.get("executionMap") |
| if not isinstance(execution_map, dict) or execution_map.get("version") != 1: |
| return None |
| routers = execution_map.get("routers") |
| if not isinstance(routers, list): |
| return None |
|
|
| roots: set[str] = set() |
| for router in routers: |
| if not isinstance(router, dict): |
| continue |
| router_node = str(router.get("node", "")) |
| selected_value = _execution_map_selected_value(setup, router, run_inputs) |
| if selected_value is None: |
| continue |
| branches = router.get("branches") |
| branch = branches.get(str(selected_value)) if isinstance(branches, dict) else None |
| if not isinstance(branch, dict): |
| continue |
| writer_nodes = branch.get("writerNodes") |
| if isinstance(writer_nodes, list): |
| roots.update(str(node_id) for node_id in writer_nodes if str(node_id) in prompt) |
| if not writer_nodes and router_node in prompt: |
| roots.add(router_node) |
| if not roots: |
| return None |
|
|
| return _prune_prompt_from_roots( |
| prompt, |
| roots, |
| _selected_app_switches(setup, prompt, run_inputs), |
| ) |
|
|
|
|
| def _execution_map_selected_value( |
| setup: dict[str, Any], |
| router: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> int | None: |
| key = router.get("switchKey") |
| app = setup.get("setupSurface", {}).get("app", {}) |
| switch = app.get("switch") if isinstance(app, dict) else None |
| if isinstance(switch, dict) and switch.get("key") == key: |
| return _selected_switch_value(switch, run_inputs) |
| selected = run_inputs.get(key) if isinstance(key, str) else None |
| if isinstance(selected, bool): |
| return None |
| if isinstance(selected, int): |
| return selected |
| if isinstance(selected, str) and selected.isdigit(): |
| return int(selected) |
| return None |
|
|
|
|
| def _validate_execution_map_inputs(setup: dict[str, Any], run_inputs: dict[str, Any]) -> list[str]: |
| execution_map = setup.get("executionMap") |
| if not isinstance(execution_map, dict) or execution_map.get("version") != 1: |
| return [] |
| routers = execution_map.get("routers") |
| if not isinstance(routers, list): |
| return [] |
| api_prompt = setup.get("apiPrompt") |
| prompt_node_ids = set(api_prompt) if isinstance(api_prompt, dict) else set() |
| app = setup.get("setupSurface", {}).get("app", {}) |
| switch = app.get("switch") if isinstance(app, dict) else None |
| errors: list[str] = [] |
| for router in routers: |
| if not isinstance(router, dict): |
| continue |
| selected_value = _execution_map_selected_value(setup, router, run_inputs) |
| key = router.get("switchKey") |
| if selected_value is None: |
| if isinstance(switch, dict) and switch.get("key") == key and isinstance(key, str): |
| if key not in run_inputs: |
| errors.append(_switch_value_error(key, switch)) |
| elif isinstance(key, str): |
| errors.append(f"input '{key}' must select a valid execution branch") |
| continue |
| branches = router.get("branches") |
| if not isinstance(branches, dict) or str(selected_value) not in branches: |
| errors.append(f"input '{key}' selects branch {selected_value}, but this setup has no execution branch for it") |
| continue |
| branch = branches.get(str(selected_value)) |
| writer_nodes = branch.get("writerNodes") if isinstance(branch, dict) else None |
| if isinstance(writer_nodes, list) and writer_nodes: |
| missing_writer_nodes = [ |
| str(node_id) |
| for node_id in writer_nodes |
| if str(node_id) not in prompt_node_ids |
| ] |
| if missing_writer_nodes: |
| errors.append( |
| f"execution map branch {selected_value} for switch '{key}' references " |
| f"writer node(s) not present in the prompt: {', '.join(missing_writer_nodes)}" |
| ) |
| elif not writer_nodes and str(router.get("node", "")) not in prompt_node_ids: |
| errors.append( |
| f"execution map branch {selected_value} for switch '{key}' has no writer nodes " |
| "and its router node is not present in the prompt" |
| ) |
| return errors |
|
|
|
|
| def _prune_prompt_for_selected_app_results( |
| setup: dict[str, Any], |
| prompt: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> dict[str, Any]: |
| result_switches = _selected_result_switches(setup, prompt, run_inputs) |
| if not result_switches: |
| return prompt |
| selected_switches = _selected_app_switches(setup, prompt, run_inputs) |
|
|
| roots = { |
| node_id |
| for node_id, _selected_value in result_switches.values() |
| if node_id in prompt |
| } |
| for field in _app_result_fields(setup): |
| target = field.get("target") |
| if isinstance(target, dict) and str(target.get("node")) in prompt: |
| roots.add(str(target["node"])) |
|
|
| selected_branch_nodes = { |
| str(prompt[switch_node_id]["inputs"][f"value{selected_value}"][0]) |
| for switch_node_id, (_node_id, selected_value) in result_switches.items() |
| if ( |
| switch_node_id in prompt |
| and isinstance(prompt[switch_node_id], dict) |
| and isinstance(prompt[switch_node_id].get("inputs"), dict) |
| and _is_api_ref(prompt[switch_node_id]["inputs"].get(f"value{selected_value}")) |
| ) |
| } |
| roots.update( |
| _selected_output_descendant_roots( |
| prompt, |
| selected_branch_nodes, |
| _setup_output_surface_node_ids(setup), |
| ) |
| ) |
| if not roots: |
| return prompt |
|
|
| return _prune_prompt_from_roots(prompt, roots, selected_switches) |
|
|
|
|
| def _prune_prompt_from_roots( |
| prompt: dict[str, Any], |
| roots: set[str], |
| selected_switches: dict[str, tuple[str, int]], |
| ) -> dict[str, Any]: |
| keep: set[str] = set() |
|
|
| def visit(node_id: str) -> None: |
| if node_id in keep or node_id not in prompt: |
| return |
| keep.add(node_id) |
| node = prompt.get(node_id) |
| if not isinstance(node, dict): |
| return |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| return |
| selected_value = selected_switches.get(node_id, (None, None))[1] |
| for input_name, value in inputs.items(): |
| if ( |
| selected_value is not None |
| and input_name.startswith("value") |
| and input_name != f"value{selected_value}" |
| ): |
| continue |
| for ref in _api_refs(value): |
| visit(str(ref[0])) |
|
|
| for root in roots: |
| visit(root) |
| if not keep: |
| return prompt |
| pruned = {node_id: deepcopy(node) for node_id, node in prompt.items() if node_id in keep} |
| for node_id, (_switch_node_id, selected_value) in selected_switches.items(): |
| node = pruned.get(node_id) |
| if not isinstance(node, dict): |
| continue |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| continue |
| node["inputs"] = { |
| key: value |
| for key, value in inputs.items() |
| if not key.startswith("value") or key == f"value{selected_value}" |
| } |
| return pruned |
|
|
|
|
| def _selected_result_switches( |
| setup: dict[str, Any], |
| api_prompt: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> dict[str, tuple[str, int]]: |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if not isinstance(app, dict): |
| return {} |
| switch = app.get("switch") |
| if not isinstance(switch, dict): |
| return {} |
| selected_value = _selected_switch_value(switch, run_inputs) |
| if selected_value is None: |
| return {} |
| selected: dict[str, tuple[str, int]] = {} |
| for field in _app_result_fields(setup): |
| result_node_id = _result_switch_node_id(setup, field, run_inputs, api_prompt) |
| if result_node_id is not None: |
| selected[result_node_id] = (result_node_id, selected_value) |
| return selected |
|
|
|
|
| def _selected_app_switches( |
| setup: dict[str, Any], |
| api_prompt: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> dict[str, tuple[str, int]]: |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if not isinstance(app, dict): |
| return {} |
| switch = app.get("switch") |
| if not isinstance(switch, dict): |
| return {} |
| switch_target = switch.get("target") |
| if not isinstance(switch_target, dict): |
| return {} |
| selected_value = _selected_switch_value(switch, run_inputs) |
| if selected_value is None: |
| return {} |
|
|
| selected: dict[str, tuple[str, int]] = {} |
| for node_id, node in api_prompt.items(): |
| if not isinstance(node, dict): |
| continue |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| continue |
| selector_ref = _switch_selector_ref(inputs) |
| if not _selector_matches_switch_target(selector_ref, switch_target, api_prompt): |
| continue |
| if _is_api_ref(inputs.get(f"value{selected_value}")): |
| selected[str(node_id)] = (str(node_id), selected_value) |
| return selected |
|
|
|
|
| def _selected_output_descendant_roots( |
| api_prompt: dict[str, Any], |
| selected_branch_nodes: set[str], |
| output_surface_node_ids: set[str], |
| ) -> set[str]: |
| if not selected_branch_nodes or not output_surface_node_ids: |
| return set() |
| children_by_node = _prompt_children_by_node(api_prompt) |
| roots: set[str] = set() |
| seen: set[str] = set() |
| pending = list(selected_branch_nodes) |
| while pending: |
| node_id = pending.pop() |
| if node_id in seen: |
| continue |
| seen.add(node_id) |
| for child_id in children_by_node.get(node_id, set()): |
| if child_id in output_surface_node_ids and child_id not in selected_branch_nodes: |
| roots.add(child_id) |
| pending.append(child_id) |
| return roots |
|
|
|
|
| def _prompt_children_by_node(api_prompt: dict[str, Any]) -> dict[str, set[str]]: |
| children: dict[str, set[str]] = {} |
| for node_id, node in api_prompt.items(): |
| if not isinstance(node, dict): |
| continue |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| continue |
| for value in inputs.values(): |
| for ref in _api_refs(value): |
| children.setdefault(str(ref[0]), set()).add(str(node_id)) |
| return children |
|
|
|
|
| def _setup_output_surface_node_ids(setup: dict[str, Any]) -> set[str]: |
| setup_surface = setup.get("setupSurface", {}) |
| if not isinstance(setup_surface, dict): |
| return set() |
| output_ids: set[str] = set() |
| outputs = setup_surface.get("outputs") |
| if not isinstance(outputs, list): |
| return output_ids |
| for group in outputs: |
| if not isinstance(group, dict) or not isinstance(group.get("nodes"), list): |
| continue |
| for node in group["nodes"]: |
| if isinstance(node, dict) and node.get("id") is not None: |
| output_ids.add(str(node["id"])) |
| return output_ids |
|
|
|
|
| def _app_result_fields(setup: dict[str, Any]) -> list[dict[str, Any]]: |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if not isinstance(app, dict) or not isinstance(app.get("results"), list): |
| return [] |
| return [field for field in app["results"] if isinstance(field, dict)] |
|
|
|
|
| def _result_switch_node_id( |
| setup: dict[str, Any], |
| result_field: dict[str, Any], |
| run_inputs: dict[str, Any], |
| api_prompt: dict[str, Any] | None = None, |
| ) -> str | None: |
| app = setup.get("setupSurface", {}).get("app", {}) |
| if not isinstance(app, dict): |
| return None |
| switch = app.get("switch") |
| if not isinstance(switch, dict): |
| return None |
| selected_value = _selected_switch_value(switch, run_inputs) |
| if selected_value is None: |
| return None |
|
|
| api_prompt = api_prompt if isinstance(api_prompt, dict) else setup.get("apiPrompt") |
| if not isinstance(api_prompt, dict): |
| return None |
| target = result_field.get("target") |
| if not isinstance(target, dict): |
| return None |
| result_node = api_prompt.get(str(target.get("node"))) |
| if not isinstance(result_node, dict): |
| return None |
| result_inputs = result_node.get("inputs") |
| if not isinstance(result_inputs, dict): |
| return None |
| result_ref = result_inputs.get(str(target.get("input"))) |
| if not _is_api_ref(result_ref): |
| return None |
|
|
| switch_node_id = str(result_ref[0]) |
| switch_node = api_prompt.get(switch_node_id) |
| if not isinstance(switch_node, dict): |
| return None |
| switch_inputs = switch_node.get("inputs") |
| if not isinstance(switch_inputs, dict): |
| return None |
| selector_ref = _switch_selector_ref(switch_inputs) |
| switch_target = switch.get("target") |
| if isinstance(switch_target, dict) and not _selector_matches_switch_target( |
| selector_ref, |
| switch_target, |
| api_prompt, |
| ): |
| return None |
| if not _is_api_ref(switch_inputs.get(f"value{selected_value}")): |
| return None |
| return switch_node_id |
|
|
|
|
| def _selected_result_branch_ref( |
| setup: dict[str, Any], |
| result_field: dict[str, Any], |
| run_inputs: dict[str, Any], |
| ) -> list[Any] | None: |
| api_prompt = setup.get("apiPrompt") |
| if not isinstance(api_prompt, dict): |
| return None |
| switch_node_id = _result_switch_node_id(setup, result_field, run_inputs, api_prompt) |
| if switch_node_id is None: |
| return None |
| switch_node = api_prompt.get(switch_node_id) |
| if not isinstance(switch_node, dict): |
| return None |
| switch_inputs = switch_node.get("inputs") |
| if not isinstance(switch_inputs, dict): |
| return None |
| switch = setup.get("setupSurface", {}).get("app", {}).get("switch") |
| selected_value = _selected_switch_value(switch, run_inputs) if isinstance(switch, dict) else None |
| if selected_value is None: |
| return None |
| branch_ref = switch_inputs.get(f"value{selected_value}") |
| return branch_ref if _is_api_ref(branch_ref) else None |
|
|
|
|
| def _selected_switch_value(switch: dict[str, Any], run_inputs: dict[str, Any]) -> int | None: |
| key = switch.get("key") |
| selected = run_inputs.get(key) if isinstance(key, str) and key in run_inputs else switch.get("default") |
| return _switch_option_value(switch, selected) |
|
|
|
|
| def _selector_matches_switch_target(selector_ref: Any, switch_target: dict[str, Any], api_prompt: dict[str, Any]) -> bool: |
| if not _is_api_ref(selector_ref): |
| return False |
| source_node_id = str(selector_ref[0]) |
| target_node_id = str(switch_target.get("node")) |
| if source_node_id != target_node_id: |
| return False |
| source_node = api_prompt.get(source_node_id) |
| if not isinstance(source_node, dict): |
| return False |
| outputs = _publish_input_output_slots(source_node) |
| return outputs.get("switch") == int(selector_ref[1]) |
|
|
|
|
| def _switch_selector_ref(switch_inputs: dict[str, Any]) -> Any: |
| if "select" in switch_inputs: |
| return switch_inputs.get("select") |
| return switch_inputs.get("index") |
|
|
|
|
| def _publish_input_output_slots(api_node: dict[str, Any]) -> dict[str, int]: |
| if api_node.get("class_type") != "Koolook_PublishInput": |
| return {} |
| return { |
| "sequence_folder": 0, |
| "qt_file": 1, |
| "single_file": 2, |
| "prompt": 3, |
| "switch": 4, |
| } |
|
|
|
|
| def _resolve_prompt_value(prompt: dict[str, Any], value: Any) -> Any: |
| if not _is_api_ref(value): |
| return value |
| node_id = str(value[0]) |
| output_index = int(value[1]) |
| node = prompt.get(node_id) |
| if not isinstance(node, dict): |
| return None |
| class_type = node.get("class_type") |
| inputs = node.get("inputs") |
| if not isinstance(inputs, dict): |
| return None |
| if class_type == "Koolook_PublishOutput": |
| values = ( |
| inputs.get("folder"), |
| inputs.get("name"), |
| inputs.get("version"), |
| ) |
| return values[output_index] if output_index < len(values) else None |
| if class_type == "EasyAIPipeline": |
| if "WRITE_file_path" in inputs: |
| values = ( |
| inputs.get("WRITE_file_path"), |
| inputs.get("output_name", ""), |
| inputs.get("version_string", ""), |
| inputs.get("output_directory", ""), |
| inputs.get("shot_duration", 0), |
| inputs.get("seed_value", 0), |
| inputs.get("shot_name", ""), |
| ) |
| return values[output_index] if output_index < len(values) else None |
| resolved_inputs = { |
| key: _resolve_prompt_value(prompt, input_value) |
| for key, input_value in inputs.items() |
| } |
| try: |
| try: |
| from .k_ai_pipeline import build_pipeline_outputs |
| except ImportError: |
| from k_ai_pipeline import build_pipeline_outputs |
|
|
| values = build_pipeline_outputs( |
| **resolved_inputs, |
| create_directory=False, |
| check_overwrite=False, |
| ) |
| except Exception: |
| return None |
| return values[output_index] if output_index < len(values) else None |
| return None |
|
|
|
|
| def _is_api_ref(value: Any) -> bool: |
| return ( |
| isinstance(value, list) |
| and len(value) == 2 |
| and isinstance(value[0], (str, int)) |
| and not isinstance(value[1], bool) |
| and isinstance(value[1], int) |
| ) |
|
|
|
|
| def _api_refs(value: Any) -> list[list[Any]]: |
| if _is_api_ref(value): |
| return [value] |
| if isinstance(value, dict): |
| refs: list[list[Any]] = [] |
| for child in value.values(): |
| refs.extend(_api_refs(child)) |
| return refs |
| if isinstance(value, list): |
| refs = [] |
| for child in value: |
| refs.extend(_api_refs(child)) |
| return refs |
| return [] |
|
|
|
|
| def _flatten_history_outputs(raw_outputs: Any) -> list[dict[str, Any]]: |
| if not isinstance(raw_outputs, dict): |
| return [] |
| items: list[dict[str, Any]] = [] |
| for node_id, node_outputs in raw_outputs.items(): |
| if not isinstance(node_outputs, dict): |
| continue |
| for kind, values in node_outputs.items(): |
| if not isinstance(values, list): |
| continue |
| for value in values: |
| item = {"nodeId": str(node_id), "kind": str(kind)} |
| if isinstance(value, dict): |
| item.update(value) |
| else: |
| item["value"] = value |
| items.append(item) |
| return items |
|
|
|
|
| def _history_output_matches_contract(item: dict[str, Any], contract_type: str) -> bool: |
| kind = str(item.get("kind", "")).lower() |
| if contract_type == "video": |
| return kind in {"video", "videos", "animated"} |
| if contract_type == "image": |
| return kind in {"image", "images"} |
| return True |
|
|