108 lines
3.2 KiB
Python
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 |