fix(dify-builder): only a localhost host marks an endpoint as a placeholder

_PLACEHOLDER_URL_RE matched "localhost" as a substring anywhere in the
URL, so a real tunnel host like abc123.localhost.run was wrongly
flagged; the check now parses the URL and only matches when the host
itself is localhost or ends with .localhost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Charles YaoandClaude Opus 5 committed 2026-09-22 02:16:28 -07:00
1 parent 291651b747
commit da338dd5fd
2 files changed
+27 -3

No files matched your search

+23 -3
View File
@@ -11,6 +11,7 @@ import re
from collections.abc import Callable, Sequence
from datetime import UTC, datetime
from typing import Any
from urllib.parse import urlsplit
from core.app.app_config.entities import ModelConfig
from core.dify_builder.contract import ResourceOption
@@ -574,21 +575,40 @@ def build_nodes(
# Hosts / tokens an LLM writes when it does not know the real endpoint. A Dify
# template (``{{#node.var#}}``) is a real reference and is NOT a placeholder;
# a bare ``{tenant}`` or ``<your-domain>`` is.
# a bare ``{tenant}`` or ``<your-domain>`` is. ``localhost`` is deliberately NOT
# here -- it names the *host*, not any substring, so it is checked separately
# against the parsed hostname (see ``_is_localhost_host``): a real tunnel host
# like ``abc123.localhost.run`` must not match just because "localhost" is a
# substring of it.
_PLACEHOLDER_URL_RE = re.compile(
r"(^|[./-])example\.(com|org|net)\b" # api.example.com, example.org
r"|your[-_]?(api|domain|server|host|company)" # your-api.com
r"|placeholder|localhost"
r"|placeholder"
r"|<[^>]+>" # <your-domain>
r"|(?<!\{)\{(?!\{)[^{}#]*\}(?!\})", # {tenant}, but not {{#s.x#}}
re.IGNORECASE,
)
def _is_localhost_host(url: str) -> bool:
"""True when the URL's HOST -- not merely a substring of the URL -- is
``localhost`` or a ``*.localhost`` name, ignoring any port. A hostname
like ``abc123.localhost.run`` is a real, routable tunnel domain (the
reserved ``.localhost`` TLD requires it to be the final label) and must
not match."""
try:
host = urlsplit(url).hostname
except ValueError:
return False
return host is not None and (host == "localhost" or host.endswith(".localhost"))
def _is_placeholder_endpoint(url: str) -> bool:
"""True for a URL the model invented rather than one the user supplied."""
text = (url or "").strip()
return not text or _PLACEHOLDER_URL_RE.search(text) is not None
if not text:
return True
return _PLACEHOLDER_URL_RE.search(text) is not None or _is_localhost_host(text)
def _ground_placeholder_endpoints(intents: list[MutationIntent]) -> list[str]:
@@ -599,5 +599,9 @@ def test_is_placeholder_endpoint_recognises_the_usual_inventions():
assert build._is_placeholder_endpoint("https://<your-domain>/generate")
assert build._is_placeholder_endpoint("https://api.acme.com/{tenant}/x")
assert build._is_placeholder_endpoint("")
assert build._is_placeholder_endpoint("http://localhost:11434/api") # the host IS localhost
assert not build._is_placeholder_endpoint("https://api.openai.com/v1")
assert not build._is_placeholder_endpoint("{{#s.endpoint#}}/x") # a Dify template is a real reference
assert not build._is_placeholder_endpoint(
"https://abc123.localhost.run/webhook"
) # a real tunnel domain, not the localhost host