SPB Git

spb/zyquo-router Public MIT

One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).

Swift 95.7% Python 2.3% Shell 1.2% Makefile 0.9%
14.2 KB · 339 lines markdown
Rendered Raw Blame History
1<div align="center">23<img src="assets/readme/app-icon.png" width="180" alt="Zyquo Router icon" />45# Zyquo Router67### One local endpoint. Every AI provider. Spec-exact.89*Turn your Mac into a private LLM gateway — 170 models from 12 providers behind a single OpenAI-compatible API.*1011<br/>1213[![Release](https://img.shields.io/github/v/release/spboucher-ai/zyquo-router?style=for-the-badge&color=0891B2&label=Release)](https://github.com/spboucher-ai/zyquo-router/releases/latest)14[![Platform](https://img.shields.io/badge/Platform-macOS%2013%2B-374151?style=for-the-badge&logo=apple&logoColor=white)](#-requirements)15[![Swift](https://img.shields.io/badge/Swift-5.9%2B-F05138?style=for-the-badge&logo=swift&logoColor=white)](#%EF%B8%8F-building-from-source)16[![License](https://img.shields.io/badge/License-MIT-2FA36B?style=for-the-badge)](LICENSE)17[![Downloads](https://img.shields.io/github/downloads/spboucher-ai/zyquo-router/total?style=for-the-badge&color=4C2BE0&label=Downloads)](https://github.com/spboucher-ai/zyquo-router/releases)1819[![Notarized](https://img.shields.io/badge/Signed%20%26%20Notarized-Developer%20ID-0891B2?style=flat-square&logo=apple&logoColor=white)](#-installation)20[![Universal](https://img.shields.io/badge/Universal-Apple%20Silicon%20%2B%20Intel-9BA3AA?style=flat-square)](#-installation)21[![SwiftNIO](https://img.shields.io/badge/Server-SwiftNIO%202-E4E8EB?style=flat-square)](#%EF%B8%8F-architecture)22[![No Electron](https://img.shields.io/badge/100%25%20Native-SwiftUI-0891B2?style=flat-square)](#%EF%B8%8F-architecture)2324<br/>2526`Anthropic` · `OpenAI` · `xAI` · `Mistral` · `Google Gemini` · `Alibaba Qwen` · `DeepSeek` · `Kimi` · `Perplexity` · `Together AI` · `DeepInfra` · `Cerebras`2728</div>2930---3132## 📖 Table of Contents3334- [Why Zyquo Router](#-why-zyquo-router)35- [Features](#-features)36- [Screenshots](#-screenshots)37- [Installation](#-installation)38- [Requirements](#-requirements)39- [Usage](#-usage)40- [Architecture](#%EF%B8%8F-architecture)41- [Building from Source](#%EF%B8%8F-building-from-source)42- [Verification](#-verification)43- [Roadmap](#-roadmap)44- [Contributing](#-contributing)45- [License](#-license)46- [Author](#-author)4748---4950## 💡 Why Zyquo Router5152Every AI provider speaks a slightly different dialect. Anthropic wants `x-api-key` and53content blocks; Gemini wants `contents/parts` and camelCase; Mistral renames `seed`;54Perplexity ends its streams with a non-spec event. Your tools — SDKs, CLIs, IDE plugins,55agents — mostly speak **one** dialect: the OpenAI API.5657**Zyquo Router** runs a tiny, native gateway on your Mac. You store your provider keys58once in an encrypted vault, pick a port, press **Start** — and everything that can talk59to OpenAI can now talk to *twelve providers* through `http://localhost:8787/v1`, with60per-request model routing, live traffic inspection, and cost tracking.6162Think **OpenRouter / LiteLLM — but native, local, private, and gorgeous.**6364- 🔒 **Private by design** — keys never leave your Mac; requests go straight from your65  machine to the provider. No middleman, no telemetry, no accounts.66- 🎯 **Spec-exact** — byte-exact `chat.completion.chunk` SSE streams that the official67  OpenAI Python and JS SDKs parse **unmodified** (verified across all 170 models).68- 🖥 **A real Mac app** — SwiftUI control room with a menu bar extra, not a Docker69  container with a YAML file.7071---7273## ✨ Features7475### 🌐 The Gateway7677| Capability | Details |78|---|---|79| **OpenAI-compatible API** | `POST /v1/chat/completions` (streaming + non-streaming), `GET /v1/models`, `GET /v1/models/{id}`, `GET /health` |80| **170 models, 12 providers** | Full catalog with context windows, capabilities, and per-Mtok pricing under the `x_zyquo` extension key |81| **Namespaced routing** | `provider/model-id` (e.g. `anthropic/claude-sonnet-4-5`, `deepinfra/meta-llama/Llama-4-Maverick`); bare IDs accepted when unambiguous |82| **Aliases** | Friendly names — `fast``cerebras/…`, `best``anthropic/…` |83| **Fallback chains** | Ordered model lists tried on upstream failure; the response honestly reports the model that answered |84| **Full translation** | Anthropic Messages API and Gemini `generateContent` translated bidirectionally: system extraction, turn merging, tools ⇄ `tool_use`/`functionCall`, images, finish-reason and usage normalization |85| **Tool calling** | Streamed `tool_calls` argument deltas in exact OpenAI shape, from all three wire formats |86| **Vision** | `image_url` content parts (data-URI base64 and remote URLs where supported) |87| **Reasoning models** | Thinking output normalized to `reasoning_content` (DeepSeek convention) — works with Claude thinking, Gemini thoughts, DeepSeek-R1, Qwen, Magistral, Perplexity `<think>`, and more |88| **Quirk normalization** | Together `finish_reason:"eos"`, Mistral thinking arrays, Perplexity citations + `.done` events, missing `object` fields, argument-repeat streams — all ironed into the spec |89| **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 |9091### 🔐 Control & Security9293- **Encrypted key vault** — AES-256-GCM with an HKDF-derived, machine-bound master key94  (hardware UUID + salted pepper). No Keychain, no plaintext, ever.95- **Localhost by default** — LAN exposure (`0.0.0.0`) is an explicit opt-in that96  *requires* at least one local API key.97- **Local API keys**`zyquo-sk-…` bearer tokens, SHA-256-hashed at rest, shown once98  at creation, per-key enable/revoke and model allow-lists.99- **Redacted logging** — request/response bodies hidden by default; revealing is an100  explicit per-session switch. Provider keys never appear in any response, log, or error.101102### 📊 Observability103104- **Live dashboard** — requests/min sparkline, tokens, estimated cost (from real105  per-model pricing), error rate, active streams, uptime, per-provider breakdown bar.106- **Request inspector** — every routed call with provider, status, latency, tokens,107  cost, SSE badge, and a detail pane with an upstream-TTFB timing waterfall.108- **Filters & export** — by provider/status/model (⌘F), pause/clear, JSON export.109110### 🛠 Developer Experience111112- **Playground** — built-in tester that calls the router's *own* endpoint, with113  side-by-side request JSON / raw SSE panes and copy-as-code.114- **In-app API docs** — the full reference rendered beautifully inside the app, from115  the same source of truth the server implements.116- **Copy-as snippets** — endpoint URL, `curl`, OpenAI-Python, OpenAI-JS, pre-filled117  with your port.118- **Menu bar extra** — status dot, Start/Stop, req/min, today's cost, copy endpoint.119- **⌘K command palette** — server control, section jumps, fuzzy model-ID copy.120- **Headless modes**`ZyquoRouter --serve [port]` (no UI) and `--load-vault`121  (seed keys from environment variables) for scripting and CI.122- **Shortcuts** — ⌘R start/stop · ⌘1–6 sections · ⌘F filter · ⌘⇧C copy endpoint · ⌘K palette.123124---125126## 📸 Screenshots127128<div align="center">129130**Dashboard — the control room**131132<img src="assets/readme/screenshot-dashboard.png" width="850" alt="Dashboard: server card with endpoint and copy-as snippets, live tiles, sparkline" />133134<br/><br/>135136**In-app API reference**137138<img src="assets/readme/screenshot-docs.png" width="850" alt="Docs: hero card, code blocks with copy, styled sections" />139140</div>141142---143144## 📦 Installation145146### Download (recommended)1471481. **[⬇️ Download ZyquoRouter.dmg](https://github.com/spboucher-ai/zyquo-router/releases/latest/download/ZyquoRouter.dmg)** from the latest release1492. Open the DMG and drag **Zyquo Router** into **Applications**1503. Launch it — the app is **Developer ID signed, notarized, and stapled**, so Gatekeeper151   opens it without warnings152153### First run — three steps to one endpoint1541551. **Keys** → paste the API keys for the providers you use (each row has a **Test**156   button that verifies the key and shows latency)1572. **Dashboard** → pick a port (default `8787`) → press **Start**1583. Point anything OpenAI-compatible at `http://localhost:8787/v1` 🎉159160---161162## 🖥 Requirements163164| | Minimum |165|---|---|166| **macOS** | 13.0 Ventura or later |167| **Architecture** | Universal binary — Apple Silicon & Intel |168| **Disk** | ~15 MB |169| **Accounts** | None. Bring your own provider API keys |170171---172173## 🚀 Usage174175### Python (official OpenAI SDK — works unmodified)176177```python178from openai import OpenAI179180client = OpenAI(base_url="http://localhost:8787/v1", api_key="zyquo")181182stream = client.chat.completions.create(183    model="anthropic/claude-sonnet-4-5",      # any of the 170 models184    messages=[{"role": "user", "content": "Hello!"}],185    stream=True,186)187for chunk in stream:188    print(chunk.choices[0].delta.content or "", end="")189```190191### JavaScript / TypeScript192193```javascript194import OpenAI from "openai";195196const client = new OpenAI({ baseURL: "http://localhost:8787/v1", apiKey: "zyquo" });197const r = await client.chat.completions.create({198  model: "gemini/gemini-2.5-flash",199  messages: [{ role: "user", content: "Hello!" }],200});201```202203### curl204205```bash206curl http://localhost:8787/v1/chat/completions \207  -H "Content-Type: application/json" \208  -d '{"model": "deepseek/deepseek-chat", "messages": [{"role": "user", "content": "Hi"}], "stream": true}'209```210211### Good to know212213- **Model discovery:** `GET /v1/models` lists everything with context windows, pricing,214  and capability flags. Disabled models 404; favorites float to the top in-app.215- **Reasoning:** pass the standard `reasoning_effort` — the router translates it per216  provider (Anthropic thinking budgets, Gemini `thinkingConfig`, Mistral `prompt_mode`, …)217  and normalizes the output into `reasoning_content`.218- **Provider extras** pass straight through: Perplexity `search_domain_filter`, Qwen219  `enable_thinking`, Together `top_k`, Anthropic `thinking`, …220- **Auth:** with no local keys the router is open on localhost. Create `zyquo-sk-…`221  keys in **Keys → Local API Keys** to require `Authorization: Bearer …` (mandatory for222  LAN mode).223- The full contract lives in [`docs/API.md`](docs/API.md) — also rendered in-app under **Docs**.224225---226227## 🏗️ Architecture228229100 % native Swift — no Electron, no Python sidecar, no Docker.230231```232SwiftUI control room  ─┐233                       ├─►  SwiftNIO HTTP/1.1 server (structured concurrency,234Menu bar extra  ───────┘    NIOAsyncChannel, spec-exact SSE writer)235236                              RequestRouter (namespaces, aliases,237                              fallback chains, capability gates)238239                 ┌───────────────────┼────────────────────┐240                 ▼                   ▼                    ▼241        AnthropicTranslator   GeminiTranslator     CompatAdjuster242        (Messages API ⇄       (generateContent ⇄   (per-provider param243         OpenAI, SSE event     OpenAI, SSE          tables + quirk244         state machine)        chunks)              normalization ×10)245                 └───────────────────┼────────────────────┘246247                    UpstreamCall → provider APIs (your keys,248                    straight from your Mac — AES-256-GCM vault)249```250251- **Server:** SwiftNIO 2 with one task per connection; client disconnects propagate as252  cancellation all the way into the upstream `URLSession` transfer.253- **Translation:** fixture-tested state machines convert Anthropic events and Gemini254  chunks into byte-exact OpenAI `chat.completion.chunk`s.255- **Persistence:** JSON documents in `~/Library/Application Support/ZyquoRouter/`;256  keys in `vault.zq` (AES-256-GCM, machine-bound HKDF key, no Keychain).257- **Zero heavyweight deps:** SwiftNIO, swift-nio-extras, swift-markdown. That's it.258259---260261## ⚙️ Building from Source262263Requirements: macOS 13+, Swift 5.9+ toolchain (Xcode or CLT), `librsvg` for the icon264pipeline (`brew install librsvg`). Built entirely with SPM — **no Xcode IDE required**.265266```bash267git clone https://github.com/spboucher-ai/zyquo-router.git268cd zyquo-router269270make dev        # release build + ad-hoc signed dist/Zyquo Router.app271make test       # unit, fixture, and gateway-behavior test suites272make icon       # regenerate AppIcon.icns from assets/icon/zyquo-router.svg273make release    # universal binary + Developer ID signing + notarization + DMG274```275276Headless gateway for scripts/CI:277278```bash279.build/release/ZyquoRouter --load-vault    # seed the vault from env vars280.build/release/ZyquoRouter --serve 8787    # run the gateway without the UI281```282283---284285## ✅ Verification286287Every release is verified by [`scripts/verify.py`](scripts/verify.py), which drives288**all 170 catalog models** through the local endpoint with the official OpenAI Python289SDK — non-streaming, streaming chunk discipline, tool calling, vision, and reasoning.290The current matrix is **170/170 green**: see [`docs/VERIFICATION.md`](docs/VERIFICATION.md).291292Gateway behavior is integration-tested against mock upstreams: client-disconnect293cancellation, fallback-chain honesty, 429 retries, and graceful shutdown with active294streams.295296---297298## 🗺 Roadmap299300- [ ] `POST /v1/embeddings` routed to embedding-capable providers301- [ ] `POST /v1/responses` compatibility surface302- [ ] Prompt-level request/response caching303- [ ] Per-key rate limiting (token bucket) and budgets304- [ ] Usage history persistence with daily/weekly summaries305- [ ] Custom OpenAI-compatible endpoints (self-hosted vLLM/Ollama upstreams)306307---308309## 🤝 Contributing310311Issues and pull requests are welcome!3123131. Fork → branch → make your change3142. `make test` must stay green (unit + fixture + gateway-behavior suites)3153. `swift build` must produce **zero warnings**3164. Every code file carries the project header (see any source file)317318---319320## 📄 License321322Released under the [MIT License](LICENSE).323324---325326## 👤 Author327328<div align="center">329330**Simon-Pierre Boucher**331332📫 Contact: [contact@spboucher.ai](mailto:contact@spboucher.ai)333334Part of the **Zyquo** family — Cloud · Local · Agent · Atlas · MLX · **Router**335336<sub>© 2026 Simon-Pierre Boucher. All rights reserved.</sub>337338</div>339