feat(ai): add OpenRouter and Ollama Cloud providers (#231)

Co-authored-by: Alex Kenley <Alex.Kenley@threatvectorsecurity.com>
This commit is contained in:
Alexander Kenley
2026-06-01 15:26:10 +10:00
committed by GitHub
parent 4dbc0fe73a
commit 2c4b8b57dd
27 changed files with 699 additions and 169 deletions

View File

@@ -55,7 +55,7 @@ def set_rag_manager(rag_mgr, personal_docs_mgr=None):
# Model resolution
# ---------------------------------------------------------------------------
from src.endpoint_resolver import normalize_base as _normalize_base
from src.endpoint_resolver import normalize_base as _normalize_base, build_chat_url, build_headers, build_models_url
def _resolve_model(spec: str) -> Tuple[str, str, Dict]:
@@ -95,9 +95,7 @@ def _resolve_model(spec: str) -> Tuple[str, str, Dict]:
for ep in endpoints:
base = _normalize_base(ep.base_url)
provider = _detect_provider(base)
headers = {}
if ep.api_key:
headers["Authorization"] = f"Bearer {ep.api_key}"
headers = build_headers(ep.api_key, base)
if provider == "anthropic":
# Anthropic: match against hardcoded model list
@@ -107,27 +105,32 @@ def _resolve_model(spec: str) -> Tuple[str, str, Dict]:
matched = am
break
if matched:
headers["x-api-key"] = ep.api_key or ""
headers["anthropic-version"] = "2023-06-01"
return base + "/v1/messages", matched, headers
return build_chat_url(base), matched, headers
else:
# OpenAI-compatible: probe /models
# OpenAI-compatible and native Ollama: probe the provider's model list.
try:
r = httpx.get(base + "/models", headers=headers, timeout=5)
r = httpx.get(build_models_url(base), headers=headers, timeout=5)
r.raise_for_status()
model_ids = [m.get("id") for m in (r.json().get("data") or []) if m.get("id")]
data = r.json()
model_ids = [m.get("id") for m in (data.get("data") or []) if m.get("id")]
if not model_ids:
model_ids = [
m.get("name") or m.get("model")
for m in (data.get("models") or [])
if m.get("name") or m.get("model")
]
except Exception:
model_ids = []
# Exact match first
for mid in model_ids:
if mid.lower() == model_name.lower():
return base + "/chat/completions", mid, headers
return build_chat_url(base), mid, headers
# Partial match
for mid in model_ids:
if model_name.lower() in mid.lower() or mid.lower() in model_name.lower():
return base + "/chat/completions", mid, headers
return build_chat_url(base), mid, headers
raise ValueError(f"Model '{spec}' not found on any configured endpoint")
finally:
@@ -1107,18 +1110,23 @@ async def do_list_models(content: str, session_id: Optional[str] = None) -> Dict
for ep in endpoints:
base = _normalize_base(ep.base_url)
provider = _detect_provider(base)
headers = {}
if ep.api_key:
headers["Authorization"] = f"Bearer {ep.api_key}"
headers = build_headers(ep.api_key, base)
model_ids = []
if provider == "anthropic":
model_ids = list(ANTHROPIC_MODELS)
else:
try:
r = httpx.get(base + "/models", headers=headers, timeout=5)
r = httpx.get(build_models_url(base), headers=headers, timeout=5)
r.raise_for_status()
model_ids = [m.get("id") for m in (r.json().get("data") or []) if m.get("id")]
data = r.json()
model_ids = [m.get("id") for m in (data.get("data") or []) if m.get("id")]
if not model_ids:
model_ids = [
m.get("name") or m.get("model")
for m in (data.get("models") or [])
if m.get("name") or m.get("model")
]
except Exception:
model_ids = ["(endpoint offline)"]