Cleanup pass over the previous commit. No behaviour change except the
cache header noted below.
app/api_docs.py (111 -> 59 lines):
- Cut the rationale comments. The file was ~32% commentary against 2-6%
in neighbouring app/ modules, and most of it argued decisions that
belong in the PR rather than the source, including a TODO explaining
why an SRI hash could not be computed.
- Inline the spec and bundle URLs into the HTML. The __TOKEN__ replace()
machinery existed only to hoist two string literals, and then needed a
comment defending its own existence.
- Drop the hand-rolled <style> block for two inline style attributes.
- Drop the os.path.isfile guard. FileResponse already answers a missing
file with a 404, so the check was a second stat for the same result.
- Serve the spec with Cache-Control: no-cache rather than no-store.
Both mean "never use a stale copy", but no-store also forbids storing
it, so every docs page load re-transferred all 230 KB. no-cache lets
the browser revalidate against the ETag FileResponse already sets; an
unchanged spec now costs a 304 instead of a full download.
tests-unit/server_test/test_api_docs.py (11 tests -> 6, 144 -> 70 lines):
- Drop the local copy of server.py's /api prefix loop and the two tests
that depended on it. They exercised the copy, not the real loop, so
they would have stayed green through a change to the thing they
claimed to protect. What actually makes prefixing work is that the
spec URL is relative, which is now asserted directly.
- Drop the static-catch-all test. It asserted aiohttp's own route
precedence against a synthetic app, and could not fail if the
registration call moved after the catch-all in server.py.
- Drop the tautological SPEC_PATH assertion, already covered by fetching
the spec through the route.
- Loosen the fallback assertions, which pinned the exact quoting and
inline-handler style of the HTML.
Claude-Session: https://claude.ai/code/session_01BvUveU9ofyGrSz3QxYeecB
openapi.yaml has been linted in CI but never reachable over HTTP. Add
--enable-api-docs (off by default) to serve it at /openapi.yaml and render
it at /api-docs.
The viewer is Redoc rather than Swagger UI specifically because it has no
request-execution feature. The local server is unauthenticated by default,
so a docs page with "Try it out" would give one-click access to
/api/interrupt, /api/free and DELETE /api/userdata/{file}.
Notes on the implementation:
- Routes are registered on PromptServer's route table, not on the app, so
they land before the web.static('/') catch-all that would shadow them.
This also means they are served at both /openapi.yaml and
/api/openapi.yaml; the docs page references the spec by a relative URL so
it resolves from either mount point.
- The spec path resolves from __file__, since ComfyUI is routinely launched
from other directories.
- Cache headers are set explicitly: the cache_control middleware only
special-cases js/css/images, so a .yaml response would otherwise be
served stale after an edit.
- /docs is left alone; it already serves embedded node help content.
The Redoc bundle comes from a pinned CDN URL, so the page needs outbound
network access. Offline installs still get the spec itself, and the page
degrades to a notice pointing at it.
Claude-Session: https://claude.ai/code/session_01BvUveU9ofyGrSz3QxYeecB
Add a ModelAttentionBackend node to manually select the attention for models in the workflows. Currently supports pytorch attention or comfy kitchen attention.
Add --use-ck-attention to enable comfy kitchen attention as the default attention backend for all models (might break some).
VAEDecode unwraps a NestedTensor latent (video/audio pair) to its
video component before calling vae.decode(). VAEDecodeTiled skipped
this unwrap and passed the NestedTensor straight into
vae.decode_tiled(), which fails deep in the MiniMax H3 video VAE when
a real tensor's .to() is called with the NestedTensor as an argument.
Fixes#15468.
* Implement tags_all/tags_any/tags_none on the assets list API (BE-6600)
Adds the three canonically-named tag filter params to GET /api/assets and
GET /api/assets/tags/refine:
- tags_all: asset carries every tag (replaces include_tags)
- tags_any: asset carries at least one tag (new)
- tags_none: asset carries no tag (replaces exclude_tags)
Clauses intersect; tags_none always wins. include_tags/exclude_tags remain
as permanent deprecated aliases and behave exactly as before when used on
their own.
Invalid combinations return 400 INVALID_TAG_FILTER, but only when the
request uses at least one new-name parameter (non-empty after
normalisation):
- mixed spellings of one slot (include_tags with tags_all, exclude_tags
with tags_none)
- the same tag in the effective all-list and none-list (query can never
match)
Old-names-only requests gain no new error paths: include_tags=a&exclude_tags=a
still returns an empty 200. tags_any/tags_none overlap stays valid (dead
term, not a dead query).
* Address review findings: positional-compat, deprecation metadata, test matrix
- Move any_tags to the end of the four touched signatures: inserting it
mid-signature silently misbound pre-existing positional callers (e.g.
a caller passing name_contains positionally would have it consumed as
any_tags).
- Mark include_tags/exclude_tags Field(deprecated=True) on both list
schemas so generated schema metadata matches the contract, not just a
comment (schemas_out.py already uses this form for Asset.name).
- Add tests: legal cross-slot old/new combinations, repeated query-key
concatenation (pins Core behavior; outside the cross-platform
contract), tags_any two-page cursor consistency (total/has_more/
no-overlap), refine-route mixed-spelling rejection + legacy-conflict
preservation, and schema deprecation metadata.
* Pin tag-value opacity: case-sensitive matching, byte-exact conflict check
The prod tag survey (~/comfy/prod-model-tag-shape.md) found live
case-distinct tag pairs (SEEDVR2/seedvr2) that resolve differently, so
the contract now states tag values are opaque byte-strings. Pin that:
case-distinct tags filter separately, and a case-distinct all/none pair
is not an INVALID_TAG_FILTER conflict.
* Document tags_all/tags_any/tags_none in openapi.yaml, deprecate aliases
Add the three tag-filter parameters to both listAssets and
getAssetTagHistogram parameter blocks and mark include_tags/exclude_tags
deprecated: true, keeping the spec in step with the runtime schemas so
generated clients can discover the new filters while the aliases stay
present for existing consumers.
* Move schemas_in import to module scope in test_list_filter
Review feedback: no import cycle requires the local import.
* Silence per-request DeprecationWarning in the tag-filter remap shim
Reading the deprecated include_tags/exclude_tags fields by attribute
fires pydantic's DeprecationWarning on every list/refine request even
for callers using only the new names. The warning is aimed at API
clients, not the server's own remap; read via model_dump instead.
* Cap tag-filter lists at 100 entries, all spellings
Review finding: unbounded tag lists fan out into one correlated EXISTS
per tag on both page and count statements. Cap each list at 100
normalized entries with 400 INVALID_TAG_FILTER naming the parameter.
Applies to the legacy spellings as well — a deliberate, decided
exception to the old-names-behave-identically rule, since a cap only on
new names would leave the same fan-out reachable through the aliases.
* Strip process narration from comments
Comments carried decision dates, contract cross-references, and review
context. Keep only the constraints the code cannot show, one line each.