chore(release): prepare v3.8.0 docs and coverage ratchet

Refresh the v3.8.0 documentation set across API, setup, Docker,
environment, Fly.io, VM deployment, and troubleshooting guides.

Document the updated budget payload, management-auth requirements,
WebSocket bridge secret, Compose profiles, production deployment
details, and expanded Node support. Also update the OpenAPI catalog,
raise coverage thresholds to match the current baseline, and align
cloud-agent task route typing and schemas with the stricter runtime
behavior.
This commit is contained in:
diegosouzapw
2026-05-13 02:15:31 -03:00
parent 46e5aa5522
commit 204e15628c
13 changed files with 700 additions and 195 deletions

View File

@@ -457,12 +457,17 @@ POST /api/usage/budget
Content-Type: application/json
{
"keyId": "key-123",
"limit": 50.00,
"period": "monthly"
"apiKeyId": "key-123",
"dailyLimitUsd": 5.00,
"weeklyLimitUsd": 30.00,
"monthlyLimitUsd": 100.00,
"warningThreshold": 0.8,
"resetInterval": "monthly"
}
```
> **Schema notes** (`setBudgetSchema`): `apiKeyId` is required; at least one of `dailyLimitUsd`, `weeklyLimitUsd`, or `monthlyLimitUsd` must be greater than zero. Optional fields: `warningThreshold` (01), `resetInterval` (`daily` | `weekly` | `monthly`), `resetTime` (`HH:MM`). The legacy `{keyId, limit, period}` shape returns `400 Bad Request`.
## Request Processing
1. Client sends request to `/v1/*`
@@ -486,3 +491,5 @@ Full architecture reference: [`ARCHITECTURE.md`](ARCHITECTURE.md)
- Login uses saved password hash; fallback to `INITIAL_PASSWORD`
- `requireLogin` toggleable via `/api/settings/require-login`
- `/v1/*` routes optionally require Bearer API key when `REQUIRE_API_KEY=true`
> **Breaking change (v3.8.0)** — `/api/v1/agents/tasks/*` and the cooldown management endpoints now require **management auth** (dashboard `auth_token` cookie or a management-scoped API key). Clients that previously called these routes unauthenticated will receive `401 Unauthorized`. See commit `588a0333` (`fix(auth): require management auth for agent and cooldown APIs`).

View File

@@ -1,6 +1,8 @@
# Test Coverage Plan
Last updated: 2026-03-28
Last updated: 2026-05-13
> Status measured on 2026-05-13: lines 82.58%, statements 82.58%, functions 84.23%, branches 75.22%. Phases 1-5 are complete. Current focus is Phase 6 (>=85%) and Phase 7 (>=90%).
## Baseline
@@ -10,7 +12,7 @@ There are multiple coverage numbers depending on how the report is computed. For
| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- |
| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` |
| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` |
| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve |
| Recommended baseline | Source-only, excluding tests and including `open-sse` | 82.58% | 75.22% | 84.23% | This is the project-wide baseline to improve |
The recommended baseline is the number to optimize against.
@@ -34,50 +36,51 @@ The recommended baseline is the number to optimize against.
## Milestones
| Phase | Target | Focus |
| ------- | ---------------------: | ------------------------------------------------- |
| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage |
| Phase 2 | 65% statements / lines | DB and route foundations |
| Phase 3 | 70% statements / lines | Provider validation and usage analytics |
| Phase 4 | 75% statements / lines | `open-sse` translators and helpers |
| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches |
| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites |
| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet |
| Phase | Target | Focus | Status |
| ------- | ---------------------: | ------------------------------------------------- | ---------- |
| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage | ✅ Done |
| Phase 2 | 65% statements / lines | DB and route foundations | ✅ Done |
| Phase 3 | 70% statements / lines | Provider validation and usage analytics | ✅ Done |
| Phase 4 | 75% statements / lines | `open-sse` translators and helpers | ✅ Done |
| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches | ✅ Done |
| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites | In progress |
| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet | Pending |
Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines.
## Priority hotspots
These files or areas offer the best return for the next phases:
These files have the lowest line coverage today (< 60%) and offer the best return for Phases 6-7. Generated from `coverage/coverage-summary.json` on 2026-05-13:
1. `open-sse/handlers`
- `chatCore.ts` at 7.57%
- Overall directory at 29.07%
2. `open-sse/translator/request`
- Overall directory at 36.39%
- Many translators are still near single-digit coverage
3. `open-sse/translator/response`
- Overall directory at 8.07%
4. `open-sse/executors`
- Overall directory at 36.62%
5. `src/lib/db`
- `models.ts` at 20.66%
- `registeredKeys.ts` at 34.46%
- `modelComboMappings.ts` at 36.25%
- `settings.ts` at 46.40%
- `webhooks.ts` at 33.33%
6. `src/lib/usage`
- `usageHistory.ts` at 21.12%
- `usageStats.ts` at 9.56%
- `costCalculator.ts` at 30.00%
7. `src/lib/providers`
- `validation.ts` at 41.16%
8. Low-risk utility and API files for early gains
- `src/shared/utils/upstreamError.ts`
- `src/shared/utils/apiAuth.ts`
- `src/lib/api/errorResponse.ts`
- `src/app/api/settings/require-login/route.ts`
- `src/app/api/providers/[id]/models/route.ts`
| # | File | Lines % |
| --- | ----------------------------------------------------------------- | ------: |
| 1 | `open-sse/services/compression/validation.ts` | 7.87% |
| 2 | `src/app/api/v1/batches/route.ts` | 9.67% |
| 3 | `src/app/docs/components/FeedbackWidget.tsx` | 9.80% |
| 4 | `open-sse/services/compression/toolResultCompressor.ts` | 10.00% |
| 5 | `src/app/docs/components/DocCodeBlocks.tsx` | 10.63% |
| 6 | `open-sse/services/compression/engines/rtk/lineFilter.ts` | 10.96% |
| 7 | `open-sse/services/specificityRules.ts` | 11.28% |
| 8 | `src/mitm/systemCommands.ts` | 12.19% |
| 9 | `open-sse/services/compression/aggressive.ts` | 12.77% |
| 10 | `src/app/api/v1/batches/[id]/cancel/route.ts` | 12.98% |
| 11 | `open-sse/services/compression/progressiveAging.ts` | 13.26% |
| 12 | `open-sse/services/compression/engines/rtk/smartTruncate.ts` | 13.43% |
| 13 | `open-sse/services/compression/engines/rtk/deduplicator.ts` | 13.51% |
| 14 | `src/lib/cloudAgent/agents/jules.ts` | 13.52% |
| 15 | `open-sse/services/compression/lite.ts` | 14.46% |
| 16 | `src/app/api/v1/rerank/route.ts` | 14.94% |
| 17 | `open-sse/services/compression/preservation.ts` | 15.07% |
| 18 | `src/lib/cloudAgent/agents/codex.ts` | 15.54% |
| 19 | `open-sse/services/tierResolver.ts` | 16.66% |
| 20 | `src/app/docs/components/DocsLazyWrapper.tsx` | 16.66% |
Themes for Phases 6-7:
- `open-sse/services/compression/**` is the densest cluster of low coverage and dominates the remaining gap.
- Batch and rerank API routes (`src/app/api/v1/batches/**`, `src/app/api/v1/rerank/route.ts`) need handler-level tests.
- Cloud agent adapters (`src/lib/cloudAgent/agents/jules.ts`, `codex.ts`) and `tierResolver.ts` need scenario tests.
- Docs UI components and `src/mitm/systemCommands.ts` are lower priority but cheap branch wins.
## Execution checklist
@@ -148,18 +151,26 @@ These files or areas offer the best return for the next phases:
Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer.
Recommended ratchet sequence:
**Current gate (as of 2026-05-13):** `npm run test:coverage` enforces **75 statements / 75 lines / 75 functions / 70 branches**. This is the conservative ratchet against the measured baseline (82.58% / 82.58% / 84.23% / 75.22%) and preserves headroom for transient flakiness.
For ad-hoc threshold checks against the latest report use:
```bash
node scripts/test-report-summary.mjs --threshold 75
```
Recommended ratchet sequence (order is `statements-lines / branches / functions`):
1. 55/60/55
2. 60/62/58
3. 65/64/62
4. 70/66/66
5. 75/70/72
5. 75/70/72 <-- current gate (75/70/75)
6. 80/75/78
7. 85/80/84
8. 90/85/88
Order is `statements-lines / branches / functions`.
Next ratchet target is `80/75/78` once branch coverage holds above 78% for two consecutive runs.
## Known gap

View File

@@ -7,6 +7,11 @@
- [Quick Run](#quick-run)
- [With Environment File](#with-environment-file)
- [Docker Compose](#docker-compose)
- [Available Profiles](#available-profiles)
- [Redis Sidecar](#redis-sidecar)
- [Production Compose](#production-compose)
- [Dockerfile Stages](#dockerfile-stages)
- [Critical Environment Variables](#critical-environment-variables)
- [Docker Compose with Caddy (HTTPS)](#docker-compose-with-caddy-https-auto-tls)
- [Cloudflare Quick Tunnel](#cloudflare-quick-tunnel)
- [Image Tags](#image-tags)
@@ -50,8 +55,114 @@ docker compose --profile base up -d
# CLI profile (Claude Code, Codex, OpenClaw built-in)
docker compose --profile cli up -d
# Host profile (Linux-first; mounts host CLI binaries read-only)
docker compose --profile host up -d
# Combine CLI + CLIProxyAPI sidecar
docker compose --profile cli --profile cliproxyapi up -d
```
## Available Profiles
OmniRoute ships four Compose profiles. Pick the one that matches your environment.
| Profile | Service | When to use | Command |
| ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `base` (default) | `omniroute-base` | Headless server / minimal runtime, no provider CLIs bundled | `docker compose --profile base up -d` |
| `cli` | `omniroute-cli` | Agentic workflows that call `omniroute providers/setup/doctor` and bundled CLIs (Codex, Claude Code, Droid, OpenClaw) | `docker compose --profile cli up -d` |
| `host` | `omniroute-host` | Linux hosts that want `network_mode`-like access to host CLIs by mounting `~/.local/bin`, `~/.codex`, `~/.claude`, etc. read-only | `docker compose --profile host up -d` |
| `cliproxyapi` | `cliproxyapi` | Run the [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) sidecar on port `8317` for upstream CLI proxying | `docker compose --profile cliproxyapi up -d` |
> Multiple profiles can be combined: `docker compose --profile cli --profile cliproxyapi up -d`.
## Redis Sidecar
OmniRoute relies on Redis to back the distributed rate limiter and shared cache. The `redis` service is **always defined** in `docker-compose.yml` (it has no profile gate) and starts alongside any other profile.
| Detail | Value |
| -------------------- | --------------------------------- |
| Image | `redis:7-alpine` |
| Container name | `omniroute-redis` |
| Internal port | `6379` |
| Host port (override) | `REDIS_PORT` (defaults to `6379`) |
| Volume | `omniroute-redis-data``/data` |
| Healthcheck | `redis-cli ping` (10s interval) |
Related environment variables:
- `REDIS_URL` — connection string injected into the app (`redis://redis:6379` by default).
- `REDIS_PORT` — host-side port mapping for the Redis container.
**Disabling Redis** is not recommended (rate limiter will degrade to in-memory fallback). If you must, either remove/comment the `redis:` service block in `docker-compose.yml` or scale it to zero:
```bash
docker compose up -d --scale redis=0
```
## Production Compose
For an isolated production snapshot running alongside dev, use `docker-compose.prod.yml`.
| Detail | Value |
| ---------------------- | ---------------------------------------------------------------------------------- |
| File | `docker-compose.prod.yml` |
| Default dashboard port | `PROD_DASHBOARD_PORT=20130` (mapped to internal `${DASHBOARD_PORT:-20128}`) |
| Default API port | `PROD_API_PORT=20131` |
| Image | `omniroute:prod` (built from `runner-cli` target) |
| Redis container | `omniroute-redis-prod` (`redis:8.6.2`, dedicated `redis-prod-data` volume) |
| Data volume | `omniroute-prod-data` (named, persisted across rebuilds) |
| Healthchecks | `node healthcheck.mjs` + `redis-cli ping`, with `depends_on` gated on Redis health |
How to use:
```bash
# Build & start the production stack
docker compose -f docker-compose.prod.yml up -d --build
# Stream logs
docker compose -f docker-compose.prod.yml logs -f
# Tear down (keep volumes)
docker compose -f docker-compose.prod.yml down
```
The prod stack runs in parallel with the dev compose (different container names, ports, and volumes), so you can keep iterating locally while production stays up.
## Dockerfile Stages
The repository ships a multi-stage Dockerfile (`Dockerfile`). Three stages are exposed; pick the right `target` for your use case.
| Stage | Base image | Purpose |
| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `builder` | `node:24.15.0-trixie-slim` | Installs deps (`npm ci --legacy-peer-deps`) and runs `npm run build -- --webpack` |
| `runner-base` | `node:24.15.0-trixie-slim` | Production runtime with the Next.js standalone output. **No provider CLIs bundled.** |
| `runner-cli` | `runner-base` | Adds `git`, `docker.io`, `docker-compose` and global CLIs: `@openai/codex`, `@anthropic-ai/claude-code`, `droid`, `openclaw`. **Pick this for agentic workflows.** |
Build a specific target manually:
```bash
docker build --target runner-base -t omniroute:base .
docker build --target runner-cli -t omniroute:cli .
```
Defaults exported by `runner-base`: `PORT=20128`, `HOSTNAME=0.0.0.0`, `NODE_OPTIONS=--max-old-space-size=256`, `DATA_DIR=/app/data`, `OMNIROUTE_MIGRATIONS_DIR=/app/migrations`.
## Critical Environment Variables
Beyond the defaults documented in [ENVIRONMENT.md](ENVIRONMENT.md), the following variables matter most when running under Docker:
| Variable | Purpose | Default |
| ----------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------- |
| `OMNIROUTE_WS_BRIDGE_SECRET` | Shared secret for the WebSocket bridge. **Required in production** — set to a strong random string. | unset (must be provided) |
| `REDIS_URL` | Connection string for the rate limiter / cache backend | `redis://redis:6379` |
| `REDIS_PORT` | Host-side port for the bundled Redis container | `6379` |
| `AUTO_UPDATE_HOST_REPO_DIR` | Host path mounted into `cli` profile at `/workspace/omniroute` for self-update workflows | `.` (current directory) |
| `OMNIROUTE_MEMORY_MB` | Node heap ceiling (`NODE_OPTIONS=--max-old-space-size`) baked into the image | `256` (set in Dockerfile) |
| `DASHBOARD_PORT` / `API_PORT` | Override exposed ports for dashboard (20128) and API (20129) | `20128` / `20129` |
| `PROD_DASHBOARD_PORT` | Host-side dashboard port for `docker-compose.prod.yml` | `20130` |
| `CLIPROXYAPI_PORT` | Host-side port for the `cliproxyapi` sidecar | `8317` |
## Docker Compose with Caddy (HTTPS Auto-TLS)
OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP.
@@ -101,9 +212,9 @@ Endpoint tunnel panels (Cloudflare, Tailscale, ngrok) can be shown or hidden fro
| Image | Tag | Size | Description |
| ------------------------ | -------- | ------ | --------------------- |
| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release |
| `diegosouzapw/omniroute` | `3.7.8` | ~250MB | Current version |
| `diegosouzapw/omniroute` | `3.8.0` | ~250MB | Current version |
Multi-platform: AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi).
Multi-platform manifest: `linux/amd64` + `linux/arm64` native (Apple Silicon, AWS Graviton, Raspberry Pi). Docker selects the matching architecture automatically; pass `--platform linux/amd64` if you need to force AMD64 emulation on ARM hosts.
## Important Notes

View File

@@ -30,6 +30,7 @@
- [21. Proxy Health](#21-proxy-health)
- [22. Debugging](#22-debugging)
- [23. GitHub Integration](#23-github-integration)
- [24. Skills Sandbox (v3.8.0+)](#24-skills-sandbox-v380)
- [Deployment Scenarios](#deployment-scenarios)
- [Audit: Removed / Dead Variables](#audit-removed--dead-variables)
@@ -39,19 +40,21 @@
These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults.
| Variable | Required | Default | Source File | Description |
| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. |
| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. |
| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. |
| Variable | Required | Default | Source File | Description |
| ---------------------------- | -------------------- | ---------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. |
| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. |
| `INITIAL_PASSWORD` | **Yes** | `CHANGEME` | Bootstrap script | Sets the initial admin dashboard password (matches `.env.example` default — kept obviously insecure to force a change). **Change before first use.** After login, change via Dashboard → Settings → Security. |
| `OMNIROUTE_WS_BRIDGE_SECRET` | **Yes** (production) | _(unset)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret for the internal Codex Responses WebSocket bridge. Authenticates bridge requests between the Electron/browser WS relay and OmniRoute. ⚠️ **REQUIRED in production — when unset, all WS bridge requests are rejected.** Generate with `openssl rand -base64 32`. |
### Generation Commands
```bash
# Generate all three secrets at once:
# Generate all four secrets at once:
echo "JWT_SECRET=$(openssl rand -base64 48)"
echo "API_KEY_SECRET=$(openssl rand -hex 32)"
echo "INITIAL_PASSWORD=$(openssl rand -base64 16)"
echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)"
```
> [!CAUTION]
@@ -63,14 +66,19 @@ echo "INITIAL_PASSWORD=$(openssl rand -base64 16)"
OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle.
| Variable | Default | Source File | Description |
| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. |
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |
| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. |
| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. |
| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. |
| Variable | Default | Source File | Description |
| -------------------------------------- | -------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. |
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |
| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. |
| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. |
| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. |
| `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(unset)_ | `src/lib/db/core.ts` | Override the periodic SQLite healthcheck interval (ms). When unset, defaults are derived from `NODE_ENV`. |
| `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | Set to `1` to force the DB healthcheck loop on, even when it would normally be skipped (e.g., short-lived tasks). |
| `OMNIROUTE_MIGRATIONS_DIR` | _(auto-detect)_ | `src/lib/db/migrationRunner.ts` | Override the directory that the migration runner scans. Useful when shipping bundled migrations in custom builds. |
| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. |
| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. |
### Scenarios
@@ -85,16 +93,17 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
## 3. Network & Ports
| Variable | Default | Source File | Description |
| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
| Variable | Default | Source File | Description |
| ------------------------- | ------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
| `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. |
### Port Modes
@@ -124,16 +133,17 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
## 4. Security & Authentication
| Variable | Default | Source File | Description |
| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. |
| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. |
| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. |
| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. |
| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). |
| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. |
| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. |
| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. |
| Variable | Default | Source File | Description |
| --------------------------------------- | --------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. |
| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. |
| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. |
| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. |
| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). |
| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. |
| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. |
| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. |
| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Allow provider URLs pointing to private/local networks (localhost, 192.168.x.x, 10.x.x.x, etc.). **REQUIRED for self-hosted providers** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). When `false`, the dashboard rejects validation of local URLs. |
### Hardening Checklist
@@ -188,14 +198,17 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp
## 7. URLs & Cloud Sync
| Variable | Default | Source File | Description |
| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
| Variable | Default | Source File | Description |
| --------------------------------------- | ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
| `OMNIROUTE_PUBLIC_BASE_URL` | _(unset)_ | `open-sse/executors/chatgpt-web.ts` | Browser-facing OmniRoute origin used for image URLs in API responses (e.g., `/v1/chatgpt-web/image/<id>`). Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL but the user's browser must fetch images from a LAN, tunnel, or public origin. Do **not** include `/v1`. |
| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000` (3 min) | `open-sse/executors/chatgpt-web.ts` | Max wait time for an async chatgpt-web image to land via the celsius WebSocket. Increase during upstream queue-deep windows. |
| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | Total in-memory byte budget (MB) for the chatgpt-web image cache serving `/v1/chatgpt-web/image/<id>`. Lower on memory-constrained hosts; raise if image generation is heavy and clients race the 30-minute TTL. |
> [!IMPORTANT]
> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match.
@@ -230,20 +243,21 @@ Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress con
Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.).
| Variable | Default | Source File | Description |
| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- |
| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. |
| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). |
| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). |
| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). |
| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. |
| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. |
| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. |
| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. |
| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. |
| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. |
| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. |
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
| Variable | Default | Source File | Description |
| ------------------------- | ---------- | ----------------------------------- | ---------------------------------------------------------------------------------- |
| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. |
| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). |
| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). |
| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). |
| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. |
| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. |
| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. |
| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. |
| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. |
| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. |
| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. |
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Custom path to the Devin CLI binary (v3.8.0). Used by the Windsurf/Devin executor. |
### Docker Example
@@ -290,28 +304,34 @@ CLI_CLAUDE_BIN=/host-cli/bin/claude
Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console.
| Variable | Provider | Notes |
| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- |
| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. |
| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` |
| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. |
| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. |
| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — |
| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. |
| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — |
| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. |
| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. |
| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. |
| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — |
| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. |
| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — |
| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. |
| `QODER_OAUTH_TOKEN_URL` | Qoder | — |
| `QODER_OAUTH_USERINFO_URL` | Qoder | — |
| `QODER_OAUTH_CLIENT_ID` | Qoder | — |
| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). |
| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. |
| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. |
| Variable | Provider | Notes |
| --------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. |
| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` |
| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. |
| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. |
| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — |
| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. |
| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — |
| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. |
| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. |
| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. |
| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — |
| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. |
| `WINDSURF_FIREBASE_API_KEY` | Windsurf / Devin (v3.8) | Public Firebase Web API key used by Windsurf's Secure Token Service to refresh short-lived browser-flow tokens. Client-side credential (not a secret). Long-lived import tokens skip this entirely. Source: extracted from Devin CLI binary. |
| `WINDSURF_API_KEY` | Windsurf / Devin (v3.8) | API key fallback used by `open-sse/executors/devin-cli.ts` when no per-connection credential is available. Optional. |
| `CLI_DEVIN_BIN` | Devin CLI (v3.8) | Custom path to the Devin CLI binary (`devin`). Resolved by `open-sse/executors/devin-cli.ts`. |
| `GITLAB_DUO_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | OAuth client ID for GitLab Duo. Register an app at `https://gitlab.com/-/profile/applications` with redirect URI `<NEXT_PUBLIC_BASE_URL>/callback` and scopes `api, read_user, openid, profile, email`. Falls back to `GITLAB_OAUTH_CLIENT_ID`. |
| `GITLAB_DUO_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | OAuth client secret for GitLab Duo. Optional — PKCE flow does not require a secret. Falls back to `GITLAB_OAUTH_CLIENT_SECRET`. |
| `GITLAB_DUO_BASE_URL` | GitLab Duo (v3.8) | Override GitLab base URL (self-hosted GitLab). Defaults to `https://gitlab.com`. Falls back to `GITLAB_BASE_URL`. |
| `QODER_OAUTH_CLIENT_SECRET` | Qoder | |
| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. |
| `QODER_OAUTH_TOKEN_URL` | Qoder | — |
| `QODER_OAUTH_USERINFO_URL` | Qoder | — |
| `QODER_OAUTH_CLIENT_ID` | Qoder | — |
| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). |
| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. |
| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. |
> [!WARNING]
> **Google OAuth** (Antigravity, Gemini CLI) credentials **only work on localhost**. For remote servers:
@@ -335,15 +355,15 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
| Variable | Default Value | When to Update |
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.121 (external, cli)` | When Anthropic releases a new CLI version |
| `CODEX_USER_AGENT` | `codex-cli/0.125.0 (Windows 10.0.26100; x64)` | When OpenAI updates the Codex CLI |
| `CODEX_CLIENT_VERSION` | `0.125.0` | Override Codex client version independently of full UA string |
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.137 (external, cli)` | When Anthropic releases a new CLI version |
| `CODEX_USER_AGENT` | `codex-cli/0.130.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
| `CODEX_CLIENT_VERSION` | `0.130.0` | Override Codex client version independently of full UA string |
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.107.0 darwin/arm64` | When Antigravity IDE updates |
| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.23.2 darwin/arm64` | When Antigravity IDE updates |
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates |
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
| `QWEN_USER_AGENT` | `QwenCode/0.15.3 (linux; x64)` | When Qwen Code updates |
| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates |
| `QWEN_USER_AGENT` | `QwenCode/0.15.9 (linux; x64)` | When Qwen Code updates |
| `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates |
| `GEMINI_CLI_USER_AGENT` | `google-api-nodejs-client/10.3.0` | When Google API client updates |
> [!TIP]
@@ -562,13 +582,14 @@ Anthropic-compatible provider instead.
## 21. Proxy Health
| Variable | Default | Source File | Description |
| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
| Variable | Default | Source File | Description |
| ---------------------------- | ---------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `RATE_LIMIT_AUTO_ENABLE` | `false` | `open-sse/services/rateLimitManager.ts` | When `true`/`1`, automatically engages the rate-limit manager on first observed upstream 429 (without requiring manual toggle). Accepts `true`/`1`/`yes`. |
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
---
@@ -657,6 +678,26 @@ CLI_COMPAT_ALL=1
---
## 24. Skills Sandbox (v3.8.0+)
Limits and safety knobs applied when the Skills framework (`src/lib/skills/`) executes user-defined automations in a sandboxed environment.
| Variable | Default | Source File | Description |
| --------------------------------- | --------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `SKILLS_SANDBOX_TIMEOUT_MS` | `10000` (10 s) | `src/lib/skills/builtins.ts` | Per-execution wall-clock timeout for sandboxed skill code. Hard cap; anything longer is killed. |
| `SKILLS_EXECUTION_TIMEOUT_MS` | _(falls back to `SKILLS_SANDBOX_TIMEOUT_MS`)_ | `src/lib/skills/` | High-level skill orchestration timeout. Set higher than `SKILLS_SANDBOX_TIMEOUT_MS` to allow multi-step workflows. |
| `SKILLS_MAX_FILE_BYTES` | `1048576` (1 MB) | `src/lib/skills/builtins.ts` | Max bytes a skill may read from any single sandboxed file. |
| `SKILLS_MAX_HTTP_RESPONSE_BYTES` | `256000` (250 KB) | `src/lib/skills/builtins.ts` | Max bytes captured from any single HTTP response inside a skill. |
| `SKILLS_MAX_SANDBOX_OUTPUT_CHARS` | `100000` | `src/lib/skills/builtins.ts` | Hard cap on stdout/stderr characters returned from a sandbox invocation. |
| `SKILLS_SANDBOX_NETWORK_ENABLED` | `false` | `src/lib/skills/builtins.ts` | Set `1`/`true` to allow outbound network from inside the sandbox. Defaults to **isolated** for safety. |
| `SKILLS_ALLOWED_SANDBOX_IMAGES` | _(empty)_ | `src/lib/skills/builtins.ts` | Comma-separated allowlist of container images permitted for sandbox execution. Empty means built-in default only. |
| `SKILLS_SANDBOX_DOCKER_IMAGE` | _(built-in default)_ | `src/lib/skills/` | Container image used when spawning a Docker-backed sandbox. Override to pin a custom hardened base image. |
> [!CAUTION]
> Enabling `SKILLS_SANDBOX_NETWORK_ENABLED=true` opens an egress path from arbitrary skill code. Pair with `OUTBOUND_SSRF_GUARD_ENABLED=true` and a strict `CORS_ORIGIN`/proxy policy in shared deployments.
---
## Audit: Removed / Dead Variables
The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed:

View File

@@ -85,7 +85,7 @@ flyctl version
### 4.1 获取代码并进入目录
```powershell
git clone https://github.com/xiaoge1688/OmniRoute.git
git clone https://github.com/diegosouzapw/OmniRoute.git
cd OmniRoute
```
@@ -139,6 +139,7 @@ flyctl deploy
- `JWT_SECRET`
- `MACHINE_ID_SALT`
- `NEXT_PUBLIC_BASE_URL`
- `OMNIROUTE_WS_BRIDGE_SECRET` (生产环境必需 / required in production / obrigatório em produção — 用于 WebSocket 桥接鉴权 / used for WebSocket bridge authentication)
- `STORAGE_ENCRYPTION_KEY`
### 5.2 关于 `INITIAL_PASSWORD`
@@ -162,20 +163,21 @@ flyctl deploy
建议放入 Fly Secrets
| 变量名 | 是否推荐 | 说明 |
| --- | --- | --- |
| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 |
| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 |
| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 |
| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 |
| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 |
| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 |
| 变量名 | 是否推荐 | 说明 |
| ---------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------- |
| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 |
| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 |
| `OMNIROUTE_WS_BRIDGE_SECRET` | 生产必需 (required / obrigatório) | WebSocket 桥接鉴权密钥 (WebSocket bridge auth / chave de autenticação da ponte WebSocket) |
| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 |
| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 |
| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 |
| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 |
### 6.2 当前项目推荐值
| 变量名 | 推荐值 |
| --- | --- |
| `DATA_DIR` | `/data` |
| 变量名 | 推荐值 |
| ---------------------- | --------------------------- |
| `DATA_DIR` | `/data` |
| `NEXT_PUBLIC_BASE_URL` | `https://omniroute.fly.dev` |
说明:
@@ -183,6 +185,34 @@ flyctl deploy
- `DATA_DIR=/data` 非常关键,必须与 Fly Volume 挂载点一致
- `NEXT_PUBLIC_BASE_URL` 用于调度器和前端回调等场景
### 6.3 OAuth 回调地址配置 (OAuth callback URL / URL de callback OAuth)
如果你需要在 Fly.io 部署上启用 OAuth 登录类的 provider例如 Antigravity、Gemini、Cursor 等),必须确保以下两点:
(If you need to enable OAuth-based providers — e.g. Antigravity, Gemini, Cursor — on the Fly.io deployment, make sure of the following two points. / Se precisar habilitar providers via OAuth — p.ex. Antigravity, Gemini, Cursor — na implantação Fly.io, garanta os dois pontos abaixo.)
1. **设置 `NEXT_PUBLIC_BASE_URL` 指向你公开的 HTTPS 域名 (set `NEXT_PUBLIC_BASE_URL` to the public HTTPS domain / defina `NEXT_PUBLIC_BASE_URL` para o domínio HTTPS público)**
```powershell
flyctl secrets set NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev -a omniroute
```
如果你使用了自定义域名 (if using a custom domain / se usar um domínio personalizado),请替换为对应域名 (e.g. `https://omniroute.yourdomain.com`)。
2. **在 provider 控制台配置回调 URL (configure the callback URL on the provider console / configure a URL de callback no painel do provider)**
通常格式为 (typical format / formato típico)
```text
<NEXT_PUBLIC_BASE_URL>/api/oauth/<provider>/callback
```
例如 (e.g. / p.ex.)
- `https://omniroute.fly.dev/api/oauth/gemini/callback`
- `https://omniroute.fly.dev/api/oauth/antigravity/callback`
- `https://omniroute.fly.dev/api/oauth/cursor/callback`
如果 `NEXT_PUBLIC_BASE_URL` 与 provider 控制台中注册的回调 URL 不一致OAuth 流程会在浏览器回跳阶段失败 (mismatch between `NEXT_PUBLIC_BASE_URL` and the registered callback URL will cause OAuth to fail at the browser redirect step / divergência entre `NEXT_PUBLIC_BASE_URL` e a URL de callback registrada quebra o OAuth no redirect do navegador)。
---
## 7. 一键设置参数
@@ -199,17 +229,29 @@ $apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Min
$jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$wsBridgeSecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
flyctl secrets set `
API_KEY_SECRET=$apiKeySecret `
JWT_SECRET=$jwtSecret `
MACHINE_ID_SALT=$machineIdSalt `
STORAGE_ENCRYPTION_KEY=$storageKey `
OMNIROUTE_WS_BRIDGE_SECRET=$wsBridgeSecret `
DATA_DIR=/data `
NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev `
-a omniroute
```
在 Linux / macOS 上,也可以直接用 `openssl rand -hex 32` 生成 (on Linux / macOS, you can also use `openssl rand -hex 32` / em Linux / macOS, também é possível usar `openssl rand -hex 32`)
```bash
flyctl secrets set OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -hex 32) -a omniroute
```
说明 (notes / observações)
- `OMNIROUTE_WS_BRIDGE_SECRET` 在生产环境必需,缺失会导致 WebSocket 桥接握手失败 (required in production; missing it breaks WebSocket bridge handshake / obrigatório em produção; sem ele o handshake da ponte WebSocket falha)
如果你还要加初始密码:
```powershell
@@ -282,6 +324,8 @@ git describe --tags --always
git show --no-patch --oneline v3.4.7
```
> 注 (note / nota):当前项目版本为 `v3.8.0` (current project version is `v3.8.0` / a versão atual do projeto é `v3.8.0`)。下文中的 `v3.4.7` 仅为历史示例 (the `v3.4.7` references below are kept as historical examples only / as referências a `v3.4.7` abaixo são apenas exemplos históricos);实际发布时请使用 `:latest` 或当前版本标签 (e.g. `:v3.8.0`) (use `:latest` or the current version tag — e.g. `:v3.8.0` — for actual releases / use `:latest` ou a tag da versão atual — p.ex. `:v3.8.0` — em releases reais)。
如果你想合并上游最新 `main`,并强制保留 fork 当前的 `fly.toml`,可按下面流程执行:
```powershell
@@ -319,7 +363,7 @@ git merge-base --is-ancestor v3.4.7 upstream/main
6. `flyctl status -a omniroute`
7. `flyctl logs --no-tail -a omniroute`
这就是当前项目升级到 `v3.4.7` 时使用的实际流程。
这就是当前项目升级到 `v3.4.7` 时使用的实际流程 (示例为历史版本,当前实际版本是 `v3.8.0` / example refers to a historical version; the current actual version is `v3.8.0` / o exemplo refere-se a uma versão histórica; a versão atual é `v3.8.0`)
---

View File

@@ -47,15 +47,42 @@ The [AUR package](https://aur.archlinux.org/packages/omniroute-bin) installs Omn
### From Source
```bash
cp .env.example .env
npm install
PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev
```
> **Note:** `npm install` auto-generates `.env` from `.env.example` on first run. Subsequent installs will not overwrite an existing `.env`, so customizations are preserved. To re-seed, delete `.env` before re-running.
### Docker
See the [Docker Guide](DOCKER_GUIDE.md) for complete Docker setup including Compose profiles and Caddy HTTPS.
### Desktop App (Electron)
OmniRoute ships a desktop wrapper built on Electron 41 + electron-builder 26.10. Available scripts (workspace root):
```bash
npm run electron:dev # Run desktop with hot-reload
npm run electron:build # Build for current OS (auto-detected)
npm run electron:build:win # Windows installer (NSIS + portable)
npm run electron:build:mac # macOS (dmg + zip, arm64+x64)
npm run electron:build:linux # Linux (AppImage + deb + rpm)
npm run electron:smoke:packaged # Smoke-test packaged build
```
Releases of the desktop installers are attached to GitHub Releases. For the full Electron deep-dive (signing, IPC bridge, distros), see [`ELECTRON_GUIDE.md`](./ELECTRON_GUIDE.md) _(criado em fase posterior)_.
### Headless server (CI/automation)
For unattended setups (Docker, Kubernetes, CI), use:
```bash
omniroute setup --non-interactive
omniroute providers test-batch
```
Combined with env vars (`INITIAL_PASSWORD`, `OMNIROUTE_WS_BRIDGE_SECRET`, etc.), this lets you spin up an OmniRoute instance fully scriptable.
### CLI Options
| Command | Description |
@@ -253,7 +280,7 @@ For Void Linux users, you can build a native package using `xbps-src`. Save this
```bash
# Template file for 'omniroute'
pkgname=omniroute
version=3.4.1
version=3.8.0
revision=1
hostmakedepends="nodejs python3 make"
depends="openssl"
@@ -262,7 +289,9 @@ maintainer="zenobit <zenobit@disroot.org>"
license="MIT"
homepage="https://github.com/diegosouzapw/OmniRoute"
distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz"
checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b
# Regenerate the checksum for each release with:
# curl -L -o /tmp/omniroute.tar.gz "https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" && sha256sum /tmp/omniroute.tar.gz
checksum=PLACEHOLDER_REGENERATE_PER_RELEASE
system_accounts="_omniroute"
omniroute_homedir="/var/lib/omniroute"
export NODE_ENV=production

View File

@@ -14,7 +14,7 @@ Common problems and solutions for OmniRoute.
| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` |
| No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled |
| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` |
| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) |
| Routing strategy not saving | Update to the latest v3.x release (Zod schema fix for settings persistence shipped in earlier versions) |
| Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below |
| `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below |
| Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below |
@@ -46,7 +46,7 @@ Common problems and solutions for OmniRoute.
3. Reinstall OmniRoute: `npm install -g omniroute`
4. Restart: `omniroute`
> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`. Node.js 24.x LTS (Krypton) is fully supported.
> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <27`. Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported.
### macOS: `dlopen` / "slice is not valid mach-o file"
@@ -72,7 +72,7 @@ npm rebuild better-sqlite3
omniroute
```
> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) is fully supported with `better-sqlite3` v12.x.
> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <27`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported with `better-sqlite3` v12.x.
---
@@ -268,10 +268,10 @@ Use **Dashboard → Translator** to debug format translation issues:
- **Thinking tags not appearing** — Check if the target provider supports thinking and the thinking budget setting
- **Tool calls dropping** — Some format translations may strip unsupported fields; verify in Playground mode
- **System prompt missing** — Claude and Gemini handle system prompts differently; check translation output
- **SDK returns raw string instead of object** — Fixed in v1.1.0: response sanitizer now strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures
- **GLM/ERNIE rejects `system` role** — Fixed in v1.1.0: role normalizer automatically merges system messages into user messages for incompatible models
- **`developer` role not recognized** — Fixed in v1.1.0: automatically converted to `system` for non-OpenAI providers
- **`json_schema` not working with Gemini** — Fixed in v1.1.0: `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema`
- **SDK returns raw string instead of object** — Resolved in v1.x; response sanitizer strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures. If you still see this on v3.x+, please file an issue.
- **GLM/ERNIE rejects `system` role** — Resolved in v1.x; role normalizer automatically merges system messages into user messages for incompatible models. If you still see this on v3.x+, please file an issue.
- **`developer` role not recognized** — Resolved in v1.x; automatically converted to `system` for non-OpenAI providers. If you still see this on v3.x+, please file an issue.
- **`json_schema` not working with Gemini** — Resolved in v1.x; `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema`. If you still see this on v3.x+, please file an issue.
---
@@ -332,6 +332,118 @@ You can ignore this section if you do not run RAG or agent pipelines behind Omni
---
## v3.8.0 Known Issues
Issues specific to the v3.8.0 release and their current workarounds. If a fix lands in a later patch, the entry will be updated or removed.
### Windsurf OAuth flow fails with 401
**Symptoms:**
- "401 unauthorized" while completing the Windsurf OAuth flow from the dashboard
- Windsurf provider card stays in "needs reconnection" state after the callback
**Causes:**
- `WINDSURF_FIREBASE_API_KEY` env var missing or empty
- `WINDSURF_API_KEY` misconfigured or pointing at a stale token
- Local firewall/proxy blocking the OAuth callback
**Fix:**
1. Verify both `WINDSURF_FIREBASE_API_KEY` and `WINDSURF_API_KEY` are set in `.env`
2. Restart OmniRoute so the new env values are picked up
3. Re-run the OAuth flow from **Dashboard → Providers → Windsurf → Reconnect**
### Devin CLI auth failures
**Symptoms:**
- "Devin CLI not found" or "auth failed" when invoking Devin-backed tools
- CLI runtime check reports `installed=false`
**Causes:**
- `CLI_DEVIN_BIN` points to a path that does not exist
- Devin CLI is not installed on the host
**Fix:**
1. Install the Devin CLI for your platform
2. Set `CLI_DEVIN_BIN=/usr/local/bin/devin` (or the real path) in `.env`
3. Restart OmniRoute and re-test from **Dashboard → CLI Tools**
### Model cooldown stuck (manual reset)
**Symptoms:**
- A model stays listed in cooldown even after the expiration time has passed
- Requests still skip the model in combo routing despite the timestamp being in the past
**Manual reset:**
- **Dashboard:** **Settings → Model Cooldowns** → click **Re-enable** on the affected card
- **API:** `DELETE /api/resilience/model-cooldowns` with management auth headers
### Command Code provider connection fails with 403
**Symptoms:**
- 403 when testing the Command Code provider connection
- The provider card shows "unauthorized" after a fresh add
**Cause:** The OAuth flow did not complete (callback not received or token not persisted).
**Fix:**
- Run `omniroute providers` from the CLI to re-trigger the OAuth flow, or
- Re-run OAuth from **Dashboard → Providers → Command Code → Reconnect**
### ModelScope returns aggressive 429 cooldowns
**Symptoms:**
- Very short or immediate cooldowns on ModelScope after a small burst of requests
- Combo routing skips ModelScope earlier than expected
**Cause:** ModelScope emits provider-specific `Retry-After` headers. v3.8.0 ships dedicated handling for those headers, so older versions misread them as generic rate-limit hints.
**Fix:**
- Ensure you are on v3.8.0 or later
- Verify the `useUpstream429BreakerHints` toggle is enabled under **Settings → Resilience**
### OMNIROUTE_WS_BRIDGE_SECRET missing in production
**Symptoms:**
- 401 on every Codex/Responses WebSocket bridge request when running on a remote production host
- WebSocket bridge handshake closes immediately after connect
**Cause:** The `OMNIROUTE_WS_BRIDGE_SECRET` env var is missing from the production environment.
**Fix:**
1. Generate a random secret: `openssl rand -hex 32`
2. Set `OMNIROUTE_WS_BRIDGE_SECRET=<random-secret>` in the production server env (and any client that talks to the bridge)
3. Restart OmniRoute
### Responses API: background mode degraded to synchronous
**Symptoms:**
- Warning logged: `background mode degraded to synchronous`
- A `background: true` request returns a normal synchronous response instead of a background job handle
**Cause:** v3.8.0 intentionally degrades `background: true` on the Responses API to synchronous execution while emitting a warning. Full async background execution is a future deliverable.
**Fix:**
- Adjust the client to call without `background`, or
- Wait for a later release that ships full async background mode (track the changelog)
---
## Still Stuck?
- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)

View File

@@ -54,7 +54,7 @@ apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo $VERSION_CODENAME) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
```
@@ -91,7 +91,7 @@ mkdir -p /opt/omniroute
### 2.2 Create environment variables file
```bash
cat > /opt/omniroute/.env << EOF
cat > /opt/omniroute/.env << 'EOF'
# === Security ===
JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY
INITIAL_PASSWORD=YourSecurePassword123!
@@ -99,13 +99,13 @@ API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY
STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY
STORAGE_ENCRYPTION_KEY_VERSION=v1
MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT
OMNIROUTE_WS_BRIDGE_SECRET=REPLACE-WITH-WS-BRIDGE-SECRET # REQUIRED em produção: usado pelo Codex Responses WS bridge
# === App ===
PORT=20128
NODE_ENV=production
HOSTNAME=0.0.0.0
DATA_DIR=/app/data
STORAGE_DRIVER=sqlite
APP_LOG_TO_FILE=true
AUTH_COOKIE_SECURE=false
REQUIRE_API_KEY=false
@@ -173,7 +173,7 @@ chmod 600 /etc/nginx/ssl/origin.key
### 3.2 Nginx Configuration
```bash
cat > /etc/nginx/sites-available/omniroute << NGINX
cat > /etc/nginx/sites-available/omniroute << 'NGINX'
# Default server — blocks direct access via IP
server {
listen 80 default_server;
@@ -208,7 +208,7 @@ server {
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Connection "upgrade";
# SSE (Server-Sent Events) — streaming AI responses
proxy_buffering off;
@@ -315,7 +315,7 @@ docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
```bash
docker stop omniroute
docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
alpine sh -c rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /
alpine sh -c "rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /"
docker start omniroute
```
@@ -326,7 +326,7 @@ docker start omniroute
### Restrict nginx to Cloudflare IPs
```bash
cat > /etc/nginx/cloudflare-ips.conf << CF
cat > /etc/nginx/cloudflare-ips.conf << 'CF'
# Cloudflare IPv4 ranges — update periodically
# https://www.cloudflare.com/ips-v4/
set_real_ip_from 173.245.48.0/20;

View File

@@ -1433,22 +1433,6 @@ paths:
"200":
description: Translation history entries
/api/translator/load:
get:
tags: [Translator]
summary: Load saved translation template
responses:
"200":
description: Template data
/api/translator/save:
post:
tags: [Translator]
summary: Save translation template
responses:
"200":
description: Template saved
# ─── CLI Tools ─────────────────────────────────────────────────
/api/cli-tools/backups:
@@ -2269,6 +2253,73 @@ paths:
"200":
description: Generated content
# ─── OpenAPI Spec ──────────────────────────────────────────────
/api/openapi/spec:
get:
tags: [System]
summary: Get OpenAPI specification catalog
description: >-
Returns a structured JSON catalog parsed from this `openapi.yaml`,
including info, servers, tags, schemas, and a flat list of endpoints
(method, path, tags, summary, security, parameters, responses).
Used by the in-app API explorer.
responses:
"200":
description: Parsed OpenAPI catalog
content:
application/json:
schema:
type: object
properties:
info:
type: object
servers:
type: array
items:
type: object
tags:
type: array
items:
type: object
endpoints:
type: array
items:
type: object
properties:
method:
type: string
path:
type: string
tags:
type: array
items:
type: string
summary:
type: string
description:
type: string
security:
type: boolean
parameters:
type: array
items:
type: object
requestBody:
type: boolean
responses:
type: array
items:
type: string
schemas:
type: array
items:
type: string
"404":
description: openapi.yaml file not found on disk
"500":
description: Failed to parse OpenAPI spec
components:
securitySchemes:
BearerAuth:
@@ -2476,13 +2527,35 @@ components:
type: array
items:
type: object
required: [role, content]
required: [role]
properties:
role:
type: string
enum: [system, user, assistant]
description: >-
Message role. The proxy accepts any non-empty string; common values
include system, user, assistant, tool, function, and developer.
example: user
content:
description: >-
Message content. May be a plain string, an array of content parts
for multimodal inputs (text, image, audio, etc.), or null when the
message only carries tool/function calls.
oneOf:
- type: string
- type: array
items:
type: object
- type: "null"
name:
type: string
tool_call_id:
type: string
tool_calls:
type: array
items:
type: object
function_call:
type: object
stream:
type: boolean
default: false
@@ -2492,6 +2565,65 @@ components:
maximum: 2
max_tokens:
type: integer
top_p:
type: number
minimum: 0
maximum: 1
n:
type: integer
minimum: 1
default: 1
stop:
description: Up to 4 stop sequences (string or array of strings).
oneOf:
- type: string
- type: array
items:
type: string
maxItems: 4
frequency_penalty:
type: number
minimum: -2
maximum: 2
presence_penalty:
type: number
minimum: -2
maximum: 2
seed:
type: integer
logprobs:
type: boolean
top_logprobs:
type: integer
minimum: 0
maximum: 20
response_format:
type: object
description: Output format constraint (e.g. JSON mode or JSON Schema).
properties:
type:
type: string
example: json_object
tools:
type: array
description: Tool definitions available to the model.
items:
type: object
tool_choice:
description: Controls which tool (if any) is invoked by the model.
oneOf:
- type: string
example: auto
- type: object
parallel_tool_calls:
type: boolean
default: true
service_tier:
type: string
example: auto
user:
type: string
description: Stable end-user identifier for abuse monitoring.
ChatCompletionResponse:
type: object
@@ -2637,7 +2769,21 @@ components:
type: string
strategy:
type: string
enum: [priority, weighted, round-robin, random, least-used, cost-optimized]
enum:
- priority
- weighted
- round-robin
- context-relay
- fill-first
- p2c
- random
- least-used
- cost-optimized
- reset-aware
- strict-random
- auto
- lkgp
- context-optimized
default: priority
nodes:
type: array

View File

@@ -95,7 +95,7 @@
"test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run --config vitest.mcp.config.ts",
"test:ecosystem": "node scripts/run-ecosystem-tests.mjs",
"test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=1 tests/unit/*.test.ts",
"test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --import tsx/esm --test --test-concurrency=1 tests/unit/*.test.ts",
"test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts",
"coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
"coverage:summary": "node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60",

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { getAgent } from "@/lib/cloudAgent/registry";
import type { CloudAgentTaskRow } from "@/lib/cloudAgent/db";
import {
createCloudAgentTaskTable,
insertCloudAgentTask,
@@ -19,8 +20,6 @@ import pino from "pino";
const logger = pino({ name: "cloud-agents-api" });
createCloudAgentTaskTable();
function getLimit(value: string | null): number {
const parsed = Number.parseInt(value || "50", 10);
if (!Number.isFinite(parsed)) return 50;
@@ -36,12 +35,14 @@ export async function GET(request: NextRequest) {
const authError = await requireCloudAgentManagementAuth(request);
if (authError) return authError;
createCloudAgentTaskTable();
const { searchParams } = new URL(request.url);
const providerId = searchParams.get("provider");
const status = searchParams.get("status");
const limit = getLimit(searchParams.get("limit"));
let tasks;
let tasks: CloudAgentTaskRow[];
if (providerId) {
tasks = getCloudAgentTasksByProvider(providerId, limit);
} else if (status) {
@@ -107,6 +108,7 @@ export async function POST(request: NextRequest) {
credentials
);
createCloudAgentTaskTable();
insertCloudAgentTask({
id: task.id,
provider_id: task.providerId,
@@ -152,6 +154,8 @@ export async function DELETE(request: NextRequest) {
const authError = await requireCloudAgentManagementAuth(request);
if (authError) return authError;
createCloudAgentTaskTable();
const { searchParams } = new URL(request.url);
const taskId = searchParams.get("id");

View File

@@ -85,13 +85,13 @@ export const CloudAgentActivitySchema = z.object({
type: z.enum(["plan", "command", "code_change", "message", "error", "completion"]),
content: z.string(),
timestamp: z.string().datetime(),
metadata: z.record(z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export const CloudAgentTaskOptionsSchema = z.object({
autoCreatePr: z.boolean().optional(),
planApprovalRequired: z.boolean().optional(),
environment: z.record(z.string()).optional(),
environment: z.record(z.string(), z.string()).optional(),
});
export const CreateCloudAgentTaskSchema = z.object({

View File

@@ -1,7 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
const { CLOUD_AGENT_STATUS, CloudAgentStatusSchema, CloudAgentTask } =
const { CLOUD_AGENT_STATUS, CloudAgentStatusSchema } =
await import("../../src/lib/cloudAgent/types.ts");
const { CreateCloudAgentTaskSchema, UpdateCloudAgentTaskSchema } =
await import("../../src/lib/cloudAgent/types.ts");