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%
1# Zyquo Router — PLAN23Phase-by-phase execution log. One phase at a time, checkpoint at each gate.45---67## Phase 0 — Mandatory research + Zyquo Cloud study89- [x] 0.A.1 OpenAI API spec documented exhaustively (chat/completions request + response + SSE streaming + models + errors; /v1/responses decision)10- [x] 0.A.2 Reference gateways studied (LiteLLM, OpenRouter, Ollama/LM Studio/vLLM compat layers) with concrete patterns extracted11- [x] 0.A.3 Translation matrices: Anthropic Messages ⇄ OpenAI, Gemini generateContent ⇄ OpenAI, per-provider deviation table for OpenAI-compatible upstreams, reasoning-content decision12- [x] 0.A.4 Swift HTTP serving approach evaluated and chosen with rationale (SwiftNIO vs Network.framework vs Hummingbird/Vapor), incl. SSE, cancellation, CORS, timeouts13- [x] 0.A.5 Gateway concerns: logging/redaction, usage extraction, cost calc, rate limiting, retries/backoff, fallback chains, health14- [x] `docs/ROUTER-RESEARCH.md` complete15- [x] 0.B.1 Zyquo Cloud provider layer studied (base URLs, auth, Codable models, OpenAICompatibleClient, AnthropicClient, GeminiClient, SSE parsing)16- [x] 0.B.2 Complete model catalog + capabilities + pricing extracted17- [x] 0.B.3 SecureKeyStore vault design (AES-256-GCM, NO Keychain) documented for reuse18- [x] 0.B.4 Provider streaming quirks documented19- [x] `docs/PROVIDER-REUSE.md` complete2021**Phase gate: PASSED (2026-07-30).**2223**Phase 0 summary:** `docs/ROUTER-RESEARCH.md` (1,646 lines) compiled from four intensive24web-research tracks: full current OpenAI chat/completions + SSE contract, gateway patterns25from LiteLLM/OpenRouter/Ollama/LM Studio/vLLM, exact bidirectional Anthropic and Gemini26translation matrices plus a 9-provider OpenAI-compat deviation table, SwiftNIO chosen for27serving, and ops (redaction, usage/cost, rate limits, retries, fallbacks). 12 binding28decisions (D1–D12) recorded up front. `docs/PROVIDER-REUSE.md` (788 lines) documents Zyquo29Cloud's 12 providers / 170-model catalog / `vault.zq` AES-256-GCM SecureKeyStore (no30Keychain) and a file-by-file port plan; key caveats: Cloud has no native GeminiClient31(Router translates Gemini natively per D10) and no tool-calling in Cloud's wire types32(Router extends them). All 12 real provider keys smoke-tested OK (HTTP 200).3334## Phase 1 — Project setup (SPM, no Xcode IDE)3536- [x] 1.1 `Package.swift`: executable target `ZyquoRouter`, macOS 13+, deps = SwiftNIO (NIOCore/NIOPosix/NIOHTTP1/NIOExtras) + swift-markdown only37- [x] 1.2 `Makefile`: `make dev` (debug build + ad-hoc-signed `Zyquo Router.app` + run), `make build`, `make app` (release universal), `make clean`; release/notarize targets stubbed for Phase 838- [x] 1.3 `Resources/Info.plist`: `CFBundleDisplayName` = `Zyquo Router`, `com.zyquo.router`, `LSMinimumSystemVersion` 13.0, `NSHighResolutionCapable`, `LSApplicationCategoryType` developer-tools39- [x] 1.4 Entry point: `@main` SwiftUI `App` + AppDelegate activation from terminal; placeholder window40- [x] 1.5 `swift build` clean, zero warnings; `make dev` launches the app window41- [x] 1.6 Mandatory file headers on every code file created4243**Phase gate: PASSED (2026-07-30).**4445**Phase 1 summary:** SPM executable `ZyquoRouter` (macOS 13+, deps: SwiftNIO 2 +46NIOExtras + swift-markdown) builds with zero warnings on the Xcode 6.3.3 toolchain —47no SDKROOT pin needed here, unlike Cloud's CLT-only machine (noted in the Makefile).48`make dev` assembles `dist/Zyquo Router.app` (com.zyquo.router, developer-tools49category, ad-hoc signed, plist lints OK); the placeholder SwiftUI window launches50from terminal, activates, and quits cleanly. Test target scaffolded and green.51Family conventions ported from Zyquo Cloud's Makefile/write-info-plist.sh, incl.52the Developer ID identity + notary profile constants for Phase 8.5354## Phase 2 — Architecture + server skeleton5556- [x] 2.1 Port from Zyquo Cloud (headers → `Zyquo Router`, type names kept): `ProviderID`, `AIModel`, `Message`+`ChatParameters`, `ProviderProtocol`, `ProviderRegistry`, `OpenAICompatibleClient`, `AnthropicClient`, `StreamingService`, `SecureKeyStore`, `ModelCatalog`, `ModelCatalogData` (170 models, byte-identical), `PersistenceService`, SSE + vault tests57- [x] 2.2 Vault decision recorded: own vault at `~/Library/Application Support/ZyquoRouter/vault.zq`, HKDF info `"ZyquoRouter.vault.v1"`; User-Agent `ZyquoRouter/1.0`58- [x] 2.3 `Server/`: `HTTPServer` (NIOAsyncChannel bootstrap, bind host/port, EADDRINUSE detection, graceful shutdown), `Routes` (`GET /health`, `GET /v1/models`), `SSEWriter` skeleton, `CORS`, `AuthMiddleware` skeleton59- [x] 2.4 `Router/`: `RequestRouter` (namespaced `provider/model` + unambiguous bare ID resolution; alias/fallback hooks), `RetryPolicy` + `UsageMeter` stubs60- [x] 2.5 App owns server lifecycle (start/stop; placeholder UI button + port field)61- [x] 2.6 Phase gate: server starts on chosen port; `/health` ok; `/v1/models` returns full namespaced catalog (spec shape); port-in-use → clean error; graceful stop drains62- [x] 2.7 `swift build` zero warnings; headers sweep6364**Phase gate: PASSED (2026-07-30).**6566**Phase 2 summary:** Provider layer ported from Zyquo Cloud (12 providers, 170-model67catalog byte-identical, SecureKeyStore with own vault `ZyquoRouter/vault.zq` + HKDF info68`ZyquoRouter.vault.v1`, StreamingService with `ZyquoRouter/1.0` UA); Cloud's SSE + vault69tests ported and green. New: `HTTPServer` on NIOAsyncChannel structured concurrency70(keep-alive, 32 MB body cap, EADDRINUSE → "Port N in use — try N+1"), `Routes`71(/health, /v1/models, /v1/models/{id} incl. IDs containing "/"), `SSEWriter`,72`CORS` (preflight 204), `AuthMiddleware` (hashed `zyquo-sk-…` gate, open when no keys),73`RequestRouter` (namespaced/bare/alias/disabled resolution), `RetryPolicy`, `UsageMeter`,74`APIKeyRecord`, `ServerController` + minimal Start/Stop UI, and a headless75`--serve [port]` CLI mode for scripted verification. Gate verified live with curl76(170 models, spec shapes, 404s, CORS) and by integration tests (graceful stop rebinds77immediately; port-in-use typed error). 23 tests green; zero warnings; headers swept.787980## Phase 3 — Standardized API (the core)8182- [x] 3.1 OpenAI wire layer in `Translate/OpenAINormalizer.swift`: parsed canonical request (raw JSON + typed fields, unknown params preserved per D5), response/chunk emission helpers83- [x] 3.2 `Translate/CompatAdjuster.swift`: per-provider param strip/rename/clamp table (seeded from `ParameterSupport` + research §3.3) for the 10 OpenAI-compatible upstreams; response/chunk quirk normalization (Together `eos`/`text`, Mistral ThinkChunk, Perplexity citations, model echo → namespaced ID)84- [x] 3.3 `Router/UpstreamCall.swift`: executes the upstream request (auth per provider), non-streaming + SSE; pre-first-byte retries via `RetryPolicy`; fallback chains; client-disconnect cancellation85- [x] 3.4 `Translate/AnthropicTranslator.swift`: request (system extraction, tools, tool_choice, max_tokens required, temp clamp), response (stop_reason/usage mapping, tool_use → tool_calls), SSE state machine → byte-exact OpenAI chunks (text/tool/thinking deltas)86- [x] 3.5 `Translate/GeminiTranslator.swift` (net-new, native API per D10): contents/parts, systemInstruction, generationConfig, tools/functionDeclarations, finishReason table, streamGenerateContent?alt=sse → OpenAI chunks87- [x] 3.6 `POST /v1/chat/completions` route: parse → resolve (404/ambiguous) → capability checks → upstream → spec-exact response/stream; `stream_options.include_usage`; reasoning_content normalization (D6); usage estimation flagged when upstream omits (D9); OpenAI-format error mapping (D7)88- [x] 3.7 Access control: local `zyquo-sk-…` keys enforced on chat route (per-key model allow-list), LAN bind requires ≥1 key89- [x] 3.8 Fixture unit tests: Anthropic events → chunks, Gemini → chunks, CompatAdjuster tables, SSE edge cases90- [x] 3.9 `docs/API.md` written as the exact public contract91- [x] 3.10 PHASE GATE: streaming + non-streaming verified with curl AND official OpenAI Python SDK against one OpenAI-compatible provider, Anthropic, and Gemini (real keys, through the router only)9293**Phase gate: PASSED (2026-07-30).**9495**Phase 3 summary:** `POST /v1/chat/completions` is live and spec-exact. New layers:96`ChatCompletionRequest` (typed parse + raw pass-through per D5), `CompatAdjuster`97(per-provider strip/rename/clamp + quirk normalization: Together `eos`, Mistral98thinking arrays, Perplexity `<think>` + citations, DeepSeek cache tokens, usage-chunk99swallowing), `AnthropicTranslator` (full request map incl. turn merging/tools/thinking100budget; SSE state machine → byte-exact chunks), `GeminiTranslator` (native101generateContent per D10 incl. functionResponse object-wrapping, STOP+functionCall102override, synthesized role chunk + router-added [DONE]), `UpstreamCall` (per-provider103endpoints/auth + ProviderError→OpenAI wire mapping D7), `ChatCompletionsRoute`104(capability gates, pre-first-byte retries D8, fallback chains, stream-aggregation for105streaming-only models, estimated-and-flagged usage D9, UsageMeter recording).106Local `zyquo-sk` keys enforced (401/403 paths verified live; /health stays open; LAN107requires ≥1 key). `--load-vault` seeds the encrypted vault from env. `docs/API.md`108written as the served contract. GATE: curl + OpenAI Python SDK 2.51 unmodified —109non-streaming, streaming (role/finish/usage/[DONE] discipline), and streamed tool110calls all PASS against xAI (compat), Anthropic (translated), Gemini (native). 31 unit/111fixture tests green; zero warnings; headers swept.112113114115## Phase 4 — Design system & UI116117- [x] 4.1 `DesignSystem/ZyquoTheme.swift`: Router palette per spec (bg `#FAFBFC`, surface `#FFFFFF`, accent signal-cyan `#0891B2` + graphite `#374151`, subtle `#E5F5F9`, text `#191C1F/#697077/#9BA3AA`, border `#E4E8EB`, status greens/ambers/reds, chart tokens); dark "ops room" derived; family type/spacing/radii/motion tokens; SF Mono where data lives118- [x] 4.2 `AppearanceStore`: Light/Dark/System + accents cyan (default), graphite, sky, emerald, violet, copper119- [x] 4.3 Shared components: status pill (● Running on :port / ○ Stopped), cards, section headers, mono copy fields, capability badges, hairlines120- [x] 4.4 App shell: 240pt translucent navigator (Dashboard/Models/Requests/Keys/Playground/Docs + footer gear + permanent status pill with Start/Stop), 1280×820 default / 1020×660 min121- [x] 4.5 Dashboard: hero server card (status, big Start/Stop, port field, bind selector + LAN warning, endpoint URL with Copy + curl/Python/JS snippets), live tiles row (requests, tokens, cost, errors, uptime), first-run onboarding card122- [x] 4.6 Models: searchable/filterable catalog, mono namespaced IDs with one-click copy, capability badges, context + pricing columns123- [x] 4.7 Keys: Provider Keys tab (masked fields, save/delete, Test with status dot + latency, vault-backed) + Local API Keys tab (create/reveal-once/revoke)124- [x] 4.8 Requests: table + detail structure with empty state (live data lands in Phase 6)125- [x] 4.9 Playground: model picker, composer, params, streaming toggle — calls the router's own endpoint126- [x] 4.10 Docs: in-app rendering of docs/API.md (bundled into the app)127- [x] 4.11 Settings window (720×540 tabs): Server + Appearance functional; Logging/Usage/Shortcuts/Advanced structured128- [x] 4.12 Zero warnings; headers; `make dev` visual pass over stopped/starting/running/port-conflict/no-keys states129130**Phase gate: PASSED (2026-07-30) — layout/structure level; the full every-state design131quality gate re-runs at the end of Phase 6 once all features are live.**132133**Phase 4 summary:** `ZyquoTheme` carries the spec palette exactly (graphite-cyan light134flagship, dark ops-room derived, provider hues, chart tokens, family type/spacing/radii/135motion; SF Mono for all data). `AppearanceStore` with cyan/graphite/sky/emerald/violet/136copper accents. Shell: 240pt translucent navigator (NSVisualEffectView sidebar), six137sections, footer gear + permanent StatusPill with Start/Stop; ⌘1–6 Go menu, ⌘⇧C copy138endpoint, ⌘R start/stop. Screens live: Dashboard (hero server card with port/bind/LAN139warning, endpoint + curl/Python/JS copy-as snippets, five 1Hz tiles fed by UsageMeter,140first-run onboarding card), Models (search/provider filter over the 170-model catalog,141mono IDs + hover copy, badges, context/pricing columns), Requests (table over142UsageRecords + empty state), Keys (Provider tab identical to Cloud UX incl. Test with143latency; Local tab create/reveal-once/enable/revoke), Playground (calls own endpoint,144streaming + pretty JSON), Docs (bundled docs/API.md renderer with copyable code blocks),145Settings 6 tabs (Server/Appearance fully live). Verified visually via screenshots:146stopped + running dashboard, Models, Keys, Docs all match the spec. 31 tests green,147zero warnings, headers swept.148149150151## Phase 5 — App icon152153- [x] 5.1 Author both motif candidates in SVG (viewBox 1024, family superellipse): `zyquo-router-hub.svg` (Z-hub: three gradient lines fan to node endpoints + cyan port dot) and `zyquo-router-switch.svg` (Z over crossing signal paths with chevrons); spec colors: #FCFCFD bg, #0B0B14→#1C1C2A Z, #2B6BFF→#4C2BE0 base, cyan #22B8D4 accents154- [x] 5.2 Render at 16→1024, inspect, pick the best → `assets/icon/zyquo-router.svg` (source of truth)155- [x] 5.3 Simplified small-size variant (drop lines/nodes) for 16/32 px156- [x] 5.4 `scripts/generate-icon.sh`: rsvg-convert → AppIcon.iconset (16→1024 + @2x) → `iconutil -c icns` → Resources/AppIcon.icns; menu bar template icon (18/36 px mono Z-with-signal-dot) from the same source157- [x] 5.5 App bundle carries the icon; visual check in Dock/Finder158159**Phase gate: PASSED (2026-07-30).**160161**Phase 5 summary:** Both spec'd motifs authored on the authentic family superellipse162(extracted from Cloud's icon): `zyquo-router-hub.svg` (Z-hub — chunky #0B0B14→#1C1C2A Z,163#2B6BFF→#4C2BE0 parallelogram base, three cyan→indigo fan lines anchored into the Z's164diagonal, glowing cyan port dot, three soft-3D node squares) and `zyquo-router-switch.svg`165(crossing signal paths + chevrons behind the centered Z). **Hub chosen** — more166distinctive, tells "one in, many out". `zyquo-router.svg` is the source of truth;167`zyquo-router-small.svg` (Z + base only) feeds 16/32 px; `zyquo-router-template.svg`168renders the mono menu-bar glyph. `scripts/generate-icon.sh` (rsvg-convert → iconset →169iconutil) produces `Resources/AppIcon.icns` + `MenuBarIcon(@2x).png`; `make icon` wired,170bundle carries the icns, and the icon verified in the Dock at small size.171172173174## Phase 6 — Features175176- [x] 6.1 `RouterConfigStore` (aliases, fallback chains, disabled models, favorites) persisted to router-config.json; snapshot feeds `RequestRouter` at Start; Models screen gets enable/disable toggles, favorites, alias editor, fallback-chain editor177- [x] 6.2 `RequestLogStore` (ring buffer, redacted-by-default bodies, per-session reveal) wired into the chat route with timing (TTFB/duration); Requests screen: live table, filters (provider/status/model), pause/clear, detail pane with pretty JSON + timing waterfall, ⌘F focuses filter, export logs178- [x] 6.3 Dashboard: requests/min sparkline (cyan), per-provider breakdown bar, active streams tile; latency in tiles179- [x] 6.4 Menu bar extra (first-class): template glyph, status + port, Start/Stop, req/min, today's cost, Copy endpoint URL; toggleable in Settings180- [x] 6.5 Playground: params (temperature/top_p/max_tokens/reasoning), side-by-side raw request/response JSON with copy-as-code181- [x] 6.6 Launch at login (SMAppService) + auto-start (done) + keep-serving (done); ⌘K command palette (start/stop, sections, copy endpoint, model search)182- [x] 6.7 Settings: Logging (retention/export), Usage (reset counters), Advanced (export/import config without keys)183- [x] 6.8 Phase 4 design quality gate re-run across all states; zero warnings; headers sweep184185**Phase gate: PASSED (2026-07-30).**186187**Phase 6 summary:** `RouterConfigStore` (aliases/chains/disabled/favorites →188router-config.json, snapshotted into `RequestRouter` at Start, honored by headless189`--serve` too); Models screen gained enable/disable switches (strikethrough + 404),190favorite stars, an alias editor and a drag-reorderable fallback-chain editor with CHAIN191badges. `RequestLogStore` (500-entry ring, bodies in memory only) wired through the chat192route with upstream TTFB; Requests is now a live 0.5 Hz table with provider/status/model193filters (⌘F), pause/clear/export, and a detail inspector (timing waterfall, redacted-by-194default bodies with per-session reveal, error detail). Dashboard: requests/min sparkline195(cyan token), per-provider breakdown bar + legend, active-streams tile. First-class196MenuBarExtra (template Z glyph, status, Start/Stop, req/min + cost, copy endpoint,197toggleable). Playground: temperature/max_tokens/reasoning controls + side-by-side198REQUEST JSON / RAW RESPONSE panes with copy. ⌘K command palette (server, sections,199copy endpoint, model-ID search/copy); ⌘R moved to a Server menu so it works from every200screen (bug found in live pass). Settings: launch-at-login (SMAppService), menu-bar201toggle, usage reset, config export/import (never keys). Live-verified end-to-end with202real traffic: tiles, sparkline, breakdown, log rows + SSE badges all populate. 31 tests203green; zero warnings; headers swept.204205206207## Phase 7 — Verification with real keys208209- [x] 7.1 `scripts/verify.py` harness (official OpenAI Python SDK against the router only): sweeps GET /v1/models, runs non-stream + stream per model (SDK-parsed chunk shapes), tools on tool-capable, vision on vision-capable, reasoning content on reasoning models; produces the matrix in `docs/VERIFICATION.md`210- [x] 7.2 Full matrix green (or every failure diagnosed + fixed; upstream-side outages/rate limits documented as such)211- [x] 7.3 Client compatibility: OpenAI Python SDK + OpenAI JS SDK + curl, both modes incl. streamed tool calls, unmodified212- [x] 7.4 Gateway behavior: port conflict; graceful shutdown with an active stream; client-disconnect cancels upstream; retry policy on 429; fallback chain reports actually-used model; local-key auth paths (valid/invalid/revoked); LAN refuses without key213- [x] 7.5 Security: provider keys never in any response/log/error; logs redacted by default; vault round-trips214- [x] 7.6 Fix-until-green loop complete; results committed215216**Phase gate: PASSED (2026-07-30).**217218**Phase 7 summary:** `scripts/verify.py` drove all **170 catalog models** through the219local endpoint with the official OpenAI Python SDK — final matrix in220`docs/VERIFICATION.md`: **170/170 green** (2 rows carry documented upstream-side221limitations: Together refuses tool_choice=required on Qwen3.7-Plus; DeepInfra 405s tool222requests on its Llama-4-Maverick deployment). The fix-until-green loop produced real223router improvements: Claude 4.7+/5 adaptive thinking, reasoning_effort stripped for224models that 400 on it (Grok 4.20/code), reasoning_effort:"none" unlock for gpt-5.6225tools, Mistral prompt_mode/none-high clamping, Perplexity `.done` normalization +226synthesized role/finish discipline for deviant streams, missing `object` injection, and227transparent stream-aggregation (now wired for all three wire formats) for models whose228buffered endpoints are broken/too slow. Client compat: OpenAI Python + JS SDKs + curl,229both modes incl. streamed tool calls, unmodified. Gateway behavior integration-tested230against mock upstreams: client-disconnect cancels the upstream in <1s, fallback chains231answer with the actually-used model, transient 429 retries, graceful shutdown ends232active streams and rebinds; LAN-without-key refusal verified in the app; auth paths233(open/valid/invalid/revoked/malformed) unit-tested. Security: all 12 key fragments234absent from every response, error, server log, and the vault file (ciphertext only);235log bodies redacted by default. 26 tests green; zero warnings; headers swept.236237238239## Design polish pass (post-Phase 7, user-requested)240241- [x] Rounder, more modern surfaces: ZyquoRadius 10/16/22 with `.continuous` squircle242 corners everywhere, cards with a whisper of depth, capsule Start/Stop with icon +243 tinted shadow244- [x] Docs screen redesigned: gradient hero card with live endpoint + copy, code blocks245 with mac-dots header bar / language chip / one-click copy, real bordered tables with246 alternating rows, accent-bar H2s, cyan bullet dots with proper hard-wrap continuation247- [x] `README.md` written (quickstart incl. pointing the OpenAI SDKs at the router,248 highlights table, build targets, docs index)249- Light theme remains the flagship; all tokens unchanged except radii.250251## Phase 8 — Signing & notarization252253- [x] 8.1 Inspect `~/Desktop/other/OTHER/zyquo-term`: Developer ID identity, Team ID, notarytool profile, entitlements — reuse exactly (never print/commit secrets)254- [x] 8.2 `entitlements.plist` with Hardened Runtime, minimal set; NOT sandboxed (developer tool serving localhost) — documented255- [x] 8.3 `make release`: universal (arm64+x86_64 lipo) → assemble → codesign `--options runtime --timestamp` → ditto zip → `notarytool submit --wait` → staple256- [x] 8.4 `spctl -a -vv` says "accepted, source=Notarized Developer ID"; `stapler validate` passes257- [x] 8.5 Optional signed DMG258- [x] 8.6 `make dev` (ad-hoc) still works for iteration259260**Phase gate: PASSED (2026-07-31).**261262**Phase 8 summary:** Reused the zyquo-term pipeline exactly: identity `Developer ID263Application: Simon-Pierre Boucher (3YM54G49SN)`, notarytool keychain profile264`MacLustr-Notarize`, and the same minimal non-sandboxed entitlements (Hardened Runtime,265`allow-jit` false; rationale documented in ZyquoRouter.entitlements — localhost266listening needs no entitlement outside the sandbox). `make release`: universal binary267(arm64 + x86_64 via lipo), assemble, `codesign --force --options runtime --timestamp`268(nested executable first), `ditto -c -k` → `notarytool submit --wait` (**Accepted**),269`stapler staple`. Verified: `spctl -a -vv` = "accepted, source=Notarized Developer ID";270`stapler validate` passes. Bonus: `dist/ZyquoRouter.dmg` built, signed, notarized271(**Accepted**) and stapled. `make dev` (ad-hoc) still works for iteration.272273---274275**ALL PHASES COMPLETE — Definition of Done satisfied (2026-07-31).**276277