mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
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:
committed by
GitHub
parent
2d4db0157e
commit
38fd4d34d9
27
.env.example
27
.env.example
@@ -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=
|
||||
|
||||
55
README.md
55
README.md
@@ -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 (~65–75% 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 (~65–75% 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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
107
tests/unit/noauth-autocombo-allowlist.test.ts
Normal file
107
tests/unit/noauth-autocombo-allowlist.test.ts
Normal 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)}`
|
||||
);
|
||||
});
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user