From 621b90a65259c143fe2fb95982cd85ef50da129e Mon Sep 17 00:00:00 2001 From: Joseph Perla Date: Thu, 23 Jul 2026 13:17:45 -0700 Subject: [PATCH] Add TrustedRouter model provider --- README.md | 2 +- coworker/providers/matrix.py | 18 ++++++++++++++++++ coworker/providers/registry.py | 16 +++++++++++++++- surfaces/gui/e2e/fixtures.ts | 2 ++ surfaces/gui/e2e/onboarding.spec.ts | 1 + surfaces/gui/src/api.test.ts | 10 ++++++++++ surfaces/gui/src/api.ts | 1 + surfaces/gui/src/providers/logos.ts | 3 +++ .../gui/src/providers/logos/trustedrouter.svg | 8 ++++++++ tests/test_provider_router.py | 8 ++++++++ tests/test_provider_verify.py | 9 +++++++++ 11 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 surfaces/gui/src/api.test.ts create mode 100644 surfaces/gui/src/providers/logos/trustedrouter.svg diff --git a/README.md b/README.md index b32a299d..6c537f7a 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Under the hood: Model access is yours: pick a provider, paste your key, switch anytime. Supported out of the box: -**OpenAI · Anthropic · Google Gemini · Inkling (Thinking Machines) · GLM (Z.ai) · DeepSeek · Kimi (Moonshot) · Qwen · MiniMax · Mistral · Grok (xAI)** - plus open-weight models via **Together** and **Fireworks**, and fully local models via **Ollama**. +**OpenAI · Anthropic · Google Gemini · Inkling (Thinking Machines) · GLM (Z.ai) · DeepSeek · Kimi (Moonshot) · Qwen · MiniMax · Mistral · Grok (xAI)** - plus multi-model routing through **[TrustedRouter](https://trustedrouter.com)**, open-weight models via **Together** and **Fireworks**, and fully local models via **Ollama**. A curated model list marks what we've verified for tool-calling work. Adding any model string works at your own risk. diff --git a/coworker/providers/matrix.py b/coworker/providers/matrix.py index bf6a878c..459b2f6a 100644 --- a/coworker/providers/matrix.py +++ b/coworker/providers/matrix.py @@ -109,6 +109,24 @@ class ModelEntry: "mistral:mistral-large-latest": ModelEntry( "Mistral Large · Mistral", _AGENTIC, 128_000 ), + # -- multi-model gateways --------------------------------------------------- + # The first prefix selects OpenWorker's provider; the remaining slash-delimited id is sent + # unchanged to TrustedRouter's OpenAI-compatible API. + "trustedrouter:trustedrouter/auto": ModelEntry( + "Auto · TrustedRouter (automatic fallback)" + ), + "trustedrouter:trustedrouter/zdr": ModelEntry( + "ZDR · TrustedRouter (zero-retention routes)" + ), + "trustedrouter:trustedrouter/e2e": ModelEntry( + "E2E · TrustedRouter (confidential routes)" + ), + "trustedrouter:trustedrouter/cheap": ModelEntry( + "Cheap · TrustedRouter (cost-optimized)" + ), + "trustedrouter:trustedrouter/plato": ModelEntry( + "Plato · TrustedRouter (open-weight advisor)" + ), # -- resellers (their model namespaces, verbatim) ----------------------------- "together:thinkingmachines/Inkling": ModelEntry("Inkling · via Together"), "together:zai-org/GLM-5.2": ModelEntry("GLM-5.2 · via Together", _AGENTIC, 128_000), diff --git a/coworker/providers/registry.py b/coworker/providers/registry.py index 6c147705..e741fabe 100644 --- a/coworker/providers/registry.py +++ b/coworker/providers/registry.py @@ -12,7 +12,8 @@ `AnthropicProvider`), `gemini` (native Google GenAI API via `GeminiProvider`), `bedrock` (models in the user's own AWS account — Claude natively, everything else via Converse), `vertex` (the user's own GCP project — Gemini and Claude natively, open-weight via the -MaaS endpoint), and `ollama` (local, OpenAI-compatible `/v1`). +MaaS endpoint), `trustedrouter` (OpenAI-compatible multi-model routing), and `ollama` +(local, OpenAI-compatible `/v1`). """ from __future__ import annotations @@ -525,6 +526,17 @@ def _compat( env_key="META_API_KEY", endpoint_help="Prefilled with the Meta Model API endpoint (public preview, US-only as of 2026-07).", ), + _compat( + "trustedrouter", + "TrustedRouter", + base_url="https://api.trustedrouter.com/v1", + recommended_model="trustedrouter/auto", + env_key="TRUSTEDROUTER_API_KEY", + endpoint_help=( + "TrustedRouter's attested OpenAI-compatible API. Create a key at " + "https://trustedrouter.com/console/api-keys." + ), + ), # Resellers: many labs' models behind one key, using THEIR model namespaces (the curated # 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 @@ -622,6 +634,8 @@ def detect_provider(api_key: str) -> Optional[str]: return "openrouter" if key.startswith("AIza"): return "gemini" + if key.startswith("sk-tr-"): + return "trustedrouter" if key.startswith(("sk-", "sk_")): return "openai" return None diff --git a/surfaces/gui/e2e/fixtures.ts b/surfaces/gui/e2e/fixtures.ts index e6dfa409..027dd268 100644 --- a/surfaces/gui/e2e/fixtures.ts +++ b/surfaces/gui/e2e/fixtures.ts @@ -334,6 +334,8 @@ const PROVIDERS = [ { name: "openai", title: "OpenAI", needs_key: true, fields: [{ key: "api_key", label: "OpenAI API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["gpt-5.5"], key_set_at: "2026-06-12", last_used_at: Math.floor(Date.now() / 1000) - 7200 }, // anthropic: configured but never used ("Not used yet"). { name: "anthropic", title: "Claude (Anthropic)", needs_key: true, fields: [{ key: "api_key", label: "API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["claude-opus-4-8"], key_set_at: null, last_used_at: null }, + // trustedrouter: first-class OpenAI-compatible multi-model backend. + { name: "trustedrouter", title: "TrustedRouter", needs_key: true, blurb: "Uses TrustedRouter's OpenAI-compatible API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "TrustedRouter API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "TrustedRouter's attested OpenAI-compatible API.", placeholder: "https://api.trustedrouter.com/v1", default: "https://api.trustedrouter.com/v1" }], configured: false, values: {}, suggested_models: ["trustedrouter/auto", "trustedrouter/zdr"], key_set_at: null, last_used_at: null }, // zai: an OpenAI-compatible vendor — unconfigured, with a prefilled editable endpoint + blurb. { name: "zai", title: "Z AI (GLM)", needs_key: true, blurb: "Uses Z AI's OpenAI-compatible API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "Z AI API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "Prefilled with Z AI's international endpoint.", placeholder: "https://api.z.ai/api/paas/v4", default: "https://api.z.ai/api/paas/v4" }], configured: false, values: {}, suggested_models: ["glm-5.2"], key_set_at: null, last_used_at: null }, // ollama: keyless local provider — "configured" without proving anything runs; the diff --git a/surfaces/gui/e2e/onboarding.spec.ts b/surfaces/gui/e2e/onboarding.spec.ts index fc832d8f..a57129fa 100644 --- a/surfaces/gui/e2e/onboarding.spec.ts +++ b/surfaces/gui/e2e/onboarding.spec.ts @@ -24,6 +24,7 @@ test("provider gallery: cards wear their state; Next arms off stored credentials // "is OpenAI already connected?" — is answered by the gallery itself). await expect(page.getByTestId("ob-provider-openai")).toContainText("✓ Connected"); await expect(page.getByTestId("ob-provider-anthropic")).toContainText("✓ Connected"); + await expect(page.getByTestId("ob-provider-trustedrouter")).toContainText("Not set up"); await expect(page.getByTestId("ob-provider-zai")).toContainText("Not set up"); await expect(page.getByTestId("ob-provider-ollama")).toContainText("No key needed"); // Recognition-first order: anthropic before openai before the OpenAI-compat tail. diff --git a/surfaces/gui/src/api.test.ts b/surfaces/gui/src/api.test.ts new file mode 100644 index 00000000..d6d01840 --- /dev/null +++ b/surfaces/gui/src/api.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; + +import { detectProvider } from "./api"; + +describe("detectProvider", () => { + it("recognizes TrustedRouter keys before the generic OpenAI prefix", () => { + expect(detectProvider("sk-tr-v1-test")).toBe("trustedrouter"); + expect(detectProvider("sk-proj-test")).toBe("openai"); + }); +}); diff --git a/surfaces/gui/src/api.ts b/surfaces/gui/src/api.ts index 13cca52e..578e653d 100644 --- a/surfaces/gui/src/api.ts +++ b/surfaces/gui/src/api.ts @@ -1583,6 +1583,7 @@ export function detectProvider(apiKey: string): string | 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-tr-")) return "trustedrouter"; if (key.startsWith("sk-") || key.startsWith("sk_")) return "openai"; return null; } diff --git a/surfaces/gui/src/providers/logos.ts b/surfaces/gui/src/providers/logos.ts index 093c4fdd..c1707868 100644 --- a/surfaces/gui/src/providers/logos.ts +++ b/surfaces/gui/src/providers/logos.ts @@ -22,6 +22,7 @@ import qwen from "./logos/qwen.svg"; import minimax from "./logos/minimax.svg"; import xai from "./logos/xai.svg"; import meta from "./logos/meta.svg"; +import trustedrouter from "./logos/trustedrouter.svg"; export const PROVIDER_LOGOS: Record = { anthropic, @@ -41,6 +42,7 @@ export const PROVIDER_LOGOS: Record = { qwen, minimax, xai, + trustedrouter, }; export const PROVIDER_ORDER = [ @@ -61,6 +63,7 @@ export const PROVIDER_ORDER = [ "qwen", "minimax", "xai", + "trustedrouter", ]; export function providerRank(name: string): number { diff --git a/surfaces/gui/src/providers/logos/trustedrouter.svg b/surfaces/gui/src/providers/logos/trustedrouter.svg new file mode 100644 index 00000000..eac56de4 --- /dev/null +++ b/surfaces/gui/src/providers/logos/trustedrouter.svg @@ -0,0 +1,8 @@ + + TrustedRouter + + + + + + diff --git a/tests/test_provider_router.py b/tests/test_provider_router.py index d5c124a9..20226b39 100644 --- a/tests/test_provider_router.py +++ b/tests/test_provider_router.py @@ -115,6 +115,9 @@ def test_router_routes_and_strips_prefix(monkeypatch): router.complete(model="gpt-5.5", messages=[]) # bare → default openai assert state["latest"]["openai"].models == ["gpt-5.5"] + router.complete(model="trustedrouter:trustedrouter/auto", messages=[]) + assert state["latest"]["trustedrouter"].models == ["trustedrouter/auto"] + def test_router_caches_and_invalidates(monkeypatch): state = _patch_build(monkeypatch) @@ -420,6 +423,10 @@ def test_provider_builders(monkeypatch): assert o._base_url == "https://my.azure.example/openai/v1" assert isinstance(build_provider_client("openai", {}, None), OpenAIResponsesProvider) + tr = build_provider_client("trustedrouter", {"api_key": "sk-tr-v1-test"}, None) + assert tr._api_key == "sk-tr-v1-test" + assert tr._base_url == "https://api.trustedrouter.com/v1" + def test_anthropic_gemini_capabilities(): for m in ("anthropic:claude-sonnet-4-6", "gemini:gemini-2.5-flash"): @@ -508,6 +515,7 @@ def test_provider_suggested_models(tmp_path, monkeypatch): mgr = SessionManager(data_dir=tmp_path) provs = {p["name"]: p for p in mgr.get_providers()} assert "gpt-5.5" in provs["openai"]["suggested_models"] + assert "trustedrouter/auto" in provs["trustedrouter"]["suggested_models"] # ollama suggestions are bare names (no `ollama:` prefix); empty when unconfigured sugg = provs["ollama"]["suggested_models"] assert isinstance(sugg, list) diff --git a/tests/test_provider_verify.py b/tests/test_provider_verify.py index 8da7fe76..f29f2512 100644 --- a/tests/test_provider_verify.py +++ b/tests/test_provider_verify.py @@ -17,6 +17,7 @@ ("sk-ant-api03-abc", "anthropic"), ("sk-or-v1-abc", "openrouter"), ("AIzaSyAbc123", "gemini"), + ("sk-tr-v1-abc", "trustedrouter"), ("sk-proj-abc", "openai"), ("sk_live_abc", "openai"), ("", None), @@ -59,6 +60,14 @@ def test_verify_openai_custom_endpoint(monkeypatch): assert cap["url"] == "https://gw.example/openai/v1/models" +def test_verify_trustedrouter_uses_attested_api(monkeypatch): + cap: dict = {} + _patch_get(monkeypatch, status=200, capture=cap) + assert verify_provider_key("trustedrouter", api_key="sk-tr-v1-test") == {"ok": True} + assert cap["url"] == "https://api.trustedrouter.com/v1/models" + assert cap["headers"]["Authorization"] == "Bearer sk-tr-v1-test" + + def test_verify_bad_key_is_invalid(monkeypatch): _patch_get(monkeypatch, status=401) assert verify_provider_key("openai", api_key="sk-bad") == {