Commit Graph
6 Commits
Author SHA1 Message Date
Jedrzej KosinskiandAmp 8a9955be72 feat(serialization): add VIDEO encode + VIDEO_MP4_BASE64 capability
Mirrors the AUDIO sweep: adds encode_video_input / is_video_input
helpers in serialization.py, wires them into proxy_node._encode_one,
and advertises VIDEO_MP4_BASE64 in CLIENT_CAPABILITIES so servers can
require it for VIDEO inputs.

encode_video_input re-encodes through video.save_to(buf, MP4, H264) to
normalize the container — Bria's video endpoints (the first vendor with
VIDEO inputs) only accept mp4_h264. Populates an optional duration_s
extra from get_duration() so servers can pre-validate against duration
bounds without demuxing.

Amp-Thread-ID: https://ampcode.com/threads/T-019e210b-f792-7389-b8db-8e02c81e4295
Co-authored-by: Amp <amp@ampcode.com>
2026-05-13 04:15:49 -07:00
Jedrzej KosinskiandAmp 11a84be06d Derive max_poll_attempts from hard_timeout_s instead of reading wire field
Wire-format rework — pairs with comfy-rnp-server branch
feat/async-polling-helper which dropped descriptor.remote.execution.
max_poll_attempts. The client now derives its poll-loop cap as
ceil((hard_timeout_s + DEFAULT_POLL_GRACE_S) / poll_interval_s)
inside _extract_execution_policy.

Why: coupling a contract bound to a cadence value silently breaks
every time you tune the cadence. A descriptor saying '150 attempts'
shrinks 4x if poll_interval_s drops 2.0 -> 0.5; expressing the
bound as a duration (hard_timeout_s) keeps it invariant. Industry
precedent: OAuth2 device flow §3.5, Kubernetes --timeout, gRPC
deadlines all express long-poll bounds as durations rather than
counts.

Changes:
- protocol.py: drop DEFAULT_MAX_POLL_ATTEMPTS, add
  DEFAULT_SOFT_TIMEOUT_S / DEFAULT_HARD_TIMEOUT_S /
  DEFAULT_POLL_GRACE_S.
- proxy_node.py: import math, derive max_poll_attempts inline in
  _extract_execution_policy from hard_timeout_s + grace /
  poll_interval_s. Falls back to soft_timeout_s when
  hard_timeout_s is absent, then to DEFAULT_HARD_TIMEOUT_S.

No compat shim — RNP/1 is prototype-stage with zero backward-
compat obligation (see comfy-rnp-server/AGENTS.md). Server and
client roll in lockstep.

Verified end-to-end with notes/run_url_fetch_executor.py,
notes/run_recraft_custom_io_executor.py, and the paid
notes/run_recraft_text_to_image_smoke.py (~\.04) after
restarting both processes with the new code on both sides.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1f96-a6f7-729b-9526-82fcebee2e17
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 22:06:14 -07:00
Jedrzej KosinskiandAmp 6b66f6c5a4 Phase A§7 review fixes: tighten encoder gate + harden parsers/decoders
* proxy_node.py: gate `_encode_one` dict-recursion on
  `_dict_contains_tensor` (new helper) + `not is_envelope` so scalar
  custom-IO chain dicts (RECRAFT_V3_STYLE / RECRAFT_CONTROLS) pass
  through untouched and already-encoded envelopes don't get re-traversed
* proxy_node.py: AUTOGROW parser validates `template_spec[0]` is a
  string and emits `log.warning` on every parse-failure path so a
  malformed descriptor surfaces visibly instead of producing a missing
  input
* serialization.py: `decode_svg_envelope` now splits on
  `</svg>\n` (with bare `</svg>` fallback) so an SVG document
  containing `</svg>` in CDATA / `<foreignObject>` / a comment
  isn't shredded mid-frame; matches the `\n` join the server uses
  in `_emit_svg_from_urls`

Amp-Thread-ID: https://ampcode.com/threads/T-019e19fc-748d-76fb-b8fa-00efd7c2d697
Co-authored-by: Amp <amp@ampcode.com>
2026-05-12 16:46:10 -07:00
Jedrzej KosinskiandAmp 9843d63569 Phase A§7: SVG envelope + IO.Autogrow + IO.DynamicCombo parser support
* protocol.py / envelopes.py: add 'svg' heavy type + svg_xml_base64
  encoding + make_svg_envelope() + Capability.SVG_XML_BASE64
* serialization.py: add decode_svg_envelope hydrating
  comfy_extras.nodes_images.SVG (split per-frame on </svg>)
* proxy_node.py: add IO.SVG to _OUTPUT_CLASSES; add SVG / AUTOGROW
  / DYNAMIC_COMBO branches in _parse_input_spec; recursive _encode_one
  so IO.Autogrow dict-of-tensors gets per-slot envelope encoding

Amp-Thread-ID: https://ampcode.com/threads/T-019e19bb-f085-76b8-a5ac-9f9bb43f145f
Co-authored-by: Amp <amp@ampcode.com>
2026-05-11 18:26:21 -07:00
Jedrzej KosinskiandAmp d7e2683e9d feat(parser): Phase-A descriptor extensions for partner-node parity
Three additive parser changes that unblock bulk porting of partner
nodes from comfy_api_nodes/nodes_*.py without breaking existing
descriptors. Spec: notes/rnp-phaseA-spec.md.

A1 — Custom IO pass-through: any unrecognised string io_type now
builds an opaque IO.Custom socket instead of being rejected. Lets
partner-defined types like RECRAFT_STYLE_V3, RECRAFT_COLOR,
Tripo3DModel, Hunyuan3DMesh round-trip between RNP nodes verbatim.
Applies to both inputs (_parse_input_spec) and outputs
(_parse_outputs).

A2 — control_after_generate round-trip: forwarded into IO.Int.Input
and IO.Combo.Input from the input options dict so seed widgets keep
their randomize/increment/fixed dropdown.

A4 — remote.hidden_forwarded opt-in: non-auth hidden markers
(prompt, extra_pnginfo, dynprompt) now only forward into the request
context when explicitly listed in descriptor.remote.hidden_forwarded.
Default = drop. Auth tokens (auth_token_comfy_org, api_key_comfy_org)
and unique_id are always forwarded when declared. Stops large
EXTRA_PNGINFO blobs from shipping unconditionally.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1889-d8bd-732f-8170-b85fd94da503
Co-authored-by: Amp <amp@ampcode.com>
2026-05-11 13:04:50 -07:00
Kosinkadink cf55bd58ae Initial RNP/1 client implementation with Tier 3 features
ComfyUI custom-node package that fetches node descriptors from an
RNP/1 (Remote Node Protocol v1) server and exposes them as native V3
nodes inside ComfyUI.

Includes:
- Manifest + capability handshake, ETag-conditional object_info fetch
- Sync execute_remote and async _run_async_polling flows via
  comfy_api_nodes.util.client.poll_op_raw
- Image / mask / audio / video envelope encode + decode
- Presigned upload helper for >8 MiB inputs
- Tier 3:
  * TASK_LOST detection + idempotent resume — survives an RNP server
    restart mid-task
  * SERVER_BUSY / MAINTENANCE 503 envelopes treated as rate limits
    with Retry-After honoring and friendly per-second progress text
  * Out-of-band url_fetch for URI-only output envelopes per the
    descriptor's remote.url_fetch policy
  * Per-poll auth refresh so long-running tasks pick up rotated tokens

Pairs with Comfy-Org/comfy-rnp-server (private) as the reference
server implementation.
2026-05-11 12:31:05 -07:00