From 7f3f062be9b993b5c4bdeb9c01bfab575051bc56 Mon Sep 17 00:00:00 2001 From: jojodecayz <121620462+jojodecayz@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:26:55 -0700 Subject: [PATCH] fix(generate): skip deprecated partner endpoints in the openapi registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_registry()` built an Endpoint for every allowlisted `/proxy` op with no deprecation check, so a retired endpoint would surface through `comfy generate` (list + run) if it lingered in the allowlist or was deprecated upstream after a `generate refresh`. comfy-api flags deprecation in the operation SUMMARY, not the machine `deprecated` flag. Mirrors the Cloud MCP partner-catalog generator's deprecation filter for cross-surface parity. - Add `_is_deprecated_op(op)`: true when `op.deprecated is True` OR the SUMMARY matches /deprecated/i. Summary-only (not description) is deliberate — the live `stability/.../generate/sd3` endpoint is current ("Stable Diffusion 3.5") yet its description mentions the older SD 3.0 API being deprecated; a description-wide scan would wrongly drop it. - Skip deprecated ops in `_registry()`, same silent-skip contract as the existing missing-node case. - Tests: `_is_deprecated_op` cases incl. the sd3 false-positive guard, and a registry test proving a deprecated allowlisted endpoint is dropped while its live sibling survives. Co-Authored-By: Claude Opus 4.8 (1M context) --- comfy_cli/command/generate/spec.py | 26 ++++++++++ tests/comfy_cli/command/generate/test_spec.py | 51 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/comfy_cli/command/generate/spec.py b/comfy_cli/command/generate/spec.py index a2c25036..c68c51b8 100644 --- a/comfy_cli/command/generate/spec.py +++ b/comfy_cli/command/generate/spec.py @@ -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() @@ -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 {} diff --git a/tests/comfy_cli/command/generate/test_spec.py b/tests/comfy_cli/command/generate/test_spec.py index e25a0f86..d94ea5fa 100644 --- a/tests/comfy_cli/command/generate/test_spec.py +++ b/tests/comfy_cli/command/generate/test_spec.py @@ -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()