Files
goodguy1963-ComfyUI-Thinkin…/__init__.py
T
Marcel Hofer b87d17e34d feat: add native Decision API backends (v3.0.2)
Add native OpenAI Decisions and OpenRouter Decisions API routing to the image and text Decision Router nodes while keeping local in-process llama.cpp inference as the default.

Discover OpenRouter decision models by input modality and decision output capability, then enforce server profile image policy and optional model allowlists both in the UI model catalog and before inference. Keep credentials and endpoints in server-side profiles; apply profile input, image, and timeout limits; never fall back to chat-generated JSON.

Preserve saved workflow widget order, release any retained local model when switching to API mode, and normalize typed route choices, probabilities, and confidence into decision_json. Add API setup documentation and adapter/router/security regression coverage.

Validation: 240 tests passed across six test modules run individually in bundled ComfyUI Python 3.12; Python and JavaScript syntax checks, API catalog validation, version metadata checks, and git diff --check passed. Provider inference is covered with mocked responses; no authenticated live inference was run.
2026-10-29 17:08:23 +01:00

118 lines
4.1 KiB
Python

"""
{
"name": "ComfyUI-ThinkingLLM",
"description": "A multimodal ComfyUI AI node pack with Qwen, Gemma 4, GGUF, Whisper, image and text decision routing, and live thinking output.",
"author": "goodguy1963",
"version": "3.0.2",
"url": "https://github.com/goodguy1963/ComfyUI-ThinkingLLM",
"category": "image"
}
"""
import importlib.util
import os
import sys
# Get the directory of the current script
current_dir = os.path.dirname(__file__)
sys.path.insert(0, current_dir)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
COMMERCIAL_RELEASE = os.environ.get("THINKINGLLM_COMMERCIAL_RELEASE") == "1"
WEB_DIRECTORY = "./web_commercial" if COMMERCIAL_RELEASE else "./web"
COMMERCIAL_MODULES = {
"AILab_OutputCleaner",
"AILab_QwenVL",
"AILab_QwenVL_PromptEnhancer",
"AILab_StreamDisplay",
}
COMMERCIAL_NODE_MODULES = {
"decision_router",
"story_split_node",
"system_prompt_preset",
"vram_cleanup",
}
LEGACY_NODE_REPLACEMENTS = {
"AILab_QwenVL": "ThinkingLLM_QwenVL",
"AILab_QwenVL_Advanced": "ThinkingLLM_QwenVL_Advanced",
"AILab_QwenVL_GGUF": "ThinkingLLM_QwenVL_GGUF",
"AILab_QwenVL_GGUF_Advanced": "ThinkingLLM_QwenVL_GGUF_Advanced",
"AILab_QwenVL_PromptEnhancer": "ThinkingLLM_QwenVL_PromptEnhancer",
"AILab_QwenVL_GGUF_PromptEnhancer": "ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
}
if COMMERCIAL_RELEASE:
LEGACY_NODE_REPLACEMENTS = {
old: new for old, new in LEGACY_NODE_REPLACEMENTS.items() if "GGUF" not in old
}
def load_modules_from_directory(directory):
for file in os.listdir(directory):
if file.endswith(".py"):
file_path = os.path.join(directory, file)
module_name = os.path.basename(file)[:-3]
if module_name == os.path.basename(__file__)[:-3]:
continue
if COMMERCIAL_RELEASE:
allowed = (
COMMERCIAL_MODULES
if os.path.abspath(directory) == os.path.abspath(current_dir)
else COMMERCIAL_NODE_MODULES
)
if module_name not in allowed:
continue
try:
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None or spec.loader is None:
raise ImportError(f"Unable to create import spec for {module_name}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
if hasattr(module, "NODE_CLASS_MAPPINGS"):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
except Exception as e:
print(f"Error loading module {module_name}: {e}")
load_modules_from_directory(current_dir)
# Also load from nodes subdirectory
nodes_dir = os.path.join(current_dir, "nodes")
if os.path.exists(nodes_dir):
load_modules_from_directory(nodes_dir)
for legacy_node_name in LEGACY_NODE_REPLACEMENTS:
NODE_CLASS_MAPPINGS.pop(legacy_node_name, None)
NODE_DISPLAY_NAME_MAPPINGS.pop(legacy_node_name, None)
def _register_legacy_node_replacements():
try:
from comfy_api.latest._io import NodeReplace
from server import PromptServer
except Exception:
return
manager = getattr(getattr(PromptServer, "instance", None), "node_replace_manager", None)
if manager is None:
return
for old_node_id, new_node_id in LEGACY_NODE_REPLACEMENTS.items():
manager.register(NodeReplace(new_node_id=new_node_id, old_node_id=old_node_id))
_register_legacy_node_replacements()
NODE_CLASS_MAPPINGS = dict(sorted(NODE_CLASS_MAPPINGS.items(), key=lambda x: NODE_DISPLAY_NAME_MAPPINGS.get(x[0], x[0])))
NODE_DISPLAY_NAME_MAPPINGS = dict(sorted(NODE_DISPLAY_NAME_MAPPINGS.items(), key=lambda x: x[1]))
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"LEGACY_NODE_REPLACEMENTS",
]