Files
glonlas-comfyUI-LMStudio-nodes/connect_node.py
Gerald LonlasandClaude Sonnet 4.6 969e3acb19 fix: api key normalization, UI typo, dead code, route timeout
connect_node.py
- Fix UI label typo: "Enable Reasonning" → "Enable Reasoning"
- Use normalize_api_key() instead of an inline (api_token or "-").strip()
  expression; the helper also checks DEFAULT_API_KEY_PLACEHOLDER so both
  code paths now agree on the canonical fallback value

client.py
- Remove build_responses_input_with_image(): dead code; image_to_text_node
  builds its own multi-image payload directly in _responses_kwargs() and
  this function was never imported or called

routes.py
- _test_handler default timeout: 600 s → 15 s, consistent with
  _models_handler. Both handlers probe connectivity identically; a 10-min
  default on a "Test Connection" button is a foot-gun when called without
  a timeout_seconds query param.
- Drop now-unused DEFAULT_TIMEOUT_SECONDS import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:11:30 +08:00

195 lines
6.9 KiB
Python

from __future__ import annotations
from comfy_api.latest import io, ui
from .client import (
DEFAULT_MAX_TOKENS,
DEFAULT_TEMPERATURE,
DEFAULT_TIMEOUT_SECONDS,
MODEL_PLACEHOLDER,
get_server_models,
normalize_api_key,
normalize_server_url,
)
from .iotypes import ParamConnection
from .models import LMStudioConnectionPayload
class LMStudioConnect(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="LMStudio_Connect",
display_name="LMStudio - Connect",
category="LMStudio",
description=(
"Creates a reusable LMStudio connection for downstream nodes. "
"Use the refresh/test buttons on this node to fetch models from the remote server."
),
inputs=[
io.String.Input(
id="server_url",
display_name="Server URL",
default="http://127.0.0.1:1234",
placeholder="http://10.168.168.7:1234",
tooltip="LMStudio server URL (without /v1).",
),
io.String.Input(
id="api_token",
display_name="API Token",
default="-",
placeholder="Leave '-' for lm-studio",
tooltip="Bearer token for LMStudio OpenAI-compatible API.",
),
io.String.Input(
id="model",
display_name="Model",
default=MODEL_PLACEHOLDER,
tooltip=(
"Model id. The web extension renders this field as a dropdown and syncs the selected model value."
),
),
io.Boolean.Input(
id="reasoning_enabled",
display_name="Enable Reasoning",
default=False,
tooltip="Enable model reasoning hints when using the responses endpoint.",
),
io.Boolean.Input(
id="use_tooling_mcp",
display_name="Use Tooling / MCP",
default=False,
tooltip=(
"Expose intent to use MCP tooling. Useful only when your target model/session is configured "
"for tool-enabled responses."
),
),
io.Int.Input(
id="max_tokens",
display_name="Max Token",
default=DEFAULT_MAX_TOKENS,
min=1,
max=1_000_000,
tooltip="Max output tokens for downstream generation nodes.",
advanced=True,
),
io.Float.Input(
id="temperature",
display_name="Temperature",
default=DEFAULT_TEMPERATURE,
min=0.0,
max=2.0,
step=0.05,
tooltip="Sampling temperature for downstream generation nodes.",
advanced=True,
),
io.Int.Input(
id="timeout_seconds",
display_name="Connection Timeout (seconds)",
default=DEFAULT_TIMEOUT_SECONDS,
min=1,
max=3600,
tooltip="HTTP request timeout for all LMStudio calls.",
advanced=True,
),
io.Boolean.Input(
id="test_connectivity",
display_name="Test Connectivity On Execute",
default=True,
tooltip="When enabled, validate server reachability and model availability during execution.",
advanced=True,
),
],
outputs=[
ParamConnection.Output(
id="connection",
display_name="Connection",
tooltip="Reusable LMStudio connection payload for text/image nodes.",
),
io.String.Output(
id="status",
display_name="Status",
tooltip="Connection status summary.",
),
],
)
@classmethod
def validate_inputs(cls, server_url: str, timeout_seconds: int, max_tokens: int) -> bool | str:
try:
normalize_server_url(server_url)
except ValueError as exc:
return str(exc)
if timeout_seconds < 1:
return "timeout_seconds must be >= 1"
if max_tokens < 1:
return "max_tokens must be >= 1"
return True
@classmethod
def execute(
cls,
server_url: str,
api_token: str | None,
model: str,
reasoning_enabled: bool,
test_connectivity: bool,
max_tokens: int,
temperature: float,
timeout_seconds: int,
use_tooling_mcp: bool,
) -> io.NodeOutput:
normalized_server_url = normalize_server_url(server_url)
model_name = (model or "").strip()
models: list[str] = []
should_probe_models = test_connectivity or model_name in {"", MODEL_PLACEHOLDER}
if should_probe_models:
models = get_server_models(
server_url=normalized_server_url,
api_key=api_token,
timeout_seconds=timeout_seconds,
)
if model_name in {"", MODEL_PLACEHOLDER}:
if models:
model_name = models[0]
else:
raise ValueError(
"No model selected and no loaded model was discovered on the LMStudio server."
)
if models and model_name not in models:
raise ValueError(
f"Selected model '{model_name}' is not available on the server. "
"Use the refresh button and pick a loaded model."
)
payload = LMStudioConnectionPayload(
server_url=normalized_server_url,
base_url=f"{normalized_server_url}/v1",
api_key=normalize_api_key(api_token),
model=model_name,
reasoning_enabled=reasoning_enabled,
max_tokens=max_tokens,
temperature=temperature,
timeout_seconds=timeout_seconds,
use_tooling_mcp=use_tooling_mcp,
)
model_count = len(models)
if should_probe_models:
status = (
f"Connected to {normalized_server_url}. "
f"Found {model_count} model(s). Using '{model_name}'."
)
else:
status = f"Connection prepared for {normalized_server_url}. Using '{model_name}'."
return io.NodeOutput(
payload,
status,
ui=ui.PreviewText(status),
)