feat(sse): restrict auto-combo no-auth pool to allowlist (opencode, felo) + docs (#8183)

Restrict the auto-combo no-auth (keyless) candidate pool to an allowlist —
opencode + felo — the only keyless backends verified to answer without any
credential on the reference egress (VPS .15). Excluded no-auth providers stay
usable via direct <alias>/<model> calls; they are just no longer auto-routed to.
Guard: tests/unit/noauth-autocombo-allowlist.test.ts.

Docs: add a "works the second you install it" free section near the top of the
README; sync the compression stack count 11 → 12 engines; document 13 env vars
(VNC browser-login knobs + VIBEPROXY_DATA_DIR) in .env.example / ENVIRONMENT.md.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-23 00:31:15 -03:00
committed by GitHub
parent 2d4db0157e
commit 38fd4d34d9
6 changed files with 226 additions and 45 deletions

View File

@@ -2232,3 +2232,30 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# Used by: open-sse/services/usage/hyperagent.ts
# ─────────────────────────────────────────────────────────────────────────────
# HYPERAGENT_USAGE_URL=https://hyperagent.com/api/settings/billing/usage
# ─────────────────────────────────────────────────────────────────────────────
# Browser-login VNC sessions (optional — src/lib/vncSession/manifest.ts)
# Containerized Chromium+VNC used for interactive browser-login credential
# capture via /api/vnc-session. All optional — defaults target the bundled
# `omniroute-vnc-chromium:local` image; override only for a custom image, ports,
# or lifecycle tuning.
# ─────────────────────────────────────────────────────────────────────────────
# OMNIROUTE_VNC_IMAGE=omniroute-vnc-chromium:local
# OMNIROUTE_DOCKER_BIN=docker
# OMNIROUTE_VNC_CONTAINER_VNC_PORT=3000
# OMNIROUTE_VNC_CONTAINER_CDP_PORT=9223
# OMNIROUTE_VNC_CONTAINER_PROFILE_DIR=/config
# OMNIROUTE_VNC_PROFILE_DIR=
# OMNIROUTE_VNC_IDLE_MS=600000
# OMNIROUTE_VNC_MAX_MS=1800000
# OMNIROUTE_VNC_MAX_SESSIONS=4
# OMNIROUTE_VNC_READY_MS=45000
# OMNIROUTE_VNC_HARVEST_MS=20000
# OMNIROUTE_VNC_CHROMIUM_ARGS=--remote-debugging-port=9222 --no-first-run --no-default-browser-check
# ─────────────────────────────────────────────────────────────────────────────
# Data-dir alias (optional — open-sse/services/notionThreadSessions.ts)
# Legacy fallback for DATA_DIR, checked only after DATA_DIR and
# OMNIROUTE_DATA_DIR are both unset. Locates the Notion web-thread session cache.
# ─────────────────────────────────────────────────────────────────────────────
# VIBEPROXY_DATA_DIR=

View File

@@ -122,6 +122,27 @@
<div align="center">
# 🆓 Works the second you install it — no keys, no config
</div>
> **Install → point your tool at the endpoint → it already answers.** OmniRoute ships with keyless free providers (OpenCode Free, Felo) already wired into the `auto` combo, so a **fresh install responds out of the box** — no API key, no signup, no configuration.
```bash
# Fresh install, zero credentials — `auto` already works:
curl http://localhost:20128/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}'
```
-**`auto` responds immediately** — OmniRoute builds a virtual combo from the built-in keyless free providers and routes to a healthy one, with no setup.
- **Add more providers anytime** — drop in a Claude / GPT / Gemini key (or any of the 278 providers) from the dashboard and they join the `auto` pool automatically.
- 🎛️ **Build your own free combos** — chain your free tiers with any of the 18 routing strategies so you never run out of quota.
<sub>Prefer a specific free backend? Call it directly, e.g. `oc/…` (OpenCode Free) or `felo/…` (Felo). Then graduate to `auto` and let OmniRoute pick.</sub>
<div align="center">
# 💥 The Promise
</div>
@@ -265,7 +286,7 @@ All **18** strategies — mix & match per combo step:
- **🤖 One-command CLI/agent setup** — `setup-*` configures 12+ coding tools; `omniroute launch` / `launch-codex` are zero-config. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
- **🛰️ Remote mode** — drive a remote OmniRoute with scoped tokens (`connect` / `contexts` / `tokens`) + an `antigravity` OAuth helper for VPS installs. → [Remote Mode](docs/guides/REMOTE-MODE.md)
- **🧭 Smarter auto-routing** — `auto/<category>:<tier>` combos, **Fusion** (model panel + judge), task-aware routing, per-request model / mode / USD-budget overrides. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
- **🗜️ Pluggable compression** — 11 composable engines + Compression Studios: LLMLingua-2, two-tier Ultra, omniglyph, per-step fidelity gate, GCF v3.2, drag-reorder editor. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
- **🗜️ Pluggable compression** — 12 composable engines + Compression Studios: LLMLingua-2, two-tier Ultra, omniglyph, per-step fidelity gate, GCF v3.2, drag-reorder editor. → [Compression](docs/compression/COMPRESSION_ENGINES.md)
- **🕵️ Transparent MITM decrypt (TPROXY)** — capture CLIs that ignore proxy env vars, with a per-SNI CA + trust-store installer. → [MITM/TPROXY](docs/security/MITM-TPROXY-DECRYPT.md)
- **💸 Cost telemetry everywhere** — `X-OmniRoute-*` cost/usage headers on every endpoint, cache-HIT savings header, per-key USD spend quotas. → [API Reference](docs/reference/API_REFERENCE.md)
- **🧠 Memory you control** — off by default, opt-in int8 vector quantization + typed decay, per-request `x-omniroute-no-memory`. → [Memory](docs/frameworks/MEMORY.md)
@@ -488,24 +509,26 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp
</div>
> **Why use many tokens when few tokens do the trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It's now a **stack of 11 composable engines** that run in order and mix & match per routing combo — building on ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR).
> **Why use many tokens when few tokens do the trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It's now a **stack of 12 composable engines** that run in order and mix & match per routing combo — building on ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR).
### 🧱 The 11-engine stack
### 🧱 The 12-engine stack
Engines run in pipeline order; each is independently toggleable and configurable per combo:
| # | Engine | What it does |
| --- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| 1 | **Session-Dedup** | Drops content repeated across turns (content-addressed, cross-turn) |
| 2 | **CCR** | Archives large blocks behind retrieve markers, fetched on demand |
| 3 | **RTK** | Smart tool-result filtering, dedup & truncation (command-aware) |
| 4 | **Headroom** | Lossless tabular compaction of homogeneous JSON arrays, flat or nested (~30%), via a vendored **GCF** codec (spec v3.2) |
| 5 | **Relevance** | Extractive sentence scoring against the last user query |
| 6 | **Caveman** | Rule-based prose compression (~6575% on output) |
| 7 | **LLMLingua-2** | ML semantic pruning via MobileBERT ONNX — code-safe, async |
| 8 | **Lite** | Whitespace + image-URL trimming (latency-light baseline) |
| 9 | **Aggressive** | Summarization + progressive aging of old turns |
| 10 | **Ultra** | Heuristic token pruning with an optional small-model (SLM) tier |
| # | Engine | What it does |
| --- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| 1 | **Session-Dedup** | Drops content repeated across turns (content-addressed, cross-turn) |
| 2 | **CCR** | Archives large blocks behind retrieve markers, fetched on demand |
| 3 | **Lite** | Whitespace + image-URL trimming (latency-light baseline) |
| 4 | **RTK** | Smart tool-result filtering, dedup & truncation (command-aware) |
| 5 | **Responses Tool Output** | Lossless-first JSON + bounded diagnostic compression for shell/patch/search/build outputs (Responses API) |
| 6 | **Headroom** | Lossless tabular compaction of homogeneous JSON arrays, flat or nested (~30%), via a vendored **GCF** codec (spec v3.2) |
| 7 | **Relevance** | Extractive sentence scoring against the last user query |
| 8 | **Caveman** | Rule-based prose compression (~6575% on output) |
| 9 | **Aggressive** | Summarization + progressive aging of old turns |
| 10 | **LLMLingua-2** | ML semantic pruning via MobileBERT ONNX — code-safe, async |
| 11 | **Ultra** | Heuristic token pruning with an optional small-model (SLM) tier |
| 12 | **OmniGlyph** | Experimental context-as-image encoding routed to Claude Fable 5 (most aggressive; opt-in) |
Code blocks, URLs and structured data are **always preserved** byte-perfect. **One-click presets** combine the engines:
@@ -538,7 +561,7 @@ Code blocks, URLs and structured data are **always preserved** byte-perfect. **O
### 📖 How it works — pipeline, architecture & savings math
<img src="./docs/diagrams/compression-pipeline.svg" width="100%" alt="OmniRoute compression pipeline: a client request of 10,000 tokens passes through 11 stacked engines — Session-Dedup, CCR, RTK, Headroom, Relevance, Caveman, LLMLingua-2, Omniglyph, Lite, Aggressive, Ultra — and reaches the provider at about 1,080 tokens, up to 95% saved. Code, URLs and JSON are always preserved byte-perfect."/>
<img src="./docs/diagrams/compression-pipeline.svg" width="100%" alt="OmniRoute compression pipeline: a client request of 10,000 tokens passes through 12 stacked engines — Session-Dedup, CCR, Lite, RTK, Responses Tool Output, Headroom, Relevance, Caveman, Aggressive, LLMLingua-2, Ultra, OmniGlyph — and reaches the provider at about 1,080 tokens, up to 95% saved. Code, URLs and JSON are always preserved byte-perfect."/>
Default stacked combo runs `RTK → Caveman`. When both act on the same tool/context payload, savings compound:

View File

@@ -1152,6 +1152,26 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `OMNIROUTE_ROTATE_400_THRESHOLD` | `1` | `open-sse/services/rotationConfig.ts` | Number of `400` errors within `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` required before the account is rotated (only consulted when `OMNIROUTE_ROTATE_ON_400=true`). |
| `OMNIROUTE_ROTATE_400_WINDOW_SECONDS` | `120` | `open-sse/services/rotationConfig.ts` | Sliding window (seconds) over which `400` errors are counted toward `OMNIROUTE_ROTATE_400_THRESHOLD`. |
### Browser-Login VNC Sessions & Data-Dir Alias
Containerized Chromium+VNC used for interactive browser-login credential capture (`/api/vnc-session`), plus a legacy `DATA_DIR` alias. All optional — the VNC defaults target the bundled `omniroute-vnc-chromium:local` image and are only overridden for a custom container image, ports, or lifecycle tuning.
| Variable | Default | Source File | Description |
| --- | --- | --- | --- |
| `OMNIROUTE_VNC_IMAGE` | `omniroute-vnc-chromium:local` | `src/lib/vncSession/manifest.ts` | Docker image tag for the Chromium+VNC login container. Build `docker/vnc-browser/chromium` or point this at a custom image. |
| `OMNIROUTE_DOCKER_BIN` | `docker` | `src/lib/vncSession/manifest.ts` | Container runtime binary used to launch the VNC container (e.g. set to `podman`). |
| `OMNIROUTE_VNC_CONTAINER_VNC_PORT` | `3000` | `src/lib/vncSession/manifest.ts` | VNC/noVNC port exposed inside the container. |
| `OMNIROUTE_VNC_CONTAINER_CDP_PORT` | `9223` | `src/lib/vncSession/manifest.ts` | Chrome DevTools Protocol port inside the container. |
| `OMNIROUTE_VNC_CONTAINER_PROFILE_DIR` | `/config` | `src/lib/vncSession/manifest.ts` | Chromium profile directory path inside the container. |
| `OMNIROUTE_VNC_PROFILE_DIR` | `$HOME/.omniroute/browser-login-profiles` | `src/lib/vncSession/manifest.ts` | Host directory holding persisted browser-login profiles. |
| `OMNIROUTE_VNC_IDLE_MS` | `600000` (10 min) | `src/lib/vncSession/manifest.ts` | Idle timeout (ms) before an inactive VNC session is reaped. |
| `OMNIROUTE_VNC_MAX_MS` | `1800000` (30 min) | `src/lib/vncSession/manifest.ts` | Hard cap (ms) on a single VNC session's lifetime. |
| `OMNIROUTE_VNC_MAX_SESSIONS` | `4` | `src/lib/vncSession/manifest.ts` | Maximum number of concurrent VNC sessions. |
| `OMNIROUTE_VNC_READY_MS` | `45000` | `src/lib/vncSession/manifest.ts` | Timeout (ms) waiting for the containerized browser to become CDP-ready. |
| `OMNIROUTE_VNC_HARVEST_MS` | `20000` | `src/lib/vncSession/manifest.ts` | Timeout (ms) for harvesting the captured session/cookies after login completes. |
| `OMNIROUTE_VNC_CHROMIUM_ARGS` | `--remote-debugging-port=9222 --no-first-run --no-default-browser-check` | `src/lib/vncSession/manifest.ts` | Extra command-line flags passed to the containerized Chromium. |
| `VIBEPROXY_DATA_DIR` | _(unset)_ | `open-sse/services/notionThreadSessions.ts` | **Legacy alias** for `DATA_DIR`, checked only after both `DATA_DIR` and `OMNIROUTE_DATA_DIR` are unset. Locates the Notion web-thread session cache (`<dir>/notion-web-thread-sessions.json`). |
---
## 26. Test & E2E Harness

View File

@@ -134,8 +134,19 @@ function hasUsableConnectionCredential(conn: VirtualFactoryConn): boolean {
const SYNTHETIC_NOAUTH_CONNECTION_ID = "noauth";
// Allowlist of no-auth (keyless) providers permitted to enter the `auto`/`auto-*`
// candidate pool. Narrowed to the backends verified to answer without any
// configuration on our reference egress (VPS .15): `opencode` and `felo-web`
// both return 200 there, while duckduckgo-web (429/VQD rate limit), theoldllm
// (403 Vercel egress block), chipotle (502), aihorde (401, anon key rejected)
// and the others are unreliable. The excluded providers stay fully usable via
// direct `<alias>/<model>` calls — they are just kept OUT of auto-routing until
// re-verified. Re-add an id here to bring it back into every auto/* pool.
const AUTO_COMBO_NOAUTH_ALLOWLIST = new Set<string>(["opencode", "felo-web"]);
function isChatAutoComboNoAuthProvider(providerDef: NoAuthProviderDefinition): boolean {
if (providerDef.noAuth !== true) return false;
if (!AUTO_COMBO_NOAUTH_ALLOWLIST.has(providerDef.id)) return false;
if (!Array.isArray(providerDef.serviceKinds) || providerDef.serviceKinds.length === 0)
return true;
return providerDef.serviceKinds.includes("llm");

View File

@@ -0,0 +1,107 @@
/**
* Auto-combo no-auth allowlist — the `auto`/`auto-*` candidate pool must only
* pull in no-auth (keyless) providers verified to work without any credential on
* our reference egress. As of this change that allowlist is narrowed to
* `opencode` and `felo-web`: on the reference VPS (.15) they are the only no-auth
* backends that answer 200 with zero configuration. The other no-auth providers
* (duckduckgo-web, theoldllm, chipotle, aihorde) stay OUT of every auto/* pool
* until re-verified — they remain usable via direct `<alias>/<model>` calls, they
* are just not auto-routed to.
*
* Regression guard: AUTO_COMBO_NOAUTH_ALLOWLIST in
* open-sse/services/autoCombo/virtualFactory.ts drives what belongs here.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-noauth-allowlist-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFactory.ts");
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
await resetStorage();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
if (ORIGINAL_DATA_DIR === undefined) {
delete process.env.DATA_DIR;
} else {
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
}
});
const ALLOWED_NOAUTH_PROVIDERS = ["opencode", "felo-web"];
const EXCLUDED_NOAUTH_PROVIDERS = ["duckduckgo-web", "theoldllm", "chipotle", "aihorde"];
test("fresh install: the allowlisted no-auth providers are present in the auto-combo pool", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);
for (const providerId of ALLOWED_NOAUTH_PROVIDERS) {
const models = combo.models.filter((m: { providerId: string }) => m.providerId === providerId);
assert.ok(
models.length >= 1,
`allowlisted no-auth provider "${providerId}" must be present in the fresh-install ` +
`auto-combo pool. Pool providers: ${JSON.stringify([...new Set(combo.models.map((m: { providerId: string }) => m.providerId))])}`
);
assert.ok(
models.every((m: { connectionId: string }) => m.connectionId === "noauth"),
`all "${providerId}" models must use the synthetic noauth connection`
);
assert.ok(
combo.autoConfig.candidatePool.includes(providerId),
`"${providerId}" must be in the candidatePool`
);
}
});
test("fresh install: non-allowlisted no-auth providers are EXCLUDED from the auto-combo pool", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);
for (const providerId of EXCLUDED_NOAUTH_PROVIDERS) {
const present = combo.models.some((m: { providerId: string }) => m.providerId === providerId);
assert.equal(
present,
false,
`no-auth provider "${providerId}" must NOT be auto-routed to (not in allowlist), ` +
`but it appeared in the pool. Pool: ${JSON.stringify(combo.models.map((m: { model: string }) => m.model))}`
);
assert.ok(
!combo.autoConfig.candidatePool.includes(providerId),
`"${providerId}" must not be in the candidatePool`
);
}
});
test("fresh install: EVERY synthetic-noauth candidate belongs to an allowlisted provider (no other keyless provider leaks in)", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);
const noauthProviders = [
...new Set(
combo.models
.filter((m: { connectionId: string }) => m.connectionId === "noauth")
.map((m: { providerId: string }) => m.providerId)
),
].sort();
assert.deepEqual(
noauthProviders,
[...ALLOWED_NOAUTH_PROVIDERS].sort(),
`only the allowlisted providers may be no-auth (connectionId="noauth") candidates on a ` +
`fresh install, but found: ${JSON.stringify(noauthProviders)}`
);
});

View File

@@ -203,38 +203,31 @@ test("createVirtualAutoCombo includes no-auth OpenCode Free without provider_con
assert.ok(combo.autoConfig.candidatePool.includes("opencode"));
});
test("createVirtualAutoCombo includes all chat-capable no-auth providers without connections", async () => {
test("createVirtualAutoCombo restricts the no-auth pool to the allowlist", async () => {
// Policy: the no-auth (keyless) auto-combo allowlist is narrowed to `opencode`
// and `felo-web` (open-sse/services/autoCombo/virtualFactory.ts::AUTO_COMBO_NOAUTH_ALLOWLIST) —
// the keyless backends verified to work without configuration on our reference
// egress. The others stay usable via direct `<alias>/<model>` calls but must
// NOT be auto-routed to. Dedicated guard:
// tests/unit/noauth-autocombo-allowlist.test.ts.
const combo: VirtualComboResult = await virtualFactory.createVirtualAutoCombo("fast");
// Each noAuth provider should have multiple models (not just the first)
const ddgwModels = combo.models.filter((m) => m.providerId === "duckduckgo-web");
assert.ok(ddgwModels.length >= 1, "duckduckgo-web should have at least one model");
assert.ok(
ddgwModels.every((m) => m.connectionId === "noauth"),
"all ddgw models should use noauth connection"
);
assert.ok(
ddgwModels.some((m) => m.model.startsWith("ddgw/")),
"ddgw models should have correct prefix"
);
for (const allowed of ["opencode", "felo-web"]) {
const models = combo.models.filter((m) => m.providerId === allowed);
assert.ok(models.length >= 1, `${allowed} should have at least one model`);
assert.ok(
models.every((m) => m.connectionId === "noauth"),
`all ${allowed} models should use noauth connection`
);
}
const tllmModels = combo.models.filter((m) => m.providerId === "theoldllm");
assert.ok(tllmModels.length >= 1, "theoldllm should have at least one model");
assert.ok(
tllmModels.every((m) => m.connectionId === "noauth"),
"all tllm models should use noauth connection"
);
assert.ok(
tllmModels.some((m) => m.model === "tllm/GPT_5_4"),
"tllm should include GPT_5_4"
);
const chipotleModels = combo.models.filter((m) => m.providerId === "chipotle");
assert.ok(chipotleModels.length >= 1, "chipotle should have at least one model");
assert.ok(
chipotleModels.every((m) => m.connectionId === "noauth"),
"all chipotle models should use noauth connection"
);
for (const excluded of ["duckduckgo-web", "theoldllm", "chipotle", "aihorde"]) {
assert.equal(
combo.models.some((model) => model.providerId === excluded),
false,
`no-auth provider "${excluded}" must be excluded from the auto-combo pool (not in allowlist)`
);
}
assert.equal(
combo.models.some((model) => model.providerId === "veoaifree-web"),