[PR #15] [MERGED] Parse DYNAMIC_COMBO + AUTOGROW inputs into real V3 IO objects #15

Closed
opened 2026-10-26 22:28:55 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-remote-nodes/pull/15
Author: @Kosinkadink
Created: 5/16/2026
Status: ✅ Merged
Merged: 5/16/2026
Merged by: @Kosinkadink

Base: main ← Head: feat/dynamic-combo-autogrow-parser


📝 Commits (1)

  • 37b77f8 Parse DYNAMIC_COMBO + AUTOGROW inputs into real V3 IO objects

📊 Changes

2 files changed (+705 additions, -19 deletions)

View changed files

➕ notes/run_dynamic_combo_parser.py (+526 -0)
📝 proxy_node.py (+179 -19)

📄 Description

What

proxy_node._parse_input_spec previously had explicit branches only for the primitive V3 input types (STRING / INT / FLOAT / BOOLEAN / COMBO / IMAGE / VIDEO / AUDIO / MASK); every other io_type string — including the two V3 dynamic input types DYNAMIC_COMBO and AUTOGROW — fell through to the opaque IO.Custom(io_type).Input bucket.

Server-side runtime validation already worked (rules-extraction at _rules_from_input_spec walks __branches__ and __template__), but on the frontend every Topaz / Magnific / Vidu / Luma / Wan / Bria / Grok / ElevenLabs / HitPaw provider that declares a DynamicCombo rendered as one opaque socket instead of the branch-selector dropdown with per-branch sub-widgets the V3 IO is designed to produce.

How

Refactor the parser so the construction logic is shared between two entry points (top-level descriptor inputs use the 2-tuple [io_type, options] form; DynamicCombo branch sub-inputs use the 3-tuple [name, io_type, options] form) and add real handlers for both dynamic types:

  • _parse_input_spec(name, spec, optional) — top-level entry, signature unchanged. Strips the [io_type, options] head and delegates.
  • _parse_named_input_spec(spec, optional) — branch-sub-input entry. Strips the leading name and delegates.
  • _build_input(name, io_type, options, optional) — shared constructor dispatch. The new DYNAMIC_COMBO branch builds IO.DynamicCombo.Input(name, options=[IO.DynamicCombo.Option(key, inputs=[...]), ...]) by recursing into _parse_named_input_spec for each branch sub-input; the new AUTOGROW branch builds IO.Autogrow.Input(name, template=IO.Autogrow.TemplatePrefix(...)) (or TemplateNames) by recursing into _parse_input_spec for the template entry.

The flattened RNP wire dialect for AUTOGROW (template is a flat [io_type, options] entry directly, with prefix / min / max or names / min as siblings of template) is what every server provider already emits (e.g. grok.py:_grok_v2_model_subinputs); the parser mirrors that shape rather than the upstream Autogrow.Input.as_dict nested form.

Guardrails

  • Empty branch input lists are valid (Bria moderation's "false" branch carries "inputs": []).
  • The branch selector's id is the outer input id ("moderation", "inputs", etc.) — not always "model".
  • Branch sub-inputs are treated as required at schema-construction time; the wire dialect doesn't express per-branch optionality, and server-side __branches__ enforcement covers runtime presence.
  • AUTOGROW template inputs that are themselves dynamic (DynamicCombo.Input / Autogrow.Input) are rejected at parse time, mirroring the upstream _AutogrowTemplate.__init__ assert.
  • Malformed DYNAMIC_COMBO / AUTOGROW specs return None (caller logs + skips the descriptor) instead of degrading to a single opaque socket — the opaque fallthrough is reserved for genuinely unknown io_type strings, not for known-but-broken dynamic specs.
  • advanced only routes to primitive WidgetInput constructors; the dynamic constructors don't accept it.

Why no capability gate in this PR

The two dynamic io_type strings are already V3-canonical so a legacy client without the parser fix sees the same opaque-socket degradation it sees today (just rendered as Custom("DYNAMIC_COMBO") instead of the real DynamicCombo input). Adding Capability.DYNAMIC_COMBO / Capability.AUTOGROW would require pairing the cap with real descriptor-load-time enforcement (server-side filtering of /object_info or client-side rejection based on required_capabilities); neither exists yet, so adding caps now would just be inert metadata. The cap pair is the right follow-up alongside the enforcement plumbing.

Smoke test

notes/run_dynamic_combo_parser.py — 8 checks covering:

  1. Bria's 2-branch DynamicCombo (with empty "false" branch).
  2. Grok V2's 3-branch DynamicCombo with nested AUTOGROW(IMAGE) per branch.
  3. ElevenLabs' branch mixing custom-IO (voiceN) + primitive STRING.
  4. Synthetic nested DynamicCombo-in-DynamicCombo (defensive recursion coverage).
  5. Malformed-spec rejection path.
  6. Regression: _collect_local_validate output byte-identical before/after.
  7. AUTOGROW rejects dynamic template input (matches upstream assert).
  8. Source-level check that the new helpers + branches exist.

All 8 checks pass; the previously-merged notes/run_model3d_decoder.py (PR #14) also still passes (no regression on the existing IO path).

What this unblocks

Client-side parsing for every server provider that declares a DynamicCombo field, including Bria image-edit / remove-bg moderation, Grok V2 image-edit, Grok VideoReference, Grok VideoExtend, HitPaw video enhance, Magnific portrait_mode / advanced_settings, ElevenLabs T2S / Dialogue model selectors, and ByteDance Seedance2 T2V. These all rendered as opaque sockets today; they now render with real branch-selector dropdowns and per-branch sub-widgets.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-remote-nodes/pull/15 **Author:** [@Kosinkadink](https://github.com/Kosinkadink) **Created:** 5/16/2026 **Status:** ✅ Merged **Merged:** 5/16/2026 **Merged by:** [@Kosinkadink](https://github.com/Kosinkadink) **Base:** `main` ← **Head:** `feat/dynamic-combo-autogrow-parser` --- ### 📝 Commits (1) - [`37b77f8`](https://github.com/Comfy-Org/comfy-remote-nodes/commit/37b77f837afa7d3fcf310b6aeb4b591b0499d682) Parse DYNAMIC_COMBO + AUTOGROW inputs into real V3 IO objects ### 📊 Changes **2 files changed** (+705 additions, -19 deletions) <details> <summary>View changed files</summary> ➕ `notes/run_dynamic_combo_parser.py` (+526 -0) 📝 `proxy_node.py` (+179 -19) </details> ### 📄 Description ## What `proxy_node._parse_input_spec` previously had explicit branches only for the primitive V3 input types (`STRING` / `INT` / `FLOAT` / `BOOLEAN` / `COMBO` / `IMAGE` / `VIDEO` / `AUDIO` / `MASK`); every other `io_type` string — including the two V3 dynamic input types `DYNAMIC_COMBO` and `AUTOGROW` — fell through to the opaque `IO.Custom(io_type).Input` bucket. Server-side runtime validation already worked (rules-extraction at `_rules_from_input_spec` walks `__branches__` and `__template__`), but on the frontend every Topaz / Magnific / Vidu / Luma / Wan / Bria / Grok / ElevenLabs / HitPaw provider that declares a `DynamicCombo` rendered as one opaque socket instead of the branch-selector dropdown with per-branch sub-widgets the V3 IO is designed to produce. ## How Refactor the parser so the construction logic is shared between two entry points (top-level descriptor inputs use the 2-tuple `[io_type, options]` form; DynamicCombo branch sub-inputs use the 3-tuple `[name, io_type, options]` form) and add real handlers for both dynamic types: - `_parse_input_spec(name, spec, optional)` — top-level entry, signature unchanged. Strips the `[io_type, options]` head and delegates. - `_parse_named_input_spec(spec, optional)` — branch-sub-input entry. Strips the leading name and delegates. - `_build_input(name, io_type, options, optional)` — shared constructor dispatch. The new `DYNAMIC_COMBO` branch builds `IO.DynamicCombo.Input(name, options=[IO.DynamicCombo.Option(key, inputs=[...]), ...])` by recursing into `_parse_named_input_spec` for each branch sub-input; the new `AUTOGROW` branch builds `IO.Autogrow.Input(name, template=IO.Autogrow.TemplatePrefix(...))` (or `TemplateNames`) by recursing into `_parse_input_spec` for the template entry. The flattened RNP wire dialect for `AUTOGROW` (template is a flat `[io_type, options]` entry directly, with `prefix` / `min` / `max` or `names` / `min` as siblings of `template`) is what every server provider already emits (e.g. `grok.py:_grok_v2_model_subinputs`); the parser mirrors that shape rather than the upstream `Autogrow.Input.as_dict` nested form. ### Guardrails - Empty branch input lists are valid (Bria moderation's `"false"` branch carries `"inputs": []`). - The branch selector's id is the outer input id (`"moderation"`, `"inputs"`, etc.) — not always `"model"`. - Branch sub-inputs are treated as required at schema-construction time; the wire dialect doesn't express per-branch optionality, and server-side `__branches__` enforcement covers runtime presence. - AUTOGROW template inputs that are themselves dynamic (`DynamicCombo.Input` / `Autogrow.Input`) are rejected at parse time, mirroring the upstream `_AutogrowTemplate.__init__` assert. - Malformed `DYNAMIC_COMBO` / `AUTOGROW` specs return `None` (caller logs + skips the descriptor) instead of degrading to a single opaque socket — the opaque fallthrough is reserved for genuinely unknown io_type strings, not for known-but-broken dynamic specs. - `advanced` only routes to primitive `WidgetInput` constructors; the dynamic constructors don't accept it. ## Why no capability gate in this PR The two dynamic io_type strings are already V3-canonical so a legacy client without the parser fix sees the same opaque-socket degradation it sees today (just rendered as `Custom("DYNAMIC_COMBO")` instead of the real DynamicCombo input). Adding `Capability.DYNAMIC_COMBO` / `Capability.AUTOGROW` would require pairing the cap with real descriptor-load-time enforcement (server-side filtering of `/object_info` or client-side rejection based on `required_capabilities`); neither exists yet, so adding caps now would just be inert metadata. The cap pair is the right follow-up alongside the enforcement plumbing. ## Smoke test `notes/run_dynamic_combo_parser.py` — 8 checks covering: 1. Bria's 2-branch DynamicCombo (with empty `"false"` branch). 2. Grok V2's 3-branch DynamicCombo with nested AUTOGROW(IMAGE) per branch. 3. ElevenLabs' branch mixing custom-IO (`voiceN`) + primitive STRING. 4. Synthetic nested DynamicCombo-in-DynamicCombo (defensive recursion coverage). 5. Malformed-spec rejection path. 6. Regression: `_collect_local_validate` output byte-identical before/after. 7. AUTOGROW rejects dynamic template input (matches upstream assert). 8. Source-level check that the new helpers + branches exist. All 8 checks pass; the previously-merged `notes/run_model3d_decoder.py` (PR #14) also still passes (no regression on the existing IO path). ## What this unblocks Client-side parsing for every server provider that declares a `DynamicCombo` field, including Bria image-edit / remove-bg moderation, Grok V2 image-edit, Grok VideoReference, Grok VideoExtend, HitPaw video enhance, Magnific portrait_mode / advanced_settings, ElevenLabs T2S / Dialogue model selectors, and ByteDance Seedance2 T2V. These all rendered as opaque sockets today; they now render with real branch-selector dropdowns and per-branch sub-widgets. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:28:55 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-remote-nodes#15
No description provided.