Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,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.

Expand Down
18 changes: 18 additions & 0 deletions coworker/providers/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ class ModelEntry:
"qwen:qwen3-max": ModelEntry("Qwen3 Max · Alibaba"),
"xai:grok-4.3": ModelEntry("Grok 4.3 · xAI"),
"mistral:mistral-large-latest": ModelEntry("Mistral Large · Mistral"),
# -- 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"),
Expand Down
18 changes: 16 additions & 2 deletions coworker/providers/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

Today: `openai` (the default, with an optional custom endpoint that covers Azure OpenAI's
`/openai/v1` and any OpenAI-compliant gateway), `anthropic` (native Messages API via
`AnthropicProvider`), `gemini` (native Google GenAI API via `GeminiProvider`), and `ollama`
(local, OpenAI-compatible `/v1`). Bedrock/Vertex auth for Claude is future work.
`AnthropicProvider`), `gemini` (native Google GenAI API via `GeminiProvider`), `trustedrouter`
(OpenAI-compatible multi-model routing), and `ollama` (local, OpenAI-compatible `/v1`).
Bedrock/Vertex auth for Claude is future work.
"""

from __future__ import annotations
Expand Down Expand Up @@ -315,6 +316,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 and OpenRouter here
# (+ their matrix rows) once the current provider surface is tested — deliberately
Expand Down Expand Up @@ -388,6 +400,8 @@ def detect_provider(api_key: str) -> Optional[str]:
return "anthropic"
if key.startswith("AIza"):
return "gemini"
if key.startswith("sk-tr-"):
return "trustedrouter"
if key.startswith(("sk-", "sk_")):
return "openai"
return None
Expand Down
2 changes: 2 additions & 0 deletions surfaces/gui/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,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
Expand Down
1 change: 1 addition & 0 deletions surfaces/gui/e2e/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions surfaces/gui/src/api.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
1 change: 1 addition & 0 deletions surfaces/gui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,7 @@ export function detectProvider(apiKey: string): string | null {
if (!key) return null;
if (key.startsWith("sk-ant-")) return "anthropic";
if (key.startsWith("AIza")) return "gemini";
if (key.startsWith("sk-tr-")) return "trustedrouter";
if (key.startsWith("sk-") || key.startsWith("sk_")) return "openai";
return null;
}
Expand Down
3 changes: 3 additions & 0 deletions surfaces/gui/src/providers/logos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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<string, string> = {
anthropic,
Expand All @@ -35,6 +36,7 @@ export const PROVIDER_LOGOS: Record<string, string> = {
qwen,
minimax,
xai,
trustedrouter,
};

export const PROVIDER_ORDER = [
Expand All @@ -52,6 +54,7 @@ export const PROVIDER_ORDER = [
"qwen",
"minimax",
"xai",
"trustedrouter",
];

export function providerRank(name: string): number {
Expand Down
8 changes: 8 additions & 0 deletions surfaces/gui/src/providers/logos/trustedrouter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions tests/test_provider_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -416,6 +419,10 @@ def test_provider_builders(monkeypatch):
assert o._base_url == "https://my.azure.example/openai/v1"
assert build_provider_client("openai", {}, None)._base_url is None

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"):
Expand Down Expand Up @@ -504,6 +511,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)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_provider_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
[
("sk-ant-api03-abc", "anthropic"),
("AIzaSyAbc123", "gemini"),
("sk-tr-v1-abc", "trustedrouter"),
("sk-proj-abc", "openai"),
("sk_live_abc", "openai"),
("", None),
Expand Down Expand Up @@ -58,6 +59,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") == {
Expand Down