Repository: huggingface/huggingface_hub (python)

Issue:
[BUG] HF_ENDPOINT not correctly handled in get_hf_file_metadata
### Describe the bug

### Describe the bug

When testing [NanoVLM](https://github.com/huggingface/nanoVLM), I noticed that when xet_file_data is None, `HF_ENDPOINT` works correctly (e.g., for datasets). However, for some models, the metadata response still contains hardcoded `huggingface.co` URLs in `XetFileData.refresh_route`.

Example metadata log :
```
url: https://<HF_ENDPOINT>/HuggingFaceTB/SmolLM2-360M-Instruct/resolve/main/model.safetensors
xet_file_data: XetFileData(
    file_hash='a24f4ebae72bd0ae8ea5962912e838391092a7408702fa7c38291ab61026143e',
    refresh_route='https://huggingface.co/api/models/HuggingFaceTB/SmolLM2-360M-Instruct/xet-read-token/cxx3xx'
)
```

Since HF_ENDPOINT is meant to replace all uses of https://huggingface.co, the client should not be receiving hardcoded huggingface.co URLs in metadata fields.


### Reproduction

_No response_

### Logs

```shell

```

### System info

```shell
- huggingface_hub version: 0.33.0
- Platform: Linux-5.15.0-134-generic-x86_64-with-glibc2.35
- Python version: 3.12.11
```

Pull request:
Override xet refresh route's base URL with HF Endpoint
fixes #3168.
as mentioned by @coyotte508 in this (private) [comment](https://github.com/huggingface-internal/moon-landing/pull/14209#issuecomment-3000994756) , client-side handling is much simpler and future-proof, introducing a new server header would be overkill and doesn't really justify a change server-side.


cc @bpronan 

Patch:
diff --git a/src/huggingface_hub/file_download.py b/src/huggingface_hub/file_download.py
index 98748fd4..ebadc875 100644
--- a/src/huggingface_hub/file_download.py
+++ b/src/huggingface_hub/file_download.py
@@ -1407,6 +1407,7 @@ def get_hf_file_metadata(
     library_version: Optional[str] = None,
     user_agent: Union[Dict, str, None] = None,
     headers: Optional[Dict[str, str]] = None,
+    endpoint: Optional[str] = None,
 ) -> HfFileMetadata:
     """Fetch metadata of a file versioned on the Hub for a given url.
 
@@ -1432,6 +1433,8 @@ def get_hf_file_metadata(
             The user-agent info in the form of a dictionary or a string.
         headers (`dict`, *optional*):
             Additional headers to be sent with the request.
+        endpoint (`str`, *optional*):
+            Endpoint of the Hub. Defaults to <https://huggingface.co>.
 
     Returns:
         A [`HfFileMetadata`] object containing metadata such as location, etag, size and
@@ -1471,7 +1474,7 @@ def get_hf_file_metadata(
         size=_int_or_none(
             r.headers.get(constants.HUGGINGFACE_HEADER_X_LINKED_SIZE) or r.headers.get("Content-Length")
         ),
-        xet_file_data=parse_xet_file_data_from_response(r),  # type: ignore
+        xet_file_data=parse_xet_file_data_from_response(r, endpoint=endpoint),  # type: ignore
     )
 
 
@@ -1531,7 +1534,7 @@ def _get_metadata_or_catch_error(
         try:
             try:
                 metadata = get_hf_file_metadata(
-                    url=url, proxies=proxies, timeout=etag_timeout, headers=headers, token=token
+                    url=url, proxies=proxies, timeout=etag_timeout, headers=headers, token=token, endpoint=endpoint
                 )
             except EntryNotFoundError as http_error:
                 if storage_folder is not None and relative_filename is not None:
diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py
index e53fcfba..500b5d13 100644
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -5392,6 +5392,7 @@ class HfApi:
             library_name=self.library_name,
             library_version=self.library_version,
             user_agent=self.user_agent,
+            endpoint=self.endpoint,
         )
 
     @validate_hf_hub_args
diff --git a/src/huggingface_hub/utils/_xet.py b/src/huggingface_hub/utils/_xet.py
index e80b0e80..3dcf9906 100644
--- a/src/huggingface_hub/utils/_xet.py
+++ b/src/huggingface_hub/utils/_xet.py
@@ -26,7 +26,9 @@ class XetConnectionInfo:
     endpoint: str
 
 
-def parse_xet_file_data_from_response(response: requests.Response) -> Optional[XetFileData]:
+def parse_xet_file_data_from_response(
+    response: requests.Response, endpoint: Optional[str] = None
+) -> Optional[XetFileData]:
     """
     Parse XET file metadata from an HTTP response.
 
@@ -52,7 +54,9 @@ def parse_xet_file_data_from_response(response: requests.Response) -> Optional[X
             refresh_route = response.headers[constants.HUGGINGFACE_HEADER_X_XET_REFRESH_ROUTE]
     except KeyError:
         return None
-
+    endpoint = endpoint if endpoint is not None else constants.ENDPOINT
+    if refresh_route.startswith(constants.HUGGINGFACE_CO_URL_HOME):
+        refresh_route = refresh_route.replace(constants.HUGGINGFACE_CO_URL_HOME.rstrip("/"), endpoint.rstrip("/"))
     return XetFileData(
         file_hash=file_hash,
         refresh_route=refresh_route,


Test patch:
diff --git a/tests/test_xet_utils.py b/tests/test_xet_utils.py
index 893ad1e5..dffd6e0c 100644
--- a/tests/test_xet_utils.py
+++ b/tests/test_xet_utils.py
@@ -51,6 +51,34 @@ def test_parse_invalid_headers_file_info() -> None:
     assert parse_xet_file_data_from_response(mock_response) is None
 
 
+@pytest.mark.parametrize(
+    "refresh_route, expected_refresh_route",
+    [
+        (
+            "/api/refresh",
+            "/api/refresh",
+        ),
+        (
+            "https://huggingface.co/api/refresh",
+            "https://xet.example.com/api/refresh",
+        ),
+    ],
+)
+def test_parse_header_file_info_with_endpoint(refresh_route: str, expected_refresh_route: str) -> None:
+    mock_response = MagicMock()
+    mock_response.headers = {
+        "X-Xet-Hash": "sha256:abcdef",
+        "X-Xet-Refresh-Route": refresh_route,
+    }
+    mock_response.links = {}
+
+    file_data = parse_xet_file_data_from_response(mock_response, endpoint="https://xet.example.com")
+
+    assert file_data is not None
+    assert file_data.refresh_route == expected_refresh_route
+    assert file_data.file_hash == "sha256:abcdef"
+
+
 def test_parse_valid_headers_connection_info() -> None:
     headers = {
         "X-Xet-Cas-Url": "https://xet.example.com",