* chore(release): open v3.8.31 development cycle * fix(mitm): exact host membership in MITM hosts test (CodeQL false positive) (#4386) getMitmToolHosts returns string[], so .includes(host) is Array.prototype.includes (exact membership). CodeQL's js/incomplete-url-substring-sanitization heuristic misreads it as a String.includes() URL-substring sanitization check and raises a HIGH alert. Switch to .some(h => h === host) — identical semantics, explicit intent, no flagged pattern. Surfaced post-v3.8.30 (#4325) once the test landed on main. Test-only change (no runtime behavior); the suite still passes and the CodeQL re-scan on merge clears the alert. * fix(codex): request reasoning summaries (#4359) Adds reasoning.summary=auto + reasoning.encrypted_content include for Codex. Thanks @xz-dev. * fix(embeddings): inject NVIDIA NIM input_type for asymmetric embed models (#4341) NVIDIA NIM asymmetric embedding models (e.g. nvidia/nv-embedqa-e5-v5) reject requests without an `input_type` ("query" | "passage") with 400 "'input_type' parameter is required". The embedding registry now carries a model-level default param for the asymmetric NVIDIA model, and the embeddings handler injects a model's default params into the upstream body only when the client omitted them, leaving a client-supplied value untouched. Reported-by: hydraromania (https://github.com/decolua/9router/issues/1378) Co-authored-by: hydraromania <252583922+hydraromania@users.noreply.github.com> * fix(api): migrate deprecated Codex [features].codex_hooks to [features].hooks (#4342) Codex renamed the `codex_hooks` feature flag to `hooks`; recent Codex CLI versions ignore the old key and warn. When OmniRoute rewrites an existing config.toml (configure/reset Codex provider) it now renames [features].codex_hooks -> [features].hooks, preserving the value and never clobbering an already-present `hooks`, then drops the deprecated key. The migration is a no-op when the flag is absent and runs on both the POST and DELETE config paths. Reported-by: Bian-Sh (https://github.com/decolua/9router/issues/1327) Co-authored-by: Bian-Sh <24520547+Bian-Sh@users.noreply.github.com> * fix(translator): drop the null flush on the same-format response path (#4344) The streaming response translator's same-format fast path returned `[chunk]` unconditionally, so the end-of-stream null/flush signal (chunk === null) propagated as a literal `[null]`. Downstream this surfaced as an empty `data: null` SSE event between chunks and crashed strict clients (e.g. Factory Droid BYOK on /v1/responses). The fast path now returns `[]` for the null flush while still passing real chunks through unchanged. Reported-by: thaitryhand (https://github.com/decolua/9router/issues/1052) Co-authored-by: thaitryhand <248103256+thaitryhand@users.noreply.github.com> * fix(translator): strip assistant echo fields on the OpenAI target path (Mistral 422) (#4350) Strict OpenAI-compatible upstreams (e.g. mistral/codestral-latest) reject client-only assistant echo fields sent back as input with 422 extra_forbidden (the report hit messages[].assistant.reasoning_content via Codex /responses). Only reasoning_content was stripped on the OpenAI target path; the sibling fields reasoning / refusal / annotations / cache_control leaked through. They are now all dropped on the non-reasoner OpenAI target path. `audio` is intentionally preserved (OpenAI audio models reference a prior assistant audio response by id; Mistral never emits audio). Reported-by: xxy9468615 (https://github.com/decolua/9router/issues/1649) Co-authored-by: xxy9468615 <63351664+xxy9468615@users.noreply.github.com> * fix(cli): honor TAILSCALE_AUTHKEY for non-interactive tailscale login (#4343) * fix(cli): honor TAILSCALE_AUTHKEY for non-interactive tailscale login (port from 9router#1263) startTailscaleLogin built `tailscale up` without ever reading process.env.TAILSCALE_AUTHKEY, so a pre-authenticated / headless daemon waited for an interactive auth URL and timed out (~15s). When TAILSCALE_AUTHKEY is set it is now passed via `--auth-key=` (an argv element to spawn(binary, args) — no shell interpolation, Hard Rule #13); when unset, behavior is unchanged. The arg builder is extracted into a pure exported `tailscaleUpArgs()` for testing. Reported-by: ipeterpetrus (https://github.com/decolua/9router/issues/1263) Co-authored-by: ipeterpetrus <93033698+ipeterpetrus@users.noreply.github.com> * chore(quality): rebaseline tailscaleTunnel.ts file-size to 1202 (#1263 +13) --------- Co-authored-by: ipeterpetrus <93033698+ipeterpetrus@users.noreply.github.com> * fix(dashboard): OAuth modal surfaces the real error on a non-JSON response (#4351) * fix(dashboard): OAuth modal surfaces real error on non-JSON responses (port from 9router#1318) The OAuth connect/reauth modal called `await res.json()` unconditionally, so a non-JSON error response (e.g. a plain-text 500 page from a build/OAuth endpoint) threw `Unexpected token 'I'...` and hid the real failure. New shared helpers parseResponseBody / getErrorMessage (src/shared/utils/api.ts) read the body safely (JSON when JSON, raw text otherwise) and produce a clean message either way; every modal fetch site now uses them. Reported-by: DNNYF (https://github.com/decolua/9router/issues/1318) Co-authored-by: DNNYF <74033321+DNNYF@users.noreply.github.com> * fix(dashboard): type OAuth modal response body as Record<string, unknown> (t11 any-budget) Switch the parseResponseBody casts from Record<string, any> to Record<string, unknown> so OAuthModal.tsx stays within its t11 explicit-any budget. getErrorMessage already takes unknown; the success paths typecheck clean under strict:false. No runtime change. --------- Co-authored-by: DNNYF <74033321+DNNYF@users.noreply.github.com> * fix(translator): accept AI SDK-style { type: image, image: data-URL } content parts (#4345) * fix(translator): accept AI SDK-style { type: image, image: "data:..." } parts (port from 9router#1330) Several OpenAI-input translators only recognized images shaped as `image_url.url` (or an object with `.source`/`.url`), so an AI SDK-style content part where `image` is a bare data-URL STRING was silently dropped before reaching a vision provider (OpenCode is one affected client; the gap is generic). The OpenAI->Claude, OpenAI->Kiro and OpenAI->Gemini/Antigravity translators now parse a string `image` data URL into each provider's native image shape (Claude base64 source, Kiro images[].source.bytes, Gemini inlineData). Reported-by: mugnimaestra (https://github.com/decolua/9router/issues/1330) Co-authored-by: mugnimaestra <13349159+mugnimaestra@users.noreply.github.com> * chore(quality): freeze openai-to-kiro.ts file-size at 807 (#1330 +9, over 800 cap) --------- Co-authored-by: mugnimaestra <13349159+mugnimaestra@users.noreply.github.com> * fix(dashboard): show a disabled connection's last error in the row (#4352) * fix(dashboard): show a disabled connection's last error in the row (port from 9router#1447) The provider card's error badge counts a disabled connection (isActive === false) that has an error — its effective status is still error/expired/unavailable — but the connection row hid the lastError text for disabled rows, so the operator saw the count without the cause. The row's error-visibility decision is extracted into shouldShowConnectionLastError() and now shows the error whenever there is one, regardless of the active toggle. Reported-by: ntdung6868 (https://github.com/decolua/9router/issues/1447) Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> * chore(quality): rebaseline ConnectionRow.tsx file-size to 942 (#1447 +1 import) --------- Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> * fix(providers): bound the OAuth connection-test probe with a timeout (#4347) * fix(providers): bound OAuth connection-test probe with a timeout (port from 9router#1449) The OAuth path of "Test Connection One-by-One" called bare fetch() with no AbortController/signal, so a provider probe that accepted the socket but never responded wedged the test queue forever. Both the initial probe and the post-refresh retry are now bounded with AbortSignal.timeout(30s) — matching the API-key path's existing budget — and a timed-out probe resolves as a failure with a clear "Test timed out after 30s" message in the route's normal error shape. Reported-by: ntdung6868 (https://github.com/decolua/9router/issues/1449) Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> * chore(quality): rebaseline providers test route file-size to 887 (#1449 + sibling #1444 growth) --------- Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> * fix(providers): label a deactivated account distinctly from a revoked token (#4353) A Codex connection whose OAuth refresh is fully healthy but whose ChatGPT account has been deactivated by the provider gets a 401 from the upstream API. The connection test labeled that the same as a bad credential ("Token invalid or revoked" -> upstream_auth_error), so an operator could not tell a deactivated account from a revoked token. The test now reads the 401/403 body and, when it indicates account deactivation, classifies it as account_deactivated (which the dashboard already renders as "Account Deactivated"); a plain auth 401 is unchanged. Reported-by: ntdung6868 (https://github.com/decolua/9router/issues/1444) Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> * fix(db): cascade-delete orphaned model aliases when a provider is removed (#4348) * fix(db): cascade-delete orphaned model aliases when a provider is removed (port from 9router#1409) Deleting a custom provider removed its connections and node but left the imported model-alias rows (key=<alias>, value="<providerId>/<model>") behind, so re-importing the same provider was blocked by stale "already exists" aliases. Add a deleteModelAliasesForProvider(providerId) DB helper that drops every alias whose stored value begins with "<providerId>/", and call it from the provider-node DELETE handler so a fresh import is unblocked. Reported-by: nguyenvanhuy0612 (https://github.com/decolua/9router/issues/1409) Co-authored-by: nguyenvanhuy0612 <57367674+nguyenvanhuy0612@users.noreply.github.com> * chore(quality): rebaseline models.ts file-size to 1221 (#1409 + sibling #1294 growth) --------- Co-authored-by: nguyenvanhuy0612 <57367674+nguyenvanhuy0612@users.noreply.github.com> * fix(api): persist max_input_tokens/max_output_tokens when adding a custom model (#4349) The POST /api/provider-models handler read the rest of the body but never the two token-limit fields, and addCustomModel() had no parameter for them, so the form values were dropped on write while the DB layer and /v1/models catalog already round-trip inputTokenLimit/outputTokenLimit. Accept the two optional limits in the schema, forward them through the handler, and persist them in addCustomModel(). TDD: failing-then-passing unit test. Reported-by: codename-zen (https://github.com/decolua/9router/issues/1294) Co-authored-by: codename-zen <263238141+codename-zen@users.noreply.github.com> * docs: feature-documentation catch-up (v3.8.20 → v3.8.30) (#4391) One-time reconciliation of the docs with every user-facing feature shipped since v3.8.20 (we had never done a dedicated pass, so debt had accumulated): - README: new '✨ What's New' section (curated v3.8.20→v3.8.30 highlights). - New guides: CLI-INTEGRATIONS (all setup-*/launch commands), MITM-TPROXY-DECRYPT (transparent-decrypt epic), CONTEXT_EDITING (delegated Anthropic clear_tool_uses). - Refreshed: AUTO-COMBO (auto/<category>:<tier> + Arena-ELO), API_REFERENCE (x-omniroute-no-memory), MEMORY (int8 quantization + off-by-default), RESILIENCE (model-lockout success-decay), RTK, AGENTBRIDGE, TRAFFIC_INSPECTOR, GUARDRAILS, CLOUD_AGENT, ENVIRONMENT, SETUP_GUIDE, CLI-TOOLS, MCP-SERVER. - Regenerated PROVIDER_REFERENCE (231 providers); synced the count in README/CLAUDE/AGENTS. - Allowlisted external-tool env vars (OPENAI_API_BASE, PROMPTFOO_PROVIDER_KEY) and the STREAM_RECOVERY config-object name in the docs-accuracy gates. All claims source-verified; check:docs-all (sync/counts/env/links/fabricated) passes. Going forward this runs every release via generate-release step 6b. * fix(executors): don't inject thinking when tool_choice forces a tool (native Claude) (#4389) Forced tool_choice now strips the adaptive thinking injection to avoid Anthropic 400. Thanks @NomenAK. * fix(translator): Gemini accepts HTTP/HTTPS image URLs (port from 9router#344) (#4373) OpenAI-style `image_url` parts with an `http://` or `https://` URL reached `convertOpenAIContentToParts` and were dropped with only a `console.warn`, because Gemini's `inlineData` requires base64 (the helper is synchronous and cannot fetch+encode upstream assets). Gemini's `Part` schema, however, natively accepts `fileData: { fileUri }` for remote URIs — the model fetches the asset itself. The helper now emits a `fileData` part (`mimeType: "image/*"`, inferred upstream on fetch) for HTTP/HTTPS URLs instead of silently dropping them. Vision requests that pass a URL — not a data: URI — now reach Gemini intact. No behavioral change for: - `data:` URIs → still emitted as `inlineData` with the parsed media type. - Unsupported schemes (e.g. `ftp:`) → still skipped (Gemini would reject them). The openai-to-claude side already passed HTTP/HTTPS URLs through as `source: { type: "url", url }` (lines 573–578) — the upstream PR's Claude-side change was already covered. Regression test: tests/unit/gemini-helper-http-image-url-port344.test.ts (4 cases: https URL, http URL, data: URI no-regression, unsupported-scheme guard). Inspired-by: https://github.com/decolua/9router/pull/344 Co-authored-by: Ibrahim Ryan <ryan@nuevanext.com> * fix(executors): strip stream_options for qwen non-streaming / thinking Claude Code requests (port from 9router#663) (#4374) Claude-Code-compatible providers force the executor-level `stream` flag on via `upstreamStream = stream || isClaudeCodeCompatible` (open-sse/handlers/chatCore.ts), but the outgoing body keeps the caller's original `stream: false`. The shared `stream && targetFormat === "openai"` branch in DefaultExecutor.transformRequest then injected `stream_options: { include_usage: true }` onto a body that still said `stream: false`, and qwen upstream rejected the request with `400 "'stream_options' only set this when you set stream: true"`. The same rejection surfaced when the body carried `thinking` / `enable_thinking`. The qwen branch now skips the injection (and strips any client-sent `stream_options`) when the body explicitly says `stream: false` or requests thinking, leaving regular qwen streaming requests with the include_usage injection intact. Other providers are unaffected. Adds a TDD regression with 4 cases covering both opt-out paths and the normal-streaming positive control. Inspired-by: https://github.com/decolua/9router/pull/663 Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com> * fix(security): scope OAuth callback postMessage to a trusted-origin allowlist (port from 9router#998) (#4372) The OAuth callback at `/callback` previously fell back to `window.opener.postMessage({ code, state, ... }, "*")` whenever the opener was cross-origin. The fallback was intended to support remote-OmniRoute + local-loopback callbacks (where opener and callback live on different origins), but the same code path also delivers the OAuth code/state to any hostile opener that pops the well-known callback URL — letting that attacker complete the OAuth flow as the user. Replace the wildcard fallback with iteration over a fixed allowlist: `window.location.origin` (same-origin parent — the popup-mode dashboard) plus Codex's fixed loopback helper (`http://localhost:1455` and the IPv4 literal `http://127.0.0.1:1455`). The browser drops `postMessage` to any opener whose actual origin is not in `targetOrigin`, so the message reaches only known parents and is silently dropped for any other. The same-origin fallback path is unchanged — methods 2 (`BroadcastChannel`) and 3 (`localStorage` storage event) still cover same-origin openers that COOP severed. The `openerSameOrigin` probe stays in place to drive the auto-close vs manual-copy UI decision (no behavior change for the success path). Adds a regression test (`tests/unit/ui/oauth-callback-postmessage-scope.test.tsx`) that mounts the page with a stubbed cross-origin opener and asserts no `postMessage` call ever uses `"*"` and every call lands on an allowlisted origin. The test failed against the pre-fix code (red), passes after the fix (green) — TDD per CLAUDE.md hard rule #18. Partial port of upstream decolua/9router#998: the upstream PR also re-enabled TLS verification on a DNS-bypass fetch in `open-sse/utils/proxyFetch.js`; that part is N/A here because OmniRoute's `proxyFetch.ts` never disabled TLS verification (no `rejectUnauthorized: false` anywhere in the file). Inspired-by: https://github.com/decolua/9router/pull/998 Co-authored-by: aeonframework <aeon@aeonframework.dev> * fix(sse): default combo per-target timeout to 120s for fast failover (#4365) Combo per-target timeout inherited the full FETCH_TIMEOUT_MS (600s) when a combo did not set its own targetTimeoutMs, so a single hung/slow target stalled the whole combo for up to 10 minutes before falling through to the next model. Introduce DEFAULT_COMBO_TARGET_TIMEOUT_MS (120s) as the unset-default in resolveComboTargetTimeoutMs (new 3rd arg) and wire it in phaseComboSetup. The upstream ceiling (600s) and per-combo opt-out (targetTimeoutMs, up to the ceiling) are preserved; single non-combo requests are unchanged. For streaming requests this only bounds time-to-first-headers, so token generation is not cut short. TDD: failing-then-passing unit test in tests/unit/combo-config.test.ts. * refactor(combo): de-dup exhausted-target skip predicate across both dispatchers (#4362) Primeiro incremento da de-dup dos 2 dispatchers de combo (handleComboChat + handleRoundRobinCombo). O bloco de pre-check #1731/#1731v2 (skip de target já exhausted no provider/connection) era BYTE-IDÊNTICO nos dois (mesmas condições, mesmas mensagens), diferindo só na tag de log e no control-flow. - comboPredicates.ts: getExhaustedTargetSkipReason(target, exhaustedProviders, exhaustedConnections) — predicate PURO que retorna a mensagem de skip (ou null); cada dispatcher mantém seu próprio log-tag + control-flow (return null / continue) + fallbackCount. No mutate do stryker (cobertura de mutação). - combo.ts: −20 linhas (os 2 blocos viram 1 chamada cada). - 7 testes de caracterização travam condições + strings exatas. Comportamento preservado: 376/376 testes combo (357 caracterização + 7 novos), integração sse-correctness 5/5, typecheck 0, complexity neutro (1895), file-size encolhe. Próximo incremento: de-dup do error-handling/exhausted-tracking (handleTargetError). * refactor(combo): de-dup upstream-error exhaustion classification across both dispatchers (#4366) Segundo incremento da de-dup dos 2 dispatchers (handleTargetError). Após cada erro de target, ambos rodavam um bloco quase-idêntico que marca o provider exhausted (#1731), a conexão connection-errored (#1731v2) ou o provider transiently rate-limited. - combo/targetExhaustion.ts: applyComboTargetExhaustion(target, opts) — atualiza os 3 Sets de exhaustion e retorna providerExhausted. As MUTAÇÕES de Set (que dirigem o skip de targets, lidas por getExhaustedTargetSkipReason) são BYTE-IDÊNTICAS nos dois; as diferenças reais viram parâmetros: tag, allAccountsRateLimited (termo extra do RR, false no handleComboChat), exhaustedLogLevel (info no handleComboChat, debug no RR). Connection-level extraído p/ markConnectionLevelExhaustion (privado, <15 complexity). - combo.ts: −73 linhas; 4 imports órfãos removidos. - 7 testes de caracterização travam as mutações + o return. ÚNICA mudança de comportamento: o WORDING das mensagens de log do RR ganha o sufixo 'on remaining targets' (cosmético; mesmo #code, mesmas mutações, mesmos níveis de log). 376/376 combo (caracterização preservada), integração sse 5/5, typecheck 0, complexity neutro (1895), file-size encolhe. * refactor(chatCore): extract checkHeapPressureGuard leaf (god-file decomposition start) (#4371) Primeiro incremento da decomposição do chatCore.ts (5127 LOC, hot-path mais quente). O guard de memória do topo do handleChatCore (rejeita 503 quando o heap V8 passa o threshold de shed) vira um leaf testável, co-locado com o threshold em heapPressure.ts. - heapPressure.ts: checkHeapPressureGuard(heapUsedMb, thresholdMb) — retorna o result 503 pronto ou null. Byte-idêntico ao guard inline (mesmo check, mesma 503, mesmo warn). A figura de heap fica em telemetria INTERNA, nunca no response do cliente (Hard Rule #12). - chatCore.ts: o bloco inline (~22 ln) vira 3 linhas; import órfão de HEAP_PRESSURE_THRESHOLD_MB trocado por checkHeapPressureGuard. - 3 testes novos (incl. assert Rule #12: o MB medido não vaza no payload). complexity-baseline 1895->1896: drift de base pós-#4338 (medido com minhas mudanças stashed = 1896); esta mudança é complexity-NEUTRA (helper complexity 2, handleChatCore só perde código). 190/190 chatcore tests, typecheck 0, file-size encolhe. * Localize CLI and stabilize fetch, memory, and coverage handling (#4383) en-only i18n, fetch-start-timeout hardening, EngineConfigPage icon fix, CI build-artifact-reuse overhaul. Memory production hunk dropped as a no-op (tests kept). Thanks @JxnLexn. * test(combo): reset circuit breakers between stream-readiness cases (restore green) (#4396) The combo-dispatch cases in combo-stream-readiness-fallback.test.ts deliberately fail `glm` (zombie streams / repeated 504s), which legitimately trips the per-provider circuit breaker. That OPEN state is a module-level singleton, so it leaked into the next test and combo.ts then SKIPPED `glm/*` targets entirely ("Skipping … circuit breaker OPEN"). That made "combo does not retry stream readiness timeouts on the same model" never attempt glm/zombie — expected ['glm/zombie','openai/gpt-5.4-mini'] but got ['openai/gpt-5.4-mini']. This was a pre-existing red on release/v3.8.31 (present at the cycle-open tip), order-dependent: the test passes in isolation, fails after the preceding cases. Add a test.beforeEach(resetAllCircuitBreakers) so each scenario starts from a clean breaker slate. Test-isolation only — the breaker behavior is correct and no production code or assertion changes. Full combo suite: 390/390 green. * fix(cli): decline confirm() cleanly on non-interactive stdin + document contexts workflow The `contexts remove` command already has `--yes` to skip confirmation, but when run without it under a non-interactive stdin (pipe, CI, EOF) the [y/N] prompt could never be answered — the readline question stayed pending and Node warned about an "unsettled top-level await" at exit. confirm() now detects `!process.stdin.isTTY` and declines cleanly (returns false), pointing at `--yes` for non-interactive use. Exported confirm() for a regression test. Docs: REMOTE-MODE.md gains a full "Managing contexts" section (list/current/use to switch between remote and local, add/show/rename, remove with --yes, export/import), fixes a `context current` -> `contexts current` typo, and the README remote-mode snippet now shows switching back to local. Verified against the live CLI: command signatures, --yes, and the non-TTY decline path all behave as documented. Tests: cli-contexts.test.ts asserts confirm() declines on non-TTY stdin (RED before, GREEN after). All docs gates (fabricated/links/symbols) pass. * ci(t11): bump any-budget for executors/base.ts (2 false-positive "any" strings) Unblocks the Fast Quality Gates on release/v3.8.31: `check:any-budget:t11` was red on `open-sse/executors/base.ts` for ALL PRs (pre-existing base drift, unrelated to this branch). The checker counts `\bany\b` after stripping comments but NOT strings, and the native-Claude tool_choice logic uses the API value `"any"` in two string literals (`tb.tool_choice === "any"`, `.type === "any"`). There are zero actual TypeScript `any` types in the file — budget set to the matched count, mirroring the existing cursor.ts false-positive entry right below it. * perf: combos UI split + next config + 1-click redis + bifrost sidecar (#3932) (#4381) Combos UI split + next.config perf + 1-click local Redis launcher + bifrost relay. Review fixes (co-author): --rm/--restart conflict, error sanitization, UI/route names, dead-guard re-doc, bifrost Zod, IPv6 + CLI test fixes. Thanks @KooshaPari. * fix(plugin): prefix OC static-catalog combo+raw keys with providerId (#4384) OC parses model ids on '/'; combo keys now carry 'omniroute/' (was 'combo/'). Live-validated against the VPS via OpenCode. Thanks @herjarsa. * docs(env): document TAILSCALE_AUTHKEY (env/docs contract drift on .31) Second pre-existing base-drift fix needed to get Fast Quality Gates green: the `repository contract is in sync` test (check:env-doc-sync) was red on ALL .31 PRs. PR #4343 added a code reference to `process.env.TAILSCALE_AUTHKEY` (src/lib/tailscaleTunnel.ts) for non-interactive `tailscale up`, but never added the var to .env.example / docs/reference/ENVIRONMENT.md — the contract requires code vars to appear in both. Add the (commented) entry to .env.example next to TAILSCALE_BIN and a row to ENVIRONMENT.md. Verified: `node scripts/check/check-env-doc-sync.mjs` → in sync. Unrelated to this branch's confirm()/docs change; surfaced because touching a quality script triggers the TIA fail-safe full unit suite. * chore(release): v3.8.31 — 2026-06-20 Finalize the v3.8.31 release: reconcile the CHANGELOG (full commit-to-bullet coverage, 26 bullets across Features/Fixed/Security/Maintenance), refresh the README What's New section, back-fill the .30/.31 sections into the 41 i18n CHANGELOG mirrors, and add TAILSCALE_AUTHKEY to the env contract (.env.example + ENVIRONMENT.md). * chore(release): align mitm-hosts test comment with main to clear merge conflict The same CodeQL false-positive fix landed twice — #4386 on release/v3.8.31 and #4387 directly on main — with only the explanatory comment differing (the assertion is byte-identical). Adopt main's comment wording on the release branch so the release PR merges without a comment-only conflict. * test(translator): align stale openai->gemini remote-URL tests with #4373 Third pre-existing base-drift fix to get Fast Quality Gates green on .31. PR #4373 ('Gemini accepts HTTP/HTTPS image URLs', port of 9router#344) intentionally changed convertOpenAIContentToParts so remote http(s) image URLs pass through as a native `fileData: { fileUri }` part instead of the old #2807 drop+warn — and added its own test (gemini-helper-http-image-url-port344.test.ts) for the new behavior. But it left three tests in translator-openai-to-gemini.test.ts asserting the OLD drop+warn contract, so they fail deterministically (verified: the file fails in isolation on clean .31). These only surface under the TIA fail-safe FULL suite, which a quality- script touch triggers. Align the three stale tests to the real, intended behavior (captured by running the function): remote URLs -> fileData.fileUri (mimeType image/*), still never inlineData (the sync path cannot fetch+encode). This is test-vs-code alignment to a deliberate, separately-tested change — not a weakened assertion. 40/40 in the file; 94/94 across the translator + env-doc combo that previously failed. * chore(quality): reconcile complexity baseline 1896->1900 (/review-prs v3.8.31 batch) (#4410) * fix(release): reconcile full-CI drift for v3.8.31 (gemini tests #4373, any-budget #4389, masking allowlist #4384) The release PR's full CI surfaced cumulative cycle drift the per-PR fast gates skip: - tests/unit/translator-openai-to-gemini.test.ts: realign 3 cases to #4373's HTTP/HTTPS-URL fileData pass-through (they asserted the old warn-and-drop). - scripts/check/check-t11-any-budget.mjs: base.ts budget 0→2 — #4389 compares tool_choice against the string literal "any" (not a TS any type). - config/quality/test-masking-allowlist.json: allowlist #4384's opencode combos net-assert reduction (obsolete combo/ namespace removed). No production behavior change. * chore(release): re-trigger full CI for v3.8.31 finalization Force a fresh pull_request CI run on the head carrying the cycle-drift fixes (gemini #4373 tests, any-budget #4389, masking allowlist #4384) — the prior synchronize event did not spawn a ci.yml run. * chore: re-trigger CI (no Actions runs registered for prior push) --------- Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: hydraromania <252583922+hydraromania@users.noreply.github.com> Co-authored-by: Bian-Sh <24520547+Bian-Sh@users.noreply.github.com> Co-authored-by: thaitryhand <248103256+thaitryhand@users.noreply.github.com> Co-authored-by: xxy9468615 <63351664+xxy9468615@users.noreply.github.com> Co-authored-by: ipeterpetrus <93033698+ipeterpetrus@users.noreply.github.com> Co-authored-by: DNNYF <74033321+DNNYF@users.noreply.github.com> Co-authored-by: mugnimaestra <13349159+mugnimaestra@users.noreply.github.com> Co-authored-by: ntdung6868 <103993527+ntdung6868@users.noreply.github.com> Co-authored-by: nguyenvanhuy0612 <57367674+nguyenvanhuy0612@users.noreply.github.com> Co-authored-by: codename-zen <263238141+codename-zen@users.noreply.github.com> Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com> Co-authored-by: Ibrahim Ryan <ryan@nuevanext.com> Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com> Co-authored-by: aeonframework <aeon@aeonframework.dev> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
🚀 OmniRoute — The Free AI Gateway
Never stop coding. Connect every AI tool to 231 providers — 50+ free — through one endpoint.
Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini. Auto-fallback.
RTK + Caveman compression saves 15–95% tokens. Never hit limits.
~1.6B documented free tokens/month — up to ~2.1B in your first month with signup credits — aggregated across the free tiers, plus a long tail of permanently-free, no-cap providers, and the compression above stretches every one further. (how we count →)
💬 Join the community
Questions, provider tips, roadmap & support → Discord · Telegram · WhatsApp 🌍 Global / 🇧🇷 Brasil
🚀 Quick Start • 🎯 Combos • 🌐 Providers • 🔌 CLI & MCP • 🗜️ Compression • 🌍 Website
💥 The Promise • 🤔 Why • 🏆 What Sets Apart • 🤖 Compatible CLIs • 🖥️ Where It Runs • 🔒 Private • 🎬 In Action • 📚 Explore More • 📧 Support
| 🇺🇸 | 🇧🇷 | 🇪🇸 | 🇫🇷 | 🇮🇹 | 🇷🇺 | 🇨🇳 | 🇩🇪 | 🇯🇵 | 🇰🇷 | 🇮🇳 |
| 🇹🇭 | 🇻🇳 | 🇮🇩 | 🇲🇾 | 🇵🇭 | 🇸🇦 | 🇮🇱 | 🇦🇿 | 🇺🇦 | 🇵🇱 | 🇨🇿 |
| 🇳🇱 | 🇧🇬 | 🇩🇰 | 🇫🇮 | 🇳🇴 | 🇸🇪 | 🇭🇺 | 🇷🇴 | 🇸🇰 | 🇵🇹 |
💰 ~1.6B Free Tokens / Month
Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute aggregates the documented free tiers of 40+ provider pools / 500+ models into one honest number and shows it live on the dashboard (
/dashboard/free-tiers).
- ~1.6B free tokens / month (steady) — and up to ~2.1B in your first month with signup credits.
- Pool-deduped, honest — we count each shared free pool once, so the headline isn't inflated by rate-limit ceilings the way multi-billion competitor claims are. (Counting every rate limit 24/7 would read ~10B; we don't publish that.)
- Plus the un-countable — permanently-free, no-token-cap providers (SiliconFlow, Z.AI GLM-Flash, Kilo, OpenCode Zen…) and a $10 OpenRouter top-up that unlocks +24M/mo, both surfaced separately so they never inflate the headline.
- Per-model breakdown, live used / remaining for the current month, and a transparent terms flag per provider.
Preview mockup — a real screenshot lands once the
/dashboard/free-tierspage is validated. Full methodology (pool dedupe, credit tiers, provider terms): docs/reference/FREE_TIERS.md.
💥 The Promise
One endpoint. 231 providers. Never stop building — and let OmniRoute pick the cheapest one that works.
| 🚫 Never hit limits Auto-fallback across 231 providers in milliseconds. Quota out? Next provider takes over — zero downtime. |
💸 Save up to 95% tokens RTK + Caveman stacked compression cuts 15–95% of eligible tokens (~89% avg on tool-heavy sessions). |
🆓 $0 to start 50+ providers with a free tier, 11 free forever (Kiro, Qoder, Pollinations, LongCat…). No card needed. |
| 🔌 Every tool works 16+ coding agents — Claude Code, Codex, Cursor, Cline, Copilot, Antigravity — through one config. |
🧩 One endpoint OpenAI ↔ Claude ↔ Gemini ↔ Responses API translation. Point any tool at /v1 and it just works. |
🛡️ Production-grade Circuit breakers, TLS stealth, MCP (87 tools), A2A, memory, guardrails, evals. 14,965 tests. |
🤔 Why OmniRoute?
Stop juggling 10 dashboards, dead API keys, and surprise bills.
| ❌ The daily pain | ✅ How OmniRoute fixes it |
|---|---|
| 📉 Subscription quota expires unused every month | Maximize subscriptions — track quota, use every token before reset |
| 🛑 Rate limits stop you mid-coding | 4-tier auto-fallback — Subscription → API → Cheap → Free, in milliseconds |
🔥 Tool outputs (git diff, grep, logs) burn tokens |
RTK + Caveman compression — save 15–95% eligible tokens per request |
| 💸 Expensive APIs ($20–50/mo per provider) | Cost-optimized routing — auto-route to the cheapest viable model |
| 🧰 Each AI tool wants its own setup | One endpoint, every tool, one dashboard |
| 🌍 AI blocked in your country | 3-level proxy + TLS fingerprint stealth — use AI from anywhere |
┌──────────────────────────────────────────────────────────┐
│ Your IDE / CLI (Claude Code, Cursor, Cline…) │
└─────────────────────────┬──────────────────────────────────┘
│ http://localhost:20128/v1
▼
┌──────────────────────────────────────────────────────────┐
│ OmniRoute — Smart Router │
│ RTK + Caveman compression · 15 routing strategies │
│ Circuit breakers · TLS stealth · MCP · A2A · Guardrails │
└─────────────────────────┬──────────────────────────────────┘
┌─────────────┬────┴────────┬─────────────┐
▼ Tier 1 ▼ Tier 2 ▼ Tier 3 ▼ Tier 4
SUBSCRIPTION API KEY CHEAP FREE
Claude Code, DeepSeek, GLM $0.5, Kiro, Qoder,
Codex, Copilot Groq, xAI MiniMax $0.2 Pollinations
quota out? ───▶ budget hit? ─▶ budget hit? ─▶ always on
🎯 Combos — The Flagship
A combo is a chain of models OmniRoute routes across automatically. Quota runs out, a provider fails, or costs spike — the combo silently slides to the next model. This is what makes OmniRoute unbreakable. 🛡️
⚡ Zero-config — just use auto
No combo to create. Set your model to auto (or a variant) and OmniRoute builds a virtual combo from your connected providers, scored live:
| Model ID | What it optimizes for |
|---|---|
auto |
🎯 Balanced default (LKGP — sticks to your last good provider) |
auto/coding |
🧑💻 Quality-first weights for code generation |
auto/fast |
⚡ Lowest latency first |
auto/cheap |
💰 Cheapest per token first |
auto/offline |
🔋 Most quota / rate-limit headroom first |
auto/smart |
🔭 Quality-first + 10% exploration to discover better models |
🔀 Or build your own — 15 routing strategies
| Goal | Strategy / combo |
|---|---|
| 🥇 Drain my subscription before paying | priority / fill-first |
| ⚖️ Spread load across accounts | round-robin · weighted · p2c · least-used |
| 💸 Always cheapest viable model | cost-optimized · auto/cheap |
| 🧠 Hand off long context between models | context-relay · context-optimized |
| 🎲 Randomized / privacy routing | random · strict-random |
| 🤖 Just make it smart | auto (9-factor scoring) · lkgp · reset-aware |
The Auto-Combo engine scores every candidate on 9 factors (health, quota, cost, latency, success rate, freshness…) — see docs/routing/AUTO-COMBO.md.
🧱 Resilience is built in (3 independent layers)
| Layer | Scope | What it does |
|---|---|---|
| 🔌 Circuit breaker | whole provider | Stops hammering a provider that's failing upstream; auto-probes to recover |
| 💤 Connection cooldown | one account / key | Skips a rate-limited key while other keys keep serving |
| 🎯 Model lockout | provider + model | Quarantines just one quota-limited model, not the whole connection |
Combo: "always-on" Strategy: priority
1. cc/claude-opus-4-7 ← subscription (use it fully)
2. cx/gpt-5.5 ← second subscription
3. glm/glm-5.1 ← cheap backup ($0.5/1M)
4. kr/claude-sonnet-4.5 ← FREE, unlimited (never fails)
Result: 4 layers of fallback = zero downtime
📖 Auto-Combo Engine · Resilience Guide
🏆 What Sets OmniRoute Apart
| Feature | OmniRoute | Other routers |
|---|---|---|
| 🌐 Providers | 231 | 20–100 |
| 🆓 Free providers | 50+ (11 free forever) | 1–5 |
| 🔀 Routing strategies | 15 (priority, weighted, cost-optimized, context-relay…) | 1–3 |
| 🗜️ Token compression | RTK + Caveman stacked (15–95%) | None / 20–40% |
| 🧰 Built-in MCP server | 87 tools, 3 transports, 30 scopes | Rare |
| 🤝 A2A agent protocol | 6 skills, JSON-RPC 2.0 | None |
| 🧠 Memory (FTS5 + vector) | Yes | Rare |
| 🛡️ Guardrails (PII, injection, vision) | Yes | Rare |
| ☁️ Cloud agents | Codex, Devin, Jules | None |
| 🥷 TLS fingerprint stealth | JA3/JA4 via wreq-js | None |
| 🖥️ Multi-platform | Web · Desktop · Termux · PWA | Web only |
| 🌍 i18n | 42 locales | 0–4 |
📊 Detailed comparison vs LiteLLM, OpenRouter & Portkey → docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md
✨ What's New
Recent highlights from v3.8.20 → v3.8.31. Full history in
CHANGELOG.md.
- 🤖 One-command CLI/agent setup — a dedicated
setup-*command configures each coding tool to route through OmniRoute (Claude Code, Codex, Cline, Continue, Cursor, Roo Code, Kilo Code, Crush, Goose, Qwen Code, Aider, OpenCode, Gemini CLI);omniroute launch/omniroute launch-codexare zero-config launchers. → CLI Integrations - 🛰️ Remote mode — drive a remote OmniRoute from any machine with scoped access tokens (
omniroute connect/omniroute contexts/omniroute tokens). → Remote Mode - 🧭 Smarter auto-routing — OpenRouter-style
auto/<category>:<tier>combos (e.g.auto/coding:fast,auto/reasoning:pro), live Arena-ELO + models.dev model intelligence, and per-step account allowlists. → Auto-Combo - 🗜️ Pluggable compression — an async compression pipeline with Compression Studios, a stable LLMLingua-2 ONNX engine, RTK, and delegated Anthropic Context Editing. → Compression
- 🕵️ Transparent MITM decrypt (TPROXY) — capture & translate traffic from CLIs that ignore proxy env vars, with a per-SNI certificate authority and a trust-store installer. → MITM/TPROXY
- 💸 Cost telemetry everywhere —
X-OmniRoute-*cost/usage headers on every endpoint (including media), a non-token cost engine, a cache-HITX-OmniRoute-Cost-Savedheader, and per-key USD spend quotas. → API Reference - 🧠 Memory you control — opt-in int8 vector quantization (Qdrant + sqlite-vec), memory off by default, and a per-request
x-omniroute-no-memoryheader. → Memory - 🛡️ Security — a prompt-injection guard across every LLM route (backed by a red-team suite), plus a free DuckDuckGo last-resort web search. → Guardrails
- 🤝 More providers & agents — Cursor Cloud Agent (a 4th cloud agent), a refreshed 231-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), and Vertex AI media generation (speech / transcription / music / video). → Providers
- ⚡ Local performance & infra — a one-click local Redis launcher (
omniroute redis up, plus a dashboard Redis panel) and an optional Bifrost Go sidecar that offloads the hottest relay path (BIFROST_BASE_URL, with automatic fallback to the TypeScript path on timeout). → Environment
🤖 Compatible CLIs & Coding Agents
One config —
http://localhost:20128/v1— and every AI IDE or CLI runs on free & low-cost models.
Claude Code |
Codex CLI |
Gemini CLI |
![]() Cursor |
![]() Copilot |
![]() Continue |
OpenCode |
Kilo Code |
Droid |
![]() OpenClaw |
Kiro |
Command |
📖 Per-tool setup for all 16+ tools → docs/reference/CLI-TOOLS.md · 🧩 OpenCode plugin → @omniroute/opencode-provider
🌐 231 AI Providers — 50+ Free
The most complete catalog of any open-source router: 231 providers, 50+ with a free tier, 11 free forever.
🆓 Free Forever — $0, no card
📖 Full machine-readable catalog → docs/reference/PROVIDER_REFERENCE.md
🖥️ Where OmniRoute Runs — Anywhere
Same app, your machine, your rules. From a global npm install to your phone via Termux.
| Platform | Install | Highlights |
|---|---|---|
| 📦 npm (global) | npm install -g omniroute |
One command, any OS |
| 🐳 Docker | docker run … diegosouzapw/omniroute |
Multi-arch AMD64 + ARM64 |
| 🖥️ Desktop (Electron) | npm run electron:build |
Native window + system tray — Windows / macOS / Linux |
| 💪 ARM | native arm64 |
Raspberry Pi, ARM servers, Apple Silicon |
| 📱 Android (Termux) | pkg install nodejs-lts && npx -y omniroute |
Runs on your phone, 24/7, no root |
| 📲 PWA | "Add to Home Screen" | Fullscreen, offline, installable from browser |
| 🧩 OpenCode plugin | @omniroute/opencode-provider |
Native OpenCode integration |
| 🛠️ From source | npm install && npm run dev |
Hack on it, contribute |
📖 Docker Guide · Desktop · Termux · PWA · OpenCode
🔒 Private & Local-First
Your keys, your machine, your data. OmniRoute is a local proxy — it never phones home.
- 🏠 Runs 100% on your hardware — npm, Docker, desktop, or your phone. No OmniRoute cloud sits in the request path.
- 🔐 Credentials encrypted at rest — API keys & OAuth tokens sealed with AES-256-GCM.
- 🚫 Zero telemetry by default — your prompts go only to the providers you choose, nowhere else.
- 🛡️ Hardened gateway — API-key scoping, IP filtering, rate limits, prompt-injection guard, loopback-only process routes.
- 📜 MIT licensed & fully open-source — audit every line, self-host forever.
📖 Authorization · Guardrails · Compliance
🔌 Full CLI + A2A & MCP
OmniRoute isn't just a server — it's a full command-line cockpit with 60+ commands, plus open agent protocols so an AI agent can drive OmniRoute by itself.
⌨️ A real CLI (not just start)
omniroute # serve gateway + dashboard (port 20128)
omniroute chat # interactive TUI chat client (slash: /model /combo /skill /memory)
omniroute setup # guided first-run wizard
omniroute doctor # diagnose providers, ports, native deps
🛰️ Remote mode — run the CLI here, OmniRoute on a VPS
OmniRoute on a server? Drive it from your laptop with the same CLI. Log in once with a scoped access token; every command then targets the remote.
omniroute connect 192.168.0.15 # password → scoped token, saved as a context
omniroute models list # ← runs against the REMOTE server
omniroute configure codex # ← picks a remote model, writes a local Codex profile
omniroute tokens create --name ci --scope read # mint narrower tokens for other machines
omniroute contexts use default # ← switch back to the local server
Tokens are scoped read / write / admin; process-spawning routes stay loopback-only.
📖 Remote Mode
providers · oauth · keys · combo · nodes · models · cache · compression · cost · usage · quota · health · resilience · telemetry · logs · audit · mcp · a2a · cloud · memory · skills · eval · tunnel · backup · sync · webhooks · policy · pricing · translator · simulate …
🤝 Connect an agent — and it controls OmniRoute itself
Expose OmniRoute over MCP or A2A and any capable agent gets the keys to the whole gateway — routing, providers, combos, cache, compression, memory — autonomously.
| Protocol | Endpoint | Use it for |
|---|---|---|
| 🧰 MCP (stdio) | omniroute --mcp |
Plug into Claude Desktop, Cursor, any MCP client |
| 🌊 MCP (HTTP) | http://localhost:20128/api/mcp/stream |
Remote MCP — 87 tools, 30 scopes, full audit trail |
| 📡 MCP (SSE) | http://localhost:20128/api/mcp/sse |
Streaming MCP transport |
| 🤝 A2A | http://localhost:20128/.well-known/agent.json |
Agent-to-agent, JSON-RPC 2.0 + SSE, 6 skills |
# Give Claude Code the full OmniRoute toolset over MCP:
claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp/stream
📖 MCP Server · A2A Server · Agent Protocols
🗜️ Save 15–95% Tokens — Automatically
Why use many token when few token do trick? Every request passes through OmniRoute's compression pipeline transparently — no client changes. It's now a stack of 9 composable engines that run in order and mix & match per routing combo — building on ideas from RTK, Caveman (⭐ 51K+), LLMLingua-2, and Troglodita (PT-BR).
🧱 The 9-engine stack
Engines run in pipeline order; each is independently toggleable and configurable per combo:
| # | Engine | What it does |
|---|---|---|
| 1 | Session-Dedup | Drops content repeated across turns (content-addressed, cross-turn) |
| 2 | CCR | Archives large blocks behind retrieve markers, fetched on demand |
| 3 | RTK | Smart tool-result filtering, dedup & truncation (command-aware) |
| 4 | Headroom | Lossless tabular compaction of homogeneous JSON arrays (~30%+) |
| 5 | Caveman | Rule-based prose compression (~65–75% on output) |
| 6 | LLMLingua-2 | ML semantic pruning via MobileBERT ONNX — code-safe, async |
| 7 | Lite | Whitespace + image-URL trimming (latency-light baseline) |
| 8 | Aggressive | Summarization + progressive aging of old turns |
| 9 | Ultra | Heuristic token pruning with an optional small-model (SLM) tier |
Code blocks, URLs and structured data are always preserved byte-perfect. One-click presets combine the engines:
| Mode | Savings | Best for |
|---|---|---|
| 🪶 Lite | ~15% | Always-on safe default |
| 🪨 Standard (Caveman) | ~30% | Daily coding |
| ⚡ Aggressive | ~50% | Long tool-heavy sessions |
| 🔥 Ultra | ~75% | Maximum savings |
| 🧰 RTK | 60–90% | Shell/test/build/git output |
| 🔗 Stacked (RTK → Caveman) | 78–95% | Mixed prompts + tool logs |
Real example — Standard mode:
Before (69 tokens): "The reason your React component is re-rendering is likely because you're creating a new object reference on each render cycle. When you pass an inline object as a prop, React's shallow comparison sees it as a different object every time, which triggers a re-render. I would recommend using useMemo to memoize the object."
After (19 tokens): "New object ref each render. Inline object prop = new ref = re-render. Wrap in useMemo."
Same answer. 72% fewer tokens. Zero accuracy loss. ✅
PT-BR example — Troglodita mode:
Antes (42 tokens): "O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."
Depois (12 tokens): "Re-render: ref nova cada ciclo (objeto inline recriado). Usar
useMemo."Mesma resposta. ~70% menos tokens. Precisão técnica intacta. ✅
📖 How it works — pipeline, architecture & savings math
Client (10,000 tok) ──▶ OmniRoute Compression (9 engines) ──▶ Provider (~1,080 tok, up to 95% saved)
Default stacked combo runs RTK → Caveman. When both act on the same tool/context payload, savings compound:
combined = 1 − (1 − RTK) × (1 − Caveman_input)
average = 1 − (1 − 0.80) × (1 − 0.46) = 89.2%
range = 78.4 – 94.6%
Code blocks, URLs, JSON and structured data are always protected by the preservation engine. Auto-trigger compression by token threshold, or assign a compression pipeline per routing combo.
📖 COMPRESSION_GUIDE.md · RTK_COMPRESSION.md · COMPRESSION_ENGINES.md
⚡ Quick Start
1) Install & run
npm install -g omniroute
omniroute
Dashboard at http://localhost:20128 · API at http://localhost:20128/v1.
2) Connect a FREE provider (no signup)
Dashboard → Providers → connect Kiro AI (free Claude unlimited) or OpenCode Free (no auth) → done.
3) Point your coding tool
Base URL: http://localhost:20128/v1
API Key: [copy from Dashboard → Endpoints]
Model: auto (zero-config smart routing — or any provider/model)
4) Verify it's working
curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY"
You should see your connected models listed. 🎉 That's it — start coding, and OmniRoute auto-routes & falls back for you.
If your client cannot send custom headers, OmniRoute also exposes tokenized compatibility aliases:
OpenAI catalog: http://localhost:20128/vscode/YOUR_KEY/
OpenAI models: http://localhost:20128/vscode/YOUR_KEY/models
OpenAI chat: http://localhost:20128/vscode/YOUR_KEY/chat/completions
OpenAI responses: http://localhost:20128/vscode/YOUR_KEY/responses
Ollama chat: http://localhost:20128/vscode/YOUR_KEY/api/chat
Ollama tags: http://localhost:20128/vscode/YOUR_KEY/api/tags
Use these only for clients that cannot attach Authorization: Bearer .... Header auth remains the preferred mode.
📦 More install methods — Docker, source, pnpm, Arch
🐳 Docker
docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \
-p 20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest
🛠️ From source
cp .env.example .env && npm install
PORT=20128 npm run dev
📦 pnpm
pnpm install -g omniroute && pnpm approve-builds -g && omniroute
🐧 Arch Linux (AUR)
yay -S omniroute-bin && systemctl --user enable --now omniroute.service
🔧 Nix (Flake)
# Using Nix flakes
nix develop
npm run dev
# Or using devbox
devbox run npm run dev
📖 Docker Guide — Compose profiles, Caddy HTTPS, Cloudflare tunnels.
🦭 Podman
# 1. Build the image
podman build --target runner-base -t omniroute:base .
# 2. Fix data directory permissions for rootless Podman
mkdir -p data && podman unshare chown 1000:1000 ./data
# 3. Set runtime in .env, then run (see contrib/podman/ for Quadlet)
echo "CONTAINER_HOST=podman" >> .env
podman compose --profile base up -d
📖 Podman Guide — Quadlet setup, podman-compose, Quadlet.
🎬 OmniRoute in Action
🎬 Made a video about OmniRoute? Open an issue or discussion with the link — we'll feature it here.
📚 Explore More
💰 Pricing at a glance & the $0 Free Stack (11 providers)
| Tier | Example | Cost |
|---|---|---|
| 💳 Subscription | Claude Code Pro / Codex / Copilot | $10–200/mo |
| 🔑 API Key (free tiers) | NVIDIA NIM, Cerebras, Groq | FREE |
| 💰 Cheap | GLM-5 $0.5/1M · MiniMax M2.5 $0.3/1M | pennies |
| 🆓 Free Forever | Kiro, Qoder, Qwen, Pollinations, LongCat | $0 |
The $0 Free Stack — combine into one unbreakable combo:
| Provider | Prefix | Free models | Quota |
|---|---|---|---|
| Kiro | kr/ |
Claude Sonnet 4.5, Haiku 4.5, Opus 4.6 | 50 credits/mo |
| Qoder | if/ |
kimi-k2-thinking, qwen3-coder-plus, deepseek-r1 | ♾️ Unlimited |
| Qwen | qw/ |
qwen3-coder-plus/flash/next | ♾️ Unlimited |
| Pollinations | pol/ |
GPT-5, Claude, Gemini, DeepSeek, Llama 4 | No key needed |
| LongCat | lc/ |
LongCat-Flash-Lite | 50M tokens/day 🔥 |
| Cloudflare AI | cf/ |
50+ models | 10K neurons/day |
| NVIDIA NIM | nvidia/ |
129 models | ~40 RPM |
| Cerebras | cerebras/ |
Qwen3 235B, GPT-OSS 120B | 1M tok/day |
💡 The dashboard "cost" is a savings tracker, not a bill — OmniRoute never charges you. A "$290 total cost" using free models means $290 saved.
📖 Complete free directory → docs/reference/FREE_TIERS.md — 25+ providers, quotas, base URLs.
🎯 Use Cases — ready-made combo playbooks
$0 forever:
1. kr/claude-sonnet-4.5 (Kiro — unlimited)
2. if/kimi-k2-thinking (Qoder — unlimited)
3. pol/gpt-5 (Pollinations — no key)
4. lc/longcat-flash-lite (50M tok/day backup)
Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
24/7 no interruptions: chain 2 subscriptions → cheap → free for 5 layers of fallback.
Blocked region: free providers + global/per-provider proxy → access AI from any country.
Max savings: subscription + cheap backup + ultra compression (~75%) → ~$150–300/mo saved for heavy users.
🌍 Bypass geo-blocks — 3-level proxy + stealth
🇷🇺 🇨🇳 🇮🇷 🇨🇺 🇹🇷 In a blocked region? OmniRoute's 3-level proxy (Global / Per-Provider / Per-Connection) proxies API requests, OAuth flows, connection tests, token refresh & model sync.
- Protocols: HTTP/HTTPS, SOCKS5, authenticated proxies
- 🆓 1proxy marketplace — hundreds of free validated proxies, quality scores, auto-rotation
- Anti-detection — TLS fingerprint spoofing (
wreq-js), CLI fingerprint matching, proxy IP preservation
✨ Full feature list — 30+ capabilities (memory, evals, observability)
Routing: 15 strategies · task-aware smart routing · thinking budget controls · wildcard routing · system prompt injection.
Compatibility: OpenAI ↔ Claude ↔ Gemini ↔ Responses API · auto OAuth refresh (PKCE, 8 providers) · multi-account round-robin · Batch + Files API · live OpenAPI 3.0.
Protocols: MCP (87 tools, 3 transports, 30 scopes) · A2A (JSON-RPC 2.0, SSE, 6 skills) · ACP · cloud agents (Codex, Devin, Jules).
Plugins: custom plugin marketplace (system-configured registry URL with SSRF-guarded fetch) · install / enable / disable · Notion + Obsidian knowledge-base integrations (WebDAV file server, vault search, note CRUD).
Embedded services: one-click install & lifecycle management of local sidecar services (CLIProxy, NineRouter).
Quality & Ops: built-in Evals (golden-set: exact/contains/regex/custom) · guardrails (PII, injection, vision) · health dashboard · p50/p95/p99 telemetry · webhooks · compliance audit.
AI Agent Skills: drop-in markdown manifests — point any agent at a skills/*/SKILL.md manifest. 43 skills available.
📖 MCP Server · A2A Server · Resilience Guide · Features Gallery
📖 Setup, env vars & FAQ
| Env var | Default | Purpose |
|---|---|---|
PORT |
20128 |
API + dashboard port |
REQUIRE_API_KEY |
false |
Require API key for all requests |
DATA_DIR |
~/.omniroute |
Database & config storage |
Will I be charged by OmniRoute? No — it's free, open-source software on your machine. You only pay paid providers directly. OmniRoute has no billing system. Are FREE providers really unlimited? Yes — Kiro, Qoder, Pollinations, LongCat, Cloudflare. No catch. Will compression hurt quality? No — it only compresses the input; code, URLs, JSON are always protected. Does it work where AI is blocked? Yes — 3-level proxy + 1proxy marketplace reach all 231 providers.
🐛 Troubleshooting
| Problem | Quick fix |
|---|---|
| "Language model did not provide messages" | Provider quota exhausted → use a combo fallback |
| Rate limiting (429) | Add fallback: cc/claude → glm/glm-4.7 → if/kimi-k2-thinking |
| OAuth token expired | Auto-refreshed; if stuck, delete + re-auth in Providers |
unsupported_country_region_territory |
Configure proxy in Settings → Proxy |
| Docker SQLite locks | Use --stop-timeout 40 for clean WAL checkpoint |
| Node runtime errors | Use Node >=22.0.0 <23 or >=24.0.0 <27 |
🐛 Reporting a bug? Run npm run system-info and attach system-info.txt. 📖 docs/guides/TROUBLESHOOTING.md
📸 Dashboard screenshots
| Page | Screenshot | Page | Screenshot |
|---|---|---|---|
| Providers | ![]() |
Combos | ![]() |
| Analytics | ![]() |
Health | ![]() |
| Translator | ![]() |
Settings | ![]() |
| CLI Tools | ![]() |
Usage Logs | ![]() |
📧 Support & Community
💬 Chat with the community — Discord, Telegram & WhatsApp (🌍 / 🇧🇷) links are at the top of this README.
- 🌍 Website: omniroute.online
- 🐙 GitHub: github.com/diegosouzapw/OmniRoute
- 🐛 Issues: report a bug (attach
npm run system-infooutput) - 🤝 Contributing: see CONTRIBUTING.md or pick a
good first issue
🛠️ Tech Stack
- Runtime: Node.js 22.x or 24.x LTS (24 LTS recommended) —
>=22.0.0 <23 || >=24.0.0 <27 - Language: TypeScript 6.0 — 100% TypeScript across
src/andopen-sse/(zeroanyin core modules since v2.0) - Framework: Next.js 16 + React 19 + Tailwind CSS 4
- Database: better-sqlite3 (SQLite) + LowDB (JSON legacy) — domain state, proxy logs, MCP audit, routing decisions, memory, skills
- Schemas: Zod (MCP tool I/O validation, API contracts)
- Protocols: MCP (stdio/HTTP) + A2A v0.3 (JSON-RPC 2.0 + SSE)
- Streaming: Server-Sent Events (SSE) + WebSocket bridge (
/v1/ws) - Auth: OAuth 2.0 (PKCE) + JWT + API Keys + MCP Scoped Authorization
- Testing: Node.js test runner + Vitest (14,965 test cases across 517 files — unit, integration, E2E, security, ecosystem)
- Platforms: Desktop (Electron), Android (Termux), PWA (any browser)
- CI/CD: GitHub Actions (auto npm publish + Docker Hub on release)
- Website: omniroute.online
- Package: npmjs.com/package/omniroute
- Docker: hub.docker.com/r/diegosouzapw/omniroute
- Resilience: Circuit breaker, exponential backoff, anti-thundering herd, TLS spoofing, auto-combo self-healing
📖 Documentation
📘 Getting Started
| Document | Description |
|---|---|
| User Guide | Providers, combos, CLI integration, deployment |
| Setup Guide | Full install methods, CLI tool configs, protocol setup, timeout tuning |
| CLI Tools Guide | Per-tool setup for Claude Code, Codex, Cursor, Cline, OpenClaw, Kilo, Copilot |
| Remote Mode | Drive a remote OmniRoute (VPS) from your laptop CLI via scoped access tokens |
| Claude Code Config | Point Claude Code at OmniRoute (local/remote) with launch + per-model profiles |
| Quick Start | 3-step install → connect → configure |
🔧 Operations & Deployment
| Document | Description |
|---|---|
| Docker Guide | Docker run, Compose profiles, Caddy HTTPS, tunnels, image tags |
| Podman Guide | Quadlet systemd integration, podman-compose, SELinux |
| VM Deployment | Complete guide: VM + nginx + Cloudflare setup |
| Fly.io Deployment | Deploy to Fly.io with persistent storage |
| Termux Guide | Run OmniRoute on Android via Termux |
| PWA Guide | Progressive Web App install, caching, architecture |
| Uninstall Guide | Clean removal for all install methods |
| Environment Config | Complete .env variables and references |
🧠 Features & Architecture
| Document | Description |
|---|---|
| Architecture | System architecture, data flow, and internals |
| Compression Guide | 7-option pipeline: off / lite / standard / aggressive / ultra / RTK / stacked |
| RTK Compression | Command-output compression, filters, trust, verify, raw-output recovery |
| Compression Engines | Caveman, RTK, stacked pipelines, dashboard/API/MCP surfaces |
| Compression Rules Format | JSON rule-pack schemas for Caveman and RTK filters |
| Compression Language Packs | Language detection and Caveman rule-pack authoring |
| Resilience Guide | Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing |
| Auto-Combo Engine | 9-factor scoring, mode packs, self-healing |
| Proxy Guide | 3-level proxy system, 1proxy marketplace, registry CRUD |
| Free Tiers | 25+ free API providers consolidated directory |
| Features Gallery | Visual dashboard tour with screenshots |
| Codebase Documentation | Beginner-friendly codebase walkthrough |
🤖 Protocols & APIs
| Document | Description |
|---|---|
| API Reference | All endpoints with examples |
| OpenAPI Spec | OpenAPI 3.0 specification |
| MCP Server | 87 MCP tools, IDE configs, Python/TS/Go clients |
| MCP Server Guide | MCP installation, transports, and tool reference |
| A2A Server | JSON-RPC 2.0 protocol, skills, streaming, task mgmt |
| A2A Server Guide | A2A agent card, tasks, skills, and streaming |
📋 Project & Quality
| Document | Description |
|---|---|
| Contributing | Development setup and guidelines |
| Changelog | Full per-version release history |
| Security Policy | Vulnerability reporting and security practices |
| i18n Guide | 40+ language support, translation workflow, RTL |
| Release Checklist | Pre-release validation steps |
| Coverage Plan | Test coverage strategy and 14,965 test suite |
⭐ Top Contributors
OmniRoute is shaped by a passionate open-source community. These individuals have made exceptional contributions that directly impact the quality, stability, and reach of the project. Thank you.
![]() oyi77 🥇 190 commits • +72K lines Analytics engine, SQL aggregations, proxy marketplace, test coverage |
![]() Chris Staley 🥈 72 commits • +5.7K lines SSE stream hardening, Responses API, Gemini pagination, test regression fixes |
![]() zenobit 🥉 62 commits • +24K lines CI/CD pipeline, i18n for 33 languages, Void Linux package, platform fixes |
![]() R.D. & Randi 🏅 107 commits • +28K lines Endpoints page, tunnel integrations, Docker workflows, A2A status, compression UI |
![]() benzntech 🏅 20 commits • +7.5K lines Electron desktop app, auto-updater, release build workflows, cross-platform CI |
🙏 These contributors' features, bug fixes, and infrastructure improvements are a core part of what makes OmniRoute reliable and feature-rich. Every pull request, every test case, and every i18n translation file matters. Open source is built by people like them.
👥 Contributors
How to Contribute
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
Releasing a New Version
# Create a release — npm publish happens automatically
gh release create v3.8.2 --title "v3.8.2" --generate-notes
🙏 Acknowledgments
OmniRoute stands on the shoulders of giants. It started as a fork of 9router and a TypeScript port of the Go project CLIProxyAPI — and from there, every subsystem below was inspired by an open-source project that got there first. Each one shaped a concrete piece of OmniRoute. This is our thank-you to all of them. 🙏
⭐ star counts as of June 2026 — go give these projects a star.
🧬 Lineage & gateway
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| 9router · decolua | 17.9k | The original project this fork is built on — extended here with multi-modal APIs and a full TypeScript rewrite. |
| CLIProxyAPI · router-for-me | 37.8k | The Go implementation that inspired this JavaScript / TypeScript port. |
| LiteLLM · BerriAI | 50.8k | The AI gateway whose public pricing dataset feeds our cost-tracking sync and whose provider-normalization model informed our routing. |
🗜️ Context & token compression — engines
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| Caveman · JuliusBrussee | 74.5k | The viral "why use many token when few token do trick" project — its caveman-speak philosophy powers our standard compression mode and 30+ filler/condensation rules. |
| RTK – Rust Token Killer · rtk-ai | 63.6k | High-performance command-output compression — inspired our RTK engine, JSON filter DSL, raw-output recovery and the stacked RTK → Caveman pipeline. |
| headroom · chopratejas | 33.6k | Reversible context-compression (SmartCrusher) — inspired our headroom engine and the ccr retrieve-marker pattern. |
| LLMLingua · Microsoft | 6.3k | Prompt-compression research (LLMLingua / LLMLingua-2) — inspired our async, code-safe, fail-open llmlingua engine. |
| llmlingua-2-js · atjsh | 27 | The JS/ONNX port (MobileBERT / XLM-RoBERTa) used as the worker-thread backend for our LLMLingua engine. |
| Troglodita · Lenine Júnior | 15 | PT-BR token compression — powers our pt-BR language pack: pleonasm reduction and filler removal tuned for Brazilian-Portuguese grammar. |
🧩 Compact formats, token research & code-aware tooling
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| TOON · toon-format | 24.6k | Token-Oriented Object Notation — its columnar, header-plus-rows model shaped our tabular compaction stage. |
| GCF – Graph Compact Format · Blackwell Systems | 11 | Schema-aware "JSON for LLMs" notation — co-inspired our lossless homogeneous-array compaction with [N rows] markers. |
| token-optimizer-mcp · ooples | 409 | Brotli/SQLite cache + per-session context-delta — inspired our session-dedup engine. |
| token-savior · Mibayy | 993 | Bash-output compaction + MCP profiles — inspired our compression bail-out discipline and MCP tool-manifest reduction. |
| token-saver · ppgranger | 103 | Content-aware, per-file-type output compression with failure-aware bail-out — validated our per-type dispatch and minimum-gain skip. |
| token-optimizer · alexgreensh | 1.4k | "Find the ghost tokens" — its offload + recoverable-handle pattern informed our CCR offload thinking. |
| TokenMizer · Shweta-Mishra-ai | 1 | A session-graph + cross-turn line-dedup blueprint that informed our session-dedup design. |
| mcp-compressor · Atlassian Labs | 80 | MCP tool-schema/description compression — informed our MCP tool-manifest cardinality reduction. |
| RepoMapper · pdavis68 | 182 | Aider-style repo-map ranking — informed our repo-map / retrieval-ranking exploration. |
| quiet-shell-mcp · mrsimpson | 4 | Declarative shell-output reduction over MCP — validated our declarative bash-output compaction. |
| ts-morph · David Sherret | 6.1k | TypeScript Compiler API toolkit — inspired our parser-based comment removal that preserves string, template and regex literals. |
🧠 Memory & RAG
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| Mem0 · mem0ai | 58.9k | Universal memory layer — its proxy-as-write/read-boundary model shaped our memory architecture. |
| Letta (MemGPT) · letta-ai | 23.4k | Stateful agents with tiered memory — inspired our Context Control & Recovery (CCR) tiered model. |
| WFGY · onestardao | 1.8k | The ProblemMap taxonomy of 16 recurring RAG/LLM failure modes — the shared vocabulary in our troubleshooting guide. |
🛰️ Traffic inspection, MITM & transparent proxy
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| llm-interceptor · chouzz | 46 | MITM interception/analysis of coding-assistant ↔ LLM traffic — our Traffic Inspector ports its SSE merge, conversation normalization, host passthrough and secret masking (MIT). |
| ProxyBridge · InterceptSuite | 5.1k | Transparent per-process proxy routing — inspired our crash-safe MITM teardown, socket idle-timeouts, /proc process attribution and TPROXY capture. |
📚 Model data, observability & UI
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| models.dev · SST / OpenCode | 5.1k | Open database of AI model specs, pricing and capabilities — synced natively into our model catalog. |
| React Flow / xyflow · xyflow | 37.1k | The node-based graph library powering our real-time Compression Studio and Combo/Routing Studio. |
| LangGraph · LangChain | 35.1k | LangGraph Studio's live workflow-graph visualization inspired our Studios' real-time cascade view. |
| Langfuse · Langfuse | 29.3k | Its trace → span → generation observability model shaped our Compression Studio waterfall. |
| Kiali · Kiali | 3.6k | Istio service-mesh observability — inspired our circuit-breaker badges and error-edge visuals in the Routing/Combo Studio. |
| lobe-icons · LobeHub | 2.1k | AI/LLM brand logos that render the provider icons across our dashboard. |
🛡️ Security
| Project | ⭐ | How it inspired OmniRoute |
|---|---|---|
| awesome-secure-defaults · tldrsec | 708 | A curated list of secure-by-default libraries that guides our security choices (Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink). |
❤️ Support
OmniRoute is free and open source, built and maintained in the open. If it saves you time or money, consider supporting development:
- ⭐ Star the repo — it genuinely helps visibility
- 💖 GitHub Sponsors — fund ongoing maintenance and new providers
- 🐛 Report bugs and share feedback in Discussions
📄 License
MIT License - see LICENSE for details.
⬆ Back to top · Built with ❤️ for the open-source AI community.
OmniRoute v3.8.24 · Node ≥22.0.0 · MIT License · omniroute.online



















