Commit Graph

14 Commits

Author SHA1 Message Date
Jedrzej Kosinski
aad3446c17 DynamicOutputs: allow selectors to reach nested DynamicCombo/DynamicSlot
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Schema.validate() now walks DynamicCombo.Input / DynamicSlot.Input options
recursively when building the selector-target map, so DynamicOutputs.ByKey /
BySlot can address a dynamic input nested inside another dynamic option via
its dotted path (e.g. selector="outer.inner_slot"). The runtime dispatch /
type resolver already handled dotted-path nested inputs; this just removes
the schema-level rejection.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-02 08:13:16 -07:00
Jedrzej Kosinski
22d467dc84 DynamicOutputs: replace FromInput with BySlot; outputs always declared in Schema.outputs
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
ByKey already covered literal-driven dispatch (Combo/DynamicCombo/String);
add BySlot as the symmetric resolved-type-driven form (mirrors DynamicSlot).

Inputs no longer carry output declarations. DynamicCombo.Option / DynamicSlot.Option
go back to {key|when, inputs} only — outputs always live on the corresponding
DynamicOutputs entry in Schema.outputs. Validation enforces that ByKey option keys
align with the referenced DynamicCombo's keys and BySlot option 'when' types are a
subset of the referenced DynamicSlot's accepted types (including when=None).

Removes FromInput/_select_from_input_outputs/_from_input_as_dict and the
option-level output serialization helpers.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 22:39:16 -07:00
Jedrzej Kosinski
cca4119fdf DynamicOutputs: tighter integration with DynamicCombo / DynamicSlot via FromInput
Lets dynamic-input nodes co-declare per-option outputs so the option list is a
single source of truth for both inputs and outputs.

* DynamicCombo.Option / DynamicSlot.Option gain an optional outputs=[...] list.
* DynamicOutputs.FromInput(input_id) is a positional placeholder in outputs[]
  that resolves to the referenced input's active-option outputs at finalize
  time. DynamicCombo selects by literal value; DynamicSlot selects by the
  upstream slot's resolved type (or when=None when unlinked).
* get_finalized_class_outputs gains schema_inputs / live_input_types and the
  TypeResolver / execute() compute live_input_types only when at least one
  FromInput → DynamicSlot is present.
* Schema.validate(): FromInput must reference an existing DynamicCombo /
  DynamicSlot input, each input may be referenced at most once, and option
  output ids stay globally unique.
* V1 info synthesizes the dynamic_outputs entry as kind='by_key' for combos
  and kind='by_slot' for slots, inlining the option outputs.

Tests: combo + slot FromInput finalization, FromInput validation (missing /
duplicate / id collision), and TypeResolver end-to-end picks for both kinds.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 21:10:17 -07:00
Jedrzej Kosinski
188065c011 DynamicOutputs: fix block_execution sizing, harden validation, trim comments
Address code-review feedback:

* Fix pre-existing bug surfaced by this work: a V3 node returning a bare
  ExecutionBlocker (normalized to NodeOutput(block_execution=msg) with no
  positional/named result) was silently dropped in get_output_from_returns.
  block_execution now takes precedence and shapes the blocker tuple to the
  active output count first.
* Reject malformed linked-input source node ids (non-string) up front in
  validate_inputs as 'bad_linked_input' instead of letting them fall into
  the type resolver.
* Schema.validate(): reject duplicate DynamicOutputs group ids.
* Drop a couple of over-defensive / restating comments.

New tests: blocker sizing through both the NodeOutput and bare-blocker paths
for dynamic nodes, and duplicate-group-id schema rejection.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 20:48:08 -07:00
Jedrzej Kosinski
54f87a09a5 DynamicOutputs.ByKey: schema, type resolver, and execution wiring
Introduce a per-prompt finalized output list for V3 nodes whose active
output shape depends on a literal input. First slice:

* Schema:
  - io.DynamicOutputs.ByKey(id, selector, options=[io.DynamicOutputs.Option(...)])
  - Validates: selector input exists; output ids unique within and across
    static + dynamic branches; no overlap between branches.
  - get_v1_info() advertises dynamic groups separately via NodeInfoV1.dynamic_outputs;
    static RETURN_TYPES / OUTPUT_IS_LIST stay as the always-present prefix.
* NodeOutput:
  - New NodeOutput.from_named({id: value}) for dynamic nodes; engine reorders
    against the finalized active output list. Mixing positional+named raises.
* Execution:
  - execute() computes FinalizedOutputs once per attempt and threads it
    through initial / pending-async / pending-subgraph output paths.
  - merge_result_data / get_output_data / get_output_from_returns size
    blocker tuples and concat behaviour against the finalized list.
  - validate_inputs rejects positional links whose slot index is outside
    the upstream node's active output count as 'bad_linked_output'.
* TypeResolver:
  - resolve_output_type and is_output_list consult the finalized output
    list when present; finalized_output_count() exposes the active count
    for validation.

Covered by new unit tests under tests-unit/comfy_api_test and
tests-unit/execution_test.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 20:34:27 -07:00
Jedrzej Kosinski
8cac12afa8 DynamicSlot: support required slots and always forceInput
- Add `optional` kwarg to DynamicSlot.Input (default True). When False,
  declaring a when=None Option is rejected because the unconnected branch
  is unreachable.
- Always publish `forceInput=True` on the slot itself. slotType may
  include widget-capable types (INT/STRING/etc.) but a DynamicSlot is
  meant to look like a connection point, never a widget.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 19:28:15 -07:00
Jedrzej Kosinski
346ee898cb DynamicSlot: forbid AnyType inside list/MultiType when
Grouping AnyType with concrete types conflates the known-type case with
the unresolvable-wildcard case under one branch and muddies slotType.
AnyType must stand alone as when=io.AnyType so the two states stay
distinct in both dispatch and the serialized schema.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 19:18:10 -07:00
Jedrzej Kosinski
16dd7d115c DynamicSlot: address code review
Bug fixes:
- execution.py validate_inputs() now calls _select_option for DynamicSlot
  links instead of validating against slotType. Old code accepted any
  concrete upstream type whenever AnyType was enumerated (slotType
  contained '*', which validate_node_input treats as accept-anything).
  Verified end-to-end: LATENT into an IMAGE+AnyType slot is now rejected.
- Thread live_input_types through get_input_data so custom V3
  validate_inputs() sees the same DynamicSlot branch that finalization
  picked, instead of re-finalizing without resolver context.
- DynamicSlot.Option._when_types is now an ordered tuple (preserves
  author declaration order); _slot_io_type/slotType ordering was
  previously nondeterministic via frozenset iteration.

Design:
- DynamicSlot.Input.get_all() now returns [self] + children, matching
  Autogrow / DynamicCombo so consumers like PriceBadge work uniformly.
- Enforce per-option type uniqueness in DynamicSlot.Input: each io_type
  may appear in at most one option's 'when', and at most one option may
  declare when=None. Removes the ambiguous first-match-on-overlap case
  for single concrete types; ordering still matters when upstream is a
  multi-type union.
- Reject non-Option entries in options=[...] explicitly.

Polish:
- Trim verbose DynamicSlot docstrings and inline comments.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 18:34:30 -07:00
Jedrzej Kosinski
d91c1d8d48 DynamicSlot: typed option dispatch driven by TypeResolver
Replaces the old connected/unconnected fixed-child DynamicSlot with a
type-keyed option list. Each Option declares a 'when' condition (None,
io.AnyType, a single ComfyType, a list, or a MultiType.Input) and the
child inputs revealed when that condition matches the slot's resolved
upstream type.

Selection happens at schema-finalization time using live_input_types
computed by TypeResolver, so API-only workflows (no frontend) get the
same expansion the UI would.

- _io.py: redesign DynamicSlot.Input / Option; auto-derive slotType as
  the union of all non-None when sets; expose it via as_dict so the
  frontend knows what types are accepted; the class io_type stays
  COMFY_DYNAMICSLOT_V3 as the parse-time dispatch tag.
- type_resolver.py: return the auto-derived _slot_io_type for
  DynamicSlot.Input; document the AnyType (*) limitation.
- execution.py: validate links into a DynamicSlot against slotType,
  not the dispatch tag COMFY_DYNAMICSLOT_V3.
- tests: new test_dynamic_slot.py + regression coverage in
  test_type_resolver.py.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 17:56:32 -07:00
Jedrzej Kosinski
15f55f1b24 Drop register_dynamic_input_func legacy-arity shim
register_dynamic_input_func is a private helper (underscore module, not
in __all__, not re-exported). Its only callers are the three core
setup_dynamic_input_funcs() registrations, all of which were updated
together. No third party can be relying on the old 5-arg signature, so
the inspect.signature shim and accompanying backward-compat tests are
over-engineered.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 16:43:08 -07:00
Jedrzej Kosinski
e01b335e39 TypeResolver: address code review (link parsing + slot_idx guard + back-compat shim)
* Add _parse_link helper validating both node_id (str) and slot_idx (int,
  rejecting bool) so malformed API JSON (e.g. ['n1', '0']) degrades to
  AnyType instead of crashing with TypeError.
* Add slot_idx type guards in resolve_output_type and is_output_list.
* Extract _get_class_def_for_node helper to dedupe node/class lookup
  across resolve_output_type, is_output_list, get_declared_slot_io_type.
* register_dynamic_input_func now detects 5-argument legacy callables
  via inspect.signature and silently wraps them; preserves backward
  compatibility for any custom node that registered its own dynamic
  input expansion against the pre-live_input_types signature.
* Tests: malformed link (str slot idx, wrong arity), bad slot type
  directly to resolve_output_type, non-string class_type. Tests for
  the legacy 5-arg shim and the modern 6-arg passthrough, including
  callables with uninspectable signatures.

Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 16:30:44 -07:00
Jedrzej Kosinski
431fadb520
fix(api-io): serialize MultiCombo multi_select as object config (#13484)
* fix(api-io): serialize MultiCombo multi_select as object config
* fix: remove dead code and redundant top-level keys from MultiCombo serialization
* fix: correct skip warning to mention comfy_entrypoint, remove nonexistent NODES_LIST
* fix: validate MultiCombo list values against options individually
* fix: gate multiselect validation on schema config, improve error message, add tests

---------

Co-authored-by: Ni-zav <ni-zav@users.noreply.github.com>
Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-05-05 13:58:32 -07:00
Christian Byrne
f1f9763b4c
Add get_duration method to Comfy VIDEO type (#8122)
* get duration from VIDEO type

* video get_duration unit test

* fix Windows unit test: can't delete opened temp file
2025-05-15 00:11:41 -04:00
Christian Byrne
cd18582578
Support saving Comfy VIDEO type to buffer (#7939)
* get output format when saving to buffer

* add unit tests for writing to file or stream with correct fmt

* handle `to_format=None`

* fix formatting
2025-05-04 23:26:57 -04:00