Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions comfy_cli/command/generate/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,26 @@ def _detect_polling(partner: str, response_schema: dict[str, Any]) -> str | None
return None


_DEPRECATED_SUMMARY_RE = _re.compile(r"\bdeprecated\b", _re.IGNORECASE)


def _is_deprecated_op(op: dict[str, Any]) -> bool:
"""Whether a proxy operation is deprecated.

True when the machine ``deprecated`` flag is set OR the operation's
``summary`` says so. Matching the summary (not the description) is
deliberate: the live ``stability/.../generate/sd3`` endpoint is current
("Stable Diffusion 3.5") yet its long description mentions the older SD 3.0
API being deprecated — a description-wide scan would wrongly drop it. The
genuinely retired endpoints (``veo/generate``, ``veo/poll``) carry
"Deprecated. Use … instead." in the summary itself.
"""
if op.get("deprecated") is True:
return True
summary = op.get("summary")
return isinstance(summary, str) and bool(_DEPRECATED_SUMMARY_RE.search(summary))


@lru_cache(maxsize=1)
def _registry() -> dict[str, Endpoint]:
spec = load_raw_spec()
Expand All @@ -338,6 +358,12 @@ def _registry() -> dict[str, Endpoint]:
# All image endpoints are POST; pick the first defined method anyway.
method = "post" if "post" in node else next(iter(node.keys()))
op = node[method]
if _is_deprecated_op(op):
# Never surface a deprecated endpoint through `comfy generate`.
# Same silent-skip contract as the missing-node case above: keep
# retired endpoints off the creative surface even if one lingers in
# the allowlist or is deprecated upstream after a `generate refresh`.
continue
partner = endpoint_id.split("/", 1)[0]

req_body = op.get("requestBody") or {}
Expand Down
51 changes: 51 additions & 0 deletions tests/comfy_cli/command/generate/test_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,54 @@ def test_find_property_descends_top_level_composition():
nested = {"anyOf": [{"oneOf": [{"properties": {"model": {"enum": ["m2"]}}}]}]}
assert spec._find_property(nested, "model") == {"enum": ["m2"]}
assert spec._find_property({"allOf": [{"type": "object"}]}, "model") is None


def test_is_deprecated_op():
# Machine flag wins.
assert spec._is_deprecated_op({"deprecated": True}) is True
# Upstream convention: deprecation announced in the SUMMARY (this is how
# the real veo/generate + veo/poll ops flag themselves — no machine flag).
assert (
spec._is_deprecated_op({"summary": "Generate a video. Deprecated. Use /proxy/veo/{modelId}/generate."}) is True
)
# sd3 guard: a live endpoint whose DESCRIPTION merely mentions deprecation
# of an older API must NOT be treated as deprecated.
assert (
spec._is_deprecated_op(
{
"summary": "Stable Diffusion 3.5",
"description": "As of April 17, 2025, we have deprecated the Stable Diffusion 3.0 APIs.",
}
)
is False
)
# Ordinary live op.
assert spec._is_deprecated_op({"summary": "Create image"}) is False
assert spec._is_deprecated_op({}) is False


def test_registry_skips_deprecated_endpoint(monkeypatch, tmp_path):
"""A deprecated allowlisted endpoint is dropped from the registry while a
sibling live endpoint on the same partner survives."""
body = (
'{"openapi":"3.1.0","servers":[{"url":"https://api.comfy.org"}],"paths":{'
'"/proxy/openai/images/generations":{"post":{"summary":"Create image. Deprecated. Use edits.",'
'"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},'
'"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}}}}}},'
'"/proxy/openai/images/edits":{"post":{"summary":"Edit image",'
'"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},'
'"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}}}}}}'
"}}"
)
cache = tmp_path / "openapi-cache.yml"
cache.write_text(body, encoding="utf-8")
monkeypatch.setattr(spec, "_USER_CACHE", cache)
spec.load_raw_spec.cache_clear()
spec._registry.cache_clear()
try:
ids = {e.id for e in spec.list_endpoints()}
assert "openai/images/generations" not in ids, "deprecated endpoint must be skipped"
assert "openai/images/edits" in ids, "live sibling endpoint must survive"
finally:
spec.load_raw_spec.cache_clear()
spec._registry.cache_clear()
Loading