Commit Graph
32 Commits
Author SHA1 Message Date
Gerald LonlasandClaude Opus 4.8 d9d5c77e0f test(contracts): add image-encoding and node status behavioral contracts
Close gaps that full line+branch coverage does not catch:

- comfy_image_to_base64_png_url: assert RGBA (4-channel) tensor round-trips
  correctly through PNG encoding (pixel values preserved).
- comfy_image_to_base64_png_url: assert non-square image dimensions (H x W)
  are preserved in the encoded PNG — catches H/W transposition mutations.
- comfy_image_to_base64_png_url: assert batch[0] (red) is encoded, not
  batch[1] (blue) — catches wrong-frame-index mutations.
- image_to_text_node._responses_kwargs: assert every image content entry uses
  the key 'image_url' (not 'url') and contains a valid data:image/png;base64,
  URL — for both single and batch paths.
- image_to_text_node._responses_kwargs: assert batch frames produce distinct
  data URLs — catches mutations that pass the same index for all frames.
- image_to_text_node.execute: assert the status message contains the actual
  model name for both the responses and chat.completions fallback paths.
- text_gen_node.execute: assert the status message contains the actual model
  name for both paths — a hardcoded name mutation goes undetected otherwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:26:41 +08:00
Gérald LONLASandClaude Opus 4.8 1ccce73904 test(contracts): add behavioral-contract tests that catch value-mutant regressions (#15)
25 new tests across client.py, routes.py, connect_node.py, and text_gen_node.py.
Each test targets a specific behavioral contract that 100% line+branch coverage
did not verify and that a realistic mutation would silently break:

- normalize_server_url: whitespace-only input, trailing-slash stripping,
  https acceptance
- coerce_seed: whitespace-only string returns None (not ValueError)
- resolve_request_seed: seed=0 passes through; large values not clipped
- build_responses_input_text: 'type' must be 'input_text' (not 'text')
- build_chat_messages: whitespace system prompt omitted; empty user included
- list_models: result is sorted AND deduplicated (both invariants checked)
- strip_think_content: surrounding text order preserved; block-only → ""
- _query_int: negative integers rejected; empty string uses default
- _models_handler: empty models → default_model="" not IndexError;
  error prefix is 'Failed to list models' not 'Connection test failed'
- _test_handler: model_count matches actual list length; distinct error prefix
- connect_node execute: empty model with test_connectivity=False still probes;
  no-probe status omits 'Found N model(s)'; probe status includes exact count
- _responses_kwargs: 'input' key present with user-role and correct prompt text;
  temperature/max_tokens forwarded from payload
- text_gen_node execute: fallback path status includes resolved seed;
  user_prompt=None raises clean ValueError

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:22:32 +08:00
Gérald LONLASandClaude Opus 4.8 4fc78624c4 fix(nodes): raise error when response is entirely think-block content (#16)
When a reasoning model returns a response consisting solely of
<think>...</think> content, the emptiness guard fired before
strip_think_content ran, so the node silently returned an empty
string rather than signalling an error. Add a post-strip check
in both LMStudioTextGen and LMStudioImageToText that raises
ValueError if strip_think_content reduces the text to nothing.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:21:55 +08:00
Gérald LONLASandClaude Opus 4.8 0d05296bc0 fix(client): strip /v1 suffix case-insensitively in normalize_server_url (#12)
`normalize_server_url` used a case-sensitive `endswith("/v1")` check, so
user-supplied URLs like `https://host/V1` passed through intact. The
downstream `to_openai_base_url` then appended another `/v1`, producing the
double-suffix broken URL `https://host/V1/v1`, causing every API call to
fail with a 404.

Fix: compare against `value.lower()` so any casing of the suffix is stripped.
Adds a targeted regression test covering lowercase, uppercase, and mixed-case
inputs as well as the end-to-end `to_openai_base_url` output.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:14:19 +08:00
Gérald LONLASandClaude Opus 4.8 25d7309fa7 test(client): cover all defensive branches in extract_responses_text (#14)
Add four targeted tests hitting the previously uncovered branches in
`client.extract_responses_text` (lines 221–233): dict items with a
non-list content field, content list entries that are not dicts, dict
entries whose text value is not a str, and object items where
`getattr(entry, "text")` is non-str.

Also declare `aiohttp` in the optional [test] extra so the full suite
can import `routes.py` without a ModuleNotFoundError.

Branch coverage: 99% (4 branches missed) → 100% (146/146 branches).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:13:45 +08:00
Gérald LONLASandClaude Opus 4.8 fa252dbac6 chore(tests): remove unused _batch_image helper from test_coverage_gaps.py (#13)
_batch_image() was defined in tests/test_coverage_gaps.py but never called
in that file — an identical helper already exists in test_image_to_text_node.py
where it is actively used. Dead code with no callers; safe to remove.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:13:41 +08:00
Gérald LONLASandClaude Opus 4.8 0e42058f20 chore: remove unused dump_openai_response and dead test helpers (#10)
- Delete client.dump_openai_response (and its import json): never called
  from any production path (connect_node, text_gen_node, image_to_text_node,
  routes, __init__); existed only as a debugging aid.
- Delete its test (test_dump_openai_response_branches in test_client_full.py)
  and the now-orphaned `import json` in that file.
- Delete make_response_with_output_text / make_response_with_output_items
  from tests/helpers/fakes.py: defined in the initial test scaffolding
  commit but never imported or referenced by any test module.

All 68 tests pass; branch coverage holds at 96.93 % (> 90 % required).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:04:33 +08:00
Gérald LONLASandClaude Opus 4.8 9d8e262304 test(coverage): close remaining branch gaps in client and image node (#9)
Add 12 focused unit tests targeting uncovered branches identified by
pytest-cov on main (97.01% → 99.30%):

client.py
- coerce_seed: .item() raises at runtime → falls through to unsupported-type error (lines 117-118, 142)
- coerce_seed: plain unsupported types (dict, object) raise ValueError (line 142)
- extract_chat_completion_text: non-str/non-list content returns "" (line 207)
- extract_chat_completion_text: list items with no usable text are skipped (lines 222-228)
- extract_responses_text: output list with unresolvable items returns "" (line 234)
- extract_responses_text: output=None returns "" (end of function)
- build_chat_messages_with_image: blank/None system_prompt omits the system message (line 291)

image_to_text_node.py
- _responses_kwargs: empty/whitespace system_prompt omits "instructions" key (line 112)
- execute: responses endpoint returns empty string → falls back to chat.completions (line 157)
- execute: both endpoints return empty text → ValueError propagates (line 181)
- execute: single-image chat fallback status does not mention dropped frames (line 183)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:00:30 +08:00
Gérald LONLASandClaude Opus 4.8 73ed6d7f38 fix(client): avoid duplicate text when output item has both content and text (#11)
In `extract_responses_text`, the loop over non-dict output items extracted
text from `item.content[].text` AND unconditionally also from `item.text`.
When an item carried both attributes (e.g. an OpenAI Responses API message
object that exposes `.content` as a list and also a convenience `.text`
shorthand), the same content was appended twice, producing a response like
`"the-answer\nthe-answer"` instead of `"the-answer"`.

The dict-item branch already used `continue` to prevent this; mirror that
guard in the object branch by making `item.text` extraction an `else` clause
that only runs when `item.content` is NOT a list.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:00:00 +08:00
Gerald Lonlas 75792130b0 docs(test): add unit test execution command v0.1 2026-03-05 01:29:10 +08:00
Gerald Lonlas 93a0268b52 test: cover image node, routes, entrypoint, and data types 2026-03-05 01:28:29 +08:00
Gerald Lonlas cbace183ca test(nodes): add connect and text-gen unit coverage 2026-03-05 01:26:53 +08:00
Gerald Lonlas 8c10bf002e test(client): cover parsing, seed, and image helper branches 2026-03-05 01:25:03 +08:00
Gerald Lonlas 99a2477a5a test: add pytest harness and coverage configuration 2026-03-05 01:23:15 +08:00
Gerald Lonlas 10bb68dd98 merge: codex/lmstudio-remote-node into master 2026-03-05 00:25:34 +08:00
Gerald Lonlas 6a63d2f1e4 docs: add concise README with screenshot and project disclaimer 2026-03-05 00:23:05 +08:00
Gerald LonlasandClaude Sonnet 4.6 ccef35cec7 fix(image-to-text): surface batch frame loss in chat.completions fallback
When the responses endpoint fails and execution falls back to
chat.completions, only the first image in a batch can be forwarded
(chat.completions accepts a single image_url per message in this
implementation). Previously extra frames were silently dropped.

Now we record batch_size before entering the fallback and append a plain-
language warning to fallback_reason when batch_size > 1, so the status
preview shown in ComfyUI makes the degradation explicit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:11:37 +08:00
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
Gerald LonlasandClaude Sonnet 4.6 617100dbe6 fix(tests): correct broken seed and think-content test assertions
- coerce_seed("") returns None, not ValueError; move "" out of the
  invalid-values parametrize and give it its own explicit assertion
- strip_think_content tests used \\n (literal backslash-n) instead of
  real newlines; .strip() cannot remove backslash or 'n' so the dangling-
  tag test always failed. Replace with actual \n to match real LLM output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:11:19 +08:00
Gerald Lonlas 42e7f9b382 refactor: simplify image-to-text outputs to response only 2026-03-04 16:04:13 +08:00
Gerald Lonlas 382db13ec2 fix: always strip <think> blocks from text and image response outputs 2026-03-04 15:43:59 +08:00
Gerald Lonlas 6afa30d350 refactor: simplify text gen outputs to response only 2026-03-04 15:24:08 +08:00
Gerald Lonlas 466ec9d001 feat: strip <think> reasoning blocks from text output when reasoning is enabled 2026-03-04 14:52:18 +08:00
Gerald Lonlas a5b030aabd fix: preserve connect widget order for api token and model dropdown 2026-03-04 14:46:45 +08:00
Gerald Lonlas 4a1fbd9acc feat: reorder LMStudio connect fields and move connectivity test to advanced 2026-03-04 14:42:32 +08:00
Gerald Lonlas ec1ac92bb1 feat: make prompt textareas mouse-resizable on LMStudio text nodes 2026-03-04 14:38:03 +08:00
Gerald Lonlas cc556cb5af fix: avoid combo whitelist validation and defer linked prompt checks to execute 2026-03-04 14:32:43 +08:00
Gerald Lonlas a78b9b0e8d fix: handle None prompt values during node validation and message build 2026-03-04 14:21:38 +08:00
Gerald Lonlas 5624847c18 feat: add connect-node web controls, docs, and client tests 2026-03-04 14:04:45 +08:00
Gerald Lonlas e99858f857 feat: implement LMStudio connect, text-gen and image-to-text nodes 2026-03-04 14:04:42 +08:00
Gerald Lonlas 8b253a85f4 feat: add LMStudio OpenAI client helpers and safe seed handling 2026-03-04 14:04:34 +08:00
Gerald Lonlas 8d891c4403 chore: bootstrap package metadata and shared io types 2026-03-04 14:04:17 +08:00