Integrated into release/v3.8.44 — IP filter persistence + authz-pipeline enforcement (closes#6131). HARD-neutro: validate-release-green on the merge shows the same 3 pre-existing base-reds as the release baseline (test-masking cycle-wide, unit red-herring, integration batch-E2E env); #6131's own tests + ip-filter/pipeline suites all green.
Proxy-registry batch management (batch-delete, auto-test, background health scheduler) + fix resolveProviderAlias to follow the alias chain transitively (oc -> opencode -> opencode-zen). Probe target now operator-configurable via PROXY_HEALTH_TEST_URL. Scope-creep files from the original branch dropped.
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Promotes Bifrost (@maximhq/bifrost — Go AI-gateway) from an env-only relay
sidecar to a first-class embedded/supervised service, matching the existing
cliproxy/9router model. Implements item #2 of #5670; the broader RouterBackend
contract (items #1, #3-#5) stays out of scope.
- Installer (npm-style, ninerouter model): install/update/getInstalledVersion/
getLatestVersion (1h cache)/resolveSpawnArgs (Go single-dash flags, pinned
BIFROST_TRANSPORT_VERSION), needsApiKey=false
- Bootstrap SERVICES entry (healthPath /v1/models) + spawn-args factory branch
- Migration 113 seeds the version_manager row (not_installed, port 8080,
auto_update=1, provider_expose=1)
- 7 lifecycle API routes under /api/services/bifrost/ (verbatim from cliproxy,
errors sanitized) — loopback-only via existing LOCAL_ONLY_API_PREFIXES
- Shared [name]/logs branch for bifrost
- Dashboard tab + registration in the services page shell
- Relay auto-wiring: getBifrostRoutingConfig defaults BIFROST_BASE_URL to the
supervised port when the instance is running; explicit env still wins; the
env-only relay path (/v1/relay/.../bifrost) stays unchanged (compat layer)
- Docs (EMBEDDED-SERVICES, openapi) + unit tests (installer/route-guard/routing,
19 tests) + RUN_SERVICES_INT-gated integration lifecycle
Note: the actual Go-binary install/start/health path requires a documented VPS
live-test before merge (Hard Rule #18 / spec section 7); the gated integration
harness is the vehicle for that run.
Adds Mux (coder/mux — local agent-orchestration daemon) as a fourth-tier
embedded service built on the existing ServiceSupervisor framework, the
same shape as 9Router and CLIProxyAPI:
- Installer (src/lib/services/installers/mux.ts): npm install/update via
runNpm (array args + env-based prefix, no shell interpolation), modeled
on ninerouter.ts. Mux ships an npm package (`mux`) with a documented
headless `mux server --host <host> --port <port>` mode, so no
git-clone+build path was needed.
- Registered in bootstrap.ts (SERVICES[] + buildSpawnArgsFactory).
- DB seed migration 113 (version_manager row, not_installed/auto_start=0).
- 7 API endpoints under /api/services/mux/ (install/start/stop/restart/
update/status/auto-start) plus the shared [name]/logs SSE endpoint,
mirroring the cliproxy route shape and delegating errors through
createErrorResponse().
- Dashboard tab (MuxServiceTab) reusing ServiceStatusCard,
ServiceLifecycleButtons, AutoStartToggle, ServiceLogsPanel.
- Docs: EMBEDDED-SERVICES.md (service table, architecture diagram, API
reference, key-injection section), openapi.yaml, ENVIRONMENT.md,
.env.example.
Security:
- Every /api/services/mux/* route is covered by the existing
LOCAL_ONLY_API_PREFIXES "/api/services/" prefix (Hard Rule #17);
added an explicit isLocalOnlyPath regression test for all 8 routes.
- Mux binds to 127.0.0.1 explicitly (never 0.0.0.0) as defense-in-depth,
since it orchestrates AI agents that can execute host commands.
- The bearer token is generated the same way as 9Router's key
(getOrCreateApiKey) and injected via MUX_SERVER_AUTH_TOKEN (mux's
documented env form) rather than a CLI flag, so it never appears in
`ps`/process listings.
- No shell interpolation anywhere in the installer (Hard Rule #13): all
npm/spawn args are static arrays; the install prefix and auth token
travel via the env option.
Inspired-by: https://github.com/decolua/9router/pull/1802
Co-authored-by: Ansh7473 <Ansh7473@users.noreply.github.com>
* fix(kimi-web): align catalog with live models
Update the kimi-web catalog and request scenario selection to match
www.kimi.com's live GetAvailableModels response.
* fix(qwen-web): stop aliasing qwen3-coder-plus
Keep qwen3-coder-plus as its own model because it is present in the
live Qwen web models catalog.
Integrated into release/v3.8.44 — fix(dashboard) render Update-now API errors as text, not the raw envelope object (#5991).
Merged with --admin: the fix is a one-line frontend change funneling the error body through the already-tested extractApiErrorMessage() helper, guarded by tests/unit/ui/home-update-error-render-5991.test.ts (3/3 pass, 3/3 fail on pre-fix source). The release branch is under a heavy parallel-merge storm (tip advanced ~6× mid-CI), so the branch is synced to the latest tip and landed atomically to avoid perpetual CONFLICTING; unit-shard reds seen earlier were pre-existing base-reds/flakes unrelated to this source-scan-only change.
* feat(i18n): auto-detect browser language on first visit
Adds a pure detectBrowserLocale() matcher (exact match, zh-HK/zh-MO
folded to zh-TW, language-prefix match, else null) plus a client-only
LocaleAutoDetect component mounted once in the root layout. On first
visit (no locale cookie set), it reads navigator.languages, computes a
match against the supported locales, and persists it via the same
cookie/localStorage writer LanguageSelector already used for manual
selection (now extracted to shared/lib/persistLocale.ts) before
refreshing the router.
Co-authored-by: anmingwei <anmingwei@dobest.com>
Inspired-by: https://github.com/decolua/9router/pull/1324
* chore(changelog): restore release entries + add browser-lang-detect bullet
---------
Co-authored-by: anmingwei <anmingwei@dobest.com>
CodeWhale (https://github.com/Hmbown/CodeWhale) is the actively-maintained
successor to DeepSeek TUI — same author, renamed project. Added as a dual
entry alongside the existing "deepseek-tui" catalog entry (rather than a
hard rename) so users who still run the old DeepSeek TUI binary keep a
working dashboard card, while new users are steered to "codewhale".
New /api/cli-tools/codewhale-settings route writes the primary
~/.codewhale/config.toml and keeps an existing legacy
~/.deepseek/config.toml in sync (read fallback + best-effort write sync),
mirroring deepseek-tui-settings/route.ts. CLI_TOOLS and cliRuntime catalogs
updated; catalog cardinality tests/constants bumped accordingly (18→19
visible code tools, 28→29 total).
Inspired-by: https://github.com/decolua/9router/pull/1761
Co-authored-by: aristorinjuang <aristorinjuang@gmail.com>
* feat(server): support reverse-proxy basePath deployment
Adds OMNIROUTE_BASE_PATH (opt-in, empty by default) to next.config.mjs
using Next.js's native basePath support so a deployment behind a
reverse-proxy subpath (e.g. https://host/omniroute/) works without
manual header stripping. Next.js strips the configured prefix from
nextUrl.pathname before route classification, so classifyRoute() and
isLocalOnlyPath() keep matching un-prefixed paths.
The two hardcoded auth redirect targets in
src/server/authz/pipeline.ts (root "/" -> "/dashboard" and
unauthenticated dashboard -> "/login") now prefix with
request.nextUrl.basePath so they stay inside the deployed subpath.
Default empty basePath is a no-op for existing root-path deployments.
Co-authored-by: zocomputer <help@zocomputer.com>
Inspired-by: https://github.com/decolua/9router/pull/1810
* docs(env): document OMNIROUTE_BASE_PATH in .env.example + ENVIRONMENT.md; restore changelog
* docs(env): document AUGGIE_BIN + CLI_AUGGIE_BIN (base-red from #5972 auggie)
---------
Co-authored-by: zocomputer <help@zocomputer.com>
* feat(providers): add SumoPod and X5Lab OpenAI-compatible providers
Both are OpenAI-compatible BYOK aggregator gateways, wired via the
default executor with bearer API-key auth. Neither ships a hardcoded
model list — both use passthroughModels with an empty seed list and a
live /v1/models fetcher, so the catalog always reflects what each
gateway actually serves instead of speculative model IDs.
- SumoPod: https://ai.sumopod.com/v1/chat/completions (sk- keys)
- X5Lab: https://api.x5lab.dev/v1/chat/completions (x5- keys)
Regression guard: tests/unit/sumopod-x5lab-provider.test.ts.
Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1288
* chore(changelog): restore release entries + add sumopod/x5lab bullet
* test(golden): regenerate translate-path for sumopod + x5lab providers
* test(providers): bump APIKEY count 164→166 for sumopod + x5lab
---------
Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
* feat(providers): add Charm Hyper OpenAI-compatible provider
Registers Charm Hyper (hyper.charm.land) as a new API-key gateway
provider: OpenAI-compatible chat completions format, bearer auth,
free tier (100 monthly Hypercredits). Models are resolved via
passthrough (modelsUrl + live /v1/models import) instead of a
hardcoded upstream model list, since the specific model catalog is
not publicly documented.
Co-authored-by: whale <admin@dyntech.cc>
Inspired-by: https://github.com/decolua/9router/pull/2006
* test(golden): regenerate translate-path for charm-hyper provider
* test(providers): bump APIKEY count 163→164 for charm-hyper
---------
Co-authored-by: whale <admin@dyntech.cc>
* feat(providers): add Nube.sh OpenAI-compatible provider
Nube.sh is a live BYOK OpenAI-compatible gateway (LiteLLM proxy) at
https://ai.nube.sh/api/v1, Bearer/API-key auth. Registered as an apikey
inference-host with an OpenAI-format, default-executor registry entry.
Its live model catalog is only reachable with a valid key
(/api/v1/models returns 401 unauthenticated), so no model IDs are
hardcoded — the entry uses passthroughModels + modelsUrl for live
enumeration instead of shipping unverifiable IDs.
Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/2294
* test(golden): regenerate translate-path for nube provider
* test(providers): bump APIKEY count 162→163 for nube
---------
Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
* feat(providers): add b.ai OpenAI-compatible provider
Adds bai as a new OpenAI-compatible BYOK provider, distinct from the
existing thebai/theb.ai provider, using passthrough model discovery
(no hardcoded model list, live catalog served from api.b.ai/v1/models).
Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
Inspired-by: https://github.com/decolua/9router/pull/963
* test(golden): regenerate translate-path for b.ai provider
* test(providers): bump APIKEY count 161→162 for b.ai
---------
Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
* feat(providers): add Qiniu OpenAI-compatible provider
Wires Qiniu (七牛云) AI inference gateway as a BYOK API-key provider.
Qiniu proxies many upstream models (DeepSeek V3/V4, Claude, Kimi and
more) behind a single key, so it ships with an empty static seed and
relies on passthroughModels + the live /v1/models catalog instead of a
single stale hardcoded model id.
- metadata: src/shared/constants/providers/apikey/gateways.ts
- registry entry: open-sse/config/providers/registry/qiniu/index.ts
(format openai, executor default, bearer auth, baseUrl
https://api.qnaigc.com/v1/chat/completions, modelsUrl
https://api.qnaigc.com/v1/models)
- added to NAMED_OPENAI_STYLE_PROVIDERS so model import serves the live
catalog and falls back to the (empty) local catalog on error, same
pattern as the existing dgrid/zenmux/orcarouter gateways
- tests: tests/unit/qiniu-provider.test.ts (metadata, registry
resolution, passthrough validation, live /v1/models fetch + fallback)
Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
Inspired-by: https://github.com/decolua/9router/pull/911
* chore(changelog): restore release entries + add qiniu bullet
* test(golden): regenerate translate-path for qiniu provider
* test(providers): bump APIKEY count 160→161 for qiniu
---------
Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
* feat(providers): add ModelScope OpenAI-compatible provider
Ports ModelScope (Alibaba 魔搭) as a new API-key, OpenAI-compatible
provider — upstream 9router PR #1764. The upstream PR hardcoded
`https://api-inference.modelscope.ai/...` (`.ai` TLD); verified against
ModelScope's own API-Inference docs and third-party integration guides
that the real production domain is `api-inference.modelscope.cn`
(`.cn` TLD) and shipped that instead. Also drops the PR's static
5-model snapshot in favor of `passthroughModels: true` with an empty
seed list + `modelsUrl`, since ModelScope's open-model catalog moves
fast.
Updates the providers-constants-split characterization test's hardcoded
APIKEY_PROVIDERS count (159 -> 160) to match the new entry.
Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1764
* chore(changelog): restore release entries + add modelscope bullet
* test(golden): regenerate translate-path for modelscope provider
---------
Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
* feat(providers): add Augment (Auggie CLI) local provider
Adds a new local, no-auth provider that spawns the user's local `auggie`
CLI (`auggie --print --quiet --model <m> --`) and pipes a flattened prompt
via stdin, wrapping stdout as an OpenAI-compatible SSE stream or a single
chat.completion JSON body depending on the request's `stream` flag.
Auth is delegated entirely to `auggie login` outside OmniRoute — the
connection is registered `noAuth: true` and `refreshCredentials()` is a
no-op, matching the existing `NOAUTH_PROVIDERS` credential-less flow
(synthetic connection, no DB row required). An optional connection row is
still admitted via `FREE_APIKEY_PROVIDER_IDS` for display/priority
tracking, consistent with `opencode`. The dashboard "Test Connection"
flow spawns `auggie --version` to confirm the CLI is installed and
runnable, since there is no API key to validate upstream.
Security hardening (spawn is an untrusted-input sink):
- Command injection: spawn no longer passes `shell: true` on Windows. The
binary is resolved to a concrete path/name and argv is handed straight to
the OS loader, so no cmd.exe metacharacter interpretation is possible.
- Argument injection (flag smuggling): `model` is validated against the
registry allowlist (`auggieProvider.models`) before any spawn — a model
that is unknown or starts with "-" is rejected with a sanitized error and
the subprocess is never started. A trailing `--` marks end-of-options in
the argv as belt-and-suspenders.
Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1200
* test(golden): regenerate translate-path for auggie provider
---------
Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
* feat(oauth): import Codex connection from a raw ChatGPT access token
OmniRoute's only Codex import path (/api/oauth/codex/import) required both
access_token and refresh_token, leaving no import path for a user who only
has a bare ChatGPT website access token (no refresh token).
- src/lib/db/providers.ts: createProviderConnection gains an explicit
authType "access_token" branch — intentionally never deduped (no stable
long-lived identity to match on) — and derives the connection name from
email/name the same way "oauth" does.
- src/lib/oauth/services/codexImport.ts: export extractCodexAccountInfo so
the new import path reuses the existing JWT decode instead of duplicating
one.
- New route POST /api/oauth/codex/import-token (Zod-validated body
{ accessToken, name? }); errors routed through buildErrorBody /
sanitizeErrorMessage. The executor's refreshCredentials() already
degrades safely to null when there is no refresh token, forcing re-auth
on expiry instead of a refresh exchange.
- OAuthModal.tsx: the callback-URL manual-paste path for codex now detects
an eyJ-prefixed pasted token and posts it to the new endpoint, mirroring
the existing grok-cli raw-token paste pattern.
Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1290
* chore(changelog): restore release entries + add codex token-import bullet
---------
Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
* feat(dashboard): add tool-source diagnostics settings toggle
Adds a Settings > Advanced card (cloned from DebugModeCard) that lets
operators flip the existing `logToolSources` flag from the UI instead
of editing the DB row directly. The backend gate (chatCore.ts) and DB
default were already present but had no toggle. Also adds
`logToolSources` to the /api/settings Zod PATCH schema (it is `.strict()`,
so the key was previously rejected) and en-only i18n strings.
Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1825
* chore(changelog): restore release entries + add tool-source toggle bullet
---------
Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
* feat(dashboard): collapse and sort provider quota rows by remaining
Sort the expanded quota list by remaining percentage (highest first)
and collapse it to the first 3 rows by default, with a "Show N more" /
"Show less" toggle when a connection reports more than 3 quotas. This
keeps the most at-risk quotas out of view below a long list of
healthy ones.
Extracts the sort/slice logic into pure helpers
(sortQuotasByRemaining, getVisibleQuotas) exported from
QuotaCardExpanded.tsx and unit-tests them directly.
Co-authored-by: CườngNH <j2.cuong@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1919
* chore(changelog): restore release entries + add quota collapse/sort bullet
---------
Co-authored-by: CườngNH <j2.cuong@gmail.com>
* feat(dashboard): suggest HuggingFace Hub media models
MVP scope:
- imageRegistry.ts: add an image kind entry for the huggingface provider
(HF Inference API text-to-image), with a dedicated "huggingface-image"
format since the endpoint returns raw image bytes rather than JSON.
- New handler open-sse/handlers/imageGeneration/providers/huggingface.ts,
wired into imageGeneration.ts's format dispatch.
- New pure helper module open-sse/services/hfModelSuggestions.ts: maps a
dashboard media kind to an HF Hub pipeline_tag and sorts/limits raw HF
Hub search results (unit-tested directly).
- New route GET /api/v1/providers/suggested-models proxies the public HF
Hub models search API server-side (Zod-validated query, buildErrorBody
on every error path, no HF token exposed client-side — this project has
no server-side HF search token config, so it calls unauthenticated).
- UI: ImageExampleCard now fetches suggested HF Hub models for the
huggingface provider and merges them into the model picker as a
selectable chip row, alongside the existing static provider models list.
- i18n: adds media.suggestedModels to en.json only.
Co-authored-by: yicone <yicone@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1633
* chore(changelog): restore release entries + add hf-hub media suggest bullet
---------
Co-authored-by: yicone <yicone@gmail.com>
* feat(cli-tools): add Crush CLI tool to the dashboard
Add a `crush` entry to the dashboard CLI-Tools catalog and a new
`/api/cli-tools/crush-settings` route (GET/POST/DELETE), cloned from the
`pi` tool's route as a template. OmniRoute already ships a `crush` CLI
command path (bin/cli/commands/setup-crush.mjs) but the dashboard catalog
had no matching entry.
The new route writes the real Crush config shape (providers.omniroute as
an openai-compat provider block) to the same canonical config path
(~/.config/crush/crush.json) that setup-crush.mjs's resolveCrushTarget()
already writes to, so the dashboard and the CLI command agree on one
location. Adds CLI_TOOL_RUNTIME_CONFIG.crush for detection/status, and
bumps EXPECTED_CODE_COUNT (18 -> 19) plus the catalog-count/schema tests
that enumerate the full tool list.
Co-authored-by: dopaemon <polarisdp@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1233
* chore(changelog): restore release entries + add crush cli bullet
---------
Co-authored-by: dopaemon <polarisdp@gmail.com>
* feat(api-keys): add per-key device/connection tracking
Tracks distinct client devices (SHA-256 fingerprint of IP + User-Agent)
seen with each API key, with a 30-minute TTL and per-key/global caps. The
tracker is in-memory only (module-scoped Map, same pattern as
sessionManager.ts — no global.* singleton) and never stores the raw IP:
it is masked before being written.
Hooked into open-sse/handlers/chatCore.ts (the real chat entry) rather
than the legacy src/sse/handlers path. New GET /api/keys/[id]/devices
management route exposes masked device details for a key, and the
API Keys dashboard tab gets a "Devices" count badge alongside the
existing Sessions badge.
This is a new granularity distinct from the existing maxSessions cap
(src/lib/db/apiKeys.ts), which limits concurrent sticky-routing sessions
rather than tracking device identity.
Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
Inspired-by: https://github.com/decolua/9router/pull/931
* chore(changelog): restore release entries + add api-keys device-tracking bullet
---------
Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
* feat(proxy): add Webshare proxy pool import and sync
Adds Webshare (https://proxy.webshare.io) as a fourth source in the
free-proxy provider framework alongside 1proxy, Proxifly, and IPLocate.
WebshareProvider paginates the account's `/api/v2/proxy/list/` endpoint
(Authorization: Token <key>), upserts proxies into the shared
`free_proxies` table via the existing db/freeProxies.ts helpers, and
tombstones proxies the account no longer lists (recycled/retired IDs)
while never touching rows already promoted into the live proxy pool.
Unlike the other sources, Webshare is a paid per-account list, so it is
gated on FREE_PROXY_WEBSHARE_API_KEY rather than a plain on/off flag.
No DB migration needed — reuses the existing free_proxies table and
proxy_registry-on-promote path.
Co-authored-by: ricatix <d.enistraju155@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1176
* chore(changelog): restore release entries + add webshare bullet
---------
Co-authored-by: ricatix <d.enistraju155@gmail.com>