Skip to content
Closed
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
9 changes: 8 additions & 1 deletion agent/model_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,10 +631,17 @@ def detect_local_server_type(base_url: str, api_key: str = "") -> Optional[str]:
import httpx

normalized = _normalize_base_url(base_url)

# Resolve localhost to IPv4 to avoid 2s IPv6 timeout on Windows dual-stack.
normalized = normalized.replace("://localhost:", "://127.0.0.1:")
normalized = normalized.replace("://localhost/", "://127.0.0.1/")
if normalized.endswith("://localhost"):
normalized = normalized[:-len("localhost")] + "127.0.0.1"

server_url = normalized
if server_url.endswith("/v1"):
server_url = server_url[:-3]
lmstudio_url = _lmstudio_server_root(base_url)
lmstudio_url = _lmstudio_server_root(normalized)

headers = _auth_headers(api_key)

Expand Down
62 changes: 61 additions & 1 deletion tests/agent/test_model_metadata_local_ctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,67 @@ def test_native_api_base_url_is_not_doubled(self):
result = detect_local_server_type("http://localhost:1234/api/v1")

assert result == "lm-studio"
assert client_mock.get.call_args_list[0].args[0] == "http://localhost:1234/api/v1/models"
assert client_mock.get.call_args_list[0].args[0] == "http://127.0.0.1:1234/api/v1/models"


class TestDetectLocalServerTypeLocalhostIPv4:
"""detect_local_server_type should resolve localhost to 127.0.0.1."""

def test_localhost_resolved_to_ipv4(self):
"""Probes should use 127.0.0.1, not localhost, to avoid IPv6 timeout."""
from agent.model_metadata import detect_local_server_type

resp = MagicMock()
resp.status_code = 200

client_mock = MagicMock()
client_mock.__enter__ = lambda s: client_mock
client_mock.__exit__ = MagicMock(return_value=False)
client_mock.get.return_value = resp

with patch("httpx.Client", return_value=client_mock):
detect_local_server_type("http://localhost:8317/v1")

for call in client_mock.get.call_args_list:
url = call[0][0]
assert "localhost" not in url, f"Probe URL still uses localhost: {url}"
assert "127.0.0.1" in url

def test_non_localhost_urls_unchanged(self):
"""Non-localhost URLs should not be modified."""
from agent.model_metadata import detect_local_server_type

client_mock = MagicMock()
client_mock.__enter__ = lambda s: client_mock
client_mock.__exit__ = MagicMock(return_value=False)
resp = MagicMock()
resp.status_code = 404
client_mock.get.return_value = resp

with patch("httpx.Client", return_value=client_mock):
detect_local_server_type("http://192.168.1.100:8080")

for call in client_mock.get.call_args_list:
url = call[0][0]
assert "192.168.1.100" in url

def test_127_0_0_1_urls_unchanged(self):
"""URLs already using 127.0.0.1 should pass through unchanged."""
from agent.model_metadata import detect_local_server_type

client_mock = MagicMock()
client_mock.__enter__ = lambda s: client_mock
client_mock.__exit__ = MagicMock(return_value=False)
resp = MagicMock()
resp.status_code = 404
client_mock.get.return_value = resp

with patch("httpx.Client", return_value=client_mock):
detect_local_server_type("http://127.0.0.1:8317")

for call in client_mock.get.call_args_list:
url = call[0][0]
assert "127.0.0.1" in url


class TestFetchEndpointModelMetadataLmStudio:
Expand Down
Loading