Compare commits
94 Commits
fix/10225-
...
release/v3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63a6618d34 | ||
|
|
fb2585530d | ||
|
|
dc32732b2a | ||
|
|
611466b419 | ||
|
|
9081b57146 | ||
|
|
7f5275ed6b | ||
|
|
6c50137eeb | ||
|
|
8ee778fabb | ||
|
|
db0b4a1955 | ||
|
|
e3bca29bbc | ||
|
|
6ff2e7b2c2 | ||
|
|
24ef1dc3d4 | ||
|
|
722748f7c1 | ||
|
|
0f402a84a4 | ||
|
|
14afdcb923 | ||
|
|
b6d2b4a41c | ||
|
|
a87c9236ff | ||
|
|
2098ba3848 | ||
|
|
b8b78f7a69 | ||
|
|
fbc67f1338 | ||
|
|
b1a2ff6887 | ||
|
|
4540d303d7 | ||
|
|
dcfbc24625 | ||
|
|
8dec2ad472 | ||
|
|
31b02ff85f | ||
|
|
48e5cf7fe4 | ||
|
|
8bd0b840f6 | ||
|
|
3e8a8f71cc | ||
|
|
8ff3a1dda3 | ||
|
|
faeca3bbac | ||
|
|
b082d0735b | ||
|
|
2723698fe2 | ||
|
|
bdc30ca4dd | ||
|
|
9bfdc15cbc | ||
|
|
33e0fea8b0 | ||
|
|
b17dfa4a14 | ||
|
|
5ca747f6a5 | ||
|
|
be364d2c70 | ||
|
|
810c6b9843 | ||
|
|
e646fe84c7 | ||
|
|
c6c134300b | ||
|
|
2162289f0a | ||
|
|
6d9336088c | ||
|
|
e5e1358693 | ||
|
|
8bd0e7b6bf | ||
|
|
6b85413b87 | ||
|
|
e1739fc71d | ||
|
|
4c7b902257 | ||
|
|
326d0e81cb | ||
|
|
d010a9979f | ||
|
|
be6f18b849 | ||
|
|
d46e8d72c9 | ||
|
|
20fcb8d205 | ||
|
|
579cae32b1 | ||
|
|
df226e55f4 | ||
|
|
e44a409aa9 | ||
|
|
b75f7dde93 | ||
|
|
3c8432791e | ||
|
|
142bd5019f | ||
|
|
d9e24d84d8 | ||
|
|
94cf4c402a | ||
|
|
6e97fbf340 | ||
|
|
201c234b96 | ||
|
|
149049ca4a | ||
|
|
b19e9772bc | ||
|
|
684ea70fb3 | ||
|
|
4b76d3b76f | ||
|
|
0b347eaea1 | ||
|
|
47f53f37ea | ||
|
|
757b195540 | ||
|
|
cb51facf12 | ||
|
|
710e43eb97 | ||
|
|
b67d9ef353 | ||
|
|
462f4fc9da | ||
|
|
dd4a33d1d8 | ||
|
|
5a7487a60a | ||
|
|
5239728d6f | ||
|
|
595d04dad9 | ||
|
|
aa5b77eb6e | ||
|
|
1a4a55cfc0 | ||
|
|
dc562d93ca | ||
|
|
b1d710d45b | ||
|
|
382b2fba26 | ||
|
|
782e480061 | ||
|
|
e315082887 | ||
|
|
972c4594b6 | ||
|
|
5379493bed | ||
|
|
282c087c27 | ||
|
|
d33e62af9c | ||
|
|
f466ea91c9 | ||
|
|
8ff7f7daf0 | ||
|
|
e168b2347e | ||
|
|
4adf50dbcb | ||
|
|
370c1b9ae7 |
42
.env.example
@@ -714,6 +714,16 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# Allow OmniRoute to write CLI config files (token refresh, etc.).
|
||||
# CLI_ALLOW_CONFIG_WRITES=true
|
||||
|
||||
# Force container detection on (1/true) or off (0/false). Leave unset for auto-detect
|
||||
# via /.dockerenv, /run/.containerenv, cgroup markers, or KUBERNETES_SERVICE_HOST.
|
||||
# Used by: src/shared/utils/containerEnv.ts — gates ephemeral-home CLI config writes.
|
||||
# OMNIROUTE_CONTAINER=1
|
||||
|
||||
# Allow CLI-tool config writes into an unmounted container path anyway (default off).
|
||||
# Prefer host-side `omniroute configure` / Remote Mode, or a bind-mounted CLI_CONFIG_HOME.
|
||||
# CLI equivalent: --allow-container-write. Used by: src/shared/utils/containerEnv.ts
|
||||
# OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true
|
||||
|
||||
# Auto-sync CLI profile files after provider model discovery changes. OPT-IN, default OFF for
|
||||
# both. When enabled, writes only the tool's profile files (~/.codex/*.config.toml or
|
||||
# ~/.claude/profiles/<name>/settings.json); never changes the active/default config. Both also
|
||||
@@ -735,6 +745,21 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# CLI_AUGGIE_BIN=auggie
|
||||
# AUGGIE_BIN=auggie
|
||||
|
||||
# ── ZCode (Z.ai GLM coding-plan CLI) local provider ──
|
||||
# The local "zcode" provider talks to the authenticated ZCode app-server over a
|
||||
# custom framed stdio protocol. Overrides below tune that stdio lifecycle.
|
||||
# ZCODE_BIN=zcode
|
||||
# ZCODE_ARGS=["--some-flag"]
|
||||
# ZCODE_CWD=
|
||||
# ZCODE_PROVIDER_ID=builtin:zai-coding-plan
|
||||
# ZCODE_SERVER_RUNTIME_ROOT=~/.zcode/server
|
||||
# ZCODE_SERVER_NODE=~/.zcode/server/node
|
||||
# ZCODE_SERVER_ENTRY=~/.zcode/server/zcode-server.cjs
|
||||
# ZCODE_STARTUP_TIMEOUT_MS=10000
|
||||
# ZCODE_RPC_TIMEOUT_MS=30000
|
||||
# ZCODE_TURN_TIMEOUT_MS=120000
|
||||
# ZCODE_POLL_INTERVAL_MS=250
|
||||
|
||||
# Override the Hermes Agent home directory (where OmniRoute reads/writes the
|
||||
# Hermes CLI config). Matches the env var the Hermes PowerShell installer sets
|
||||
# on Windows (%LOCALAPPDATA%\hermes); defaults to ~/.hermes when unset.
|
||||
@@ -1519,6 +1544,10 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 100000
|
||||
# PROXY_LOGS_TABLE_MAX_ROWS=100000
|
||||
|
||||
# Include client/egress IPs and account prefixes in [ProxyEgress] console logs.
|
||||
# Default: false (the dashboard/database proxy-log records retain full details).
|
||||
# PROXY_LOG_INCLUDE_IPS=false
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 17. MEMORY OPTIMIZATION (Low-RAM / Docker)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1869,6 +1898,13 @@ APP_LOG_TO_FILE=true
|
||||
# PROXY_AUTO_REMOVE=false
|
||||
# Consecutive failures before an auto-remove fires. Default: 3.
|
||||
# PROXY_AUTO_REMOVE_AFTER=3
|
||||
# Set "true" to let the scheduler auto-disable (status "dead") proxies after
|
||||
# repeated failures instead of deleting them. Non-destructive alternative to
|
||||
# PROXY_AUTO_REMOVE — the row stays in the registry, drops out of pool/rotation
|
||||
# resolution immediately, and is automatically re-activated once it starts
|
||||
# answering probes again. Shares the PROXY_AUTO_REMOVE_AFTER threshold above.
|
||||
# If both PROXY_AUTO_REMOVE and PROXY_AUTO_DISABLE are "true", auto-remove wins.
|
||||
# PROXY_AUTO_DISABLE=false
|
||||
# Let automated reachability probes (the scheduler + the "Test All" button) WRITE
|
||||
# a proxy's status. Default "false": probes are read-only and never deactivate a
|
||||
# proxy — only the operator sets active/inactive (a flaky probe must not strand an
|
||||
@@ -2373,6 +2409,12 @@ APP_LOG_TO_FILE=true
|
||||
# intended to be published as `omniroute-secure`. See SECURITY.md.
|
||||
# OMNIROUTE_BUILD_PROFILE=full
|
||||
|
||||
# Skip emitting `.tar.gz` tarballs during optional-pack staging for the Electron
|
||||
# standalone tree (pack directories + optional-packs.index.json are still produced).
|
||||
# Used by the desktop release workflow to trim artifact upload size.
|
||||
# Default (when unset): 1 (tarballs emitted). Set to 0 to disable.
|
||||
# OMNIROUTE_OPTIONAL_PACK_TAR=1
|
||||
|
||||
# Electron smoke harness (used by scripts/dev/smoke-electron-packaged.mjs).
|
||||
# ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login
|
||||
# ELECTRON_SMOKE_TIMEOUT_MS=45000
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
@@ -697,11 +697,12 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
needs: build
|
||||
# WS1.5 (v3.8.49 plan): the Electron rebuild/spawn path previously executed for
|
||||
# WS1.5 (v3.8.49 plan): the Electron native-module path previously executed for
|
||||
# the FIRST time on the release tag — the v3.8.48 Windows bug (npx.cmd spawned
|
||||
# without shell, CVE-2024-27980 behavior change) could only surface at release.
|
||||
# windows-latest runs prepare:bundle (the ABI rebuild + spawn plan) per release
|
||||
# PR; ubuntu keeps the full pack + headless smoke.
|
||||
# windows-latest runs prepare:bundle (better-sqlite3 prebuild verification since
|
||||
# v13 — the node-gyp rebuild is gone) per release PR; ubuntu keeps the full
|
||||
# pack + headless smoke.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -738,7 +739,7 @@ jobs:
|
||||
# precedent): its first-ever real run (2026-07-15, run 29457533565) died in
|
||||
# 0.7s with the error swallowed by pwsh — bash shell captures stderr and
|
||||
# continue-on-error keeps the heavy gate green while we harden it (#7336).
|
||||
- name: Prepare Electron standalone (Windows ABI rebuild + spawn path)
|
||||
- name: Prepare Electron standalone (Windows prebuild verification)
|
||||
if: runner.os == 'Windows'
|
||||
working-directory: electron
|
||||
continue-on-error: true
|
||||
|
||||
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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
- uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
queries: security-extended
|
||||
- uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
- uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
2
.github/workflows/dast-smoke.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- name: Build CLI bundle
|
||||
env:
|
||||
OMNIROUTE_BUILD_BACKEND_ONLY: "1"
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
@@ -372,7 +372,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.4
|
||||
uses: github/codeql-action/upload-sarif@v4.37.6
|
||||
with:
|
||||
sarif_file: trivy-results.sarif
|
||||
category: trivy-image
|
||||
|
||||
116
.github/workflows/electron-release.yml
vendored
@@ -55,9 +55,75 @@ jobs:
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "✓ Valid version: $VERSION"
|
||||
|
||||
web-build:
|
||||
name: Build shared Next standalone
|
||||
needs: validate
|
||||
# Stage 8 (issue #10321): the four desktop legs used to each run the full
|
||||
# `npm run build` (Next standalone) — ~111 runner-minutes per release just to
|
||||
# produce the same platform-independent bundle four times. This job builds it
|
||||
# once on ubuntu; every leg then restores the byte-verified archive and
|
||||
# re-forks its native optionals (scripts/build/standaloneBundle.mjs).
|
||||
#
|
||||
# Rollback lever: set the repo variable ELECTRON_SHARED_STANDALONE=disabled.
|
||||
# This job then skips, every leg falls back to building its own web bundle
|
||||
# (the legacy step below), and the pipeline behaves exactly like pre-Stage 8 —
|
||||
# no revert needed.
|
||||
if: ${{ !cancelled() && needs.validate.result == 'success' && vars.ELECTRON_SHARED_STANDALONE != 'disabled' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
NPM_CONFIG_LEGACY_PEER_DEPS: true
|
||||
|
||||
- name: Build Next.js standalone
|
||||
# webpack, not Turbopack, for the same hosted-runner RAM reason as the
|
||||
# linux leg (see the long comment on the fallback step in `build`).
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
NODE_OPTIONS: "--max_old_space_size=6144"
|
||||
OMNIROUTE_USE_TURBOPACK: "0"
|
||||
run: npm run build
|
||||
|
||||
- name: Pack standalone bundle
|
||||
# Deterministic tar.gz + byte-level manifest; the manifest embeds the
|
||||
# archive's own sha256 so artifact-transfer corruption is caught before
|
||||
# extraction, and every entry is re-verified after extraction.
|
||||
run: node scripts/build/standaloneBundle.mjs pack --out web-bundle.tar.gz
|
||||
|
||||
- name: Upload shared web bundle
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: web-standalone-bundle
|
||||
# compression-level 0: the payload is already a deterministic tar.gz;
|
||||
# re-zipping would only burn runner CPU without shrinking it further.
|
||||
compression-level: 0
|
||||
# Legs consume this within minutes; no reason to retain it like the
|
||||
# installer artifacts (default 90d).
|
||||
retention-days: 3
|
||||
path: |
|
||||
web-bundle.tar.gz
|
||||
web-bundle.tar.gz.manifest.json
|
||||
|
||||
build:
|
||||
name: Build Electron (${{ matrix.platform }})
|
||||
needs: validate
|
||||
needs: [validate, web-build]
|
||||
# `web-build` is skipped when ELECTRON_SHARED_STANDALONE=disabled (rollback
|
||||
# mode); legs then run the legacy per-leg web build below. If it ran and
|
||||
# failed, fail closed: legs cannot package without the bundle, and silently
|
||||
# falling back to four per-leg builds would hide exactly the regression the
|
||||
# shared job exists to surface.
|
||||
if: ${{ !cancelled() && needs.validate.result == 'success' && (needs.web-build.result == 'success' || needs.web-build.result == 'skipped') }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: write # electron-builder may publish artifacts with GH_TOKEN
|
||||
@@ -69,19 +135,27 @@ jobs:
|
||||
runner: windows-latest
|
||||
target: win
|
||||
ext: .exe
|
||||
os: win32
|
||||
arch: x64
|
||||
- platform: macos-intel
|
||||
runner: macos-15-intel
|
||||
target: mac-x64
|
||||
ext: .dmg
|
||||
os: darwin
|
||||
arch: x64
|
||||
- platform: macos-arm64
|
||||
runner: macos-latest
|
||||
target: mac-arm64
|
||||
ext: -arm64.dmg
|
||||
os: darwin
|
||||
arch: arm64
|
||||
- platform: linux
|
||||
runner: ubuntu-latest
|
||||
target: linux
|
||||
ext: .AppImage
|
||||
deb_ext: .deb
|
||||
os: linux
|
||||
arch: x64,arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
@@ -93,14 +167,6 @@ jobs:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
@@ -116,7 +182,11 @@ jobs:
|
||||
mkdir -p "$RUNNER_TEMP/home"
|
||||
echo "USERPROFILE=$RUNNER_TEMP/home" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build Next.js standalone
|
||||
- name: Build Next.js standalone (legacy per-leg fallback)
|
||||
# Stage 8: only runs in rollback mode (ELECTRON_SHARED_STANDALONE=disabled)
|
||||
# or when the shared web-build job was skipped. Otherwise the leg restores
|
||||
# the shared bundle from the `web-build` job below.
|
||||
if: needs.web-build.result == 'skipped'
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
NODE_OPTIONS: "--max_old_space_size=6144"
|
||||
@@ -134,6 +204,30 @@ jobs:
|
||||
OMNIROUTE_USE_TURBOPACK: ${{ matrix.platform == 'linux' && '0' || '1' }}
|
||||
run: npm run build
|
||||
|
||||
- name: Download shared web bundle
|
||||
# Stage 8: inverse of the fallback step above — runs exactly when the
|
||||
# shared `web-build` job produced the bundle.
|
||||
if: needs.web-build.result == 'success'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: web-standalone-bundle
|
||||
|
||||
- name: Restore + hydrate shared web bundle
|
||||
if: needs.web-build.result == 'success'
|
||||
shell: bash
|
||||
# restore: verify the archive's sha256 against the manifest, extract, then
|
||||
# re-verify every entry (existence + size + content hash + symlink
|
||||
# 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-*,
|
||||
# fsevents) carry linux forks. Replace them with the forks this
|
||||
# leg's own `npm ci` resolved, then assert every bundled native
|
||||
# (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
|
||||
node scripts/build/standaloneBundle.mjs hydrate --platform ${{ matrix.os }} --arch ${{ matrix.arch }}
|
||||
|
||||
- name: Sync version in electron/package.json
|
||||
shell: bash
|
||||
env:
|
||||
@@ -158,7 +252,7 @@ jobs:
|
||||
|
||||
- name: Install Electron dependencies
|
||||
working-directory: electron
|
||||
run: npm install --no-audit --no-fund
|
||||
run: npm ci --no-audit --no-fund
|
||||
|
||||
- name: Build Electron for ${{ matrix.platform }}
|
||||
working-directory: electron
|
||||
|
||||
12
.github/workflows/quality.yml
vendored
@@ -137,7 +137,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
# One walk of src/app/api for openapi-routes + docs-symbols (both still fail independently).
|
||||
- run: npm run check:api-docs-refs
|
||||
- name: Docs accuracy (fabricated-docs + i18n mirrors, strict)
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- name: Restore ESLint file cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
# WS5.2/5.3: JUnit feeds Trunk Flaky Tests — the fast-path runs on EVERY PR,
|
||||
# which is where flaky-detection volume actually comes from (ci.yml's heavy
|
||||
# jobs only run on the release PR). Advisory upload, own-origin only.
|
||||
@@ -476,7 +476,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
# QW-d: fonte única — o mesmo npm script do CI pesado/local. Fecha dois drifts do
|
||||
# comando inline antigo: os dirs `memory` e `usage` estavam FORA do glob (testes
|
||||
# silenciosamente não rodavam no fast path) e o setupPolyfill não era importado.
|
||||
@@ -516,7 +516,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- name: Restore ESLint file cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
@@ -583,7 +583,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- name: CHANGELOG integrity (nenhum bullet da base pode sumir no merge-result)
|
||||
run: npm run check:changelog-integrity
|
||||
- name: Agent-skills generator sync (SKILL.md gerado ≡ catálogo)
|
||||
|
||||
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# project-specific directories
|
||||
/output/
|
||||
.slim/deepwork/
|
||||
.omnivscodeagent/
|
||||
omnirouteCloud/
|
||||
@@ -69,6 +70,8 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
# Local gitleaks artifacts (do not commit)
|
||||
gitleaks-local.json
|
||||
!.env.example
|
||||
!.env.homolog.example
|
||||
!.env.devin-bridge.example
|
||||
|
||||
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 340 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 341 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -56,7 +56,7 @@ 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 (148 migrations) |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (150 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 109 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 |
|
||||
|
||||
@@ -167,6 +167,7 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **providers**: honor `PATCH /api/providers/[id]` so `omniroute providers rotate` stops 405ing (the OpenAPI spec and CLI already use PATCH) (PR #10366)
|
||||
- **executors**: fix internal timeout misclassified as client disconnect (499) for 7 niche executors — pass TimeoutError reason to controller.abort() (#8197 side-finding)
|
||||
- test(combo): guard auto/best-free never leaks the combo name as a model (#7754)
|
||||
- fix(vision-bridge): describe-model no longer returns unreachable "openai/gpt-4o-mini" when every vision-capable provider is unreachable on the instance — returns null instead and surfaces a clear error (#8430)
|
||||
|
||||
22
Dockerfile
@@ -8,8 +8,8 @@ WORKDIR /app
|
||||
# that already have a fix published in trixie. CVEs without an upstream fix yet
|
||||
# (local-only TOCTOU, etc.) remain until the distro patches them and the image
|
||||
# is rebuilt; none are reachable from the proxy's request surface at runtime.
|
||||
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
|
||||
@@ -61,8 +61,8 @@ FROM base AS builder
|
||||
|
||||
# Build tools for native module compilation
|
||||
# apt-get update needed here because base's rm -rf clears the shared cache
|
||||
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -108,7 +108,7 @@ RUN test -f package-lock.json \
|
||||
# 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=npm-cache,target=/root/.npm \
|
||||
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) \
|
||||
@@ -158,7 +158,7 @@ ARG OMNIROUTE_BUILD_MEMORY_MB=4096
|
||||
ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_BUILD_MEMORY_MB}"
|
||||
|
||||
COPY . ./
|
||||
RUN --mount=type=cache,id=next-cache,target=/app/.build/next/cache \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-next-cache,target=/app/.build/next/cache \
|
||||
mkdir -p /app/data \
|
||||
&& npm run build \
|
||||
&& node --input-type=module -e "import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; const standaloneRoot = '/app/.build/next/standalone/node_modules/'; const require = createRequire('/app/.build/next/standalone/package.json'); for (const pkg of ['@atjsh/llmlingua-2', '@huggingface/transformers', '@tensorflow/tfjs', 'js-tiktoken']) { const resolved = require.resolve(pkg); if (!resolved.startsWith(standaloneRoot)) throw new Error(pkg + ' resolved outside standalone: ' + resolved); await import(pathToFileURL(resolved).href); } const onnxRuntime = require.resolve('onnxruntime-node'); if (!onnxRuntime.startsWith(standaloneRoot)) throw new Error('onnxruntime-node resolved outside standalone: ' + onnxRuntime); await import(pathToFileURL(onnxRuntime).href);"
|
||||
@@ -262,8 +262,8 @@ COPY --from=builder /app/node_modules/playwright ./node_modules/playwright
|
||||
# browsers land under /home/node which persists across image layers and is
|
||||
# accessible to the non-root runtime user.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright
|
||||
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& node node_modules/playwright/cli.js install chromium --with-deps \
|
||||
&& chown -R node:node /home/node/.cache \
|
||||
@@ -284,15 +284,15 @@ COPY --from=builder /app/node_modules/playwright-core ./node_modules/playwright-
|
||||
COPY --from=builder /app/node_modules/playwright ./node_modules/playwright
|
||||
|
||||
# Install system dependencies required by openclaw (git+ssh references).
|
||||
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-lists,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates docker.io docker-compose \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& git config --system url."https://github.com/".insteadOf "ssh://git@github.com/"
|
||||
|
||||
# Install CLI tools globally. Separate layer from apt for better cache reuse.
|
||||
RUN --mount=type=cache,id=npm-cache,target=/root/.npm \
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
|
||||
npm install -g --no-audit --no-fund @openai/codex @anthropic-ai/claude-code droid openclaw@latest
|
||||
|
||||
USER node
|
||||
|
||||
50
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 → 340 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. 340 AI providers · 90+ 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 → 341 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. 341 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
| | v3.8.49 | **v3.8.50** | `v3.8.51+` |
|
||||
| ------------------------- | :-----: | :---------: | :---------: |
|
||||
| 🌐 Providers | 290 | **340** | more queued |
|
||||
| 🌐 Providers | 290 | **341** | more queued |
|
||||
| 🧠 Documented models | 1185 | **1202** | — |
|
||||
| 🖼️ Modality Bridge | — | 🆕 vision | video |
|
||||
| 📡 Radar free catalog | — | 🆕 opt-in | — |
|
||||
@@ -101,7 +101,7 @@
|
||||
<tr>
|
||||
<td align="right"><b>⚙️ Features</b></td>
|
||||
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
|
||||
<td align="center"><a href="#-340-ai-providers--90-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-341-ai-providers--90-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI & MCP</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -210,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. 340 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 340 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 15–95%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 109 tools, A2A, memory, guardrails, evals — 25,000+ tests)."/>
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint. 341 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 341 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 15–95%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 109 tools, A2A, memory, guardrails, evals — 25,000+ tests)."/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
@@ -461,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 — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 340 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, built-in MCP server with 109 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project's docs."/>
|
||||
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 341 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, built-in MCP server with 109 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project's docs."/>
|
||||
|
||||
<sub>📊 Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
|
||||
|
||||
@@ -513,6 +513,8 @@ Pix copia-e-cola:
|
||||
|
||||
<br/>
|
||||
|
||||
<p><strong>Developer notes:</strong> The project may generate a local <code>.env</code> file during npm install/postinstall for developer convenience. This file is intentionally ignored via <code>.gitignore</code> (see <code>.gitignore</code>) and must never be committed — if accidentally committed, rotate any exposed secrets and remove the file from history. See <a href="docs/DEVELOPER-ENVIRONMENT.md">docs/DEVELOPER-ENVIRONMENT.md</a> for guidance on managing local environment files and secrets.</p>
|
||||
|
||||
## 📡 OmniRoute Radar
|
||||
|
||||
The main free-tier headline remains **~1.53B tokens/month** from the documented,
|
||||
@@ -557,7 +559,7 @@ 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, Freepik, Adobe Firefly, Microsoft Designer, Google Imagen, Segmind, EdgeTTS. → [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** — 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 **340-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 **341-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)
|
||||
|
||||
@@ -610,7 +612,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
<b>+ also works with</b> · Kiro · Command Code · Antigravity · Windsurf · AMP · <b>any OpenAI-compatible tool</b>
|
||||
</div>
|
||||
|
||||
<sub>📖 Per-tool setup for all 33 tools (25 CLI Code's + 8 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
|
||||
<sub>📖 Per-tool setup for all 34 tools (26 CLI Code's + 8 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -618,11 +620,11 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🌐 340 AI Providers — 90+ Free
|
||||
## 🌐 341 AI Providers — 90+ Free
|
||||
|
||||
</div>
|
||||
|
||||
> The most complete catalog of any open-source router: **340 providers**, **90+ with a free tier**, **56 free forever**.
|
||||
> The most complete catalog of any open-source router: **341 providers**, **90+ with a free tier**, **56 free forever**.
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -702,6 +704,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
<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>
|
||||
<tr><td align="left" nowrap>🧩 <b>OpenCode plugin</b></td><td align="left" nowrap><code>@omniroute/opencode-provider</code></td><td align="left">Native OpenCode integration</td></tr>
|
||||
<tr><td align="left" nowrap>🤖 <b>VS Code Copilot Chat</b></td><td align="left" nowrap>install <b>OmniCopilot</b> extension</td><td align="left">Every OmniRoute model in the native Copilot Chat picker — stable & Insiders</td></tr>
|
||||
<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>
|
||||
|
||||
@@ -711,6 +714,33 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
|
||||
<div align="center">
|
||||
|
||||
### 🧩 New: OmniRoute inside VS Code's native Copilot Chat
|
||||
|
||||
</div>
|
||||
|
||||
> No new sidebar, no new chat UI — every model OmniRoute serves shows up right in the
|
||||
> **Copilot Chat model picker you already use**. Since VS Code 1.122, provider models work
|
||||
> without a GitHub sign-in or a Copilot subscription — agent mode, tool calling and vision, for
|
||||
> free.
|
||||
|
||||
Install the **[OmniCopilot](https://github.com/diegosouzapw/OmniCopilot)** extension, point it
|
||||
at your OmniRoute server (defaults to `localhost:20128`), then open Copilot Chat → model picker
|
||||
→ **Manage Models…** → **OmniRoute**.
|
||||
|
||||
<table>
|
||||
<tr><th align="left">Store</th><th align="left">Link</th><th align="left">Works with</th></tr>
|
||||
<tr><td align="left" nowrap>🧩 <b>VS Code Marketplace</b></td><td align="left"><a href="https://marketplace.visualstudio.com/items?itemName=diegosouzapw.omnicopilot">Install →</a></td><td align="left">VS Code — stable & Insiders</td></tr>
|
||||
<tr><td align="left" nowrap>🔓 <b>Open VSX Registry</b></td><td align="left"><a href="https://open-vsx.org/extension/diegosouzapw/omnicopilot">Install →</a></td><td align="left">Cursor, Windsurf, VSCodium, Theia, code-server, Gitpod, Antigravity, Kiro…</td></tr>
|
||||
</table>
|
||||
|
||||
From inside the editor: open the **Extensions** view, search **"OmniRoute"**, click **Install**
|
||||
— works the same way on both stores. Source, issues and the publishing runbook live at
|
||||
[diegosouzapw/OmniCopilot](https://github.com/diegosouzapw/OmniCopilot).
|
||||
|
||||
<br/>
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🔒 Private & Local-First
|
||||
|
||||
</div>
|
||||
@@ -1080,7 +1110,7 @@ same process on one port, so there is no separate CLI-only package today.
|
||||
<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) — 117 domain modules, 148 migrations</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 117 domain modules, 150 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>
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveDataDir } from "../data-dir.mjs";
|
||||
import { registerContexts } from "./contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function ensureBackup(configPath) {
|
||||
if (!fs.existsSync(configPath)) return;
|
||||
@@ -87,6 +88,13 @@ async function runConfigSetCommand(toolId, opts = {}) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const guard = await guardHostConfigTarget(result.configPath, {
|
||||
toolLabel: toolId,
|
||||
hostCommand: `omniroute config set ${toolId}`,
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
const nonInteractive = opts.nonInteractive || opts.yes;
|
||||
|
||||
if (!nonInteractive) {
|
||||
@@ -271,6 +279,10 @@ export function registerConfig(program) {
|
||||
.option("--model <model>", "Model identifier (where applicable)")
|
||||
.option("--non-interactive", "Do not prompt for confirmation")
|
||||
.option("--yes", "Skip confirmation prompt")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write the config even when OmniRoute runs in a container and the target is not mounted from the host"
|
||||
)
|
||||
.action(async (tool, opts, cmd) => {
|
||||
const globalOpts = cmd.parent.optsWithGlobals();
|
||||
const exitCode = await runConfigSetCommand(tool, {
|
||||
@@ -306,6 +318,10 @@ export function registerConfig(program) {
|
||||
.option("--model <model>", "Model identifier")
|
||||
.option("--non-interactive", "Do not prompt for confirmation")
|
||||
.option("--yes", "Skip confirmation prompt")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write the config even when OmniRoute runs in a container and the target is not mounted from the host"
|
||||
)
|
||||
.action(async (opts, cmd) => {
|
||||
const globalOpts = cmd.parent.optsWithGlobals();
|
||||
const exitCode = await runConfigSetCommand("opencode", {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
|
||||
import { apiFetch } from "../api.mjs";
|
||||
import { createPrompt, printSuccess, printError, printInfo, printHeading } from "../io.mjs";
|
||||
import { t } from "../i18n.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
/**
|
||||
* `omniroute configure <cli>` — interactive provider+model picker that writes a
|
||||
@@ -75,6 +76,12 @@ function buildCodexProfile(modelId, ctx) {
|
||||
|
||||
async function configureCodex(modelId, ctxWindow, opts) {
|
||||
const codexHome = opts.codexHome || path.join(os.homedir(), ".codex");
|
||||
const guard = await guardHostConfigTarget(codexHome, {
|
||||
toolLabel: "Codex",
|
||||
hostCommand: "omniroute configure codex",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
if (!existsSync(codexHome)) mkdirSync(codexHome, { recursive: true });
|
||||
const profile = opts.name || profileNameFromModel(modelId);
|
||||
const filePath = path.join(codexHome, `${profile}.config.toml`);
|
||||
@@ -86,6 +93,7 @@ async function configureCodex(modelId, ctxWindow, opts) {
|
||||
printInfo(`Use it: codex --profile ${profile}`);
|
||||
printInfo("Prereq: ~/.codex/config.toml must define the [model_providers.omniroute] block");
|
||||
printInfo(" (run the Codex setup once — see docs/guides/CODEX-CLI-CONFIGURATION.md).");
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function runConfigureCommand(cli, opts = {}, cmd) {
|
||||
@@ -130,7 +138,9 @@ export async function runConfigureCommand(cli, opts = {}, cmd) {
|
||||
}
|
||||
const inProvider = ids.filter((id) => providerList.includes(providerOf(byId(models, id))));
|
||||
const candidates = inProvider.length ? inProvider : ids;
|
||||
printInfo(`Models: ${candidates.slice(0, 40).join(", ")}${candidates.length > 40 ? " …" : ""}`);
|
||||
printInfo(
|
||||
`Models: ${candidates.slice(0, 40).join(", ")}${candidates.length > 40 ? " …" : ""}`
|
||||
);
|
||||
chosenId = await prompt.ask("Model id");
|
||||
} finally {
|
||||
prompt.close();
|
||||
@@ -149,7 +159,7 @@ export async function runConfigureCommand(cli, opts = {}, cmd) {
|
||||
const ctxWindow = contextWindowOf(entry);
|
||||
|
||||
if (target === "codex") {
|
||||
await configureCodex(chosenId, ctxWindow, opts);
|
||||
return await configureCodex(chosenId, ctxWindow, opts);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -173,6 +183,10 @@ export function registerConfigure(program) {
|
||||
.option("--model <id>", "Model id (skips the interactive model prompt)")
|
||||
.option("--name <name>", "Profile name to write (default: derived from model)")
|
||||
.option("--codex-home <dir>", "Codex home dir (default: ~/.codex)")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write the config even when OmniRoute runs in a container and the target is not mounted from the host"
|
||||
)
|
||||
.action(async (cli, opts, cmd) => {
|
||||
const code = await runConfigureCommand(cli, opts, cmd);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
166
bin/cli/commands/packs.mjs
Normal file
@@ -0,0 +1,166 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { t } from "../i18n.mjs";
|
||||
import { resolveDataDir } from "../data-dir.mjs";
|
||||
import {
|
||||
EXIT_CODES,
|
||||
emit,
|
||||
exitWith,
|
||||
printError,
|
||||
printInfo,
|
||||
printSuccess,
|
||||
printWarning,
|
||||
} from "../output.mjs";
|
||||
import { findPack } from "../../../scripts/packs/optionalPackManifest.mjs";
|
||||
import {
|
||||
findPackIndexFile,
|
||||
installPack,
|
||||
listPackStates,
|
||||
packState,
|
||||
packsRoot,
|
||||
readPackIndex,
|
||||
removePack,
|
||||
} from "../../../scripts/packs/optionalPackInstaller.mjs";
|
||||
|
||||
const CLI_DIR = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
/**
|
||||
* Locate + parse the bundle-shipped `optional-packs.index.json`.
|
||||
* Search order: explicit --source dir, then walking up from the CLI module
|
||||
* (bundle installs keep the index at the bundle root), then cwd.
|
||||
*/
|
||||
function loadIndex(sourceDir) {
|
||||
const indexFile = findPackIndexFile([sourceDir, CLI_DIR, process.cwd()]);
|
||||
if (!indexFile) return { indexFile: null, index: null };
|
||||
return { indexFile, index: readPackIndex(indexFile) };
|
||||
}
|
||||
|
||||
function stateRow(state, dataDir) {
|
||||
return {
|
||||
pack: state.name,
|
||||
packVersion: state.packVersion,
|
||||
installed: state.installed ? "yes" : "no",
|
||||
verified: state.verified === null ? "-" : state.verified ? "ok" : "FAILED",
|
||||
members: state.members.length,
|
||||
installDir: path.join(packsRoot(dataDir), state.name),
|
||||
errors: state.errors ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
const STATE_SCHEMA = [
|
||||
{ key: "pack", header: "pack" },
|
||||
{ key: "packVersion", header: "packVersion" },
|
||||
{ key: "installed", header: "installed" },
|
||||
{ key: "verified", header: "verified" },
|
||||
{ key: "members", header: "members" },
|
||||
];
|
||||
|
||||
async function run(action) {
|
||||
try {
|
||||
await action();
|
||||
} catch (err) {
|
||||
exitWith(EXIT_CODES.ERROR, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
export function registerPacks(program) {
|
||||
const packs = program.command("packs").description(t("packs.description"));
|
||||
|
||||
packs
|
||||
.command("list")
|
||||
.description(t("packs.listDescription"))
|
||||
.option("--source <dir>", t("packs.sourceOpt"))
|
||||
.action(async (opts) => {
|
||||
await run(async () => {
|
||||
const dataDir = resolveDataDir();
|
||||
const { index } = loadIndex(opts.source);
|
||||
emit(
|
||||
(await listPackStates({ dataDir, index })).map((s) => stateRow(s, dataDir)),
|
||||
opts,
|
||||
STATE_SCHEMA
|
||||
);
|
||||
if (!index) printWarning(t("packs.warnNoIndex"));
|
||||
});
|
||||
});
|
||||
|
||||
packs
|
||||
.command("install <name>")
|
||||
.description(t("packs.installDescription"))
|
||||
.option("--source <dir>", t("packs.sourceOpt"))
|
||||
.action(async (name, opts) => {
|
||||
await run(async () => {
|
||||
if (!findPack(name)) exitWith(EXIT_CODES.INVALID_ARG, t("packs.errUnknown", { name }));
|
||||
const { indexFile, index } = loadIndex(opts.source);
|
||||
if (!index) exitWith(EXIT_CODES.ERROR, t("packs.errNoIndex"));
|
||||
const dataDir = resolveDataDir();
|
||||
// The payload (tarball or extracted pack dir) lives next to the index
|
||||
// unless the caller pointed elsewhere via --source.
|
||||
await installPack(name, {
|
||||
dataDir,
|
||||
index,
|
||||
sourceDir: opts.source || path.dirname(indexFile),
|
||||
log: (msg) => printInfo(msg.replace(/^\[optional-packs\]\s*/, "")),
|
||||
});
|
||||
const installDir = path.join(packsRoot(dataDir), name);
|
||||
printSuccess(t("packs.installed", { name, dir: installDir }));
|
||||
printInfo(t("packs.restartHint"));
|
||||
emit({ pack: name, installed: "yes", verified: "ok", installDir }, opts, STATE_SCHEMA);
|
||||
});
|
||||
});
|
||||
|
||||
packs
|
||||
.command("verify [name]")
|
||||
.description(t("packs.verifyDescription"))
|
||||
.option("--source <dir>", t("packs.sourceOpt"))
|
||||
.action(async (name, opts) => {
|
||||
await run(async () => {
|
||||
if (name && !findPack(name))
|
||||
exitWith(EXIT_CODES.INVALID_ARG, t("packs.errUnknown", { name }));
|
||||
const { index } = loadIndex(opts.source);
|
||||
if (!index) exitWith(EXIT_CODES.ERROR, t("packs.errNoIndex"));
|
||||
const dataDir = resolveDataDir();
|
||||
const states = name
|
||||
? [await packState(name, { dataDir, index })]
|
||||
: await listPackStates({ dataDir, index });
|
||||
emit(
|
||||
states.map((s) => stateRow(s, dataDir)),
|
||||
opts,
|
||||
STATE_SCHEMA
|
||||
);
|
||||
const broken = states.filter((s) => s.installed && s.verified !== true);
|
||||
if (broken.length > 0) {
|
||||
for (const state of broken) {
|
||||
for (const error of state.errors ?? []) printError(`${state.name}: ${error}`);
|
||||
}
|
||||
exitWith(EXIT_CODES.ERROR, t("packs.verifyFailed", { count: broken.length }));
|
||||
}
|
||||
if (!states.some((s) => s.installed)) {
|
||||
printInfo(t("packs.noneInstalled"));
|
||||
return;
|
||||
}
|
||||
printSuccess(t("packs.verifyOk"));
|
||||
});
|
||||
});
|
||||
|
||||
packs
|
||||
.command("remove <name>")
|
||||
.description(t("packs.removeDescription"))
|
||||
.action(async (name, opts) => {
|
||||
await run(async () => {
|
||||
if (!findPack(name)) exitWith(EXIT_CODES.INVALID_ARG, t("packs.errUnknown", { name }));
|
||||
const dataDir = resolveDataDir();
|
||||
const removed = removePack(name, {
|
||||
dataDir,
|
||||
log: (msg) => printInfo(msg.replace(/^\[optional-packs\]\s*/, "")),
|
||||
});
|
||||
if (removed) {
|
||||
printSuccess(t("packs.removed", { name }));
|
||||
printInfo(t("packs.restartHint"));
|
||||
} else {
|
||||
printInfo(t("packs.notInstalled", { name }));
|
||||
}
|
||||
emit({ pack: name, installed: removed ? "no" : "no" }, opts, STATE_SCHEMA);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -79,6 +79,7 @@ import { registerConfigure } from "./configure.mjs";
|
||||
import { registerApiCommands } from "../api-commands/registry.mjs";
|
||||
import { registerPlugin } from "./plugin.mjs";
|
||||
import { registerRadar } from "./radar.mjs";
|
||||
import { registerPacks } from "./packs.mjs";
|
||||
|
||||
export function registerCommands(program) {
|
||||
registerMemory(program);
|
||||
@@ -163,4 +164,5 @@ export function registerCommands(program) {
|
||||
registerApiCommands(program);
|
||||
registerPlugin(program);
|
||||
registerRadar(program);
|
||||
registerPacks(program);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { platform, totalmem, hostname as osHostname } from "node:os";
|
||||
import { platform, totalmem } from "node:os";
|
||||
import { t } from "../i18n.mjs";
|
||||
import { writePidFile, cleanupPidFile, waitForServer } from "../utils/pid.mjs";
|
||||
import { ServerSupervisor, detectMitmCrash } from "../runtime/processSupervisor.mjs";
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isFatalInstrumentationHookFailure,
|
||||
formatAndroidInstrumentationFailureHint,
|
||||
} from "../utils/ensureAndroidCacheDir.mjs";
|
||||
import { resolveServerHost } from "../utils/serverHost.mjs";
|
||||
import {
|
||||
resolveMaxOldSpaceMb,
|
||||
calibrateHeapFallbackMb,
|
||||
@@ -207,16 +208,10 @@ export async function runServe(opts = {}) {
|
||||
PORT: String(dashboardPort),
|
||||
DASHBOARD_PORT: String(dashboardPort),
|
||||
API_PORT: String(apiPort),
|
||||
// #6194: POSIX shells (bash/zsh) auto-set HOSTNAME to the machine name — the
|
||||
// .env loader (first-wins) can never override it. Ignore HOSTNAME when it
|
||||
// matches the OS-reported hostname (the auto-set signature). OMNIROUTE_SERVER_HOST
|
||||
// takes precedence; legacy HOSTNAME values that don't match os.hostname() are
|
||||
// still honoured for backward compatibility (e.g. Windows CMD/PowerShell users
|
||||
// who set HOSTNAME in .env where it is NOT auto-set).
|
||||
HOSTNAME:
|
||||
process.env.OMNIROUTE_SERVER_HOST ||
|
||||
(process.env.HOSTNAME !== osHostname() ? process.env.HOSTNAME : undefined) ||
|
||||
"0.0.0.0",
|
||||
// #10492: HOSTNAME is standard shell state on Unix-like systems, not an
|
||||
// OmniRoute bind setting. The resolver only keeps its legacy meaning on
|
||||
// Windows; OMNIROUTE_SERVER_HOST is the cross-platform explicit setting.
|
||||
HOSTNAME: resolveServerHost(),
|
||||
NODE_ENV: "production",
|
||||
// #5238: preserve a user-set NODE_OPTIONS (incl. their own
|
||||
// `--max-old-space-size=…`) instead of clobbering it with the calibrated
|
||||
|
||||
@@ -13,6 +13,7 @@ import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function stripToRoot(url) {
|
||||
const s = String(url || "").replace(/\/+$/, "");
|
||||
@@ -25,7 +26,9 @@ export function resolveAiderTarget(opts = {}) {
|
||||
if (opts.remote) root = stripToRoot(opts.remote);
|
||||
else {
|
||||
try {
|
||||
root = stripToRoot(resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl);
|
||||
root = stripToRoot(
|
||||
resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl
|
||||
);
|
||||
} catch {
|
||||
/* none */
|
||||
}
|
||||
@@ -78,7 +81,7 @@ async function fetchModelIds(apiBase, apiKey) {
|
||||
const res = await fetch(`${apiBase}/v1/models`, { headers, signal: AbortSignal.timeout(8000) });
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -88,7 +91,16 @@ async function fetchModelIds(apiBase, apiKey) {
|
||||
export async function runSetupAiderCommand(opts = {}) {
|
||||
const { apiBase, apiKey } = resolveAiderTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const configPath = opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".aider.conf.yml");
|
||||
const configPath =
|
||||
opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".aider.conf.yml");
|
||||
|
||||
const guard = await guardHostConfigTarget(configPath, {
|
||||
toolLabel: "Aider",
|
||||
hostCommand: "omniroute setup-aider",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
printHeading("OmniRoute → Aider (openai-compatible via LiteLLM)");
|
||||
printInfo(`OPENAI_API_BASE: ${apiBase} (no /v1 — LiteLLM appends it)`);
|
||||
@@ -107,7 +119,9 @@ export async function runSetupAiderCommand(opts = {}) {
|
||||
}
|
||||
}
|
||||
if (!model) {
|
||||
printError("A model is required. Pass --model <id> (the openai/ prefix is added automatically).");
|
||||
printError(
|
||||
"A model is required. Pass --model <id> (the openai/ prefix is added automatically)."
|
||||
);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -139,6 +153,10 @@ export function registerSetupAider(program) {
|
||||
.option("--config-path <path>", ".aider.conf.yml path (default: ~/.aider.conf.yml)")
|
||||
.option("--yes", "Non-interactive: do not prompt (requires --model)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupAiderCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
import {
|
||||
categoriseModel,
|
||||
isCodexCompatibleTextModel,
|
||||
@@ -147,6 +148,14 @@ export async function runSetupClaudeCommand(opts = {}) {
|
||||
printHeading("OmniRoute → Claude Code profile generator");
|
||||
printInfo(`Connecting to ${baseUrl} …`);
|
||||
|
||||
const guard = await guardHostConfigTarget(profilesRoot, {
|
||||
toolLabel: "Claude Code",
|
||||
hostCommand: "omniroute setup-claude",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
// ── Fetch model catalog ───────────────────────────────────────────────────
|
||||
let models;
|
||||
try {
|
||||
@@ -220,6 +229,10 @@ export function registerSetupClaude(program) {
|
||||
"Comma-separated substrings — only matching model IDs (e.g. glm,kimi)"
|
||||
)
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const exitCode = await runSetupClaudeCommand(opts);
|
||||
if (exitCode !== 0) process.exit(exitCode);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function stripToRoot(url) {
|
||||
let s = String(url || "").replace(/\/+$/, "");
|
||||
@@ -28,11 +29,14 @@ export function resolveClineTarget(opts = {}) {
|
||||
if (opts.remote) baseUrl = stripToRoot(opts.remote);
|
||||
else {
|
||||
try {
|
||||
baseUrl = stripToRoot(resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl);
|
||||
baseUrl = stripToRoot(
|
||||
resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl
|
||||
);
|
||||
} catch {
|
||||
/* none */
|
||||
}
|
||||
if (!baseUrl) baseUrl = `http://localhost:${Number(opts.port ?? process.env.PORT ?? 20128) || 20128}`;
|
||||
if (!baseUrl)
|
||||
baseUrl = `http://localhost:${Number(opts.port ?? process.env.PORT ?? 20128) || 20128}`;
|
||||
}
|
||||
let apiKey = opts.apiKey ?? opts["api-key"];
|
||||
if (!apiKey) {
|
||||
@@ -81,7 +85,7 @@ async function fetchModelIds(baseUrl, apiKey) {
|
||||
const res = await fetch(`${baseUrl}/v1/models`, { headers, signal: AbortSignal.timeout(8000) });
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -93,6 +97,14 @@ export async function runSetupClineCommand(opts = {}) {
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const clineDir = opts.clineDir ?? opts["cline-dir"] ?? join(os.homedir(), ".cline", "data");
|
||||
|
||||
const guard = await guardHostConfigTarget(clineDir, {
|
||||
toolLabel: "Cline",
|
||||
hostCommand: "omniroute setup-cline",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
printHeading("OmniRoute → Cline (OpenAI-compatible)");
|
||||
printInfo(`Server: ${baseUrl}`);
|
||||
|
||||
@@ -122,7 +134,18 @@ export async function runSetupClineCommand(opts = {}) {
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`\n── [dry-run] ${gsPath} ──`);
|
||||
console.log(JSON.stringify({ actModeApiProvider: globalState.actModeApiProvider, planModeApiProvider: globalState.planModeApiProvider, openAiBaseUrl: globalState.openAiBaseUrl, openAiModelId: globalState.openAiModelId }, null, 2));
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
actModeApiProvider: globalState.actModeApiProvider,
|
||||
planModeApiProvider: globalState.planModeApiProvider,
|
||||
openAiBaseUrl: globalState.openAiBaseUrl,
|
||||
openAiModelId: globalState.openAiModelId,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
console.log(`\n── [dry-run] ${secPath} ── (openAiApiKey: ${apiKey ? "set" : "sk_omniroute"})`);
|
||||
} else {
|
||||
if (!existsSync(clineDir)) mkdirSync(clineDir, { recursive: true });
|
||||
@@ -133,7 +156,9 @@ export async function runSetupClineCommand(opts = {}) {
|
||||
}
|
||||
|
||||
// The VS Code extension uses opaque globalStorage — can't be file-written.
|
||||
printInfo("\nFor the Cline VS Code extension, set these in its Settings → API (OpenAI Compatible):");
|
||||
printInfo(
|
||||
"\nFor the Cline VS Code extension, set these in its Settings → API (OpenAI Compatible):"
|
||||
);
|
||||
printInfo(` Base URL: ${baseUrl} (NOT /v1 — Cline appends it)`);
|
||||
printInfo(` API Key: <your OMNIROUTE_API_KEY>`);
|
||||
printInfo(` Model: ${model}`);
|
||||
@@ -153,6 +178,10 @@ export function registerSetupCline(program) {
|
||||
.option("--cline-dir <dir>", "Cline data dir (default: ~/.cline/data)")
|
||||
.option("--yes", "Non-interactive: do not prompt (requires --model)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupClineCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
import { t } from "../i18n.mjs";
|
||||
|
||||
// ── Model categorisation ──────────────────────────────────────────────────────
|
||||
@@ -306,6 +307,14 @@ export async function runSetupCodexCommand(opts = {}) {
|
||||
const onlyFilter = opts.only ? opts.only.split(",").map((s) => s.trim()) : null;
|
||||
|
||||
printHeading(`OmniRoute → Codex CLI profile generator`);
|
||||
|
||||
const guard = await guardHostConfigTarget(codexHome, {
|
||||
toolLabel: "Codex",
|
||||
hostCommand: "omniroute setup-codex",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
printInfo(`Connecting to ${baseUrl} …`);
|
||||
|
||||
// ── Fetch model catalog ───────────────────────────────────────────────────
|
||||
@@ -380,6 +389,10 @@ export function registerSetupCodex(program) {
|
||||
"Comma-separated substrings — only generate profiles for matching model IDs (e.g. glm,kimi)"
|
||||
)
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const exitCode = await runSetupCodexCommand(opts);
|
||||
if (exitCode !== 0) process.exit(exitCode);
|
||||
|
||||
@@ -14,6 +14,7 @@ import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { categoriseModel } from "./setup-codex.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
const SECRET_REF = "${{ secrets.OMNIROUTE_API_KEY }}";
|
||||
|
||||
@@ -92,7 +93,7 @@ async function fetchModelIds(apiBase, apiKey) {
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not fetch models: ${e.message}`);
|
||||
@@ -102,8 +103,22 @@ async function fetchModelIds(apiBase, apiKey) {
|
||||
export async function runSetupContinueCommand(opts = {}) {
|
||||
const { apiBase, apiKey } = resolveContinueTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
||||
const configPath = opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".continue", "config.yaml");
|
||||
const only = opts.only
|
||||
? opts.only
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: null;
|
||||
const configPath =
|
||||
opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".continue", "config.yaml");
|
||||
|
||||
const guard = await guardHostConfigTarget(configPath, {
|
||||
toolLabel: "Continue",
|
||||
hostCommand: "omniroute setup-continue",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
printHeading("OmniRoute → Continue (config.yaml)");
|
||||
printInfo(`apiBase: ${apiBase}`);
|
||||
@@ -150,7 +165,7 @@ export async function runSetupContinueCommand(opts = {}) {
|
||||
printInfo("\nProvide the key (config.yaml references it, not stores it):");
|
||||
printInfo(" cn CLI: export OMNIROUTE_API_KEY=... (read from your shell)");
|
||||
printInfo(" IDE: echo 'OMNIROUTE_API_KEY=...' >> ~/.continue/.env");
|
||||
printInfo("Run: cn -p \"reply OK\"");
|
||||
printInfo('Run: cn -p "reply OK"');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -166,6 +181,10 @@ export function registerSetupContinue(program) {
|
||||
.option("--only <patterns>", "Comma-separated substrings — keep only matching model IDs")
|
||||
.option("--config-path <path>", "config.yaml path (default: ~/.continue/config.yaml)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupContinueCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -13,6 +13,7 @@ import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { categoriseModel } from "./setup-codex.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
const API_KEY_REF = "$OMNIROUTE_API_KEY";
|
||||
|
||||
@@ -87,15 +88,29 @@ async function fetchModelIds(baseUrl, apiKey) {
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
}
|
||||
|
||||
export async function runSetupCrushCommand(opts = {}) {
|
||||
const { baseUrl, apiKey } = resolveCrushTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
||||
const configPath = opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".config", "crush", "crush.json");
|
||||
const only = opts.only
|
||||
? opts.only
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: null;
|
||||
const configPath =
|
||||
opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".config", "crush", "crush.json");
|
||||
|
||||
const guard = await guardHostConfigTarget(configPath, {
|
||||
toolLabel: "Crush",
|
||||
hostCommand: "omniroute setup-crush",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
printHeading("OmniRoute → Crush (openai-compat)");
|
||||
printInfo(`base_url: ${baseUrl}`);
|
||||
@@ -120,13 +135,17 @@ export async function runSetupCrushCommand(opts = {}) {
|
||||
|
||||
if (dryRun) {
|
||||
console.log("\n" + (out.length > 3500 ? out.slice(0, 3500) + "\n… (truncated)" : out));
|
||||
printInfo(`[dry-run] ${provider.models.length} model(s) under providers.omniroute → ${configPath}`);
|
||||
printInfo(
|
||||
`[dry-run] ${provider.models.length} model(s) under providers.omniroute → ${configPath}`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
mkdirSync(join(configPath, ".."), { recursive: true });
|
||||
writeFileSync(configPath, out, "utf8");
|
||||
printSuccess(`Wrote ${configPath} (${provider.models.length} models under providers.omniroute)`);
|
||||
printInfo("Provide the key (config references $OMNIROUTE_API_KEY): export OMNIROUTE_API_KEY=...");
|
||||
printInfo(
|
||||
"Provide the key (config references $OMNIROUTE_API_KEY): export OMNIROUTE_API_KEY=..."
|
||||
);
|
||||
printInfo("Then run: crush");
|
||||
return 0;
|
||||
}
|
||||
@@ -141,6 +160,10 @@ export function registerSetupCrush(program) {
|
||||
.option("--only <patterns>", "Comma-separated substrings — keep only matching model IDs")
|
||||
.option("--config-path <path>", "crush.json path (default: ~/.config/crush/crush.json)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupCrushCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
import { printHeading, printInfo, printSuccess } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { isContainerRuntime } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function ensureV1(url) {
|
||||
const s = String(url || "").replace(/\/+$/, "");
|
||||
@@ -71,7 +72,7 @@ async function fetchModelIds(apiBase, apiKey) {
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -84,19 +85,32 @@ export async function runSetupCursorCommand(opts = {}) {
|
||||
printInfo(`Server: ${apiBase}`);
|
||||
|
||||
let models = [];
|
||||
const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
||||
const only = opts.only
|
||||
? opts.only
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: null;
|
||||
const ids = await fetchModelIds(apiBase, apiKey);
|
||||
models = only ? ids.filter((id) => only.some((f) => id.includes(f))) : ids;
|
||||
|
||||
console.log("\n" + buildCursorInstructions({ apiBase, models }));
|
||||
printSuccess("\nCursor is configured manually (no file written — Cursor's storage is opaque).");
|
||||
if (await isContainerRuntime()) {
|
||||
printInfo(
|
||||
"Note: this ran inside a container, so the base URL above is the container's own view. " +
|
||||
"Use the address the host reaches OmniRoute on (e.g. the published port) in Cursor's settings."
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function registerSetupCursor(program) {
|
||||
program
|
||||
.command("setup-cursor")
|
||||
.description("Print the steps to point Cursor at OmniRoute (chat panel; Cursor config is not file-writable)")
|
||||
.description(
|
||||
"Print the steps to point Cursor at OmniRoute (chat panel; Cursor config is not file-writable)"
|
||||
)
|
||||
.option("--port <port>", "Local OmniRoute port (ignored when --remote is set)", "20128")
|
||||
.option("--remote <url>", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
|
||||
.option("--api-key <key>", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
|
||||
|
||||
@@ -14,6 +14,7 @@ import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function stripToRoot(url) {
|
||||
const s = String(url || "").replace(/\/+$/, "");
|
||||
@@ -26,7 +27,9 @@ export function resolveGooseTarget(opts = {}) {
|
||||
if (opts.remote) root = stripToRoot(opts.remote);
|
||||
else {
|
||||
try {
|
||||
root = stripToRoot(resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl);
|
||||
root = stripToRoot(
|
||||
resolveActiveContext(opts.context ?? process.env.OMNIROUTE_CONTEXT)?.baseUrl
|
||||
);
|
||||
} catch {
|
||||
/* none */
|
||||
}
|
||||
@@ -80,7 +83,7 @@ async function fetchModelIds(host, apiKey) {
|
||||
const res = await fetch(`${host}/v1/models`, { headers, signal: AbortSignal.timeout(8000) });
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -90,7 +93,16 @@ async function fetchModelIds(host, apiKey) {
|
||||
export async function runSetupGooseCommand(opts = {}) {
|
||||
const { host, apiKey } = resolveGooseTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const configPath = opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".config", "goose", "config.yaml");
|
||||
const configPath =
|
||||
opts.configPath ?? opts["config-path"] ?? join(os.homedir(), ".config", "goose", "config.yaml");
|
||||
|
||||
const guard = await guardHostConfigTarget(configPath, {
|
||||
toolLabel: "Goose",
|
||||
hostCommand: "omniroute setup-goose",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
printHeading("OmniRoute → Goose (openai-compatible)");
|
||||
printInfo(`OPENAI_HOST: ${host} (no /v1 — Goose appends it)`);
|
||||
@@ -128,14 +140,16 @@ export async function runSetupGooseCommand(opts = {}) {
|
||||
|
||||
printInfo("\nProvide the key (Goose reads it from the env / OS keyring):");
|
||||
console.log(buildGooseEnvRecipe({ host, model }));
|
||||
printInfo("Then run: goose session (or: goose run -t \"reply OK\")");
|
||||
printInfo('Then run: goose session (or: goose run -t "reply OK")');
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function registerSetupGoose(program) {
|
||||
program
|
||||
.command("setup-goose")
|
||||
.description("Configure Goose for OmniRoute: write ~/.config/goose/config.yaml + print the env recipe")
|
||||
.description(
|
||||
"Configure Goose for OmniRoute: write ~/.config/goose/config.yaml + print the env recipe"
|
||||
)
|
||||
.option("--port <port>", "Local OmniRoute port (ignored when --remote is set)", "20128")
|
||||
.option("--remote <url>", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
|
||||
.option("--api-key <key>", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
|
||||
@@ -143,6 +157,10 @@ export function registerSetupGoose(program) {
|
||||
.option("--config-path <path>", "config.yaml path (default: ~/.config/goose/config.yaml)")
|
||||
.option("--yes", "Non-interactive: do not prompt (requires --model)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupGooseCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError, createPrompt } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
/** Ensure the URL ends with /v1 (Kilo appends /chat/completions to it). */
|
||||
function ensureV1(url) {
|
||||
@@ -61,7 +62,11 @@ export function buildKiloAuth(existing, { apiKey, baseUrl, model }) {
|
||||
/** Merge the kilocode.* keys into VS Code settings.json (extension surface). */
|
||||
export function buildKiloVscodeSettings(existing, { apiKey, baseUrl, model }) {
|
||||
const s = { ...(existing || {}) };
|
||||
s["kilocode.customProvider"] = { name: "OmniRoute", baseURL: baseUrl, apiKey: apiKey || "sk_omniroute" };
|
||||
s["kilocode.customProvider"] = {
|
||||
name: "OmniRoute",
|
||||
baseURL: baseUrl,
|
||||
apiKey: apiKey || "sk_omniroute",
|
||||
};
|
||||
s["kilocode.defaultModel"] = model;
|
||||
return s;
|
||||
}
|
||||
@@ -85,7 +90,7 @@ async function fetchModelIds(root, apiKey) {
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -95,9 +100,22 @@ async function fetchModelIds(root, apiKey) {
|
||||
export async function runSetupKiloCommand(opts = {}) {
|
||||
const { baseUrl, apiKey } = resolveKiloTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const authPath = opts.authPath ?? opts["auth-path"] ?? join(os.homedir(), ".local", "share", "kilo", "auth.json");
|
||||
const authPath =
|
||||
opts.authPath ??
|
||||
opts["auth-path"] ??
|
||||
join(os.homedir(), ".local", "share", "kilo", "auth.json");
|
||||
|
||||
const guard = await guardHostConfigTarget(authPath, {
|
||||
toolLabel: "Kilo Code",
|
||||
hostCommand: "omniroute setup-kilo",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
const vscodePath =
|
||||
opts.vscodeSettings ?? opts["vscode-settings"] ?? join(os.homedir(), ".config", "Code", "User", "settings.json");
|
||||
opts.vscodeSettings ??
|
||||
opts["vscode-settings"] ??
|
||||
join(os.homedir(), ".config", "Code", "User", "settings.json");
|
||||
|
||||
printHeading("OmniRoute → Kilo Code (OpenAI-compatible)");
|
||||
printInfo(`Server: ${baseUrl}`);
|
||||
@@ -116,7 +134,9 @@ export async function runSetupKiloCommand(opts = {}) {
|
||||
}
|
||||
}
|
||||
if (!model) {
|
||||
printError("A model is required. Pass --model <id> (Kilo's extension has no model auto-discovery).");
|
||||
printError(
|
||||
"A model is required. Pass --model <id> (Kilo's extension has no model auto-discovery)."
|
||||
);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -132,12 +152,19 @@ export async function runSetupKiloCommand(opts = {}) {
|
||||
console.log(`\n── [dry-run] ${authPath} ──`);
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{ "openai-compatible": { ...auth["openai-compatible"], apiKey: apiKey ? "set" : "sk_omniroute" } },
|
||||
{
|
||||
"openai-compatible": {
|
||||
...auth["openai-compatible"],
|
||||
apiKey: apiKey ? "set" : "sk_omniroute",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
console.log(`\n── [dry-run] ${vscodePath} ── ${vscodeExists ? "(would merge kilocode.* keys)" : "(skipped — file absent)"}`);
|
||||
console.log(
|
||||
`\n── [dry-run] ${vscodePath} ── ${vscodeExists ? "(would merge kilocode.* keys)" : "(skipped — file absent)"}`
|
||||
);
|
||||
} else {
|
||||
mkdirSync(join(authPath, ".."), { recursive: true });
|
||||
writeFileSync(authPath, JSON.stringify(auth, null, 2) + "\n", "utf8");
|
||||
@@ -167,10 +194,20 @@ export function registerSetupKilo(program) {
|
||||
.option("--remote <url>", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
|
||||
.option("--api-key <key>", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
|
||||
.option("--model <id>", "Model id for Kilo (required unless picked interactively)")
|
||||
.option("--auth-path <path>", "Kilo CLI auth.json path (default: ~/.local/share/kilo/auth.json)")
|
||||
.option("--vscode-settings <path>", "VS Code settings.json (default: ~/.config/Code/User/settings.json)")
|
||||
.option(
|
||||
"--auth-path <path>",
|
||||
"Kilo CLI auth.json path (default: ~/.local/share/kilo/auth.json)"
|
||||
)
|
||||
.option(
|
||||
"--vscode-settings <path>",
|
||||
"VS Code settings.json (default: ~/.config/Code/User/settings.json)"
|
||||
)
|
||||
.option("--yes", "Non-interactive: do not prompt (requires --model)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupKiloCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -30,6 +30,7 @@ import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { t } from "../i18n.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -316,6 +317,13 @@ export async function runSetupOpenCodeCommand(opts = {}) {
|
||||
printInfo(`OpenCode config dir: ${opencodeConfigDir}`);
|
||||
printInfo(`OpenCode data dir: ${opencodeDataDir}`);
|
||||
|
||||
const guard = await guardHostConfigTarget(opencodeConfigDir, {
|
||||
toolLabel: "OpenCode",
|
||||
hostCommand: "omniroute setup opencode",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
});
|
||||
if (guard !== 0) return { exitCode: guard };
|
||||
|
||||
// 1. Resolve bundled plugin
|
||||
let pluginInfo;
|
||||
try {
|
||||
@@ -420,6 +428,10 @@ export function registerSetupOpenCode(setupCommand) {
|
||||
false
|
||||
)
|
||||
.option("--non-interactive", "Do not prompt; skip the auth login step", false)
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts, cmd) => {
|
||||
// The parent `setup` command uses cmd.optsWithGlobals(); we mirror
|
||||
// that here so global flags (--json, --base-url, --api-key) still
|
||||
|
||||
@@ -14,6 +14,7 @@ import { basename, dirname } from "node:path";
|
||||
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
const ENV_KEY_REF = "{env:OMNIROUTE_API_KEY}";
|
||||
const JSON_FORMATTING_OPTIONS = { insertSpaces: true, tabSize: 2 };
|
||||
@@ -119,6 +120,15 @@ export async function runSetupOpencodeCommand(opts = {}) {
|
||||
const { resolveOpencodeConfigPath } =
|
||||
await import("../../../src/shared/services/opencodeConfigPath.ts");
|
||||
configPath = resolveOpencodeConfigPath();
|
||||
|
||||
const guard = await guardHostConfigTarget(configPath, {
|
||||
toolLabel: "OpenCode",
|
||||
hostCommand: "omniroute setup-opencode",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
|
||||
raw = await generateOpencodeConfig({
|
||||
baseUrl,
|
||||
apiKey,
|
||||
@@ -163,6 +173,10 @@ export function registerSetupOpencode(program) {
|
||||
.option("--model <id>", "Set the default top-level model (omniroute/<id>)")
|
||||
.option("--only <patterns>", "Comma-separated substrings — keep only matching model IDs")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupOpencodeCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
normalizeQwenCodeBaseUrl,
|
||||
} from "../../../src/shared/services/qwenCodeConfig.ts";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
import { createPrompt, printError, printHeading, printInfo, printSuccess } from "../io.mjs";
|
||||
|
||||
/** Resolve base URL and key from flags, active context, then local defaults. */
|
||||
@@ -102,6 +103,16 @@ export async function runSetupQwenCommand(opts = {}) {
|
||||
printHeading("OmniRoute → Qwen Code (OpenAI-compatible)");
|
||||
printInfo(`baseUrl: ${baseUrl}`);
|
||||
|
||||
for (const target of [settingsPath, envPath]) {
|
||||
const guard = await guardHostConfigTarget(target, {
|
||||
toolLabel: "Qwen Code",
|
||||
hostCommand: "omniroute setup-qwen",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
}
|
||||
|
||||
let model = String(opts.model || "").trim();
|
||||
if (!model && !opts.yes) {
|
||||
const modelIds = await fetchModelIds(baseUrl, apiKey);
|
||||
@@ -159,6 +170,10 @@ export function registerSetupQwen(program) {
|
||||
.option("--env-path <path>", "Qwen Code .env path")
|
||||
.option("--yes", "Non-interactive; requires --model")
|
||||
.option("--dry-run", "Print settings without writing files or secrets")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupQwenCommand(opts);
|
||||
if (code !== 0) process.exitCode = code;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { join } from "node:path";
|
||||
import os from "node:os";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveActiveContext } from "../contexts.mjs";
|
||||
import { guardHostConfigTarget } from "../utils/config-home-guard.mjs";
|
||||
|
||||
function ensureV1(url) {
|
||||
const s = String(url || "").replace(/\/+$/, "");
|
||||
@@ -89,7 +90,7 @@ async function fetchModelIds(baseUrl, apiKey) {
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const list = Array.isArray(body) ? body : body.data ?? body.models ?? [];
|
||||
const list = Array.isArray(body) ? body : (body.data ?? body.models ?? []);
|
||||
return list.map((m) => (typeof m === "string" ? m : m?.id)).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -99,9 +100,20 @@ async function fetchModelIds(baseUrl, apiKey) {
|
||||
export async function runSetupRooCommand(opts = {}) {
|
||||
const { baseUrl, apiKey } = resolveRooTarget(opts);
|
||||
const dryRun = Boolean(opts.dryRun ?? opts["dry-run"]);
|
||||
const importPath = opts.importPath ?? opts["import-path"] ?? join(os.homedir(), ".omniroute", "roo-settings.json");
|
||||
const importPath =
|
||||
opts.importPath ?? opts["import-path"] ?? join(os.homedir(), ".omniroute", "roo-settings.json");
|
||||
|
||||
const guard = await guardHostConfigTarget(importPath, {
|
||||
toolLabel: "Roo Code",
|
||||
hostCommand: "omniroute setup-roo",
|
||||
allowContainerWrite: Boolean(opts.allowContainerWrite ?? opts["allow-container-write"]),
|
||||
dryRun,
|
||||
});
|
||||
if (guard !== 0) return guard;
|
||||
const vscodePath =
|
||||
opts.vscodeSettings ?? opts["vscode-settings"] ?? join(os.homedir(), ".config", "Code", "User", "settings.json");
|
||||
opts.vscodeSettings ??
|
||||
opts["vscode-settings"] ??
|
||||
join(os.homedir(), ".config", "Code", "User", "settings.json");
|
||||
|
||||
printHeading("OmniRoute → Roo Code (OpenAI-compatible)");
|
||||
printInfo(`Server: ${baseUrl}`);
|
||||
@@ -130,8 +142,27 @@ export async function runSetupRooCommand(opts = {}) {
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`\n── [dry-run] ${importPath} ──`);
|
||||
console.log(JSON.stringify({ ...importDoc, providerProfiles: { ...importDoc.providerProfiles, apiConfigs: { OmniRoute: { ...importDoc.providerProfiles.apiConfigs.OmniRoute, openAiApiKey: apiKey ? "set" : "sk_omniroute" } } } }, null, 2));
|
||||
console.log(`\n── [dry-run] ${vscodePath} ── ${vscodeExists ? "(would set roo-cline.autoImportSettingsPath)" : "(skipped — file absent)"}`);
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
...importDoc,
|
||||
providerProfiles: {
|
||||
...importDoc.providerProfiles,
|
||||
apiConfigs: {
|
||||
OmniRoute: {
|
||||
...importDoc.providerProfiles.apiConfigs.OmniRoute,
|
||||
openAiApiKey: apiKey ? "set" : "sk_omniroute",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`\n── [dry-run] ${vscodePath} ── ${vscodeExists ? "(would set roo-cline.autoImportSettingsPath)" : "(skipped — file absent)"}`
|
||||
);
|
||||
} else {
|
||||
mkdirSync(join(importPath, ".."), { recursive: true });
|
||||
writeFileSync(importPath, JSON.stringify(importDoc, null, 2) + "\n", "utf8");
|
||||
@@ -161,10 +192,20 @@ export function registerSetupRoo(program) {
|
||||
.option("--remote <url>", "Remote OmniRoute URL, e.g. http://192.168.0.15:20128")
|
||||
.option("--api-key <key>", "OmniRoute API key (defaults to OMNIROUTE_API_KEY env var)")
|
||||
.option("--model <id>", "Model id for Roo (required unless picked interactively)")
|
||||
.option("--import-path <path>", "Roo import JSON path (default: ~/.omniroute/roo-settings.json)")
|
||||
.option("--vscode-settings <path>", "VS Code settings.json (default: ~/.config/Code/User/settings.json)")
|
||||
.option(
|
||||
"--import-path <path>",
|
||||
"Roo import JSON path (default: ~/.omniroute/roo-settings.json)"
|
||||
)
|
||||
.option(
|
||||
"--vscode-settings <path>",
|
||||
"VS Code settings.json (default: ~/.config/Code/User/settings.json)"
|
||||
)
|
||||
.option("--yes", "Non-interactive: do not prompt (requires --model)")
|
||||
.option("--dry-run", "Print what would be written without touching the filesystem")
|
||||
.option(
|
||||
"--allow-container-write",
|
||||
"Write even when the target is inside a container and not mounted from the host"
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const code = await runSetupRooCommand(opts);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
@@ -1304,5 +1304,23 @@
|
||||
},
|
||||
"setupCodex": {
|
||||
"description": "Generate ~/.codex profile files from OmniRoute live model catalog"
|
||||
},
|
||||
"packs": {
|
||||
"description": "Manage optional runtime packs (ML / browser automation)",
|
||||
"listDescription": "List optional packs and their install state",
|
||||
"installDescription": "Install an optional pack into DATA_DIR",
|
||||
"verifyDescription": "Verify installed packs against the shipped checksum index",
|
||||
"removeDescription": "Remove an installed optional pack",
|
||||
"sourceOpt": "Directory holding pack payloads and the pack index",
|
||||
"warnNoIndex": "optional-packs.index.json not found — install/verify are unavailable in this checkout (desktop bundles ship it)",
|
||||
"errUnknown": "unknown pack: {name}",
|
||||
"errNoIndex": "pack index not found; pass --source <dir> holding the pack payload (desktop bundles ship it next to the app)",
|
||||
"installed": "pack \"{name}\" installed and verified at {dir}",
|
||||
"restartHint": "restart the OmniRoute server (or desktop app) so the runtime picks the pack up",
|
||||
"removed": "pack \"{name}\" removed",
|
||||
"notInstalled": "pack \"{name}\" was not installed",
|
||||
"verifyOk": "all installed packs verified",
|
||||
"verifyFailed": "{count} pack(s) failed verification",
|
||||
"noneInstalled": "no optional packs installed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,5 +1301,23 @@
|
||||
},
|
||||
"setupCodex": {
|
||||
"description": "Gera os arquivos de perfil ~/.codex a partir do catálogo de modelos ao vivo do OmniRoute"
|
||||
},
|
||||
"packs": {
|
||||
"description": "Gerencia packs opcionais de runtime (ML / automação de navegador)",
|
||||
"listDescription": "Lista os packs opcionais e seu estado de instalação",
|
||||
"installDescription": "Instala um pack opcional no DATA_DIR",
|
||||
"verifyDescription": "Verifica os packs instalados contra o índice de checksums embarcado",
|
||||
"removeDescription": "Remove um pack opcional instalado",
|
||||
"sourceOpt": "Diretório com os payloads dos packs e o índice de packs",
|
||||
"warnNoIndex": "optional-packs.index.json não encontrado — install/verify indisponíveis neste checkout (instaladores desktop o embarcam)",
|
||||
"errUnknown": "pack desconhecido: {name}",
|
||||
"errNoIndex": "índice de packs não encontrado; passe --source <dir> com o payload do pack (instaladores desktop o embarcam ao lado do app)",
|
||||
"installed": "pack \"{name}\" instalado e verificado em {dir}",
|
||||
"restartHint": "reinicie o servidor OmniRoute (ou o app desktop) para o runtime reconhecer o pack",
|
||||
"removed": "pack \"{name}\" removido",
|
||||
"notInstalled": "o pack \"{name}\" não estava instalado",
|
||||
"verifyOk": "todos os packs instalados verificados",
|
||||
"verifyFailed": "{count} pack(s) falharam na verificação",
|
||||
"noneInstalled": "nenhum pack opcional instalado"
|
||||
}
|
||||
}
|
||||
|
||||
122
bin/cli/utils/config-home-guard.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
import { printError, printInfo } from "../io.mjs";
|
||||
|
||||
/**
|
||||
* Container guard for CLI-tool config writes.
|
||||
*
|
||||
* `omniroute setup-*` writes to `~/.codex`, `~/.claude`, ... — paths that only
|
||||
* mean something on the operator's host. Run the same command inside the
|
||||
* OmniRoute container and the write "succeeds" into an ephemeral layer that no
|
||||
* host CLI ever reads and that disappears with the container. This guard turns
|
||||
* that silent no-op into an actionable refusal.
|
||||
*
|
||||
* Bind-mounted targets (the compose `host` profile) are allowed through: the
|
||||
* mount is the operator's explicit statement that the path reaches the host.
|
||||
*/
|
||||
|
||||
const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
|
||||
|
||||
/** Exit code for a refused write — matches the CLI's usage-error convention. */
|
||||
export const CONTAINER_WRITE_EXIT_CODE = 2;
|
||||
|
||||
function envAllowsContainerWrite(env = process.env) {
|
||||
return TRUE_VALUES.has(
|
||||
String(env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a pending config write.
|
||||
*
|
||||
* @param {string} targetPath Absolute path the command is about to write.
|
||||
* @param {{
|
||||
* toolLabel?: string,
|
||||
* hostCommand?: string,
|
||||
* allowContainerWrite?: boolean,
|
||||
* dryRun?: boolean,
|
||||
* env?: NodeJS.ProcessEnv,
|
||||
* deps?: object,
|
||||
* }} options
|
||||
* @returns {Promise<{ok: boolean, message?: string, warning?: string}>}
|
||||
*/
|
||||
export async function assertHostConfigTarget(targetPath, options = {}) {
|
||||
const {
|
||||
toolLabel,
|
||||
hostCommand,
|
||||
allowContainerWrite = false,
|
||||
dryRun = false,
|
||||
env = process.env,
|
||||
deps,
|
||||
} = options;
|
||||
|
||||
let describeContainerTarget;
|
||||
let buildContainerWriteRefusal;
|
||||
let CLI_OVERRIDE_HINT;
|
||||
try {
|
||||
// `.ts` extension is required so the published package (which ships only TS
|
||||
// source, resolved through tsx) can load these. See #2509.
|
||||
({ describeContainerTarget } = await import("../../../src/shared/utils/containerEnv.ts"));
|
||||
({ buildContainerWriteRefusal, CLI_OVERRIDE_HINT } =
|
||||
await import("../../../src/shared/utils/containerConfigGuard.ts"));
|
||||
} catch {
|
||||
// Fail open: a guard that cannot load must not block a legitimate host run.
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const info = describeContainerTarget(targetPath, deps);
|
||||
if (!info.ephemeral) return { ok: true };
|
||||
|
||||
if (dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
warning:
|
||||
`[dry-run] ${targetPath} is inside the container and is not mounted from the host — ` +
|
||||
`a real run would be refused. See --allow-container-write.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (allowContainerWrite || envAllowsContainerWrite(env)) {
|
||||
return {
|
||||
ok: true,
|
||||
warning:
|
||||
`Writing to ${targetPath} inside the container as requested — this file is lost when ` +
|
||||
`the container is recreated and host CLIs will not see it.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
message: buildContainerWriteRefusal(targetPath, {
|
||||
toolLabel,
|
||||
hostCommand,
|
||||
overrideHint: CLI_OVERRIDE_HINT,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Container check for commands that write nothing but still print host-oriented
|
||||
* instructions (setup-cursor). Fails closed to `false` so a broken import never
|
||||
* turns into a spurious warning.
|
||||
*/
|
||||
export async function isContainerRuntime(deps) {
|
||||
try {
|
||||
const { isRunningInContainer } = await import("../../../src/shared/utils/containerEnv.ts");
|
||||
return isRunningInContainer(deps);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard + report. Returns 0 to continue, or CONTAINER_WRITE_EXIT_CODE when the
|
||||
* caller should abort and return that code.
|
||||
*/
|
||||
export async function guardHostConfigTarget(targetPath, options = {}) {
|
||||
const result = await assertHostConfigTarget(targetPath, options);
|
||||
if (result.warning) printInfo(result.warning);
|
||||
if (result.ok) return 0;
|
||||
printError(result.message);
|
||||
return CONTAINER_WRITE_EXIT_CODE;
|
||||
}
|
||||
26
bin/cli/utils/serverHost.mjs
Normal file
@@ -0,0 +1,26 @@
|
||||
import { hostname, platform } from "node:os";
|
||||
|
||||
/**
|
||||
* Resolve the bind host passed to the standalone Next.js server.
|
||||
*
|
||||
* HOSTNAME is a standard shell variable on Unix-like systems, so only the
|
||||
* dedicated OmniRoute variable is treated as configuration there. Windows
|
||||
* keeps the legacy HOSTNAME fallback for compatibility with existing .env
|
||||
* files, while still ignoring the OS-reported machine name.
|
||||
*
|
||||
* @param {NodeJS.ProcessEnv} [env]
|
||||
* @param {NodeJS.Platform} [runtimePlatform]
|
||||
* @param {string} [machineHostname]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function resolveServerHost(
|
||||
env = process.env,
|
||||
runtimePlatform = platform(),
|
||||
machineHostname = hostname()
|
||||
) {
|
||||
if (env.OMNIROUTE_SERVER_HOST) return env.OMNIROUTE_SERVER_HOST;
|
||||
if (runtimePlatform === "win32" && env.HOSTNAME && env.HOSTNAME !== machineHostname) {
|
||||
return env.HOSTNAME;
|
||||
}
|
||||
return "0.0.0.0";
|
||||
}
|
||||
1
changelog.d/features/10057-docker-aware-auto-config.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(cli):** container-aware auto-config — `setup-*`, `omniroute configure`, `omniroute config set` and the CLI-tool config APIs now refuse to write into a containerised OmniRoute's ephemeral home (CLI exits `2`, API returns `422` with `containerEphemeralTarget`) and point at the host-CLI or bind-mount setup instead; `--allow-container-write` / `OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true` opt back in. Also fixes `CLI_CONFIG_HOME` so the Compose `host` profile's `/host-home` bind mounts are honoured instead of silently falling back to the container home. (#10057)
|
||||
1
changelog.d/features/9760-video-bridge.md
Normal file
@@ -0,0 +1 @@
|
||||
- feat(modality-bridge): bridge Chat and Responses video parts through a strict trusted-loopback, quota-bounded FFmpeg broker; enforce HTTPS redirects/SSRF plus format, protocol, stream, pixel, frame, 50 MiB broker/remote, 36 MiB inline, and 120-second limits; propagate caller aborts; preserve the actual successful fallback model through cache/meta/headers; expose sampled latency and honest success telemetry; and ship the localized Video settings UI (#9760)
|
||||
1
changelog.d/fixes/10144-claude-import-cli-user-id.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(oauth):** Claude connections created via `claude-auth/import` now send required CLI headers on the bootstrap identity call and persist a `cliUserID` device identity, fixing intermittent "Third-party apps now draw from your extra usage" 400s on otherwise valid imported subscription tokens ([#10144](https://github.com/diegosouzapw/OmniRoute/pull/10144), fixes [#10143](https://github.com/diegosouzapw/OmniRoute/issues/10143))
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(cursor):** Stop truncating pending tool calls on non-composer models when a KV checkpoint arrives after text but before the `exec_mcp` frame — the KV short-circuit is now gated to the composer family where it was verified ([#10215](https://github.com/diegosouzapw/OmniRoute/issues/10215)).
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(responses):** repair corrupted SSE deltas for non-ASCII streams by keeping a single stream-aware `TextDecoder` (`{ stream: true }`) across `transform()` calls instead of recreating it per chunk and decoding without the `stream` flag. When a multi-byte UTF-8 character (CJK/emoji) was split across two TCP chunks — common in Chinese streaming text — the per-chunk decoder truncated it to `U+FFFD`, corrupting every delta while the rebuilt `*.done` snapshot stayed internally identical ([#10223](https://github.com/diegosouzapw/OmniRoute/issues/10223))
|
||||
1
changelog.d/fixes/10233-freeaiapikey-endpoint-moved.md
Normal file
@@ -0,0 +1 @@
|
||||
- **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))
|
||||
1
changelog.d/fixes/10234-monsterapi-deprecation-inert.md
Normal file
@@ -0,0 +1 @@
|
||||
- **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))
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(providers):** preserve validator HTTP status codes in API-key and web connection-test results so callers can distinguish authentication, rate-limit, and upstream failures ([#10272](https://github.com/diegosouzapw/OmniRoute/pull/10272)) — thanks @Zartharas
|
||||
1
changelog.d/fixes/10284-reasoning-probe-truncated-200.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(sse):** tiny-budget reasoning probes (e.g. Claude Code's `/model` check sends `max_tokens: 1`) are answered with a valid truncated 200 instead of relaying the upstream 5xx "empty response content" — which previously also marked the connection unavailable and poisoned fallback/cooldown bookkeeping for a request that is only a probe ([#10281](https://github.com/diegosouzapw/OmniRoute/issues/10281)) — thanks @harkaranbrar7
|
||||
1
changelog.d/fixes/10286-gemini-3-5-flash-thinking.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): mark gemini-3.5-flash as thinking-capable so reasoning_effort is no longer rejected with a spurious 400 (#10286)
|
||||
1
changelog.d/fixes/10293-windows-tailscale-branches.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(build):** stop Turbopack from dead-code-eliminating the Windows Tailscale branches of `src/lib/tailscaleTunnel.ts` in the published build (#10293). The release `dist` is bundled on a Linux runner, and the bundler constant-folds `process.platform`, pruning every non-Linux branch — the Windows installers shipped with no `where` lookup, an always-injected `--socket`, and a lost `net start Tailscale`/windows-default-binary path. The module now reads the platform at runtime via `os.platform()` (a function call a bundler cannot fold), so the Windows branches survive on any build machine; a vitest regression test mocking `os.platform()` → `win32` guards the anti-fold invariant (RED before, GREEN after).
|
||||
1
changelog.d/fixes/10322-process-wide-admission-budget.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(chat-body-admission):** restore a single process-wide admission budget — heavyweight leases and queued bytes are now bounded once for the whole process instead of per session, so one session can no longer mint extra capacity or starve others; per-session fairness is preserved via round-robin dispatch ([#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110))
|
||||
1
changelog.d/fixes/10329-zai-web-auth-semantics.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(providers):** validate Z.ai web Local Storage sessions against the authenticated user-settings endpoint and preserve exact upstream status codes ([#10329](https://github.com/diegosouzapw/OmniRoute/pull/10329)) — thanks @Zartharas
|
||||
1
changelog.d/fixes/10348-default-logs-redact-client.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(backend): redact client IPs and account prefixes from default proxy logs (#10348)
|
||||
1
changelog.d/fixes/10372-debug-mode-default-false.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(db):** `getSettings()` defaults `debugMode` to `false` — fresh installs no longer run in debug mode (persisted `debugMode: true` is preserved) ([#10372](https://github.com/diegosouzapw/OmniRoute/pull/10372) — thanks @lamchun1110)
|
||||
1
changelog.d/fixes/10393-opencode-rotate-network-throw.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(executors):** OpencodeExecutor and MimocodeExecutor now rotate to the next account on network exceptions (timeout, connection refused/reset) when the failed account has a dedicated proxy, not only on 429 — a throw on one account no longer fails the whole request when other accounts remain. Accounts sharing the default egress (no proxy) fail fast instead of retrying the same outage against every account. The shared rotation mechanics (`pickAccount`/`markCooldown`/`markSuccess`) are now extracted into `accountRotation.ts`, fixing an identical unconditional-cooldown gap that pre-dated this PR in MimocodeExecutor ([#10393](https://github.com/diegosouzapw/OmniRoute/pull/10393))
|
||||
1
changelog.d/fixes/10397-header-budget-warn-dedupe.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(sse):** the header-budget drop warning fires once per unique dropped-header set instead of on every SSE response (warn-storm fix) ([#10397](https://github.com/diegosouzapw/OmniRoute/pull/10397) — thanks @lamchun1110)
|
||||
1
changelog.d/fixes/10415-vision-bridge-combo-reroute.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(guardrails):** Vision Bridge now reroutes whole requests for named combos whose targets have zero vision-capable models (previously such image requests died with `capability_mismatch` when the describe path could not run), and when the fallback describe path also fails for every image the request degrades to explicit `(unavailable)` stub text instead of preserving images the combo cannot consume ([#10415](https://github.com/diegosouzapw/OmniRoute/pull/10415)) — thanks @rqzbeh
|
||||
@@ -0,0 +1,2 @@
|
||||
- **fix(antigravity):** geo-blocked egress (Google "User location is not supported") is now classified (scoped to the Google AI surfaces that emit it: Cloud Code/Gemini Code Assist, Gemini API, Vertex), cached as a 24h per-account exclusion so routing continues with other accounts, and surfaced with an actionable message; the dashboard connection test now probes the real `streamGenerateContent` model surface instead of the non-geo-restricted OAuth userinfo endpoint ([#10420](https://github.com/diegosouzapw/OmniRoute/pull/10420)) — thanks @rqzbeh
|
||||
- **fix(antigravity):** strip competing-agent identity sentences from system prompts (e.g. "You are a Claude agent, built on Anthropic's Claude Agent SDK.") that Antigravity flags and answers with 429 RESOURCE_EXHAUSTED (port of decolua/9router b566b20) ([#10420](https://github.com/diegosouzapw/OmniRoute/pull/10420)) — thanks @rqzbeh
|
||||
@@ -0,0 +1,2 @@
|
||||
- **fix(antigravity):** accounts with an empty Cloud Code `projectId` now heal themselves — failed auto-onboarding (`onboardUser`) attempts are retried after a short backoff instead of being memoized forever, so the missing Google project is created without user action on a later request or token refresh ([#10424](https://github.com/diegosouzapw/OmniRoute/pull/10424)) — thanks @rqzbeh
|
||||
- **fix(antigravity):** Google deprecated automatic project creation for standard-tier (personal) accounts — when `onboardUser` completes without a project id the account now fails fast with a clear `403 GCP_PROJECT_REQUIRED` message (no more generic 422 or delayed 429 RESOURCE_EXHAUSTED), and a manual GCP Project ID override is available in the connection editor so operators can enter their own project id ([#10424](https://github.com/diegosouzapw/OmniRoute/pull/10424)) — thanks @rqzbeh
|
||||
1
changelog.d/fixes/10430-antigravity-usage-envelope.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(usage):** read Gemini `usageMetadata` out of the antigravity `{ response: {...} }` envelope so non-streaming requests log real token usage instead of `IN 0 | OUT 0` (port of decolua/9router#59d858b) ([#10430](https://github.com/diegosouzapw/OmniRoute/pull/10430)) — thanks @rqzbeh
|
||||
1
changelog.d/fixes/10465-gemini-cached-tokens.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(usage):** surface Gemini `cachedContentTokenCount` into `cached_tokens` for non-streaming requests so cache-hit accounting matches the OpenAI/Claude/Responses branches and the streaming path (follow-up to the #10430 envelope fix) ([#10465](https://github.com/diegosouzapw/OmniRoute/pull/10465)) — thanks @rqzbeh
|
||||
1
changelog.d/fixes/10482-docker-images-and-basepath.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(docker):** point the bifrost sidecar at the real `ghcr.io/maximhq/bifrost:v1.6.11` tag and the cliproxyapi sidecar at the official `docker.io/eceasy/cli-proxy-api:v6.9.7` image (the previously pinned tags never existed), and complete the runtime `OMNIROUTE_BASE_PATH` subpath patch for Next 16 standalone (assetPrefix + client env + baked asset URLs) so prebuilt images respect the webpath env var ([#10482](https://github.com/diegosouzapw/OmniRoute/pull/10482))
|
||||
1
changelog.d/fixes/10484-hermes-obfuscate-zwj.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(sse): stop ZWJ-obfuscating the substring "hermes" in user messages and hostnames (#10484)
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(providers):** allow token-backed web sessions stored with `authType: "cookie"` to refresh their token through the provider update API ([#10518](https://github.com/diegosouzapw/OmniRoute/pull/10518)) — thanks @Zartharas
|
||||
1
changelog.d/fixes/10557-fedora-hostname-bind.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(cli):** ignore the operating system `HOSTNAME` when choosing the server bind address on Linux and macOS, preventing startup failures when the shell hostname differs from `os.hostname()`; use `OMNIROUTE_SERVER_HOST` for explicit non-Windows configuration while preserving the legacy `HOSTNAME` fallback on Windows ([#10557](https://github.com/diegosouzapw/OmniRoute/pull/10557), closes [#10492](https://github.com/diegosouzapw/OmniRoute/issues/10492)) — thanks @redzrush101
|
||||
@@ -0,0 +1 @@
|
||||
- fix(combo): recovery hint for all_targets_skipped now points at provider quota/availability instead of 'transient, just retry' (#9303)
|
||||
1
changelog.d/fixes/9617-gemini-uniqueitems-strip.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): strip uniqueItems from Gemini tool schemas (Gemini rejects it with 400 'Unknown name uniqueItems') (#9617)
|
||||
1
changelog.d/fixes/9935-media-playground-masked-bearer.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): media mini-playgrounds authenticate via session instead of sending the masked API key as Bearer, fixing 401s under REQUIRE_API_KEY (#9935)
|
||||
@@ -0,0 +1 @@
|
||||
- fix(sse): exclude search providers from credential-health scheduler sweep to stop burning billed API queries (#9970)
|
||||
1
changelog.d/fixes/api-manager-empty-combo-allowlist.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(api-manager):** Allowed Combos can now be restricted to zero entries: **All** is stored explicitly as `combo/*`, while **Restrict** with no selection saves an empty allowlist that denies Combo routes without blocking direct models. Existing keys are migrated to preserve their previous allow-all behavior.
|
||||
1
changelog.d/fixes/catalog-cache-hash-apikey.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(api):** hash API keys in the `/v1/models` catalog cache Map key so heap dumps cannot leak bearer tokens (`src/app/api/v1/models/catalogCache.ts`)
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(db):** the `compression_run_telemetry` retention sweep now actually deletes expired rows. Its cutoff was computed in epoch seconds while the column stores epoch milliseconds, so `WHERE timestamp < cutoff` never matched and the table added by #6848 to bound `storage.sqlite` growth was unbounded in practice. Same unit mismatch as #9625, which corrected the sibling `domain_cost_history` sweep and missed this call site
|
||||
1
changelog.d/fixes/dbstat-optional-vtab.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(db):** database settings API no longer returns HTTP 500 on SQLite builds compiled without the optional `dbstat` virtual table (sql.js/WASM); per-table sizes degrade to 0 instead of failing the whole stats call
|
||||
1
changelog.d/fixes/docker-healthcheck-use-healthz.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(ops):** Docker HEALTHCHECK probes lightweight `/healthz` instead of `/api/monitoring/health` so a busy event loop does not mark the container Unhealthy (`scripts/dev/healthcheck.mjs`)
|
||||
1
changelog.d/fixes/forward-codex-quota-headers.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(sse):** keep Codex/Anthropic quota headers under the upstream forwarding budget; drop `x-codex-turn-state` and raise the 768-byte cap (`open-sse/handlers/chatCore/responseHeaders.ts`)
|
||||
1
changelog.d/fixes/models-dev-sync-env-killswitch.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(models):** honor `MODELS_DEV_SYNC_ENABLED=0` as a hard kill switch over the dashboard setting so a wedged `/healthz` / UI can be recovered without HTTP (`src/lib/modelsDevSync.ts`)
|
||||
1
changelog.d/fixes/sqljs-atomic-persist.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(db):** the sql.js fallback now publishes the database atomically — temp file in the same directory, `fsync`, then `rename()` — instead of rewriting it in place with `writeFileSync`. sql.js has no incremental write path, so every save rewrote the whole image through an `O_TRUNC` open: for the duration of the write the on-disk database was 0 bytes and then partial, a window that scales with database size and recurs on every save. Unlike better-sqlite3 / node:sqlite, that window is not covered by SQLite's locking protocol, so it was visible to every OTHER process reading the same file (a backup job, a metrics exporter, an operator running `sqlite3`), which got `SQLITE_CORRUPT` — "database disk image is malformed" — while `PRAGMA integrity_check` passed moments later. It also closes a total-loss window: a crash mid-write used to leave the real database truncated, and now only leaves a stale temp file
|
||||
@@ -0,0 +1 @@
|
||||
- **docs(ops):** document Kubernetes probe recommendations — TCP (or soft HTTP) liveness, HTTP `/healthz` readiness, avoid `/api/monitoring/health` as kubelet liveness ([#10297](https://github.com/diegosouzapw/OmniRoute/pull/10297)) — thanks @RaviTharuma
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(release):** resync the v3.8.50 provider and CLI catalogs, register the existing ChatCore mutation-coverage test, and document the local ZCode handshake identifier so the release quality gates reflect the current tree without changing ratchet baselines.
|
||||
@@ -0,0 +1 @@
|
||||
- **chore(release):** synchronize migration-count documentation and document the opt-in `PROXY_LOG_INCLUDE_IPS` logging flag so the v3.8.50 quality gates match the release tree.
|
||||
@@ -3319,4 +3319,4 @@
|
||||
"count": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ services:
|
||||
# fall back to the chatCore path with zero code changes. See
|
||||
# docs/architecture/cluster-decisions.md for the activation plan.
|
||||
bifrost:
|
||||
image: ghcr.io/maximhq/bifrost:1.5.21
|
||||
image: ghcr.io/maximhq/bifrost:v1.6.11
|
||||
container_name: omniroute-bifrost
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -266,9 +266,12 @@ services:
|
||||
- bifrost
|
||||
|
||||
# ── Profile: cliproxyapi (CLIProxyAPI as sidecar) ─────────────────
|
||||
# Official pre-built image lives on Docker Hub (eceasy/cli-proxy-api);
|
||||
# ghcr.io/router-for-me/* is not publicly pullable. v6.9.7 is the pinned
|
||||
# version the sidecar integration (port 8317, /v1/models healthcheck) targets.
|
||||
cliproxyapi:
|
||||
container_name: cliproxyapi
|
||||
image: ghcr.io/router-for-me/cliproxyapi:v6.9.7
|
||||
image: docker.io/eceasy/cli-proxy-api:v6.9.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}"
|
||||
|
||||
29
docs/DEVELOPER-ENVIRONMENT.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Developer environment notes
|
||||
|
||||
This page explains the project's local `.env` behavior and how to handle environment files and secrets when developing OmniRoute.
|
||||
|
||||
## .env postinstall behavior
|
||||
|
||||
The project may generate a local `.env` file during `npm install` / `postinstall` for developer convenience. This file is intended only for local development and testing and must never be committed to version control.
|
||||
|
||||
Key points:
|
||||
|
||||
- The repository's `.gitignore` already ignores `.env*` files (see the `.gitignore` entry). Do not remove or alter that rule unless you deliberately intend to commit a specific example file and have a documented process for it.
|
||||
- If a real secret is accidentally committed to the repo, rotate/revoke the credential immediately and remove it from the repository history (for example, using `git filter-repo` or an equivalent remediation workflow). Contact the security/contact owner if you need help.
|
||||
- For CI and production, use the CI secrets or a secrets manager (GitHub Actions Secrets, Azure Key Vault, HashiCorp Vault, etc.) rather than committing secrets to files.
|
||||
|
||||
## Recommended local workflow
|
||||
|
||||
- Keep `.env` in your local workspace only. Use `.env.example` (already tracked) to document required variables and acceptable example values.
|
||||
- When running tests locally that require secret-like values, prefer synthetic placeholders or runtime-generated ephemeral keys rather than real credentials.
|
||||
- Add a short comment in tests that use placeholders so reviewers understand the fixture is synthetic.
|
||||
|
||||
## Scanner notes
|
||||
|
||||
- Some compiled or binary assets (e.g., embedded base64 WASM blobs) can contain ASCII substrings that look like credentials and may trigger text-based secret scanners. If these assets are legitimate, either mark them in the scanner's allowlist or exclude the directories in the scanner config.
|
||||
|
||||
## If you find a leak
|
||||
|
||||
1. Rotate/revoke the key immediately.
|
||||
2. Remove the secret from the history and force-push a cleaned branch if necessary.
|
||||
3. Notify maintainers and follow your org's incident response checklist.
|
||||
@@ -55,9 +55,9 @@ The two profiles here are **scale-out options for deployments that hit the SQLit
|
||||
|
||||
**What it adds:**
|
||||
|
||||
| Service | Image | Ports | Notes |
|
||||
| --------- | -------------------------------- | ------ | ----------------------------------------------------------------------- |
|
||||
| `bifrost` | `ghcr.io/maximhq/bifrost:1.5.21` | `8080` | Go-based Tier-1 router; persistent logs volume `omniroute_bifrost_logs` |
|
||||
| Service | Image | Ports | Notes |
|
||||
| --------- | --------------------------------- | ------ | ----------------------------------------------------------------------- |
|
||||
| `bifrost` | `ghcr.io/maximhq/bifrost:v1.6.11` | `8080` | Go-based Tier-1 router; persistent logs volume `omniroute_bifrost_logs` |
|
||||
|
||||
**Activation:** set `BIFROST_BASE_URL=http://bifrost:8080` in `.env.example`. The existing sidecar proxy route at [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (added in PR #4381) will pick this up automatically.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (340 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over the 80+ command surface: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (341 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over the 80+ command surface: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
|
||||
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.345;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
|
||||
<rect width="1200" height="350" fill="#0d1117"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 340 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 109 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 341 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 109 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
|
||||
<defs>
|
||||
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint, 340 providers — never stop building, OmniRoute picks the cheapest one that works. Six pillars. Never hit limits: auto-fallback across 340 providers in milliseconds, quota out means the next provider takes over with zero downtime. Save up to 95 percent of tokens: RTK plus Caveman stacked compression cuts 15 to 95 percent of eligible tokens, about 89 percent average on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier, 56 free forever — Qoder, Pollinations, Cloudflare, SiliconFlow — no card needed. Every tool works: 33 coding agents including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation — point any tool at /v1 and it just works. Production-grade: circuit breakers, TLS stealth, MCP with 109 tools, A2A, memory, guardrails, evals — 25,000+ tests.">
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint, 341 providers — never stop building, OmniRoute picks the cheapest one that works. Six pillars. Never hit limits: auto-fallback across 341 providers in milliseconds, quota out means the next provider takes over with zero downtime. Save up to 95 percent of tokens: RTK plus Caveman stacked compression cuts 15 to 95 percent of eligible tokens, about 89 percent average on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier, 56 free forever — Qoder, Pollinations, Cloudflare, SiliconFlow — no card needed. Every tool works: 33 coding agents including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation — point any tool at /v1 and it just works. Production-grade: circuit breakers, TLS stealth, MCP with 109 tools, A2A, memory, guardrails, evals — 25,000+ tests.">
|
||||
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -21,7 +21,7 @@
|
||||
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">340 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">341 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
</g>
|
||||
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
@@ -38,7 +38,7 @@
|
||||
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
|
||||
</g>
|
||||
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 340 providers in</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 341 providers in</text>
|
||||
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
|
||||
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over — zero downtime.</text>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 340 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 340 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 341 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 341 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -28,7 +28,7 @@
|
||||
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
|
||||
|
||||
<!-- subheadline -->
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">340 providers</tspan> — <tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">341 providers</tspan> — <tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
|
||||
|
||||
<!-- plug line -->
|
||||
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity  →  <tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -79,7 +79,7 @@ Use a real key instead when your OmniRoute server is protected or remote.
|
||||
|
||||
Codex CLI deprecated `wire_api = "chat"` (Chat Completions) in February 2026 and now **requires** `wire_api = "responses"` (OpenAI Responses API). Setting `wire_api = "chat"` causes an immediate startup crash since v0.138.
|
||||
|
||||
DeepSeek, GLM, Kimi and others only expose a Chat Completions endpoint — not the Responses API. If you pointed Codex directly at them, it would fail.
|
||||
Many providers, including GLM and Kimi, still expose only a Chat Completions endpoint. DeepSeek V4 now exposes a native Responses API as well as an Anthropic-compatible endpoint; OmniRoute uses Responses by default and lets each DeepSeek connection select Anthropic compatibility.
|
||||
|
||||
**OmniRoute solves this transparently:**
|
||||
|
||||
@@ -87,8 +87,8 @@ DeepSeek, GLM, Kimi and others only expose a Chat Completions endpoint — not t
|
||||
Codex CLI
|
||||
→ wire_api = "responses"
|
||||
→ POST /v1/responses (OmniRoute)
|
||||
→ OmniRoute Responses ↔ Chat Completions transformer
|
||||
→ POST /chat/completions (DeepSeek / Mistral / GLM / Kimi / any provider)
|
||||
→ OmniRoute selects the provider's native protocol and translates when needed
|
||||
→ POST /responses (DeepSeek V4) or /chat/completions (Mistral / GLM / Kimi / others)
|
||||
```
|
||||
|
||||
You never need a separate translation proxy when using OmniRoute. **All models use `wire_api = "responses"`** — OmniRoute handles the rest.
|
||||
|
||||
@@ -14,6 +14,7 @@ lastUpdated: 2026-06-28
|
||||
- [With Environment File](#with-environment-file)
|
||||
- [Docker Compose](#docker-compose)
|
||||
- [Available Profiles](#available-profiles)
|
||||
- [Configuring host CLI tools when OmniRoute runs in Docker](#configuring-host-cli-tools-when-omniroute-runs-in-docker)
|
||||
- [Redis Sidecar](#redis-sidecar)
|
||||
- [Production Compose](#production-compose)
|
||||
- [Dockerfile Stages](#dockerfile-stages)
|
||||
@@ -82,6 +83,61 @@ OmniRoute ships four Compose profiles. Pick the one that matches your environmen
|
||||
|
||||
> Multiple profiles can be combined: `docker compose --profile cli --profile cliproxyapi up -d`.
|
||||
|
||||
## Configuring host CLI tools when OmniRoute runs in Docker
|
||||
|
||||
`omniroute setup-codex`, `setup-claude`, `config set <tool>` and the dashboard's
|
||||
**Save config** button all write files like `~/.codex/*.config.toml`. Those paths
|
||||
only mean something on the machine where the CLI actually runs. Run them inside
|
||||
the container and the write lands in the container's own home (`/home/node` —
|
||||
the image runs `USER node`), where no host CLI will ever read it and where it is
|
||||
discarded the moment the container is recreated.
|
||||
|
||||
OmniRoute detects this and refuses the write with instructions instead of
|
||||
reporting a success you cannot use: the CLI exits `2`, and the API answers `422`
|
||||
with `containerEphemeralTarget: true`.
|
||||
|
||||
### Recommended: run the CLI on the host, OmniRoute in Docker
|
||||
|
||||
The container serves the API; the CLI configures your host tools.
|
||||
|
||||
```bash
|
||||
docker compose --profile base up -d
|
||||
|
||||
npm install -g omniroute
|
||||
omniroute connect http://localhost:20128 # point the CLI at the container
|
||||
omniroute setup-codex # writes the real ~/.codex on your host
|
||||
```
|
||||
|
||||
This is the right choice when Codex, Claude Code, Cursor or similar run on your
|
||||
laptop — which is the usual setup.
|
||||
|
||||
### Alternative: bind-mount the host config dirs (`host` profile)
|
||||
|
||||
If you want the container itself to write your host config, mount the
|
||||
directories in and point `CLI_CONFIG_HOME` at the mount root. The `host` profile
|
||||
already does this:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- CLI_CONFIG_HOME=/host-home
|
||||
- CLI_ALLOW_CONFIG_WRITES=true
|
||||
volumes:
|
||||
- ~/.codex:/host-home/.codex:rw
|
||||
- ~/.claude:/host-home/.claude:rw
|
||||
```
|
||||
|
||||
A bind mount is what makes the path trustworthy: OmniRoute reads
|
||||
`/proc/self/mountinfo` and allows writes to mounted paths (and to directories
|
||||
whose children are mounts, which is exactly the `/host-home` shape above) while
|
||||
still refusing unmounted ones.
|
||||
|
||||
### Escape hatch: configure the container's own CLIs
|
||||
|
||||
When the CLIs genuinely live inside the container (the `cli` profile), the write
|
||||
is intentional. Pass `--allow-container-write` to any `setup-*` command, or set
|
||||
`OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true` for the server. The write proceeds
|
||||
with a warning that it will not survive the container.
|
||||
|
||||
## Redis Sidecar
|
||||
|
||||
OmniRoute relies on Redis to back the distributed rate limiter and shared cache. The `redis` service is **always defined** in `docker-compose.yml` (it has no profile gate) and starts alongside any other profile.
|
||||
@@ -229,8 +285,12 @@ Next.js `basePath` is compiled into the standalone bundle. OmniRoute records the
|
||||
value in a sentinel file at the app root (written during `npm run build`; read by
|
||||
`scripts/docker/ensure-docker-base-path.mjs`) and compares it with
|
||||
`OMNIROUTE_BASE_PATH` when the container starts. When they differ and the image was
|
||||
built for the domain root, the entrypoint rewrites the standalone manifests and embedded
|
||||
`basePath` literals before `node dev/run-standalone.mjs` runs.
|
||||
built for the domain root, the entrypoint rewrites the standalone manifests, the
|
||||
embedded `basePath`/`assetPrefix` literals (Next 16 renders SSR asset URLs from
|
||||
`assetPrefix` alone — the patcher mirrors the subpath into it), the baked
|
||||
`/_next/static` asset URLs (client-reference manifests, media imports, prerendered
|
||||
error pages) and the client `process.env` shim before `node dev/run-standalone.mjs`
|
||||
runs.
|
||||
|
||||
### Compose build (recommended)
|
||||
|
||||
@@ -270,7 +330,22 @@ prefix). Traefik should route `PathPrefix(`/omniroute`)` to the container withou
|
||||
`/omniroute/_next/...`.
|
||||
|
||||
The Docker healthcheck probes `/api/monitoring/health` prefixed with the active
|
||||
`OMNIROUTE_BASE_PATH`.
|
||||
`OMNIROUTE_BASE_PATH`. That path is a **deep** check (DB + monitoring summary). It is
|
||||
appropriate for Docker’s infrequent `HEALTHCHECK`, but **not** for Kubernetes
|
||||
`livenessProbe` intervals.
|
||||
|
||||
For orchestrators (Kubernetes, Nomad, etc.):
|
||||
|
||||
| Probe | Prefer | Avoid |
|
||||
| --- | --- | --- |
|
||||
| Liveness | TCP on the main port (`PORT`, default `20128`), or soft HTTP `/healthz` | `/api/monitoring/health` as liveness |
|
||||
| Readiness | HTTP `GET /healthz` | Tight timeouts that treat event-loop busy as dead |
|
||||
| Deep / blackbox | `/api/monitoring/health` | — |
|
||||
|
||||
`/healthz` only reports process lifecycle (`ok` / `starting` / `stopping`). It still
|
||||
runs on the same Node event loop as request handling, so CPU-bound catalog or
|
||||
compression work can delay it — busy ≠ dead. Full probe guidance:
|
||||
[Monitoring guide — Kubernetes probe recommendations](../ops/MONITORING_GUIDE.md#kubernetes-probe-recommendations).
|
||||
|
||||
## Docker Compose with Caddy (HTTPS Auto-TLS)
|
||||
|
||||
|
||||
@@ -39,15 +39,15 @@ system tray, auto-updater, IPC bridge, and zero-config secret bootstrap.
|
||||
|
||||
Confirmed from `electron/package.json`:
|
||||
|
||||
| Package | Version |
|
||||
| ------------------ | -------------------------- |
|
||||
| `electron` | `^41.5.1` |
|
||||
| `electron-builder` | `^26.10.0` |
|
||||
| `electron-updater` | `^6.8.5` |
|
||||
| `better-sqlite3` | `^12.9.0` |
|
||||
| App version | `3.8.0` |
|
||||
| App id | `online.omniroute.desktop` |
|
||||
| Product name | `OmniRoute` |
|
||||
| Package | Version |
|
||||
| ------------------ | --------------------------------------------------------- |
|
||||
| `electron` | `^41.5.1` |
|
||||
| `electron-builder` | `^26.10.0` |
|
||||
| `electron-updater` | `^6.8.5` |
|
||||
| `better-sqlite3` | root `^13.0.2` (Node-API prebuilds — no Electron rebuild) |
|
||||
| App version | `3.8.0` |
|
||||
| App id | `online.omniroute.desktop` |
|
||||
| Product name | `OmniRoute` |
|
||||
|
||||
## Scripts (root `package.json`)
|
||||
|
||||
@@ -260,14 +260,14 @@ Releases are published to GitHub Releases (`diegosouzapw/OmniRoute`), which is a
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
| --------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `Cannot find module 'better-sqlite3'` after Electron major bump | `cd electron && npm rebuild` |
|
||||
| `ERR_DLOPEN_FAILED` for native module | Re-run `prepare:bundle` and verify ABI matches Electron's Node |
|
||||
| Window appears blank on Linux | Confirm Next.js server actually bound to PORT (check `[Server]` logs) |
|
||||
| macOS notarization stalls | Ensure `APPLE_*` vars are exported, not just in `.env` |
|
||||
| Windows SmartScreen warning | Sign with EV cert, or users right-click → "Run anyway" |
|
||||
| Smoke test fails with port-in-use | Stop any local dev server on 20128 before running `electron:smoke:packaged` |
|
||||
| Symptom | Fix |
|
||||
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Cannot find module 'better-sqlite3'` after Electron major bump | better-sqlite3 v13 ships Node-API prebuilds — re-run `npm install` at the root and `prepare:bundle` (it verifies the prebuild for the current platform) |
|
||||
| `ERR_DLOPEN_FAILED` for native module | Re-run `prepare:bundle` — it fails fast when the Node-API prebuild for the current platform is missing |
|
||||
| Window appears blank on Linux | Confirm Next.js server actually bound to PORT (check `[Server]` logs) |
|
||||
| macOS notarization stalls | Ensure `APPLE_*` vars are exported, not just in `.env` |
|
||||
| Windows SmartScreen warning | Sign with EV cert, or users right-click → "Run anyway" |
|
||||
| Smoke test fails with port-in-use | Stop any local dev server on 20128 before running `electron:smoke:packaged` |
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 340 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 341 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (109 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 150 migrations)
|
||||
- **State management:** Zustand (client), SQLite (server persistence)
|
||||
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
|
||||
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
|
||||
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ │ ├── secrets.ts # Secrets management
|
||||
│ │ │ ├── stateReset.ts # State reset utilities
|
||||
│ │ │ ├── migrationRunner.ts # Schema migration runner
|
||||
│ │ │ └── migrations/ # 148 versioned SQL migration files
|
||||
│ │ │ └── migrations/ # 149 versioned SQL migration files
|
||||
│ │ ├── evals/ # Eval runner and scheduler
|
||||
│ │ ├── memory/ # Persistent conversational memory
|
||||
│ │ │ ├── extraction.ts # Memory extraction from conversations
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (340), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (341), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **340 AI providers** with automatic format translation
|
||||
- **341 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
|
||||
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
|
||||
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
|
||||
|
||||
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
|
||||
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 150 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
|
||||
|
||||
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.
|
||||
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **340-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **341-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 109 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||