feat: host_type field for OpenRouter / OpenAI-compatible API support

Adds host_type ("openwebui" | "openai") to the host schema so Cortex can
talk to both Open WebUI/Ollama and OpenRouter/standard-OpenAI endpoints.

Path differences per type:
  openwebui (default): /api/chat/completions, /api/models
  openai:              /chat/completions,     /models

model_registry.py:
  - host_type added to host schema (default "openwebui", backward compat)
  - save_host() accepts host_type parameter
  - _resolve_model() passes host_type through with the merged host fields

llm_client._local():
  - Reads host_type from resolved model_cfg
  - Selects correct chat completions path accordingly

routers/local_llm.py:
  - save_host route accepts host_type form field
  - fetch-models uses /models for openai type, /api/models for openwebui
  - Existing host rows show type selector pre-filled from stored value

local_llm.html:
  - "Add host" form includes type selector

To use OpenRouter:
  - Add host: URL = https://openrouter.ai/api/v1, Type = OpenAI-compatible
  - API key from openrouter.ai (store in .env or model_registry.json only)
  - Fetch models or add manually (e.g. anthropic/claude-sonnet-4-5-20251022)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-04-06 21:11:22 -04:00
parent 2dd94696d5
commit a6e404c143
4 changed files with 82 additions and 32 deletions

View File

@@ -175,14 +175,17 @@ async def _local(system_prompt: str, messages: list[dict], model_cfg: dict | Non
if not model:
raise RuntimeError("local_model not configured — add a model at /settings/local")
logger.info("local backend: %s @ %s", model, api_url)
host_type = cfg.get("host_type", "openwebui")
# "openwebui" uses Open WebUI/Ollama path layout; "openai" uses standard OpenAI layout
chat_path = "/chat/completions" if host_type == "openai" else "/api/chat/completions"
logger.info("local backend (%s): %s @ %s", host_type, model, api_url)
msgs: list[dict] = []
if system_prompt:
msgs.append({"role": "system", "content": system_prompt})
msgs.extend(messages)
url = api_url.rstrip("/") + "/api/chat/completions"
url = api_url.rstrip("/") + chat_path
headers: dict[str, str] = {}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"