Add OpenRouter as an OpenAI-compatible reseller provider

Descriptor + curated matrix rows + sk-or- key auto-detect (server and GUI).
This commit is contained in:
Rohit C Prasad
2026-07-25 16:07:33 -07:00
parent db93d75bf6
commit ee495b9006
5 changed files with 29 additions and 8 deletions
+10 -2
View File
@@ -11,8 +11,8 @@ falls back to the conservative heuristics in ``capabilities.py`` at their own ri
degraded results. Ids verified against vendor/reseller catalogs on 2026-07-04; refresh the
reseller rows when catalogs rotate (they rename on every model generation).
Resellers: Together + Fireworks for now. TODO: add Groq and OpenRouter entries here AND their
descriptors in ``registry.py`` once the current provider surface is tested — deliberately
Resellers: Together + Fireworks + OpenRouter. TODO: add Groq entries here AND its
descriptor in ``registry.py`` once the current provider surface is tested — deliberately
deferred to bound how much needs verifying at once.
"""
@@ -112,6 +112,14 @@ MATRIX: dict[str, ModelEntry] = {
"fireworks:accounts/fireworks/models/llama4-maverick-instruct-basic": ModelEntry(
"Llama 4 Maverick · via Fireworks"
),
# OpenRouter slugs are lowercase `<lab>/<model>` (checked against their catalog
# 2026-07-25); same labs as above, one key for all of them.
"openrouter:z-ai/glm-5.2": ModelEntry("GLM-5.2 · via OpenRouter"),
"openrouter:moonshotai/kimi-k2.6": ModelEntry("Kimi K2.6 · via OpenRouter"),
"openrouter:deepseek/deepseek-v4-pro": ModelEntry("DeepSeek V4 Pro · via OpenRouter"),
"openrouter:meta-llama/llama-4-maverick": ModelEntry(
"Llama 4 Maverick · via OpenRouter"
),
}
+13 -4
View File
@@ -211,7 +211,7 @@ DESCRIPTORS: list[ProviderDescriptor] = [
secret=False,
required=False,
placeholder="https://…/openai/v1",
help="For Azure OpenAI, OpenRouter, vLLM, or any OpenAI-compliant server. Leave blank for api.openai.com.",
help="For Azure OpenAI, vLLM, or any OpenAI-compliant server. Leave blank for api.openai.com.",
),
],
build=_build_openai,
@@ -316,9 +316,9 @@ DESCRIPTORS: list[ProviderDescriptor] = [
endpoint_help="Prefilled with the Meta Model API endpoint (public preview, US-only as of 2026-07).",
),
# Resellers: many labs' models behind one key, using THEIR model namespaces (the curated
# ids + display labels live in providers/matrix.py). TODO: add Groq and OpenRouter here
# (+ their matrix rows) once the current provider surface is tested — deliberately
# deferred to bound how much needs verifying at once (owner call, 2026-07-04).
# ids + display labels live in providers/matrix.py). TODO: add Groq here (+ its matrix
# rows) once the current provider surface is tested — deliberately deferred to bound
# how much needs verifying at once (owner call, 2026-07-04).
_compat(
"together",
"Together AI",
@@ -333,6 +333,13 @@ DESCRIPTORS: list[ProviderDescriptor] = [
recommended_model="accounts/fireworks/models/glm-5p2",
env_key="FIREWORKS_API_KEY",
),
_compat(
"openrouter",
"OpenRouter",
base_url="https://openrouter.ai/api/v1",
recommended_model="z-ai/glm-5.2",
env_key="OPENROUTER_API_KEY",
),
ProviderDescriptor(
name="ollama",
title="Ollama (local models)",
@@ -386,6 +393,8 @@ def detect_provider(api_key: str) -> Optional[str]:
return None
if key.startswith("sk-ant-"):
return "anthropic"
if key.startswith("sk-or-"):
return "openrouter"
if key.startswith("AIza"):
return "gemini"
if key.startswith(("sk-", "sk_")):
+1
View File
@@ -1324,6 +1324,7 @@ export function detectProvider(apiKey: string): string | null {
const key = (apiKey || "").trim();
if (!key) return null;
if (key.startsWith("sk-ant-")) return "anthropic";
if (key.startsWith("sk-or-")) return "openrouter";
if (key.startsWith("AIza")) return "gemini";
if (key.startsWith("sk-") || key.startsWith("sk_")) return "openai";
return null;
+1
View File
@@ -15,6 +15,7 @@ from coworker.providers import detect_provider, verify_provider_key
"key,expected",
[
("sk-ant-api03-abc", "anthropic"),
("sk-or-v1-abc", "openrouter"),
("AIzaSyAbc123", "gemini"),
("sk-proj-abc", "openai"),
("sk_live_abc", "openai"),
+4 -2
View File
@@ -381,6 +381,8 @@ def test_matrix_answers_capabilities_for_reseller_ids():
"together:zai-org/GLM-5.2",
"together:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
"fireworks:accounts/fireworks/models/kimi-k2p6",
"openrouter:z-ai/glm-5.2",
"openrouter:meta-llama/llama-4-maverick",
):
caps = capabilities_for(mid)
assert caps.tools and caps.parallel_tool_calls and caps.streaming
@@ -402,12 +404,12 @@ def test_matrix_labels_and_custom_model_fallback():
def test_reseller_descriptors_and_matrix_stay_in_lockstep():
"""Together/Fireworks suggested models derive from the matrix, and each descriptor's
"""Reseller suggested models derive from the matrix, and each descriptor's
recommended model must be one of them (set_provider's auto-add depends on it)."""
from coworker.providers.matrix import models_for_provider
from coworker.providers.registry import get_descriptor
for name in ("together", "fireworks"):
for name in ("together", "fireworks", "openrouter"):
d = get_descriptor(name)
assert d is not None and d.needs_key
curated = models_for_provider(name)