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 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). 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 Resellers: Together + Fireworks + OpenRouter. TODO: add Groq entries here AND its
descriptors in ``registry.py`` once the current provider surface is tested — deliberately descriptor in ``registry.py`` once the current provider surface is tested — deliberately
deferred to bound how much needs verifying at once. 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( "fireworks:accounts/fireworks/models/llama4-maverick-instruct-basic": ModelEntry(
"Llama 4 Maverick · via Fireworks" "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, secret=False,
required=False, required=False,
placeholder="https://…/openai/v1", 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, 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).", 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 # 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 # ids + display labels live in providers/matrix.py). TODO: add Groq here (+ its matrix
# (+ their matrix rows) once the current provider surface is tested — deliberately # rows) once the current provider surface is tested — deliberately deferred to bound
# deferred to bound how much needs verifying at once (owner call, 2026-07-04). # how much needs verifying at once (owner call, 2026-07-04).
_compat( _compat(
"together", "together",
"Together AI", "Together AI",
@@ -333,6 +333,13 @@ DESCRIPTORS: list[ProviderDescriptor] = [
recommended_model="accounts/fireworks/models/glm-5p2", recommended_model="accounts/fireworks/models/glm-5p2",
env_key="FIREWORKS_API_KEY", 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( ProviderDescriptor(
name="ollama", name="ollama",
title="Ollama (local models)", title="Ollama (local models)",
@@ -386,6 +393,8 @@ def detect_provider(api_key: str) -> Optional[str]:
return None return None
if key.startswith("sk-ant-"): if key.startswith("sk-ant-"):
return "anthropic" return "anthropic"
if key.startswith("sk-or-"):
return "openrouter"
if key.startswith("AIza"): if key.startswith("AIza"):
return "gemini" return "gemini"
if key.startswith(("sk-", "sk_")): if key.startswith(("sk-", "sk_")):
+1
View File
@@ -1324,6 +1324,7 @@ export function detectProvider(apiKey: string): string | null {
const key = (apiKey || "").trim(); const key = (apiKey || "").trim();
if (!key) return null; if (!key) return null;
if (key.startsWith("sk-ant-")) return "anthropic"; if (key.startsWith("sk-ant-")) return "anthropic";
if (key.startsWith("sk-or-")) return "openrouter";
if (key.startsWith("AIza")) return "gemini"; if (key.startsWith("AIza")) return "gemini";
if (key.startsWith("sk-") || key.startsWith("sk_")) return "openai"; if (key.startsWith("sk-") || key.startsWith("sk_")) return "openai";
return null; return null;
+1
View File
@@ -15,6 +15,7 @@ from coworker.providers import detect_provider, verify_provider_key
"key,expected", "key,expected",
[ [
("sk-ant-api03-abc", "anthropic"), ("sk-ant-api03-abc", "anthropic"),
("sk-or-v1-abc", "openrouter"),
("AIzaSyAbc123", "gemini"), ("AIzaSyAbc123", "gemini"),
("sk-proj-abc", "openai"), ("sk-proj-abc", "openai"),
("sk_live_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:zai-org/GLM-5.2",
"together:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", "together:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
"fireworks:accounts/fireworks/models/kimi-k2p6", "fireworks:accounts/fireworks/models/kimi-k2p6",
"openrouter:z-ai/glm-5.2",
"openrouter:meta-llama/llama-4-maverick",
): ):
caps = capabilities_for(mid) caps = capabilities_for(mid)
assert caps.tools and caps.parallel_tool_calls and caps.streaming 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(): 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).""" 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.matrix import models_for_provider
from coworker.providers.registry import get_descriptor from coworker.providers.registry import get_descriptor
for name in ("together", "fireworks"): for name in ("together", "fireworks", "openrouter"):
d = get_descriptor(name) d = get_descriptor(name)
assert d is not None and d.needs_key assert d is not None and d.needs_key
curated = models_for_provider(name) curated = models_for_provider(name)