Zyquo Router
One local endpoint. Every AI provider. Spec-exact.
Turn your Mac into a private LLM gateway — 170 models from 12 providers behind a single OpenAI-compatible API.
Anthropic · OpenAI · xAI · Mistral · Google Gemini · Alibaba Qwen · DeepSeek · Kimi · Perplexity · Together AI · DeepInfra · Cerebras
📖 Table of Contents
- Why Zyquo Router
- Features
- Screenshots
- Installation
- Requirements
- Usage
- Architecture
- Building from Source
- Verification
- Roadmap
- Contributing
- License
- Author
💡 Why Zyquo Router
Every AI provider speaks a slightly different dialect. Anthropic wants x-api-key and
content blocks; Gemini wants contents/parts and camelCase; Mistral renames seed;
Perplexity ends its streams with a non-spec event. Your tools — SDKs, CLIs, IDE plugins,
agents — mostly speak one dialect: the OpenAI API.
Zyquo Router runs a tiny, native gateway on your Mac. You store your provider keys
once in an encrypted vault, pick a port, press Start — and everything that can talk
to OpenAI can now talk to twelve providers through http://localhost:8787/v1, with
per-request model routing, live traffic inspection, and cost tracking.
Think OpenRouter / LiteLLM — but native, local, private, and gorgeous.
- 🔒 Private by design — keys never leave your Mac; requests go straight from your machine to the provider. No middleman, no telemetry, no accounts.
- 🎯 Spec-exact — byte-exact
chat.completion.chunkSSE streams that the official OpenAI Python and JS SDKs parse unmodified (verified across all 170 models). - 🖥 A real Mac app — SwiftUI control room with a menu bar extra, not a Docker container with a YAML file.
✨ Features
🌐 The Gateway
| Capability | Details |
|---|---|
| OpenAI-compatible API | POST /v1/chat/completions (streaming + non-streaming), GET /v1/models, GET /v1/models/{id}, GET /health |
| 170 models, 12 providers | Full catalog with context windows, capabilities, and per-Mtok pricing under the x_zyquo extension key |
| Namespaced routing | provider/model-id (e.g. anthropic/claude-sonnet-4-5, deepinfra/meta-llama/Llama-4-Maverick); bare IDs accepted when unambiguous |
| Aliases | Friendly names — fast → cerebras/…, best → anthropic/… |
| Fallback chains | Ordered model lists tried on upstream failure; the response honestly reports the model that answered |
| Full translation | Anthropic Messages API and Gemini generateContent translated bidirectionally: system extraction, turn merging, tools ⇄ tool_use/functionCall, images, finish-reason and usage normalization |
| Tool calling | Streamed tool_calls argument deltas in exact OpenAI shape, from all three wire formats |
| Vision | image_url content parts (data-URI base64 and remote URLs where supported) |
| Reasoning models | Thinking output normalized to reasoning_content (DeepSeek convention) — works with Claude thinking, Gemini thoughts, DeepSeek-R1, Qwen, Magistral, Perplexity <think>, and more |
| Quirk normalization | Together finish_reason:"eos", Mistral thinking arrays, Perplexity citations + .done events, missing object fields, argument-repeat streams — all ironed into the spec |
| Resilience | Exponential-backoff retries with jitter (never after the first streamed byte), honest OpenAI-format error mapping (401/429/502/504), client-disconnect cancels the upstream call in <1s |
🔐 Control & Security
- Encrypted key vault — AES-256-GCM with an HKDF-derived, machine-bound master key (hardware UUID + salted pepper). No Keychain, no plaintext, ever.
- Localhost by default — LAN exposure (
0.0.0.0) is an explicit opt-in that requires at least one local API key. - Local API keys —
zyquo-sk-…bearer tokens, SHA-256-hashed at rest, shown once at creation, per-key enable/revoke and model allow-lists. - Redacted logging — request/response bodies hidden by default; revealing is an explicit per-session switch. Provider keys never appear in any response, log, or error.
📊 Observability
- Live dashboard — requests/min sparkline, tokens, estimated cost (from real per-model pricing), error rate, active streams, uptime, per-provider breakdown bar.
- Request inspector — every routed call with provider, status, latency, tokens, cost, SSE badge, and a detail pane with an upstream-TTFB timing waterfall.
- Filters & export — by provider/status/model (⌘F), pause/clear, JSON export.
🛠 Developer Experience
- Playground — built-in tester that calls the router's own endpoint, with side-by-side request JSON / raw SSE panes and copy-as-code.
- In-app API docs — the full reference rendered beautifully inside the app, from the same source of truth the server implements.
- Copy-as snippets — endpoint URL,
curl, OpenAI-Python, OpenAI-JS, pre-filled with your port. - Menu bar extra — status dot, Start/Stop, req/min, today's cost, copy endpoint.
- ⌘K command palette — server control, section jumps, fuzzy model-ID copy.
- Headless modes —
ZyquoRouter --serve [port](no UI) and--load-vault(seed keys from environment variables) for scripting and CI. - Shortcuts — ⌘R start/stop · ⌘1–6 sections · ⌘F filter · ⌘⇧C copy endpoint · ⌘K palette.
📸 Screenshots
Dashboard — the control room
In-app API reference
📦 Installation
Download (recommended)
- ⬇️ Download ZyquoRouter.dmg from the latest release
- Open the DMG and drag Zyquo Router into Applications
- Launch it — the app is Developer ID signed, notarized, and stapled, so Gatekeeper opens it without warnings
First run — three steps to one endpoint
- Keys → paste the API keys for the providers you use (each row has a Test button that verifies the key and shows latency)
- Dashboard → pick a port (default
8787) → press Start - Point anything OpenAI-compatible at
http://localhost:8787/v1🎉
🖥 Requirements
| Minimum | |
|---|---|
| macOS | 13.0 Ventura or later |
| Architecture | Universal binary — Apple Silicon & Intel |
| Disk | ~15 MB |
| Accounts | None. Bring your own provider API keys |
🚀 Usage
Python (official OpenAI SDK — works unmodified)
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8787/v1", api_key="zyquo")
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4-5", # any of the 170 models
messages=[{"role": "user", "content": "Hello!"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")JavaScript / TypeScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:8787/v1", apiKey: "zyquo" });
const r = await client.chat.completions.create({
model: "gemini/gemini-2.5-flash",
messages: [{ role: "user", content: "Hello!" }],
});curl
curl http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "deepseek/deepseek-chat", "messages": [{"role": "user", "content": "Hi"}], "stream": true}'Good to know
- Model discovery:
GET /v1/modelslists everything with context windows, pricing, and capability flags. Disabled models 404; favorites float to the top in-app. - Reasoning: pass the standard
reasoning_effort— the router translates it per provider (Anthropic thinking budgets, GeminithinkingConfig, Mistralprompt_mode, …) and normalizes the output intoreasoning_content. - Provider extras pass straight through: Perplexity
search_domain_filter, Qwenenable_thinking, Togethertop_k, Anthropicthinking, … - Auth: with no local keys the router is open on localhost. Create
zyquo-sk-…keys in Keys → Local API Keys to requireAuthorization: Bearer …(mandatory for LAN mode). - The full contract lives in
docs/API.md— also rendered in-app under Docs.
🏗️ Architecture
100 % native Swift — no Electron, no Python sidecar, no Docker.
SwiftUI control room ─┐
├─► SwiftNIO HTTP/1.1 server (structured concurrency,
Menu bar extra ───────┘ NIOAsyncChannel, spec-exact SSE writer)
│
RequestRouter (namespaces, aliases,
fallback chains, capability gates)
│
┌───────────────────┼────────────────────┐
▼ ▼ ▼
AnthropicTranslator GeminiTranslator CompatAdjuster
(Messages API ⇄ (generateContent ⇄ (per-provider param
OpenAI, SSE event OpenAI, SSE tables + quirk
state machine) chunks) normalization ×10)
└───────────────────┼────────────────────┘
▼
UpstreamCall → provider APIs (your keys,
straight from your Mac — AES-256-GCM vault)- Server: SwiftNIO 2 with one task per connection; client disconnects propagate as
cancellation all the way into the upstream
URLSessiontransfer. - Translation: fixture-tested state machines convert Anthropic events and Gemini
chunks into byte-exact OpenAI
chat.completion.chunks. - Persistence: JSON documents in
~/Library/Application Support/ZyquoRouter/; keys invault.zq(AES-256-GCM, machine-bound HKDF key, no Keychain). - Zero heavyweight deps: SwiftNIO, swift-nio-extras, swift-markdown. That's it.
⚙️ Building from Source
Requirements: macOS 13+, Swift 5.9+ toolchain (Xcode or CLT), librsvg for the icon
pipeline (brew install librsvg). Built entirely with SPM — no Xcode IDE required.
git clone https://github.com/spboucher-ai/zyquo-router.git
cd zyquo-router
make dev # release build + ad-hoc signed dist/Zyquo Router.app
make test # unit, fixture, and gateway-behavior test suites
make icon # regenerate AppIcon.icns from assets/icon/zyquo-router.svg
make release # universal binary + Developer ID signing + notarization + DMGHeadless gateway for scripts/CI:
.build/release/ZyquoRouter --load-vault # seed the vault from env vars
.build/release/ZyquoRouter --serve 8787 # run the gateway without the UI✅ Verification
Every release is verified by scripts/verify.py, which drives
all 170 catalog models through the local endpoint with the official OpenAI Python
SDK — non-streaming, streaming chunk discipline, tool calling, vision, and reasoning.
The current matrix is 170/170 green: see docs/VERIFICATION.md.
Gateway behavior is integration-tested against mock upstreams: client-disconnect cancellation, fallback-chain honesty, 429 retries, and graceful shutdown with active streams.
🗺 Roadmap
-
POST /v1/embeddingsrouted to embedding-capable providers -
POST /v1/responsescompatibility surface - Prompt-level request/response caching
- Per-key rate limiting (token bucket) and budgets
- Usage history persistence with daily/weekly summaries
- Custom OpenAI-compatible endpoints (self-hosted vLLM/Ollama upstreams)
🤝 Contributing
Issues and pull requests are welcome!
- Fork → branch → make your change
make testmust stay green (unit + fixture + gateway-behavior suites)swift buildmust produce zero warnings- Every code file carries the project header (see any source file)
📄 License
Released under the MIT License.
👤 Author
Simon-Pierre Boucher
📫 Contact: contact@spboucher.ai
Part of the Zyquo family — Cloud · Local · Agent · Atlas · MLX · Router
© 2026 Simon-Pierre Boucher. All rights reserved.