Jade Guo c8a9bad000 fix(combo): fall back on Responses SSE failures (#7256)
* fix(combo): fall back on Responses SSE failures

* fix(combo): cancel rejected upstream streams

* chore(release): script the 0a.0b PR re-home with a verified read-back (#7312)

The parallel-cycle model hands the frozen release/vX to the captain and cuts
release/vX+1 for everyone else. Phase 0a.0b step 3 then re-homes every open PR
onto the new cycle — today as a hand-run loop of gh pr edit --base.

Three things make that loop unreliable at exactly the moment it matters:

  1. gh pr edit --base FAILS SILENTLY (v3.8.42). It exits 0 and leaves the base
     untouched, so every edit needs a gh pr view --json baseRefName read-back.
     A human mid-release skips that.
  2. gh pr list caps at 30 results by default. A loop written without --limit
     re-homes the first 30 of 148 and reports success.
  3. Volume: the v3.8.49 freeze had 148 open PRs — roughly 450 API calls across
     edit, verify and comment.

The script does the read-back on every PR, uses --limit 300, is idempotent (a
PR already on the next base is skipped, so a resumed release re-runs safely),
refuses to start when the next branch does not exist yet, and exits non-zero
listing any PR whose retarget did not take.

It also prints the reminder that it cannot solve the other half: PRs opened
AFTER it runs. Those need the repo default_branch pointed at the live cycle —
contributors open PRs against the default branch, and while that stays on main
they never target a release branch at all (6 such PRs on 2026-07-15).

classify() is pure and unit-tested: retarget open and draft PRs on the frozen
branch; never touch main (the release PR's own lane), an older shipped release,
or a PR already re-homed.

Refs #7307

* fix(build): packed tarball boot crash — server-ws timeout import escaped the package (#7065 class) (#7308)

* fix(build): server-ws timeout helper as shipped sibling — ../../src import crashed every packed boot (#7065 class)

* test(build): align pack-artifact-policy fixture with the new dist/main-server-timeouts.mjs required path

* fix(skills): register cli-skill-collector in the agent-skills catalog (Integration 2/2 base-red) (#7310)

* fix(skills): register cli-skill-collector in the agent-skills catalog (#6294 shipped the dir only)

* chore(skills): regenerate cli-skill-collector SKILL.md via the generator, preserving the #6294 authored workflow in the custom block

* fix(skills): derive coverage totals from the id lists + align remaining count assertions (45 catalog / 21 cli)

* fix(skills): SkillCoverage totals are number, not stale literals

* chore(ci): make the Electron Windows leg advisory with bash stderr capture (first-run failure diagnosis) (#7340)

* fix(ci): Coverage job timeout 10->20min (lcov reporter pushed it past the old cap) (#7342)

* test(ci): make #6634 selfref guard hermetic — read file from disk, no git ref (#7327)

check-test-masking-selfref-6634.test.ts did git I/O inside a unit test
(`git show origin/main:<file>`), the single most common red across today's
babysit sweep — GitHub-hosted runners use shallow/single-ref checkouts with
no origin/main, so the show fails with "fatal: invalid object name". The
prior hotfix (2e42b8efc, #7174) wrapped it in try/catch + on-demand fetch +
t.skip() on failure, but t.skip() itself trips the PR Test Policy
weakened-assert gate (confirmed today on #7300), and origin/main was the
wrong ref anyway — PRs target release/v3.8.49, not main.

Ported the hermetic version proven on PR #7300 (@growab): read the real
current source of check-test-masking.test.ts from disk instead of diffing
against a git ref, and use an empty-string base (baseTaut/baseExtTaut = 0)
instead of the pre-#6404 git snapshot — this maximizes headTaut - baseTaut,
the strictest input for the exclusion under test, so the guard is exercised
at least as hard as before. No git ref, no skip, no CI-shape dependency.

Verified both directions locally:
- SELF_TEST_FIXTURE_RE neutralized in check-test-masking.mjs -> test FAILS
  (10 new bare tautologies + 28 new extended tautologies reported)
- restored -> test PASSES, and the full check-test-masking.test.ts suite
  (55 tests) stays green, confirming the #6634 self-referential-fixture
  regression this guard exists for is still covered.

Co-authored-by: growab <nekron@icloud.com>

* chore(quality): tighten the coverage ratchet to the CI's real numbers (#7326)

The Quality Ratchet has been red on main, and not for a regression — the report
says 'OK (57 métricas, 11 melhoraram)'. It fails the --require-tighten step:

  ✗ coverage.branches: melhorou de 73 para 78.1 (delta 5.1000 > slack 5)
    — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado

The gate was asking for this in plain text. The baseline's own note names the
same trigger: 'Apertar via quality:ratchet -- --update a partir do 1o run de
coverage mergeada do CI que popule essas chaves.'

Values are the CI's, not a local run. The baseline warns that a local
test:coverage measures ~68% against the CI's ~76.5% — tightening to local
numbers would write the wrong floor. So this reproduces the CI's exact inputs:
eslint-results + coverage-report artifacts downloaded from the merged-coverage
run on main (29387411665), re-rooted from the runner's paths to the local cwd so
extractModuleCoverage can match CRITICAL_MODULE_PATHS, then quality:collect +
quality:ratchet --update. Collected output matches the CI's report line for
line (branches 78.1, statements/lines 80.8, functions 86.44, chatCore 72.98,
combo 85.42, accountFallback 96.78, auth 92.55).

Verified: no baseline key added or removed (56 before, 56 after) — only the 12
coverage values moved. The 57-vs-56 metric count between the CI's run and a
local one is --allow-missing skipping the metrics only CI collects (mutation
scores, CodeQL, bundle size).

Worth recording why the improvement appeared now: it is real, but it surfaced
because Coverage had been SKIPPED whenever unit shards went red — so the ratchet
was passing trivially over ABSENT data. Fixing the shards on #7300 made coverage
run and the ratchet finally had something to compare.

* fix(stream): reconcile encrypted Codex reasoning visibility without mutating upstream item (#7304)

* fix(stream): reconcile encrypted Codex reasoning visibility without mutating upstream item

Resolves the collision between two open PRs on ensureVisibleResponsesReasoningSummary:
#7095 (xz-dev) found that chat clients see nothing when Codex exposes reasoning
only as encrypted_content, and added a visible placeholder — but did so by
mutating item.summary in place. #7176 (JxnLexn) found that same mutation
corrupts the forwarded response item, discarding the encrypted_content shape
Codex needs for follow-up requests, and removed the mutation — but that also
silently dropped the placeholder, so chat clients went back to seeing nothing.

The mutation existed only so a later line could read the summary text back off
the same item. getVisibleResponsesReasoningSummaryText() computes that text
without touching the item, so:
  - synthetic response.reasoning_summary_text.delta / .part.done events still
    carry the placeholder for chat clients (#7095's goal), and
  - the forwarded response.output_item.done payload keeps its original
    encrypted_content intact with no fabricated summary field (#7176's goal).

Applied at both call sites #7095 identified: the native Responses passthrough
in stream.ts/passthroughTailProcessor.ts, and the Responses-to-Chat-Completions
translator in openai-responses.ts.

Closes #7095, closes #7176.

Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>

* test(stream): guard the encrypted-reasoning mutation via the completed backfill path

The output_item.done line is echoed verbatim on the wire, so a re-introduced
item.summary mutation does NOT surface in that event — verified by re-injecting
the mutation, which left the existing assertion green. The mutation does surface
in the response.completed snapshot, where the captured reasoning item is
re-serialized when upstream sends an empty output (store: false).

Adds that case, which fails as expected when the mutation is re-introduced,
making the #7176 half of the reconciliation an enforced regression guard rather
than an incidental property of the current code path.

Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>

---------

Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>

* chore(ci): stop dependabot proposing typescript majors — peer-blocked by typescript-eslint (#7306)

typescript-eslint pins a hard upper bound on its typescript peer
(8.64.0 → ">=4.8.4 <6.1.0"). A major TS bump violates it, so the failure is
not one check — it is the whole toolchain at once.

#7068 is the demonstration: dependabot grouped typescript ^6→^7 with six
harmless dev bumps (@types/node, eslint, fast-check, knip, prettier,
typescript-eslint) and turned Build, Lint, Quality Ratchet, Unit (6/8, 8/8),
Integration (1/2, 2/2) and dast-smoke red in a single PR. The six innocuous
updates were blocked by the one that could never pass.

Ignoring the major lets the rest of the group flow on its own. TS majors are a
toolchain migration and deserve their own PR and their own CI run — not a
weekly automated attempt that cannot succeed until typescript-eslint widens the
peer.

Refs #7068

* test(dashboard): dedicated regression guard for #6815 density guarantee (#7291)

* test(dashboard): dedicated regression guard for #6815 density guarantee

Coverage for the #6815 multi-column density guarantee was only ever
asserted incidentally, by two other guards (#7072, #3520) that pinned the
literal sm:grid-cols-2 token. That coupling evaporated the coverage when
PR #7027 migrated the component to a container-driven auto-fit template
and the literal token was removed from both files.

Adds a dedicated guard that simulates, from the shipped className, how
many columns the per-group card grid renders at a wide container width
-- supporting both the breakpoint-ladder and auto-fit mechanisms this
component has shipped with -- and asserts >1 column, without asserting
any specific Tailwind token.

* chore(changelog): fragment for #7291 density guard

* test(ci): mock route bridge surfaces error message, not raw stack (#7354)

The E2E mock HTTP server's 500 catch sent error.stack straight to the
response body, which CodeQL flags as js/stack-trace-exposure (medium).
It's test-only localhost code, but the repo-wide CodeQL ratchet counts
open alerts across all branches — so this one alert (baseline 0 → 1)
turned the Quality Ratchet red on EVERY open PR into both main and
release, masking whatever each PR actually changed.

Surface error.message instead: clears the alert, keeps a useful signal
for a failing mock route, and doesn't log to stderr (node:test native
runner corrupts its report stream on console output). The test only
asserts status 200, so the 500 body is not checked.

Introduced by the #7304 integration test added this cycle.

* ci(release-green): add a main-green arm to detect when main goes red (#7355)

The release-green workflow already reproduces the release-equivalent gate
on release/** and opens a tracking issue on HARD failures — but main had
no such watch. Under the parallel-cycle model main only receives merged
work at the release squash, so a gate/infra fix that landed only on the
release branch leaves main red the whole cycle, and repo-wide gates
(CodeQL alert count, ratchet baselines) turn EVERY PR into main red on a
check unrelated to its diff. v3.8.49 hit this 3× in one night.

Adds a dedicated main-green job (push to main + the same 3 crons +
dispatch) that checks out main literally (no resolver, no injection
surface), runs the same validate-release-green.mjs, and opens/updates a
'🔴 main branch not green' issue pointing at the companion-PR fix. Gates
the existing release-green job with an if: so a push to main doesn't
re-validate release and vice-versa; schedule/dispatch sweep both.

Detection backstop for the prevention rule in _shared/merge-gates.md §8.

* fix(sse): sanitize non-ok Antigravity streaming error body (port from 9router#2461) (#7106)

Root cause: the STREAMING branch of AntigravityExecutor.executeOnce() had no
!response.ok check at all — it unconditionally wrapped the upstream response
body in a pass-through TransformStream, unlike the sibling non-streaming
branch which already built a sanitized error via buildAntigravityUpstreamError.
When Google's 403 error body was binary/non-UTF8 (observed: gzip-magic-byte
payloads), those raw bytes were forwarded verbatim, corrupting the
client-visible error message ('[ERROR] [403]: <control-byte garbage>').

Fix: add the same !response.ok guard to the streaming branch, routing through
buildAntigravityUpstreamError()/buildErrorBody() (hard rule #12) instead of
piping unknown bytes through as if they were an SSE stream.

Reported-by: Duongkhanhtool (https://github.com/decolua/9router/issues/2461)

* fix(6954,6953): preserve system role + strip empty-signature thinking blocks (#6982)

* fix(6954,6953): preserve system role + strip empty-signature thinking blocks

#6954 — System turns misattributed as assistant (claude-to-openai.ts:352)
  The ternary `msg.role === 'user' || msg.role === 'tool' ? 'user' : 'assistant'`
  mapped any non-user/non-tool role (including 'system') to 'assistant'.
  Mid-conversation system turns (Claude format) lost their role on
  translation to OpenAI format, causing them to be treated as assistant
  output. Fix: add explicit 'system' branch to the ternary.

#6953 — Empty-signature thinking blocks poison Anthropic leg (openai-to-claude.ts)
  Non-Anthropic providers (codex/gpt-5.x) synthesize thinking blocks with
  signature:''\. On replay, the old code fabricated a DEFAULT_THINKING_CLAUDE_SIGNATURE
  to fill the empty signature — but Anthropic rejects foreign signatures with
  HTTP 400, permanently degrading combo/blend routes to codex-only.
  Fix: strip thinking blocks with empty/missing signatures and redacted_thinking
  blocks with empty/missing data entirely. They carry no replayable value.

Tests: 8 new tests (4 per bug), all passing. Existing #5312 and #5945
regression tests still pass — no interference.

* fix(6953): strip only signature:"" thinking blocks, preserve undefined signature

CI caught a regression: translator-helper-branches test had a Claude-format
thinking block without signature field (undefined) that was being stripped
by the original fix. The fix was too aggressive — it stripped both
signature:"" (non-Anthropic synthesized) and signature: undefined
(legitimate Claude-format).

Correct behavior:
- signature === "" (empty string): strip — hallmark of codex/gpt-5.x block
- signature === undefined: preserve with DEFAULT_THINKING_CLAUDE_SIGNATURE fallback
- redacted_thinking data === "": strip
- redacted_thinking data === undefined: preserve with fallback

Added regression test for undefined-signature preservation.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(6980): classify Cloudflare AI neuron exhaustion as quota_exhausted (#6983)

Cloudflare Workers AI free tier (10k Neurons/day, account-wide) returns
429 with body 'you have used up your daily free allocation of 10,000
neurons' which matched no QUOTA_PATTERNS keyword — falling through to
rate_limit (~60s cooldown) instead of quota_exhausted.

Two layers:
1. Provider-specific rule for 'cloudflare-ai' in providerRuleRegistry
   (scope: connection — budget is account-wide, not per-model)
2. Defense-in-depth: /daily free allocation/i in classify429 QUOTA_PATTERNS

Tests: 11/11 pass (provider rule + classify429 paths covered).

Closes #6980

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(models): preserve chat-capable image model rows (#7004)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(sse): register ollama-cloud in USAGE_FETCHER_PROVIDERS (#7026) (#7041)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(quality): read cognitiveComplexity= machine line in validate-release-green (#7009) (#7042)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(relay): bound Bifrost stream lifetime (#7093)

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(sse): recognize xiaomi-tokenplan mimo as a thinking-mode model (#7098)

* fix(sse): recognize xiaomi-tokenplan mimo as a thinking-mode model (port from 9router#1321)

The reasoning_content injector already handles DeepSeek/Kimi/K2/MiniMax thinking-mode upstreams, echoing a placeholder reasoning_content on assistant turns that lack one. Its THINKING_MODEL_PATTERNS list omitted the xiaomi-tokenplan mimo family, so requests through xiaomi-tokenplan/mimo-v2.5-pro still hit upstream's 400 'reasoning_content in the thinking mode must be passed back to the API', making the model unusable in multi-turn conversations (e.g. Codex CLI). Add a /\bmimo\b/i pattern so mimo models get the same treatment.

Reported-by: z.wl (@xxue-z) (https://github.com/decolua/9router/issues/1321)

* docs(changelog): add fragment for #7098 mimo thinking-model fix

* fix(codex): strip regex lookaround from tool schema patterns (#7100)

* fix(codex): strip regex lookaround from tool schema patterns (port from 9router#1556)

Codex/OpenAI's Responses API rejects JSON Schema pattern fields using regex lookaround (e.g. ^(?=.*@).+$) with a 400 'regex lookaround is not supported' error. The existing numeric-field sanitizer (coerceSchemaNumericFields) was only wired into the translated-request path (openai-to-claude.ts), not the native codex/openai passthrough path (normalizeCodexTools in open-sse/executors/codex/tools.ts), so lookahead/lookbehind patterns reached upstream unmodified and broke tool calls for clients that emit them (e.g. IDE agent harnesses validating an email field).

Reported-by: evin (@evinjohnn) (https://github.com/decolua/9router/issues/1556)

* chore(changelog): move #1556 entry to changelog.d fragment

Consistency with the repo's canonical changelog.d/fixes/ workflow (avoids
merge-storm re-conflicts from editing CHANGELOG.md directly).

* refactor(codex): table-drive the regex-strip recursion to keep the complexity ratchet at baseline

The #1556 lookaround strip walked every sub-schema field with its own
copy-pasted if-block (properties / patternProperties / definitions / $defs,
then prefixItems / anyOf / oneOf / allOf), pushing stripUnsupportedRegexPatterns
past the cyclomatic threshold and check:complexity to 2057 > baseline 2056.

Collapse the eight near-identical blocks into two loops over the field-name
constants, with the object-map recursion factored into a helper. Same fields,
same traversal order, same behavior — complexity is back at baseline 2056 and
the #1556 regression tests still pass.

* fix(compression): Headroom SmartCrusher skips developer-role messages (port from 9router#2132) (#7102)

Root cause: SmartCrusher's system-message guard only excluded role === "system", but Codex CLI (open-sse/executors/codex.ts) sends its instructions/tool-schema turn with role "developer" (the Responses-API equivalent of system used by newer models). Every other system-exclusion guard in this codebase also covers developer (roleNormalizer.ts, contextManager.ts, claudeUpstreamMessages.ts, etc.) except this one, so Headroom happily tabular-compacted JSON arrays embedded in the developer turn (e.g. an update_plan tool schema example), corrupting the instructions the model needs to call the plan tool and breaking Codex CLI plan mode.

Fix: extend the guard in crushMessages()/collectCompactableArrays() (smartcrusher.ts) to skip role === "developer" alongside role === "system".

Reported-by: SingCJ (https://github.com/decolua/9router/issues/2132)

* fix(providers): surface a warning on 404 model_not_found in OpenAI-compatible Check (port from 9router#2032) (#7103)

Root cause: validateOpenAICompatibleProvider's chat-completions probe fallback treated ANY 4xx other than 401/403/429/400 as a silent 'credentials valid' pass with no warning, so a bogus/non-standard model id (e.g. Featherless/OpenRouter vendor/model typos) went undetected at Check time. The first real request then hit the upstream 404 model_not_found and the per-model lockout, holding the model unavailable for the configured reset window with no prior indication anything was wrong.

User-visible effect: 'Check' now returns valid:true with an explicit warning (including the upstream error message when parseable) whenever the chat probe answers 404, so a bad model id is caught before it reaches production traffic and the lockout.

Reported-by: advane204f (https://github.com/decolua/9router/issues/2032)

* fix(executors): forward X-Session-ID/X-Title agent metadata headers (#7104)

* fix(executors): forward X-Session-ID/X-Title agent metadata headers (port from 9router#2413)

Custom agent clients (e.g. non-OpenCode providers) commonly send X-Session-ID and X-Title headers for upstream request tracking/attribution, but forwardOpencodeClientHeaders() only forwarded x-opencode-* keys plus User-Agent, silently dropping these for every client. Extends the existing case-insensitive allowlist forwarding path with x-session-id/x-title.

Reported-by: Atikur Rahman Chitholian (@chitholian) (https://github.com/decolua/9router/issues/2413)

* chore(changelog): move #2413 entry to changelog.d fragment

Consistency with the repo's canonical changelog.d/fixes/ workflow (avoids
merge-storm re-conflicts from editing CHANGELOG.md directly).

* fix(cli): verify better-sqlite3 native binary is actually loadable (#7105)

* fix(cli): verify better-sqlite3 native binary is actually loadable (port from 9router#2493)

isBetterSqliteBinaryValid() only checked the .node file's magic bytes (ELF/Mach-O/PE header), never whether the binary was built for the ABI (NODE_MODULE_VERSION) of the Node runtime that loads it. A stale or foreign-ABI binary passed the check and then segfaulted the process on the first database call instead of triggering a rebuild via npmInstallRuntime(). The fix adds a real load probe (require() in a throwaway subprocess) after the magic-byte check, so an incompatible binary is now correctly reported as invalid and the runtime self-heal reinstalls it.

Reported-by: Manikandan (@mrprohack) (https://github.com/decolua/9router/issues/2493)

* chore(changelog): move #2493 entry to changelog.d fragment

Consistency with the repo's canonical changelog.d/fixes/ workflow (avoids
merge-storm re-conflicts from editing CHANGELOG.md directly).

* fix(sse): handle space-separated arg name/value in Composer tool calls (port from 9router#1811) (#7116)

parseInnerCall only split arg segments on a newline between the arg name
and its value. Cursor's live Composer/Auto output has been observed using a
single space instead, so those segments were treated as one long
(space-containing) arg name with an empty value, silently no-opping
Write/tool calls for Composer/Auto models.

Fall back to splitting on the first whitespace boundary when no newline is
present in the segment.

Reported-by: way-art (https://github.com/decolua/9router/issues/1811)

* fix(cli): remove MITM DNS spoof entries before killing server process (#7117)

* fix(cli): remove MITM DNS spoof entries before killing server process (port from 9router#1809)

stopMitm() killed the spawned MITM server process first and only removed the /etc/hosts DNS-spoof entries afterward. During that window any client whose DNS still resolved a target host to 127.0.0.1 but whose MITM listener was already dead got connect ECONNREFUSED 127.0.0.1:443 — exactly the community-confirmed workaround (stop DNS before stopping the server) proves. Swap the two steps so DNS is always cleared first, mirroring the ordering already used by repairMitm() and handleExitCleanup().

Reported-by: dionisius95 (https://github.com/decolua/9router/issues/1809)

* refactor(mitm): extract repair planning out of manager to respect the file-size cap

The #1809 DNS-before-kill ordering fix pushed src/mitm/manager.ts to 813
lines, over the 800-line cap check:file-size enforces for non-frozen files.

Move the pure repair-planning pieces (collectManagedHosts, the RepairPlan
shape and its filesystem/cert/DNS sweep) into a sibling src/mitm/repair.ts.
The in-memory session bookkeeping repairMitm() owns — cached sudo password,
orphaned flag, PID file — deliberately stays in manager.ts, so the seam is
"plan the repair" vs "own the session".

manager.ts is now 731 lines; behavior is unchanged. The DNS-first ordering
fix and its regression guard (tests/unit/mitm-stop-dns-before-kill-1809.ts)
are untouched and still pass.

* fix(mitm): split stopMitm() DNS/kill steps to fix complexity ratchet regression

stopMitm()'s new DNS-before-kill ordering (#1809) pushed its cyclomatic
complexity to 18 (max 15), regressing the complexity ratchet from 2056 to
2057. Extract the DNS-removal step and the process-kill step (in-memory +
PID-file fallback) into two private helpers, mirroring the existing
performRepairSteps() extraction pattern in repair.ts. Behavior unchanged;
complexity back at 2056 (cognitive-complexity drops to 889, one under
baseline).

* fix(api): check Vercel SSO-protection PATCH response on relay deploy (#7119)

* fix(api): check Vercel SSO-protection PATCH response on relay deploy (port from 9router#1037)

The Vercel relay deploy route disabled Deployment Protection (SSO) by firing a PATCH request with .catch(() => {}) and never checking res.ok. When Vercel rejects or no-ops the PATCH (plan doesn't allow disabling protection, an under-scoped token, etc.), the relay was still saved and activated as a healthy proxy pool, and later requests routed through it failed with an undiagnosed 403 Access denied from Vercel's own deployment protection — indistinguishable from an upstream-provider rejection (e.g. Codex/ChatGPT edge-IP blocking).

Extract disableSsoProtection() to check the PATCH response and surface an ssoProtectionWarning in the deploy response when it fails, so the failure source can be diagnosed instead of silently masked.

Reported-by: Rico Aditya (@ricatix) (https://github.com/decolua/9router/issues/1037)

* refactor(api): extract vercel-deploy POST helpers to keep the cognitive-complexity ratchet at baseline

The SSO-protection check added to POST pushed its cognitive complexity from
15 to 21, regressing the cognitive-complexity ratchet (891 > baseline 890).

Extract two pure helpers with identical behavior:
- buildDeployErrorResponse(): the sanitized non-ok Vercel deploy response
- resolveSsoProtectionWarning(): the SSO PATCH check + warning string

POST now reads as a flat sequence of guards. No behavior change.

* fix(combos): reject oversized fusion panels before fan-out (port from 9router#1905) (#7120)

A fusion combo fans every panel model out in parallel and buffers each
model's full response text in memory simultaneously. With the runtime heap
capped by Dockerfile's OMNIROUTE_MEMORY_MB (default 1024MB), a large panel
(reported: ~73 models via an 'auto' combo with strategy: fusion) with
sizable concurrent responses can exceed the heap ceiling and OOM-crash the
whole container instead of failing one request.

handleFusionChat now rejects panels above a configurable hard cap
(FUSION_DEFAULTS.maxPanel = 40, overridable per-combo via
fusionTuning.maxPanel) with a clean 400 before fan-out begins.

Reported-by: Phong Vu (@fontvu) (https://github.com/decolua/9router/issues/1905)

* fix(combo): detect empty content_block in streaming SSE peek (#7121)

* fix(combo): detect empty content_block in streaming SSE peek (port from 9router#1382)

The bounded SSE peek in validateResponseQuality() treated ANY content_block_start/delta/stop event as proof of real output and stopped buffering immediately, without checking whether the block actually carried text/tool_use content. Some upstreams (reported: DeepSeek, GLM via claude→openai translation) can open and close a text content_block with empty text and no tool_use on tool-heavy requests — the gateway logged success and forwarded a client-visible empty completion, and combo routing never failed over to the next model.

Track real content separately from 'a content_block_* event was seen': a tool_use/redacted_thinking block start is self-evidently real signal, a text/thinking block start is not (real content only confirmed via a subsequent delta carrying non-empty text/thinking, or an input_json_delta streaming tool arguments). A completed lifecycle (message_start + message_delta/stop) that never produced real content now fails validateResponseQuality(), matching the existing content_filter empty-stream detection path (#3685).

Reported-by: heishen6 (https://github.com/decolua/9router/issues/1382)

* refactor(combo): extract SSE lifecycle applier to keep the complexity ratchets at baseline

The #1382 empty-content_block peek added a branchy switch inline in
parseAccumulatedSse, pushing check:complexity to 2057 > baseline 2056.

Move the switch to a module-level applySseLifecycleEvent() and hold the
four lifecycle booleans in a single SseLifecycleFlags object threaded
through it, so the closure no longer copies flags in and out per event.
The per-event predicates (content_block_start / content_block_delta /
message_delta) are split into small guard helpers, which keeps the
applier flat — cognitive complexity punishes nesting, and an earlier
switch-only extraction traded the cyclomatic ratchet for a cognitive
regression at 891 > 890.

Logic is unchanged; both ratchets are now green (complexity 2055,
cognitive-complexity 890) and the #1382 regression tests still pass.

* fix(auto): use p95 fallback in speed factors (#7128)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* Use OpenAI chunks for early chat keepalives (#7136)

* Use OpenAI chunks for early chat keepalives

* Update keepalive assertion to match chat completion chunk format

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* [needs-vps] fix(dashboard): add vision-capability toggle for custom OpenAI-compatible models (#7124)

* fix(dashboard): add vision-capability toggle for custom OpenAI-compatible models (port from 9router#1904)

detectVisionInput()/getCustomVisionCapabilityFields() already honoured an explicit
supportsVision flag on a custom-model record, but there was no way to set it: the
POST/PUT /api/provider-models Zod schema and updateCustomModel()/addCustomModel()
silently dropped the field, and the 'Custom Models' add/edit UI had no checkbox at
all. Self-hosted/local backends that don't self-report an image input modality
(OpenRouter-style architecture.input_modalities) therefore had no way to be flagged
vision-capable, so the vision tag never appeared and image inputs were rejected.

Reported-by: nguyenphi37 (https://github.com/decolua/9router/issues/1904)

* refactor(dashboard): extract providerCredentialText from providerPageHelpers to respect the file-size gate

providerPageHelpers.ts is a frozen god-file (cap 1053, split(\n).length metric)
and this PR's own +3 lines (the #1904 supportsVision field) pushed it to 1054,
failing check:file-size. Extract the cohesive providerText utility + the 4
web-session-credential label/hint/title helpers into a new leaf module
(providerCredentialText.ts), re-exported from providerPageHelpers.ts for
backward compatibility so all existing import sites keep working unchanged.
File now sits at 946 lines, well under the frozen cap.

* refactor(db): extract tri-state override helper to keep the complexity ratchet at baseline

The #1904 supportsVision override added a second copy of the "absent keeps /
null clears / else coerce" block already used by preserveOpenAIDeveloperRole,
pushing updateCustomModel to 84 lines and check:complexity to 2057 > 2056.

The file-size failure was masking this one: the gate exits on its first red,
so complexity never ran until providerPageHelpers was back under its cap.

Fold both blocks into applyTriStateBooleanOverride(). Behavior is unchanged —
updateCustomModel is back under max-lines-per-function and the global count
returns to the 2056 baseline (cognitive-complexity stays at 890).

* [needs-vps] fix(dashboard): align onboarding tier content (#7125)

* fix(dashboard): align onboarding welcome feature cards vertically

* fix(dashboard): align onboarding tier content

* chore: scope onboarding PR to UI fix

* i18n(pt-BR): add onboarding.tier.flowCaption + afterSetup keys

The two new tier keys added to en.json were missing from pt-BR.json, tripping
the i18n-pt-br no-drift test (#6695). Add their pt-BR translations.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(ci): fetch full base history in pr-test-policy (shallow graft broke merge-base) (#7501)

With --depth=1 the base ref is grafted, so 'git diff base...HEAD' resolves a
wrong merge-base for PR branches that recently merged the release branch. The
three-dot diff then attributes ALREADY-MERGED sibling PRs' changes to the PR
under test, producing false high-signal reds (deleted test files / weakened
asserts that exist in no ref reachable from the PR).

Observed live on #7329: the job blamed it for tests/unit/ui/provider-plan-config.test.tsx
(deleted by an unrelated merged PR) and for #7106's antigravity files. Local
reproduction with full history returns PASS for the same head.

The job's checkout is already fetch-depth: 0, so the full base fetch only
updates the ref — negligible cost.

* [needs-vps] fix(electron): materialize Turbopack hashed-module symlinks during packaging (#6724, #6594) (#6794)

* fix(electron): materialize Turbopack hashed-module symlinks during packaging

Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy
files belong to #6620, not this PR); keeps only the author's changes.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(electron): actually enable materializeSymlinks on the electron standalone path

The option existed in assembleStandalone but no production callsite passed it,
so packaged builds still shipped absolute symlinks into the build machine's
worktree for Turbopack hashed externals (better-sqlite3-<hash>, sqlite-vec-<hash>)
— verified by dpkg -c on a freshly built .deb. One-line enablement on the
electron prepare path, which is exactly the surface #6724/#6594 report.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: huohua-dev <258873123+huohua-dev@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(grok): strip reasoningEffort for grok cli models (#6938)

Co-authored-by: minisforum <no@mail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* chore(quality): rebaseline zizmor 169->175 (cycle workflow drift)

+6 from v3.8.48/v3.8.49 workflow changes (npm-publish WS1.3 #7092, electron-release,
nightly-compat, nightly-release-green, CI restructures incl. #7501). Breakdown vs v3.8.47:
+3 unpinned-uses (@vN convention), +2 cache-poisoning (own release-workflow artifact
upload/cache -- operator-controlled, not fork-PR exploitable), +1 excessive-permissions
(nightly-compat issues perm). No new template-injection/artipacked/dangerous-triggers.
Measured zizmor 1.25.2 = 175 on da3a0be69. Unblocks Quality Gates (Extended) for #7329.

* fix(codex): Test probe uses a ChatGPT-account-supported model (#7521) (#7524)

The connection Test button always reported success for a Codex connection
backed by a ChatGPT account: the probe sent `gpt-5.3-codex`, a codex-only
model the ChatGPT-account backend rejects outright with a 400 — the same
status the probe treats as 'auth accepted, body invalid'. A bad token and a
good token both came back 400, so Test could never fail on a bad token.

Probe with `gpt-5.5` (confirmed served for ChatGPT-account sessions via live
VPS test 2026-07-16) instead; `input: []` still yields the intended 400 for a
good token, 401/403 for a bad one.

Live verification (VPS): gpt-5.3-codex, gpt-5.6-sol and the gpt-5*-codex ids all
return 'not supported when using Codex with a ChatGPT account'; gpt-5.5 and
gpt-5.6-terra answer normally on the same account.

Closes #7521

* fix(codex): validate refresh_token on import before persisting (#7522) (#7525)

POST /api/oauth/codex/import accepted a payload with an already-invalidated
refresh_token and persisted it as an 'active' connection that could never
work — the failure surfaced confusingly only on first real use, long after
the import looked successful.

Validate each record's refresh_token against OpenAI's OAuth endpoint before
persisting, reusing refreshCodexToken() (free exchange, no quota). On an
unrecoverable refresh error the record is rejected with a clear re-auth
message; a valid token imports as before, with any rotated tokens applied.
Bulk import still processes each record independently — one dead token no
longer blocks the valid ones.

Reproduced live 2026-07-16: a 2026-07-10 auth.json imported clean but its
refresh_token returned 401 refresh_token_invalidated. TDD: 3 tests RED against
the old route, 5/5 GREEN with the fix.

Closes #7522

* fix(codex): non-stream chat 502 'Response body is already used' (single-reader peek) (#7526)

Every non-streaming Codex chat request for a ChatGPT-account connection failed
instantly with [502]: Response body is already used (reset after 1m). The
streaming/playground path was unaffected.

Root cause: peekCodexSseTransientError (open-sse/executors/codex.ts) peeked the
SSE prefix with response.body.getReader(), then called reader.releaseLock() and
response.body.getReader() a SECOND time on the same already-disturbed body to
build the replacement stream. Re-acquiring a reader on a disturbed body throws
on undici ('Response body is already used'); chatCore's generic upstream-error
handling then stamped the TypeError with a default 60s cooldown, masking a pure
code defect as a rate limit (and tripping the codex circuit breaker).

Fix: keep the single reader already held; never touch response.body again.

TDD: a getReader spy that throws on the 2nd acquire reproduces the exact hazard
— 1 test RED against the release code, 2/2 GREEN with the fix; the replacement
body stays byte-identical to the upstream SSE. No regression across the codex
unit suite. Reproduced live on the VPS 2026-07-16.

* fix(oauth): surface tunnel hint when Codex OAuth runs on a remote host (#7523) (#7527)

The PKCE callback server binds the SERVER's loopback (localhost:PORT). When
the operator drives the OAuth flow from a different machine (OmniRoute on a
remote host/VPS), the provider redirects the browser to the operator's OWN
localhost:PORT — the confirmation screen hangs forever with no explanation.

start-callback-server now inspects the request Host: on a non-loopback host it
returns { remoteHost, tunnelCommand, message } so the UI can show the
'ssh -L PORT:127.0.0.1:PORT' instruction (or steer to the paste/import flow)
instead of a silent hang. Loopback access is unaffected. The Host header is
spoofable, so this drives only a UI hint — never an auth decision.

Logic extracted to remoteOAuthHint.ts (keeps the god-route under its size
budget and makes it unit-testable). TDD: 4 tests covering loopback (no hint),
null host (fail-open), and remote host (correct tunnel command for both the
fixed 1455 and OS-assigned ports).

Closes #7523

* fix(compression): lazy-load typescript in RTK codeStripper so prod-lean deploys don't break (#7096) (#7164)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>

* fix(combo): fall back on Responses SSE failures

* fix(combo): cancel rejected upstream streams

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: growab <nekron@icloud.com>
Co-authored-by: Xiangzhe <xiangzhedev@gmail.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: Rafael Dias Zendron <rafael.zendron22@gmail.com>
Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Ronaldo Davi <ronaldodavi@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Co-authored-by: huohua-dev <celentanohertor@gmail.com>
Co-authored-by: huohua-dev <258873123+huohua-dev@users.noreply.github.com>
Co-authored-by: CitrusIce <31264099+CitrusIce@users.noreply.github.com>
Co-authored-by: minisforum <no@mail.com>
2026-07-18 21:18:14 -03:00
2026-07-13 09:12:40 -03:00
2026-06-13 17:27:40 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-06-04 20:05:38 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-07-04 13:00:30 -03:00
2026-07-02 10:47:13 -03:00
2026-06-16 01:00:40 -03:00
2026-06-11 04:01:24 -03:00
2026-06-30 06:54:29 -03:00
2026-07-13 09:12:40 -03:00
2026-06-13 17:27:40 -03:00
2026-06-16 01:00:40 -03:00
2026-06-17 02:43:21 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-05-29 12:44:29 -03:00
2026-05-29 12:44:29 -03:00
2026-04-02 20:37:54 +08:00
2026-06-13 17:27:40 -03:00
2026-07-13 09:12:40 -03:00
2026-07-04 13:00:30 -03:00
2026-07-04 13:00:30 -03:00
2026-06-17 02:43:21 -03:00
2026-05-29 12:44:29 -03:00
2026-05-24 18:05:58 -03:00
2026-07-04 13:00:30 -03:00
2026-06-29 08:40:06 -03:00

OmniRoute Dashboard

🚀 OmniRoute — The Free AI Gateway

Never stop coding. Connect every AI tool to 264 providers90+ free — through one endpoint.

Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini. Auto-fallback.

RTK + Caveman compression saves 1595% 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 →)


Star the repo if OMNIROUTE helped you save money and make your work easier.

Stars diegosouzapw%2FOmniRoute | Trendshift Star History Rank


251 AI Providers 90+ Free 1.6B Free Tokens/mo Token Savings 18 Strategies $0 to start


💬 Join the community

Discord Telegram WhatsApp Global WhatsApp Brasil Website

Questions, provider tips, roadmap & support → Discord · Telegram · WhatsApp 🌍 Global / 🇧🇷 Brasil


🧩 Available

npm version NPM Monthly Docker Hub License: MIT Docker Pulls Electron Downloads

🚀 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.
OmniRoute free-tier budget card: ~1.6B free tokens per month steady, up to ~2.1B in the first month with signup credits, from the documented free tiers of 40+ provider pools / 500+ models behind one endpoint. Honest pool-deduped math — each shared pool counted once (counting every rate limit 24/7 would read ~10B; not published), 15 providers ToS-flagged so you decide. Budget bar of the 21 countable free pools with per-model grid (Mistral Large 3 1B, GPT-4o mini 150M, LongCat 150M, Gemini 2.5 Flash 60M … Auto 25K), ~616M one-time first-month signup credits (vertex 300M, agentrouter 200M, predibase 25M, together 25M, glm-cn 20M, doubao 15M, ai21 10M, deepseek 5M, hyperbolic 5M), plus permanently-free no-token-cap providers (SiliconFlow, Z.AI GLM-Flash, Kilo, OpenCode Zen, baidu …) and a $10 OpenRouter top-up unlocking +24M/mo — surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers.

Animated summary of the live /dashboard/free-tiers page. Full methodology (pool dedupe, credit tiers, provider terms): docs/reference/FREE_TIERS.md.


💥 The Promise

One endpoint. 264 providers. Never stop building — and let OmniRoute pick the cheapest one that works.

🚫 Never hit limits
Auto-fallback across 264 providers in milliseconds. Quota out? Next provider takes over — zero downtime.
💸 Save up to 95% tokens
RTK + Caveman stacked compression cuts 1595% of eligible tokens (~89% avg on tool-heavy sessions).
🆓 $0 to start
90+ providers with a free tier, 11 free forever (Kiro, Qoder, Pollinations, LongCat…). No card needed.
🔌 Every tool works
24+ 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 (94 tools), A2A, memory, guardrails, evals. 21,000+ 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 1595% eligible tokens per request
💸 Expensive APIs ($2050/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
OmniRoute request flow: your IDE or CLI (Claude Code, Cursor, Cline…) calls one local endpoint (http://localhost:20128/v1); the OmniRoute Smart Router (RTK + Caveman compression, 18 routing strategies, circuit breakers, TLS stealth, MCP, A2A, guardrails) auto-falls back across 4 provider tiers — Tier 1 Subscription (Claude Code, Codex, Copilot), quota out? Tier 2 API Key (DeepSeek, Groq, xAI), budget hit? Tier 3 Cheap (GLM $0.5, MiniMax $0.2), budget hit? Tier 4 Free (Kiro, Qoder, Pollinations) — 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 — 18 routing strategies

All 18 strategies — mix & match per combo step:

# Strategy What it does
1 priority First-target ordered list — drain each before the next 🥇
2 fill-first Fill each target's quota fully before moving on
3 weighted Weighted random by per-target weight
4 round-robin Cycle through targets in order
5 p2c Power-of-two-choices random load balancing
6 least-used Pick the target with the lowest current load
7 random Uniform random pick (deduplicated)
8 strict-random Random without de-duplicating repeats 🎲
9 cost-optimized Minimize $ per request from live catalog pricing 💸
10 headroom Pick the target with the most remaining quota
11 reset-window Prefer the target whose quota window resets soonest
12 reset-aware Rank by quota reset time — short windows first 📊
13 context-relay Hand off context across targets for long conversations 🧠
14 context-optimized Pick the best fit for the current context size
15 lkgp Last-Known-Good Path — sticky to the last successful target
16 auto 12-factor live scoring across every connection 🤖
17 fusion Fan out to a panel of models + a judge synthesizes one answer 🧬
18 pipeline Chain steps — each target's output feeds the next one 🔗

The Auto-Combo engine scores every candidate on 12 factors (health, quota, cost, latency, success rate, freshness…) — see docs/routing/AUTO-COMBO.md.

⚖️ Quota-Share — split one subscription across a team NEW

Running several keys against the same upstream account (one Codex Pro plan, one Kimi key, one GLM Coding seat)? A burst on one key can burn the whole 5-hour / hourly quota and lock everyone else out. Quota-Share distributes a provider's time-based quota fairly across the keys in a pool — and it's work-conserving, so an idle member's slice is lent out instead of wasted.

Knob What it controls
⚖️ Allocation weight each key's slice of the pool — e.g. 50 / 30 / 20
📐 Dimensions track % · requests · tokens · $, per 5h / 7d / per-model window
🚦 Policy hard (block over share) · soft (deprioritize) · burst (use idle headroom)
🧱 Cap absolute ceiling per key, independent of mode
OmniRoute key pool 'team-codex': one Codex Pro account shared by 3 keys over a 5-hour window. alice weight 50 (up to 50% of the shared 5h quota), bob weight 30, ci-bot weight 20. In generous mode (under 50% pool used) idle shares are lent out; once the pool crosses 50% strict mode holds each key to its fair share.

Enforced in the hot path before the request leaves OmniRoute, with per-(key, model) caps + session stickiness for prompt-cache integrity (now with a per-combo / global disable toggle). 📖 Quota Sharing Engine

🧱 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
OmniRoute combo 'always-on' with priority strategy: requests go to 1. cc/claude-opus-4-7 (subscription, use it fully); on failure fall through to 2. cx/gpt-5.5 (second subscription), then 3. glm/glm-5.1 (cheap backup at $0.5/1M), then 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 251 20100
🆓 Free providers 90+ (11 free forever) 15
🔀 Routing strategies 18 (priority, weighted, cost-optimized, context-relay, fusion…) 13
🗜️ Token compression RTK + Caveman stacked (1595%) None / 2040%
🧰 Built-in MCP server 94 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, Cursor, Devin, Jules None
🥷 TLS fingerprint stealth JA3/JA4 via wreq-js None
🖥️ Multi-platform Web · Desktop · Termux · PWA Web only
🌍 i18n 42 locales 04

📊 Detailed comparison vs LiteLLM, OpenRouter & Portkey → docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md


What's New

Recent highlights from v3.8.20 → v3.8.47. Full history in CHANGELOG.md.

  • 🗜️ Compression hardening — a default-on inflation guard (discard the stacked result and send the verbatim original whenever compression would grow the prompt), completed Caveman rule packs for German / French / Japanese (dedup + ultra) plus a new Chinese (文言 / wényán) input pack with zh-vs-ja auto-detection, and RTK filters for Gradle & .NET (dotnet) build output. → Compression
  • 💸 Honest flat-rate cost — subscription / coding-plan providers (ChatGPT Web, grok-web, the Minimax / Kimi / GLM / Alibaba Coding plans, Xiaomi MiMo…) now read $0 in cost analytics instead of an inflated per-token estimate, while budget / quota / routing keep estimating unchanged. → API Reference
  • ⚖️ Quota-Share routing — a dedicated combo strategy that spreads load across accounts by available quota: Deficit-Round-Robin scheduling, per-connection max_concurrent with cooldown-wait queueing, multi-window usage buckets (5h / 7d / per-model), per-(key, model) caps, session stickiness for prompt-cache integrity (now with a per-combo / global disable toggle), and proactive saturation from upstream token-usage headers. → Resilience Guide
  • 🤖 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); omniroute launch / omniroute launch-codex are zero-config launchers. → CLI Integrations
  • 🛰️ Remote mode — drive a remote OmniRoute from any machine with scoped access tokens (omniroute connect / omniroute contexts / omniroute tokens), plus an omniroute login antigravity helper that runs Google "native/desktop" OAuth on your own machine and pastes a credential blob into a remote/VPS install (where the loopback redirect is unreachable). → Remote Mode
  • 🧭 Smarter auto-routing — OpenRouter-style auto/<category>:<tier> combos (e.g. auto/coding:fast, auto/reasoning:pro), a Fusion strategy (fan out to a panel of models in parallel, then synthesize via a judge), task-aware routing (best-fit connection per task type), per-request X-Route-Model override, live Arena-ELO + models.dev model intelligence, per-step account allowlists, provider-wildcard combo steps, nested combo-ref execution, sticky weighted selection, web_search-aware routing (now with per-model web-search/web-fetch interception rules), native xAI Grok /v1/responses routing, and per-request Auto-Combo controls (X-OmniRoute-Mode mode-preset override + X-OmniRoute-Budget hard USD cost ceiling, scoped to a single request). Embeddings-only and rerank-only models (JinaAI, OpenRouter custom, reranker models…) no longer disappear from the combo builder's model picker. → Auto-Combo
  • 🗜️ Pluggable compression — an async pipeline of 10 composable engines with Compression Studios, an LLMLingua-2 ONNX engine and a heuristic/SLM two-tier Ultra, RTK, delegated Anthropic Context Editing, Output Styles (output-axis steering: terse-prose / less-code / terse-CJK), an adaptive context-budget dial (escalate only as far as needed to fit the context window), per-request x-omniroute-compression control, an opt-in offline eval harness, one-click Headroom proxy lifecycle management from the dashboard (Docker sidecar supported), a synthetic compression playground (Play lanes + A/B Compare with USD-capped fidelity verdicts), an opt-in per-step fidelity gate that rejects a lossy engine before it degrades the prompt, a best-of-N candidate encoder (GCF vs TOON — keep whichever is shorter, with an A/B bytes/token table in the studio), the vendored GCF codec updated to spec v3.2 (nested flattening — deeply-nested payloads go from ~3% to ~32% compression vs JSON), a new omniglyph engine (context-as-image, ~10× fewer tokens on the converted block), CCR ranged/grep/stats retrieval (pull an exact byte/line slice or summary of a stored block instead of re-expanding it), a unified panel with named profiles + an active-profile selector, an opt-in per-engine pipeline circuit-breaker, an opt-in LLM-tier engine (a model pass for higher-ratio semantic compression), a read-lifecycle engine that collapses superseded file reads, usage-observed prefix freeze, a graduated CCR retrieval-feedback ramp, a preserveSystemPrompt mode enum, and a drag-reorder pipeline editor in the studio. → 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 everywhereX-OmniRoute-* cost/usage headers on every endpoint (including media), a non-token cost engine, a cache-HIT X-OmniRoute-Cost-Saved header, and per-key USD spend quotas. → API Reference
  • 🧠 Memory you control — opt-in int8 vector quantization (Qdrant + sqlite-vec), opt-in typed memory decay (aged low-value memories fade on a per-type schedule), memory off by default, and a per-request x-omniroute-no-memory header. → 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
  • 🖼️ New endpoints/v1/ocr (Mistral OCR) and /v1/audio/translations (Whisper-style audio translation) round out the media API surface. → API Reference
  • 🌍 Deployment & ops — reverse-proxy basePath deployment (OMNIROUTE_BASE_PATH, e.g. serving OmniRoute under /omniroute/), browser-language auto-detect on first visit, per-API-key device/connection tracking (IP+UA fingerprint, masked, in-memory only), root-less MITM cert trust for user-namespaced containers (OMNIROUTE_NO_SUDO), server-side configured-only / available-only filters on the Free Provider Rankings page, and Traditional Chinese (zh-TW) localization for the frontend + CLI. → Environment
  • 🤝 More providers & agents — Cursor Cloud Agent (a 4th cloud agent), CodeBuddy CN (copilot.tencent.com), a Google Flow video-generation provider, new gateways DGrid and Pioneer AI (Fastino Labs), inbound xAI Grok translators plus Grok Build (xAI) with an OAuth import-token flow, GPT-4 / GPT-4o-mini on the GitHub Copilot provider, multi-model Factory Droid, ZenMux Free (session-cookie free tier), Alibaba DashScope text-to-video (wan2.7-t2v), a refreshed 250-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), Vertex AI media generation (speech/transcription/music/video), a first-class Ollama local-provider card, the SenseNova free Token Plan (chat + text-to-image), one-click account import from CLIProxyAPI (~/.cli-proxy-api/), Claude Sonnet 5 wired end-to-end, a new provider wave (Kenari, SumoPod, X5Lab, Charm Hyper, Nube.sh, b.ai, Qiniu, ModelScope, Augment/Auggie CLI, ClinePass, NVIDIA NIM image generation), Codex account import from a raw ChatGPT access token, the Requesty gateway (BYOK, ~200 free req/day), Yuanbao (web) as a cookie-session provider (DeepSeek V3/R1 + Hunyuan), the Zed hosted LLM aggregator (OAuth), Claude 5 Sonnet on the Claude Web provider, Kiro adaptive-thinking reasoning surfaced as reasoning_content, bulk API-key add for Cloudflare Workers AI, and OpenVecta (AI inference gateway). → Providers
  • Local performance & infra — a one-click local Redis launcher (omniroute redis up, plus a dashboard Redis panel), one-click Cloudflare Workers and Deno Deploy relay deployers wired into the proxy pool, a relay-backend selector (OMNIROUTE_RELAY_BACKEND=ts|bifrost|auto) so /v1/relay stays the stable surface while choosing the fastest backend internally, Bifrost (Go AI-gateway) and Mux (agent-orchestration daemon) promoted to first-class embedded/supervised services alongside 9Router/CLIProxyAPI, Webshare added as a paid fourth source in the free-proxy provider framework, and shorthand proxy formats + protocol header mode for bulk proxy import. → Embedded Services

🤖 Compatible CLIs & Coding Agents

One config — http://localhost:20128/v1 — and every AI IDE or CLI runs on free & low-cost models.

Claude Code
Claude Code
Codex CLI
Codex CLI
Cursor
Cursor
Copilot
Copilot
Continue
Continue
OpenCode
OpenCode
Kilo Code
Kilo Code
Droid
Droid
OpenClaw
OpenClaw
Kiro
Kiro
Command Code
Command
also works with · Cline · Antigravity · Windsurf · AMP · Hermes · Qwen CLI · Roo · Continue · any OpenAI-compatible tool

📖 Per-tool setup for all 24+ tools → docs/reference/CLI-TOOLS.md · 🧩 OpenCode plugin → @omniroute/opencode-provider


🌐 251 AI Providers — 90+ Free

The most complete catalog of any open-source router: 264 providers, 90+ with a free tier, 11 free forever.

🏢 Every major lab — through one endpoint

OpenAI
OpenAI
Anthropic
Anthropic
Gemini
Gemini
xAI Grok
xAI Grok
DeepSeek
DeepSeek
Mistral
Mistral
Qwen
Qwen
Meta Llama
Meta Llama
Groq
Groq
NVIDIA
NVIDIA
MiniMax
MiniMax
Cohere
Cohere
Perplexity
Perplexity
Hugging Face
HuggingFace
Together
Together
Fireworks
Fireworks
Cloudflare
Cloudflare
Baidu
Baidu

…and 220+ more — every icon resolves live from the dashboard's provider catalog. 📖 Provider Reference


🆓 Free Forever — $0, no card

AgentRouter
AgentRouter
GPT-5, Claude, Gemini
$100 free credits
Qoder AI
Qoder AI
Kimi-K2, DeepSeek-R1
Unlimited FREE
Pollinations
Pollinations
GPT-5, Claude, Llama 4
No key needed
LongCat
LongCat
LongCat-2.0
10M tokens one-time (KYC) 🔑
Cloudflare AI
Cloudflare AI
50+ models
10K neurons/day
NVIDIA NIM
NVIDIA NIM
129 models
~40 RPM free
Cerebras
Cerebras
Qwen3 235B
1M tokens/day

📖 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 && 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 80+ 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

Animated terminal demoing the OmniRoute CLI — omniroute providers list, omniroute combo list, omniroute health — cycling over the 80+ command surface: 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 — 94 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 1595% Tokens — Automatically

Why use many tokens when few tokens do the trick? Every request passes through OmniRoute's compression pipeline transparently — no client changes. It's now a stack of 10 composable engines that run in order and mix & match per routing combo — building on ideas from RTK, Caveman ( 78K+), LLMLingua-2, and Troglodita (PT-BR).

🧱 The 10-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, flat or nested (~30%), via a vendored GCF codec (spec v3.2)
5 Relevance Extractive sentence scoring against the last user query
6 Caveman Rule-based prose compression (~6575% on output)
7 LLMLingua-2 ML semantic pruning via MobileBERT ONNX — code-safe, async
8 Lite Whitespace + image-URL trimming (latency-light baseline)
9 Aggressive Summarization + progressive aging of old turns
10 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 6090% Shell/test/build/git output
🔗 Stacked (RTK → Caveman) 7895% 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

OmniRoute compression pipeline: a client request of 10,000 tokens passes through 10 stacked engines — Session-Dedup, CCR, RTK, Headroom, Relevance, Caveman, LLMLingua-2, Lite, Aggressive, Ultra — and reaches the provider at about 1,080 tokens, up to 95% saved. Code, URLs and JSON are always preserved byte-perfect.

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.

🎚️ Beyond the engines — output styles, the adaptive dial & per-request control

The 10 engines above shrink what goes in. Three more layers shape how, when, and what comes out:

  • 🪄 Output Styles (output-axis steering) — inject deterministic, cache-safe response-shaping instructions; combinable, each at lite / full / ultra intensity. Adding a style is a one-line registry entry:
    • Terse prose — drop filler / articles / hedging; keep technical substance exact.
    • Less code — "lazy senior dev" YAGNI: smallest working change, no unrequested scaffolding.
    • Terse CJK (文言) — classical-Chinese ultra-terse style (locale-gated to zh).
  • 🎯 Adaptive context-budget (the dial) — instead of one on/off token threshold, escalate the cheapest, most-lossless engines only as far as needed to fit the model's context window. Policy: reserve-output (default, model-aware) · percentage · absolute. Mode: floor (guarantee fit) · replace-autotrigger (your explicit choice wins) · off (legacy threshold).
  • 🎛️ Where compression is decided (precedence, high → low) — per-request x-omniroute-compression header routing-combo override active named profile adaptive / auto-trigger panel default off. The applied plan echoes back in the X-OmniRoute-Compression: <mode>; source=<source> response header.

Auto-trigger by token threshold, flip on the adaptive dial, pin a named profile, set a one-off per request, or assign a pipeline per routing combo — whichever fits the workload. An opt-in offline eval harness (npm run eval:compression) scores fidelity vs. savings on a pinned corpus before you promote a change.

📖 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, ~50 credits/month per account) 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 add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core && 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.

Faster / leaner install (skip the native build)

The native SQLite engine (better-sqlite3) is an optional dependency, so a global install never blocks on compiling from source: it uses a prebuilt binary when one matches your platform/Node, and otherwise falls back transparently to a pure-JS engine (node:sqlite on Node 22+, else the bundled sql.js WASM) — no build tools required.

To skip the post-install native warm-up entirely (CI, headless, or slow machines):

OMNIROUTE_SKIP_POSTINSTALL=1 npm install -g omniroute   # CI=1 also skips it

For the fastest installs prefer pnpm (content-addressed store + hard links — see above). For a dashboard-free, headless runtime use the Docker base profile (above) or the Termux guide. The CLI and the web dashboard are served by the same process on one port, so there is no separate CLI-only package today.


🎬 OmniRoute in Action

Guia em Português
🇧🇷 Português
Guia completo
English Guide
🇺🇸 English
Complete walkthrough
Руководство
🇷🇺 Русский
Полное руководство

🎬 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 $10200/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-2.0 10M one-time (KYC)
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 — ~50 credits/mo per acct)
2. if/kimi-k2-thinking    (Qoder — unlimited)
3. pol/gpt-5              (Pollinations — no key)
4. lc/LongCat-2.0         (10M one-time backup, KYC)
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%) → ~$150300/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

📖 docs/ops/PROXY_GUIDE.md

Full feature list — 30+ capabilities (memory, evals, observability)

Routing: 18 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 (94 tools, 3 transports, 30 scopes) · A2A (JSON-RPC 2.0, SSE, 6 skills) · ACP · cloud agents (Codex, Cursor, 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? Mostly — Qoder, Pollinations, LongCat, and Cloudflare are free with no per-account credit cap. Kiro is free too but capped at ~50 credits/month per account. Stack multiple free providers in a combo and auto-fallback keeps you serving for $0. 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 264 providers.

📖 User Guide · API Reference · Environment Config

🐛 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.22.2 <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 Providers Combos Combos
Analytics Analytics Health Health
Translator Translator Settings Settings
CLI Tools CLI Tools Usage Logs Usage

📧 Support & Community

💬 Chat with the community — Discord, Telegram & WhatsApp (🌍 / 🇧🇷) links are at the top of this README.



🛠️ Tech Stack

  • Runtime: Node.js 22.x or 24.x LTS (24 LTS recommended) — >=22.22.2 <23 || >=24.0.0 <27
  • Language: TypeScript 6.0 — 100% TypeScript across src/ and open-sse/ (zero any in 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 (21,000+ test cases across 2,586 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 12-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 95 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 21,000+ 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
oyi77

🥇 189 commits • +155K lines
Analytics engine, SQL aggregations,
proxy marketplace, test coverage
Chris Staley
Chris Staley

🥈 70 commits • +5.7K lines
SSE stream hardening, Responses API,
Gemini pagination, test regression fixes
zenobit
zenobit

🥉 62 commits • +24K lines
CI/CD pipeline, i18n for 33 languages,
Void Linux package, platform fixes
R.D. & Randi
R.D. & Randi

🏅 108 commits • +30K lines
Endpoints page, tunnel integrations,
Docker workflows, A2A status, compression UI
benzntech
benzntech

🏅 22 commits • +7.5K lines
Electron desktop app, auto-updater,
release build workflows, cross-platform CI
herjarsa
herjarsa

🏅 21 commits • +6K lines
Zero-latency combos, vision-bridge auto-routing,
catalog context-length, resilience 429 hints

🙏 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.



👥 280+ Contributors

Contributors

How to Contribute

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. 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

📊 Stars

Star History Chart

🌍 StarMapper

StarMapper

🙏 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 19.0k The original project this fork is built on — extended here with multi-modal APIs and a full TypeScript rewrite.
CLIProxyAPI · router-for-me 38.8k The Go implementation that inspired this JavaScript / TypeScript port.
LiteLLM · BerriAI 52.1k 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 78.2k 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 67.3k High-performance command-output compression — inspired our RTK engine, JSON filter DSL, raw-output recovery and the stacked RTK → Caveman pipeline.
headroom · headroomlabs-ai 54.5k Reversible context-compression (SmartCrusher) — inspired our headroom engine and the ccr retrieve-marker pattern.
LLMLingua · Microsoft 6.4k Prompt-compression research (LLMLingua / LLMLingua-2) — inspired our async, code-safe, fail-open llmlingua engine.
llmlingua-2-js · atjsh 28 The JS/ONNX port (MobileBERT / XLM-RoBERTa) used as the worker-thread backend for our LLMLingua engine.
Troglodita · Lenine Júnior 16 PT-BR token compression — powers our pt-BR language pack: pleonasm reduction and filler removal tuned for Brazilian-Portuguese grammar.
ponytail · DietrichGebert 68.8k The viral "lazy senior dev" YAGNI-coder skill — inspired our less-code Output Style: smallest-working-change steering that cuts generated code (the output-axis sibling to Caveman's terse prose).

🧩 Compact formats, token research & code-aware tooling

Project How it inspired OmniRoute
TOON · toon-format 24.7k Token-Oriented Object Notation — its columnar, header-plus-rows model shaped our tabular compaction stage.
GCF Graph Compact Format · Blackwell Systems 14 First inspired our tabular compaction stage; now its zero-dependency, lossless generic-profile encoder is vendored directly as the Headroom codec (MIT, SPDX-marked), current with GCF spec v3.2.
token-optimizer-mcp · ooples 421 Brotli/SQLite cache + per-session context-delta — inspired our session-dedup engine.
token-savior · Mibayy 1.0k Bash-output compaction + MCP profiles — inspired our compression bail-out discipline and MCP tool-manifest reduction.
token-saver · ppgranger 110 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.5k "Find the ghost tokens" — its offload + recoverable-handle pattern informed our CCR offload thinking.
TokenMizer · Shweta-Mishra-ai 2 A session-graph + cross-turn line-dedup blueprint that informed our session-dedup design.
OmniCompress · jessefreitas 2 Rust columnar-JSON + content-addressed retrieve + cross-message dedup — validated our headroom/ccr/session-dedup engine design and the cache-stable "compressed form is position-independent" invariant.
mcp-compressor · Atlassian Labs 89 MCP tool-schema/description compression — informed our MCP tool-manifest cardinality reduction.
RepoMapper · pdavis68 181 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 59.8k Universal memory layer — its proxy-as-write/read-boundary model shaped our memory architecture.
Letta (MemGPT) · letta-ai 23.6k 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 48 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.3k 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.6k Open database of AI model specs, pricing and capabilities — synced natively into our model catalog.
React Flow / xyflow · xyflow 37.4k The node-based graph library powering our real-time Compression Studio and Combo/Routing Studio.
LangGraph · LangChain 36.1k LangGraph Studio's live workflow-graph visualization inspired our Studios' real-time cascade view.
Langfuse · Langfuse 30.1k 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.2k 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).

🧭 Complementary tools

Project How it composes with OmniRoute
CodeWebChat · robertpiosik Editor-side companion — VS Code + browser extension that autofills 15+ chatbot web UIs with editor context. Owns the free-web-UI rail alongside OmniRoute's API rail; can point its API mode at OmniRoute.

❤️ 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.43 · Node ≥22.22.2 · MIT License · omniroute.online

Languages
TypeScript 94%
JavaScript 5.7%
Shell 0.1%