mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-04 14:02:12 +03:00
Compare commits
1 Commits
fix/releas
...
chore/esli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f72493751b |
103
.env.example
103
.env.example
@@ -240,7 +240,7 @@ PORT=20128
|
||||
# Used by: src/app/api/v1/relay/chat/completions/route.ts
|
||||
# RELAY_IP_PER_MINUTE=30
|
||||
|
||||
# Bundler selection for `npm run dev` and `npm run build`. Set to 0 to fall back to webpack.
|
||||
# Bundler selection for `npm run dev`. Set to 0 to fall back to webpack.
|
||||
# Default is 1 (Turbopack). PR #4092 had forced webpack because earlier
|
||||
# Turbopack 16.2.x panicked on the OmniRoute module graph with "internal error:
|
||||
# entered unreachable code: there must be a path to a root"
|
||||
@@ -250,9 +250,8 @@ PORT=20128
|
||||
# /api/v1/models, /api/mcp) and repeated HMR rebuilds: zero panics. Turbopack
|
||||
# also keeps dev memory far lower on the edit→rebuild loop (HMR rebuild RSS stays
|
||||
# ~flat vs webpack's monotonic growth), which mitigates the dev-server OOM on
|
||||
# this 60+ route app. The production build (scripts/build/build-next-isolated.mjs)
|
||||
# reads the same flag: Turbopack by default, 0 builds with webpack (`npm run
|
||||
# build:contributor` sets it for you).
|
||||
# this 60+ route app. The production build still uses webpack (build pipeline is
|
||||
# unaffected by this dev-only flag).
|
||||
OMNIROUTE_USE_TURBOPACK=1
|
||||
|
||||
# Disable systemd sd_notify (Type=notify / WatchdogSec=) even when running
|
||||
@@ -813,7 +812,6 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# CLI_CRUSH_BIN=crush
|
||||
# CLI_OMP_BIN=omp
|
||||
# CLI_LETTA_BIN=letta
|
||||
# CLI_PRIME_AGENT_BIN=prime-agent
|
||||
# Windsurf has no default binary — set this to enable binary detection for it.
|
||||
# CLI_WINDSURF_BIN=windsurf
|
||||
# CLI_AUGGIE_BIN=auggie
|
||||
@@ -864,17 +862,6 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# Legacy alias for OMNIROUTE_API_KEY.
|
||||
# ROUTER_API_KEY=
|
||||
|
||||
# Days of A2A task history to keep before the daily purge deletes a row.
|
||||
# Used by: src/lib/a2a/taskManager.ts (historyRetentionDays). Unset, non-numeric,
|
||||
# or <= 0 falls back to the default.
|
||||
# OMNIROUTE_A2A_HISTORY_RETENTION_DAYS=30
|
||||
|
||||
# Kill-switch for the A2A memory-hits observability feature (Orchestration Canvas
|
||||
# Fase 2). Set to "0" to skip the memory recall lookup entirely; any other value
|
||||
# (including unset) keeps it enabled.
|
||||
# Used by: src/lib/a2a/taskExecution.ts (collectMemoryHits).
|
||||
# OMNIROUTE_A2A_MEMORY_HITS=1
|
||||
|
||||
# Enable the offline/local Issue Agent recorded-triage endpoint.
|
||||
# Used by: src/app/api/issue-agent/runs/route.ts. Default: disabled.
|
||||
# OMNIROUTE_ISSUE_AGENT_ENABLED=false
|
||||
@@ -1163,6 +1150,11 @@ CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
|
||||
# Trae OAuth token override. Used by: open-sse/executors/trae.ts.
|
||||
# TRAE_TOKEN=
|
||||
|
||||
# ── The Old LLM (theoldllm) ──
|
||||
# Playwright navigation timeout (ms) for the browser-backed token capture.
|
||||
# Used by: open-sse/executors/theoldllm.ts. Default: 30000 (30s).
|
||||
# THEOLDLLM_NAV_TIMEOUT_MS=30000
|
||||
|
||||
# ── Gemini / Antigravity (Google-based) ──
|
||||
# These providers ship public OAuth client_id/secret values embedded in their
|
||||
# public CLIs. Defaults are baked into the code via
|
||||
@@ -1290,7 +1282,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
|
||||
# Update these when providers release new CLI versions to avoid blocks.
|
||||
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.258 (external, cli)"
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"
|
||||
|
||||
# Disable the deterministic tool-name cloak applied on both Anthropic-bound paths
|
||||
# (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) —
|
||||
@@ -1477,15 +1469,17 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# FIRECRAWL_BASE_URL=https://api.firecrawl.dev
|
||||
# FIRECRAWL_TIMEOUT_MS=30000 # Per-request timeout (default: 30000 = 30s)
|
||||
|
||||
# ── Claude TLS transport (Chromium-fingerprinted client) ──
|
||||
# Used by: open-sse/services/claudeTlsClient.ts — native wreq-js request timeout
|
||||
# plus the absolute JS hard-deadline grace when the native request is wedged.
|
||||
# ── Claude TLS sidecar (Chromium-fingerprinted client) ──
|
||||
# Used by: open-sse/services/claudeTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_CLAUDE_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Perplexity TLS transport (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/perplexityTlsClient.ts — native wreq-js request
|
||||
# timeout plus the absolute JS hard-deadline grace.
|
||||
# ── Perplexity TLS sidecar (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/perplexityTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_PPLX_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_PPLX_TLS_GRACE_MS=10000
|
||||
|
||||
@@ -1497,16 +1491,18 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# meta-commentary. Set to 1/true/yes/on to restore the old behavior.
|
||||
# OMNIROUTE_PPLX_SEARCH_HINT=0
|
||||
|
||||
# ── Grok web TLS transport (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/grokTlsClient.ts — native wreq-js request timeout
|
||||
# plus the absolute JS hard-deadline grace.
|
||||
# ── Grok web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/grokTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged.
|
||||
# OMNIROUTE_GROK_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_GROK_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Notion web TLS transport (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/notionTlsClient.ts — native wreq-js request timeout
|
||||
# plus the absolute JS hard-deadline grace. The notion-web executor raises the
|
||||
# native timeout per request to 180000 for long generations.
|
||||
# ── Notion web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/notionTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged. The notion-web executor raises
|
||||
# the wire timeout per-request to 180000 for long generations.
|
||||
# OMNIROUTE_NOTION_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_NOTION_TLS_GRACE_MS=10000
|
||||
|
||||
@@ -1524,15 +1520,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# request into the browser-backed path.
|
||||
# OMNIROUTE_BROWSER_POOL=on
|
||||
# WEB_COOKIE_USE_BROWSER=0
|
||||
# Obscura (https://github.com/h4ckf0r0day/obscura) is the primary headless
|
||||
# engine: a lightweight CDP server the pool and cloudflare-playground connect
|
||||
# to before falling back to Chromium. Unset OBSCURA_BIN to auto-detect from
|
||||
# PATH; set OBSCURA_CDP_ENDPOINT to reuse an already-running Obscura instead
|
||||
# of spawning one; set OBSCURA_PORT to pin the spawned serve port.
|
||||
# Used by: open-sse/services/obscura.ts
|
||||
# OBSCURA_BIN=
|
||||
# OBSCURA_CDP_ENDPOINT=
|
||||
# OBSCURA_PORT=
|
||||
|
||||
# ── Kimi Web (international kimi.ai Connect-RPC) ──
|
||||
# Used by: open-sse/executors/kimi-web.ts. Override the base/chat URLs only if
|
||||
@@ -1841,7 +1828,7 @@ APP_LOG_TO_FILE=true
|
||||
# short alias prefix and the canonical provider prefix for each model (cc/claude-sonnet-4-6
|
||||
# AND claude/claude-sonnet-4-6) so client configs that hardcoded either form keep working —
|
||||
# which roughly doubles the catalog. "alias" emits one id per model; "canonical" emits only
|
||||
# the full provider-id prefix (providers whose alias is already canonical keep their one id).
|
||||
# the full provider-id prefix (and drops providers whose alias is already canonical).
|
||||
# A client can override per request with GET /v1/models?prefix=alias instead.
|
||||
# Also configurable from Dashboard > Settings > Feature Flags.
|
||||
# Used by: src/shared/constants/featureFlagDefinitions.ts, src/app/api/v1/models/catalog.ts
|
||||
@@ -2279,16 +2266,6 @@ APP_LOG_TO_FILE=true
|
||||
# Cursor image-generation wall clock (ms). Default: 210000.
|
||||
# CURSOR_IMG_TIMEOUT_MS=210000
|
||||
|
||||
# UC (uncensored.com) image-generation result-poll cadence + wall clock (ms).
|
||||
# Used by: open-sse/handlers/imageGeneration/providers/ucImage.ts. Defaults: 2000 / 60000.
|
||||
# UC_IMAGE_POLL_INTERVAL_MS=2000
|
||||
# UC_IMAGE_POLL_TIMEOUT_MS=60000
|
||||
|
||||
# UC (uncensored.com) video-generation result-poll cadence + wall clock (ms).
|
||||
# Used by: open-sse/handlers/videoGeneration/providers/ucVideo.ts. Defaults: 3000 / 300000.
|
||||
# UC_VIDEO_POLL_INTERVAL_MS=3000
|
||||
# UC_VIDEO_POLL_TIMEOUT_MS=300000
|
||||
|
||||
# Shared-seat concurrency gate for Cursor image jobs. Default: 2.
|
||||
# CURSOR_IMG_MAX_CONCURRENT=2
|
||||
|
||||
@@ -2420,9 +2397,6 @@ APP_LOG_TO_FILE=true
|
||||
# Bundled Codeium/language-server extension_version, distinct from Desktop.
|
||||
# Must use x.y.z format; invalid/unset values use the bundled default 1.48.2.
|
||||
# DEVIN_DESKTOP_EXTENSION_VERSION=1.48.2
|
||||
# Optional override for the Codeium seat-management API used by Devin CLI quota.
|
||||
# Used by: open-sse/services/usage/devinCli.ts. Default: https://server.codeium.com
|
||||
# DEVIN_SEAT_API_URL=https://server.codeium.com
|
||||
|
||||
# ── Command Code (custom CLI) callback ──
|
||||
# Local port used for OAuth-style callbacks from the Command Code CLI helper.
|
||||
@@ -2492,6 +2466,14 @@ APP_LOG_TO_FILE=true
|
||||
# When enabled, the node authenticates with the API key stored on its connection.
|
||||
# AUDIO_REMOTE_PROVIDER_NODES=false
|
||||
|
||||
# ── 1Proxy egress pool ──
|
||||
# Used by: src/lib/oneproxySync.ts — fetches proxy nodes from the OmniRoute
|
||||
# CrofAI 1Proxy service. Disable, override URL, or tune the import quality.
|
||||
# ONEPROXY_ENABLED=true
|
||||
# ONEPROXY_API_URL=https://1proxy-api.aitradepulse.com
|
||||
# ONEPROXY_MAX_PROXIES=500
|
||||
# ONEPROXY_MIN_QUALITY_THRESHOLD=50
|
||||
|
||||
# ── Free Proxy Pool (auto-sync scheduler) ──
|
||||
# Background refresh of the free-proxy pool. Opt-in, OFF by default (parallels
|
||||
# Hard Rule #20's default-off posture for data-mutating background features).
|
||||
@@ -2910,14 +2892,6 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# PROMPTQL_TOKEN_REFRESH_URL=https://auth.pro.ql.app/ddn/project/token
|
||||
# PROMPTQL_POLL_TIMEOUT_MS=180000
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Kilo Code usage quotas (src/shared/constants/providers/kilocode.ts)
|
||||
# Personal USD balance and Kilo Pass usage lookup. Optional — the default
|
||||
# points at the public Kilo API; override only for a relay/test fixture.
|
||||
# Authentication uses the connection's existing OAuth access token.
|
||||
# Used by: open-sse/services/usage/kilocode.ts
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# KILO_API_URL=https://api.kilo.ai
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# HyperAgent web provider (Unofficial/Experimental — src/shared/constants/providers/web-cookie.ts)
|
||||
# Reverse-engineered session bridge for hyperagent.com. Optional — defaults
|
||||
@@ -2937,12 +2911,7 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# CHATGPT_WEB_CODEX_CDP_URL=http://chatgpt-web-codex-browser:9223
|
||||
# CHATGPT_WEB_CODEX_TUNNEL_ID=tunnel_0123456789abcdef0123456789abcdef
|
||||
# CHATGPT_WEB_CODEX_RUNTIME_KEY=
|
||||
# CHATGPT_WEB_CODEX_CONNECTOR_NAME=OmniRoute Codex v2
|
||||
# CODEX_CHATGPT_WEB_HOME=/var/lib/omniroute/chatgpt-web-codex
|
||||
# CODEX_CHATGPT_WEB_BROWSER_DIAGNOSTICS=0
|
||||
# CODEX_CHATGPT_WEB_LAUNCHER=/absolute/path/to/codex-chatgpt-web
|
||||
# CODEX_CHATGPT_WEB_BUN=/absolute/path/to/bun
|
||||
# CODEX_WEB_GPT_BUN=/absolute/path/to/bun
|
||||
# CHATGPT_WEB_CODEX_CONNECTOR_NAME=OmniRoute Codex
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Browser-login VNC sessions (optional — src/lib/vncSession/manifest.ts)
|
||||
|
||||
48
.github/actions/npm-ci-retry/action.yml
vendored
48
.github/actions/npm-ci-retry/action.yml
vendored
@@ -1,45 +1,9 @@
|
||||
name: npm ci with retry
|
||||
description: >-
|
||||
Install dependencies. Restores node_modules from the Actions cache when the exact
|
||||
lockfile / runner / Node version / postinstall inputs match; otherwise runs npm ci
|
||||
with retries for transient registry/network failures and saves the tree for the
|
||||
next run.
|
||||
inputs:
|
||||
cache:
|
||||
description: Set to "false" to skip the node_modules cache and always run npm ci.
|
||||
required: false
|
||||
default: "true"
|
||||
description: Run npm ci with retries for transient registry/network failures.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve Node version for the cache key
|
||||
id: node
|
||||
shell: bash
|
||||
run: echo "version=$(node --version)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# #8084 D3 (plan 3.8.51 task 5): every job used to pay ~80-90 s of `npm ci` even
|
||||
# with setup-node's npm tarball cache warm — 36 jobs per ci.yml run, ~55 min of
|
||||
# runner time per run just installing. A node_modules cache keyed on EVERYTHING
|
||||
# that shapes the tree lets a hit skip the install entirely.
|
||||
#
|
||||
# No restore-keys on purpose (same rule as the ESLint cache, #11600): a partial
|
||||
# tree from another lockfile / Node / postinstall script is exactly the kind of
|
||||
# silent drift a lockfile-pinned CI must never inherit. Exact key or a full npm ci.
|
||||
#
|
||||
# postinstall (scripts/build/postinstall.mjs + helpers) only mutates node_modules
|
||||
# on a plain install — its dist/ branch is gated on dist/ existing, which never
|
||||
# holds at install time in CI — so the cached tree already carries its effects.
|
||||
- name: Restore node_modules
|
||||
id: node-modules
|
||||
if: inputs.cache == 'true'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node.outputs.version }}-${{ hashFiles('package-lock.json', '.npmrc', 'scripts/build/postinstall.mjs', 'scripts/build/postinstallSupport.mjs', 'scripts/build/colocateOptionals.mjs', 'scripts/build/wreqJsNative.mjs', 'scripts/build/fixPlaywrightAndroid.mjs', 'scripts/build/native-binary-compat.mjs') }}
|
||||
|
||||
- name: npm ci (with retry)
|
||||
if: steps.node-modules.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
- shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -51,8 +15,7 @@ runs:
|
||||
echo "npm ci attempt $attempt/$max_attempts after transient failure"
|
||||
fi
|
||||
|
||||
# --no-audit: `audit:deps` is its own gate; the inline audit only adds latency.
|
||||
if npm ci --no-audit --no-fund; then
|
||||
if npm ci; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -64,8 +27,3 @@ runs:
|
||||
sleep "$delay_seconds"
|
||||
delay_seconds=$((delay_seconds * 2))
|
||||
done
|
||||
|
||||
- name: node_modules restored from cache
|
||||
if: steps.node-modules.outputs.cache-hit == 'true'
|
||||
shell: bash
|
||||
run: echo "node_modules restored from cache (key hit) — npm ci skipped"
|
||||
|
||||
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@@ -58,22 +58,6 @@ updates:
|
||||
# on the VPS — so keep auto-bumps frozen (no update-types = ignore every version).
|
||||
# Migrate it intentionally, not via dependabot (#4050).
|
||||
- dependency-name: "@huggingface/transformers"
|
||||
# onnxruntime-node is the OTHER HALF of the @huggingface/transformers pair frozen
|
||||
# above: the hoisted copy must equal the exact version transformers pins, or npm
|
||||
# nests a second ABI-incompatible native copy (contract test
|
||||
# tests/unit/onnxruntime-single-copy.test.ts, pair established in #9962). A solo
|
||||
# bump can never be correct — it only ever moves together with transformers, in
|
||||
# the same deliberate migration PR. Freezing it keeps the production group PRs
|
||||
# (e.g. #12219) from being born red on the pair contract.
|
||||
- dependency-name: "onnxruntime-node"
|
||||
# eslint-plugin-react-hooks is pinned to 7.0.1 by a contract test
|
||||
# (tests/unit/eslint-react-hooks-version-pinned.test.ts) until the 7.1.1 rule set
|
||||
# is adopted deliberately — that adoption needs a full cold lint run and its own
|
||||
# PR (the #12146 react-hooks migration finished on 2026-09-01, so the path is
|
||||
# open; the bump still must not ride a dependabot group, where it reds the
|
||||
# development group PRs, e.g. #12220). Remove this ignore in the adoption PR
|
||||
# together with the pin test.
|
||||
- dependency-name: "eslint-plugin-react-hooks"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
|
||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -502,11 +502,6 @@ jobs:
|
||||
cache: npm
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- run: node scripts/i18n/check-ui-keys-coverage.mjs --threshold=65
|
||||
# Real-translation ratchet: a leaf copied verbatim from en.json passes key
|
||||
# parity above but is still English to the user (es shipped 55% English).
|
||||
# Advisory in PR-0; flipped to blocking once the backlog is retranslated (PR-4).
|
||||
- name: i18n real-translation ratio (advisory)
|
||||
run: node scripts/i18n/check-translation-ratio.mjs --warn
|
||||
# #8463: a rewritten English value used to leave its 39 translations behind
|
||||
# silently (googleOAuthWarning shipped wrong copy in 39 locales for months).
|
||||
# Key parity above cannot see it — a stale translation counts as covered.
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
queries: security-extended
|
||||
- uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -535,7 +535,7 @@ jobs:
|
||||
- name: Upload Trivy SARIF to Security tab
|
||||
if: needs.prepare.outputs.version != 'main'
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@v4.37.8
|
||||
with:
|
||||
sarif_file: trivy-results.sarif
|
||||
category: trivy-image
|
||||
|
||||
20
.github/workflows/electron-release.yml
vendored
20
.github/workflows/electron-release.yml
vendored
@@ -187,22 +187,6 @@ jobs:
|
||||
env:
|
||||
NPM_CONFIG_LEGACY_PEER_DEPS: true
|
||||
|
||||
# The Linux leg produces x64 + arm64 installers from one x64 runner. npm
|
||||
# deliberately installs only host-compatible optional dependencies, so
|
||||
# hydrateNativeDeps cannot source the arm64 fork unless we fetch the exact
|
||||
# package pinned in package-lock before either build path runs.
|
||||
- name: Install Linux arm64 wreq binding for cross-package
|
||||
if: matrix.platform == 'linux'
|
||||
shell: bash
|
||||
run: |
|
||||
npm install --no-save --ignore-scripts --force --legacy-peer-deps \
|
||||
@wreq-js/binding-linux-arm64-gnu@3.2.0
|
||||
git diff --exit-code -- package.json package-lock.json
|
||||
mkdir -p "$RUNNER_TEMP/omniroute-wreq-verify"
|
||||
DATA_DIR="$RUNNER_TEMP/omniroute-wreq-verify" node --import tsx/esm --test \
|
||||
--test-name-pattern='wreq-js 3.2 manifest pins all nine' \
|
||||
tests/unit/wreq-native-manifest.test.ts
|
||||
|
||||
- name: Sanitize Windows home directory
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
@@ -251,9 +235,9 @@ jobs:
|
||||
# targets, and no unlisted files) byte-for-byte.
|
||||
# hydrate: the bundle was built on ubuntu, so install-machine-forked native
|
||||
# optionals (@img/sharp-*, @img/sharp-libvips-*, @ngrok/ngrok-*,
|
||||
# @wreq-js/binding-*, fsevents) carry linux forks. Replace them with the forks this
|
||||
# fsevents) carry linux forks. Replace them with the forks this
|
||||
# leg's own `npm ci` resolved, then assert every bundled native
|
||||
# (better-sqlite3 prebuilds, wreq-js, onnxruntime)
|
||||
# (koffi triplets, better-sqlite3 prebuilds, wreq-js, onnxruntime)
|
||||
# can service this leg's platform/arch before packaging starts.
|
||||
run: |
|
||||
node scripts/build/standaloneBundle.mjs restore --archive web-bundle.tar.gz
|
||||
|
||||
5867
.i18n-state.json
5867
.i18n-state.json
File diff suppressed because it is too large
Load Diff
10
.trivyignore
10
.trivyignore
@@ -18,3 +18,13 @@
|
||||
#
|
||||
# Keep this list SHORT and reviewed every release. Prefer fixing (rebuild on a
|
||||
# patched base / bump the dep) over suppressing. Stale entries are debt.
|
||||
#
|
||||
# CVE-2025-68121 — Go stdlib crypto/tls (session-resumption certificate validation)
|
||||
# inside the PREBUILT bogdanfinn/tls-client v1.15.1 .so that tls-client-node's
|
||||
# postinstall downloads (built with go 1.24.1; fixed in 1.24.13). No upstream
|
||||
# rebuild exists (v1.15.1 is still the latest release) and nothing in this repo
|
||||
# can bump it. The binary is only loaded by the browser-TLS web-provider
|
||||
# executors (claude-web / grok-web / lmarena / perplexity-web / notion-web),
|
||||
# whose handshakes go through utls. Tracking issue: #12084. Revisit at the next
|
||||
# tls-client release or base-image bump and BEFORE the v3.8.51 tag (2026-09-15).
|
||||
CVE-2025-68121
|
||||
|
||||
@@ -3477,7 +3477,7 @@ export function createOmniRouteProviderHook(
|
||||
|
||||
// ── Combo LCD across nested combo-refs (T-NN) ───────────────────────
|
||||
// Combos can nest other combos via `kind: "combo-ref"` members
|
||||
// (e.g. MASTER-LIGHT contains LEGACY, KIRO, Opecode Zen FREE). The
|
||||
// (e.g. MASTER-LIGHT contains OldLLM, KIRO, Opecode Zen FREE). The
|
||||
// nested combo's own `limit.context` is computed below in this same
|
||||
// loop, so we need a fixpoint iteration: if a combo-ref points at a
|
||||
// combo not yet processed, defer this combo and try again after the
|
||||
@@ -4495,7 +4495,7 @@ export function buildStaticProviderEntry(
|
||||
// ── Combo LCD across nested combo-refs (T-NN mirror) ─────────────────
|
||||
// Mirror of the dynamic-catalog fixpoint iteration: combos can nest
|
||||
// other combos via `kind: "combo-ref"` members (e.g. MASTER-LIGHT
|
||||
// contains LEGACY, KIRO, Opecode Zen FREE). The nested combo's own
|
||||
// contains OldLLM, KIRO, Opecode Zen FREE). The nested combo's own
|
||||
// capabilities and limits are computed in this same loop, so we need
|
||||
// a fixpoint pass: if a combo-ref points at a combo not yet processed,
|
||||
// defer this combo and try again after the sibling combos catch up.
|
||||
|
||||
@@ -641,13 +641,13 @@ test("models(): combos fetcher receives the resolved baseURL + apiKey", async ()
|
||||
|
||||
test("models(): nested combo-ref context is the min of nested + raw members", async () => {
|
||||
// Top-level combo MASTER-LIGHT has 1 raw model (claude-primary, 200k)
|
||||
// and 2 combo-refs: LEGACY (8k member) and KIRO (32k member). The OLD
|
||||
// and 2 combo-refs: OldLLM (8k member) and KIRO (32k member). The OLD
|
||||
// plugin would advertise 200k (only the raw model); the fix should
|
||||
// make it advertise 8k (the bottleneck across the member graph).
|
||||
const modelsFetcher = stubModelsFetcher([
|
||||
MODEL_PRIMARY,
|
||||
{
|
||||
id: "legacy-member-1",
|
||||
id: "oldllm-member-1",
|
||||
context_length: 8_000,
|
||||
max_output_tokens: 4_000,
|
||||
capabilities: {
|
||||
@@ -677,9 +677,9 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
|
||||
]);
|
||||
const combosFetcher = stubCombosFetcher([
|
||||
{
|
||||
id: "legacy",
|
||||
name: "LEGACY",
|
||||
models: [{ id: "s1", kind: "model", model: "legacy-member-1", weight: 100 }],
|
||||
id: "oldllm",
|
||||
name: "OldLLM",
|
||||
models: [{ id: "s1", kind: "model", model: "oldllm-member-1", weight: 100 }],
|
||||
},
|
||||
{
|
||||
id: "kiro",
|
||||
@@ -691,7 +691,7 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
|
||||
name: "MASTER-LIGHT",
|
||||
models: [
|
||||
{ id: "r1", kind: "model", model: "claude-primary", weight: 50 },
|
||||
{ id: "r2", kind: "combo-ref", comboName: "LEGACY", weight: 25 },
|
||||
{ id: "r2", kind: "combo-ref", comboName: "OldLLM", weight: 25 },
|
||||
{ id: "r3", kind: "combo-ref", comboName: "KIRO", weight: 25 },
|
||||
],
|
||||
},
|
||||
@@ -706,6 +706,6 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
|
||||
assert.equal(
|
||||
masterLight.limit.context,
|
||||
8_000,
|
||||
`expected 8_000 (LEGACY bottleneck), got ${masterLight.limit.context}`
|
||||
`expected 8_000 (OldLLM bottleneck), got ${masterLight.limit.context}`
|
||||
);
|
||||
});
|
||||
|
||||
75
AGENTS.md
75
AGENTS.md
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 356 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 352 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -56,9 +56,9 @@ Repository map and Reference Documentation sections below.
|
||||
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
|
||||
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (169 migrations) |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (167 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 110 tools (45 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
|
||||
| MCP Server | `open-sse/mcp-server/` | 110 tools (44 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
| Skills | `src/lib/skills/` | Extensible skill framework |
|
||||
| Memory | `src/lib/memory/` | Persistent conversational memory |
|
||||
@@ -83,7 +83,7 @@ Client → /v1/chat/completions (Next.js route)
|
||||
|
||||
API routes follow a consistent pattern: `Route → CORS preflight → Zod body validation → Optional auth (extractApiKey/isValidApiKey) → API key policy enforcement → Handler delegation (open-sse)`. No global Next.js middleware — interception is route-specific.
|
||||
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 16-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. The `fusion` strategy is the exception: it fans out to a panel of models in parallel, then a judge model synthesizes one final answer (`open-sse/services/fusion.ts`). See `docs/routing/AUTO-COMBO.md` for the 15-factor Auto-Combo scoring + the full strategy table and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
|
||||
|
||||
---
|
||||
|
||||
@@ -110,36 +110,26 @@ upstream/service level, so one unhealthy provider does not slow down every reque
|
||||
- Shared wrappers: `open-sse/services/accountFallback.ts`
|
||||
- Persisted state table: `domain_circuit_breakers`
|
||||
|
||||
**States** (4 — `src/shared/utils/circuitBreaker.ts`):
|
||||
**States**:
|
||||
|
||||
- `CLOSED`: normal traffic is allowed.
|
||||
- `DEGRADED`: early-warning band — failures crossed the degradation threshold but not the
|
||||
breaker threshold yet; traffic still flows, dashboards show the warning.
|
||||
- `OPEN`: provider is temporarily blocked; callers get a provider-circuit-open response
|
||||
or combo routing skips to another target.
|
||||
- `HALF_OPEN`: reset timeout has elapsed; allow a probe request. Success closes the
|
||||
breaker, failure opens it again.
|
||||
|
||||
**Defaults** (`open-sse/config/constants.ts` → `PROVIDER_PROFILES`, consumed via
|
||||
`DEFAULT_RESILIENCE_SETTINGS.providerBreaker` in `src/lib/resilience/settings.ts` →
|
||||
`getCircuitBreaker(provider, …)` in `src/sse/handlers/chatHelpers.ts`). The whole-provider
|
||||
breaker runs on `circuitBreakerThreshold` / `circuitBreakerReset`:
|
||||
**Defaults** (`open-sse/config/constants.ts` → `PROVIDER_PROFILES`). Two thresholds live side by
|
||||
side — do not confuse them:
|
||||
|
||||
| Profile | degrades at | opens at (`circuitBreakerThreshold`) | reset (`circuitBreakerReset`) |
|
||||
| ------- | ----------: | -----------------------------------: | ----------------------------: |
|
||||
| OAuth | `5` | `8` | `60s` |
|
||||
| API key | `7` | `12` | `30s` |
|
||||
| Local | (derived) | `2` | `15s` |
|
||||
| Profile | `providerFailureThreshold` (whole provider) | `providerCooldownMs` | `circuitBreakerThreshold` (one connection) | `circuitBreakerReset` |
|
||||
| ------- | ------------------------------------------: | -------------------: | -----------------------------------------: | --------------------: |
|
||||
| OAuth | `10` | `5min` | `8` | `60s` |
|
||||
| API key | `15` | `10min` | `12` | `30s` |
|
||||
| Local | `2` | `1min` | `2` | `15s` |
|
||||
|
||||
`PROVIDER_PROFILES` also defines `providerFailureThreshold` (10/15/2),
|
||||
`providerFailureWindowMs` (15/30/5 min) and `providerCooldownMs` (5/10/1 min): these power the
|
||||
**window gate of the opt-in global Provider Cooldown** (`PROVIDER_COOLDOWN_ENABLED`, default
|
||||
off) — a provider-level entry in `open-sse/services/providerCooldownTracker.ts` only counts as
|
||||
cooling after `providerFailureThreshold` failures inside `providerFailureWindowMs`, and then
|
||||
cools for `providerCooldownMs`. They are NOT the live breaker's thresholds — do not tune them
|
||||
expecting breaker behavior. Every default is overridable through the
|
||||
`OMNIROUTE_PROVIDER_BREAKER_*` and `OMNIROUTE_CIRCUIT_BREAKER_*` env vars; the
|
||||
runtime-accurate reference table lives in `docs/architecture/RESILIENCE_GUIDE.md`.
|
||||
The provider-level thresholds were scaled up for deployments with 500+ connections (OAuth was
|
||||
`3`, API key was `5`); every default is overridable through the `OMNIROUTE_PROVIDER_BREAKER_*`
|
||||
and `OMNIROUTE_CIRCUIT_BREAKER_*` env vars.
|
||||
|
||||
Only provider-level failure statuses should trip the provider breaker:
|
||||
|
||||
@@ -252,7 +242,7 @@ Read the nearest `AGENTS.md` and the linked deep-dive before making a non-trivia
|
||||
| Streaming request handling | `open-sse/handlers/` | [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) |
|
||||
| Provider execution and translation | `open-sse/executors/`, `open-sse/translator/` | [`docs/architecture/CODEBASE_DOCUMENTATION.md`](docs/architecture/CODEBASE_DOCUMENTATION.md) |
|
||||
| Routing and resilience | `open-sse/services/` | [`open-sse/services/AGENTS.md`](open-sse/services/AGENTS.md), [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md) |
|
||||
| Database and migrations | `src/lib/db/`, `src/lib/db/migrations/` | [`src/lib/db/AGENTS.md`](src/lib/db/AGENTS.md) |
|
||||
| Database and migrations | `src/lib/db/`, `db/migrations/` | [`src/lib/db/AGENTS.md`](src/lib/db/AGENTS.md) |
|
||||
| Domain policy | `src/domain/` | [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) |
|
||||
| MCP and A2A | `open-sse/mcp-server/`, `src/lib/a2a/` | [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md), [`docs/frameworks/A2A-SERVER.md`](docs/frameworks/A2A-SERVER.md) |
|
||||
| Agent features | `src/lib/{acp,memory,skills,cloudAgent}/` | [`docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`](docs/frameworks/AGENT_PROTOCOLS_GUIDE.md), [`docs/frameworks/SKILLS.md`](docs/frameworks/SKILLS.md) |
|
||||
@@ -264,13 +254,13 @@ Read the nearest `AGENTS.md` and the linked deep-dive before making a non-trivia
|
||||
## File placement & repo-root hygiene
|
||||
|
||||
- **Test files**: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the `tests/` directory (e.g., `tests/unit/`, `tests/integration/`). NEVER create test files in the project root (`/`).
|
||||
- **Scripts and utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside one of the `scripts/` subfolders (`build/`, `dev/`, `check/`, `docs/`, `i18n/`, `ad-hoc/`, `quality/`, `release/`, `ci/`, `ops/`, `perf/`, `research/`, `sre/`, `vps/`, `homolog/`, `packs/`, `skills/`, `test/`, `cli/`, `compression/`, `compression-eval/`, `devin-bridge/`, `docker/`, `features/`, `router-eval/`). One-shot or experimental code goes under `scripts/ad-hoc/`. NEVER dump loose scripts in the project root (`/`) or the top-level `scripts/` folder.
|
||||
- **Scripts and utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside one of the `scripts/` subfolders (`build/`, `dev/`, `check/`, `docs/`, `i18n/`, `ad-hoc/`, `quality/`, `release/`, `ci/`, `ops/`, `perf/`, `research/`, `sre/`, `vps/`, `homolog/`, `raycast/`, `skills/`, `test/`, `cli/`, `compression/`, `compression-eval/`, `devin-bridge/`, `docker/`, `features/`, `router-eval/`). One-shot or experimental code goes under `scripts/ad-hoc/`. NEVER dump loose scripts in the project root (`/`) or the top-level `scripts/` folder.
|
||||
|
||||
**The project root MUST ONLY contain:**
|
||||
|
||||
- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, `tsconfig*.json`, `playwright.config.ts`, `prettier.config.mjs`, `postcss.config.mjs`, `sonar-project.properties`, `fly.toml`, `docker-compose*.yml`, `Dockerfile`)
|
||||
- Dependency files (`package.json`, `package-lock.json`)
|
||||
- Documentation files (`README.md`, `CHANGELOG.md`, `ROADMAP.md`, `LICENSE`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `llm.txt`)
|
||||
- Documentation files (`README.md`, `CHANGELOG.md`, `ROADMAP.md`, `LICENSE`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `llm.txt`, `Tuto_Qdrant.md`)
|
||||
- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`, `.npmignore`, `.npmrc`, `.node-version`, `.nvmrc`, `.env.example`)
|
||||
|
||||
When creating _any_ validation tests or one-off logic scripts, default to `scripts/ad-hoc/` or `tests/unit/` according to your goals. Do not pollute the `/` root context.
|
||||
@@ -343,7 +333,6 @@ Documentation must describe verified behavior, not plausible behavior.
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
0. Check `docs/reference/REMOVED_PROVIDERS.md` first — providers removed at their operator's request must never be reintroduced (guarded by `tests/unit/removed-providers-blocklist.test.ts`)
|
||||
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load)
|
||||
2. Add executor in `open-sse/executors/` if custom logic needed (extend `BaseExecutor`)
|
||||
3. Add translator in `open-sse/translator/` if non-OpenAI format
|
||||
@@ -372,11 +361,11 @@ Documentation must describe verified behavior, not plausible behavior.
|
||||
1. Add tool definition in `open-sse/mcp-server/tools/` with Zod input schema + async handler
|
||||
2. Register in tool set (wired by `createMcpServer()`)
|
||||
3. Assign to appropriate scope(s)
|
||||
4. Write tests (tool invocation logged to the `mcp_tool_audit` table)
|
||||
4. Write tests (tool invocation logged to `mcp_audit` table)
|
||||
|
||||
### Adding a New A2A Skill
|
||||
|
||||
1. Create skill in `src/lib/a2a/skills/` (6 already exist: smart-routing, quota-management, provider-discovery, cost-analysis, health-report, list-capabilities)
|
||||
1. Create skill in `src/lib/a2a/skills/` (5 already exist: smart-routing, quota-management, provider-discovery, cost-analysis, health-report)
|
||||
2. Skill receives task context (messages, metadata) → returns structured result
|
||||
3. Register in `A2A_SKILL_HANDLERS` in `src/lib/a2a/taskExecution.ts`
|
||||
4. Expose in `src/app/.well-known/agent.json/route.ts` (Agent Card)
|
||||
@@ -385,7 +374,7 @@ Documentation must describe verified behavior, not plausible behavior.
|
||||
|
||||
### Adding a New Cloud Agent
|
||||
|
||||
1. Create agent class in `src/lib/cloudAgent/agents/` extending `CloudAgentBase` (4 already exist: codex-cloud, devin, jules, cursor-cloud)
|
||||
1. Create agent class in `src/lib/cloudAgent/agents/` extending `CloudAgentBase` (3 already exist: codex-cloud, devin, jules)
|
||||
2. Implement `createTask`, `getStatus`, `approvePlan`, `sendMessage`, `listSources`
|
||||
3. Register in `src/lib/cloudAgent/registry.ts`
|
||||
4. Add OAuth/credentials handling if needed (`src/lib/oauth/providers/`)
|
||||
@@ -396,7 +385,7 @@ Documentation must describe verified behavior, not plausible behavior.
|
||||
1. Create installer in `src/lib/services/installers/{name}.ts` modeled on `ninerouter.ts` (use `runNpm` from `installers/utils.ts` — no shell interpolation, hard rule #13).
|
||||
2. Register the service in `src/lib/services/bootstrap.ts` (add to `SERVICES[]` array and extend `buildSpawnArgsFactory()`).
|
||||
3. Add a DB seed row for the new service in `src/lib/db/migrations/` (`version_manager` table, `status='not_installed'`, `auto_start=0`).
|
||||
4. Create 8 API endpoints under `src/app/api/services/{name}/` (`_lib.ts`, `install`, `start`, `stop`, `restart`, `update`, `status`, `auto-start`, `auto-restart-adopted`). All delegate errors through `createErrorResponse()`. The shared `logs` endpoint is already wired via `[name]/logs/route.ts`.
|
||||
4. Create 7 API endpoints under `src/app/api/services/{name}/` (`_lib.ts`, `install`, `start`, `stop`, `restart`, `update`, `status`, `auto-start`). All delegate errors through `createErrorResponse()`. The shared `logs` endpoint is already wired via `[name]/logs/route.ts`.
|
||||
5. Verify `/api/services/` is in `LOCAL_ONLY_API_PREFIXES` in `src/server/authz/routeGuard.ts`; add a test asserting `isLocalOnlyPath()` returns `true` for the new prefix if you add one (hard rule #17).
|
||||
6. Add a UI tab in `src/app/(dashboard)/dashboard/providers/services/tabs/` reusing `ServiceStatusCard`, `ServiceLifecycleButtons`, `ServiceLogsPanel`.
|
||||
7. Document in `docs/frameworks/EMBEDDED-SERVICES.md` (update §1 service table + §4 API reference) and `docs/openapi.yaml`.
|
||||
@@ -423,7 +412,7 @@ For any non-trivial change, read the matching deep-dive first:
|
||||
| Repo navigation | `docs/architecture/REPOSITORY_MAP.md` |
|
||||
| Architecture | `docs/architecture/ARCHITECTURE.md` |
|
||||
| Engineering reference | `docs/architecture/CODEBASE_DOCUMENTATION.md` |
|
||||
| Auto-Combo (16-factor scoring, 19 strategies) | `docs/routing/AUTO-COMBO.md` |
|
||||
| Auto-Combo (15-factor scoring, 19 strategies) | `docs/routing/AUTO-COMBO.md` |
|
||||
| Resilience (3 mechanisms) | `docs/architecture/RESILIENCE_GUIDE.md` |
|
||||
| Reasoning replay | `docs/routing/REASONING_REPLAY.md` |
|
||||
| Skills framework | `docs/frameworks/SKILLS.md` |
|
||||
@@ -449,7 +438,7 @@ For any non-trivial change, read the matching deep-dive first:
|
||||
| VS Code Copilot Chat (OmniCopilot extension) | `docs/guides/VSCODE-COPILOT.md` |
|
||||
| Release flow | `docs/ops/RELEASE_CHECKLIST.md` |
|
||||
| Embedded services | `docs/frameworks/EMBEDDED-SERVICES.md` |
|
||||
| Quality gates (~90 scripts, allowlist policy) | `docs/architecture/QUALITY_GATES.md` |
|
||||
| Quality gates (~80 scripts, allowlist policy) | `docs/architecture/QUALITY_GATES.md` |
|
||||
|
||||
---
|
||||
|
||||
@@ -495,12 +484,6 @@ Why this matters: fixing bug A while opening bug B is worse than not fixing at a
|
||||
pipeline, and A2A skills.
|
||||
- Do not close a contributor pull request after using its code; merge it through GitHub so
|
||||
the contributor receives credit.
|
||||
- **Never merge a PR that touches an agent-instruction surface without explicit operator
|
||||
approval** — `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `llm.txt` (+ mirrors) and
|
||||
`skills/**/SKILL.md` are executed as authority by every AI session; a merged instruction
|
||||
compromises every future agent run. Check with `gh pr diff <N> --name-only` before any
|
||||
merge. Incident record: PR #11770 (2026-09-01) told agents to execute a third-party
|
||||
setup script and was swept in by a merge campaign; reverted in #12249.
|
||||
|
||||
---
|
||||
|
||||
@@ -646,8 +629,8 @@ focused checks, and use a Conventional Commit message (for example, `docs: slim
|
||||
|
||||
## Environment
|
||||
|
||||
- **Runtime**: Node.js ≥22.22.2 <23 || ≥24.0.0 <27, ES Modules. This is the **only supported** runtime for the published `omniroute` CLI, the server, and the test suites (`node:test` + vitest) — `engines.node` is authoritative and end users never need Bun. A **best-effort `bun:sqlite` compatibility path** exists so a global Bun install (`bun install -g omniroute`) can start without `better-sqlite3` (driver adapter + Bun-aware process spawning); it is **not** a supported runtime — no support guarantees — and every Bun-specific runtime change MUST preserve the Node driver/fallback chain and ship a Bun test (`test:bun:db`) or an explicit reason why the path is Node-only.
|
||||
- **Bun (build/dev script runner + compatibility smoke only)**: Bun `1.4.0` is pinned as an **exact devDependency** (provisioned through the existing `npm ci` via the lockfile's `@oven/bun-*` platform binaries — no `setup-bun`/ad-hoc install). It is used **only** to execute a small, allow-listed set of TypeScript **gate/generator scripts** (replacing `node --import tsx` for startup speed): the CI checks `check:provider-consistency`, `check:compression-budget`, `check:known-symbols`, and the non-CI `gen:provider-reference`, `bench:compression` — plus the focused `test:bun:db` compatibility smoke suite for the best-effort `bun:sqlite` path. **Do NOT** widen Bun to `npm install`, the build (`build:cli*`), `check:pack-artifact`, the supported published runtime, or the main test runners — those stay on Node. Any new Bun-invoking gate/generator script must be validated byte-identical against its `node --import tsx` output first. After pulling the lockfile change, run `npm install` so `bun` resolves locally (a stale `node_modules` will fail those scripts with `bun: not found`).
|
||||
- **Runtime**: Node.js ≥22.0.0 <23 || ≥24.0.0 <27, ES Modules. This is the **only supported** runtime for the published `omniroute` CLI, the server, and the test suites (`node:test` + vitest) — `engines.node` is authoritative and end users never need Bun. A **best-effort `bun:sqlite` compatibility path** exists so a global Bun install (`bun install -g omniroute`) can start without `better-sqlite3` (driver adapter + Bun-aware process spawning); it is **not** a supported runtime — no support guarantees — and every Bun-specific runtime change MUST preserve the Node driver/fallback chain and ship a Bun test (`test:bun:db`) or an explicit reason why the path is Node-only.
|
||||
- **Bun (build/dev script runner + compatibility smoke only)**: Bun `1.3.14` is pinned as an **exact devDependency** (provisioned through the existing `npm ci` via the lockfile's `@oven/bun-*` platform binaries — no `setup-bun`/ad-hoc install). It is used **only** to execute a small, allow-listed set of TypeScript **gate/generator scripts** (replacing `node --import tsx` for startup speed): the CI checks `check:provider-consistency`, `check:compression-budget`, `check:known-symbols`, and the non-CI `gen:provider-reference`, `bench:compression` — plus the focused `test:bun:db` compatibility smoke suite for the best-effort `bun:sqlite` path. **Do NOT** widen Bun to `npm install`, the build (`build:cli*`), `check:pack-artifact`, the supported published runtime, or the main test runners — those stay on Node. Any new Bun-invoking gate/generator script must be validated byte-identical against its `node --import tsx` output first. After pulling the lockfile change, run `npm install` so `bun` resolves locally (a stale `node_modules` will fail those scripts with `bun: not found`).
|
||||
- **TypeScript**: 6.0+, target ES2022, module esnext, resolution bundler
|
||||
- **Path aliases**: `@/*` → `src/`, `@omniroute/open-sse` → `open-sse/`, `@omniroute/open-sse/*` → `open-sse/*`
|
||||
- **Default port**: 20128 (API + dashboard on same port)
|
||||
@@ -659,12 +642,12 @@ focused checks, and use a Conventional Commit message (for example, `docs: slim
|
||||
|
||||
## Quality Gates & Ratchets
|
||||
|
||||
OmniRoute has **~90 quality-gate scripts** (`scripts/check/` + `scripts/quality/`) wired
|
||||
OmniRoute has **~80 quality-gate scripts** (`scripts/check/` + `scripts/quality/`) wired
|
||||
across **9 gate-running jobs** in `.github/workflows/ci.yml` (`lint`, `quality-gate`,
|
||||
`quality-extended`, `docs-sync-strict`, `i18n-ui-coverage`, `i18n`, `pr-test-policy`,
|
||||
`test-vitest`, `sonarqube`), plus the `quality.yml` fast-gates job (PR→`release/**`) and
|
||||
5 quality nightly workflows (`nightly-property`, `nightly-resilience`,
|
||||
`nightly-llm-security`, `nightly-mutation`, `nightly-schemathesis`). Full inventory, per-job breakdown, and operational
|
||||
3 nightly workflows (`nightly-property`, `nightly-resilience`, `nightly-llm-security`;
|
||||
`nightly-mutation` once merged). Full inventory, per-job breakdown, and operational
|
||||
procedures are in [`docs/architecture/QUALITY_GATES.md`](docs/architecture/QUALITY_GATES.md).
|
||||
|
||||
**Quick reference:**
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -4,7 +4,6 @@
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **feat(dashboard):** adaptive context-budget dial on the compression settings panel — mode (`off` / `floor` / `replace-autotrigger`) and policy (`reserve-output` / `percentage` / `absolute`) persist via `PUT /api/settings/compression` `contextBudget`. Completes the dashboard half of #7005 (API + DB already shipped in #7183).
|
||||
- **feat(sse): STRICT_ZERO_COST** — opt-in, off-by-default `freeAccessPolicy: "strict"` setting
|
||||
that hard-verifies every auto-combo candidate against live quota state and per-connection
|
||||
economic safety before it can be dispatched, going beyond `hidePaidModels`'s static catalog
|
||||
@@ -98,10 +97,6 @@ _Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). B
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI,
|
||||
Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw
|
||||
failures for internal classification and keeping client disconnects out of provider failure state.
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
@@ -721,6 +716,7 @@ _By commits in `ed2db6cb19..v3.8.50`, author identities consolidated via `.mailm
|
||||
- **[TS7] fix(types): normalize DuckDuckGo request messages** ([#9847](https://github.com/diegosouzapw/OmniRoute/pull/9847), original [#9797](https://github.com/diegosouzapw/OmniRoute/pull/9797)) — thanks @backryun
|
||||
- **[TS7] fix(types): expose SQLite transaction state** ([#9848](https://github.com/diegosouzapw/OmniRoute/pull/9848), original [#9796](https://github.com/diegosouzapw/OmniRoute/pull/9796)) — thanks @backryun
|
||||
- **[TS7] fix(types): validate default executor pool config** ([#9849](https://github.com/diegosouzapw/OmniRoute/pull/9849), original [#9795](https://github.com/diegosouzapw/OmniRoute/pull/9795)) — thanks @backryun
|
||||
- **[TS7] fix(types): preserve The Old LLM proxy contracts** ([#9850](https://github.com/diegosouzapw/OmniRoute/pull/9850), original [#9793](https://github.com/diegosouzapw/OmniRoute/pull/9793)) — thanks @backryun
|
||||
- **[TS7] fix(types): normalize Gemini Business credentials** ([#9851](https://github.com/diegosouzapw/OmniRoute/pull/9851), original [#9792](https://github.com/diegosouzapw/OmniRoute/pull/9792)) — thanks @backryun
|
||||
- **[TS7] fix(types): preserve Claude thinking body contracts** ([#9852](https://github.com/diegosouzapw/OmniRoute/pull/9852), original [#9791](https://github.com/diegosouzapw/OmniRoute/pull/9791)) — thanks @backryun
|
||||
- **fix(response): strip internal reasoning placeholder from all reasoning fields** ([#9853](https://github.com/diegosouzapw/OmniRoute/pull/9853), original [#9790](https://github.com/diegosouzapw/OmniRoute/pull/9790)) — thanks @adevwithpurpose
|
||||
@@ -892,7 +888,7 @@ _By commits in `ed2db6cb19..v3.8.50`, author identities consolidated via `.mailm
|
||||
- **fix(combo):** defer the known-context-overflow hard rejection for compressible requests so compression runs before the final context gate, instead of a raw-body estimate 400'ing generic Responses clients targeting a large model before OmniRoute can shrink it ([#10225](https://github.com/diegosouzapw/OmniRoute/issues/10225))
|
||||
- **fix(api):** deleting a manually-added custom model no longer tombstones a provider-synced model that shares its id. `DELETE /api/provider-models` is addressed by `provider` + `model` alone, so when both a custom row and a synced row existed for one id it removed both and wrote `isDeleted:true`. `replaceSyncedAvailableModelsForConnection` then filtered that id out of every subsequent re-import, so the provider could never resync — model sync kept reporting `added: N` while the catalog stayed empty and `/v1/models` never listed the model again, even though routing to it still worked. The custom row is now removed first and its presence is treated as the operator's intent, leaving the synced sibling importable; a synced-only delete still tombstones as before (#3199, #3782 unaffected) ([#10228](https://github.com/diegosouzapw/OmniRoute/pull/10228)) — thanks @Neuron-Mr-White
|
||||
- **Audio Bridge:** fix production transcription self-loop uploads so real audio reaches the configured STT provider instead of falling back to an unavailable-provider stub ([#10229](https://github.com/diegosouzapw/OmniRoute/pull/10229)).
|
||||
- **fix(api):** DeepSeek V4's native `max` reasoning tier is now reachable. DeepSeek accepts `reasoning_effort` `low`/`high`/`max` and maps `medium`/`xhigh` down to `high`, while OmniRoute's canonical vocabulary collapses `max` onto `xhigh` — so `{"effort":"max"}` silently resolved to `high` and the catalog never advertised a `max` tier (or its `<model>-max` variant). Following the existing `extendCodexGpt56EffortValues` precedent, the native tier is now preserved for `deepseek`/`ds` V4 models only; the global effort vocabulary is unchanged, routed namespaces (`openrouter/deepseek/…`, `oc/deepseek-v4-flash-free`) keep the canonical behavior, and an explicit client `reasoning_effort` still wins ([#10230](https://github.com/diegosouzapw/OmniRoute/pull/10230)) — thanks @Neuron-Mr-White
|
||||
- **fix(api):** DeepSeek V4's native `max` reasoning tier is now reachable. DeepSeek accepts `reasoning_effort` `low`/`high`/`max` and maps `medium`/`xhigh` down to `high`, while OmniRoute's canonical vocabulary collapses `max` onto `xhigh` — so `{"effort":"max"}` silently resolved to `high` and the catalog never advertised a `max` tier (or its `<model>-max` variant). Following the existing `extendCodexGpt56EffortValues` precedent, the native tier is now preserved for `deepseek`/`ds` V4 models only; the global effort vocabulary is unchanged, routed namespaces (`openrouter/deepseek/…`, `tllm/deepseek_v4`, `oc/deepseek-v4-flash-free`) keep the canonical behavior, and an explicit client `reasoning_effort` still wins ([#10230](https://github.com/diegosouzapw/OmniRoute/pull/10230)) — thanks @Neuron-Mr-White
|
||||
- **fix(providers):** FreeAIAPIKey now targets `api.freeaiapikey.com`, the host upstream names in its `410 endpoint_moved` response — every request through the provider was failing — and its catalog is resynced to the 10 models the live `/v1/models` actually serves ([#10233](https://github.com/diegosouzapw/OmniRoute/pull/10233))
|
||||
- **fix(providers):** MonsterAPI's deprecation now actually applies — the flag was written as `isDeprecated`, a key no consumer or schema reads, so the provider kept rendering as healthy in the dashboard, the onboarding wizard and the generated provider reference ([#10234](https://github.com/diegosouzapw/OmniRoute/pull/10234))
|
||||
- **fix(cliproxy):** read platform/arch at runtime via `os.platform()`/`os.arch()` in `binaryManager` so the embedded installer selects the Windows/ARM assets even when the release bundle is built on a Linux runner (fixes #10244)
|
||||
@@ -3004,6 +3000,7 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
|
||||
- Stream model health probes for slow providers ([#7377](https://github.com/diegosouzapw/OmniRoute/pull/7377)) — thanks @JxnLexn
|
||||
- Refresh NVIDIA free metadata and detect catalog drift ([#7378](https://github.com/diegosouzapw/OmniRoute/pull/7378)) — thanks @JxnLexn
|
||||
- Reject invalid output token budgets ([#7379](https://github.com/diegosouzapw/OmniRoute/pull/7379)) — thanks @JxnLexn
|
||||
- Honor provider proxies for The Old LLM Vercel blocks ([#7380](https://github.com/diegosouzapw/OmniRoute/pull/7380)) — thanks @JxnLexn
|
||||
- Restore proxy navigation and sidebar accordion state ([#7381](https://github.com/diegosouzapw/OmniRoute/pull/7381)) — thanks @JxnLexn
|
||||
- Expose proxy controls for no-auth providers ([#7419](https://github.com/diegosouzapw/OmniRoute/pull/7419)) — thanks @JxnLexn
|
||||
- Add reasoning-based model and effort routing ([#7607](https://github.com/diegosouzapw/OmniRoute/pull/7607)) — thanks @JxnLexn
|
||||
@@ -3445,7 +3442,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
||||
- **fix(cli):** `omniroute serve` no longer hangs silently on a readiness timeout ([#6321](https://github.com/diegosouzapw/OmniRoute/issues/6321)) — the child server's stdout was piped to `"ignore"` whenever `--log`/`OMNIROUTE_SHOW_LOG` wasn't set (the default), discarding any debug output, and `runWithSupervisor`'s `waitForServer(...).then((up) => { if (up) {...} })` had no `else` branch, so a boot that never became ready produced zero further output after "⏳ Starting server...". Stdout is now buffered alongside stderr (`ServerSupervisor.getRecentLog()`), and a timeout prints a clear diagnostic plus the buffered output instead of staying silent. Does not by itself explain why boot never completes on a given machine — see the issue for further reproduction. Regression guard: `tests/unit/cli-serve-readiness-timeout-6321.test.ts`.
|
||||
- **fix(pricing):** Pricing Sync dashboard no longer stuck on "Next Sync: Never" / "Synced Models: 0" ([#6325](https://github.com/diegosouzapw/OmniRoute/issues/6325)) — `pricingSync.ts` kept sync state (`lastSyncTime`, `lastSyncModelCount`) in module-level vars, but the background periodic sync (`instrumentation-node.ts`) and the dashboard status route (`/api/pricing/sync`) each import the module from separate Next.js standalone webpack chunks, giving each its own independent state; `getSyncStatus()` read the (empty) API-route instance's vars. Sync status is now additionally persisted to a new `pricing_sync_status` `key_value` namespace and `getSyncStatus()` falls back to it when the local module instance never ran a sync itself. Regression guard: `tests/unit/pricing-sync-cross-instance.test.ts`.
|
||||
- **fix(api):** stop spuriously 403-ing "Invalid request origin" on `POST /api/providers/health-autopilot/actions` for Docker/LAN dashboard requests ([#6277](https://github.com/diegosouzapw/OmniRoute/issues/6277)) — the route carried a duplicate per-route `validateBrowserMutationOrigin` check re-added by the v3.8.42 release squash after PR #5278 centralized origin enforcement in the authz pipeline; the pipeline strips `PEER_IP_HEADER` before forwarding, so the stale duplicate check could no longer resolve the LAN "direct-local-host" candidate and rejected legitimate same-origin LAN mutations (e.g. clicking "remove cooldown" when accessed via a LAN IP). Removed the duplicate check — origin validation is now solely enforced by the centralized pipeline check, which already handles this case correctly. Regression guard: `tests/unit/serial/provider-health-autopilot.test.ts`.
|
||||
- **fix(resilience):** a bare, unrecognized `403` from a no-credential (`authType:"none"`) provider like mimocode no longer permanently bans the connection ([#6315](https://github.com/diegosouzapw/OmniRoute/issues/6315), [#6345](https://github.com/diegosouzapw/OmniRoute/issues/6345)) — `classifyProviderError()`'s 403 branch only exempted `apikey` providers from the terminal `FORBIDDEN` classification, so these free/stateless proxies (no real account/credential to revoke) fell through to `FORBIDDEN` on the first unmatched 403 and got `isActive:false, testStatus:"banned"` with no cooldown or retry. The exemption now also covers `authType:"none"` providers, returning `null` (recoverable) so the existing connection-cooldown/retry layer handles it. Regression guard: `tests/unit/errorClassifier-noauth-403-6315.test.ts`.
|
||||
- **fix(resilience):** a bare, unrecognized `403` from a no-credential (`authType:"none"`) provider like mimocode or theoldllm no longer permanently bans the connection ([#6315](https://github.com/diegosouzapw/OmniRoute/issues/6315), [#6345](https://github.com/diegosouzapw/OmniRoute/issues/6345)) — `classifyProviderError()`'s 403 branch only exempted `apikey` providers from the terminal `FORBIDDEN` classification, so these free/stateless proxies (no real account/credential to revoke) fell through to `FORBIDDEN` on the first unmatched 403 and got `isActive:false, testStatus:"banned"` with no cooldown or retry. The exemption now also covers `authType:"none"` providers, returning `null` (recoverable) so the existing connection-cooldown/retry layer handles it. Regression guard: `tests/unit/errorClassifier-noauth-403-6315.test.ts`.
|
||||
- **fix(providers):** the **Auggie (Augment CLI)** executor no longer fails on Windows with `spawn EINVAL` ([#6304](https://github.com/diegosouzapw/OmniRoute/issues/6304)) — the global-npm install exposes `auggie` as a `.cmd` shim, which Node's `child_process.spawn` cannot launch on win32 without `shell: true`. Both spawn sites (streaming + the `auggie --version` test) now go through a shared `buildAuggieSpawnOptions()` that sets `shell: process.platform === "win32"`; the argv (built by `buildAuggieArgs()` with a registry-validated `model` and a trailing `--` end-of-options marker) is unchanged, so the argument-injection surface stays closed on non-Windows. Regression guard: `tests/unit/auggie-win32-spawn-6304.test.ts`.
|
||||
- **fix(api):** the dashboard **"Test model"** action is now a clean connection test ([#6240](https://github.com/diegosouzapw/OmniRoute/issues/6240)) — `modelTestRunner` sent its probe request without an explicit compression override, so whenever the operator's global `compression.enabled` flag was on the test call inherited compression (and any Output-Styles system prompt), polluting the result. The internal test requests now send `X-OmniRoute-Compression: off`, and `chatCore` honors an explicit `off` header even when `compression.enabled` is globally true. Regression guards: `tests/unit/model-test-runner-compression-off-6240.test.ts`, `tests/integration/test-model-compression-off-6240.test.ts`.
|
||||
- **fix(startup):** an update/restart could crash the whole server at boot with `TypeError: Cannot create property 'message' on string 'Database closed'`, masking the real failure and 500-ing every request until manually restarted ([#6560](https://github.com/diegosouzapw/OmniRoute/issues/6560), plausibly the root cause of #6594's post-upgrade 500) — `driverFactory.ts::preInitSqlJs()` cached its sql.js WASM adapter per file path in a `globalThis`-backed map for idempotency, but never checked whether the cached adapter had since been closed (e.g. by `gracefulShutdown`/`resetDbInstance` racing a reload); reusing that dead handle made the very next query throw sql.js's own bare string `"Database closed"` (not an `Error`) straight out of `instrumentation-node.ts`'s previously-unguarded `ensureDbInitialized()` call, and Next.js's internal `registerInstrumentation()` wrapper unconditionally does `err.message = ...` on whatever `register()` rejects with — assigning `.message` on a primitive string throws in strict mode, so the secondary `TypeError` is what actually crashed the process. Fixed in two parts: `preInitSqlJs()` now evicts a closed cached adapter and creates a fresh one instead of returning it; a new `ensureDbReadyForBoot()` wraps the DB-init call, normalizes any non-Error throw via `normalizeBootError()`, and retries once specifically for a transient "database closed" message (now succeeding against the fresh adapter) before re-throwing anything else as a real `Error`. Regression guard: `tests/unit/instrumentation-database-closed-6560.test.ts`.
|
||||
@@ -4001,6 +3998,7 @@ Thanks to everyone whose work landed in v3.8.45:
|
||||
- **feat(api):** expose a read-only provider plugin manifest at `GET /api/v1/provider-plugin-manifest` for sidecar/relay discovery. ([#6001](https://github.com/diegosouzapw/OmniRoute/pull/6001)) (thanks @KooshaPari)
|
||||
- **feat(sidecar):** advertise the provider manifest URL to Bifrost/CLIProxyAPI via the `X-OmniRoute-Provider-Manifest-Url` header (`OMNIROUTE_PROVIDER_MANIFEST_URL`). ([#6007](https://github.com/diegosouzapw/OmniRoute/pull/6007)) (thanks @KooshaPari)
|
||||
- **feat(autoCombo):** add a latency/speed-optimized routing mode (shared `rankBySpeed` scoring core) plus the `omniroute_pick_fastest_model` MCP tool. ([#6011](https://github.com/diegosouzapw/OmniRoute/pull/6011)) (thanks @KooshaPari)
|
||||
- **feat(providers):** refresh The Old LLM (Free) model catalog ([#5181](https://github.com/diegosouzapw/OmniRoute/issues/5181)) — seed the current free `/api/chatgpt` tier (GPT-5/5.1/5.2/5.3/5.4, o3/o4-mini, Gemini 3 Pro / 2.5 Pro / 2.0 Flash / 1.5 Flash, Claude 4.6 Opus/Sonnet & 4.5 Haiku, GPT-4o, Grok 4, DeepSeek V3/R1, Sonar Pro) while keeping the legacy alias IDs for saved-preference compatibility. Also fixes a latent routing bug: `mapModel()` now passes known upstream IDs through unchanged, so Gemini/o-series/Grok/DeepSeek/Sonar models no longer silently collapse onto `GPT_5_4`. Regression guard: `tests/unit/theoldllm-model-refresh-5181.test.ts`. (thanks @WslzGmzs)
|
||||
- **feat(resilience):** surface Codex **banked reset credits** per connected account ([#5199](https://github.com/diegosouzapw/OmniRoute/issues/5199)) — the Codex quota parsers (`buildCodexUsageQuotas`, `parseCodexUsageResponse`) now additively read `rate_limit_reset_credits.available_count` (+ optional `rate_limit_reached_type`) from the `/wham/usage` payload OmniRoute already fetches, and the provider-limits dashboard renders a **"Banked Reset Credits"** row when a positive count is present. Display-only and **fail-open** — the field is eligibility-gated, so accounts without it are unaffected (parsers never throw on absent/garbage shapes); redemption (an unofficial mutating endpoint) is intentionally out of scope. Regression guard: `tests/unit/codex-banked-reset-credits-5199.test.ts` (8). (thanks @ofekbetzalel)
|
||||
- **feat(providers):** add sign-up geo-restriction notices for **SenseNova** and **StepFun** ([#5462](https://github.com/diegosouzapw/OmniRoute/issues/5462)) — the provider add-form now warns that SenseNova's console appears to require a Chinese (+86) phone number with no documented international path, and that StepFun's default endpoint is its China platform while a global StepFun Open Platform (`platform.stepfun.ai`, operated by Sparkling AI Pte. Ltd., Singapore) with email/Google/Discord login exists for international users. Informational `notice` only — neither provider is disabled. Regression guard: `tests/unit/regional-provider-cn-notices-5462.test.ts`. (thanks @chirag127)
|
||||
- **feat(usage):** add on-demand period-scoped usage-data reset (Settings → System Storage) with a purge API and time-window selector. ([#5831](https://github.com/diegosouzapw/OmniRoute/pull/5831))
|
||||
@@ -5646,6 +5644,7 @@ Thanks to everyone whose work landed in v3.8.43:
|
||||
- **fix(sse): map `reasoning_effort` to DeepSeek V4's native `{high, max}` vocabulary** — DeepSeek V4 only understands `high`/`max` reasoning levels, so other `reasoning_effort` values are mapped onto its native vocabulary instead of being rejected. ([#4219](https://github.com/diegosouzapw/OmniRoute/pull/4219))
|
||||
- **fix(glm): default `max_tokens` and an extended timeout for GLM-5.2+ thinking** — GLM-5.2+ thinking responses are slow and need headroom, so OmniRoute now sets a sensible default `max_tokens` and a longer timeout for them. ([#4255](https://github.com/diegosouzapw/OmniRoute/pull/4255) — thanks @dhaern)
|
||||
- **fix(antigravity): default `includeThoughts` for modern Gemini models** — modern Gemini models on the Antigravity path now default to including thoughts so reasoning isn't silently dropped. ([#4180](https://github.com/diegosouzapw/OmniRoute/pull/4180) — thanks @dhaern)
|
||||
- **fix(provider-registry): add correct `contextLength` to theoldllm models** — fills in accurate context-window sizes for theoldllm's models. ([#4184](https://github.com/diegosouzapw/OmniRoute/pull/4184) — thanks @herjarsa)
|
||||
- **fix(models): expose combo model token limits** — `/v1/models` now reports token limits for combo models. ([#4189](https://github.com/diegosouzapw/OmniRoute/pull/4189) — thanks @megamen32)
|
||||
- **fix(combo): keep the passthrough quota fallback scoped** — prevents the passthrough quota fallback from leaking across unrelated targets. ([#4194](https://github.com/diegosouzapw/OmniRoute/pull/4194) — thanks @Svetznaniy33)
|
||||
- **fix(combo): opt proactive-fallback compression into the TV1 bail-out (no silent target drop)** — proactive-fallback compression now participates in the TV1 bail-out so a target is never silently dropped. ([#4228](https://github.com/diegosouzapw/OmniRoute/pull/4228))
|
||||
@@ -6305,6 +6304,7 @@ Thanks to everyone whose work landed in v3.8.43:
|
||||
- **fix(catalog):** Codex CLI model-catalog refresh no longer errors — `GET /v1/models` now returns a top-level `models: []` array for Codex clients (detected via the `originator` / `user-agent` = `codex_*` headers it sends on `GET /v1/models?client_version=...`), so `codex_models_manager` stops failing to decode the OpenAI-standard response and no longer logs `failed to refresh available models` on every startup. The array is intentionally empty: Codex replaces its built-in per-model agent prompt (`base_instructions`, ~21k chars) with whatever a populated entry carries for the selected model, so emitting our catalog would break Codex's agent behaviour — an empty list keeps Codex on its built-in model info (same inference as before, minus the error). Non-Codex OpenAI clients receive the unchanged `{object,data}` response. ([#3481](https://github.com/diegosouzapw/OmniRoute/pull/3481) — thanks @diegosouzapw)
|
||||
- **fix(provider):** Cursor's Responses-API-shaped bodies on `/chat/completions` are detected and handled — a body with `input` but no `messages` is now classified as `openai-responses` (instead of forcing `openai` and building from undefined `messages` → upstream 400); standard OpenAI clients are unaffected by the `messages===undefined` guard. ([#3490](https://github.com/diegosouzapw/OmniRoute/pull/3490) — thanks @borodulin)
|
||||
- **fix(sse):** numeric provider IDs normalized to strings across 4 more surfaces — extends #3427 to the Responses-API SSE passthrough (`response_id`/`item_id`/`call_id`), the buffered/flush path in `stream.ts`, the dedup-key builders, and `sseParser.ts`, preventing `undefined` lookups when IDs arrive as numbers. ([#3451](https://github.com/diegosouzapw/OmniRoute/pull/3451) — thanks @disafronov)
|
||||
- **fix(theoldllm):** `X-Request-Token` generated server-side, dropping the Playwright dependency — replicates the site's client `rie()` token (djb2 hash + `oldllm-client-2026` seed + UA prefix + 8-hex `crypto.randomUUID` suffix) directly, so The Old LLM no longer needs a headless browser to mint tokens. ([#3491](https://github.com/diegosouzapw/OmniRoute/pull/3491) — thanks @borodulin / @diegosouzapw)
|
||||
- **fix(combo):** parallel pre-screen + circuit-breaker fast-exit for priority combos — provider profiles and model availability for all targets are pre-screened concurrently (max 5), and targets whose circuit breaker is OPEN are skipped immediately, reducing first-token latency on multi-target priority combos. ([#3169](https://github.com/diegosouzapw/OmniRoute/pull/3169) — thanks @pizzav-xyz)
|
||||
- **fix(authz):** URL-tokenized client endpoints (`/api/v1/vscode/<key>/...`) authenticate again when the caller sends its own non-OmniRoute `Authorization` header — a non-`Bearer <token>` header (e.g. VS Code Copilot's own, or an empty `Bearer `) no longer short-circuits auth; it falls through to the path-scoped URL token (still validated downstream), instead of 401'ing under `REQUIRE_API_KEY=true`. ([#3504](https://github.com/diegosouzapw/OmniRoute/pull/3504) — thanks @zhiru / @diegosouzapw)
|
||||
- **fix(playground):** the dashboard provider Test playground works under `REQUIRE_API_KEY=true` — it previously sent the **masked** key (`sk-xxxx****yyyy`) as a bearer (always invalid → 401). It now authenticates via the dashboard session and sends only the key **id** (`x-omniroute-playground-key-id`); the gateway resolves the secret server-side, honored **only** for an authenticated session and never putting the key secret on the wire. ([#3503](https://github.com/diegosouzapw/OmniRoute/pull/3503) — thanks @zhiru / @diegosouzapw)
|
||||
@@ -6337,7 +6337,7 @@ Thanks to everyone whose work landed in v3.8.43:
|
||||
|
||||
- **fix(translator):** Vertex AI tool calls no longer fail with `400 Unknown name "id"` — the OpenAI-style `id` field is stripped from `functionCall`/`functionResponse` parts for `vertex`/`vertex-partner`; the public Gemini API still receives `id` as required for Gemini 3+ signature matching. ([#3457](https://github.com/diegosouzapw/OmniRoute/pull/3457) — thanks @nullbytef0x / @diegosouzapw)
|
||||
- **fix(claude):** Claude Code `claude-opus-4-8` tool calls no longer break with `tool call could not be parsed` — OmniRoute no longer force-injects `interleaved-thinking` / `advanced-tool-use` / `effort` beta flags the client never negotiated; clients sending their own `anthropic-beta` header control those betas themselves. ([#3458](https://github.com/diegosouzapw/OmniRoute/pull/3458) — thanks @Forcerecon / @diegosouzapw)
|
||||
- **fix(catalog):** imported/custom models on no-auth providers now appear in `GET /api/v1/models` and the Playground model selector — the eligibility gate required a DB connection row which no-auth providers never have, silently dropping every imported model for them. ([#3463](https://github.com/diegosouzapw/OmniRoute/pull/3463) — thanks @tjengbudi / @diegosouzapw)
|
||||
- **fix(catalog):** imported/custom models on no-auth providers (e.g. The Old LLM) now appear in `GET /api/v1/models` and the Playground model selector — the eligibility gate required a DB connection row which no-auth providers never have, silently dropping every imported model for them. ([#3463](https://github.com/diegosouzapw/OmniRoute/pull/3463) — thanks @tjengbudi / @diegosouzapw)
|
||||
- **fix(browser):** optional `cloakbrowser` import no longer causes bundle errors when the package is absent — the import is now wrapped in a dynamic require so the build succeeds on environments that don't install the optional dep. ([#3460](https://github.com/diegosouzapw/OmniRoute/pull/3460) — thanks @rdself)
|
||||
- **fix(claude-web):** claude-web session handling cleanup — corrects an edge case where session cookies were not properly refreshed after a Turnstile challenge, and removes stale wrapper code left over from the provider split. ([#3449](https://github.com/diegosouzapw/OmniRoute/pull/3449) — thanks @androw)
|
||||
- **fix(analytics):** SQL named params are now scoped per query context — a shared params object was being mutated across concurrent analytics queries, causing `SQLITE_MISUSE: named parameter not found` errors under load. ([#3447](https://github.com/diegosouzapw/OmniRoute/pull/3447) — thanks @ReqX)
|
||||
@@ -6513,7 +6513,8 @@ Thanks to everyone whose work landed in v3.8.14:
|
||||
|
||||
- **fix(dashboard):** Agent Bridge page (`/dashboard/tools/agent-bridge`) no longer crashes with "Internal Server Error" — the page replaced its well-shaped state with the raw `/api/tools/agent-bridge/state` response (`{ server, agents }`), leaving `serverState` undefined and throwing `Cannot read properties of undefined (reading 'running')`. A shared `normalizeAgentBridgeState()` now maps the route shape into the page contract (incl. `server.certExists → certTrusted`) and always returns safe defaults, used by both the SSR loader and the polling hook. (#3318 — thanks @tycronk20)
|
||||
- **fix(codex):** strip client-only params (`prompt_cache_retention`, `safety_identifier`, `user`) on the native `codex/` `/v1/responses` passthrough — Codex upstream rejects them with `400 Unsupported parameter`, which broke Factory Droid and any client injecting those fields. The chat-completions path already stripped them; the responses→responses passthrough now does too. (#3317 — thanks @tycronk20)
|
||||
- **fix(dashboard):** keep no-auth providers (opencode, duckduckgo-web, veoaifree-web) visible under the "Show configured only" filter — they never create a connection row (`stats.total === 0`) but are always usable and already appear in `/v1/models`, so the filter now treats `displayAuthType === "no-auth"` as configured. (#3290 — thanks @uniQta)
|
||||
- **fix(theoldllm):** stop the `[502]: Body is unusable: Body has already been read` error on the cached-token path — the executor read the same upstream `Response` body with `.text()` twice; it now reads it once and only re-reads after a token-rejection refetch. (#3296 — thanks @onizukashonan14-png)
|
||||
- **fix(dashboard):** keep no-auth providers (opencode, duckduckgo-web, theoldllm, veoaifree-web) visible under the "Show configured only" filter — they never create a connection row (`stats.total === 0`) but are always usable and already appear in `/v1/models`, so the filter now treats `displayAuthType === "no-auth"` as configured. (#3290 — thanks @uniQta)
|
||||
- **fix(dashboard):** refresh the connection list after a Codex/Claude/Gemini auth import — the import modals called `fetchData()` (which only reloads provider metadata), so a freshly-imported connection stayed invisible until a manual reload; they now call `fetchConnections()`. ([#3320](https://github.com/diegosouzapw/OmniRoute/pull/3320) — thanks @zhiru)
|
||||
- **fix(cli):** `omniroute update` no longer always fails on a global install — `getCurrentVersion()` and `createBackup()` now resolve `package.json`/`bin` relative to the script (`import.meta.url`) instead of `process.cwd()` (the user's working dir on a global npm/brew install → _"Could not determine current version"_), and the backup copies the `cli` directory with `cpSync({recursive:true})` instead of `copyFileSync`, which threw a swallowed `EISDIR` → _"Failed to create backup. Aborting"_. (#3295 — thanks @uniQta)
|
||||
- **fix(sse):** harden the passthrough stream against empty upstream responses — emit a synthetic retry chunk on an empty `choices: []` (fixes a Copilot Chat crash) and log empty post-`tool_calls` completions; also registers **MiniMax M3** (1M context) across 8 provider tiers. ([#3297](https://github.com/diegosouzapw/OmniRoute/pull/3297), #3110 — thanks @wilsonicdev)
|
||||
@@ -6605,6 +6606,7 @@ Thanks to everyone whose work landed in v3.8.12:
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **theoldllm:** add The Old LLM — a free, Playwright-backed provider with dual-mode operation (cached browser token + direct fetch) bridged through a Vercel relay (#3217 — thanks @oyi77)
|
||||
- **codex:** add Codex login via OpenAI's browser-driven device authorization flow, exposed as a shareable "Adicionar Externo" public link (`/connect/codex/{token}`) so a third party can complete the OpenAI device login without dashboard access (#3195 — thanks @zhiru)
|
||||
- **proxy:** per-connection proxy distribution — `proxy_enabled` DB schema + Zod-validated resolution backend, automatic proxy-fallback selection when provider validation hits a network error, and a dashboard UI with per-connection toggles and a tag-filtered "Distribute Proxies" button (#3170, #3171, #3172 — thanks @pizzav-xyz)
|
||||
- **api:** `/v1/images/generations` and `/v1/images/edits` now resolve a bare combo/alias model name (e.g. `image`) to its single image target, and `/v1/images/edits` forwards multipart edits to custom OpenAI-compatible providers' `{base_url}/images/edits` (also accepting JSON/data-URL edit input) instead of rejecting everything but chatgpt-web (#3214, #3215 — thanks @ngocquynh85)
|
||||
|
||||
@@ -73,9 +73,6 @@ npm run dev
|
||||
npm run build # next build → .build/next/ then assembleStandalone → dist/
|
||||
npm run start
|
||||
|
||||
# Fast backend/API-only compile for contributor changes
|
||||
npm run build:contributor
|
||||
|
||||
# Release build (clean rebuild + HEAD sentinel — required for deploy)
|
||||
npm run build:release # rm -rf .build dist && build + writes dist/BUILD_SHA
|
||||
|
||||
@@ -83,10 +80,6 @@ npm run build:release # rm -rf .build dist && build + writes dist/BUILD_SHA
|
||||
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
|
||||
```
|
||||
|
||||
The contributor build performs compile-only validation: it does not assemble the standalone
|
||||
distribution or build optional native packaging assets. Use the regular production build when
|
||||
you need to validate the shippable bundle.
|
||||
|
||||
### Build Output Layout
|
||||
|
||||
| Directory | Contents | Tracked |
|
||||
@@ -107,11 +100,6 @@ npm run build
|
||||
`npm run build:release` additionally cleans both directories first and writes
|
||||
`dist/BUILD_SHA` (= `git rev-parse --short HEAD`) as a deploy integrity sentinel.
|
||||
|
||||
`npm run build:contributor` uses the backend-only build profile. It temporarily stubs
|
||||
dashboard UI files while building, keeps API route handlers, and restores the original files
|
||||
after the build. Use `npm run build` for changes that affect the dashboard UI or for full
|
||||
release validation; the contributor profile is not a replacement for the release build.
|
||||
|
||||
> **VPS deploy note:** the remote image directory `/usr/lib/node_modules/omniroute/app/`
|
||||
> is unchanged. The deploy skills rsync the contents of `dist/` into it.
|
||||
> Only the in-repo build output path moved (`app/` → `dist/`).
|
||||
@@ -177,13 +165,6 @@ npm run test:all
|
||||
# Single test file (Node.js native test runner — most tests use this)
|
||||
node --import tsx/esm --test tests/unit/your-file.test.ts
|
||||
|
||||
# Only the unit tests impacted by your change (same TIA selector as the CI gate, #8084)
|
||||
npm run test:scoped # changes in the last commit (or the working tree)
|
||||
npm run test:scoped:staged # staged changes only — pairs well with a pre-commit run
|
||||
npm run test:scoped:full # rebuild the import-graph map first (after adding/moving files)
|
||||
# Exit 1 + "run the full suite" means a hub file (tsconfig, package.json, …) or an
|
||||
# unmapped source changed — the selector fails safe, it never silently skips.
|
||||
|
||||
# Vitest (MCP server, autoCombo, cache)
|
||||
npm run test:vitest
|
||||
|
||||
@@ -320,7 +301,7 @@ src/ # TypeScript (.ts / .tsx)
|
||||
open-sse/ # @omniroute/open-sse workspace
|
||||
├── executors/ # 89 executor implementation modules
|
||||
├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.)
|
||||
├── mcp-server/ # MCP server (110 unique tools, 3 transports, 33 scopes)
|
||||
├── mcp-server/ # MCP server (107 unique tools, 3 transports, 32 scopes)
|
||||
├── services/ # 178 top-level services (combo, autoCombo, rateLimitManager, etc.)
|
||||
├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
|
||||
├── transformer/ # Responses API transformer
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -103,12 +103,25 @@ RUN test -f package-lock.json \
|
||||
# node-gyp comes from npm's own bundled copy (deterministic, already in the image)
|
||||
# instead of `npx --yes`, which would install an arbitrary registry version
|
||||
# on-demand and run its lifecycle scripts (Sonar docker:S6505).
|
||||
#
|
||||
# tls-client-node (claude-web/grok-web/lmarena/perplexity-web TLS
|
||||
# impersonation) hits the same --ignore-scripts wall: its own postinstall.js
|
||||
# fetches a platform .so/.dylib/.dll from the bogdanfinn/tls-client GitHub
|
||||
# Releases API and is never invoked when npm ci skips lifecycle scripts. Unlike
|
||||
# better-sqlite3 above, that script never throws on failure — it only
|
||||
# `console.warn`s and exits 0 — so a rate-limited or offline build would
|
||||
# otherwise succeed silently with an empty bin/ and only fail at first request
|
||||
# in production (TlsClientUnavailableError, #7802). Run it explicitly here so
|
||||
# a broken/rate-limited fetch fails the BUILD loudly instead of shipping a
|
||||
# broken image.
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
|
||||
npm ci --include=optional --no-audit --no-fund --legacy-peer-deps --ignore-scripts \
|
||||
&& (cd node_modules/better-sqlite3 \
|
||||
&& node /usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js rebuild) \
|
||||
&& node -e "require('better-sqlite3')(':memory:').close()" \
|
||||
&& node -e "const wreq=require('wreq-js'); if(typeof wreq.createTransport!=='function') process.exit(1)"
|
||||
&& node node_modules/tls-client-node/scripts/postinstall.js \
|
||||
&& (test -n "$(find node_modules/tls-client-node/bin -mindepth 1 -print -quit 2>/dev/null)" \
|
||||
|| (echo "tls-client-node native binary missing after postinstall — GitHub API fetch likely rate-limited or failed (#7802)" >&2 && exit 1))
|
||||
|
||||
# Build with Turbopack (stable in Next 16, the repo default). The v3.8.27-era
|
||||
# TurbopackInternalError panic ("entered unreachable code: there must be a path to a
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ── Multi-stage Dockerfile for Native Bun Runtime (web-latest-bun) ───────────
|
||||
FROM oven/bun:1.4.0-slim AS base
|
||||
FROM oven/bun:1.3.14-slim AS base
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -31,8 +31,10 @@ COPY scripts/dev/sync-env.mjs ./scripts/dev/sync-env.mjs
|
||||
# Fast Bun native package install
|
||||
RUN bun install --include=optional --quiet
|
||||
|
||||
# Fail the build if wreq-js cannot resolve its current platform binding.
|
||||
RUN bun -e "const wreq = require('wreq-js'); if (typeof wreq.createTransport !== 'function') process.exit(1)"
|
||||
# Fetch tls-client-node native binary if script exists
|
||||
RUN if [ -f "node_modules/tls-client-node/scripts/postinstall.js" ] && [ ! -d "node_modules/tls-client-node/bin" ]; then \
|
||||
bun node_modules/tls-client-node/scripts/postinstall.js || true; \
|
||||
fi
|
||||
|
||||
# Smoke check native database driver used by Bun (bun:sqlite)
|
||||
RUN bun -e "import { Database } from 'bun:sqlite'; const db = new Database(':memory:'); db.query('SELECT 1 AS ok').get(); db.close(); console.log('bun:sqlite smoke: OK');"
|
||||
@@ -56,7 +58,7 @@ ENV NODE_ENV=production
|
||||
RUN bun run --quiet build
|
||||
|
||||
# ── Runner Base stage (100% Bun Native Production Runtime) ──────────────────
|
||||
FROM oven/bun:1.4.0-slim AS runner-base
|
||||
FROM oven/bun:1.3.14-slim AS runner-base
|
||||
|
||||
LABEL org.opencontainers.image.title="omniroute" \
|
||||
org.opencontainers.image.description="Unified AI proxy — route any LLM through one endpoint (Bun Native)" \
|
||||
|
||||
104
README.md
104
README.md
@@ -7,7 +7,7 @@
|
||||
|
||||
# 🚀 OmniRoute — The Free AI Gateway
|
||||
|
||||
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 356 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 356 AI providers · 150+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 352 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 352 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **437 free-tier entries across 38 recurring pool keys** and computes the token headline from the **20 pools with a published positive monthly budget**, deduplicated by shared pool. The result stays visible on the dashboard (`/dashboard/free-tiers`).
|
||||
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **445 free-tier entries across 39 recurring pool keys** and computes the token headline from the **20 pools with a published positive monthly budget**, deduplicated by shared pool. The result stays visible on the dashboard (`/dashboard/free-tiers`).
|
||||
|
||||
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.51B free tokens per month steady, up to ~2.13B in the first month with signup credits, from 38 documented recurring pool keys covering 437 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 20 recurring pools with a published positive monthly token budget; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, LLM7 150M, Nara 150M, Gemini 60M and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
|
||||
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.51B free tokens per month steady, up to ~2.13B in the first month with signup credits, from 39 documented recurring pool keys covering 445 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 20 recurring pools with a published positive monthly token budget; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, LLM7 150M, Nara 150M, Gemini 60M and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers 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](docs/reference/FREE_TIERS.md)**.
|
||||
>
|
||||
@@ -49,11 +49,11 @@
|
||||
|
||||
[](https://discord.gg/U47eFqAXCn)
|
||||
[](https://t.me/omnirouteOficial)
|
||||
[](https://chat.whatsapp.com/FvuCbrpZmQ6I85n2vW5QIC?s=cl&p=a&mlu=4)
|
||||
[](https://chat.whatsapp.com/KWgatljAjmbELQory59Oti?s=cl&p=a&mlu=4)
|
||||
[](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
|
||||
[](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz)
|
||||
[](https://omniroute.online)
|
||||
|
||||
**Questions, provider tips, roadmap & support → [Discord](https://discord.gg/U47eFqAXCn) · [Telegram](https://t.me/omnirouteOficial) · WhatsApp [🌍 Global](https://chat.whatsapp.com/FvuCbrpZmQ6I85n2vW5QIC?s=cl&p=a&mlu=4) / [🇧🇷 Brasil](https://chat.whatsapp.com/KWgatljAjmbELQory59Oti?s=cl&p=a&mlu=4) / [Portal](https://portal.sthub.com.br/communities/groups/st-hub/channels/Omniroute-World-8kRjmK)**
|
||||
**Questions, provider tips, roadmap & support → [Discord](https://discord.gg/U47eFqAXCn) · [Telegram](https://t.me/omnirouteOficial) · WhatsApp [🌍 Global](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) / [🇧🇷 Brasil](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz)**
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
| | v3.8.49 | **v3.8.50** | `v3.8.51+` |
|
||||
| ------------------------- | :-----: | :-----------------------: | :---------: |
|
||||
| 🌐 Providers | 290 | **352** | more queued |
|
||||
| 🌐 Providers | 290 | **350** | more queued |
|
||||
| 🧠 Unique chat model IDs | 1185 | **1312** | — |
|
||||
| 🖼️ Modality Bridge | — | 🆕 vision + audio + video | — |
|
||||
| 📡 Radar free catalog | — | 🆕 opt-in | — |
|
||||
@@ -133,7 +133,7 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<b>🌐 In 42 languages</b>
|
||||
<b>🌐 In 43 languages</b>
|
||||
<br/><br/>
|
||||
<a href="README.md"><img src="docs/assets/flags/us.svg" width="30" alt="English (en)" title="English (en)"></a>
|
||||
<a href="docs/i18n/pt-BR/README.md"><img src="docs/assets/flags/br.svg" width="30" alt="Português — Brasil (pt-BR)" title="Português — Brasil (pt-BR)"></a>
|
||||
@@ -164,6 +164,7 @@
|
||||
<a href="docs/i18n/id/README.md"><img src="docs/assets/flags/id.svg" width="30" alt="Bahasa Indonesia (id)" title="Bahasa Indonesia (id)"></a>
|
||||
<a href="docs/i18n/ms/README.md"><img src="docs/assets/flags/my.svg" width="30" alt="Bahasa Melayu (ms)" title="Bahasa Melayu (ms)"></a>
|
||||
<a href="docs/i18n/phi/README.md"><img src="docs/assets/flags/ph.svg" width="30" alt="Filipino (phi)" title="Filipino (phi)"></a>
|
||||
<a href="docs/i18n/in/README.md"><img src="docs/assets/flags/in.svg" width="30" alt="हिन्दी (in)" title="हिन्दी (in)"></a>
|
||||
<a href="docs/i18n/hi/README.md"><img src="docs/assets/flags/in.svg" width="30" alt="हिन्दी (hi)" title="हिन्दी (hi)"></a>
|
||||
<a href="docs/i18n/gu/README.md"><img src="docs/assets/flags/in.svg" width="30" alt="ગુજરાતી (gu)" title="ગુજરાતી (gu)"></a>
|
||||
<a href="docs/i18n/mr/README.md"><img src="docs/assets/flags/in.svg" width="30" alt="मराठी (mr)" title="मराठी (mr)"></a>
|
||||
@@ -209,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 356 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 356 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 53 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 352 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 352 providers · up to 95% token savings on eligible workloads · $0 to start with 90+ free tiers and 56 recurring/keyless free-forever providers · 35 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
@@ -331,8 +332,6 @@ No combo to create. Set your model to `auto` (or a variant) and OmniRoute builds
|
||||
<tr><td align="left" nowrap><code>auto/cheap</code></td><td align="left">💰 Cheapest per token first</td></tr>
|
||||
<tr><td align="left" nowrap><code>auto/offline</code></td><td align="left">🔋 Most quota / rate-limit headroom first</td></tr>
|
||||
<tr><td align="left" nowrap><code>auto/smart</code></td><td align="left">🔭 Quality-first + 10% exploration to discover better models</td></tr>
|
||||
<tr><td align="left" nowrap><code>auto/lkgp</code></td><td align="left">📌 Explicit last-known-good-provider stickiness</td></tr>
|
||||
<tr><td align="left" nowrap><code>auto/chaos</code></td><td align="left">🧪 Fault-injection weights for resilience testing (chaos engineering)</td></tr>
|
||||
</table>
|
||||
|
||||
##
|
||||
@@ -430,7 +429,7 @@ All **19** strategies — mix & match per combo step:
|
||||
<tr>
|
||||
<td align="center">17</td>
|
||||
<td nowrap><code>auto</code></td>
|
||||
<td>16-factor live scoring across every connection 🤖</td>
|
||||
<td>15-factor live scoring across every connection 🤖</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">18</td>
|
||||
@@ -444,13 +443,13 @@ All **19** strategies — mix & match per combo step:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<sub>The Auto-Combo engine scores every candidate on **16 factors** (health, quota, cost, latency, task fit, quality, session availability…) — see [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md).</sub>
|
||||
<sub>The Auto-Combo engine scores every candidate on **15 factors** (health, quota, cost, latency, task fit, quality, session availability…) — see [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md).</sub>
|
||||
|
||||
##
|
||||
|
||||
### 🧱 Resilience is built in (3 independent layers)
|
||||
|
||||
<img src="./docs/diagrams/resilience-layers.svg" width="100%" alt="OmniRoute resilience — 3 independent self-healing layers, the right layer for the right failure. Layer 1 provider circuit breaker (whole provider): trips only on 408/5xx, thresholds OAuth 8× / API-key 12× / local 2×, resets 60s/30s/15s into a HALF-OPEN probe, lazy recovery; while OPEN the combo reroutes to the next provider. Layer 2 connection cooldown (one key/account): base 5s OAuth / 3s API-key, exponential ×2 backoff with anti-thundering-herd guard, 429 honors Retry-After, success clears all error state; one cooling key is skipped while sibling keys keep serving. Layer 3 model lockout (one model): per-model 429, local 404 or mode denials lock just that model — never the whole connection. Terminal states (banned, expired, credits exhausted) are for the operator, not cooldowns."/>
|
||||
<img src="./docs/diagrams/resilience-layers.svg" width="100%" alt="OmniRoute resilience — 3 independent self-healing layers, the right layer for the right failure. Layer 1 provider circuit breaker (whole provider): trips only on 408/5xx, thresholds OAuth 10× / API-key 15× / local 2×, resets 60s/30s/15s into a HALF-OPEN probe, lazy recovery; while OPEN the combo reroutes to the next provider. Layer 2 connection cooldown (one key/account): base 5s OAuth / 3s API-key, exponential ×2 backoff with anti-thundering-herd guard, 429 honors Retry-After, success clears all error state; one cooling key is skipped while sibling keys keep serving. Layer 3 model lockout (one model): per-model 429, local 404 or mode denials lock just that model — never the whole connection. Terminal states (banned, expired, credits exhausted) are for the operator, not cooldowns."/>
|
||||
|
||||
<sub>📖 [Auto-Combo Engine](docs/routing/AUTO-COMBO.md) · [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md)</sub>
|
||||
|
||||
@@ -462,7 +461,7 @@ All **19** strategies — mix & match per combo step:
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 356 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
|
||||
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 352 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
|
||||
|
||||
<sub>📊 Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
|
||||
|
||||
@@ -549,7 +548,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
- **🗜️ Compression hardening** — default-on inflation guard, Caveman packs for DE / FR / JA + Chinese (wényán), RTK filters for Gradle & .NET. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
|
||||
- **💸 Honest flat-rate cost** — subscription / coding-plan providers read **$0** in cost analytics; budget, quota & routing keep estimating. → [API Reference](docs/reference/API_REFERENCE.md)
|
||||
- **⚖️ Quota-Share routing** — split a shared account's quota fairly across pooled keys, work-conserving so idle slices are lent out. → [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md)
|
||||
- **🤖 One-command CLI/agent setup** — 13 registered `setup-*` commands; `omniroute run` launches 7 CLIs (Claude Code, Codex, Aider, Goose, OpenCode, Qwen Code, Gemini CLI); `omniroute configure` supports 10 targets with an interactive provider+model picker and per-context favorites. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
- **🤖 One-command CLI/agent setup** — 12 registered `setup-*` commands; `omniroute run` launches 7 CLIs (Claude Code, Codex, Aider, Goose, OpenCode, Qwen Code, Gemini CLI); `omniroute configure` supports 9 targets with an interactive provider+model picker and per-context favorites. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
- **🛰️ Remote mode** — drive a remote OmniRoute with scoped tokens (`connect` / `contexts` / `tokens`) + an `antigravity` OAuth helper for VPS installs. → [Remote Mode](docs/guides/REMOTE-MODE.md)
|
||||
- **🧭 Smarter auto-routing** — `auto/<category>:<tier>` combos, **Fusion** (model panel + judge), task-aware routing, per-request model / mode / USD-budget overrides. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
|
||||
- **🗜️ Pluggable compression** — 12 composable engines + Compression Studios: LLMLingua-2, two-tier Ultra, omniglyph, per-step fidelity gate, GCF v3.2, drag-reorder editor. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
|
||||
@@ -560,10 +559,9 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
|
||||
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind, and speech providers such as ElevenLabs. → [API Reference](docs/reference/API_REFERENCE.md)
|
||||
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
|
||||
- **🤝 More providers & agents** — cloud agents (Codex Cloud, Cursor, Devin, Jules), Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **352-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
|
||||
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **351-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
|
||||
- **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
|
||||
- **⚡ Local performance & infra** — one-click local Redis, Cloudflare Workers / Deno Deploy relay deployers, Bifrost & Mux as supervised embedded services. → [Embedded Services](docs/frameworks/EMBEDDED-SERVICES.md)
|
||||
- **🧩 Also in the box** — plugin framework + marketplace, Omni/Agent/GitHub skills frameworks, Obsidian vault integration (22 MCP tools), OpenAI-compatible Batch & Files APIs, semantic response cache, gamification with leaderboards, ACP agent discovery (15 built-in agents), scheduled log export to BigQuery, `auto/chaos` fault injection, a Telegram bot bridge, an in-app version manager and LMArena-ELO free-provider rankings. → [Docs](docs/README.md)
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -633,7 +631,7 @@ omniroute run qwen --model glm/glm-5.2 -- -p "reply OK"
|
||||
omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "reply OK"
|
||||
|
||||
# Or pick provider+model interactively and write the tool's own config:
|
||||
omniroute configure codex # also: claude opencode qwen aider goose gemini cline continue kilo
|
||||
omniroute configure codex # also: claude opencode qwen aider goose cline continue kilo
|
||||
```
|
||||
|
||||
Every command honors the active remote context (`omniroute connect <host>`), `--dry-run`
|
||||
@@ -644,11 +642,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🌐 352 AI Providers — 152 Catalog-Marked Free
|
||||
## 🌐 352 AI Providers — 154 Catalog-Marked Free
|
||||
|
||||
</div>
|
||||
|
||||
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **437 per-model rows**, **38 recurring pools** and **53 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
> **351 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **154 carrying `hasFree: true` discovery metadata**. The chat model registry covers **268 providers / 2,566 distinct provider-model pairs / 1,312 raw model IDs**; the separate free-budget catalog has **455 per-model rows**, **40 recurring pools** and **56 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -724,7 +722,6 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
<tr><td align="left" nowrap>📦 <b>npm (global)</b></td><td align="left" nowrap><code>npm install -g omniroute</code></td><td align="left">One command, any OS</td></tr>
|
||||
<tr><td align="left" nowrap>🐳 <b>Docker</b></td><td align="left" nowrap><code>docker run … diegosouzapw/omniroute</code></td><td align="left">Multi-arch <b>AMD64 + ARM64</b></td></tr>
|
||||
<tr><td align="left" nowrap>🖥️ <b>Desktop (Electron)</b></td><td align="left" nowrap><code>npm run electron:build</code></td><td align="left">Native window + system tray — <b>Windows / macOS / Linux</b></td></tr>
|
||||
<tr><td align="left" nowrap>🎩 <b>Menu-bar (OmniRouteTray)</b></td><td align="left" nowrap><code>brew install --cask zoispag/tap/omniroute-tray</code></td><td align="left">Supervises & auto-updates the server — <b>macOS</b></td></tr>
|
||||
<tr><td align="left" nowrap>💪 <b>ARM</b></td><td align="left" nowrap>native <code>arm64</code></td><td align="left">Raspberry Pi, ARM servers, Apple Silicon</td></tr>
|
||||
<tr><td align="left" nowrap>📱 <b>Android (Termux)</b></td><td align="left" nowrap><code>pkg install nodejs && npx -y omniroute</code></td><td align="left">Runs <b>on your phone</b>, 24/7, no root</td></tr>
|
||||
<tr><td align="left" nowrap>📲 <b>PWA</b></td><td align="left" nowrap>"Add to Home Screen"</td><td align="left">Fullscreen, offline, installable from browser</td></tr>
|
||||
@@ -733,7 +730,7 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
<tr><td align="left" nowrap>🛠️ <b>From source</b></td><td align="left" nowrap><code>npm install && npm run dev</code></td><td align="left">Hack on it, contribute</td></tr>
|
||||
</table>
|
||||
|
||||
<sub>📖 [Docker Guide](docs/guides/DOCKER_GUIDE.md) · [Desktop](electron/README.md) · [Menu-bar tray](https://github.com/zoispag/omniroute-tray) · [Termux](docs/guides/TERMUX_GUIDE.md) · [PWA](docs/guides/PWA_GUIDE.md) · [OpenCode](docs/frameworks/OPENCODE.md)</sub>
|
||||
<sub>📖 [Docker Guide](docs/guides/DOCKER_GUIDE.md) · [Desktop](electron/README.md) · [Termux](docs/guides/TERMUX_GUIDE.md) · [PWA](docs/guides/PWA_GUIDE.md) · [OpenCode](docs/frameworks/OPENCODE.md)</sub>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -768,42 +765,6 @@ From inside the editor: open the **Extensions** view, search **"OmniRoute"**, cl
|
||||
|
||||
<div align="center">
|
||||
|
||||
### 🎩 New: OmniRouteTray — your gateway, living in the menu bar
|
||||
|
||||
</div>
|
||||
|
||||
> `omniroute serve` is happiest when it's always on. **[OmniRouteTray](https://github.com/zoispag/omniroute-tray)**
|
||||
> turns that into a set-and-forget menu-bar app for macOS: it starts the server, keeps it alive
|
||||
> across reboots, updates it in place, and puts your live token budget one click away — **no
|
||||
> terminal window left open, no `npm install -g omniroute` to babysit.**
|
||||
|
||||
Built with [Tauri v2](https://v2.tauri.app/) (a Rust core the size of a rounding error), it ships
|
||||
its own signed Node 24 runtime and manages an app-owned OmniRoute install, so it never fights your
|
||||
global `node`/`bun`. It **shares your existing `~/.omniroute/` config and database** — so it's the
|
||||
same OmniRoute you already run, just with a hat on. 🎩
|
||||
|
||||
<table>
|
||||
<tr><th align="left">What it does</th><th align="left">How</th></tr>
|
||||
<tr><td align="left" nowrap>🟢 <b>Supervises the server</b></td><td align="left">Spawns <code>omniroute serve</code>, adopts an already-running instance instead of duplicating it</td></tr>
|
||||
<tr><td align="left" nowrap>📊 <b>Live usage at a glance</b></td><td align="left">Provider quota bars, Claude session/weekly limits with reset countdowns, 30-day cost breakdown</td></tr>
|
||||
<tr><td align="left" nowrap>🔄 <b>Auto-updates in place</b></td><td align="left">Staged install, atomic swap, rollback on failure — always on the newest release</td></tr>
|
||||
<tr><td align="left" nowrap>🚀 <b>Start on login</b></td><td align="left">Optional launch at login; tray-only, no dock icon</td></tr>
|
||||
<tr><td align="left" nowrap>🩺 <b>Doctor & logs</b></td><td align="left">One-click diagnostics and server log access</td></tr>
|
||||
</table>
|
||||
|
||||
```sh
|
||||
brew install --cask zoispag/tap/omniroute-tray
|
||||
```
|
||||
|
||||
<sub>Prefer a download? Grab the latest <code>.dmg</code> from
|
||||
<a href="https://github.com/zoispag/omniroute-tray/releases">Releases</a>. Source, issues and build
|
||||
docs live at <a href="https://github.com/zoispag/omniroute-tray">zoispag/omniroute-tray</a>.
|
||||
<br/>💛 A community project by <a href="https://github.com/zoispag">@zoispag</a> — not an official OmniRoute release.</sub>
|
||||
|
||||
<br/>
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🔒 Private & Local-First
|
||||
|
||||
</div>
|
||||
@@ -849,7 +810,7 @@ Tokens are scoped `read` / `write` / `admin`; process-spawning routes stay loopb
|
||||
|
||||
<div align="left">
|
||||
|
||||
<img src="./docs/diagrams/cli-terminal.svg" width="50%" alt="Animated terminal demoing the OmniRoute CLI — omniroute providers list, omniroute combo list and omniroute health — cycling over the 86-command top-level 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 …"/>
|
||||
<img src="./docs/diagrams/cli-terminal.svg" width="50%" alt="Animated terminal demoing the OmniRoute CLI — omniroute providers list, omniroute combo list and omniroute health — cycling over the 85-command top-level 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 …"/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -860,11 +821,11 @@ Expose OmniRoute over **MCP**, **A2A**, a **REST API**, **webhooks** or a **remo
|
||||
<table>
|
||||
<tr><th align="left">Interface</th><th align="left">Endpoint / command</th><th align="left">Use it for</th></tr>
|
||||
<tr><td align="left" nowrap>🧰 <b>MCP (stdio)</b></td><td align="left" nowrap><code>omniroute --mcp</code></td><td align="left">Plug into Claude Desktop, Cursor, any MCP client</td></tr>
|
||||
<tr><td align="left" nowrap>🌊 <b>MCP (HTTP)</b></td><td align="left" nowrap><code>/api/mcp/stream</code></td><td align="left">Remote MCP — <b>110 tools</b>, 33 scopes (enforcement opt-in), full audit trail</td></tr>
|
||||
<tr><td align="left" nowrap>🌊 <b>MCP (HTTP)</b></td><td align="left" nowrap><code>/api/mcp/stream</code></td><td align="left">Remote MCP — <b>110 tools</b>, 33 scopes, full audit trail</td></tr>
|
||||
<tr><td align="left" nowrap>📡 <b>MCP (SSE)</b></td><td align="left" nowrap><code>/api/mcp/sse</code></td><td align="left">Streaming MCP transport</td></tr>
|
||||
<tr><td align="left" nowrap>🤝 <b>A2A</b></td><td align="left" nowrap><code>/.well-known/agent.json</code></td><td align="left">Agent-to-agent, <b>JSON-RPC 2.0</b> + SSE, 6 skills</td></tr>
|
||||
<tr><td align="left" nowrap>🌐 <b>REST API</b></td><td align="left" nowrap><code>/v1/*</code></td><td align="left">OpenAI-compatible — chat, embeddings, images, audio, OCR</td></tr>
|
||||
<tr><td align="left" nowrap>🔔 <b>Webhooks</b></td><td align="left" nowrap><code>/api/webhooks</code></td><td align="left">Push request / quota events to Slack, Discord, Telegram or any URL</td></tr>
|
||||
<tr><td align="left" nowrap>🔔 <b>Webhooks</b></td><td align="left" nowrap><code>/api/webhooks</code></td><td align="left">Push events (usage, quota, errors, routing) to your URL</td></tr>
|
||||
<tr><td align="left" nowrap>🛰️ <b>Remote CLI</b></td><td align="left" nowrap><code>omniroute connect <host></code></td><td align="left">Drive a remote instance with scoped access tokens</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -956,8 +917,6 @@ The 12 engines above shrink what goes **in**. Three more layers shape **how**, *
|
||||
- **🪄 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.
|
||||
- **Ponytail (lazy senior dev)** — climb the YAGNI ladder, fix the root cause, smallest working diff.
|
||||
- **I have ADHD (action-first)** — next action leads, steps numbered, one concrete next step, no preamble.
|
||||
- **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.
|
||||
@@ -1056,7 +1015,7 @@ Full table: [Docker Guide — runtime RAM](docs/guides/DOCKER_GUIDE.md#runtime-r
|
||||
Standard `bun install` and global installation (`bun install -g omniroute`) are supported via Bun runtime detection:
|
||||
|
||||
- **Built-in `bun:sqlite`**: OmniRoute uses Bun's built-in `bun:sqlite` driver when running under Bun, falling back to `better-sqlite3` on Node.js or `sql.js`.
|
||||
- **Automatic Webpack bundler selection in dev**: Development (`bun run dev`) automatically detects Bun and disables Turbopack in favor of Webpack to prevent native V8 binding incompatibilities. Production builds (`bun run build`) follow `OMNIROUTE_USE_TURBOPACK` exactly as on Node: Turbopack by default, `OMNIROUTE_USE_TURBOPACK=0` to build with Webpack (`Dockerfile.bun` exposes it as a `--build-arg`).
|
||||
- **Automatic Webpack bundler selection**: Development (`bun run dev`) and production builds (`bun run build`) automatically detect Bun and disable Turbopack in favor of Webpack to prevent native V8 binding incompatibilities.
|
||||
- **Dedicated Bun Dockerfile**: Multi-stage `Dockerfile.bun` for native Bun production deployments (`docker build -f Dockerfile.bun -t omniroute:bun .`).
|
||||
|
||||
```bash
|
||||
@@ -1219,10 +1178,9 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
| 🐙 **GitHub** — follow for releases & tips | [@diegosouzapw](https://github.com/diegosouzapw) |
|
||||
| 💬 **Discord** | [discord.gg/U47eFqAXCn](https://discord.gg/U47eFqAXCn) |
|
||||
| ✈️ **Telegram** | [t.me/omnirouteOficial](https://t.me/omnirouteOficial) |
|
||||
| 🟢 **WhatsApp — 🌍 Global** | [join the group](https://chat.whatsapp.com/FvuCbrpZmQ6I85n2vW5QIC?s=cl&p=a&mlu=4) |
|
||||
| 🟢 **WhatsApp — 🇧🇷 Brasil** | [entrar no grupo](https://chat.whatsapp.com/KWgatljAjmbELQory59Oti?s=cl&p=a&mlu=4) |
|
||||
| 🟢 **WhatsApp — 🌍 Global** | [join the group](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) |
|
||||
| 🟢 **WhatsApp — 🇧🇷 Brasil** | [entrar no grupo](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz) |
|
||||
| 🌍 **Website** | [omniroute.online](https://omniroute.online) |
|
||||
| 🌍 **🌍StHub OmniRoute Community (free)** | [portal sthub](https://portal.sthub.com.br/communities/groups/st-hub/channels/Omniroute-World-8kRjmK) |
|
||||
| 📦 **Source code** | [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) |
|
||||
| 🐛 **Report a bug** | [open an issue](https://github.com/diegosouzapw/OmniRoute/issues) — attach `npm run system-info` output |
|
||||
| 🤝 **Contribute** | [CONTRIBUTING.md](CONTRIBUTING.md) · [Branching & Release Model](docs/ops/BRANCHING_MODEL.md) · pick a `good first issue` |
|
||||
@@ -1244,7 +1202,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>>=22.22.2 <23 || >=24.0.0 <27</code></td></tr>
|
||||
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
|
||||
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 169 migrations</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 120 domain modules, 167 migrations</td></tr>
|
||||
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
|
||||
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
|
||||
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>
|
||||
@@ -1305,9 +1263,9 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b><a href="docs/compression/COMPRESSION_RULES_FORMAT.md">Compression Rules Format</a></b></td><td>JSON rule-pack schemas for Caveman and RTK filters</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/compression/COMPRESSION_LANGUAGE_PACKS.md">Compression Language Packs</a></b></td><td>Language detection and Caveman rule-pack authoring</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/architecture/RESILIENCE_GUIDE.md">Resilience Guide</a></b></td><td>Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/routing/AUTO-COMBO.md">Auto-Combo Engine</a></b></td><td>16-factor scoring, mode packs, self-healing</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/routing/AUTO-COMBO.md">Auto-Combo Engine</a></b></td><td>15-factor scoring, mode packs, self-healing</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/ops/PROXY_GUIDE.md">Proxy Guide</a></b></td><td>3-level proxy system, 1proxy marketplace, registry CRUD</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 38 documented recurring pools / 437 cataloged free-tier entries</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 39 documented recurring pools / 445 cataloged free-tier entries</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/guides/FEATURES.md">Features Gallery</a></b></td><td>Visual dashboard tour with screenshots</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/architecture/CODEBASE_DOCUMENTATION.md">Codebase Documentation</a></b></td><td>Beginner-friendly codebase walkthrough</td></tr>
|
||||
</table>
|
||||
@@ -1332,7 +1290,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b><a href="docs/ops/BRANCHING_MODEL.md">Branching & Release Model</a></b></td><td>Where PRs target (<code>release/*</code>), what <code>main</code> and tags mean</td></tr>
|
||||
<tr><td nowrap><b><a href="CHANGELOG.md">Changelog</a></b></td><td>Full per-version release history</td></tr>
|
||||
<tr><td nowrap><b><a href="SECURITY.md">Security Policy</a></b></td><td>Vulnerability reporting and security practices</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/guides/I18N.md">i18n Guide</a></b></td><td>42-language support, translation workflow, RTL</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/guides/I18N.md">i18n Guide</a></b></td><td>43-language support, translation workflow, RTL</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/ops/RELEASE_CHECKLIST.md">Release Checklist</a></b></td><td>Pre-release validation steps</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/ops/COVERAGE_PLAN.md">Coverage Plan</a></b></td><td>Test coverage strategy for 39,000+ static test declarations across 5,100+ tracked test files</td></tr>
|
||||
</table>
|
||||
@@ -1708,7 +1666,7 @@ MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
**[⬆ Back to top](#-omniroute)** · Built with ❤️ for the open-source AI community.
|
||||
|
||||
<sub>OmniRoute v3.8.51 · Node ≥22.22.2 · MIT License · <a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
<sub>OmniRoute v3.8.50 · Node ≥22.22.2 · MIT License · <a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
|
||||
</div>
|
||||
<!-- GitHub Discussions enabled for community Q&A -->
|
||||
|
||||
@@ -1,56 +1,10 @@
|
||||
# Third-Party Notices
|
||||
|
||||
## wreq-js 3.2.0 native transport
|
||||
|
||||
OmniRoute ships `wreq-js@3.2.0` and its platform-specific native bindings for browser-
|
||||
fingerprinted HTTP transport. The npm package and all nine binding tarballs are tied by npm SLSA
|
||||
attestations to signed tag `v3.2.0` and immutable source commit
|
||||
[`0d52d5fa252841aeef34d4d063b1766a59612bf7`](https://github.com/sqdshguy/wreq-js/commit/0d52d5fa252841aeef34d4d063b1766a59612bf7).
|
||||
|
||||
- Root tarball:
|
||||
<https://registry.npmjs.org/wreq-js/-/wreq-js-3.2.0.tgz>
|
||||
- npm integrity:
|
||||
`sha512-dawhEbhvd5hxivKZSvv/mAQGO3mwZYESyctOvIIZ/H3DvQJzUM2UoFQsij0fg7hIClQ/GEQgg+2259UcFwhpMQ==`
|
||||
- Exact platform, integrity, size, and SHA-256 receipts for all nine native addons:
|
||||
[`config/release/wreq-js-native-manifest.json`](config/release/wreq-js-native-manifest.json)
|
||||
- Locked per-target Cargo closure, with runtime and compile-only packages kept separate:
|
||||
[`config/release/wreq-js-rust-license-inventory.json`](config/release/wreq-js-rust-license-inventory.json)
|
||||
- Deduplicated license texts and attribution notices for the conservative native runtime closure,
|
||||
including patched BoringSSL, Unicode ICU4X components, and Mozilla root-certificate data:
|
||||
[`config/release/wreq-js-rust-notices.md`](config/release/wreq-js-rust-notices.md)
|
||||
|
||||
The native tarballs themselves contain no LICENSE/NOTICE file. The bundled inventory is therefore
|
||||
shipped beside them. It intentionally over-approximates the locked link-eligible Cargo closure;
|
||||
exact post-LTO membership cannot be claimed without an upstream artifact SBOM/link map or a
|
||||
reproducible-build receipt. The Android addon also dynamically requires `libc++_shared.so`, which
|
||||
is not included in its npm tarball; any artifact that supplies that library needs its separate
|
||||
LLVM/Apache-with-LLVM-exception notice.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 will-work-for-meal
|
||||
Copyright (c) 2025 Oleksandr Herasymov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
||||
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
## codex-chatgpt-web
|
||||
|
||||
Parts of `open-sse/vendor/codex-chatgpt-web/` are adapted from
|
||||
[`miuuyy/codex-chatgpt-web`](https://github.com/miuuyy/codex-chatgpt-web), v4.0.7 commit
|
||||
`b59d7dc51b84fb1f465ff1d00f5207f3b2b4a494`.
|
||||
[`miuuyy/codex-chatgpt-web`](https://github.com/miuuyy/codex-chatgpt-web), commit
|
||||
`55592fca0ba19a27f1b769cec8fff61ff340a785`.
|
||||
|
||||
MIT License
|
||||
|
||||
|
||||
@@ -32,20 +32,17 @@ export function resolveChatGptWebCodexMcpEntry(rootDir = root, exists = existsSy
|
||||
return candidates.find((candidate) => exists(candidate)) ?? null;
|
||||
}
|
||||
|
||||
export async function loadChatGptWebCodexMcpModule(entry) {
|
||||
if (entry.endsWith(".ts")) {
|
||||
await import("tsx/esm");
|
||||
}
|
||||
return import(pathToFileURL(entry).href);
|
||||
}
|
||||
|
||||
export async function startChatGptWebCodexMcp(args = process.argv.slice(2), rootDir = root) {
|
||||
const socketIndex = args.indexOf("--broker-socket");
|
||||
const brokerSocketPath = socketIndex >= 0 ? args[socketIndex + 1] : undefined;
|
||||
if (!brokerSocketPath) throw new Error("--broker-socket is required");
|
||||
const entry = resolveChatGptWebCodexMcpEntry(rootDir);
|
||||
if (!entry) throw new Error("ChatGPT Web (Codex) MCP entrypoint was not found");
|
||||
const module = await loadChatGptWebCodexMcpModule(entry);
|
||||
if (entry.endsWith(".ts")) {
|
||||
const { register } = await import("node:module");
|
||||
register("tsx/esm", pathToFileURL(`${rootDir}/`));
|
||||
}
|
||||
const module = await import(pathToFileURL(entry).href);
|
||||
await module.runChatGptMcpServer({ brokerSocketPath });
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ bin/cli/
|
||||
├── provider-test.mjs ← testProviderApiKey()
|
||||
├── settings-store.mjs ← DB CRUD for key_value settings
|
||||
├── locales/
|
||||
│ ├── en.json ← English strings (source of truth, 42 locales)
|
||||
│ ├── en.json ← English strings (source of truth, 43 locales)
|
||||
│ ├── pt-BR.json ← Portuguese (Brazil) — fully translated
|
||||
│ └── {locale}.json ← 41 additional locales (ar, az, de, es, fr, ja, zh-CN, …)
|
||||
│ └── {locale}.json ← 42 additional locales (ar, az, de, es, fr, ja, zh-CN, …)
|
||||
├── scripts/
|
||||
│ └── generate-locales.mjs ← scaffold new locale files from config/i18n.json
|
||||
└── commands/
|
||||
|
||||
@@ -5,11 +5,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { platform, totalmem } from "node:os";
|
||||
import { t } from "../i18n.mjs";
|
||||
import { writePidFile, cleanupPidFile, waitForServer } from "../utils/pid.mjs";
|
||||
import {
|
||||
ServerSupervisor,
|
||||
detectMitmCrash,
|
||||
BUN_PRELOAD_PATH,
|
||||
} from "../runtime/processSupervisor.mjs";
|
||||
import { ServerSupervisor, detectMitmCrash } from "../runtime/processSupervisor.mjs";
|
||||
import { isTermux } from "../../../scripts/build/postinstallSupport.mjs";
|
||||
import {
|
||||
ensureAndroidCacheDir,
|
||||
@@ -310,7 +306,7 @@ function runDaemon(serverJs, env, memoryLimit, dashboardPort, apiPort) {
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
[
|
||||
...(process.versions.bun
|
||||
? ["--preload", BUN_PRELOAD_PATH]
|
||||
? ["--preload", join(APP_DIR, "open-sse/utils/setupPolyfill.ts")]
|
||||
: buildNodeHeapArgs(process.env, memoryLimit)),
|
||||
serverJs,
|
||||
],
|
||||
@@ -335,7 +331,7 @@ function runWithoutRecovery(serverJs, env, memoryLimit, dashboardPort, apiPort,
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
[
|
||||
...(process.versions.bun
|
||||
? ["--preload", BUN_PRELOAD_PATH]
|
||||
? ["--preload", join(APP_DIR, "open-sse/utils/setupPolyfill.ts")]
|
||||
: buildNodeHeapArgs(process.env, memoryLimit)),
|
||||
serverJs,
|
||||
],
|
||||
@@ -427,9 +423,7 @@ async function runWithSupervisor(
|
||||
if (detectMitmCrash(crashLog)) {
|
||||
try {
|
||||
const PROJECT_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
||||
const { updateSettings } = await import(
|
||||
pathToFileURL(join(PROJECT_ROOT, "src/lib/db/settings.ts")).href
|
||||
);
|
||||
const { updateSettings } = await import(pathToFileURL(join(PROJECT_ROOT, "src/lib/db/settings.ts")).href);
|
||||
updateSettings({ mitmEnabled: false });
|
||||
} catch {}
|
||||
return "disable-mitm-and-retry";
|
||||
|
||||
@@ -5,25 +5,6 @@ import { fileURLToPath } from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const LOCALES_DIR = join(__dirname, "locales");
|
||||
const FALLBACK_LOCALE = "en";
|
||||
const I18N_CONFIG_PATH = join(__dirname, "..", "..", "config", "i18n.json");
|
||||
let aliasMap = null; // lower-case tag → canonical locale code
|
||||
|
||||
function loadAliasMap() {
|
||||
if (aliasMap) return aliasMap;
|
||||
aliasMap = new Map();
|
||||
try {
|
||||
const { locales } = JSON.parse(readFileSync(I18N_CONFIG_PATH, "utf8"));
|
||||
for (const entry of locales) {
|
||||
aliasMap.set(entry.code.toLowerCase(), entry.code);
|
||||
for (const alias of entry.aliases || []) {
|
||||
aliasMap.set(String(alias).toLowerCase(), entry.code);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// config absent (trimmed package): keep file-based detection only
|
||||
}
|
||||
return aliasMap;
|
||||
}
|
||||
|
||||
const cache = new Map();
|
||||
let activeLocale = null;
|
||||
@@ -43,16 +24,8 @@ function normalize(raw) {
|
||||
const stripped = String(raw).split(".")[0].replaceAll("_", "-");
|
||||
if (!stripped || !/^[a-zA-Z0-9-]+$/.test(stripped)) return FALLBACK_LOCALE;
|
||||
if (hasCatalog(stripped)) return stripped;
|
||||
const lower = stripped.toLowerCase();
|
||||
const base = lower.split("-")[0];
|
||||
const aliases = loadAliasMap();
|
||||
const viaAlias = aliases.get(lower) ?? aliases.get(base);
|
||||
if (viaAlias && hasCatalog(viaAlias)) return viaAlias;
|
||||
const base = stripped.split("-")[0];
|
||||
if (hasCatalog(base)) return base;
|
||||
const regional = [...new Set(aliases.values())].find(
|
||||
(code) => code.includes("-") && code.toLowerCase().split("-")[0] === base
|
||||
);
|
||||
if (regional && hasCatalog(regional)) return regional;
|
||||
return FALLBACK_LOCALE;
|
||||
}
|
||||
|
||||
@@ -130,5 +103,4 @@ export function resetForTests() {
|
||||
cache.clear();
|
||||
activeLocale = null;
|
||||
fallbackCatalog = null;
|
||||
aliasMap = null;
|
||||
}
|
||||
|
||||
5
bin/cli/locales/in.json
Normal file
5
bin/cli/locales/in.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"redis": {
|
||||
"description": "Luncurkan kontainer Redis lokal dengan satu klik (Podman atau Docker) untuk caching dan pelacakan kuota OmniRoute"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { writePidFile, cleanupPidFile, killAllSubprocesses, isPidRunning } from "../utils/pid.mjs";
|
||||
import {
|
||||
RESTART_RESET_MS,
|
||||
@@ -18,24 +17,6 @@ import {
|
||||
|
||||
const CRASH_LOG_LINES = 50;
|
||||
|
||||
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
||||
// Bun needs the Node-compat polyfill preloaded (#9761). The file ships at the
|
||||
// package root via package.json "files" (see scripts/build/pack-artifact-policy.ts)
|
||||
// and is never copied into dist/, so the path must resolve against the package
|
||||
// root — resolving it next to the server bundle fails with "preload not found" (#11980).
|
||||
export const BUN_PRELOAD_PATH = join(PACKAGE_ROOT, "open-sse", "utils", "setupPolyfill.ts");
|
||||
|
||||
/**
|
||||
* Argument vector for the server child. Kept pure so tests can assert on it
|
||||
* directly: the bare `import { spawn }` above cannot be intercepted without
|
||||
* --experimental-test-module-mocks (same seam as #8131).
|
||||
*/
|
||||
export function buildServerSpawnArgs(serverPath, memoryLimit, env = process.env) {
|
||||
return process.versions.bun
|
||||
? ["--preload", BUN_PRELOAD_PATH, serverPath]
|
||||
: buildNodeRuntimeArgs(env, memoryLimit, serverPath);
|
||||
}
|
||||
|
||||
export class ServerSupervisor {
|
||||
constructor({
|
||||
serverPath,
|
||||
@@ -74,11 +55,21 @@ export class ServerSupervisor {
|
||||
// Node args come from buildNodeRuntimeArgs (#9209 IPv4-first DNS + #5238
|
||||
// heap flag handling); the Bun branch keeps #9761's polyfill preload —
|
||||
// Bun does not accept the Node-only flags.
|
||||
this.child = spawn(process.execPath, buildServerSpawnArgs(this.serverPath, this.memoryLimit), {
|
||||
cwd: dirname(this.serverPath),
|
||||
env: this.env,
|
||||
stdio: showLog ? "inherit" : ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
this.child = spawn(
|
||||
process.execPath,
|
||||
process.versions.bun
|
||||
? [
|
||||
"--preload",
|
||||
join(dirname(this.serverPath), "open-sse/utils/setupPolyfill.ts"),
|
||||
this.serverPath,
|
||||
]
|
||||
: buildNodeRuntimeArgs(process.env, this.memoryLimit, this.serverPath),
|
||||
{
|
||||
cwd: dirname(this.serverPath),
|
||||
env: this.env,
|
||||
stdio: showLog ? "inherit" : ["ignore", "pipe", "pipe"],
|
||||
}
|
||||
);
|
||||
|
||||
writePidFile("server", this.child.pid);
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
* For top-tier languages, a translated `common` + `program` section is included.
|
||||
* All other keys fall back to `en` via i18n.mjs's existing fallback mechanism.
|
||||
*
|
||||
* Run: node bin/cli/scripts/generate-locales.mjs [--force] [--code=<locale>]
|
||||
* --code=<locale> generate only that locale (scripts/i18n/add-locale.mjs uses it
|
||||
* to scaffold the catalog of the locale it is adding)
|
||||
* Run: node bin/cli/scripts/generate-locales.mjs [--force]
|
||||
*/
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
@@ -19,13 +17,8 @@ const ROOT = join(__dirname, "..", "..", "..");
|
||||
const LOCALES_DIR = join(__dirname, "..", "locales");
|
||||
const I18N_CFG = join(ROOT, "config", "i18n.json");
|
||||
const FORCE = process.argv.includes("--force");
|
||||
const ONLY = process.argv.find((arg) => arg.startsWith("--code="))?.slice("--code=".length) || null;
|
||||
|
||||
const { locales } = JSON.parse(readFileSync(I18N_CFG, "utf8"));
|
||||
if (ONLY && !locales.some((locale) => locale.code === ONLY)) {
|
||||
console.error(`--code=${ONLY} is not listed in config/i18n.json`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// common + program translations for each language code.
|
||||
// Keys that are absent fall back to en automatically.
|
||||
@@ -890,12 +883,12 @@ const TRANSLATIONS = {
|
||||
|
||||
// Languages with no translation in this script — will be created as empty objects
|
||||
// All keys fall back to `en` via i18n.mjs's fallback mechanism.
|
||||
const SCAFFOLD_ONLY = ["bn", "gu", "he", "mr", "ms", "phi", "sw", "ta", "te", "ur"];
|
||||
const SCAFFOLD_ONLY = ["bn", "gu", "he", "in", "mr", "ms", "phi", "sw", "ta", "te", "ur"];
|
||||
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const locale of locales.filter((candidate) => !ONLY || candidate.code === ONLY)) {
|
||||
for (const locale of locales) {
|
||||
const { code } = locale;
|
||||
if (code === "en" || code === "pt-BR") {
|
||||
skipped++;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`.
|
||||
@@ -1 +0,0 @@
|
||||
- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior.
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)).
|
||||
@@ -1 +0,0 @@
|
||||
- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White)
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403)
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones)
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496)
|
||||
@@ -1,6 +0,0 @@
|
||||
- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls`
|
||||
- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite)
|
||||
- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations`
|
||||
- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list`
|
||||
- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth
|
||||
- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic
|
||||
@@ -1,5 +0,0 @@
|
||||
- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the
|
||||
`agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch
|
||||
trigger, and relaxes its background poll from 5s to 30s while that WS connection is up —
|
||||
falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect
|
||||
transition, whenever the socket is down.
|
||||
@@ -1,11 +0,0 @@
|
||||
- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider
|
||||
filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL
|
||||
so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a
|
||||
"copy trace JSON" action and hardened error/empty-state and accessibility handling; the
|
||||
Agents-tab edges now animate traveling particles along active (running) connections; and the
|
||||
canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*`
|
||||
CSS custom properties, so they adapt correctly to light/dark mode.
|
||||
- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test
|
||||
coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the
|
||||
`?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and
|
||||
`showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271).
|
||||
@@ -1 +0,0 @@
|
||||
- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design
|
||||
@@ -1,8 +0,0 @@
|
||||
- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of
|
||||
finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell
|
||||
opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle
|
||||
transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via
|
||||
`OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new
|
||||
`GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to
|
||||
persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are
|
||||
not persisted locally — the tab says so instead of silently omitting them.
|
||||
@@ -1,24 +0,0 @@
|
||||
- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent,
|
||||
A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the
|
||||
confirm window to fire) re-submits the original prompt/input as a new run. The button is
|
||||
disabled with an explanatory tooltip whenever the original input can't be recovered from the
|
||||
loaded task detail (e.g. it never carried a prompt, or the detail failed to load).
|
||||
Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which
|
||||
authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY`
|
||||
makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error
|
||||
line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the
|
||||
whole skill run, so the success confirmation only appears once the repeated task finishes.
|
||||
A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the
|
||||
endpoint's auth posture is an operator decision, not a side effect of this feature.
|
||||
- **feat(a2a):** A2A task execution now records which memories were consulted for the task's
|
||||
last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits`
|
||||
history event, purely for observability — the retrieved memory is never injected into a
|
||||
skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default
|
||||
enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used"
|
||||
section lists these hits for a2a tasks and is omitted whenever there are none. Known
|
||||
limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a
|
||||
SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no
|
||||
hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits
|
||||
are also kept out of the task's own `input` (and therefore out of the persisted input and of
|
||||
the "Repeat" request body), so repeating a task never re-sends the previous run's memory
|
||||
snippets.
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER))
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session.
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `<tool>` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live `<think>`/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider.
|
||||
@@ -1 +0,0 @@
|
||||
- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs.
|
||||
@@ -1,3 +0,0 @@
|
||||
- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics.
|
||||
- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests.
|
||||
- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged.
|
||||
@@ -1,5 +0,0 @@
|
||||
- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists
|
||||
for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or
|
||||
untyped (#11761)
|
||||
- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it
|
||||
returns when `dimensions` is not sent (#11761)
|
||||
@@ -1 +0,0 @@
|
||||
- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151))
|
||||
@@ -1,4 +0,0 @@
|
||||
- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking
|
||||
context cache protection, or emptying the system message or tool filter, sends an explicit
|
||||
`null` instead of dropping the field from the `PUT` body, which the update merge read as
|
||||
"leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh
|
||||
@@ -1,5 +0,0 @@
|
||||
- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is
|
||||
measured from the first embedding that comes back instead of being read from a registry
|
||||
that cannot describe them, so `vec_memories` is created and memories stop piling up
|
||||
unvectorized behind a green health check
|
||||
([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)).
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائهدهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: <built-in>` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581))
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted
|
||||
@@ -1 +0,0 @@
|
||||
- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback.
|
||||
@@ -1 +0,0 @@
|
||||
- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7).
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user