Files
HomeAssistance/custom_components/ai_automation_suggester/endpoint_utils.py
T
2026-06-05 22:34:31 -04:00

108 lines
3.2 KiB
Python

"""Endpoint normalization helpers for provider setup and requests."""
from __future__ import annotations
import re
def ensure_http_url(value: str | None, *, default_scheme: str = "http") -> str:
"""Return a stripped HTTP(S) URL, adding a scheme when one is omitted."""
endpoint = str(value or "").strip().rstrip("/")
if not endpoint:
return ""
if re.match(r"^https?://", endpoint):
return endpoint
return f"{default_scheme}://{endpoint}"
def openai_chat_endpoint(endpoint: str | None) -> str:
"""Normalize an OpenAI-compatible endpoint to a chat completions URL."""
base = ensure_http_url(endpoint)
if not base:
return ""
if base.endswith("/chat/completions"):
return base
if base.endswith("/api"):
return f"{base}/chat/completions"
if base.endswith("/v1"):
return f"{base}/chat/completions"
return f"{base}/v1/chat/completions"
def openai_model_endpoint_candidates(endpoint: str | None) -> list[str]:
"""Return likely model-listing endpoints for OpenAI-compatible servers."""
base = ensure_http_url(endpoint)
if not base:
return []
if base.endswith("/chat/completions"):
base = base[: -len("/chat/completions")]
candidates: list[str]
if base.endswith("/api"):
candidates = [f"{base}/models", f"{base}/v1/models"]
elif base.endswith("/v1"):
candidates = [f"{base}/models"]
else:
candidates = [f"{base}/v1/models", f"{base}/models", f"{base}/api/models"]
return _dedupe(candidates)
def ollama_base_url(
*,
base_url: str | None = None,
ip_address: str | None = None,
port: int | str | None = None,
https: bool = False,
) -> str:
"""Build an Ollama-compatible base URL from either a full URL or host/port fields."""
if base_url:
return ensure_http_url(base_url)
if not ip_address:
return ""
host = str(ip_address).strip().rstrip("/")
if re.match(r"^https?://", host):
return host
proto = "https" if https else "http"
return f"{proto}://{host}:{port or 11434}"
def ollama_api_candidates(base_url: str, api_path: str) -> list[str]:
"""Return likely Ollama API paths for native Ollama and Open WebUI proxies."""
base = ensure_http_url(base_url)
if not base:
return []
path = api_path.strip("/")
if base.endswith(f"/{path}"):
return [base]
if base.endswith("/api") and path.startswith("api/"):
return [f"{base}/{path.split('/', 1)[1]}"]
candidates = [f"{base}/{path}"]
if not base.endswith("/ollama"):
candidates.append(f"{base}/ollama/{path}")
return _dedupe(candidates)
def bearer_auth_headers(api_key: str | None) -> dict[str, str] | None:
"""Return bearer authorization headers for provider APIs that need a token."""
token = str(api_key or "").strip()
if not token:
return None
if token.lower().startswith("bearer "):
return {"Authorization": token}
return {"Authorization": f"Bearer {token}"}
def _dedupe(values: list[str]) -> list[str]:
seen: set[str] = set()
deduped: list[str] = []
for value in values:
if value not in seen:
seen.add(value)
deduped.append(value)
return deduped