chore: import upstream snapshot with attribution
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: "Authorization Guide"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Authorization Guide
|
||||
|
||||
> **Source of truth:** `src/server/authz/`, `src/shared/constants/publicApiRoutes.ts`, `src/lib/api/requireManagementAuth.ts`, `src/shared/utils/apiAuth.ts`
|
||||
> **Last updated:** 2026-06-28 — v3.8.40
|
||||
|
||||
OmniRoute has a route-aware authorization pipeline that gates every API request. Classification is **deterministic** and **fail-closed** — anything that cannot be classified ends up as `MANAGEMENT` and demands a session or management-grade token. This page explains the model for engineers maintaining routes or designing new endpoints.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/authz-pipeline.mmd](../diagrams/authz-pipeline.mmd)
|
||||
|
||||
## Two Auth Modes
|
||||
|
||||
### 1. API Key (Bearer)
|
||||
|
||||
Used for the OpenAI/Anthropic/Gemini-compatible client APIs and a few management routes when the key has the `manage` scope.
|
||||
|
||||
```
|
||||
Authorization: Bearer <api-key>
|
||||
```
|
||||
|
||||
Validated by `isValidApiKey()` / `extractApiKey()` in `src/sse/services/auth.ts` and re-exported through `src/shared/utils/apiAuth.ts`. The validator also accepts the `OMNIROUTE_API_KEY` / `ROUTER_API_KEY` env vars as persistent passthrough keys (issue #1350).
|
||||
|
||||
### 2. Dashboard Session (auth_token cookie)
|
||||
|
||||
For dashboard pages and admin operations.
|
||||
|
||||
```
|
||||
Cookie: auth_token=<JWT signed with JWT_SECRET>
|
||||
```
|
||||
|
||||
Verified by `isDashboardSessionAuthenticated()` in `src/shared/utils/apiAuth.ts`. The pipeline auto-refreshes the JWT when it has fewer than 7 days left in its 30-day lifetime.
|
||||
|
||||
Some management routes accept **either** mode: cookie OR `Bearer <key>` when the API key has the `manage` (or `admin`) scope. This is what enables the "configurable via API calls" workflow added in v3.8.
|
||||
|
||||
## Route Classes
|
||||
|
||||
`src/server/authz/types.ts` defines three classes; any route that cannot be classified deterministically falls back to `MANAGEMENT`.
|
||||
|
||||
| Class | Description | Auth required |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `PUBLIC` | Explicitly safe routes — login, logout, status, init, health, onboarding bootstrap. | None |
|
||||
| `CLIENT_API` | Model-serving endpoints — `/api/v1/*`, `/api/v1beta/*`, plus aliases `/v1/*`, `/v1beta/*`, `/chat/completions`, `/responses`, `/models`, `/codex/*`. | Bearer key when the effective `REQUIRE_API_KEY` feature flag is enabled |
|
||||
| `MANAGEMENT` | Dashboard pages, settings, providers, keys, admin and diagnostics endpoints. | Dashboard session OR Bearer with `manage` scope |
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Incoming request → src/proxy.ts
|
||||
→ runAuthzPipeline() in src/server/authz/pipeline.ts
|
||||
1. Strip trusted internal headers (x-omniroute-auth-*, x-omniroute-route-class)
|
||||
2. Generate request id, classify route via classifyRoute()
|
||||
3. If pathname == "/" → redirect /dashboard
|
||||
4. If draining (graceful shutdown) and /api/* → 503
|
||||
5. If non-GET /api/* → checkBodySize() guard
|
||||
6. If OPTIONS → CORS preflight 204
|
||||
7. If options.enforce == false → pass-through with route-class headers
|
||||
8. Otherwise: POLICIES[routeClass].evaluate(ctx)
|
||||
- allow → stamp x-omniroute-auth-{kind,id,label,scopes} → NextResponse.next()
|
||||
- reject → JSON error w/ correlation_id (dashboard pages → 302 /login)
|
||||
```
|
||||
|
||||
Trusted internal headers (defined in `src/server/authz/headers.ts`) are **stripped from incoming requests** before classification — clients cannot pre-populate `x-omniroute-auth-*` to impersonate a subject.
|
||||
|
||||
### Policy contracts
|
||||
|
||||
Each route class has a policy in `src/server/authz/policies/`:
|
||||
|
||||
- **`publicPolicy`** (`policies/public.ts`) — always returns `allow({ kind: "anonymous", id: "anonymous" })`.
|
||||
- **`clientApiPolicy`** (`policies/clientApi.ts`) — extracts Bearer, validates via `validateApiKey()`. Falls through to anonymous only when the effective `REQUIRE_API_KEY` feature flag is disabled. The effective flag is resolved through `isRequireApiKeyEnabled()` (`DB feature flag override > process.env.REQUIRE_API_KEY > default`) so Dashboard Feature Flags and environment variables govern `/api/v1/*`, `/api/v1beta/*`, and aliases consistently; resolver failures fail closed. Allows dashboard-session requests on client API routes (including `/api/v1/models`, used by the dashboard model catalog).
|
||||
- **`managementPolicy`** (`policies/management.ts`) — accepts dashboard session, internal model-sync requests (matched against `/api/providers/[name]/(sync-models|models)`), or skips entirely if `isAuthRequired()` returns false. Returns 403 (`AUTH_001`) when a Bearer token is present but invalid, 401 otherwise. Also enforces the route-guard tiers (LOCAL_ONLY / ALWAYS_PROTECTED) before any auth branch — see [Route Guard Tiers](../security/ROUTE_GUARD_TIERS.md). LOCAL_ONLY paths in `LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES` (today: `/api/mcp/`) may be accessed from non-loopback when the Bearer key carries the `manage` scope; all other LOCAL_ONLY paths remain strict-loopback regardless of scope.
|
||||
|
||||
A successful policy returns `AuthSubject` with `kind ∈ { client_api_key, dashboard_session, management_key, anonymous }`. Downstream handlers can read it via `assertAuth(request, "CLIENT_API")` in `src/server/authz/assertAuth.ts` instead of re-running auth logic.
|
||||
|
||||
## Public Routes List
|
||||
|
||||
`src/shared/constants/publicApiRoutes.ts` is the explicit allowlist:
|
||||
|
||||
```ts
|
||||
PUBLIC_API_ROUTE_PREFIXES = [
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/api/auth/status",
|
||||
"/api/init",
|
||||
"/api/v1/", // treated as CLIENT_API in classify, not as "no-auth public"
|
||||
"/api/cloud/",
|
||||
"/api/sync/bundle",
|
||||
"/api/oauth/",
|
||||
];
|
||||
|
||||
PUBLIC_READONLY_API_ROUTE_PREFIXES = ["/api/monitoring/health", "/api/settings/require-login"];
|
||||
|
||||
PUBLIC_READONLY_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
||||
```
|
||||
|
||||
Read-only prefixes are public **only** for safe methods. Note: `classifyRoute()` excludes `/api/v1/*` and `/api/v1beta/*` from the PUBLIC fall-through — those are always `CLIENT_API` so the Bearer-key policy still applies.
|
||||
|
||||
## Adding a New Route
|
||||
|
||||
### Pattern 1 — Public client API endpoint (Bearer-auth)
|
||||
|
||||
Routes under `/api/v1/` and `/api/v1beta/` are classified `CLIENT_API` automatically. The middleware enforces the Bearer check; route handlers don't need to redo it but can read the subject if useful.
|
||||
|
||||
```typescript
|
||||
// src/app/api/v1/your-route/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { assertAuth } from "@/server/authz/assertAuth";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const subject = assertAuth(req, "CLIENT_API");
|
||||
// subject.kind === "client_api_key" | "anonymous" | "dashboard_session"
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2 — Management endpoint (session or Bearer + manage)
|
||||
|
||||
Use `requireManagementAuth()` from `src/lib/api/requireManagementAuth.ts`:
|
||||
|
||||
```typescript
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const rejection = await requireManagementAuth(request);
|
||||
if (rejection) return rejection;
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
`requireManagementAuth()` returns `null` on success or a JSON error `Response`:
|
||||
|
||||
- 401 `AUTH_001` "Authentication required" — no credentials at all
|
||||
- 403 — invalid Bearer **or** Bearer present but key lacks the `manage` / `admin` scope
|
||||
|
||||
`hasManageScope(scopes)` returns true for `"manage"` or `"admin"`.
|
||||
|
||||
### Pattern 3 — Adding to the public allowlist
|
||||
|
||||
Add the prefix to `PUBLIC_API_ROUTE_PREFIXES` (or `PUBLIC_READONLY_API_ROUTE_PREFIXES` for GET-only). Update unit tests at `tests/unit/public-api-routes.test.ts` and `tests/unit/authz/classify.test.ts`.
|
||||
|
||||
## Scopes
|
||||
|
||||
API keys carry a `scopes` array (stored as JSON in `api_keys.scopes`, see `src/lib/db/apiKeys.ts`).
|
||||
|
||||
### Management scope
|
||||
|
||||
- `manage` / `admin` — grants the key access to management API endpoints when sent as Bearer.
|
||||
|
||||
### MCP scopes (`src/shared/constants/mcpScopes.ts`)
|
||||
|
||||
Each MCP tool requires specific scopes via `MCP_TOOL_SCOPES`. Full list (`MCP_SCOPE_LIST`):
|
||||
|
||||
```
|
||||
read:health, read:combos, write:combos, read:quota, read:usage,
|
||||
read:models, execute:completions, execute:search, write:budget,
|
||||
write:resilience, pricing:write, read:cache, write:cache,
|
||||
read:compression, write:compression, read:proxies
|
||||
```
|
||||
|
||||
Scope enforcement in `open-sse/mcp-server/server.ts` passes each tool's scope list into
|
||||
`evaluateToolScopes()` after `resolveCallerScopeContext()` resolves scopes from MCP auth info,
|
||||
request metadata, or `OMNIROUTE_MCP_SCOPES`.
|
||||
|
||||
## Auth Required Toggle
|
||||
|
||||
`isAuthRequired()` in `src/shared/utils/apiAuth.ts` decides whether **any** auth is enforced for a request:
|
||||
|
||||
- `settings.requireLogin === false` → auth is globally disabled.
|
||||
- No password configured **and** no `INITIAL_PASSWORD` env var → bootstrap mode allows the onboarding wizard and loopback requests, but exposed network requests still need credentials.
|
||||
- Any DB error → fails closed (secure-by-default).
|
||||
|
||||
Client API key enforcement uses `isRequireApiKeyEnabled()` in `src/shared/utils/featureFlags.ts`, not a direct `process.env.REQUIRE_API_KEY` read. This matters for deployed instances: toggling `REQUIRE_API_KEY` in Dashboard → Feature Flags stores a DB override and immediately affects `/v1/*`, `/v1beta/*`, `/models`, `/responses`, `/chat/completions`, `/codex/*`, and other client-API auth checks that share this helper. If the feature flag store cannot be read, client API auth fails closed and requires a key.
|
||||
|
||||
## Breaking Change — v3.8.0
|
||||
|
||||
The `/api/v1/agents/tasks/*` and `/api/resilience/model-cooldowns` endpoints **now require management auth** (commit `588a0333`). Clients previously sending a normal API key without the `manage` scope receive `403`. Migration: either issue the key the `manage` scope in the API Keys dashboard, or use a logged-in dashboard session.
|
||||
|
||||
## Behaviour Change — v3.8.2
|
||||
|
||||
`/api/mcp/*` (the remote MCP server) is still LOCAL_ONLY by default but now accepts non-loopback requests when the `Authorization: Bearer <api-key>` header carries the `manage` scope. The carve-out is gated explicitly per-path via `LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES` in `src/server/authz/routeGuard.ts`; the sibling LOCAL_ONLY prefix `/api/cli-tools/runtime/*` is intentionally NOT bypassable because it can spawn arbitrary subprocesses. Anonymous requests to `/api/mcp/*` from non-loopback continue to return `403 LOCAL_ONLY` — the default for any new LOCAL_ONLY path remains strict-loopback. See [Route Guard Tiers](../security/ROUTE_GUARD_TIERS.md#manage-scope-carve-out).
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests: `tests/unit/authz/` — `classify.test.ts`, `pipeline.test.ts`, `client-api-policy.test.ts`, `management-policy.test.ts`, `public-policy.test.ts`.
|
||||
- Public allowlist: `tests/unit/public-api-routes.test.ts`.
|
||||
- Run focused: `node --import tsx/esm --test tests/unit/authz/classify.test.ts`.
|
||||
|
||||
## Debugging
|
||||
|
||||
The pipeline always stamps responses with:
|
||||
|
||||
```
|
||||
x-request-id: <correlation id, echoed in error bodies>
|
||||
x-omniroute-route-class: PUBLIC | CLIENT_API | MANAGEMENT
|
||||
```
|
||||
|
||||
For authenticated requests the upstream (handler-side) request headers also include:
|
||||
|
||||
```
|
||||
x-omniroute-auth-kind: client_api_key | dashboard_session | management_key | anonymous
|
||||
x-omniroute-auth-id: key_<last-4> | "dashboard" | "anonymous"
|
||||
x-omniroute-auth-label: (optional)
|
||||
x-omniroute-auth-scopes: comma-separated list
|
||||
```
|
||||
|
||||
Use `assertAuth(req, expectedClass)` inside handlers — it throws `AuthzAssertionError` with code `AUTHZ_NOT_INITIALIZED` if the middleware was bypassed (helpful for catching configuration regressions in tests).
|
||||
|
||||
## See Also
|
||||
|
||||
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — auth marker per endpoint
|
||||
- [COMPLIANCE.md](../security/COMPLIANCE.md) — audit log for auth events
|
||||
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — MCP scope enforcement details
|
||||
- Source: `src/server/authz/`, `src/lib/api/requireManagementAuth.ts`
|
||||
@@ -0,0 +1,844 @@
|
||||
---
|
||||
title: "OmniRoute Codebase Documentation"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# OmniRoute Codebase Documentation
|
||||
|
||||
> **Version:** v3.8.0
|
||||
> **Last updated:** 2026-06-28
|
||||
> **Audience:** Engineers contributing to OmniRoute or building integrations on top of it.
|
||||
>
|
||||
> For high-level architecture diagrams and the reasoning behind each subsystem, read
|
||||
> [ARCHITECTURE.md](./ARCHITECTURE.md). For deep dives on individual subsystems
|
||||
> (Auto Combo, MCP server, A2A server, Skills, Memory, Cloud Agents, Resilience,
|
||||
> Compression, etc.) see their dedicated files in this `docs/` directory.
|
||||
|
||||
This file describes **what exists in the repository today** so that a new engineer
|
||||
can navigate the tree, understand the runtime layering, and know where to add code
|
||||
without inventing new modules.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tech Stack
|
||||
|
||||
| Concern | Choice |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Web framework | **Next.js 16** (App Router, standalone output, no global middleware) |
|
||||
| Language | **TypeScript 6.0+** — target `ES2022`, `module: esnext`, `moduleResolution: bundler`, `strict: false` |
|
||||
| Runtime | **Node.js** `>=22.22.2 <23` or `>=24.0.0 <27` (enforced via `engines` + `SUPPORTED_NODE_RANGE`) |
|
||||
| Database | **SQLite** via `better-sqlite3` (singleton, WAL journaling) |
|
||||
| Desktop | **Electron 41** + `electron-builder` 26.10 (separate workspace at `electron/`) |
|
||||
| Tests | **Node native test runner** (unit/integration), **Vitest** (MCP, autoCombo, cache), **Playwright** (e2e + protocols-e2e) |
|
||||
| Build | Next.js standalone via `scripts/build/build-next-isolated.mjs` |
|
||||
| Lint/format | ESLint flat config + Prettier (`lint-staged` via Husky pre-commit) |
|
||||
| Module system | ESM everywhere (`"type": "module"`) |
|
||||
| Workspaces | npm workspace — `open-sse` is the only sub-workspace |
|
||||
|
||||
Path aliases (`tsconfig.json`):
|
||||
|
||||
- `@/*` → `src/*`
|
||||
- `@omniroute/open-sse` → `open-sse/index.ts`
|
||||
- `@omniroute/open-sse/*` → `open-sse/*`
|
||||
|
||||
Default HTTP port: **`20128`** (API and dashboard share the same process). Data
|
||||
directory is `DATA_DIR` env var, defaulting to `~/.omniroute/`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Repository Layout
|
||||
|
||||
```
|
||||
OmniRoute/
|
||||
├── src/ Next.js application (App Router, libs, domain, server, shared)
|
||||
├── open-sse/ Streaming engine workspace (@omniroute/open-sse)
|
||||
├── electron/ Desktop wrapper (Electron 41 main + preload)
|
||||
├── bin/ CLI entry points (omniroute, reset-password)
|
||||
├── tests/ Unit, integration, e2e, protocols-e2e, translator, security, fixtures
|
||||
├── scripts/ Build, sync, check, migration, and runtime helper scripts
|
||||
├── docs/ Public documentation (this directory)
|
||||
├── public/ Static assets, PWA manifest, service worker
|
||||
├── config/ Runtime config samples
|
||||
├── images/ Marketing/screenshot assets
|
||||
├── _ideia/, _references/, _mono_repo/, _tasks/ Internal scratch / planning (not shipped)
|
||||
├── CLAUDE.md Repo rules for Claude Code
|
||||
├── AGENTS.md Deeper architecture reference for agents
|
||||
├── package.json v3.8.0, workspace root
|
||||
└── tsconfig.json Path aliases + core compiler options
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `src/` — Next.js Application
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ App Router pages + API routes
|
||||
├── lib/ Core libraries (DB, auth, OAuth, skills, memory, …)
|
||||
├── domain/ Pure domain layer (policy, fallback, cost, lockout, …)
|
||||
├── server/ Server-only modules (authz, cors, auth)
|
||||
├── shared/ Types, constants, validation, contracts, utils (cross-boundary safe)
|
||||
├── mitm/ Man-in-the-middle proxy helpers for CLI integration
|
||||
├── models/ Local model metadata / aliasing
|
||||
├── sse/ Legacy SSE handlers that still live under src/ (not open-sse/)
|
||||
├── store/ Client-side state stores
|
||||
├── middleware/ Route-level middleware utilities (not Next.js global middleware)
|
||||
├── scripts/ In-tree scripts importable by app code
|
||||
├── types/ Ambient and shared TS types
|
||||
├── i18n/ Locale bundles
|
||||
├── instrumentation.ts Next.js instrumentation hook
|
||||
├── instrumentation-node.ts
|
||||
├── server-init.ts Process-level bootstrap (env, DB, jobs, sync)
|
||||
└── proxy.ts Top-level proxy bootstrap helper
|
||||
```
|
||||
|
||||
### 3.1 `src/app/` — App Router
|
||||
|
||||
The App Router exposes both the dashboard UI and the public/management HTTP API.
|
||||
There is **no global middleware** — interception is done per-route.
|
||||
|
||||
Top-level segments under `src/app/`:
|
||||
|
||||
| Path | Purpose |
|
||||
| ----------------------------------------------------------------------------- | ----------------------------------------- |
|
||||
| `api/` | All HTTP API routes (see breakdown below) |
|
||||
| `a2a/` | A2A JSON-RPC 2.0 endpoint (`POST /a2a`) |
|
||||
| `.well-known/agent.json/` | A2A Agent Card discovery document |
|
||||
| `(dashboard)/` | Dashboard UI (route group, no URL prefix) |
|
||||
| `auth/`, `login/`, `forgot-password/`, `callback/` | Auth flows |
|
||||
| `landing/` | Marketing/landing page |
|
||||
| `docs/` | Embedded API docs viewer |
|
||||
| `status/`, `maintenance/`, `offline/` | Operational pages |
|
||||
| `privacy/`, `terms/` | Legal pages |
|
||||
| `400/`, `401/`, `403/`, `408/`, `429/`, `500/`, `502/`, `503/` | Static error pages |
|
||||
| `error.tsx`, `global-error.tsx`, `not-found.tsx`, `forbidden/`, `loading.tsx` | Framework error/loading boundaries |
|
||||
| `layout.tsx`, `page.tsx`, `globals.css`, `manifest.ts` | Root shell |
|
||||
|
||||
#### 3.1.1 `src/app/(dashboard)/dashboard/` — UI pages
|
||||
|
||||
`agents`, `analytics`, `api-manager`, `audit`, `auto-combo`, `batch`, `cache`,
|
||||
`changelog`, `cli-tools`, `cloud-agents`, `combos`, `compression`, `context`,
|
||||
`costs`, `endpoint`, `health`, `limits`, `logs`, `memory`, `onboarding`,
|
||||
`playground`, `providers`, `search-tools`, `settings`, `skills`, `system`,
|
||||
`translator`, `usage`, `webhooks`, plus root `page.tsx`, `HomePageClient.tsx`,
|
||||
`BootstrapBanner.tsx`.
|
||||
|
||||
#### 3.1.2 `src/app/api/` — Top-level API groups
|
||||
|
||||
```
|
||||
src/app/api/
|
||||
├── a2a/{status, tasks}
|
||||
├── acp/
|
||||
├── admin/
|
||||
├── analytics/
|
||||
├── assess/
|
||||
├── auth/
|
||||
├── batches/
|
||||
├── cache/
|
||||
├── cli-tools/
|
||||
├── cloud/{codex-responses-ws}
|
||||
├── combos/
|
||||
├── compliance/
|
||||
├── compression/
|
||||
├── context/
|
||||
├── db/, db-backups/
|
||||
├── evals/
|
||||
├── fallback/
|
||||
├── files/
|
||||
├── health/
|
||||
├── init/
|
||||
├── internal/{concurrency}
|
||||
├── keys/
|
||||
├── logs/
|
||||
├── mcp/{audit, sse, status, stream, tools}
|
||||
├── memory/{health, [id]/, route.ts}
|
||||
├── model-combo-mappings/
|
||||
├── models/
|
||||
├── monitoring/
|
||||
├── oauth/
|
||||
├── openapi/
|
||||
├── policies/
|
||||
├── pricing/
|
||||
├── provider-metrics/, provider-models/, provider-nodes/
|
||||
├── providers/
|
||||
├── rate-limit/, rate-limits/
|
||||
├── resilience/
|
||||
├── restart/, shutdown/
|
||||
├── search/
|
||||
├── sessions/
|
||||
├── settings/
|
||||
├── skills/{executions, [id], install, marketplace, route.ts, skillssh}
|
||||
├── storage/
|
||||
├── sync/, synced-available-models/
|
||||
├── system/
|
||||
├── tags/
|
||||
├── telemetry/
|
||||
├── token-health/
|
||||
├── translator/
|
||||
├── tunnels/
|
||||
├── services/ Embedded service management (9router, cliproxy) — LOCAL_ONLY
|
||||
├── upstream-proxy/
|
||||
├── usage/
|
||||
├── v1/ OpenAI-compatible public API
|
||||
├── v1beta/ Gemini-style compat
|
||||
├── version-manager/
|
||||
└── webhooks/
|
||||
```
|
||||
|
||||
#### 3.1.2a `src/app/api/services/` — Embedded Services management
|
||||
|
||||
Routes for installing, starting, stopping, and monitoring 9Router and CLIProxyAPI.
|
||||
All paths are classified **LOCAL_ONLY** (loopback only, hard rule #17) because they
|
||||
can invoke `npm install` and spawn child processes.
|
||||
|
||||
```
|
||||
src/app/api/services/
|
||||
├── 9router/
|
||||
│ ├── _lib.ts getOrInitSupervisor() helper
|
||||
│ ├── install/route.ts POST — npm install via execFile
|
||||
│ ├── start/route.ts POST — supervisor.start()
|
||||
│ ├── stop/route.ts POST — supervisor.stop()
|
||||
│ ├── restart/route.ts POST — supervisor.restart()
|
||||
│ ├── update/route.ts POST — npm install newer version
|
||||
│ ├── rotate-key/route.ts POST — generate new API key + restart
|
||||
│ ├── status/route.ts GET — live + DB status + version metadata
|
||||
│ └── auto-start/route.ts POST — toggle auto_start flag
|
||||
├── cliproxy/
|
||||
│ ├── _lib.ts getOrInitSupervisor() helper
|
||||
│ ├── install/route.ts POST — npm install
|
||||
│ ├── start/route.ts POST — supervisor.start()
|
||||
│ ├── stop/route.ts POST — supervisor.stop()
|
||||
│ ├── restart/route.ts POST — supervisor.restart()
|
||||
│ ├── update/route.ts POST — npm install newer version
|
||||
│ ├── status/route.ts GET — live + DB status + version metadata
|
||||
│ └── auto-start/route.ts POST — toggle auto_start flag
|
||||
└── [name]/
|
||||
└── logs/route.ts GET — SSE log tail (shared by all services)
|
||||
```
|
||||
|
||||
Corresponding dashboard UI:
|
||||
`src/app/(dashboard)/dashboard/providers/services/` — two-tab page (CLIProxyAPI + 9Router).
|
||||
Reverse proxy for 9Router embedded UI:
|
||||
`src/app/(dashboard)/dashboard/providers/services/[name]/embed/[...path]/route.ts`
|
||||
|
||||
Deep-dive: `docs/frameworks/EMBEDDED-SERVICES.md`
|
||||
|
||||
#### 3.1.3 `src/app/api/v1/` — OpenAI-compatible public API
|
||||
|
||||
```
|
||||
v1/
|
||||
├── accounts/[id]/ account lookup
|
||||
├── agents/tasks/[id]/, agents/tasks/ A2A-flavored task endpoints
|
||||
├── api/ internal API helpers exposed under v1/api
|
||||
├── audio/{speech, transcriptions}/ TTS + STT
|
||||
├── batches/[id]/{cancel}, batches/ OpenAI Batches API
|
||||
├── chat/completions/ Chat Completions (the main endpoint)
|
||||
├── chatgpt-web/ ChatGPT-Web compat
|
||||
├── completions/ Legacy text completions
|
||||
├── embeddings/ Embeddings
|
||||
├── files/[id]/, files/ Files API
|
||||
├── _helpers/ Shared route helpers (no public URL)
|
||||
├── images/{edits, generations}/ Image gen + edit
|
||||
├── issues/ Triage helper endpoints
|
||||
├── management/{proxies}/ Management-scoped routes inside v1
|
||||
├── messages/{count_tokens}/ Anthropic-style messages compat
|
||||
├── models/ Model listing (`route.ts`, `catalog.ts`)
|
||||
├── moderations/ Moderation
|
||||
├── music/ Music gen
|
||||
├── providers/[provider]/ Per-provider operations
|
||||
├── quotas/{check} Quota probes
|
||||
├── registered-keys/ Registered key admin
|
||||
├── rerank/ Reranking
|
||||
├── responses/[...path]/ OpenAI Responses API (catch-all)
|
||||
├── search/ Web search
|
||||
├── videos/ Video gen
|
||||
├── ws/ WebSocket bridge
|
||||
└── route.ts Index handler
|
||||
```
|
||||
|
||||
Every route file follows the same pattern:
|
||||
|
||||
```
|
||||
Route → CORS preflight → Zod body validation → optional auth
|
||||
→ API key policy enforcement → handler delegation (open-sse)
|
||||
```
|
||||
|
||||
`v1beta/` is the Gemini-style compat surface (a thin wrapper that translates into
|
||||
the same `open-sse/handlers/` pipeline).
|
||||
|
||||
### 3.2 `src/lib/` — Core libraries
|
||||
|
||||
Always import data, sync, OAuth, skill, memory, etc. through these modules. The
|
||||
table groups the actual directories and notable top-level files.
|
||||
|
||||
| Module | Purpose |
|
||||
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (6 skills: cost analysis, health report, provider discovery, quota management, smart routing, list-capabilities) |
|
||||
| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` |
|
||||
| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` |
|
||||
| `auth/` | `managementPassword.ts` (password reset / hashing) |
|
||||
| `batches/` | OpenAI Batches API service (`service.ts`) |
|
||||
| `catalog/` | OpenRouter catalog sync (`openrouterCatalog.ts`) |
|
||||
| `cloudAgent/` | Cloud agent registry: `api.ts`, `baseAgent.ts`, `db.ts`, `index.ts`, `registry.ts`, `types.ts`, `agents/{codex, devin, jules}.ts` |
|
||||
| `combos/` | Combo resolution helpers |
|
||||
| `compliance/` | Audit + provider audit: `index.ts`, `providerAudit.ts` |
|
||||
| `config/` | Runtime config glue |
|
||||
| `db/` | SQLite domain modules (see §3.2.1) |
|
||||
| `display/` | UI/display helpers used by API responses |
|
||||
| `embeddings/` | Embedding service registry |
|
||||
| `env/` | Env loading + introspection |
|
||||
| `evals/` | Eval runtime |
|
||||
| `guardrails/` | `piiMasker.ts`, `promptInjection.ts`, `visionBridge.ts`, `visionBridgeHelpers.ts`, `registry.ts`, `base.ts` |
|
||||
| `jobs/` | Background jobs (`autoUpdate.ts`, …) |
|
||||
| `memory/` | Persistent memory: `store.ts`, `cache.ts`, `retrieval.ts`, `summarization.ts`, `extraction.ts`, `injection.ts`, `qdrant.ts`, `settings.ts`, `verify.ts`, `schemas.ts`, `types.ts` |
|
||||
| `monitoring/` | `observability.ts` |
|
||||
| `oauth/` | OAuth providers (14): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `qwen`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` |
|
||||
| `plugins/` | Plugin loader (`index.ts`) |
|
||||
| `promptCache/` | `prefixAnalyzer.ts`, `index.ts` |
|
||||
| `providerModels/` | Managed model lifecycle: `modelDiscovery.ts`, `managedModelImport.ts`, `managedAvailableModels.ts`, `cursorAgent.ts` |
|
||||
| `providers/` | Provider helpers: `catalog.ts`, `validation.ts`, `imageValidation.ts`, `claudeExtraUsage.ts`, `codexConnectionDefaults.ts`, `codexFastTier.ts`, `webCookieAuth.ts`, `managedAvailableModels.ts`, `requestDefaults.ts` |
|
||||
| `resilience/` | `settings.ts` — settings for circuit breaker, cooldown, lockout |
|
||||
| `runtime/` | Runtime feature detection |
|
||||
| `search/` | `executeWebSearch.ts` |
|
||||
| `services/` | Embedded services framework: `ServiceSupervisor.ts` (generic child-process supervisor with operation lock, ring buffer, health checker), `bootstrap.ts` (process-level registration and auto-start), `registry.ts` (tool → supervisor map), `apiKey.ts` (AES-256-GCM key store), `modelSync.ts` (periodic model sync), `ringBuffer.ts` (5 MB circular log buffer), `healthCheck.ts` (HTTP health probe), `types.ts`, `embedWsProxy.ts` (WebSocket proxy), `installers/{ninerouter,cliproxy}.ts`. See `docs/frameworks/EMBEDDED-SERVICES.md` |
|
||||
| `agentSkills/` | Agent Skills catalog + generator: `catalog.ts` (getCatalog/getSkillById/filterCatalog/computeCoverage), `generator.ts` (generateAgentSkills → writes `skills/{id}/SKILL.md`), `openapiParser.ts` (extracts REST endpoints from OpenAPI spec), `cliRegistryParser.ts` (extracts CLI subcommands from bin/cli-registry), `schemas.ts` (Zod: AgentSkillSchema, SkillCoverageSchema, ListQuerySchema, GenerateBodySchema), `types.ts` (AgentSkill, SkillCoverage, SkillMarkdown, GeneratorReport). Consumed by REST routes (`/api/agent-skills/*`), MCP tools (`omniroute_agent_skills_*`), and A2A skill `list-capabilities`. See [AGENT-SKILLS.md](../frameworks/AGENT-SKILLS.md). |
|
||||
| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` |
|
||||
| `spend/` | `batchWriter.ts` (write-behind buffer) |
|
||||
| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) |
|
||||
| `system/` | System-level helpers |
|
||||
| `translator/` | Top-level translator glue (delegates into `open-sse/translator/`) |
|
||||
| `usage/` | Usage accounting: `costCalculator.ts`, `tokenAccounting.ts`, `usageHistory.ts`, `aggregateHistory.ts`, `usageStats.ts`, `callLogs.ts`, `callLogArtifacts.ts`, `fetcher.ts`, `providerLimits.ts`, `migrations.ts` |
|
||||
| `versionManager/` | Auto-update + version manifest |
|
||||
| `ws/` | WebSocket bridge |
|
||||
| `zed-oauth/` | Zed editor OAuth flow |
|
||||
|
||||
Top-level files in `src/lib/`:
|
||||
|
||||
- `localDb.ts` — re-export layer only. **Never** add logic here.
|
||||
- `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts`
|
||||
- `oneproxyRotator.ts`, `oneproxySync.ts`
|
||||
- `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts`
|
||||
- `cloudSync.ts`, `initCloudSync.ts`
|
||||
- `cloudflaredTunnel.ts`, `ngrokTunnel.ts`, `tailscaleTunnel.ts`
|
||||
- `consoleInterceptor.ts`, `container.ts`, `gracefulShutdown.ts`, `idempotencyLayer.ts`
|
||||
- `ipUtils.ts`, `logEnv.ts`, `logPayloads.ts`, `logRotation.ts`
|
||||
- `modelAliasSeed.ts`, `modelCapabilities.ts`, `modelMetadataRegistry.ts`, `modelsDevSync.ts`
|
||||
- `piiSanitizer.ts`, `pricingSync.ts`
|
||||
- `apiKeyExposure.ts`, `cacheControlSettings.ts`, `dataPaths.ts`, `toolPolicy.ts`
|
||||
- `translatorEvents.ts`, `usageDb.ts`, `usageAnalytics.ts`, `webhookDispatcher.ts`
|
||||
|
||||
#### 3.2.1 `src/lib/db/`
|
||||
|
||||
Singleton SQLite database (`getDbInstance()` in `core.ts`, WAL journaling).
|
||||
**Never write raw SQL in routes or handlers** — go through these modules.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/db-schema-overview.mmd](../diagrams/db-schema-overview.mmd)
|
||||
|
||||
Domain modules (each owns one or more tables): `apiKeys.ts`, `backup.ts`,
|
||||
`batches.ts`, `cleanup.ts`, `cliToolState.ts`, `combos.ts`,
|
||||
`commandCodeAuth.ts`, `compression.ts`, `compressionAnalytics.ts`,
|
||||
`compressionCacheStats.ts`, `compressionCombos.ts`, `compressionScheduler.ts`,
|
||||
`contextHandoffs.ts`, `core.ts`, `creditBalance.ts`, `databaseSettings.ts`,
|
||||
`detailedLogs.ts`, `domainState.ts`, `encryption.ts`, `evals.ts`, `files.ts`,
|
||||
`healthCheck.ts`, `jsonMigration.ts`, `migrationRunner.ts`,
|
||||
`modelComboMappings.ts`, `models.ts`, `oneproxy.ts`, `prompts.ts`,
|
||||
`providers.ts`, `providerLimits.ts`, `proxies.ts`, `quotaSnapshots.ts`,
|
||||
`readCache.ts`, `reasoningCache.ts`, `registeredKeys.ts`, `secrets.ts`,
|
||||
`sessionAccountAffinity.ts`, `settings.ts`, `stateReset.ts`, `stats.ts`,
|
||||
`syncTokens.ts`, `tierConfig.ts`, `upstreamProxy.ts`, `versionManager.ts`,
|
||||
`webhooks.ts`.
|
||||
|
||||
`migrations/` holds 55 versioned `.sql` files (idempotent, transactional) and is
|
||||
executed by `migrationRunner.ts` at boot.
|
||||
|
||||
Tables created across the migrations (52 total):
|
||||
|
||||
`a`, `account_key_limits`, `api_keys`, `batches`, `call_logs`,
|
||||
`combo_adaptation_state`, `combos`, `command_code_auth_sessions`,
|
||||
`compression_analytics`, `compression_cache_stats`,
|
||||
`compression_combo_assignments`, `compression_combos`, `context_handoffs`,
|
||||
`daily_usage_summary`, `db_meta`, `domain_budgets`, `domain_circuit_breakers`,
|
||||
`domain_cost_history`, `domain_fallback_chains`, `domain_lockout_state`,
|
||||
`eval_cases`, `eval_runs`, `eval_suites`, `files`, `hourly_usage_summary`,
|
||||
`key_value`, `mcp_tool_audit`, `memories`, `model_combo_mappings`,
|
||||
`provider_connections`, `provider_key_limits`, `provider_nodes`,
|
||||
`proxy_assignments`, `proxy_logs`, `proxy_registry`, `quota_snapshots`,
|
||||
`reasoning_cache`, `registered_keys`, `request_detail_logs`,
|
||||
`routing_decisions`, `semantic_cache`, `session_account_affinity`,
|
||||
`skill_executions`, `skills`, `sync_tokens`, `tier_assignments`,
|
||||
`tier_config`, `upstream_proxy_config`, `usage_history`, `version_manager`,
|
||||
`webhooks` (plus FTS5 virtual tables for memory search).
|
||||
|
||||
### 3.3 `src/domain/` — Domain layer
|
||||
|
||||
Pure business logic, no I/O. Imported by routes and handlers.
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------------ | ------------------------------------------------- |
|
||||
| `policyEngine.ts` | Top-level policy resolver |
|
||||
| `fallbackPolicy.ts` | Fallback decision tree |
|
||||
| `costRules.ts` | Cost calculation rules |
|
||||
| `lockoutPolicy.ts` | Model lockout decisions |
|
||||
| `tagRouter.ts` | Tag-based routing |
|
||||
| `comboResolver.ts` | Combo resolution from request → target list |
|
||||
| `connectionModelRules.ts` | Per-connection model filters |
|
||||
| `modelAvailability.ts` | Model availability check |
|
||||
| `degradation.ts` | Degraded-mode transitions |
|
||||
| `providerExpiration.ts` | Expired account/key detection |
|
||||
| `quotaCache.ts` | Cached quota decisions |
|
||||
| `responses.ts`, `omnirouteResponseMeta.ts` | Response shape helpers |
|
||||
| `configAudit.ts` | Config change audit |
|
||||
| `assessment/` | Model assessment (per RFC, partially implemented) |
|
||||
| `types.ts` | Shared domain types |
|
||||
|
||||
### 3.4 `src/server/` — Server-only
|
||||
|
||||
Cannot be imported from client components.
|
||||
|
||||
```
|
||||
server/
|
||||
├── auth/loginGuard.ts
|
||||
├── authz/
|
||||
│ ├── classify.ts Classifies routes as public vs management
|
||||
│ ├── assertAuth.ts Assertion helper
|
||||
│ ├── context.ts Per-request authz context
|
||||
│ ├── headers.ts
|
||||
│ ├── pipeline.ts Authz pipeline
|
||||
│ ├── policies/ Concrete policies
|
||||
│ └── types.ts
|
||||
└── cors/origins.ts CORS origin allowlist
|
||||
```
|
||||
|
||||
### 3.5 `src/shared/` — Safe-to-share
|
||||
|
||||
Split into focused subdirectories:
|
||||
|
||||
- `constants/` — `providers.ts` (Zod-validated provider catalog), `models.ts`,
|
||||
`modelSpecs.ts`, `modelCompat.ts`, `pricing.ts`, `cliTools.ts`,
|
||||
`cliCompatProviders.ts`, `routingStrategies.ts`, `comboConfigMode.ts`,
|
||||
`headers.ts`, `upstreamHeaders.ts` (denylist), `mcpScopes.ts`,
|
||||
`errorCodes.ts`, `publicApiRoutes.ts`, `batch.ts`, `batchEndpoints.ts`,
|
||||
`bodySize.ts`, `colors.ts`, `appConfig.ts`, `config.ts`,
|
||||
`sidebarVisibility.ts`, `visionBridgeDefaults.ts`.
|
||||
- `validation/` — `schemas.ts` (~80 Zod schemas), `compressionConfigSchemas.ts`,
|
||||
`oneproxySchemas.ts`, `providerSchema.ts`, `settingsSchemas.ts`, `helpers.ts`.
|
||||
- `contracts/` — public API contracts shipped to npm.
|
||||
- `types/` — shared TS types.
|
||||
- `utils/` — `circuitBreaker.ts`, `apiAuth.ts`, `apiKey.ts`, `apiKeyPolicy.ts`,
|
||||
`apiResponse.ts`, `api.ts`, `classify429.ts`, `cliCompat.ts`, `clipboard.ts`,
|
||||
`cloud.ts`, `cn.ts`, `cors.ts`, `costEstimator.ts`, `featureFlags.ts`,
|
||||
`fetchTimeout.ts`, `formatting.ts`, `inputSanitizer.ts`, `logger.ts`,
|
||||
`machine.ts`, `machineId.ts`, `maskEmail.ts`, `modelCatalogSearch.ts`,
|
||||
`nodeRuntimeSupport.ts`, `parseApiKeys.ts`, `providerHints.ts`,
|
||||
`providerModelAliases.ts`, `rateLimiter.ts`, `releaseNotes.ts`,
|
||||
`a11yAudit.ts`, plus dashboard hooks/components under `services/`, `network/`,
|
||||
`middleware/`, `schemas/`, `hooks/`, `components/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. `open-sse/` — Streaming engine workspace
|
||||
|
||||
Separate npm workspace published as `@omniroute/open-sse`. Owns request
|
||||
processing, executors, translators, services, transformer, and the MCP server.
|
||||
|
||||
```
|
||||
open-sse/
|
||||
├── index.ts Public exports
|
||||
├── package.json Workspace manifest
|
||||
├── tsconfig.json
|
||||
├── types.d.ts
|
||||
├── config/ Provider registries, header profiles, identity, …
|
||||
├── handlers/ Request handlers (chat, embeddings, audio, image, …)
|
||||
├── executors/ 75 provider-specific HTTP executors
|
||||
├── translator/ Format conversion (OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro)
|
||||
├── transformer/ Responses API ↔ Chat Completions stream transformer
|
||||
├── services/ 80+ service modules (combos, fallback, quotas, identity, …)
|
||||
├── utils/ Streaming helpers, TLS client, AWS SigV4, proxy fetch, …
|
||||
└── mcp-server/ MCP server (3 transports, 30 scopes, 94 tools)
|
||||
```
|
||||
|
||||
### 4.1 `open-sse/handlers/`
|
||||
|
||||
| Handler | Purpose |
|
||||
| ----------------------- | ------------------------------------------------------------------------ |
|
||||
| `chatCore.ts` | Main chat pipeline (cache, rate limit, combo routing, executor dispatch) |
|
||||
| `responsesHandler.ts` | OpenAI Responses API entry point |
|
||||
| `embeddings.ts` | Embeddings |
|
||||
| `imageGeneration.ts` | Image generation |
|
||||
| `audioSpeech.ts` | Text-to-speech |
|
||||
| `audioTranscription.ts` | Speech-to-text |
|
||||
| `videoGeneration.ts` | Video generation |
|
||||
| `musicGeneration.ts` | Music generation |
|
||||
| `rerank.ts` | Reranking |
|
||||
| `moderations.ts` | Moderation |
|
||||
| `search.ts` | Web search |
|
||||
| `sseParser.ts` | SSE event parser |
|
||||
| `usageExtractor.ts` | Pull token counts out of upstream streams |
|
||||
| `responseSanitizer.ts` | Strip provider-specific noise |
|
||||
| `responseTranslator.ts` | Glue between provider response and translator layer |
|
||||
|
||||
### 4.2 `open-sse/executors/`
|
||||
|
||||
75 provider executors, each extending `BaseExecutor` (`base.ts`):
|
||||
|
||||
`antigravity`, `azure-openai`, `blackbox-web`, `chatgpt-web`, `cliproxyapi`,
|
||||
`cloudflare-ai`, `codex`, `commandCode`, `cursor`, `default`, `devin-cli`,
|
||||
`muse-spark-web`, `nlpcloud`, `opencode`, `perplexity-web`, `petals`,
|
||||
`pollinations`, `puter`, `qoder`, `vertex`, `windsurf`, plus `claudeIdentity.ts`
|
||||
(shared identity helper) and `index.ts` (registry).
|
||||
|
||||
> Note: providers not listed here are served by `default.ts` using the generic
|
||||
> OpenAI-compatible executor. The full provider catalog (237 entries) lives in
|
||||
> `src/shared/constants/providers.ts`.
|
||||
|
||||
### 4.3 `open-sse/translator/`
|
||||
|
||||
Hub-and-spoke translation (OpenAI is the hub).
|
||||
|
||||
- **9 request translators** (`translator/request/`):
|
||||
`antigravity-to-openai`, `claude-to-gemini`, `claude-to-openai`,
|
||||
`gemini-to-openai`, `openai-responses`, `openai-to-claude`,
|
||||
`openai-to-cursor`, `openai-to-gemini`, `openai-to-kiro`.
|
||||
- **9 response translators** (`translator/response/`):
|
||||
`claude-to-openai`, `cursor-to-openai`, `gemini-to-claude`, `gemini-to-openai`,
|
||||
`kiro-to-openai`, `openai-responses`, `openai-to-antigravity`,
|
||||
`openai-to-claude`.
|
||||
- **9 helpers** (`translator/helpers/`):
|
||||
`claudeHelper`, `geminiHelper`, `geminiToolsSanitizer`, `maxTokensHelper`,
|
||||
`openaiHelper`, `responsesApiHelper`, `schemaCoercion`, `toolCallHelper`, plus
|
||||
helper tests.
|
||||
- **Image helpers** (`translator/image/sizeMapper.ts`).
|
||||
- Top-level: `bootstrap.ts`, `formats.ts`, `registry.ts`, `index.ts`.
|
||||
|
||||
### 4.4 `open-sse/transformer/`
|
||||
|
||||
- `responsesTransformer.ts` — `TransformStream`-based Responses API ↔ Chat
|
||||
Completions converter (used by the `responses/` route catch-all).
|
||||
|
||||
### 4.5 `open-sse/services/`
|
||||
|
||||
Highlights (full list under `open-sse/services/`):
|
||||
|
||||
| Concern | Files |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Combo routing | `combo.ts` (17 strategies), `comboConfig.ts`, `comboMetrics.ts`, `comboManifestMetrics.ts`, `comboAgentMiddleware.ts` |
|
||||
| Auto Combo engine | `autoCombo/` — `engine.ts`, `scoring.ts`, `taskFitness.ts`, `virtualFactory.ts`, `modePacks.ts`, `autoPrefix.ts`, `persistence.ts`, `providerDiversity.ts`, `providerRegistryAccessor.ts`, `routerStrategy.ts`, `selfHealing.ts`, `index.ts` |
|
||||
| Resilience | `accountFallback.ts` (cooldown + lockout), `errorClassifier.ts`, `emergencyFallback.ts`, `rateLimitManager.ts`, `rateLimitSemaphore.ts`, `accountSemaphore.ts`, `accountSelector.ts` |
|
||||
| Quotas | `quotaMonitor.ts`, `quotaPreflight.ts`, `bailianQuotaFetcher.ts`, `codexQuotaFetcher.ts`, `deepseekQuotaFetcher.ts`, `crofUsageFetcher.ts`, `antigravityCredits.ts` |
|
||||
| Caching | `reasoningCache.ts`, `searchCache.ts`, `signatureCache.ts`, `requestDedup.ts` |
|
||||
| Routing intelligence | `intentClassifier.ts`, `taskAwareRouter.ts`, `backgroundTaskDetector.ts`, `volumeDetector.ts`, `wildcardRouter.ts`, `workflowFSM.ts`, `specificityDetector.ts`, `specificityRules.ts`, `specificityTypes.ts` |
|
||||
| Model handling | `modelCapabilities.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`, `modelStrip.ts`, `model.ts`, `provider.ts`, `providerRequestDefaults.ts`, `providerCostData.ts`, `payloadRules.ts` |
|
||||
| Compression | `compression/` — full compression engine wiring |
|
||||
| Token + session | `tokenRefresh.ts`, `sessionManager.ts`, `apiKeyRotator.ts`, `contextManager.ts`, `contextHandoff.ts`, `systemPrompt.ts`, `roleNormalizer.ts`, `responsesInputSanitizer.ts`, `toolSchemaSanitizer.ts`, `toolLimitDetector.ts`, `thinkingBudget.ts` |
|
||||
| Tier / manifest | `tierResolver.ts`, `tierConfig.ts`, `tierDefaults.json`, `tierTypes.ts`, `manifestAdapter.ts` |
|
||||
| IP / network | `ipFilter.ts`, `webSearchFallback.ts` |
|
||||
| Batches | `batchProcessor.ts` |
|
||||
| Usage | `usage.ts` |
|
||||
|
||||
### 4.6 `open-sse/mcp-server/`
|
||||
|
||||
- **31 registered tools** wired in `server.ts` (12 scoped under `schemas/tools.ts`,
|
||||
5 compression tools, 3 memory tools, 4 skills tools, plus advanced tools added
|
||||
through `advancedTools.ts`).
|
||||
- **3 transports**: stdio, HTTP Streamable, SSE.
|
||||
- **13 scopes** declared in `src/shared/constants/mcpScopes.ts`.
|
||||
- Audit table: `mcp_tool_audit` (populated by `audit.ts`).
|
||||
- Files: `server.ts`, `index.ts`, `httpTransport.ts`, `audit.ts`, `scopeEnforcement.ts`,
|
||||
`runtimeHeartbeat.ts`, `descriptionCompressor.ts`, `schemas/{tools, a2a, audit, index}.ts`,
|
||||
`tools/{advancedTools, compressionTools, memoryTools, skillTools}.ts`,
|
||||
plus tests under `__tests__/`.
|
||||
- See [MCP-SERVER.md](../frameworks/MCP-SERVER.md) for the full tool catalog.
|
||||
|
||||
### 4.7 `open-sse/config/`
|
||||
|
||||
Provider registries (`providerRegistry.ts`, `providerModels.ts`,
|
||||
`providerHeaderProfiles.ts`), per-format model registries (`audioRegistry.ts`,
|
||||
`embeddingRegistry.ts`, `imageRegistry.ts`, `moderationRegistry.ts`,
|
||||
`musicRegistry.ts`, `rerankRegistry.ts`, `searchRegistry.ts`, `videoRegistry.ts`),
|
||||
identity helpers (`codexIdentity.ts`, `codexInstructions.ts`,
|
||||
`anthropicHeaders.ts`, `antigravityUpstream.ts`, `antigravityModelAliases.ts`,
|
||||
`cliFingerprints.ts`, `toolCloaking.ts`, `defaultThinkingSignature.ts`),
|
||||
credential helpers (`credentialLoader.ts`, `codexClient.ts`), and cloud
|
||||
adapters (`azureAi.ts`, `bedrock.ts`, `datarobot.ts`, `glmProvider.ts`,
|
||||
`maritalk.ts`, `oci.ts`, `petals.ts`, `runway.ts`, `sap.ts`, `watsonx.ts`,
|
||||
`ollamaModels.ts`, `errorConfig.ts`, `constants.ts`, `registryUtils.ts`).
|
||||
|
||||
### 4.8 `open-sse/utils/`
|
||||
|
||||
Streaming primitives and provider helpers: `stream.ts`, `streamHandler.ts`,
|
||||
`streamHelpers.ts`, `streamPayloadCollector.ts`, `streamReadiness.ts`,
|
||||
`sseHeartbeat.ts`, `proxyFetch.ts`, `proxyDispatcher.ts`, `tlsClient.ts`,
|
||||
`networkProxy.ts`, `awsSigV4.ts`, `cacheControlPolicy.ts`,
|
||||
`cursorChecksum.ts`, `cursorAgentProtobuf.ts`, `cursorVersionDetector.ts`,
|
||||
`comfyuiClient.ts`, `kieTask.ts`, `bypassHandler.ts`, `aiSdkCompat.ts`,
|
||||
`thinkTagParser.ts`, `urlSanitize.ts`, `usageTracking.ts`, `requestLogger.ts`,
|
||||
`progressTracker.ts`, `cors.ts`, `error.ts`, `logger.ts`, `sleep.ts`,
|
||||
`ollamaTransform.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. `electron/` — Desktop wrapper
|
||||
|
||||
```
|
||||
electron/
|
||||
├── main.js Electron main process
|
||||
├── preload.js Preload bridge (contextIsolation enabled)
|
||||
├── types.d.ts
|
||||
├── package.json electron-builder config, version 3.8.0
|
||||
├── README.md
|
||||
├── assets/ Build resources (icons, entitlements, …)
|
||||
├── node_modules/ Dedicated node_modules (better-sqlite3, electron-updater)
|
||||
└── dist-electron/ Build output (not committed)
|
||||
```
|
||||
|
||||
Five npm scripts at the workspace root: `electron:dev`, `electron:build`,
|
||||
`electron:build:{win,mac,linux}`, `electron:smoke:packaged`. Auto-update is via
|
||||
`electron-updater` pointing at the GitHub release feed.
|
||||
|
||||
---
|
||||
|
||||
## 6. `bin/` — CLI
|
||||
|
||||
```
|
||||
bin/
|
||||
├── omniroute.mjs Main CLI entry (Node ESM)
|
||||
├── reset-password.mjs Reset the management password from CLI
|
||||
├── mcp-server.mjs MCP server launcher (stdio)
|
||||
├── nodeRuntimeSupport.mjs Node version guard
|
||||
└── cli/
|
||||
├── program.mjs Commander program builder
|
||||
├── runtime.mjs withRuntime helper (server-first/db-fallback)
|
||||
├── output.mjs Output formatters (json/jsonl/table/csv)
|
||||
├── i18n.mjs t() helper with locales
|
||||
├── api.mjs API fetch helper
|
||||
├── data-dir.mjs
|
||||
├── encryption.mjs
|
||||
├── sqlite.mjs
|
||||
└── commands/
|
||||
├── registry.mjs Command registration
|
||||
├── setup.mjs
|
||||
├── doctor.mjs
|
||||
├── providers.mjs
|
||||
└── ... (one file per command/group)
|
||||
```
|
||||
|
||||
Two binaries are exposed in `package.json` → `bin`:
|
||||
|
||||
- `omniroute` → `bin/omniroute.mjs`
|
||||
- `omniroute-reset-password` → `bin/reset-password.mjs`
|
||||
|
||||
---
|
||||
|
||||
## 7. `tests/`
|
||||
|
||||
| Directory | Type |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `tests/unit/` | Unit tests via Node native test runner (1821 files, plus `api/`, `auth/`, `authz/` subdirs) |
|
||||
| `tests/integration/` | Cross-module + DB-state tests |
|
||||
| `tests/e2e/` | Playwright UI tests |
|
||||
| `tests/protocols-e2e/` | MCP/A2A protocol e2e |
|
||||
| `tests/translator/` | Translator-specific tests |
|
||||
| `tests/security/` | Security regressions |
|
||||
| `tests/load/` | Load / stress tests |
|
||||
| `tests/golden-set/` | Reference outputs for translator regressions |
|
||||
| `tests/helpers/`, `tests/fixtures/`, `tests/manual/`, `tests/scratch_test.mjs` | Support |
|
||||
|
||||
Common commands:
|
||||
|
||||
| Command | What it runs |
|
||||
| -------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `npm run test:unit` | All `tests/unit/*.test.ts` via Node test runner (concurrency 10) |
|
||||
| `npm run test:vitest` | Vitest suite (MCP, autoCombo, cache) |
|
||||
| `npm run test:e2e` | Playwright UI suite |
|
||||
| `npm run test:protocols:e2e` | MCP + A2A protocol e2e |
|
||||
| `npm run test:coverage` | Coverage gate (≥60% lines/statements/functions/branches) |
|
||||
| `node --import tsx/esm --test tests/unit/<file>.test.ts` | Single file run |
|
||||
|
||||
---
|
||||
|
||||
## 8. `scripts/`
|
||||
|
||||
Organized into 6 subfolders by purpose.
|
||||
|
||||
- **`scripts/build/`** — `build-next-isolated.mjs`, `prepublish.ts`,
|
||||
`prepare-electron-standalone.mjs`, `pack-artifact-policy.ts`,
|
||||
`validate-pack-artifact.ts`, `postinstall.mjs`, `postinstallSupport.mjs`,
|
||||
`uninstall.mjs`, `bootstrap-env.mjs`, `runtime-env.mjs`,
|
||||
`native-binary-compat.mjs`.
|
||||
- **`scripts/dev/`** — `run-next.mjs`, `run-next-playwright.mjs`,
|
||||
`run-standalone.mjs`, `standalone-server-ws.mjs`, `responses-ws-proxy.mjs`,
|
||||
`v1-ws-bridge.mjs`, `smoke-electron-packaged.mjs`,
|
||||
`run-playwright-tests.mjs`, `run-ecosystem-tests.mjs`,
|
||||
`run-protocol-clients-tests.mjs`, `sync-env.mjs`, `healthcheck.mjs`,
|
||||
`system-info.mjs`.
|
||||
- **`scripts/check/`** — `check-cycles.mjs`, `check-docs-sync.mjs`,
|
||||
`check-docs-counts-sync.mjs`, `check-env-doc-sync.mjs`,
|
||||
`check-deprecated-versions.mjs`, `check-route-validation.mjs`,
|
||||
`check-t11-any-budget.mjs`, `check-pr-test-policy.mjs`,
|
||||
`check-supported-node-runtime.ts`, `test-report-summary.mjs`.
|
||||
- **`scripts/docs/`** — `generate-docs-index.mjs`, `gen-provider-reference.ts`.
|
||||
- **`scripts/i18n/`** — `generate-multilang.mjs`, `run-visual-qa.mjs`,
|
||||
`generate-qa-checklist.mjs`, `apply-priority-overrides.mjs`,
|
||||
`validate_translation.py`, `check_translations.py`, `i18n_autotranslate.py`,
|
||||
`untranslatable-keys.json`.
|
||||
- **`scripts/ad-hoc/`** — `cursor-tap.cjs`, `sync-cursor-models.mjs`,
|
||||
`migrate-env.mjs`, `dbsetup.js`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Request Pipeline (Summary)
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd)
|
||||
|
||||
```
|
||||
Client request
|
||||
→ /v1/chat/completions (route.ts)
|
||||
CORS preflight check
|
||||
Zod validation (chatCompletionsSchema in shared/validation/schemas.ts)
|
||||
Auth (extractApiKey + isValidApiKey OR requireManagementAuth)
|
||||
Policy engine (src/server/authz/pipeline.ts)
|
||||
Guardrails (PII masker, prompt injection, vision bridge)
|
||||
→ handleChatCore() (open-sse/handlers/chatCore.ts)
|
||||
Cache check (semantic + read cache)
|
||||
Rate limit (rateLimitManager, accountSemaphore)
|
||||
Combo routing (if model resolves to a combo)
|
||||
comboResolver → loop per target → handleSingleModel()
|
||||
translateRequest() (open-sse/translator/request/*)
|
||||
getExecutor(providerId).execute() (open-sse/executors/*)
|
||||
fetch upstream → retry/backoff via accountFallback
|
||||
translateResponse() (open-sse/translator/response/*)
|
||||
SSE stream OR JSON response
|
||||
If Responses API: TransformStream via open-sse/transformer/responsesTransformer.ts
|
||||
→ Compliance audit (src/lib/compliance/)
|
||||
→ Response to client
|
||||
```
|
||||
|
||||
### Resilience runtime state (three mechanisms)
|
||||
|
||||
| Mechanism | Scope | Where |
|
||||
| ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| Provider circuit breaker | Whole provider | `src/shared/utils/circuitBreaker.ts`, persisted in `domain_circuit_breakers` |
|
||||
| Connection cooldown | One account/key | `markAccountUnavailable()` in `src/sse/services/auth.ts`; consumed by `accountFallback.checkFallbackError()` |
|
||||
| Model lockout | Provider + connection + model | `open-sse/services/accountFallback.ts`, persisted in `domain_lockout_state` |
|
||||
|
||||
See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in
|
||||
[CLAUDE.md](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 10. How to Contribute
|
||||
|
||||
### Add a new provider
|
||||
|
||||
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load).
|
||||
2. Add an executor in `open-sse/executors/` if custom logic is required
|
||||
(extend `BaseExecutor`).
|
||||
3. Add a translator in `open-sse/translator/` if it does not speak OpenAI format.
|
||||
4. If OAuth-based, add config under `src/lib/oauth/providers/` and
|
||||
`src/lib/oauth/services/`.
|
||||
5. Register models in `open-sse/config/providerRegistry.ts` (or the format-specific
|
||||
registry under `open-sse/config/`).
|
||||
6. Write tests under `tests/unit/`.
|
||||
|
||||
### Add a new API route
|
||||
|
||||
1. Create `src/app/api/your-route/route.ts`.
|
||||
2. Follow the pattern: CORS → Zod body validation → auth → handler delegation.
|
||||
3. If new request shape: add the Zod schema in `src/shared/validation/schemas.ts`.
|
||||
4. If management-only: add the path to `src/shared/constants/publicApiRoutes.ts`
|
||||
(denylist for the public API surface).
|
||||
5. Add tests under `tests/unit/`.
|
||||
6. Update `docs/reference/API_REFERENCE.md` and `docs/openapi.yaml`.
|
||||
|
||||
### Add a new DB module
|
||||
|
||||
1. Create `src/lib/db/yourModule.ts` and import `getDbInstance()` from `./core.ts`.
|
||||
2. Export CRUD functions for your domain.
|
||||
3. If new tables: add a migration under `src/lib/db/migrations/`, numbered
|
||||
sequentially, idempotent, transactional.
|
||||
4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**).
|
||||
5. Add tests under `tests/unit/`.
|
||||
|
||||
### Add a new MCP tool
|
||||
|
||||
1. Add the tool definition under `open-sse/mcp-server/tools/` (or extend
|
||||
`open-sse/mcp-server/schemas/tools.ts`).
|
||||
2. Assign the appropriate scope(s) in `src/shared/constants/mcpScopes.ts`.
|
||||
3. Register the tool in `open-sse/mcp-server/server.ts`.
|
||||
4. Add tests under `open-sse/mcp-server/__tests__/`.
|
||||
5. Update [MCP-SERVER.md](../frameworks/MCP-SERVER.md).
|
||||
|
||||
### Add a new A2A skill
|
||||
|
||||
See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills live in
|
||||
`src/lib/a2a/skills/` and are registered through the A2A task manager.
|
||||
|
||||
---
|
||||
|
||||
## 11. Conventions
|
||||
|
||||
- **Code style**: 2-space indent, double quotes, 100 char width, semicolons,
|
||||
`es5` trailing commas — enforced by Prettier via `lint-staged`.
|
||||
- **Imports**: external → internal (`@/`, `@omniroute/open-sse`) → relative.
|
||||
- **Naming**: files `camelCase` or `kebab-case`, components `PascalCase`,
|
||||
constants `UPPER_SNAKE`.
|
||||
- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = `error` everywhere;
|
||||
`no-explicit-any` = `warn` in `open-sse/` and `tests/`, error elsewhere.
|
||||
- **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over
|
||||
inference for cross-module boundaries.
|
||||
- **Database**: never write raw SQL in routes or handlers — always go through
|
||||
`src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`.
|
||||
- **Errors**: try/catch with specific error types, log with pino context. Never
|
||||
silently swallow errors in SSE streams; use abort signals for cleanup.
|
||||
- **Security**: never use `eval()` / `new Function()` / implied eval. Validate
|
||||
all inputs with Zod. Encrypt credentials at rest (AES-256-GCM). Keep
|
||||
`src/shared/constants/upstreamHeaders.ts` denylist aligned with the
|
||||
sanitize/validation layer.
|
||||
- **Commits**: Conventional Commits — `feat(scope): subject`. Allowed scopes:
|
||||
`db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`,
|
||||
`a2a`, `memory`, `skills`.
|
||||
- **Branches**: prefixes `feat/`, `fix/`, `refactor/`, `docs/`, `test/`,
|
||||
`chore/`. Never commit directly to `main`.
|
||||
- **Husky**: pre-commit runs `lint-staged` + `check:docs-sync` +
|
||||
`check:any-budget:t11`; pre-push runs `check:any-budget:t11` + `check:tracked-artifacts` (fast gates; excludes `test:unit`).
|
||||
|
||||
---
|
||||
|
||||
## 12. Hard Rules (from CLAUDE.md)
|
||||
|
||||
1. Never commit secrets or credentials.
|
||||
2. Never add logic to `src/lib/localDb.ts`.
|
||||
3. Never use `eval()` / `new Function()` / implied eval.
|
||||
4. Never commit directly to `main`.
|
||||
5. Never write raw SQL in routes — always go through `src/lib/db/` modules.
|
||||
6. Never silently swallow errors in SSE streams.
|
||||
7. Always validate inputs with Zod schemas.
|
||||
8. Always include tests when changing production code.
|
||||
9. Coverage must stay ≥ 60% (statements, lines, functions, branches).
|
||||
|
||||
---
|
||||
|
||||
## 13. See Also
|
||||
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md) — high-level architecture and module
|
||||
responsibilities.
|
||||
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — public + management API reference.
|
||||
- [FEATURES.md](../guides/FEATURES.md) — feature matrix and version highlights.
|
||||
- [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) — circuit breaker, cooldown,
|
||||
lockout deep dive.
|
||||
- [AUTO-COMBO.md](../routing/AUTO-COMBO.md) — Auto Combo scoring and strategies.
|
||||
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — full MCP tool catalog + transports.
|
||||
- [A2A-SERVER.md](../frameworks/A2A-SERVER.md) — A2A protocol skills and discovery.
|
||||
- [COMPRESSION_GUIDE.md](../compression/COMPRESSION_GUIDE.md) — RTK + Caveman compression.
|
||||
- [CLI-TOOLS.md](../reference/CLI-TOOLS.md) — CLI integrations.
|
||||
- [ELECTRON_GUIDE.md](../guides/ELECTRON_GUIDE.md) (if present), [DOCKER_GUIDE.md](../guides/DOCKER_GUIDE.md), [FLY_IO_DEPLOYMENT_GUIDE.md](../ops/FLY_IO_DEPLOYMENT_GUIDE.md), [VM_DEPLOYMENT_GUIDE.md](../ops/VM_DEPLOYMENT_GUIDE.md), [TERMUX_GUIDE.md](../guides/TERMUX_GUIDE.md), [PWA_GUIDE.md](../guides/PWA_GUIDE.md) — deployment targets.
|
||||
- [TROUBLESHOOTING.md](../guides/TROUBLESHOOTING.md) — common operational issues.
|
||||
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — contributor workflow.
|
||||
- [CLAUDE.md](../../CLAUDE.md) — repo rules for Claude Code (the source of truth
|
||||
for many of the conventions above).
|
||||
- [AGENTS.md](../../AGENTS.md) — deeper architecture reference used by agents.
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: "Monitoring & Costs — Navigation Structure"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Monitoring & Costs — Navigation Structure
|
||||
|
||||
> Implemented in Group B (plan 16). See `src/shared/constants/sidebarVisibility.ts`.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Navigation
|
||||
|
||||
The dashboard sidebar (after Group B) has these top-level sections in order:
|
||||
|
||||
```
|
||||
Home
|
||||
Providers
|
||||
Combos
|
||||
API Keys
|
||||
Settings
|
||||
Analytics
|
||||
Costs ← NEW (Group B, plan 16)
|
||||
Monitoring ← REORGANIZED (Group B, plan 16)
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Costs section (new, level 1)
|
||||
|
||||
Path prefix: `/dashboard/costs/`
|
||||
|
||||
| Item | URL | Description |
|
||||
| ------------- | ------------------------------------ | ------------------------------------------------ |
|
||||
| Overview | `/dashboard/costs` | Aggregated cost dashboard (moved from Analytics) |
|
||||
| Pricing | `/dashboard/costs/pricing` | Per-model pricing table |
|
||||
| Budget | `/dashboard/costs/budget` | Budget thresholds + alerts |
|
||||
| Quota Sharing | `/dashboard/costs/quota-share` | Quota Share pools + usage |
|
||||
| Plan Config | `/dashboard/costs/quota-share/plans` | Per-provider plan overrides |
|
||||
|
||||
**Rationale**: Pricing, Budget, and Quota Sharing were previously under
|
||||
`Monitoring > Costs Parameters`. Moving them to a dedicated top-level section
|
||||
makes them discoverable without navigating through observability tooling.
|
||||
|
||||
---
|
||||
|
||||
## Monitoring section (reorganized)
|
||||
|
||||
The Monitoring section now has **Activity at the top** followed by **3 subgroups**:
|
||||
|
||||
```
|
||||
Monitoring
|
||||
├── Activity ← Timeline feed (top-level item)
|
||||
├── Logs group
|
||||
│ ├── Logs (all)
|
||||
│ ├── Proxy Logs
|
||||
│ └── Console Logs
|
||||
├── Audit group
|
||||
│ ├── Audit Log
|
||||
│ ├── MCP Audit
|
||||
│ └── A2A Audit
|
||||
└── System group
|
||||
├── Health
|
||||
└── Runtime
|
||||
```
|
||||
|
||||
### What changed from the old structure
|
||||
|
||||
| Before | After |
|
||||
| -------------------------------------------------------------------------------- | ------------------------------------------------- |
|
||||
| Activity = tab inside Logs that rendered the Audit Log | Activity = dedicated feed (`/dashboard/activity`) |
|
||||
| Costs Parameters group in Monitoring | Moved to Costs section |
|
||||
| Flat list: Logs, Activity (logs), Audit, Health, Runtime, Pricing, Budget, Quota | Structured 3-group + dedicated Costs section |
|
||||
|
||||
---
|
||||
|
||||
## Activity vs Audit Log
|
||||
|
||||
These two are now distinct:
|
||||
|
||||
| Dimension | Activity (`/dashboard/activity`) | Audit Log (`/dashboard/audit`) |
|
||||
| ---------------- | ------------------------------------------------------ | ----------------------------------------- |
|
||||
| **Purpose** | User-facing event feed ("what happened recently") | Compliance / security log |
|
||||
| **Data source** | `GET /api/compliance/audit-log?level=high` | `GET /api/compliance/audit-log?level=all` |
|
||||
| **Format** | Timeline, grouped by day, human-readable verbs + icons | Dense paginaged table, 50/page |
|
||||
| **Filters** | Event type category | Action, severity, actor, date range |
|
||||
| **Export** | Not available | JSON export |
|
||||
| **Actor filter** | Not applicable | Filterable by actor |
|
||||
| **Events shown** | High-level actions only (allowlist) | All audit events |
|
||||
|
||||
### High-Level Actions allowlist
|
||||
|
||||
Defined in `src/lib/audit/highLevelActions.ts`. Controls which events appear in
|
||||
the Activity feed. The allowlist includes:
|
||||
|
||||
- Provider add/remove/test events
|
||||
- Combo create/update/delete
|
||||
- API key lifecycle (create, revoke, rotate)
|
||||
- Budget threshold reached
|
||||
- Auth login/logout
|
||||
- Cloud agent session creation
|
||||
- MCP tool registration
|
||||
- Webhook create/delete
|
||||
- Quota pool/plan changes (`quota.*` actions, Group B)
|
||||
- Platform events (update, deploy)
|
||||
- Skill install/remove
|
||||
|
||||
Events not in this list appear only in the Audit Log.
|
||||
|
||||
### Adding a new high-level action
|
||||
|
||||
Edit `src/lib/audit/highLevelActions.ts` and add the action string to
|
||||
`HIGH_LEVEL_ACTIONS`. This requires a PR (the list is code, not DB-configurable).
|
||||
The corresponding icon can be added to `src/lib/audit/activityIcons.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Redirect: `/dashboard/logs/activity`
|
||||
|
||||
The old path `/dashboard/logs/activity` is permanently redirected (HTTP 308) to
|
||||
`/dashboard/activity` via `permanentRedirect()` in
|
||||
`src/app/(dashboard)/dashboard/logs/activity/page.tsx`.
|
||||
|
||||
The legacy sidebar ID `logs-activity` is preserved in `HIDEABLE_SIDEBAR_ITEM_IDS`
|
||||
(but removed from `SIDEBAR_DEFINITIONS`) to avoid breaking user presets that
|
||||
reference the old ID.
|
||||
|
||||
---
|
||||
|
||||
## i18n
|
||||
|
||||
Namespaces added by Group B:
|
||||
|
||||
| Namespace key | Covers |
|
||||
| ----------------------- | -------------------------------------------------------------- |
|
||||
| `sidebar.costsSection` | Costs section label |
|
||||
| `sidebar.activity` | Activity sidebar item |
|
||||
| `sidebar.logsGroup` | Logs subgroup label |
|
||||
| `sidebar.systemGroup` | System subgroup label |
|
||||
| `sidebar.costsOverview` | Costs overview item |
|
||||
| `activity.*` | All Activity page strings (title, verbs, filters, empty state) |
|
||||
|
||||
Source-of-truth locales: `pt-BR` and `en`. All other 39 locales fall back to
|
||||
English via the `next-intl` fallback mechanism (configured in `src/i18n/config.ts`).
|
||||
@@ -0,0 +1,279 @@
|
||||
---
|
||||
title: Quality Gates Reference
|
||||
---
|
||||
|
||||
# Quality Gates Reference
|
||||
|
||||
This document is the authoritative reference for all CI quality gates in OmniRoute.
|
||||
It describes each gate, what it validates, which CI job it runs in, whether it uses
|
||||
a ratchet baseline or a pass/fail policy, and whether it blocks the build or is advisory.
|
||||
|
||||
For a short summary and the allowlist policy, see the "Quality Gates & Ratchets" section
|
||||
in `CLAUDE.md`.
|
||||
|
||||
---
|
||||
|
||||
## Gate Inventory (~50 scripts)
|
||||
|
||||
Scripts live under `scripts/check/` (policy gates) and `scripts/quality/` (ratchet engine).
|
||||
The CI source of truth is `.github/workflows/ci.yml`.
|
||||
|
||||
### Job: `lint`
|
||||
|
||||
Runs on every PR to `main`. Blocks merge on failure.
|
||||
|
||||
| Script (`npm run ...`) | Validates | Blocking |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
|
||||
| `check:node-runtime` | Node.js version is within the supported range | Yes |
|
||||
| `check:cycles` | Circular imports — all `src/` + `open-sse/` modules | Yes |
|
||||
| `check:route-validation:t06` | Zod schemas present on all routes (Tier 6 policy) | Yes |
|
||||
| `check:any-budget:t11` | `@ts-expect-error // any` count does not exceed budget (Tier 11 catraca) | Yes |
|
||||
| `check:provider-consistency` | Every provider in `providers.ts` has a matching entry in `providerRegistry.ts` (and vice-versa, within the allowlist) | Yes |
|
||||
| `check:fetch-targets` | Every `fetch("/api/...")` in client-side `src/` resolves to a real `route.ts` | Yes |
|
||||
| `check:deps` | All `npm install`-able deps across every `package.json` in the repo are in `dependency-allowlist.json`; new unpinned or slopsquatted packages flagged | Yes |
|
||||
| `audit:deps` | `npm audit` (root + electron) — no high/critical advisories (overlaps osv `check:vuln-ratchet`; see Rationalization Backlog) | Yes |
|
||||
| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashes, no host overrides | Yes |
|
||||
| `check:licenses` | SPDX license allowlist for production dependencies | Yes |
|
||||
| `check:tracked-artifacts` | No build artifacts / committed `node_modules` symlinks (also runs in husky pre-push) | Yes |
|
||||
| `check:file-size` | No source file exceeds the per-extension cap (ratchet: frozen large files in `frozen` list) | Yes |
|
||||
| `check:error-helper` | Error responses in executors/handlers use `buildErrorBody()` / `sanitizeErrorMessage()` (Hard Rule #12) | Yes |
|
||||
| `check:migration-numbering` | Migration SQL files are sequentially numbered, no gaps or duplicates | Yes |
|
||||
| `check:public-creds` | No literal OAuth `client_id`/`client_secret` or Firebase Web keys outside `publicCreds.ts` (Hard Rule #11) | Yes |
|
||||
| `check:db-rules` | No raw SQL outside `src/lib/db/` modules; no barrel-imports from `localDb.ts` (Hard Rules #2/#5) | Yes |
|
||||
| `check:known-symbols` | Provider executors, routing strategies, and translators registered in their dispatch tables match the files on disk — no orphaned or undeclared symbols | Yes |
|
||||
| `check:route-guard-membership` | Every route that spawns a child process is classified by `isLocalOnlyPath()` (Hard Rules #15/#17) | Yes |
|
||||
| `check:test-discovery` | Every `*.test.ts` / `*.spec.ts` file in the repo is collected by at least one test runner (ratchet: orphan list in `test-discovery-baseline.json` can only shrink) | Yes |
|
||||
| `check:docs-sync` | CHANGELOG version, OpenAPI version, and `llm.txt` are in sync | Yes |
|
||||
| `typecheck:core` | TypeScript compilation without errors (advisory warnings only) | Yes |
|
||||
| `typecheck:noimplicit:core` | Strict `noImplicitAny` — forward-looking; many pre-existing call sites still need annotations | **Advisory** (`continue-on-error: true`) |
|
||||
|
||||
### Job: `quality-gate`
|
||||
|
||||
Runs after `test-coverage`. Blocks merge on failure.
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
|
||||
| `quality:collect` | Emits `quality-metrics.json` (ESLint warning count, coverage from merged shard report) | Yes (upstream of ratchet) |
|
||||
| `quality:ratchet` | Each metric in `quality-baseline.json` has not regressed (ESLint warnings ≤ baseline; coverage ≥ baseline) | Yes |
|
||||
| `check:duplication` | Code duplication (jscpd@4) does not exceed baseline in `quality-baseline.json` | Yes |
|
||||
| `check:complexity` | File-level cyclomatic complexity does not exceed the cap (core ESLint `complexity` + `max-lines-per-function`) | Yes |
|
||||
| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — separate ESLint pass; mergeable with `check:complexity` (see Backlog) | Yes |
|
||||
| `check:dead-code` | Unused exports / files ratchet (knip) does not regress vs baseline | Yes |
|
||||
| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) does not regress; largely subsumes `typecheck:noimplicit:core` | Yes |
|
||||
| `check:codeql-ratchet` | Open CodeQL alert count does not regress (reads via `gh api`; graceful-skip without token) | Yes |
|
||||
|
||||
### Job: `quality-extended`
|
||||
|
||||
Entire job is advisory (`continue-on-error: true`). The npm-based ratchets run for
|
||||
real; the external scanners install via `gh release download` and self-skip (exit 0)
|
||||
when a binary is still absent.
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ |
|
||||
| `check:circular-deps` | No circular dependencies (dpdm) | **Advisory** |
|
||||
| `check:bundle-size` | Bundle size does not exceed the cap | **Advisory** |
|
||||
| `check:secrets` | Secret scanning (gitleaks) — skips if binary absent | **Advisory** |
|
||||
| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) do not regress — skips if binary absent | **Advisory** |
|
||||
| `check:workflows` | Workflow lint (actionlint + zizmor) — skips if binaries absent | **Advisory** |
|
||||
| `check:openapi-breaking` | Breaking changes to the public API contract (`openapi.yaml`) vs the base branch (oasdiff) — emits `openapiBreaking=N`; skips if oasdiff absent or base spec unresolvable | **Advisory** |
|
||||
|
||||
### Job: `docs-sync-strict`
|
||||
|
||||
Runs on every PR to `main`. Blocks merge on failure.
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `check:docs-all` | Meta-gate that runs the 6 sub-gates below sequentially | Yes |
|
||||
| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt version consistency | Yes |
|
||||
| ↳ `check:docs-counts` | Counts in prose (provider count, migration count, etc.) are within the ratchet window of the real counts | Yes |
|
||||
| ↳ `check:env-doc-sync` | Every env var in `.env.example` is documented in a docs table, and vice versa | Yes |
|
||||
| ↳ `check:deprecated-versions` | No deprecated version strings in docs | Yes |
|
||||
| ↳ `check:doc-links` | Internal markdown links in docs resolve to real files (`[text]`/`(path)` form) | Yes |
|
||||
| ↳ `check:fabricated-docs` | Routes, env vars, CLI commands, hook names, and file paths cited in docs exist in the codebase. Hard gate via `--strict`; soft-fail without flag. | Yes (via `--strict` in CI) |
|
||||
| `check:cli-i18n` | CLI command strings are present in all i18n locale files | Yes |
|
||||
| `check:openapi-coverage` | OpenAPI spec covers at least a ratcheted floor of real routes | Yes |
|
||||
| `check:openapi-security-tiers` | Security tier annotations in `openapi.yaml` are consistent with `routeGuard.ts` classifications | **Advisory** |
|
||||
| `check:openapi-routes` | Every path in `openapi.yaml` resolves to a real `route.ts` (anti-hallucination) | Yes |
|
||||
| `check:docs-symbols` | Every `/api/...` reference in `docs/**/*.md` resolves to a real `route.ts` (anti-hallucination) | Yes |
|
||||
| `i18n translation drift` | Untranslated keys in i18n locale files — warn only | **Advisory** |
|
||||
|
||||
### Job: `i18n-ui-coverage`
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| --------------------------------- | ----------------------------- | -------- |
|
||||
| `check-ui-keys-coverage` (inline) | UI i18n key coverage is ≥ 65% | Yes |
|
||||
|
||||
### Job: `i18n`
|
||||
|
||||
Full i18n validation matrix (one job per locale). Entire job is advisory.
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| ------------------------------- | ----------------------------------- | ----------------------------------------------------- |
|
||||
| `validate_translation.py quick` | Translation completeness per locale | **Advisory** (`continue-on-error: true` on whole job) |
|
||||
|
||||
### Job: `pr-test-policy`
|
||||
|
||||
Runs on pull requests only.
|
||||
|
||||
| Script | Validates | Blocking |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `check:pr-test-policy` | PRs that change production code in `src/`, `open-sse/`, `electron/`, or `bin/` must include or update tests (Hard Rule #8) | Yes |
|
||||
| `check:test-masking` | Changed test files do not reduce net assert count or add `assert.ok(true)` tautologies | Yes |
|
||||
| `check:pr-evidence` | PR body cites test/VPS evidence for the change (mechanizes Hard Rule #18 by grepping PR prose — fragile, see Backlog) | Yes |
|
||||
|
||||
### Job: `test-vitest`
|
||||
|
||||
Runs after `build`. Blocks merge on failure.
|
||||
|
||||
| Suite | Validates | Blocking |
|
||||
| ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `test:vitest` | MCP server (94 tools), autoCombo, cache — vitest runner | Yes |
|
||||
| `test:vitest:ui` | UI component tests — vitest runner | **Advisory** (`continue-on-error: true`) — failing until Fase 6A UI triage |
|
||||
|
||||
### Nightly workflows (scheduled, advisory)
|
||||
|
||||
These run on a cron schedule (and `workflow_dispatch`), never on PRs. All are advisory.
|
||||
|
||||
| Workflow | Validates | Blocking |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
||||
| `nightly-property` | fast-check property tests with a random seed + high run count | **Advisory** |
|
||||
| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** |
|
||||
| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (skipped without a provider secret) | **Advisory** |
|
||||
| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) against a live OmniRoute using `docs/openapi.yaml` — surfaces spec violations / unhandled 500s (Fase 8 B.4) | **Advisory** |
|
||||
|
||||
---
|
||||
|
||||
## Ratchet Baseline (`quality-baseline.json`)
|
||||
|
||||
The ratchet engine (`scripts/quality/check-quality-ratchet.mjs`) reads `quality-baseline.json`
|
||||
and compares it against the freshly collected `quality-metrics.json`. Any metric that regresses
|
||||
beyond its epsilon fails the build.
|
||||
|
||||
Current tracked metrics:
|
||||
|
||||
| Metric | Direction | Meaning |
|
||||
| --------------------- | --------- | ---------------------------------- |
|
||||
| `eslintWarnings` | `down` | ESLint warning count must not grow |
|
||||
| `coverage.statements` | `up` | Statement coverage must not fall |
|
||||
| `coverage.lines` | `up` | Line coverage must not fall |
|
||||
| `coverage.functions` | `up` | Function coverage must not fall |
|
||||
| `coverage.branches` | `up` | Branch coverage must not fall |
|
||||
|
||||
To update the baseline after a genuine improvement:
|
||||
|
||||
```bash
|
||||
npm run quality:ratchet -- --update
|
||||
git add quality-baseline.json
|
||||
```
|
||||
|
||||
The `--update` flag writes the current measured values into `quality-baseline.json`.
|
||||
Commit this file alongside the change that improved the metric. A PR that improves a
|
||||
metric without updating the baseline will be caught by `--require-tighten` (Fase 6A.5,
|
||||
pending implementation).
|
||||
|
||||
---
|
||||
|
||||
## Allowlist Policy
|
||||
|
||||
Every gate that cannot fail on pre-existing violations uses a frozen allowlist
|
||||
(e.g., `KNOWN_STALE_DOC_REFS`, `KNOWN_MISSING`, `KNOWN_RAW_SQL`). The policy is:
|
||||
|
||||
**Fix the root cause; use the allowlist only when the violation is pre-existing and
|
||||
cannot be fixed in the same PR.**
|
||||
|
||||
When adding an entry to an allowlist:
|
||||
|
||||
1. Include a comment with the justification.
|
||||
2. Reference the tracking issue (e.g., `// #3498 — Phase 2 feature, not yet implemented`).
|
||||
3. Remove the entry in the same PR that fixes the violation — a stale entry that no longer
|
||||
suppresses an active violation is itself a defect (6A.3 stale-enforcement will
|
||||
fail the gate on an orphaned allowlist entry once implemented).
|
||||
|
||||
Do **not** add allowlist entries to make tests pass faster. A green gate with a growing
|
||||
allowlist is a false sense of quality.
|
||||
|
||||
### When a gate fails on your PR
|
||||
|
||||
1. **Read the gate output carefully** — it tells you exactly which file or symbol violated
|
||||
the rule.
|
||||
2. **Fix the violation** — most gates are deterministic filesystem checks that pass as soon
|
||||
as the code is correct.
|
||||
3. **If the violation is pre-existing** (i.e., you did not introduce it but the gate now
|
||||
covers it): add an allowlist entry with a justification comment and a tracking issue.
|
||||
4. **If the gate is a ratchet** (coverage, ESLint warnings, duplication, complexity):
|
||||
your change made the metric worse. Fix the underlying issue, or (rarely) run
|
||||
`npm run quality:ratchet -- --update` if the change is intentional and the metric
|
||||
degradation is acceptable — but document why in the PR description.
|
||||
5. **Advisory gates** (`continue-on-error: true`) are informational — they do not block
|
||||
merge but appear in the CI summary. Fix them anyway.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Gate
|
||||
|
||||
1. Create `scripts/check/check-<name>.mjs` (or `.ts`). Policy gates exit 0/1.
|
||||
Ratchet-style gates emit a metric to `quality-metrics.json` via `collect-metrics.mjs`.
|
||||
2. Add `"check:<name>": "node scripts/check/check-<name>.mjs"` to `package.json`.
|
||||
3. Wire it in `.github/workflows/ci.yml` under the appropriate job
|
||||
(policy → `lint` or `docs-sync-strict`; ratchet → `quality-gate`).
|
||||
4. If it has an allowlist, apply `reportStaleEntries()` from
|
||||
`scripts/check/lib/allowlist.mjs` so stale entries are detected automatically.
|
||||
5. Write a test in `tests/unit/build/` covering the gate's detection logic.
|
||||
6. Update this document (add a row to the relevant job table).
|
||||
|
||||
---
|
||||
|
||||
## Agent tooling: LSP-in-the-loop (opt-in)
|
||||
|
||||
Beyond the CI gates, OmniRoute ships an **opt-in** `agent-lsp` scaffold
|
||||
(a project-level `.mcp.json`, Fase 7 Task 15). Create `.mcp.json`
|
||||
to expose a TypeScript language server to coding agents, so they resolve symbols /
|
||||
diagnostics **before** writing code — a compile-before-claim companion to
|
||||
`typecheck:core` that cuts "invented symbol" errors at the source. It is intentionally
|
||||
not auto-loaded (you pick and verify the MCP↔LSP bridge); a broken entry only logs a
|
||||
connection error and never breaks sessions.
|
||||
|
||||
---
|
||||
|
||||
## Rationalization Backlog (ROI review — Fase 9 Onda 3)
|
||||
|
||||
This inventory was reconciled against `ci.yml` on 2026-06-17 (the prior version omitted
|
||||
`audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`,
|
||||
`check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`,
|
||||
`check:codeql-ratchet`, `check:pr-evidence`). An ROI review of the reconciled set
|
||||
identified the following rationalization candidates. **The merges are mechanical CI
|
||||
changes; the flips/drops are policy decisions reserved for the operator.** Nothing below
|
||||
is applied yet.
|
||||
|
||||
**Also undocumented above** (advisory, low signal): the `docs-lint` job
|
||||
(markdownlint + Vale, whole job `continue-on-error`) and the standalone scanner workflows
|
||||
`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` is in
|
||||
`quality-baseline.json` but is not wired to a blocking ratchet in `ci.yml` — the metric is
|
||||
currently orphaned.
|
||||
|
||||
### Merge / dedup (mechanical, lower risk)
|
||||
|
||||
Each candidate was validated against the live gate state on 2026-06-17 (trust-but-verify);
|
||||
several "obvious" merges turned out to hide debt and are **not** clean drop-ins.
|
||||
|
||||
- **`check:docs-sync` runs twice** — standalone in the `lint` job and again inside `check:docs-all` (`docs-sync-strict`) and the husky pre-commit hook. ✅ **DONE** — standalone `lint` invocation removed.
|
||||
- **CVE scanning** — ❌ **NOT a clean merge.** `audit:deps` hard-fails on any high/critical CVE; `check:vuln-ratchet` (osv) only fails on a _regression_ vs baseline (currently 1 MODERATE). Different semantics — dropping `audit:deps` would lose the absolute high/critical gate. Keep both.
|
||||
- **Cycle detection** — ❌ **NOT a clean merge.** `check:circular-deps` (dpdm) reports **91 cycles** (that is why it is advisory); it cannot be promoted to blocking without first resolving them, and it has a broader scope than the green, curated `check:cycles`. Keep `check:cycles` blocking; resolving the 91 dpdm cycles is its own backlog.
|
||||
- **Complexity** — ⏳ valid but real surgery. `check:complexity` (core ESLint) + `check:cognitive-complexity` (sonarjs) are two ESLint passes over `src` + `open-sse`; merging into one config emitting both metrics needs careful ratchet re-wiring. Deferred.
|
||||
- **`/api` anti-hallucination** — ⏳ valid but script surgery. `check:openapi-routes` (spec→route) + `check:docs-symbols` (prose→route) share resolution logic; collapsing them is a non-trivial script change. Deferred.
|
||||
- **`check:node-runtime` runs in 11 jobs** — ⚠️ **low ROI.** Each is a separate runner and the check is <1s; total savings ~10s, against losing a cheap per-job guard. Not worth the churn.
|
||||
|
||||
### Flip / decide (operator policy)
|
||||
|
||||
- `check:openapi-security-tiers` (advisory) — ❌ **NOT cleanly flippable.** It exits 0 but warns that several `traffic-inspector` routes under `LOCAL_ONLY_API_PREFIXES` lack the `x-loopback-only: true` annotation. Enforcing it requires adding those annotations to `openapi.yaml` first.
|
||||
- `typecheck:noimplicit:core` (advisory) — largely subsumed by the blocking `check:type-coverage` ratchet. Flip to a ratchet or drop the redundant second `tsc` pass.
|
||||
- `test:vitest:ui` (advisory, 14 parked fails) — fix-and-block or delete; don't leave rotting.
|
||||
- `check:secrets` (gitleaks, blocking ratchet frozen at 3 documented false-positives) — allowlist the 3 to reach 0, or demote to advisory. Overlaps GitHub native secret-scanning + `check:public-creds`.
|
||||
- `check:pr-evidence` (blocking, greps PR-body prose) — high false-positive risk; weakens Hard Rule #18 enforcement if dropped, so this is a genuine policy call.
|
||||
- `semgrep` (advisory standalone) — overlaps CodeQL for the OWASP families; wire its baseline to a ratchet or drop.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- Supply-chain (provenance, SBOM, Trivy, Scorecard): [`docs/security/SUPPLY_CHAIN.md`](../security/SUPPLY_CHAIN.md)
|
||||
@@ -0,0 +1,583 @@
|
||||
---
|
||||
title: "Repository Map"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Repository Map
|
||||
|
||||
> **One-line description for every directory and root file.**
|
||||
> Last updated: 2026-06-28 — OmniRoute v3.8.40
|
||||
>
|
||||
> Use this map to navigate the codebase quickly. For deep dives, follow links to dedicated docs.
|
||||
|
||||
## Top-level tree
|
||||
|
||||
```
|
||||
OmniRoute/
|
||||
├── src/ # Next.js 16 application (UI + API routes + libs + domain + server)
|
||||
├── open-sse/ # Streaming engine workspace (handlers, executors, translator, MCP server)
|
||||
├── electron/ # Desktop wrapper (Electron 41 + electron-builder 26.10)
|
||||
├── bin/ # CLI entry point and command handlers
|
||||
├── scripts/ # Build, check, sync, and one-off scripts
|
||||
├── docs/ # Public documentation (you are here)
|
||||
├── tests/ # All test suites (unit, integration, e2e, protocols-e2e)
|
||||
├── public/ # Next.js static assets, PWA manifest, service worker, icons
|
||||
├── config/ # Static config + quality-gate state (i18n, payloadRules, quality/)
|
||||
├── images/ # Marketing / README image assets
|
||||
├── @omniroute/ # Publishable companion packages (opencode-plugin, opencode-provider)
|
||||
├── skills/ # CLI/agent skill packs (cli-* + omni-* + config-codex-cli)
|
||||
├── examples/ # Sample plugins + omniroute-cmd-hello starter
|
||||
├── contrib/ # Community contributions (podman/)
|
||||
├── .source/ # Fumadocs source config (source.config.mjs + server/browser/dynamic)
|
||||
├── .github/ # GitHub Actions workflows + issue templates + PR template
|
||||
├── .husky/ # Git hooks (pre-commit, pre-push)
|
||||
├── .claude/ # Claude Code slash commands (project-scoped)
|
||||
├── .agents/ # Codex / generic agent workflows + skills (mirror of .claude/)
|
||||
├── .vscode/ # VS Code workspace settings
|
||||
├── _ideia/ # Planning notes (informal; not shipped)
|
||||
├── _mono_repo/ # Historic subprojects (cloud, site, vscode-extension)
|
||||
├── _references/ # Read-only reference clones from related OSS projects
|
||||
├── _tasks/ # Per-release task tracking files (informal)
|
||||
├── .build/ .worktrees/ dist/ # local build / git-worktree / build-output scratch (gitignored)
|
||||
├── .issues/ # Local issue cache (gitignored)
|
||||
├── .playwright-mcp/ # Playwright MCP test artifacts
|
||||
├── coverage/ # c8 coverage output (gitignored)
|
||||
├── logs/ # Runtime logs (gitignored)
|
||||
├── node_modules/ # Dependencies (gitignored)
|
||||
├── package/ # npm pack staging area (build artifact)
|
||||
├── .next/ # Next.js build output (gitignored)
|
||||
└── (root files — see below)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root files
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **README.md** | Marketing landing page + quick start + feature matrix (see also `llm.txt`) |
|
||||
| **CHANGELOG.md** | Per-release changelog (auto-generated by `/version-bump-cc` skill) |
|
||||
| **LICENSE** | MIT license text |
|
||||
| **CLAUDE.md** | Project rules for Claude Code agents (hard rules, conventions, scenarios) |
|
||||
| **AGENTS.md** | Same as CLAUDE.md but for non-Claude AI agents (Codex, Cursor, etc.) |
|
||||
| **GEMINI.md** | Concise rules for Gemini-based agents (subset of CLAUDE.md) |
|
||||
| **CONTRIBUTING.md** | Contributor guide: setup, conventional commits, testing, PR flow |
|
||||
| **SECURITY.md** | Vulnerability reporting policy, supported versions, threat model |
|
||||
| **CODE_OF_CONDUCT.md** | Contributor Covenant — community behavior expectations |
|
||||
| **llm.txt** | Plain-text landing optimized for LLM crawlers (SEO for AI assistants) |
|
||||
| **package.json** | npm manifest, scripts, dependencies, engines, c8 coverage gate |
|
||||
| **package-lock.json** | Locked dependency tree |
|
||||
| **tsconfig.json** | Root TypeScript config |
|
||||
| **tsconfig.typecheck-core.json** | Typecheck config for `src/` core |
|
||||
| **tsconfig.typecheck-noimplicit-core.json** | Strict (`noImplicitAny`) typecheck |
|
||||
| **tsconfig.tsbuildinfo** | TS incremental build cache (gitignored) |
|
||||
| **next.config.mjs** | Next.js 16 build configuration (standalone output) |
|
||||
| **next-env.d.ts** | Next.js auto-generated env types |
|
||||
| **eslint.config.mjs** | ESLint flat config (rules per project area) |
|
||||
| **prettier.config.mjs** | Prettier formatting rules |
|
||||
| **postcss.config.mjs** | PostCSS config for Tailwind/CSS pipeline |
|
||||
| **playwright.config.ts** | Playwright E2E test config |
|
||||
| **vitest.config.ts** | Vitest config (default suite) |
|
||||
| **vitest.mcp.config.ts** | Vitest config for MCP server / autoCombo / cache suites |
|
||||
| **sonar-project.properties** | SonarQube/SonarCloud config (code quality) |
|
||||
| **Dockerfile** | Multi-stage Docker build (builder → runner-base → runner-cli) |
|
||||
| **docker-compose.yml** | Dev compose with 4 profiles (base, cli, host, cliproxyapi) + redis sidecar |
|
||||
| **docker-compose.prod.yml** | Production compose (port 20130, redis, named volumes) |
|
||||
| **.dockerignore** | Files excluded from Docker context |
|
||||
| **fly.toml** | Fly.io deployment config (region `sin`, port 20128, /data volume) |
|
||||
| **.env.example** | Template env file (auto-copied to `.env` on first install) |
|
||||
| **.gitignore** | Git ignore patterns |
|
||||
| **.npmignore** | npm publish exclusion list |
|
||||
| **.npmrc** | npm config (registry, lockfile policy) |
|
||||
| **.node-version** | Node version pin (used by nvm-compatible tools) |
|
||||
| **.nvmrc** | Node version pin for nvm |
|
||||
| **eslint.complexity.config.mjs** | ESLint config for the complexity ratchet (`scripts/check/check-complexity.mjs --config`) |
|
||||
| **eslint.sonarjs.config.mjs** | ESLint config for SonarJS rules (cognitive complexity / duplication) |
|
||||
| **source.config.ts** | Fumadocs `defineDocs` source config (feeds `.source/`) |
|
||||
| **knip.json** | Knip config — unused files/exports/deps (feeds the dead-code gate) |
|
||||
| **stryker.conf.json** | Stryker mutation-testing config |
|
||||
| **.size-limit.json** | size-limit bundle budget config |
|
||||
| **promptfooconfig.yaml** | promptfoo eval config |
|
||||
| **.gitleaks.toml** | gitleaks secret-scan ruleset |
|
||||
| **.zizmor.yml** | zizmor GitHub-Actions security-lint config |
|
||||
| **socket.yml** | Socket.dev supply-chain config |
|
||||
| **news.json** | In-app release-notes feed (read by `src/shared/utils/releaseNotes.ts`) |
|
||||
| **flake.nix** / **flake.lock** | Nix dev-shell definition + lock |
|
||||
| **.env** | Local secrets (gitignored — generated from `.env.example`) |
|
||||
|
||||
> **Moved out of the root in v3.8.26 (declutter):**
|
||||
>
|
||||
> - **→ `config/quality/`:** `quality-baseline.json`, `complexity-baseline.json`, `duplication-baseline.json`, `file-size-baseline.json`, `test-discovery-baseline.json`, `dependency-allowlist.json`, `.license-allowlist.json`, and the generated `quality-metrics.json` (gitignored). See [`## config/`](#config--static-configs--quality-gate-state).
|
||||
|
||||
---
|
||||
|
||||
## `src/` — Next.js application
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # App Router (pages + API routes + status pages + landing)
|
||||
├── lib/ # Core libraries / domain modules (~50 subdirs + ~30 top-level files)
|
||||
├── domain/ # Pure domain logic (policy engine, fallback, cost, lockout, comboResolver, assessment)
|
||||
├── server/ # Server-only modules (authz pipeline, cors, auth middleware) — cannot import from client
|
||||
├── shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils)
|
||||
├── i18n/ # next-intl config + per-locale message JSON (30+ locales)
|
||||
├── middleware/ # Next.js middleware (request enrichment, locale detection)
|
||||
├── mitm/ # MITM proxy core: cert gen/install, handlers, targets, inspector, masks, passthrough
|
||||
│ ├── handlers/ # 9 IDE-agent handler classes extending MitmHandlerBase (antigravity, kiro, copilot, codex, cursor, zed, claudeCode, openCode, trae)
|
||||
│ └── inspector/ # Traffic capture layer: buffer (in-memory ring), sseMerger, conversationNormalizer, kindDetector, contextKey, httpProxyServer, systemProxyConfig
|
||||
├── models/ # Model adapter glue (legacy shim)
|
||||
├── scripts/ # In-tree maintenance scripts (e.g., backfillAggregation)
|
||||
├── sse/ # Legacy SSE handlers/services (chat.ts, chatHelpers.ts, services/auth.ts)
|
||||
├── store/ # Legacy in-memory store (being phased out for src/lib/db)
|
||||
├── types/ # Shared TS type files
|
||||
├── instrumentation.ts # Next.js telemetry hook (browser + edge)
|
||||
├── instrumentation-node.ts # Node-only instrumentation
|
||||
├── server-init.ts # Server bootstrap (DB migrations, jobs, cleanup)
|
||||
└── proxy.ts # HTTP-proxy entry shim
|
||||
```
|
||||
|
||||
### `src/app/` — App Router (Next.js 16)
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `app/api/v1/` | Public OpenAI-compat API (~25 sub-routes: chat, completions, embeddings, files, batches, audio, images, videos, music, rerank, moderations, search, ws, agents, accounts, providers, etc.) |
|
||||
| `app/api/v1beta/` | Gemini-style API endpoints |
|
||||
| `app/api/playground/` | Playground Studio routes: `improve-prompt/` (POST — LLM prompt rewriter), `presets/` (GET list / POST create), `presets/[id]/` (GET / PUT / DELETE) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
|
||||
| `app/api/` (non-v1) | Management/admin routes (~60 directories: providers, combos, settings, mcp, a2a, evals, memory, skills, webhooks, compliance, resilience, monitoring, tunnels, cli-tools, etc.) |
|
||||
| `app/api/tools/agent-bridge/` | AgentBridge REST API — 12 routes (server control, agent state/DNS/mappings, bypass, cert, upstream-CA). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/AGENTBRIDGE.md §7`. |
|
||||
| `app/api/tools/traffic-inspector/` | Traffic Inspector REST + WS API — 16+ routes (requests, sessions, hosts, capture-modes, export, ws). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/TRAFFIC_INSPECTOR.md §8`. |
|
||||
| `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) |
|
||||
| `app/.well-known/agent.json/` | A2A Agent Card (discovery) |
|
||||
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~35 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, activity, etc.) |
|
||||
| `app/(dashboard)/dashboard/search-tools/` | Search Tools Studio UI (3 tabs: Search/Scrape/Compare + SearchConceptCard + ProviderCatalog) — see `docs/frameworks/SEARCH_TOOLS_STUDIO.md` |
|
||||
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~30 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, etc.) |
|
||||
| `app/(dashboard)/dashboard/memory/` | Memory Studio (plan 21): `page.tsx` (3-tab shell), `components/` (MemoryConceptCard, MemoryEngineStatus, EmbeddingSourceSelector, EditMemoryModal, RetrievePreview, QdrantConfigCard, RerankConfigCard), `components/tabs/` (MemoriesTab, PlaygroundTab, EngineTab), `hooks/` (useEngineStatus, useMemorySettings) |
|
||||
| `app/(dashboard)/dashboard/tools/agent-bridge/` | AgentBridge dashboard page — server card, 9 agent cards, setup wizard, model mapping, bypass list. i18n PT-BR + EN. See `docs/frameworks/AGENTBRIDGE.md`. |
|
||||
| `app/(dashboard)/dashboard/tools/traffic-inspector/` | Traffic Inspector dashboard page — DevTools split, 7 detail tabs, 4 capture mode toggles, session recorder, context colorization. i18n PT-BR + EN. See `docs/frameworks/TRAFFIC_INSPECTOR.md`. |
|
||||
| `app/(dashboard)/dashboard/activity/` | Activity feed page (Group B): `page.tsx` (server) + `ActivityFeedClient.tsx` + `components/{ActivityFeed,ActivityItem,DayHeader,EventTypeFilter}.tsx` — see `docs/architecture/MONITORING_SECTIONS.md` |
|
||||
| `app/(dashboard)/dashboard/costs/quota-share/` | Quota Sharing page (Group B): `QuotaSharePageClient.tsx` + `components/{PoolCard,DimensionBar,AllocationTable,BurnRateChart,QuotaConceptCard,CreatePoolModal,EditAllocationsModal}.tsx` + `hooks/{usePools,usePoolUsage,useLocalStoragePoolMigration}.ts` |
|
||||
| `app/(dashboard)/dashboard/costs/quota-share/plans/` | Provider plan config page (Group B): `page.tsx` + `ProviderPlanConfigClient.tsx` — quota dimensions per connection override |
|
||||
| `app/docs/` | Embedded documentation viewer (renders `docs/*.md`) |
|
||||
| `app/landing/` | Marketing landing page |
|
||||
| `app/login/`, `forgot-password/`, `forbidden/` | Auth-related pages |
|
||||
| `app/{400,401,403,408,429,500,502,503}/` | HTTP error pages |
|
||||
| `app/maintenance/`, `offline/`, `status/`, `privacy/`, `terms/`, `callback/` | Static/status pages |
|
||||
| `app/layout.tsx`, `page.tsx`, `manifest.ts`, `globals.css` | Root layout, home, PWA manifest, global CSS |
|
||||
| `app/error.tsx`, `global-error.tsx`, `not-found.tsx`, `loading.tsx` | Error boundaries |
|
||||
|
||||
### `src/lib/` — Core libraries (~50 modules)
|
||||
|
||||
| Module | Purpose |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `a2a/` | A2A protocol task manager, skills (5), streaming |
|
||||
| `acp/` | CLI Agent Registry (local CLI discovery — see `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`) |
|
||||
| `api/` | Shared API helpers (`requireManagementAuth`, validation) |
|
||||
| `auth/` | Session, password hashing, token validation |
|
||||
| `batches/` | OpenAI Batches API handlers |
|
||||
| `catalog/` | Provider catalog Zod validation + capability resolution |
|
||||
| `cloudAgent/` | Cloud Agents (Codex Cloud, Devin, Jules) — see `docs/frameworks/CLOUD_AGENT.md` |
|
||||
| `combos/` | Combo resolution + reorder helpers |
|
||||
| `audit/` | Activity feed helpers: `highLevelActions.ts` (allowlist + `isHighLevelAction()`), `activityIcons.ts` (action → icon/verb map), `timeline.ts` (groupByDay/relativeTime) — see `docs/architecture/MONITORING_SECTIONS.md` |
|
||||
| `compliance/` | Audit log + provider audit — see `docs/security/COMPLIANCE.md` |
|
||||
| `compression/` | Compression engine glue (engines live in `open-sse/services/compression/`) |
|
||||
| `config/` | Runtime config helpers |
|
||||
| `db/` | 95+ domain DB modules + 110+ migrations (always go through here for SQLite) |
|
||||
| `quota/` | Quota Sharing Engine: `dimensions.ts` (types/Zod), `types.ts` (QuotaStore interface), `sqliteQuotaStore.ts`, `redisQuotaStore.ts`, `storeFactory.ts`, `fairShare.ts`, `burnRate.ts`, `planResolver.ts`, `planRegistry.ts`, `saturationSignals.ts`, `enforce.ts`, `spendRecorder.ts` — see `docs/routing/QUOTA_SHARE.md` |
|
||||
| `display/` | UI formatting helpers (cost, latency, etc.) |
|
||||
| `embeddings/` | Embeddings service helpers |
|
||||
| `env/` | Env variable parsing + validation |
|
||||
| `evals/` | Eval framework (suites, runner, runtime) — see `docs/frameworks/EVALS.md` |
|
||||
| `guardrails/` | PII masker, prompt injection, vision bridge — see `docs/security/GUARDRAILS.md` |
|
||||
| `jobs/` | Background jobs (cron-like) |
|
||||
| `memory/` | Conversational memory (SQLite FTS5 + sqlite-vec hybrid RRF + Qdrant tier 2) — see `docs/frameworks/MEMORY.md` |
|
||||
| `memory/embedding/` | Multi-source embedding layer: `index.ts` (resolver), `remote.ts`, `staticPotion.ts`, `transformersLocal.ts`, `cache.ts`, `types.ts` (plan 21) |
|
||||
| `memory/vectorStore.ts` | sqlite-vec v0.1.9 wrapper — KNN brute-force + hybrid RRF (FTS5 + vector, k=60). Lazy-init, degrades gracefully when sqlite-vec unavailable. (plan 21) |
|
||||
| `memory/reindex.ts` | `runReindexBatch()` — processes memories with `needs_reindex=1` in background; called by `POST /api/memory/reindex` and lazy-backfill path. (plan 21) |
|
||||
| `monitoring/` | Health checks, metrics emission |
|
||||
| `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) |
|
||||
| `plugins/` | Plugin registry |
|
||||
| `promptCache/` | Anthropic-style prompt cache breakpoints |
|
||||
| `skills/` | Skills framework (built-in + marketplace + SkillsSH) — see `docs/frameworks/SKILLS.md` |
|
||||
| `playground/` | Playground Studio shared helpers: `codeExport.ts` (curl/Python/TS generator), `promptImprover.ts` (meta-prompt builder), `streamMetrics.ts` (pure TTFT/TPS), `types.ts` (pricing table) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
|
||||
| `webhookDispatcher.ts` | HMAC webhook delivery — see `docs/frameworks/WEBHOOKS.md` |
|
||||
| `cloudflaredTunnel.ts`, `ngrokTunnel.ts` | Tunnel managers — see `docs/ops/TUNNELS_GUIDE.md` |
|
||||
| `oneproxySync.ts`, `oneproxyRotator.ts` | 1proxy free proxy marketplace — see `docs/ops/PROXY_GUIDE.md` |
|
||||
| `cloudSync.ts`, `initCloudSync.ts` | Optional cloud sync of state |
|
||||
| `localDb.ts` | Re-export barrel for db modules (no logic — re-exports only) |
|
||||
| `cacheLayer.ts`, `idempotencyLayer.ts` | Request caching + idempotency |
|
||||
| (~30 more top-level files) | Specialized helpers (logEnv, modelsDevSync, piiSanitizer, etc.) |
|
||||
|
||||
### `src/db/` — Database (94 modules + 106 migrations)
|
||||
|
||||
| Subdir | Purpose |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `db/core.ts` | `getDbInstance()` singleton with WAL journaling |
|
||||
| `db/migrations/` | Versioned SQL files (idempotent, transactional). `073_memory_vec.sql` adds `memory_vec_meta` + `needs_reindex` column (plan 21). |
|
||||
| `db/playgroundPresets.ts` | CRUD module for Playground Studio presets (`listPlaygroundPresets`, `getPlaygroundPreset`, `createPlaygroundPreset`, `updatePlaygroundPreset`, `deletePlaygroundPreset`) |
|
||||
| `db/memoryVec.ts` | CRUD for `memory_vec_meta` (active_dim, embedding_signature, last_reset_at, vec_loaded) + `markMemoryNeedsReindex`, `getMemoryReindexQueue`, etc. (plan 21) |
|
||||
| `db/<domain>.ts` | One module per domain: providers, combos, apiKeys, users, sessions, usage, audit*log, webhooks, skills, memory_entries, cloud_agent_tasks, evals*\*, reasoning_cache, etc. |
|
||||
|
||||
### `src/domain/`
|
||||
|
||||
| Module | Purpose |
|
||||
| ---------------------- | ----------------------------------------------------------------------- |
|
||||
| `policy.ts` | Policy engine |
|
||||
| `fallbackPolicy.ts` | Fallback decision tree |
|
||||
| `costRules.ts` | Cost calculation rules |
|
||||
| `lockoutPolicy.ts` | Model/connection lockout policy |
|
||||
| `tagRouter.ts` | Tag-based routing |
|
||||
| `comboResolver.ts` | Combo resolution (used by combo engine) |
|
||||
| `modelAvailability.ts` | Per-model availability check |
|
||||
| `assessment/` | Model assessment (Phase 1 of RFC-AUTO-ASSESSMENT — see `docs/archive/`) |
|
||||
|
||||
### `src/server/`
|
||||
|
||||
| Module | Purpose |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `authz/` | Authorization pipeline: `classify` → `policies` → `enforce` — see `docs/architecture/AUTHZ_GUIDE.md` |
|
||||
| `cors/` | CORS configuration |
|
||||
| `auth/` | Session middleware |
|
||||
|
||||
### `src/shared/`
|
||||
|
||||
| Module | Purpose |
|
||||
| -------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `constants/providers.ts` | **236 providers** with Zod validation (source of truth) |
|
||||
| `constants/cliTools.ts` | External CLI tool registry |
|
||||
| `constants/routingStrategies.ts` | **17 routing strategies** with priorities |
|
||||
| `constants/publicApiRoutes.ts` | Routes that require Bearer (vs management) auth |
|
||||
| `constants/upstreamHeaders.ts` | Header denylist for upstream requests |
|
||||
| `validation/schemas.ts` | ~80 Zod schemas (single source of truth for API contracts) |
|
||||
| `validation/helpers.ts` | Zod validation helpers (`validateBody`, etc.) |
|
||||
| `types/` | Shared TS types |
|
||||
| `contracts/` | Public API contracts (consumed by `files:` in `package.json`) |
|
||||
| `utils/circuitBreaker.ts` | Provider circuit breaker (see `docs/architecture/RESILIENCE_GUIDE.md`) |
|
||||
| `utils/apiAuth.ts` | API key validation, scope checking |
|
||||
| `utils/fetchTimeout.ts` | Timeout/abort wrappers for upstream fetch |
|
||||
|
||||
---
|
||||
|
||||
## `open-sse/` — Streaming Engine Workspace
|
||||
|
||||
Separate npm workspace (`@omniroute/open-sse`). Handles request processing + provider execution.
|
||||
|
||||
```
|
||||
open-sse/
|
||||
├── handlers/ # 16 files (12 handlers + 4 helpers): chatCore, responsesHandler, embeddings, audio, image, video, music, rerank, moderations, search, etc.
|
||||
├── executors/ # 67 provider-specific executors (extend BaseExecutor)
|
||||
├── translator/ # Format converters (9 request, 9 response, 9 helpers)
|
||||
├── transformer/ # Responses API ↔ Chat Completions (TransformStream)
|
||||
├── services/ # ~80+ service modules (combo, accountFallback, autoCombo, reasoningCache, claude code/chatgpt stealth, modelDeprecation, taskAwareRouter, workflowFSM, etc.)
|
||||
├── mcp-server/ # MCP server (94 tools, 3 transports, 30 scopes)
|
||||
├── config/ # Provider/model registries, header config, model aliases
|
||||
├── utils/ # TLS client, proxy fetch/dispatcher, network helpers
|
||||
├── index.ts # Workspace entry
|
||||
├── package.json # Workspace manifest
|
||||
├── tsconfig.json # Workspace TS config
|
||||
└── types.d.ts # Workspace type declarations
|
||||
```
|
||||
|
||||
### `open-sse/mcp-server/`
|
||||
|
||||
| Path | Purpose |
|
||||
| --------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `server.ts` | MCP server lifecycle (stdio + HTTP transports) |
|
||||
| `httpTransport.ts` | HTTP Streamable + SSE transports (`/api/mcp/sse`, `/api/mcp/stream`) |
|
||||
| `audit.ts` | Audit logging to `mcp_tool_audit` table |
|
||||
| `scopeEnforcement.ts` | Per-tool scope validation |
|
||||
| `runtimeHeartbeat.ts` | Health heartbeat to `DATA_DIR/runtime/mcp-heartbeat.json` |
|
||||
| `descriptionCompressor.ts` | Compress tool description metadata to save context |
|
||||
| `schemas/tools.ts` | 34 base tool definitions + scopes |
|
||||
| `tools/advancedTools.ts` | Advanced tool implementations |
|
||||
| `tools/memoryTools.ts` | 3 memory tools (search/add/clear) |
|
||||
| `tools/skillTools.ts` | 4 skill tools (list/enable/execute/executions) |
|
||||
| `tools/compressionTools.ts` | 5 compression tools |
|
||||
| `README.md` | Internal MCP server README (cross-linked from `docs/frameworks/MCP-SERVER.md`) |
|
||||
|
||||
---
|
||||
|
||||
## `electron/` — Desktop Wrapper
|
||||
|
||||
| File | Purpose |
|
||||
| ---------------- | --------------------------------------------------------------------------------- |
|
||||
| `main.js` | Electron main process (BrowserWindow, embedded Next.js server, tray, auto-update) |
|
||||
| `preload.js` | IPC bridge (contextBridge → `window.omniroute`) |
|
||||
| `package.json` | electron-builder config + Electron 41 + electron-builder 26.10 deps |
|
||||
| `assets/` | App icons (Windows .ico, macOS .icns, Linux .png) |
|
||||
| `dist-electron/` | Build output (gitignored) |
|
||||
| `types.d.ts` | Type declarations for renderer bridge |
|
||||
| `README.md` | Internal Electron README (see also `docs/guides/ELECTRON_GUIDE.md`) |
|
||||
|
||||
---
|
||||
|
||||
## `bin/` — CLI
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `omniroute.mjs` | Main CLI entry — `omniroute serve`, `omniroute setup`, `omniroute doctor`, `omniroute providers`, `omniroute combos`, etc. |
|
||||
| `reset-password.mjs` | Standalone password reset CLI |
|
||||
| `cli/commands/setup.mjs` | Interactive + non-interactive setup wizard |
|
||||
| `cli/commands/doctor.mjs` | System health diagnostics (8+ checks) |
|
||||
| `cli/commands/providers.mjs` | Provider list/test/validate |
|
||||
| `cli/{args,data-dir,encryption,io,provider-catalog,provider-store,provider-test,settings-store,sqlite}.mjs` | CLI helper modules |
|
||||
| `cli/tray/tray.ts` | System tray integration (cross-platform: NotifyIcon on Windows, systray2 on macOS/Linux) |
|
||||
| `cli/tray/tray.ps1` | PowerShell NotifyIcon backend (Windows, zero new binaries) |
|
||||
| `cli/tray/autostart.ts` | Cross-platform autostart (LaunchAgent / .desktop / registry) |
|
||||
| `cli/runtime/sqliteRuntime.mjs` | 5-step SQLite driver resolution chain (bundled → runtime → lazy-install → node:sqlite → sql.js) |
|
||||
| `cli/runtime/magicBytes.mjs` | Binary magic-byte validation (ELF / Mach-O / Mach-O fat / PE) |
|
||||
| `cli/runtime/index.mjs` | `warmUpRuntimes()` — pre-resolves drivers at postinstall / first startup |
|
||||
| `nodeRuntimeSupport.mjs` | Validate supported Node.js version on install |
|
||||
|
||||
---
|
||||
|
||||
## `skills/` — Public Agent Skills
|
||||
|
||||
| File | Purpose |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `skills/omniroute*/SKILL.md` | 10 skill manifests for external AI agents (Claude Desktop, ChatGPT, Cursor, Cline) |
|
||||
|
||||
---
|
||||
|
||||
## `scripts/` — Build & Check Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `run-next.mjs` | Dev/start runner with env hydration |
|
||||
| `build-next-isolated.mjs` | Standalone build (Next.js 16 standalone) |
|
||||
| `prepublish.ts` | Package preparation before `npm pack` |
|
||||
| `postinstall.mjs` | Auto-create `.env` from `.env.example` on first install |
|
||||
| `sync-env.mjs` | Re-sync `.env` keys with `.env.example` |
|
||||
| `check-cycles.mjs` | Detect circular dependencies |
|
||||
| `check-route-validation.mjs` | Validate all API routes have Zod validation |
|
||||
| `check-t11-any-budget.mjs` | Enforce explicit `any` budget per file |
|
||||
| `check-docs-sync.mjs` | Validate docs version sync (existing pre-commit) |
|
||||
| **`check-env-doc-sync.mjs`** | NEW: cross-check env vars in code vs `.env.example` vs `ENVIRONMENT.md` |
|
||||
| **`check-docs-counts-sync.mjs`** | NEW: validate counts (executors, strategies, OAuth, A2A skills) match docs |
|
||||
| **`check-deprecated-versions.mjs`** | NEW: flag stale versions/dates in docs |
|
||||
| `check-supported-node-runtime.ts` | Validate current Node version is supported |
|
||||
| `check-pr-test-policy.mjs` | Enforce "tests required" rule on production code changes |
|
||||
| **`gen-provider-reference.ts`** | NEW: auto-generate `docs/reference/PROVIDER_REFERENCE.md` from catalog |
|
||||
| `i18n/generate-multilang.mjs` | Translate UI strings + docs via Google Translate |
|
||||
| `i18n_autotranslate.py` | LLM-based doc translation pipeline |
|
||||
| `validate_translation.py` | Per-locale translation validation |
|
||||
| `check_translations.py` | Code-side i18n key check |
|
||||
| `run-playwright-tests.mjs` | Playwright E2E runner |
|
||||
| `run-protocol-clients-tests.mjs` | MCP/A2A E2E runner |
|
||||
| `run-ecosystem-tests.mjs` | Ecosystem (provider integration) tests |
|
||||
| `test-report-summary.mjs` | Generate coverage summary markdown |
|
||||
| `smoke-electron-packaged.mjs` | Smoke-test packaged Electron build |
|
||||
| `native-binary-compat.mjs` | Validate native deps (`better-sqlite3`) match Electron's Node |
|
||||
| `validate-pack-artifact.ts` | Validate npm pack output |
|
||||
| `responses-ws-proxy.mjs` | WebSocket bridge for Codex Responses API |
|
||||
| `v1-ws-bridge.mjs` | WebSocket bridge for `/api/v1/ws` endpoint |
|
||||
| `standalone-server-ws.mjs` | Standalone WS server runner |
|
||||
| `system-info.mjs` | Print system/runtime info for support |
|
||||
| `healthcheck.mjs` | One-shot health check (used by Docker HEALTHCHECK) |
|
||||
| `uninstall.mjs` | Clean uninstall script |
|
||||
|
||||
---
|
||||
|
||||
## `docs/` — Public Documentation (44 files + 4 subdirs)
|
||||
|
||||
### Top-level guides
|
||||
|
||||
| Doc | Purpose |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `ARCHITECTURE.md` | High-level architecture, subsystem map, dashboard surface |
|
||||
| `CODEBASE_DOCUMENTATION.md` | Engineering reference: directories, modules, conventions |
|
||||
| `FEATURES.md` | Feature matrix with v3.8 highlights |
|
||||
| `USER_GUIDE.md` | End-user manual (setup, models, combos, CLIs, audio, etc.) |
|
||||
| `API_REFERENCE.md` | API endpoint reference with auth model |
|
||||
| `openapi.yaml` | OpenAPI 3.0 spec (121 paths) |
|
||||
| `SETUP_GUIDE.md` | Install methods (npm, npx, Docker, Electron, Termux, source) |
|
||||
| `ENVIRONMENT.md` | All env vars (~219 used in code, ~810 lines `.env.example`) |
|
||||
| `TROUBLESHOOTING.md` | Common errors + v3.8.0 known issues |
|
||||
| `RELEASE_CHECKLIST.md` | Full release flow (skills, husky, conventional commits, deploy) |
|
||||
| `COVERAGE_PLAN.md` | Coverage goals and current state |
|
||||
| `FREE_TIERS.md` | Curated free-tier providers (48+ free + 11 OAuth) |
|
||||
| `CLI-TOOLS.md` | External CLI integrations + Internal OmniRoute CLI |
|
||||
| `I18N.md` | i18n architecture, adding a language, 30 locales |
|
||||
| `UNINSTALL.md` | Clean uninstall steps |
|
||||
| `PROVIDER_REFERENCE.md` | **Auto-generated** catalog of 236 providers (regen: `npm run gen:provider-reference`) |
|
||||
|
||||
### Subsystem deep-dives
|
||||
|
||||
| Doc | Purpose |
|
||||
| -------------------------- | ------------------------------------------------------------------- |
|
||||
| `MCP-SERVER.md` | MCP server: 94 tools, 3 transports, 30 scopes, REST endpoints |
|
||||
| `A2A-SERVER.md` | A2A v0.3: JSON-RPC, 5 skills, REST helpers, agent card |
|
||||
| `AGENT_PROTOCOLS_GUIDE.md` | Unified guide: A2A vs ACP vs Cloud Agents |
|
||||
| `CLOUD_AGENT.md` | Codex Cloud / Devin / Jules orchestration |
|
||||
| `SKILLS.md` | Skills framework (built-in + marketplace + SkillsSH + sandbox) |
|
||||
| `MEMORY.md` | Memory system (SQLite FTS5 + Qdrant) |
|
||||
| `EVALS.md` | Eval framework (suites, runs, rubrics) |
|
||||
| `GUARDRAILS.md` | PII masker, prompt injection, vision bridge |
|
||||
| `COMPLIANCE.md` | Audit log, retention, noLog opt-out |
|
||||
| `WEBHOOKS.md` | HMAC-signed webhook delivery |
|
||||
| `REASONING_REPLAY.md` | Hybrid memory/SQLite cache for `reasoning_content` |
|
||||
| `AUTHZ_GUIDE.md` | Authorization pipeline (`classify` → `policies` → `enforce`) |
|
||||
| `RESILIENCE_GUIDE.md` | Circuit breaker + cooldown + model lockout |
|
||||
| `STEALTH_GUIDE.md` | TLS fingerprinting (JA3/JA4), Claude Code CCH, MITM cert |
|
||||
| `AUTO-COMBO.md` | Auto Combo engine (9-factor scoring, 4 mode packs, virtual factory) |
|
||||
|
||||
### Compression
|
||||
|
||||
| Doc | Purpose |
|
||||
| ------------------------------- | ---------------------------------------- |
|
||||
| `COMPRESSION_GUIDE.md` | Overview of compression modes + roadmap |
|
||||
| `COMPRESSION_ENGINES.md` | Caveman + RTK engines, registry contract |
|
||||
| `COMPRESSION_RULES_FORMAT.md` | Caveman rule pack JSON schema |
|
||||
| `COMPRESSION_LANGUAGE_PACKS.md` | Per-language rule pack inventory |
|
||||
| `RTK_COMPRESSION.md` | RTK declarative pipeline (49 filters) |
|
||||
|
||||
### Deployment
|
||||
|
||||
| Doc | Purpose |
|
||||
| ---------------------------- | ----------------------------------------------------------------- |
|
||||
| `DOCKER_GUIDE.md` | Docker build, profiles (base/cli/host/cliproxyapi), Redis sidecar |
|
||||
| `VM_DEPLOYMENT_GUIDE.md` | Generic VM/VPS deployment (Ubuntu/Debian + nginx + systemd) |
|
||||
| `FLY_IO_DEPLOYMENT_GUIDE.md` | Fly.io deployment (currently Chinese-only) |
|
||||
| `TERMUX_GUIDE.md` | Android headless via Termux |
|
||||
| `PWA_GUIDE.md` | Progressive Web App install + service worker |
|
||||
| `ELECTRON_GUIDE.md` | Desktop app build + sign + distribute |
|
||||
| `TUNNELS_GUIDE.md` | Cloudflared + ngrok + Tailscale Funnel |
|
||||
| `PROXY_GUIDE.md` | 4-level outbound proxy + 1proxy marketplace |
|
||||
|
||||
### Subdirectories
|
||||
|
||||
| Subdir | Purpose |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `docs/archive/` | Archived/historical docs (e.g., `RFC-AUTO-ASSESSMENT-DRAFT.md` — superseded by EVALS) |
|
||||
| `docs/i18n/` | Localized doc translations (~42 locales) |
|
||||
| `docs/screenshots/` | Image assets for guides |
|
||||
| `_tasks/superpowers/` | Plans/specs from superpowers (`writing-plans`/`brainstorming`) + research — isolated, separately-versioned repo, gitignored by the main tree. See CLAUDE.md → "Planning & Research Artifacts". |
|
||||
|
||||
---
|
||||
|
||||
## `tests/` — Test Suites
|
||||
|
||||
| Subdir | Type | Runner |
|
||||
| ---------------------- | --------------------------------------- | --------------------------------------- |
|
||||
| `tests/unit/` | Unit tests (~500 files, fastest) | Node native test runner |
|
||||
| `tests/integration/` | Multi-module + DB integration tests | Node native test runner (concurrency 1) |
|
||||
| `tests/e2e/` | UI + workflow E2E | Playwright |
|
||||
| `tests/protocols-e2e/` | MCP + A2A real-client E2E | Custom protocol clients |
|
||||
| `tests/ecosystem/` | Provider integration (network-touching) | Node native test runner |
|
||||
|
||||
---
|
||||
|
||||
## `public/` — Static Assets
|
||||
|
||||
| Path | Purpose |
|
||||
| ------------------- | ---------------------------------------------------------------- |
|
||||
| `public/` (root) | Favicons, robots.txt, manifest, service worker, marketing images |
|
||||
| `public/providers/` | Provider logo PNG/SVG (used in dashboard) |
|
||||
|
||||
---
|
||||
|
||||
## `config/` — Static Configs + Quality-Gate State
|
||||
|
||||
Shipped configuration templates plus the committed quality-gate baselines
|
||||
(moved here from the repo root in v3.8.26 to keep the root lean).
|
||||
|
||||
| Path | Purpose |
|
||||
| --------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| `config/i18n.json` | Locale list + metadata (canonical source for the 42-locale count) |
|
||||
| `config/i18n-schema.json` | JSON schema validating `i18n.json` |
|
||||
| `config/payloadRules.json` | Upstream payload sanitization rules |
|
||||
| `config/quality/quality-baseline.json` | Multi-metric ratchet baseline (`scripts/quality/check-quality-ratchet.mjs`) |
|
||||
| `config/quality/complexity-baseline.json` | Frozen ESLint-complexity baseline (`check-complexity.mjs`) |
|
||||
| `config/quality/duplication-baseline.json` | Frozen jscpd duplication baseline (`check-duplication.mjs`) |
|
||||
| `config/quality/file-size-baseline.json` | Frozen per-file size baseline (`check-file-size.mjs`) |
|
||||
| `config/quality/test-discovery-baseline.json` | Frozen orphan-test baseline (`check-test-discovery.mjs`) |
|
||||
| `config/quality/dependency-allowlist.json` | Approved dependencies allowlist (`check-deps.mjs`) |
|
||||
| `config/quality/.license-allowlist.json` | SPDX license allowlist (`check-licenses.mjs`) |
|
||||
| `config/quality/quality-metrics.json` | Ephemeral collected metrics (generated by `collect-metrics.mjs`; **gitignored**) |
|
||||
|
||||
---
|
||||
|
||||
## `.github/` — GitHub Integration
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------------------- | -------------------------------------------------------------- |
|
||||
| `.github/workflows/` | GitHub Actions CI/CD workflows (lint, test, coverage, release) |
|
||||
| `.github/ISSUE_TEMPLATE/` | Bug/feature issue templates |
|
||||
| `.github/PULL_REQUEST_TEMPLATE.md` | PR template |
|
||||
| `.github/dependabot.yml` | Dependency update config |
|
||||
|
||||
---
|
||||
|
||||
## `.husky/` — Git Hooks
|
||||
|
||||
| File | Purpose |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `pre-commit` | Runs `lint-staged + check-docs-sync + check:any-budget:t11` |
|
||||
| `pre-push` | Currently disabled (commented). Run `npm run test:unit` manually. |
|
||||
| `_/` | Husky internals |
|
||||
|
||||
---
|
||||
|
||||
## `.claude/` — Claude Code Slash Commands
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------------------------------------- | -------------------------------------------------- |
|
||||
| `commands/version-bump-cc.md` | `/version-bump-cc` — bump version + auto-changelog |
|
||||
| `commands/generate-release-cc.md` | `/generate-release-cc` — full release workflow |
|
||||
| `commands/deploy-vps-{local,akamai,both}-cc.md` | Deploy to VPS |
|
||||
| `commands/capture-release-evidences-cc.md` | Browser-record new features as WebP |
|
||||
| `commands/review-{prs,discussions}-cc.md` | Triage GitHub PRs/discussions |
|
||||
| `commands/{review-issues,implement-features}-cc.md` | Issue workflows |
|
||||
| `settings.local.json` | Per-project Claude Code settings |
|
||||
|
||||
---
|
||||
|
||||
## `.agents/` — Generic Agent Workflows (Codex / Cursor / etc.)
|
||||
|
||||
| Path | Purpose |
|
||||
| ------------------------ | ------------------------------------------------------- |
|
||||
| `workflows/*-ag.md` | 11 workflow definitions (mirror of `.claude/commands/`) |
|
||||
| `skills/<name>/SKILL.md` | 9 skill definitions with Codex Execution Notes |
|
||||
|
||||
> **Note:** Workflows and commands are currently identical byte-by-byte. If `.agents/` is meant to target a different agent runtime (Codex), the variants need to diverge meaningfully.
|
||||
|
||||
---
|
||||
|
||||
## `_ideia/`, `_mono_repo/`, `_references/`, `_tasks/` — Out-of-tree
|
||||
|
||||
These underscore-prefixed directories hold non-shipping content:
|
||||
|
||||
- **`_ideia/`** — design notes (defer / notfit / viable categories)
|
||||
- **`_mono_repo/`** — historic subprojects (omnirouteCloud, omnirouteSite, vscode-extension)
|
||||
- **`_references/`** — read-only clones of related OSS projects (LiteLLM, 9router, ClawRouter, CLIProxyAPI, modelrelay, new-api, etc.) for cross-reference during development
|
||||
- **`_tasks/`** — per-release task tracking files (informal)
|
||||
|
||||
Not included in `npm pack` output. See `.npmignore`.
|
||||
|
||||
---
|
||||
|
||||
## Generated / Gitignored
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------- | ----------------------------- |
|
||||
| `node_modules/` | npm dependencies |
|
||||
| `.next/` | Next.js build output |
|
||||
| `coverage/` | c8 coverage reports |
|
||||
| `logs/` | Runtime logs |
|
||||
| `package/` | npm pack staging |
|
||||
| `.playwright-mcp/` | Playwright MCP test artifacts |
|
||||
| `.issues/` | Local issue cache |
|
||||
| `tsconfig.tsbuildinfo` | TS incremental cache |
|
||||
|
||||
---
|
||||
|
||||
## Navigation tips
|
||||
|
||||
- **New contributor?** Read `CONTRIBUTING.md` → `CLAUDE.md` → `docs/architecture/ARCHITECTURE.md` → `docs/architecture/CODEBASE_DOCUMENTATION.md`.
|
||||
- **Adding a provider?** Follow `docs/architecture/ARCHITECTURE.md § Adding a New Provider` + cross-check `docs/reference/PROVIDER_REFERENCE.md`.
|
||||
- **Adding a route?** `docs/architecture/ARCHITECTURE.md § Adding a New API Route` + `src/shared/validation/schemas.ts`.
|
||||
- **Adding an MCP tool?** `docs/frameworks/MCP-SERVER.md § Adding a Tool`.
|
||||
- **Adding an A2A skill?** `docs/frameworks/A2A-SERVER.md § Adding a New Skill`.
|
||||
- **Running locally?** `docs/guides/SETUP_GUIDE.md`.
|
||||
- **Deploying?** `docs/guides/DOCKER_GUIDE.md` / `docs/ops/VM_DEPLOYMENT_GUIDE.md` / `docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md`.
|
||||
- **Releasing?** `docs/ops/RELEASE_CHECKLIST.md` (and `/generate-release-cc` Claude Code skill).
|
||||
@@ -0,0 +1,248 @@
|
||||
---
|
||||
title: "Resilience Guide"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Resilience Guide
|
||||
|
||||
OmniRoute has three distinct but related resilience mechanisms. Each has a different scope and purpose. Keep them separate when debugging routing behavior.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd)
|
||||
|
||||
## 1. Provider Circuit Breaker
|
||||
|
||||
**Scope:** entire provider (e.g., `glm`, `openai`, `anthropic`).
|
||||
|
||||
**Purpose:** stop sending traffic to a provider that is repeatedly failing at the upstream/service level.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Core class: `src/shared/utils/circuitBreaker.ts`
|
||||
- Wiring: `src/sse/handlers/chatHelpers.ts`, `src/sse/handlers/chat.ts`
|
||||
- Status API: `GET /api/monitoring/health`
|
||||
- Reset API: `POST /api/resilience/reset`
|
||||
- Wrappers: `open-sse/services/accountFallback.ts`
|
||||
- DB table: `domain_circuit_breakers`
|
||||
|
||||
**States:**
|
||||
|
||||
- `CLOSED` — normal traffic allowed
|
||||
- `DEGRADED` — traffic still allowed, but elevated provider failures are being tracked
|
||||
- `OPEN` — provider temporarily blocked; combo routing skips it
|
||||
- `HALF_OPEN` — reset timeout elapsed; probe request allowed
|
||||
|
||||
**Configurable defaults (`open-sse/config/constants.ts`, exposed in Dashboard → Settings → Resilience):**
|
||||
|
||||
| Class | Degraded at | Opens at | Reset timeout |
|
||||
| ------- | ----------- | ----------- | ------------- |
|
||||
| OAuth | 5 failures | 8 failures | 60s |
|
||||
| API-key | 7 failures | 12 failures | 30s |
|
||||
| Local | derived | 2 failures | 15s |
|
||||
|
||||
`degradationThreshold` controls when a provider enters `DEGRADED`; `failureThreshold` controls when it opens and is skipped. Local provider profiles are not exposed on the Resilience settings page yet.
|
||||
|
||||
**Trip codes:** only provider-level statuses `[408, 500, 502, 503, 504]`. Do NOT trip for account-level errors (most 401/403/429 — those belong to cooldown or lockout).
|
||||
|
||||
**Lazy recovery:** when `OPEN` expires, `getStatus()`, `canExecute()`, `getRetryAfterMs()` refresh state to `HALF_OPEN`. No background timer needed.
|
||||
|
||||
---
|
||||
|
||||
## 2. Connection Cooldown
|
||||
|
||||
**Scope:** single provider connection/account/key.
|
||||
|
||||
**Purpose:** skip one bad key while other connections for the same provider keep serving.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Mark unavailable: `src/sse/services/auth.ts::markAccountUnavailable()`
|
||||
- Selection: `getProviderCredentials*` in same file
|
||||
- Cooldown calc: `open-sse/services/accountFallback.ts::checkFallbackError()`
|
||||
- Settings: `src/lib/resilience/settings.ts`
|
||||
|
||||
**Fields per connection:**
|
||||
|
||||
- `rateLimitedUntil` — timestamp until cooldown expires
|
||||
- `testStatus: "unavailable"`
|
||||
- `lastError`, `lastErrorType`, `errorCode`
|
||||
- `backoffLevel` — exponential backoff counter
|
||||
|
||||
**Default cooldowns:**
|
||||
|
||||
- OAuth base: 5s
|
||||
- API-key base: 3s
|
||||
- API-key 429: prefers upstream `Retry-After`/reset headers/parseable reset text
|
||||
- Backoff: `baseCooldownMs * 2 ** failureIndex`
|
||||
|
||||
**Anti-thundering-herd guard:** prevents concurrent failures from over-extending cooldown or double-incrementing `backoffLevel`.
|
||||
|
||||
**Terminal states (NOT cooldowns):**
|
||||
|
||||
- `banned` — set by banned-keyword / account-ban detection (see [BAN_DETECTION](../security/BAN_DETECTION.md))
|
||||
- `expired`
|
||||
- `credits_exhausted`
|
||||
|
||||
These persist until credentials change or an operator resets them. Do not overwrite terminal states with transient cooldown state.
|
||||
|
||||
**Lazy recovery:** when `rateLimitedUntil` is past, connection becomes eligible again. On successful use, `clearAccountError()` clears all error fields.
|
||||
|
||||
---
|
||||
|
||||
## 3. Model Lockout
|
||||
|
||||
**Scope:** provider + connection + model triple.
|
||||
|
||||
**Purpose:** avoid disabling a whole connection when only one model is unavailable or quota-limited.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- Per-model quota providers returning 429
|
||||
- Local providers returning 404 for one missing model
|
||||
- Provider-specific mode/model permission failures (e.g., Grok modes)
|
||||
|
||||
**Implementation:** `open-sse/services/accountFallback.ts` — `lockModel()`, `clearModelLock()`, `getAllModelLockouts()`.
|
||||
|
||||
### Model Cooldowns Dashboard (v3.8.0)
|
||||
|
||||
UI: Settings → Model Cooldowns (`src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx`)
|
||||
|
||||
Lists active lockouts with: provider, connection, model, reason, expiresAt. Operators can manually re-enable a model from the card.
|
||||
|
||||
**REST API:**
|
||||
|
||||
- `GET /api/resilience/model-cooldowns` — list active lockouts
|
||||
- `DELETE /api/resilience/model-cooldowns` — manual re-enable. Body: `{provider, connection, model}`. Auth: management.
|
||||
|
||||
### Lockout settings UI + success-decay recovery (v3.8.23)
|
||||
|
||||
Model lockout went from always-on hardcoded behavior to a fully configurable,
|
||||
opt-in feature with its own settings card and a self-healing recovery path.
|
||||
|
||||
**Settings card:** Settings → Model Lockout
|
||||
(`src/app/(dashboard)/dashboard/settings/components/ModelLockoutCard.tsx`).
|
||||
This is **distinct** from the read-only `ModelCooldownsCard` above (which only
|
||||
_lists_ active lockouts) — the new card _configures the parameters_. Defaults
|
||||
live in `DEFAULT_MODEL_LOCKOUT_SETTINGS`
|
||||
(`src/lib/resilience/modelLockoutSettings.ts`):
|
||||
|
||||
| Setting | Default | Meaning |
|
||||
| ----------------------- | -------------------------------- | -------------------------------------------------------------- |
|
||||
| `enabled` | `false` | Master toggle — model lockout is **off by default**. |
|
||||
| `errorCodes` | `[403, 404, 429, 502, 503, 504]` | Upstream statuses that count as a model-scoped failure. |
|
||||
| `baseCooldownMs` | `120_000` (120 s) | Initial lockout duration for the first failure. |
|
||||
| `maxCooldownMs` | `1_800_000` (30 min) | Cap on the escalated cooldown. |
|
||||
| `maxBackoffSteps` | `10` | Max exponential-backoff escalation steps. |
|
||||
| `useExponentialBackoff` | `true` | Whether repeated failures escalate the cooldown exponentially. |
|
||||
|
||||
Settings persist through the normal settings store and validate via the
|
||||
resilience settings schema; the card clamps `baseCooldownMs`/`maxCooldownMs`
|
||||
(with `maxCooldownMs ≥ baseCooldownMs`) and `maxBackoffSteps`.
|
||||
|
||||
**Success-decay recovery:** recovery is **not** purely timer expiry. A healthy
|
||||
response walks the model's failure count back down so a model that recovered
|
||||
mid-window stops escalating (and clears) before its timer would. On a successful
|
||||
combo target, `open-sse/services/combo.ts` calls `decayModelFailureCount()`
|
||||
(`open-sse/services/accountFallback.ts`), which **halves** the stored
|
||||
`failureCount` (`Math.floor(failureCount / 2)`); when it reaches `0` the lockout
|
||||
entry is deleted entirely. The counterpart `recordModelLockoutFailure()`
|
||||
increments the count (and escalates the cooldown) on failures within the
|
||||
escalation window. This success-decay is in addition to plain timer expiry —
|
||||
either path can re-enable a model.
|
||||
|
||||
**State:** lockouts are held **in-memory** (per-process `Map`s of
|
||||
`ModelLockoutEntry` keyed by `provider:connectionId:model`), not persisted to
|
||||
the DB — they are lost on restart. The _settings_ are persisted; the active
|
||||
lockout _state_ is ephemeral.
|
||||
|
||||
---
|
||||
|
||||
## 4. Quota-Share Concurrency Control (v3.8.36)
|
||||
|
||||
Subscription accounts (GLM, MiniMax, etc.) often accept only ~1–3 concurrent
|
||||
requests; exceeding that triggers 429s and cooldowns. This is acute under
|
||||
**quota-share** (`qtSd/…`) combos, where several API keys share one upstream
|
||||
account. Three layers keep a shared account from being flooded.
|
||||
|
||||
### Per-connection concurrency cap (`max_concurrent`)
|
||||
|
||||
Each provider connection can declare a `max_concurrent` ceiling
|
||||
(`provider_connections.max_concurrent`, set in the connection modal / API / DB).
|
||||
Leave it empty for no limit. This is the single knob that drives the serialization
|
||||
layer below — set it to the account's real concurrency (e.g. GLM ~1, MiniMax ~2).
|
||||
|
||||
### Quota-share request serialization
|
||||
|
||||
When a quota-share dispatch targets a connection that declares a positive
|
||||
`max_concurrent`, concurrent requests to that **account** are serialized through a
|
||||
per-connection semaphore (key `qsconn:<connectionId>`): excess requests **wait in
|
||||
the queue** instead of flooding the account. It is **fail-open** — a saturated
|
||||
queue or timeout proceeds without a slot rather than ever rejecting a dispatchable
|
||||
request. Toggle in **Settings → Resilience → Quota-share per-connection
|
||||
concurrency** (`resilienceSettings.quotaShareConcurrencyLimit.enabled`, default
|
||||
on). Without a `max_concurrent` cap the behavior is unchanged.
|
||||
|
||||
> The quota-share routing gate (`selectQuotaShareTarget`, DRR + P2C) is itself
|
||||
> fail-open and only _deprioritizes_ an at-cap connection — with a
|
||||
> single-connection pool it cannot hard-limit, so this semaphore is what actually
|
||||
> contains the flood.
|
||||
|
||||
### Combo cooldown-aware retry
|
||||
|
||||
For quota-share combos only, a request that would crystallize a 429 for a SHORT
|
||||
transient cooldown waits it out and re-dispatches instead of returning the 429.
|
||||
Bounded by `comboCooldownWait` (`enabled`, `maxWaitMs` 5s, `maxAttempts` 2,
|
||||
`budgetMs` 8s) in **Settings → Resilience**. It never waits on `quota_exhausted`
|
||||
(locked until midnight) or auth/not-found reasons.
|
||||
|
||||
---
|
||||
|
||||
## Other Resilience Features
|
||||
|
||||
- **18 routing strategies** (priority, weighted, round-robin, context-relay, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, fusion, pipeline) — see [AUTO-COMBO.md](../routing/AUTO-COMBO.md).
|
||||
- **Reset-aware routing** (v3.8.0) — prioritizes connections by quota reset time.
|
||||
- **Background mode degradation** — Responses API `background: true` degraded to sync with warning.
|
||||
- **Dynamic tool limit detection** — backs off providers when tool count limits hit.
|
||||
- **Emergency fallback** — controlled by `OMNIROUTE_EMERGENCY_FALLBACK`; operators can override it from the Feature Flags page without a restart.
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
- All keys for a provider skipped → check both circuit breaker state AND each connection's `rateLimitedUntil`/`testStatus`.
|
||||
- Provider permanently excluded after reset window → code reading raw `state` instead of `getStatus()`/`canExecute()`.
|
||||
- One key fails, others should work → prefer connection cooldown over circuit breaker.
|
||||
- Only one model fails → prefer model lockout over connection cooldown.
|
||||
- State should self-recover but doesn't → check for future timestamp + read path that refreshes expired state. Permanent statuses require manual changes.
|
||||
|
||||
---
|
||||
|
||||
## TLS Fingerprinting & Stealth
|
||||
|
||||
Provider-specific stealth (JA3/JA4, CCH, obfuscation) is separately documented — see [STEALTH_GUIDE.md](../security/STEALTH_GUIDE.md).
|
||||
|
||||
---
|
||||
|
||||
## Resilience testing (Phase 8 · Block C)
|
||||
|
||||
Beyond unit tests for resilience logic, three tests exercise the runtime under
|
||||
real stress/failure conditions (all integration/nightly — none block PRs):
|
||||
|
||||
| Test | What | Run |
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| Chaos | Fake-upstream node injects real latency/reset/timeout/503; validates that the circuit breaker opens/recovers and `checkFallbackError` classifies 503 as recoverable fallback. | `RUN_CHAOS_INT=1 npm run test:chaos` |
|
||||
| Heap-growth | ~500 streams per `createSSEStream` under `--expose-gc`; fails if the heap grows beyond the ceiling (OOM guard #3069). | `npm run test:heap` |
|
||||
| k6 soak | Sustained load against `/api/monitoring/health`; p95/error thresholds. | `k6 run tests/load/k6-soak.js` (nightly) |
|
||||
|
||||
Orchestrated by `.github/workflows/nightly-resilience.yml` (cron + dispatch). In the
|
||||
default `test:integration`, chaos and heap self-skip (without `RUN_CHAOS_INT`/`--expose-gc`).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Architecture Guide](./ARCHITECTURE.md) — System architecture and internals
|
||||
- [User Guide](../guides/USER_GUIDE.md) — Providers, combos, CLI integration
|
||||
- [Auto-Combo Engine](../routing/AUTO-COMBO.md) — 12-factor scoring, mode packs
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "Router Backends & Embedded Services (ADR)"
|
||||
version: 3.8.43
|
||||
lastUpdated: 2026-07-02
|
||||
---
|
||||
|
||||
# Router Backends & Embedded Services — architecture contract (ADR)
|
||||
|
||||
> **Status:** Accepted · **Context:** [#5670](https://github.com/diegosouzapw/OmniRoute/issues/5670),
|
||||
> [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) · **Contract:** `domain/routing/routerBackends.ts`
|
||||
> (typed registry — code lands with [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868))
|
||||
|
||||
This ADR pins down how `ts` (native), `bifrost`, `cliproxy`, `9router`, and
|
||||
VibeProxy-compatible engines relate to each other, so contributors stop
|
||||
conflating two things that are architecturally distinct. It documents the typed
|
||||
registry introduced by the router-backend-registry work as the single source of
|
||||
truth for that model.
|
||||
|
||||
## The core distinction — two orthogonal axes
|
||||
|
||||
An engine's role is described by **two independent axes**, encoded together in the
|
||||
registry's `RouterBackendDefinition`:
|
||||
|
||||
1. **Lifecycle** (`RouterBackendLifecycle`) — _how the engine runs_:
|
||||
- `in-process` — runs inside the OmniRoute Node process (the native TS pipeline).
|
||||
- `supervised` — a local child process OmniRoute installs/starts/stops/health-checks
|
||||
via `ServiceSupervisor`, then consumes as a provider connection.
|
||||
- `external` — an HTTP endpoint OmniRoute dispatches to but does **not** manage
|
||||
(configured by an env base URL).
|
||||
- `disabled` — registered but not selectable.
|
||||
2. **Selection axis** (relay routing backend) — _whether the relay dispatches to it_:
|
||||
`RelayRoutingBackend = "ts" | "bifrost" | "auto"` in
|
||||
`src/app/api/v1/relay/chat/completions/routingBackend.ts`.
|
||||
|
||||
The mistake to avoid: treating "embedded service" and "routing backend" as one
|
||||
list. They are not. A `supervised` engine (9router/cliproxy) is a **provider
|
||||
connection consumed by the native pipeline**, not an alternate relay dispatch
|
||||
backend. `bifrost` is the reverse — a relay dispatch backend that (historically)
|
||||
was `external`-only.
|
||||
|
||||
## The registry — single source of truth
|
||||
|
||||
The `domain/routing/routerBackends.ts` contract (code lands with
|
||||
[#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) declares every engine once, with its
|
||||
lifecycle, capabilities, service identity, default port, health config, and
|
||||
telemetry support. Consumers look engines up via `getRouterBackend(id)`,
|
||||
`listRouterBackends()`, and `listRouterBackendsByCapability(cap)` instead of
|
||||
special-casing each sidecar.
|
||||
|
||||
| Backend | Lifecycle | Service (axis A) | Relay backend (axis B) | Health | Default port |
|
||||
| ----------- | ------------ | ---------------- | ---------------------- | ------------- | ------------ |
|
||||
| `ts` | `in-process` | — | `ts` (native) | — | — |
|
||||
| `bifrost` | `external`¹ | —¹ | `bifrost` / `auto` | `/health` | — |
|
||||
| `cliproxy` | `supervised` | `cliproxy` | — (provider) | `/v1/models` | 8317 |
|
||||
| `9router` | `supervised` | `9router` | — (provider) | `/api/health` | 20130 |
|
||||
| `vibeproxy` | `external` | — | — (provider adapter) | `/v1/models` | — |
|
||||
|
||||
¹ Bifrost's promotion to a `supervised` embedded service (installable/startable
|
||||
from `/api/services/bifrost/`) is tracked in
|
||||
[#5817](https://github.com/diegosouzapw/OmniRoute/pull/5817); until it merges,
|
||||
Bifrost is `external`-only (reachable solely via `BIFROST_BASE_URL`).
|
||||
|
||||
`capabilities` (`chat`, `responses`, `streaming`, `tools`, `vision`,
|
||||
`oauth-backed`, `dashboard-embed`, `model-sync`, `native-hot-path`) let callers
|
||||
filter by what an engine can actually do rather than hard-coding per-id branches.
|
||||
|
||||
## Axis A — embedded services (supervised process side)
|
||||
|
||||
- **Registry of supervised processes:** `src/lib/services/bootstrap.ts` `SERVICES[]`
|
||||
(today: `9router`, `cliproxy`).
|
||||
- **Lifecycle owner:** `src/lib/services/ServiceSupervisor.ts` — `start()` spawns the
|
||||
child, gates on `waitForHealthy()`, taps stdout/stderr into a ring buffer;
|
||||
`stop()` SIGTERM→SIGKILL; all serialized under a lock.
|
||||
- **State union** (`src/lib/services/types.ts`):
|
||||
`not_installed | stopped | starting | running | stopping | error`, plus an
|
||||
orthogonal `HealthState = healthy | unhealthy | unknown`.
|
||||
- **Why a separate process (not an in-proc SDK)?** Process isolation is what makes
|
||||
install/start/stop/health/logs independently controllable per sidecar and lets the
|
||||
loopback spawn-guard apply. Modeling an in-proc adapter is future work — the
|
||||
`native-hot-path` capability flag is where that would be expressed.
|
||||
|
||||
### Lifecycle route contract (`/api/services/<tool>/…`)
|
||||
|
||||
Status codes are **state/verb/path-specific by design** — this is the contract, not
|
||||
inconsistency:
|
||||
|
||||
| Call | Condition | Status |
|
||||
| ---------------------------- | ------------------------------- | ------------------------------------ |
|
||||
| `POST .../start` | service `not_installed` | **409** (precondition) |
|
||||
| `POST .../stop` | already stopped | **200** (idempotent no-op) |
|
||||
| `GET .../status` | OK | **200** (`live ?? row ?? "unknown"`) |
|
||||
| `POST .../start` | spawn failure | **503** (transient) |
|
||||
| `GET .../status`, `.../stop` | uncaught error | **500** |
|
||||
| `GET /api/services/<x>/logs` | unknown tool `<x>` | **404** `Service '<x>' not found` |
|
||||
| `GET .../status?reveal=key` | missing `X-Reveal-Confirm: yes` | **403** (9router only) |
|
||||
| **any** `/api/services/*` | caller not loopback/private-LAN | **403 LOCAL_ONLY** |
|
||||
|
||||
All error bodies are shaped by `createErrorResponse()` →
|
||||
`{ error: { message, type }, requestId }`, where `type` is derived from the status
|
||||
(`500→server_error`, `404→not_found`, `409→conflict`, else `invalid_request`) and is
|
||||
the machine-actionable discriminator. Messages are pre-sanitized
|
||||
(`sanitizeErrorMessage()`, Hard Rule #12).
|
||||
|
||||
**The loopback guard** is the most common source of a `403`: `/api/services/` is in
|
||||
`LOCAL_ONLY_API_PREFIXES` (`src/server/authz/routeGuard.ts`) and
|
||||
`src/server/authz/policies/management.ts` rejects any non-loopback / non-private-LAN
|
||||
caller **before auth**, because these routes spawn child processes (Hard Rules 15
|
||||
and 17). Reaching them through a public tunnel is `403` by design.
|
||||
|
||||
## Axis B — relay routing backend (dispatch side)
|
||||
|
||||
Only the relay proxy path `/api/v1/relay/chat/completions` selects a dispatch
|
||||
backend; the main `/api/v1/chat/completions` surface never consults
|
||||
`routingBackend.ts`.
|
||||
|
||||
- **Selection** (`resolveRelayRoutingBackend`): a single global env toggle —
|
||||
`OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` ∈ {`ts`, `bifrost`, `auto`}.
|
||||
If unset, `auto` when Bifrost is configured+enabled, else `ts`.
|
||||
- **Behavior:**
|
||||
- `bifrost` (forced): Bifrost failure → hard `502`, no fallback.
|
||||
- `auto`: try Bifrost, on failure/cooldown silently fall through to native.
|
||||
- `ts` / post-fallback: the native `open-sse` translator/executor pipeline.
|
||||
- **Cooldown:** per-`baseUrl` failure cooldown in `bifrostCooldown.ts`.
|
||||
|
||||
Selection is **all-or-nothing at the relay level today** — there is no per-provider
|
||||
or per-request engine swap on `release/v3.8.43`. The per-request gate is being added
|
||||
by the sidecar-manifest work
|
||||
([#5869](https://github.com/diegosouzapw/OmniRoute/pull/5869) manifest +
|
||||
[#5870](https://github.com/diegosouzapw/OmniRoute/pull/5870) `shouldTryBifrostForRequest`),
|
||||
which lets `auto` route only manifest-eligible providers through Bifrost.
|
||||
|
||||
## Dashboard integration
|
||||
|
||||
The services dashboard polls `GET /api/services/<tool>/status` every 5s via
|
||||
`src/app/(dashboard)/dashboard/providers/services/hooks/useServiceStatus.ts`,
|
||||
returning `{ tool, state, pid, port, health, installedVersion, latestVersion,
|
||||
updateAvailable, autoStart, … }`. There is no shared availability-context provider —
|
||||
each component calls the hook per tool. On `!res.ok` the hook currently surfaces a
|
||||
bare `HTTP <status>`; mapping the `error.type` field to a human explanation is a
|
||||
tracked UX improvement, not a contract change.
|
||||
|
||||
## Consequences
|
||||
|
||||
- New engines register once in `ROUTER_BACKENDS`; consumers gain them via capability
|
||||
queries without new per-id branches.
|
||||
- "Is this a service or a routing backend?" is answered by the `lifecycle` field, not
|
||||
by which list an id happens to appear in.
|
||||
- The Bifrost supervision (#5817) and native hot-path migration (#5670) build on this
|
||||
shared contract instead of special-casing each sidecar.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "Cluster Decisions"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Cluster Decisions — Optional Sidecar Profiles
|
||||
|
||||
**Status:** proposal (awaiting @diegosouzapw review)
|
||||
**Date:** 2026-06-20
|
||||
**Refs:** [#3932](https://github.com/diegosouzapw/OmniRoute/issues/3932), PR #4381
|
||||
|
||||
## TL;DR
|
||||
|
||||
Two opt-in compose profiles (`memory`, `bifrost`) for the existing 8-service deployment in [`docker-compose.yml`](../../docker-compose.yml). Default-up behaviour is **unchanged**: 3 × `omniroute` replicas + Caddy + Redis + CliproxyAPI. The two new profiles add Qdrant and Bifrost as optional sidecars, gated by `docker compose --profile <name> up`. **No existing service is removed or replaced.**
|
||||
|
||||
## Why this is conservative
|
||||
|
||||
OmniRoute's existing deployment shape is already lean and proven:
|
||||
|
||||
- **`redis:7-alpine`** handles the rate-limit/cache workload at production scale.
|
||||
- **SQLite + sqlite-vec + FTS5** cover local memory + vector + text-search (see [`src/lib/memory/vectorStore.ts:108`](../../src/lib/memory/vectorStore.ts)).
|
||||
- **Caddy** is already the LB + TLS terminator ([`docker-compose.yml`](../../docker-compose.yml)).
|
||||
- **Bifrost** is already integrated as the Tier-1 router in [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (sidecar proxy with kill switch via `BIFROST_ENABLED` env var — set `=0` to bypass the sidecar and fall through to the TS path).
|
||||
|
||||
The two profiles here are **scale-out options for deployments that hit the SQLite ceiling** — not migrations. Both are default-off.
|
||||
|
||||
## The two profiles
|
||||
|
||||
### `memory` — Qdrant Vector Memory Sidecar
|
||||
|
||||
**When to flip on:**
|
||||
|
||||
- > 1M embeddings per deployment (sqlite-vec starts to slow at scale).
|
||||
- Multi-replica deployment that needs shared vector state across `omniroute-1/2/3`.
|
||||
- You already have an external Qdrant cluster (Qdrant Cloud, on-prem).
|
||||
|
||||
**What it adds:**
|
||||
|
||||
| Service | Image | Ports | Notes |
|
||||
| -------- | ----------------------- | ----------- | ----------------------------------------------------- |
|
||||
| `qdrant` | `qdrant/qdrant:v1.12.4` | `6333` HTTP | HNSW index; persistent volume `omniroute_qdrant_data` |
|
||||
|
||||
**Activation:** flip `qdrantEnabled = true` in the Settings UI **or** set `QDRANT_HOST=qdrant` env. See [`src/lib/memory/qdrant.ts:60`](../../src/lib/memory/qdrant.ts) for the precedence rules (settings table → env var → default).
|
||||
|
||||
**Env vars:** `QDRANT_HOST`, `QDRANT_PORT`, `QDRANT_API_KEY`, `QDRANT_COLLECTION`, `QDRANT_VECTOR_SIZE`, `QDRANT_HNSW_EF_CONSTRUCT` (see `.env.example` lines 1672-1683).
|
||||
|
||||
### `bifrost` — Bifrost Tier-1 Router Sidecar
|
||||
|
||||
**When to flip on:**
|
||||
|
||||
- You run ≥3 `omniroute` replicas and want provider rotation centralised in a single Go process.
|
||||
- You want a single audit/logging surface for upstream-provider requests across all replicas.
|
||||
- You want horizontal scaling of the Tier-1 routing layer independent of the OmniRoute replicas.
|
||||
|
||||
**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` |
|
||||
|
||||
**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.
|
||||
|
||||
**Env vars:** `BIFROST_BASE_URL`, `BIFROST_API_KEY`, `BIFROST_STREAMING_ENABLED`, `BIFROST_TIMEOUT_MS` (see `.env.example` lines 1685-1695).
|
||||
|
||||
## What this PR explicitly does NOT do
|
||||
|
||||
The original issue thread floated a larger cluster rewrite. After auditing the actual workload shape, the following are **rejected** for the reasons given:
|
||||
|
||||
| Component | Verdict | Reason |
|
||||
| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| **Dragonfly** | **DROP** | `redis:7-alpine` is already fine for the rate-limit workload at production scale; no ceiling to break. |
|
||||
| **NATS** | **DROP** | Each `omniroute` replica is a single Node.js process; no multi-process pub/sub workload exists. |
|
||||
| **PostgreSQL** | **DROP** | SQLite + sqlite-vec + FTS5 cover all 3 use cases; 97 migrations + Electron packaging block migration. |
|
||||
| **Neo4j** | **DROP** | Routing is a 5-table join; recursive CTE on SQLite is sufficient. |
|
||||
| **MinIO** | **DROP** | No multi-MB blob workload; images/audio are passthrough proxies. |
|
||||
| **pgvector / pg_ai / pg_textsearch** | **DROP** | Same SQLite-ceiling reason as PostgreSQL; pgvector ecosystem fragmented. |
|
||||
| **HAProxy / Envoy** | **DROP** | Caddy already does LB + TLS; both were explicitly rejected as Tier-1 routers (see `AGENTS.md`). |
|
||||
|
||||
If a future use case proves out one of these, this doc is the place to amend.
|
||||
|
||||
## 4-week rollout (if approved)
|
||||
|
||||
1. **Wk 1** — Land this PR + verification of opt-in profiles with a 3-replica compose stack.
|
||||
2. **Wk 2** — Bifrost full activation for OpenAI/Claude/Gemini/Ollama (4 of 14+ providers) using the sidecar proxy route at [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (gated by `BIFROST_ENABLED`, kill-switchable at runtime).
|
||||
3. **Wk 3** — Qdrant memory profile enabled in a single test deployment; measure latency delta vs sqlite-vec.
|
||||
4. **Wk 4** — Observability healthchecks (`docker compose ps` exit codes + `wget` smoke tests); 71-pillar refresh per ADR-041.
|
||||
|
||||
## Files changed in this PR
|
||||
|
||||
| File | Change |
|
||||
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `docker-compose.yml` | +30 lines: `memory` profile (Qdrant), `bifrost` profile (Bifrost), persistent volumes, healthchecks. |
|
||||
| `.env.example` | +24 lines: `QDRANT_*` (6 vars), `BIFROST_*` (4 vars). |
|
||||
| `docs/reference/ENVIRONMENT.md` | +6 rows in section 25 for the `QDRANT_*` env vars. |
|
||||
| `src/lib/memory/qdrant.ts` | +33 lines: env-var fallback chain (settings → env → default) for `QDRANT_HOST`/`QDRANT_PORT`/`QDRANT_API_KEY`/`QDRANT_COLLECTION`/`QDRANT_VECTOR_SIZE`/`QDRANT_HNSW_EF_CONSTRUCT`/`QDRANT_EMBEDDING_MODEL`. |
|
||||
| `src/lib/memory/__tests__/qdrant-wiring.test.ts` | +88 lines: 9 new test cases pinning the env-var fallback precedence. |
|
||||
| `docs/architecture/cluster-decisions.md` (this file) | NEW — decision record for the opt-in profiles. |
|
||||
| `AGENTS.md` | +1 line: pointer to this doc in the reference documentation table. |
|
||||
|
||||
**Net touched code:** 4 production files (`docker-compose.yml`, `qdrant.ts`, `.env.example`, `ENVIRONMENT.md`), 1 test file (`qdrant-wiring.test.ts`), 2 doc files (`cluster-decisions.md`, `AGENTS.md`).
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"title": "Architecture",
|
||||
"pages": [
|
||||
"ARCHITECTURE",
|
||||
"AUTHZ_GUIDE",
|
||||
"CODEBASE_DOCUMENTATION",
|
||||
"REPOSITORY_MAP",
|
||||
"RESILIENCE_GUIDE",
|
||||
"QUALITY_GATES"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user