Compare commits

...

25 Commits

Author SHA1 Message Date
Markus Hartung
7dcb1fba69 board #11146 (fix items; baseline reconciliado) 2026-08-25 01:36:37 -03:00
Hermes Developer
0a6cdbe0f6 docs(routing): mark rungBudgetUsd as not-yet-enforced + freeze virtualFactory at merge size
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-25 01:35:15 -03:00
Markus Hartung
77e1755579 board #11454 2026-08-25 01:21:51 -03:00
Markus Hartung
06d4c6ed50 board #11370 2026-08-25 01:21:48 -03:00
Markus Hartung
78f4cebb6e board #11146 2026-08-25 01:21:46 -03:00
Markus Hartung
e9098e67b0 board #11389 2026-08-25 01:21:42 -03:00
Markus Hartung
beff0db02d board #11340 2026-08-25 01:21:40 -03:00
Markus Hartung
b417f172c9 board #11441 2026-08-25 01:21:39 -03:00
Tuan Dinh
045b9dcfc3 fix(providers): send Responses API payload during OpenAI-compatible validation
The connection test posted chat-completions fields (`messages`, `max_tokens`)
to `/responses`. Send `input` and `max_output_tokens` instead.

Fixes #11453
2026-08-25 09:52:43 +07:00
kriptoburak
8da73b87ec fix(docs): reconcile provider counts after rebase 2026-08-25 05:29:22 +03:00
kriptoburak
c2c92a89e8 fix(search): sync generated inference skill 2026-08-25 05:17:07 +03:00
kriptoburak
22da128df4 fix(search): document API and update dispatch golden 2026-08-25 05:17:07 +03:00
kriptoburak
b130f3e55f docs(changelog): add Xquik search fragment 2026-08-25 05:17:07 +03:00
kriptoburak
27d3c76c82 feat(search): add Xquik X search provider 2026-08-25 05:17:07 +03:00
Max
42c209797c fix(free-models): stop reporting discontinued Pollinations models as free
The shared isFreeModel() predicate ignored each catalog entry's
freeType, so Pollinations models marked "discontinued" (moved behind a
paid key) were still reported free and could be routed under
hidePaidModels.

Add a compiler-checked grantsFreeAccess(freeType) table and use it in
both the routing predicate and strictZeroCostFilter.

Tests: tests/unit/autoCombo/free-regime-not-read-by-predicate.test.ts
2026-08-25 01:03:45 +02:00
KaspaPulse
85b2a06fad feat(dashboard): surface exclusive managed leases in Sessions view
Reuse the official exclusive-lease authority for durable idle visibility, and use pending-request accounting only for the localized active indication. Preserve legacy session fields and rows while de-duplicating leased connections and withholding lease ownership and fencing data.

Refs #10514

Follow-up to #10362
2026-08-24 13:53:27 +00:00
Zartharas
c89bdb21a2 chore(changelog): document web-session contract 2026-08-24 00:07:51 -05:00
Zartharas
c5e694131a style(test): format web-session contract regression 2026-08-24 00:07:33 -05:00
Aman
9e7096a048 refactor(providers): use canonical contract imports 2026-08-23 23:36:47 -05:00
Aman
a84f0b683e test(providers): cover web-session contract publication 2026-08-23 23:36:47 -05:00
Aman
ae0b79ab7c feat(api): publish web-session credential contract 2026-08-23 23:36:47 -05:00
Aman
6daa31de0b feat(providers): add web-session contract builder 2026-08-23 23:36:46 -05:00
Hermes Developer
a92430feb4 Merge remote-tracking branch 'upstream/release/v3.8.50' into feat/subscription-first-routing 2026-08-23 13:20:22 -04:00
Hermes Developer
7c4d44fe18 docs(changelog): fragment for #11146 2026-08-22 11:56:35 -04:00
Hermes Developer
4150167a63 feat(routing): subscription-first auto groupings (auto/subscription, auto/thrifty)
OmniRoute answers "is this model free?" (hidePaidModels) and "can this
connection ever bill me?" (STRICT_ZERO_COST), but both fail closed and every
paid-side mechanism (cost-optimized, budgetCap, the cost-saver mode pack) is
tier-agnostic. Nothing answers "use the plan quota I already pay for; when it
runs out either stop, or step up one rung at a time; and come back when it
resets."

The blocker was that billing is a property of the CONNECTION, not the model:
classifyTier() keys on (provider, model), while the same model is plan-included
through an OAuth connection and metered through an API-key one. auth_type is
not a safe proxy in either direction. So this adds a curated per-connection
billing catalog, hand-set from published terms, following the same pattern
FreeModelBudget.hardStopGuaranteed already established. Uncurated resolves to
unknown and is consumed as metered, so new providers start outside the
subscription rung.

Two ids, sharing one rung model (subscription > keyless > free > cheap >
premium):

  - auto/subscription: rung 0 only, hard-stop overage only, live quota verified
    per connection. Fails CLOSED — an empty pool is the intended answer.
  - auto/thrifty: all rungs ordered, exhausted ones gated out, scoring still
    runs within the survivors. Fails OPEN one rung at a time.

Both reuse STRICT_ZERO_COST's connection-safety invariant: each connection in
allowedConnectionIds is verified individually and the array is rewritten to the
surviving subset, so autoStrategy.ts can only dispatch to a verified account.

Reset re-entry: a cached quota reading whose own resetAt has passed is now
stale regardless of TTL, and rung eligibility is recomputed per pool build with
no persisted demotion that could outlive a reset. clampCooldownToReset() is
implemented and tested but not yet wired — the quota cache is invalidated in
auth.ts before any cooldown is written, so resetAt must be captured earlier
there; that hot-path change belongs in its own PR.

Both ids are opt-in by being requested; no existing pool, strategy or default
changes. Settings are tuning-only on purpose (no enabled flag that could leave
auto/subscription silently serving paid capacity).

Also corrects docs/guides/TIERS.md, which advertised a combo strategy named
"subscription" that has never existed in ROUTING_STRATEGY_VALUES.
2026-08-22 11:55:05 -04:00
101 changed files with 3443 additions and 305 deletions

View File

@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
## Project at a Glance
**OmniRoute** — unified AI proxy/router. One endpoint, 350 LLM providers, auto-fallback.
**OmniRoute** — unified AI proxy/router. One endpoint, 353 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -7,7 +7,7 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 350 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 350 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 353 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 353 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
@@ -210,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 350 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 350 providers · up to 95% token savings on eligible workloads · $0 to start with 90+ free tiers and 56 recurring/keyless free-forever providers · 35 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 353 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 353 providers · up to 95% token savings on eligible workloads · $0 to start with 90+ free tiers and 56 recurring/keyless free-forever providers · 35 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<br/>
<br/>
@@ -461,7 +461,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 350 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 353 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -559,7 +559,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [API Reference](docs/reference/API_REFERENCE.md)
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **350-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **353-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
- **⚡ Local performance & infra** — one-click local Redis, Cloudflare Workers / Deno Deploy relay deployers, Bifrost & Mux as supervised embedded services. → [Embedded Services](docs/frameworks/EMBEDDED-SERVICES.md)
@@ -642,11 +642,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<div align="center">
## 🌐 350 AI Providers — 154 Catalog-Marked Free
## 🌐 353 AI Providers — 154 Catalog-Marked Free
</div>
> **350 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **154 carrying `hasFree: true` discovery metadata**. The chat model registry covers **268 providers / 2,566 distinct provider-model pairs / 1,312 raw model IDs**; the separate free-budget catalog has **455 per-model rows**, **40 recurring pools** and **56 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
> **353 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **154 carrying `hasFree: true` discovery metadata**. The chat model registry covers **268 providers / 2,566 distinct provider-model pairs / 1,312 raw model IDs**; the separate free-budget catalog has **455 per-model rows**, **40 recurring pools** and **56 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
<div align="center">

View File

@@ -0,0 +1 @@
- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146))

View File

@@ -0,0 +1 @@
- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas

View File

@@ -0,0 +1 @@
- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak

View File

@@ -0,0 +1 @@
- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse

View File

@@ -0,0 +1 @@
- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441))

View File

@@ -478,7 +478,8 @@
"_rebaseline_2026_08_23_tip_drift_post_batch0823": "Tip drift after the 2026-08-23 merge wave: chatBodyAdmission.ts 1009->1118 (+109, gate count incl. +1) and auth.ts 3337->3344 (+7), both grown by merges already on origin/release/v3.8.50 (verified identical on the pristine tip) — not by the codex-appserver-hardening PR that carries this bump. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.",
"src/lib/guardrails/videoBridgeRuntime.ts": 1009,
"_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive)."
"_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
"open-sse/services/autoCombo/virtualFactory.ts": 1130
},
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
@@ -646,5 +647,6 @@
"_rebaseline_2026_08_18_imageregistry_merge_train": "merge-train 2026-08-18 (owner-authorized, /merge-prs batch of 84): open-sse/config/imageRegistry.ts crossed the 1000-line new-file cap for the first time purely from combining three independent, already-legitimate provider registrations boarded in the same local merge-train — #10542 (aihorde optional-key image catalog), #10494 (gemini-web image generation), #10594 (freepik/magnific provider rename + validation). 996 on release tip -> 1019 on the train tip. Each PR individually adds a small, additive IMAGE_PROVIDERS registry entry at the existing chokepoint; none crosses the cap alone. Not modularized as part of this train's gate fix (out of scope for a merge reconciliation, not a feature change). Covered by each PR's own focused tests (aihorde-image-catalog/generation, gemini-web image tests, freepik/magnific provider tests).",
"_rebaseline_2026_08_20_v3850_merge_train_batch1": "Merge-train batch1 (2026-08-19/20, 30 PRs boarded onto release/v3.8.50): gateways.ts 1255->1268 = PR #10722 (Token Kiosk OpenAI-compatible provider gateway catalog entry, +13 declarative lines, same god-file no-split rationale as prior gateways.ts rebaselines); chatHelpers.ts (uncapped, not previously frozen) new 1017 = PR #10797 (relay/bifrost error normalization, +23/-2, own-PR growth, existing file already near cap from accumulated chokepoint wiring per its own rebaseline history above); chatBodyAdmission.ts (uncapped) new 1005 = pre-existing base-red on the pure release tip (1004>1000 before this train boarded anything, no PR in this batch touches this file) — frozen here at its current size, not authorizing further growth. Owner-authorized rebaseline (2026-08-19 merge-prs session).",
"_rebaseline_2026_08_20_8338_cursor_image_provider": "PR (reimplementation of #8338, @valvesss): imageRegistry.ts 1019->1033 = new cursor IMAGE_PROVIDERS entry (Cursor plan image generation via Agent CLI), +14 lines of declarative provider metadata. Same god-registry no-split rationale as prior imageRegistry/gateways rebaselines.",
"_rebaseline_2026_08_20_imageregistry_1034": "imageRegistry.ts 1033->1034: +1 line drift between #10842 (cursor image provider, froze at 1033) and its actual merged state on release (measured 1034) — trivial rebaseline, not a new feature."
"_rebaseline_2026_08_20_imageregistry_1034": "imageRegistry.ts 1033->1034: +1 line drift between #10842 (cursor image provider, froze at 1033) and its actual merged state on release (measured 1034) — trivial rebaseline, not a new feature.",
"_rebaseline_2026_08_25_11146_subscription_first_auto": "PR #11146 (@yourspraveen, subscription-first auto groupings auto/subscription+auto/thrifty): open-sse/services/autoCombo/virtualFactory.ts is a NEW file in this PR landing at 1128 lines (+2 margin) — two opt-in flat auto ids built on the established auto/best-free pattern (connectionBillingCatalog + subscriptionLadder pure functions). Frozen at merge size per owner-authorized rebaseline directive (2026-08-19, merge-batch Step 4); no further growth without split rationale."
}

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (350 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 85 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (353 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 85 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
<rect width="1200" height="350" fill="#0d1117"/>
@@ -6,7 +6,7 @@
<path d="M 0 34 L 1200 34" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
<circle cx="24" cy="17" r="6" fill="#ff5f56"/><circle cx="46" cy="17" r="6" fill="#ffbd2e"/><circle cx="68" cy="17" r="6" fill="#27c93f"/>
<text x="600" y="22" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#71717a">omniroute &#8212; 85 top-level commands</text>
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">&#8230; 346 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">&#8230; 347 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.315;0.33;1" dur="18s" repeatCount="indefinite"/>
<text x="40" y="66" fill="#22c55e">$</text>
<g clip-path="url(#tw0)"><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text></g>
@@ -14,7 +14,7 @@
<animate attributeName="x" calcMode="discrete" values="64;95;125;156;186;217;248;278;309;309" keyTimes="0.000;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.011;0.012;0.022;0.032;0.042;0.052;0.074;1" dur="18s" repeatCount="indefinite"/>
</rect>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>&#8230; 346 more providers</text>
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e&#160;&#160;anthropic&#160;&#160;&#160;Claude Max 20x&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a&#160;&#160;codex&#160;&#160;&#160;&#160;&#160;&#160;&#160;Codex Pro (team)&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b&#160;&#160;glm&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;GLM Coding Plan&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f&#160;&#160;kimi&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Kimi K2 free&#160;&#160;&#160;&#160;&#160;&#160;<tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>&#8230; 347 more providers</text>
</g><g opacity="0" font-family="Consolas, 'Courier New', monospace" font-size="17">
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.333;0.34800000000000003;0.648;0.663;1" dur="18s" repeatCount="indefinite"/>
<text x="40" y="66" fill="#22c55e">$</text>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 350 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 353 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
<defs>
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 350 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier and 56 recurring or keyless free-forever providers. Every tool works: 35 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 353 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier and 56 recurring or keyless free-forever providers. Every tool works: 35 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
<defs>
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -21,7 +21,7 @@
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
</g>
<g>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">350 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">353 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
</g>
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
@@ -38,7 +38,7 @@
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
</g>
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 350 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 353 providers in</text>
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 350 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 350 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 353 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 353 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
<defs>
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -28,7 +28,7 @@
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
<!-- subheadline -->
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">350 providers</tspan><tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">353 providers</tspan><tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
<!-- plug line -->
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity&#160;&#160;&#160;&#160;<tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -66,22 +66,22 @@ Cursor, Cline, and compatible MCP client setup.
## Essential Tools (13) — Phase 1
| Tool | Scopes | Description |
| :------------------------------ | :-------------------- | :------------------------------------------------------------ |
| `omniroute_get_health` | `read:health` | Uptime, memory, circuit breakers, rate limits, cache stats |
| `omniroute_list_combos` | `read:combos` | All configured combos with strategies (optional metrics) |
| `omniroute_get_combo_metrics` | `read:combos` | Performance metrics for a specific combo |
| `omniroute_switch_combo` | `write:combos` | Activate or deactivate a combo |
| `omniroute_create_combo` | `write:combos` | Create a validated combo through the existing combo API |
| `omniroute_check_quota` | `read:quota` | Quota used/total, percent remaining, reset time, token health |
| `omniroute_route_request` | `execute:completions` | Send a chat completion through OmniRoute routing |
| `omniroute_cost_report` | `read:usage` | Cost report by period (session/day/week/month) |
| `omniroute_list_models_catalog` | `read:models` | Full model catalog with capabilities, status, pricing |
| `omniroute_radar_catalog` | `read:radar` | Local signed Radar catalog; optional provider/family filters |
| `omniroute_tool_search` | `read:tools` | Discover tools from the registered MCP catalog |
| `omniroute_web_search` | `execute:search` | Web search through the configured search providers. Not X/Twitter. |
| `omniroute_x_search` | `execute:search` | Search X (Twitter) through SuperGrok / xAI server-side `x_search`. Requires `xai-oauth` or an xAI API key. Not the X Developer Platform MCP. |
| `omniroute_web_fetch` | `execute:search` | Fetch web content through the configured fetch providers |
| Tool | Scopes | Description |
| :------------------------------ | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
| `omniroute_get_health` | `read:health` | Uptime, memory, circuit breakers, rate limits, cache stats |
| `omniroute_list_combos` | `read:combos` | All configured combos with strategies (optional metrics) |
| `omniroute_get_combo_metrics` | `read:combos` | Performance metrics for a specific combo |
| `omniroute_switch_combo` | `write:combos` | Activate or deactivate a combo |
| `omniroute_create_combo` | `write:combos` | Create a validated combo through the existing combo API |
| `omniroute_check_quota` | `read:quota` | Quota used/total, percent remaining, reset time, token health |
| `omniroute_route_request` | `execute:completions` | Send a chat completion through OmniRoute routing |
| `omniroute_cost_report` | `read:usage` | Cost report by period (session/day/week/month) |
| `omniroute_list_models_catalog` | `read:models` | Full model catalog with capabilities, status, pricing |
| `omniroute_radar_catalog` | `read:radar` | Local signed Radar catalog; optional provider/family filters |
| `omniroute_tool_search` | `read:tools` | Discover tools from the registered MCP catalog |
| `omniroute_web_search` | `execute:search` | Web search through the configured search providers. Not X/Twitter. |
| `omniroute_x_search` | `execute:search` | Search X through xAI/SuperGrok, or choose `xquik-search` for Xquik API results. Requires credentials for the selected backend. |
| `omniroute_web_fetch` | `execute:search` | Fetch web content through the configured fetch providers |
## Advanced Tools (11) — Phase 2

View File

@@ -24,8 +24,11 @@ it expires.
| Antigravity / Devin Desktop | Built-in quotas |
**Strategy**: route here first for every request that fits the model's
strengths. Quota tracker monitors approaching reset; combo strategies
`reset-aware` and `subscription` prioritize accordingly.
strengths. The quota tracker monitors approaching resets, and the `reset-aware`
combo strategy prioritizes accordingly. To route Tier 1 first and only step out
to paid tiers as quota runs out, use the `auto/thrifty` id — or `auto/subscription`
to stay on plan-included capacity and fail closed instead. See
[Subscription-first routing](../routing/SUBSCRIPTION_LADDER.md).
## Tier 2 — Cheap

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -43,6 +43,8 @@ tags:
description: Content moderation
- name: Rerank
description: Document reranking
- name: Search
description: Unified web, news, and X search
- name: Models
description: Available model listing
- name: Providers
@@ -1153,6 +1155,199 @@ paths:
$ref: "#/components/responses/Unauthorized"
# ─── Proxy Endpoints ──────────────────────────────────────────
/api/v1/search:
get:
tags: [Search]
summary: List search providers
description: Lists configured search providers and their supported search types.
responses:
"200":
description: Search provider catalog
content:
application/json:
schema:
type: object
required: [object, data]
properties:
object:
type: string
const: list
data:
type: array
items:
type: object
required: [id, object, created, name, search_types]
properties:
id:
type: string
object:
type: string
const: search_provider
created:
type: integer
name:
type: string
search_types:
type: array
items:
type: string
enum: [web, news, x]
post:
tags: [Search]
summary: Run a unified search
description: >-
Searches the web, news, or X through a configured provider. Set `provider`
to `xquik-search` to use Xquik for X search. The aliases `xquik` and
`xquik_search` resolve to the same provider.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query]
properties:
query:
type: string
minLength: 1
maxLength: 500
provider:
type: string
minLength: 1
description: A search provider id or registered alias.
examples: [xquik-search]
max_results:
type: integer
minimum: 1
maximum: 100
default: 5
search_type:
type: string
enum: [web, news, x]
default: web
offset:
type: integer
minimum: 0
default: 0
country:
type: string
maxLength: 2
language:
type: string
minLength: 2
maxLength: 5
time_range:
type: string
enum: [any, hour, day, week, month, year]
content:
type: object
properties:
snippet: { type: boolean, default: true }
full_page: { type: boolean, default: false }
format: { type: string, enum: [text, markdown], default: text }
max_characters: { type: integer, minimum: 100, maximum: 100000 }
filters:
type: object
properties:
include_domains:
type: array
maxItems: 20
items: { type: string, maxLength: 253 }
exclude_domains:
type: array
maxItems: 20
items: { type: string, maxLength: 253 }
safe_search: { type: string, enum: [off, moderate, strict] }
provider_options:
type: object
additionalProperties: true
strict_filters:
type: boolean
default: false
additionalProperties: true
responses:
"200":
description: Normalized search results
content:
application/json:
schema:
type: object
required: [id, provider, query, results, answer, usage, metrics, errors, cached]
properties:
id:
type: string
pattern: ^search-
provider:
type: string
query:
type: string
cached:
type: boolean
results:
type: array
items:
type: object
required: [title, url, snippet, position, citation]
properties:
title: { type: string }
url: { type: string, format: uri }
display_url: { type: string }
snippet: { type: string }
position: { type: integer, minimum: 1 }
score:
type: [number, "null"]
minimum: 0
maximum: 1
published_at: { type: [string, "null"] }
favicon_url: { type: [string, "null"], format: uri }
citation:
type: object
required: [provider, retrieved_at, rank]
properties:
provider: { type: string }
retrieved_at: { type: string, format: date-time }
rank: { type: integer, minimum: 1 }
answer:
type: [object, "null"]
usage:
type: object
required: [queries_used, search_cost_usd]
properties:
queries_used: { type: integer, minimum: 0 }
search_cost_usd: { type: number, minimum: 0 }
llm_tokens: { type: integer, minimum: 0 }
metrics:
type: object
required: [response_time_ms, upstream_latency_ms, total_results_available]
properties:
response_time_ms: { type: number, minimum: 0 }
upstream_latency_ms: { type: number, minimum: 0 }
gateway_latency_ms: { type: number, minimum: 0 }
total_results_available: { type: [integer, "null"], minimum: 0 }
errors:
type: array
items:
type: object
required: [provider, code, message]
properties:
provider: { type: string }
code: { type: string }
message: { type: string }
"400":
description: Invalid request, provider, credentials, or search type
"401":
$ref: "#/components/responses/Unauthorized"
"403":
description: Search provider blocked by API key or security policy
"429":
description: Every eligible provider credential is rate limited
"500":
$ref: "#/components/responses/InternalError"
"502":
description: Search provider failed
/api/v1/chat/completions:
post:
tags: [Chat]

View File

@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.50
lastUpdated: 2026-08-23
version: 3.8.51
lastUpdated: 2026-08-25
---
# Provider Reference
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
> Regenerate with: `npm run gen:provider-reference`
> **Last generated:** 2026-08-23
> **Last generated:** 2026-08-25
Total providers: **350**. See category breakdown below.
Total providers: **353**. See category breakdown below.
## Categories
@@ -122,7 +122,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — |
| `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — |
## API Key Providers (paid / paid-with-free-credits) (231)
## API Key Providers (paid / paid-with-free-credits) (233)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
@@ -342,6 +342,8 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `vertex-partner` | `vp` | Vertex AI Partners | API key, enterprise | [link](https://cloud.google.com/vertex-ai) | Provide the same Service Account JSON used for Vertex AI partner models. |
| `void-ai` | `void-ai` | Void AI | API key, aggregator | [link](https://voidai.app) | The public model catalog marks some models with a free plan requirement, but access is conditional and no numeric quota is confirmed. |
| `volcengine` | `volcengine` | Volcengine | API key | [link](https://www.volcengine.com) | — |
| `volcengine-agent-plan` | `veap` | Volcengine Ark Agent Plan | API key | [link](https://console.volcengine.com/ark/region:cn-beijing/subscription/agent-plan) | Connect your Volcano Engine account or use an Ark Agent Plan subscription API key. |
| `volcengine-coding-plan` | `vecp` | Volcengine Ark Coding Plan | API key | [link](https://console.volcengine.com/ark/region:cn-beijing/subscription/coding-plan) | Connect your Volcano Engine account or use an Ark Coding Plan subscription API key. |
| `voyage-ai` | `voyage` | Voyage AI | API key, embed/rerank | [link](https://www.voyageai.com) | Bearer API key for Voyage AI embeddings and rerank APIs. |
| `wafer` | `wafer` | Wafer AI | API key | [link](https://wafer.ai) | — |
| `wandb` | `wandb` | Weights & Biases Inference | API key | [link](https://wandb.ai) | — |
@@ -377,7 +379,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `vllm` | `vllm` | vLLM | Local, self-hosted | [link](https://github.com/vllm-project/vllm) | API key optional. Configure the local vLLM OpenAI-compatible base URL (default: http://localhost:8000/v1). |
| `xinference` | `xinference` | XInference | Local, self-hosted | [link](https://inference.readthedocs.io) | API key optional. Configure the local XInference OpenAI-compatible base URL (default: http://localhost:9997/v1). |
## Search Providers (14)
## Search Providers (15)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
@@ -394,6 +396,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `serper-search` | `serper-search` | Serper Search | Search | [link](https://serper.dev) | API key from serper.dev dashboard |
| `tavily-search` | `tavily-search` | Tavily Search | Search | [link](https://tavily.com) | API key from app.tavily.com (format: tvly-...) |
| `x-search` | `x_search` | X Search (Grok) | Search | [link](https://docs.x.ai/developers/tools/x-search) | SuperGrok OAuth (xai-oauth) or xAI API key. This is Grok X Search, not the X Developer MCP. |
| `xquik-search` | `xquik` | Xquik X Search | Search | [link](https://docs.xquik.com) | Xquik API key (xq_...). Search is metered per returned post; the catalog estimate uses 5 results. |
| `youcom-search` | `youcom-search` | You.com Search | Search | [link](https://you.com/business/api/) | X-API-Key from the You.com platform dashboard |
## Audio-only Providers (12)
@@ -438,7 +441,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (108 implementations)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (109 implementations)
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
## See Also

View File

@@ -0,0 +1,182 @@
---
title: "Subscription-first routing"
version: 3.8.50
lastUpdated: 2026-08-22
---
# Subscription-first routing
> Two new `auto/*` ids — `auto/subscription` and `auto/thrifty`. Both are opt-in by being
> requested: nothing routes through them unless a caller asks for the id by name, and no
> existing pool, strategy, or default changes.
## Why this exists
OmniRoute already answers two cost questions, and neither is the one most operators ask.
| Existing mechanism | Answers |
| -------------------------------------------------------- | ----------------------------------- |
| `hidePaidModels` (`autoCombo/paidModelFilter.ts`) | "is this model catalogued free?" |
| `freeAccessPolicy: "strict"` (`strictZeroCostFilter.ts`) | "can this connection ever bill me?" |
| `quotaPreflight` (`combo/quotaExhaustionCutoff.ts`) | "is this connection near its wall?" |
| `budgetCap` / `budgetFallback` (`autoCombo/engine.ts`) | "cap spend, degrade to cheapest" |
Every free-only mechanism **fails closed** — an exhausted free pool is an empty pool, never a
step up to a paid option — and every paid-side mechanism is tier-agnostic. Neither answers:
> "Use the quota I already pay for. When it runs out, either stop, or step up one rung at a
> time through the cheapest paid options — and come back the moment it resets."
## Billing is a connection fact, not a model fact
`classifyTier()` (`open-sse/services/tierResolver.ts`) keys on `(provider, model)` and returns
`free | cheap | premium` from catalog pricing. But whether a request costs incremental money
depends on **which connection serves it**: the same model is plan-included through a Claude Code
OAuth connection and billed per token through an API-key connection.
`provider_connections.auth_type` is not a safe proxy in either direction — metered OAuth
connections exist, and plan-included API-key connections exist (a Copilot seat token is not a
metered API key). So billing class comes from a **curated catalog**,
`open-sse/config/connectionBillingCatalog.ts`, hand-set from each provider's published terms —
the same pattern `FreeModelBudget.hardStopGuaranteed` already established for free models.
```ts
type ConnectionBillingClass = "subscription" | "metered" | "keyless" | "unknown";
type ConnectionOverageBehavior = "hard-stop" | "meters-to-paid" | "unknown";
```
Resolution order (`autoCombo/connectionBilling.ts`): the synthetic no-auth sentinel →
`keyless`; a catalog entry matching provider **and** `authType`; a provider-wide entry;
otherwise `unknown`. **Uncurated is not free**`unknown` is consumed as `metered`
everywhere, so a provider added tomorrow starts outside the subscription rung and has to be
curated in deliberately.
## The rung model
Five rungs in escalation order. They differ in more than price — each has its **own**
exhaustion signal, which is why this is not merely a sort.
| # | Rung | Membership | Exhausted when |
| --- | -------------- | -------------------------------------------------- | ----------------------------- |
| 0 | `subscription` | curated `billing: "subscription"` | quota window at/below cutoff |
| 1 | `keyless` | the synthetic no-auth path | connection cooldown / breaker |
| 2 | `free` | metered connection, `classifyTier() === "free"` | free allowance exhausted |
| 3 | `cheap` | metered connection, `classifyTier() === "cheap"` | per-rung budget consumed |
| 4 | `premium` | metered connection, `classifyTier() === "premium"` | per-rung budget consumed |
Rungs 0-2 exhaust on **quota**, which is observable and already tracked. Rungs 3-4 have no
quota — a paid connection serves forever — so their only sane exhaustion signal is a per-rung
**budget**. Without one, "escalate when cheap is exhausted" has no trigger.
## `auto/subscription` — fail closed
Pool = rung 0 only, restricted to connections whose overage is a documented `hard-stop`, each
verified live to have quota headroom. Everything ambiguous is excluded: an uncurated provider,
an unverifiable quota reading, a stale reading, or an overage that meters to paid.
An empty pool is the **intended** answer, not a defect — the caller's existing empty-pool path
turns it into a clear error rather than a silent, billable fallback. That is the whole promise
of the id.
`keyless` deliberately does **not** qualify: this grouping means "the plan I pay for", so a
no-auth backend does not belong in it. Use `auto/thrifty` (or `auto/best-free`) for that.
### Connection safety
A candidate is not always tied to one connection — a logical candidate carries an
`allowedConnectionIds` allowlist, and the account actually used is chosen later, at dispatch,
by `open-sse/services/combo/autoStrategy.ts`. Both groupings therefore verify **each connection
individually** and rewrite `allowedConnectionIds` down to exactly the surviving subset — never
the full original list, never one arbitrarily-chosen member. Because `autoStrategy.ts` already
enforces that array as a hard allowlist, rewriting it here makes "verified" and "actually used"
the same set by construction. This is the same invariant, and the same reasoning, as
[STRICT_ZERO_COST](./STRICT_ZERO_COST.md).
## `auto/thrifty` — escalate one rung at a time
Pool = all rungs, ordered by rung index, with exhausted candidates gated out. The `auto` engine
still scores **within** the surviving pool: the ladder decides which rungs are in play, scoring
decides which candidate wins inside them. Ordering is stable within a rung, so the scorer's own
ranking is never reshuffled by this overlay.
This is an ordering + gating overlay, **not** a new dispatcher: `combo.ts`'s speculative loop
already walks targets in order and falls through on failure, so a runtime exhaustion the
preflight did not catch still escalates to the next rung inside the same request.
Where `auto/subscription` fails closed, `auto/thrifty` fails **open**: a plan-included
connection with no usable quota reading is still tried first. Trying it costs nothing, and if
it turns out to be exhausted the fall-through reaches the next rung anyway — whereas refusing
to try it would send the request to a paid rung on missing telemetry, the exact outcome the
grouping exists to avoid.
## Returning to the plan after a reset
Three independent things must expire before routing returns to rung 0. Fixing only one leaves
the ladder stuck on paid rungs long after the plan refilled.
1. **The quota-state cache**`freeAccessQuota.ts` caches per `(provider, connection)` with a
180s TTL. A cached entry whose own `resetAt` has already passed describes a window that no
longer exists, so it is now treated as stale **regardless of age** and forces a refresh.
Without this, a plan that refilled at midnight keeps reading exhausted until the TTL happens
to lapse.
2. **The ladder's own state** — there is none, by design. Rung eligibility is recomputed from
live quota state on every pool build; no persisted "currently on rung 3" record exists that
could outlive a reset and wedge routing.
3. **The connection cooldown** — the exhausting 429 sets `rateLimitedUntil` from exponential
backoff, which for a plan connection can overshoot the real reset. `clampCooldownToReset()`
(`subscriptionLadder.ts`) narrows a cooldown to the upstream's own reset instant and can
never extend one. **It is implemented and tested but not yet wired**: the quota cache is
invalidated in `src/sse/services/auth.ts` _before_ any cooldown is written, so `resetAt`
must be captured earlier in that function — a change to the resilience hot path that
belongs in its own reviewed PR. Until then, re-entry waits out the connection cooldown
(which already prefers upstream `Retry-After` hints when the provider sends them).
### Anti-flap
A rung that just reset is re-admitted only above `reentryMinRemainingPercent` (default 5),
while a connection already in play only has to stay above `exitCutoffPercent` (default 2,
matching `quotaPreflight.defaultThresholdPercent`). The gap is the hysteresis band — without
it, a connection hovering at the cutoff oscillates between rungs on consecutive requests.
## Configuration
Tuning only. There is deliberately **no** `enabled` flag: a toggle able to switch these off
would leave `auto/subscription` quietly serving the full pool — paid models included — under a
name that promises the opposite.
```jsonc
{
"subscriptionLadder": {
"exitCutoffPercent": 2,
"reentryMinRemainingPercent": 5,
"rungBudgetUsd": { "cheap": 5.0, "premium": 0 }, // 0 disables a rung outright
},
}
```
Budget gating is inert until a spend resolver is wired: with no accounting available a paid
rung is ordered but never gated. As of v3.8.51 the `rungBudgetUsd` setting is accepted by the
schema but NOT yet enforced — treat it as reserved configuration, not an active spend cap. Rung ordering, quota-based exhaustion, and reset re-entry all
work without it.
## Composition
`subscription` and `thrifty` are `AutoTier` values, so they compose with every category:
`auto/coding:thrifty`, `auto/reasoning:subscription`, and so on. The two flat ids
(`auto/subscription`, `auto/thrifty`) are advertised in `/v1/models` and the dashboard.
Neither id is paid-tier, so `isPaidTierAutoId()` returns `false` for both and
`auto/subscription` survives `hidePaidModels`.
## Where the code lives
| Concern | File |
| ------------------------------- | --------------------------------------------------- |
| Curated billing facts | `open-sse/config/connectionBillingCatalog.ts` |
| Classifier | `open-sse/services/autoCombo/connectionBilling.ts` |
| Rungs, both groupings, re-entry | `open-sse/services/autoCombo/subscriptionLadder.ts` |
| Wiring into the candidate pool | `open-sse/services/autoCombo/virtualFactory.ts` |
| Reset-aware cache staleness | `open-sse/services/autoCombo/freeAccessQuota.ts` |
| Tier surface | `open-sse/services/autoCombo/suffixComposition.ts` |
| Advertised ids | `open-sse/services/autoCombo/builtinCatalog.ts` |
| Tests | `tests/unit/autoCombo/subscription-ladder.test.ts` |

View File

@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **353 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free
@@ -475,7 +475,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **350-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -0,0 +1,143 @@
/**
* Curated billing classification for provider CONNECTIONS.
*
* The economic tier resolver (`open-sse/services/tierResolver.ts`) answers
* "how much does this MODEL cost per token?" — a fact about the catalog. It
* cannot answer the question subscription-first routing actually needs:
*
* "does serving this request through THIS connection cost incremental money,
* or is it already covered by a flat-rate plan the operator pays anyway?"
*
* That is a property of the credential, not the model: `claude/claude-*` is
* plan-included through a Claude Code OAuth connection and billed per token
* through an API-key connection — same provider, same model, opposite
* economics.
*
* `provider_connections.auth_type` alone is NOT a safe proxy in either
* direction: metered OAuth connections exist (cloud-billed OAuth), and
* plan-included API-key connections exist (Copilot seat tokens). So this is a
* CURATED table, hand-set from each provider's published terms — deliberately
* the same pattern `FreeModelBudget.hardStopGuaranteed`
* (`open-sse/config/freeModelCatalog.ts`) already established: a fact about
* the upstream's commercial terms, never derived from `authType` and never
* inferred from a live API response.
*
* Uncurated is not "free": anything absent here resolves to `unknown`, which
* every consumer treats as `metered`. New providers therefore start OUTSIDE
* the subscription rung and have to be curated in deliberately — the same
* fail-safe direction STRICT_ZERO_COST uses for uncatalogued models.
*/
/**
* How a connection's upstream charges for the requests it serves.
*
* - `subscription` — covered by a flat-rate plan the operator already pays.
* Consuming its quota costs nothing incremental; the plan is a sunk cost.
* - `metered` — pay-per-token / pay-per-credit. Every request adds spend.
* - `keyless` — no credential exists at all, so no request can be billed by
* construction (the synthetic no-auth path).
* - `unknown` — not curated. Consumed as `metered` everywhere.
*/
export type ConnectionBillingClass = "subscription" | "metered" | "keyless" | "unknown";
/**
* What happens when a subscription plan's allowance runs out.
*
* - `hard-stop` — the upstream refuses further requests until the window
* resets. Exhaustion cannot cost money, so such a connection is admissible
* to the strictest "never spend a cent extra" grouping.
* - `meters-to-paid` — the upstream keeps serving and bills the overage.
* Perfectly usable while quota remains, never admissible to the strict
* grouping.
* - `unknown` — not established. Treated exactly like `meters-to-paid` by
* every consumer; it is the conservative default for a provider whose terms
* allow an operator to opt into usage-based billing past the plan.
*/
export type ConnectionOverageBehavior = "hard-stop" | "meters-to-paid" | "unknown";
export interface ConnectionBillingEntry {
/** Provider id as registered in `open-sse/config/providers/registry/`. */
provider: string;
/**
* Restricts the entry to connections whose `authType` matches. Omit for a
* provider-wide entry. A matching auth-typed entry always wins over the
* provider-wide one, so a provider offering both a plan-included OAuth login
* and a metered API key can declare both.
*/
authType?: string;
billing: ConnectionBillingClass;
overage: ConnectionOverageBehavior;
/** Operator-visible justification for the classification. */
reason: string;
}
/**
* Curated entries. Conservative by design — a provider whose terms let the
* operator enable usage-based billing past the plan is recorded as `unknown`
* overage, not `hard-stop`, because the strict grouping's entire promise is
* that it cannot surprise you.
*
* Providers already classified free by the economic tier resolver (`kiro`,
* `qoder`, and the rest of `LEGACY_FREE_PROVIDERS` /
* `deriveNoAuthFreeProviders()` in `open-sse/services/tierConfig.ts`) are
* deliberately NOT listed here: they land on the ladder's `free` rung through
* `classifyTier()` and would only be double-claimed by an entry here.
*/
export const CONNECTION_BILLING_CATALOG: readonly ConnectionBillingEntry[] = [
{
provider: "claude",
authType: "oauth",
billing: "subscription",
overage: "hard-stop",
reason:
"Claude Code OAuth serves the operator's Anthropic Pro/Max plan windows. " +
"Exceeding a window is refused until it resets; no per-token charge accrues.",
},
{
provider: "codex",
authType: "oauth",
billing: "subscription",
overage: "hard-stop",
reason:
"Codex OAuth serves the ChatGPT plan's included Codex quota. Exhaustion is " +
"refused until the plan window resets rather than billed.",
},
{
provider: "antigravity",
authType: "oauth",
billing: "subscription",
overage: "hard-stop",
reason:
"Antigravity OAuth serves built-in plan quotas that stop serving once consumed; " +
"OmniRoute already tracks their reset windows (see antigravityCredits.ts).",
},
{
provider: "cursor",
authType: "oauth",
billing: "subscription",
overage: "unknown",
reason:
"Cursor Pro includes a request allowance, but usage-based pricing past the plan " +
"can be enabled per account and OmniRoute cannot observe that setting. Recorded " +
"as unknown overage so the strict grouping excludes it.",
},
{
provider: "copilot-web",
authType: "apikey",
billing: "subscription",
overage: "unknown",
reason:
"GitHub Copilot is a per-seat subscription (the credential is a seat token, not a " +
"metered API key), but additional premium requests can be billed when the account " +
"opts in. Recorded as unknown overage.",
},
{
provider: "devin-desktop",
authType: "oauth",
billing: "subscription",
overage: "meters-to-paid",
reason:
"Devin Desktop draws on the plan's included ACUs and continues billing past them, " +
"so it is plan-included while quota remains but never overage-safe.",
},
];

View File

@@ -69,6 +69,31 @@ export interface FreeModelTotals {
const RECURRING = new Set<FreeModelFreeType>(["recurring-daily", "recurring-monthly", "keyless"]);
/**
* What each free-tier regime engages for "can I route here without paying?".
* Exhaustive by construction: adding a member to `FreeModelFreeType` will not
* compile until it is classified here. `discontinued` is the one regime a
* provider uses to retire a free tier behind a paid key — it does NOT grant
* free access, and the shared predicate (`isFreeModel`) must read this instead
* of treating every catalogued id as free. `RECURRING` (above) answers a
* different question (which regimes feed the headline token totals) and is left
* independent on purpose — deriving it from this table would silently change
* the homepage totals.
*/
const FREE_REGIME_TRAITS = {
"recurring-daily": { grantsFreeAccess: true },
"recurring-monthly": { grantsFreeAccess: true },
"recurring-credit": { grantsFreeAccess: true },
"recurring-uncapped": { grantsFreeAccess: true },
"one-time-initial": { grantsFreeAccess: true },
keyless: { grantsFreeAccess: true },
discontinued: { grantsFreeAccess: false },
} satisfies Record<FreeModelFreeType, { grantsFreeAccess: boolean }>;
export function grantsFreeAccess(freeType: FreeModelFreeType): boolean {
return FREE_REGIME_TRAITS[freeType].grantsFreeAccess;
}
/**
* Deposit-unlock boosts: a one-time small top-up that permanently raises a
* provider's recurring free quota. Kept OUT of the steady headline and surfaced

View File

@@ -330,6 +330,25 @@ export const SEARCH_PROVIDERS: Record<string, SearchProviderConfig> = {
timeoutMs: 60_000,
cacheTTLMs: 5 * 60 * 1000,
},
// Direct X API search through Xquik. Keep it fallback-only so the existing
// SuperGrok provider remains the default for search_type "x".
"xquik-search": {
id: "xquik-search",
name: "Xquik X Search",
baseUrl: "https://xquik.com/api/v1/x/tweets/search",
method: "GET",
authType: "apikey",
authHeader: "x-api-key",
costPerQuery: 0.00075,
freeMonthlyQuota: 0,
searchTypes: ["x"],
defaultMaxResults: 5,
maxMaxResults: 20,
timeoutMs: 15_000,
cacheTTLMs: 5 * 60 * 1000,
fallbackOnly: true,
},
};
/**
@@ -377,6 +396,8 @@ export const SEARCH_PROVIDER_ALIASES: Record<string, string> = {
c7: "context7",
x_search: "x-search",
x: "x-search",
xquik: "xquik-search",
xquik_search: "xquik-search",
};
export function resolveSearchProviderId(providerId: string): string {

View File

@@ -8,6 +8,7 @@ import { randomUUID } from "crypto";
* firecrawl, google-pse-search, linkup-search, searchapi-search,
* youcom-search, searxng-search, ollama-search, zai-search, jina-search,
* duckduckgo-free, x-search (Grok / SuperGrok X Search — explicit or search_type "x")
* and xquik-search (direct X API search — explicit or credentialed fallback)
*
* Request format:
* {
@@ -28,6 +29,7 @@ import * as fcSearch from "./search/firecrawlSearch.ts";
import { type FirecrawlSearchEnvelope } from "./search/firecrawlSearch.ts";
import { buildJinaSearchRequest, extractJinaSearchItems } from "./search/jinaSearch.ts";
import * as xSearch from "./search/xSearch.ts";
import * as xquikSearch from "./search/xquikSearch.ts";
import { freeWebSearch } from "../services/freeWebSearch.ts";
import { saveCallLog } from "@/lib/usageDb";
import { safeOutboundFetch } from "@/shared/network/safeOutboundFetch";
@@ -714,6 +716,7 @@ const requestBuilders: Record<string, SearchRequestBuilder> = {
"ollama-search": buildOllamaRequest,
"jina-search": buildJinaSearchRequest,
"x-search": xSearch.buildXSearchRequest,
"xquik-search": xquikSearch.buildXquikSearchRequest,
};
function buildRequest(
@@ -1290,6 +1293,7 @@ const responseNormalizers: Record<string, SearchResponseNormalizer> = {
"ollama-search": normalizeOllamaResponse,
"jina-search": normalizeJinaSearchResponse,
"x-search": normalizeXSearchResponse,
"xquik-search": (data) => xquikSearch.normalizeXquikSearchResponse(data, makeResult),
};
function normalizeResponse(

View File

@@ -0,0 +1,158 @@
/** Xquik-backed X search for the unified search gateway. */
import { z } from "zod";
import type { SearchProviderConfig } from "../../config/searchRegistry.ts";
import type { SearchResult } from "../search.ts";
export const XQUIK_SEARCH_PROVIDER_ID = "xquik-search";
export interface XquikSearchParams {
query: string;
maxResults: number;
token?: string;
timeRange?: string;
providerOptions?: Record<string, unknown>;
providerSpecificData?: Record<string, unknown>;
}
export interface XquikSearchHit {
title: string;
url: string;
snippet: string;
author?: string;
publishedAt?: string;
}
type MakeResult = (
providerId: string,
item: {
title?: string;
url?: string;
snippet?: string;
published_at?: string;
author?: string;
source_type?: string;
},
index: number,
now: string
) => SearchResult;
const X_HANDLE_RE = /^[A-Za-z0-9_]{1,15}$/;
const TWEET_ID_RE = /^\d+$/;
const XquikTweetSchema = z
.object({
id: z.string().regex(TWEET_ID_RE),
text: z.string(),
createdAt: z.string().optional(),
author: z
.object({
username: z.string().regex(X_HANDLE_RE),
name: z.string().optional(),
})
.passthrough()
.optional(),
})
.passthrough();
const XquikSearchEnvelopeSchema = z
.object({
tweets: z.array(z.unknown()).default([]),
})
.passthrough();
function getProviderSettingString(
params: Pick<XquikSearchParams, "providerOptions" | "providerSpecificData">,
key: string
): string | undefined {
const option = params.providerOptions?.[key];
if (typeof option === "string" && option.trim()) return option.trim();
const configured = params.providerSpecificData?.[key];
if (typeof configured === "string" && configured.trim()) return configured.trim();
return undefined;
}
function sinceTimeForRange(timeRange: string | undefined, now = Date.now()): string | undefined {
const hour = 60 * 60 * 1000;
const durations: Record<string, number> = {
hour,
day: 24 * hour,
week: 7 * 24 * hour,
month: 30 * 24 * hour,
year: 365 * 24 * hour,
};
const duration = timeRange ? durations[timeRange] : undefined;
return duration ? new Date(now - duration).toISOString() : undefined;
}
export function buildXquikSearchRequest(
config: SearchProviderConfig,
params: XquikSearchParams
): { url: string; init: RequestInit } {
const queryType = getProviderSettingString(params, "queryType") === "Top" ? "Top" : "Latest";
const query = new URLSearchParams({
q: params.query,
queryType,
limit: String(params.maxResults),
});
const sinceTime = sinceTimeForRange(params.timeRange);
if (sinceTime) query.set("sinceTime", sinceTime);
return {
url: `${config.baseUrl.replace(/\/+$/, "")}?${query}`,
init: {
method: "GET",
headers: {
Accept: "application/json",
...(params.token ? { "x-api-key": params.token } : {}),
},
},
};
}
export function extractXquikSearchHits(data: unknown, maxResults: number): XquikSearchHit[] {
const envelope = XquikSearchEnvelopeSchema.safeParse(data);
if (!envelope.success) return [];
const hits: XquikSearchHit[] = [];
for (const value of envelope.data.tweets) {
const parsed = XquikTweetSchema.safeParse(value);
if (!parsed.success) continue;
const tweet = parsed.data;
const author = tweet.author?.username;
hits.push({
title: author ? `@${author}` : "X post",
url: author
? `https://x.com/${author}/status/${tweet.id}`
: `https://x.com/i/status/${tweet.id}`,
snippet: tweet.text,
author,
publishedAt: tweet.createdAt,
});
if (hits.length >= maxResults) break;
}
return hits;
}
export function normalizeXquikSearchResponse(
data: unknown,
makeResult: MakeResult
): { results: SearchResult[]; totalResults: number } {
const now = new Date().toISOString();
const results = extractXquikSearchHits(data, 20).map((hit, index) =>
makeResult(
XQUIK_SEARCH_PROVIDER_ID,
{
title: hit.title,
url: hit.url,
snippet: hit.snippet,
published_at: hit.publishedAt,
author: hit.author,
source_type: "x",
},
index,
now
)
);
return { results, totalResults: results.length };
}

View File

@@ -358,6 +358,38 @@ describe("omniroute_x_search handler (via MCP dispatch)", () => {
expect(body.search_type).toBe("x");
expect(body.provider).toBe("x-search");
});
it("should route an explicit Xquik search through xquik-search", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
id: "xs2",
provider: "xquik-search",
query: "agents sdk",
results: [
{
title: "@openai",
url: "https://x.com/openai/status/1912345678901234567",
snippet: "Agents SDK update",
position: 1,
},
],
cached: false,
usage: { queries_used: 1, search_cost_usd: 0.00015 },
}),
});
const result = await client.callTool({
name: "omniroute_x_search",
arguments: { query: "agents sdk", max_results: 5, provider: "xquik-search" },
});
expect(result.isError).toBeFalsy();
const [, options] = mockFetch.mock.calls[0];
const body = JSON.parse(options.body as string);
expect(body.search_type).toBe("x");
expect(body.provider).toBe("xquik-search");
});
});
// ── omniroute_get_health: handler dispatch tests ──────────────────────────────

View File

@@ -531,12 +531,17 @@ export const xSearchInput = z.object({
.max(20)
.default(5)
.describe("Maximum number of X results to return"),
provider: z
.enum(["x-search", "xquik-search"])
.optional()
.default("x-search")
.describe("X search backend: x-search uses xAI/SuperGrok; xquik-search uses Xquik"),
});
export const xSearchTool: McpToolDefinition<typeof xSearchInput, typeof webSearchOutput> = {
name: "omniroute_x_search",
description:
"Search X (Twitter) through OmniRoute using SuperGrok / xAI server-side x_search. Requires a connected xai-oauth (SuperGrok) or xAI API key. This is Grok X Search, not web search and not the X Developer Platform MCP.",
"Search X (Twitter) through OmniRoute. Uses SuperGrok / xAI server-side x_search by default, or Xquik when provider is xquik-search. Requires credentials for the selected backend. This is not web search.",
inputSchema: xSearchInput,
outputSchema: webSearchOutput,
scopes: ["execute:search"],

View File

@@ -666,7 +666,11 @@ async function handleWebSearch(args: {
}
}
async function handleXSearch(args: { query: string; max_results?: number }) {
async function handleXSearch(args: {
query: string;
max_results?: number;
provider?: "x-search" | "xquik-search";
}) {
const start = Date.now();
try {
const result = await omniRouteFetch("/v1/search", {
@@ -675,7 +679,7 @@ async function handleXSearch(args: { query: string; max_results?: number }) {
query: args.query,
max_results: args.max_results ?? 5,
search_type: "x",
provider: "x-search",
provider: args.provider ?? "x-search",
}),
signal: AbortSignal.timeout(120000),
});

View File

@@ -44,6 +44,12 @@ export const AUTO_TEMPLATE_VARIANTS: Record<string, AutoVariant | undefined> = {
"auto/claude-opus": "smart",
"auto/claude-sonnet": "coding",
"auto/best-free": "cheap",
// Subscription-first routing (see `subscriptionLadder.ts`). `auto/subscription`
// maps to no weight variant on purpose: its pool is already restricted to
// plan-included connections, so the scorer should rank them on merit rather
// than bias toward cheap/fast within an allowance the operator already paid for.
"auto/subscription": undefined,
"auto/thrifty": "cheap",
// Chaos mode — parallel dispatch to top-N stable models
"auto/best-chaos": "chaos",
"auto/chaos": "chaos",
@@ -67,6 +73,18 @@ export const AUTO_SUFFIX_VARIANTS: string[] = [
"auto/multimodal",
];
/**
* Flat `auto/*` ids that carry a tier overlay even though they are not written
* in `<category>:<tier>` form. `auto/best-free` established the pattern; the
* two subscription-first ids reuse it so a caller can ask for the behavior
* without also having to pick a category.
*/
export const FLAT_TIER_OVERLAY_IDS: Record<string, AutoTier> = {
"auto/best-free": "free",
"auto/subscription": "subscription",
"auto/thrifty": "thrifty",
};
type ResolvedAutoVariant =
{ recognized: true; variant: AutoVariant | undefined } | { recognized: false };
@@ -199,8 +217,9 @@ export async function createBuiltinAutoCombo(
}
if ("variant" in spec && spec.variant !== undefined) {
const overlayTier = FLAT_TIER_OVERLAY_IDS[modelStr];
const virtualCombo = await materialize(spec.variant, {
...(modelStr === "auto/best-free" ? { tier: "free" as const } : {}),
...(overlayTier ? { tier: overlayTier } : {}),
});
virtualCombo.name = modelStr;
virtualCombo.id = modelStr;
@@ -211,7 +230,11 @@ export async function createBuiltinAutoCombo(
// auto/best-chat, auto/pro-chat) still materialize via the default
// (unconstrained) virtual combo rather than throwing "Unknown built-in".
if (Object.prototype.hasOwnProperty.call(AUTO_TEMPLATE_VARIANTS, modelStr)) {
const virtualCombo = await materialize(undefined);
const overlayTier = FLAT_TIER_OVERLAY_IDS[modelStr];
const virtualCombo = await materialize(
undefined,
overlayTier ? { tier: overlayTier } : undefined
);
virtualCombo.name = modelStr;
virtualCombo.id = modelStr;
return virtualCombo;

View File

@@ -0,0 +1,100 @@
/**
* Pure classifier over the curated connection-billing catalog
* (`open-sse/config/connectionBillingCatalog.ts`).
*
* Kept dependency-light on purpose — the same constraint `paidModelFilter.ts`
* and `strictZeroCostFilter.ts` state in their own headers — so subscription
* routing is unit-testable without seeding the DB or the virtual factory. No
* provider name appears in this file: a connection is classified purely from
* the catalog plus the two facts the caller already has (`provider`,
* `authType`), so curating a new provider needs no code change here.
*/
import {
CONNECTION_BILLING_CATALOG,
type ConnectionBillingClass,
type ConnectionBillingEntry,
type ConnectionOverageBehavior,
} from "@omniroute/open-sse/config/connectionBillingCatalog.ts";
import { SYNTHETIC_NOAUTH_CONNECTION_ID } from "./resilienceCandidateFilter";
/** The minimum a caller must know about a connection to classify it. */
export interface BillableConnection {
provider: string;
/** `provider_connections.auth_type` — `oauth` / `apikey` / `cookie` / … */
authType?: string | null;
/** Connection id; the synthetic no-auth sentinel classifies as `keyless`. */
connectionId?: string | null;
}
export interface ConnectionBillingVerdict {
billing: ConnectionBillingClass;
overage: ConnectionOverageBehavior;
reason: string;
}
const UNKNOWN_VERDICT: ConnectionBillingVerdict = {
billing: "unknown",
overage: "unknown",
reason: "No curated billing entry for this provider/authType — assumed metered.",
};
const KEYLESS_VERDICT: ConnectionBillingVerdict = {
billing: "keyless",
overage: "hard-stop",
reason:
"Synthetic no-auth connection: no credential exists, so no request against it can be billed.",
};
/**
* Classify one connection.
*
* Resolution order, first match wins:
* 1. the synthetic no-auth sentinel → `keyless` (no credential can be billed);
* 2. a catalog entry matching BOTH provider and `authType`;
* 3. a provider-wide catalog entry (no `authType` declared);
* 4. otherwise `unknown`.
*
* `unknown` is never treated as free by any caller — `isPlanIncluded()` below
* returns false for it, so an uncurated provider stays outside the
* subscription rung until someone curates it deliberately.
*/
export function classifyConnectionBilling(
connection: BillableConnection,
catalog: readonly ConnectionBillingEntry[] = CONNECTION_BILLING_CATALOG
): ConnectionBillingVerdict {
if (connection.connectionId === SYNTHETIC_NOAUTH_CONNECTION_ID) return KEYLESS_VERDICT;
const provider = connection.provider;
if (!provider) return UNKNOWN_VERDICT;
const providerEntries = catalog.filter((entry) => entry.provider === provider);
if (providerEntries.length === 0) return UNKNOWN_VERDICT;
const authType = typeof connection.authType === "string" ? connection.authType : null;
const authMatch = authType
? providerEntries.find((entry) => entry.authType === authType)
: undefined;
const entry = authMatch ?? providerEntries.find((entry) => entry.authType === undefined);
if (!entry) return UNKNOWN_VERDICT;
return { billing: entry.billing, overage: entry.overage, reason: entry.reason };
}
/**
* True when serving a request through this connection consumes an allowance
* the operator already pays for, rather than adding incremental spend.
* `keyless` qualifies: it costs nothing by construction.
*/
export function isPlanIncluded(verdict: ConnectionBillingVerdict): boolean {
return verdict.billing === "subscription" || verdict.billing === "keyless";
}
/**
* True when exhausting this connection's allowance cannot start costing money.
* The strict `auto/subscription` grouping admits nothing else: an operator who
* asked never to spend extra must not be surprised by a provider that meters
* past the plan, nor by one whose terms simply are not established.
*/
export function isOverageSafe(verdict: ConnectionBillingVerdict): boolean {
return verdict.overage === "hard-stop";
}

View File

@@ -22,6 +22,7 @@ import {
import { getCachedProviderConnections } from "@/lib/db/readCache";
import { defaultLogger as log } from "@omniroute/open-sse/utils/logger";
import type { FreeAccessState } from "./strictZeroCostFilter";
import { isStateStaleForReset } from "./subscriptionLadder";
const USAGE_FETCHER_PROVIDER_SET = new Set<string>(USAGE_FETCHER_PROVIDERS);
@@ -191,7 +192,13 @@ export function resolveFreeAccessState(
const key = cacheKey(provider, connectionId);
const entry = cache.get(key);
const fresh = entry && Date.now() - entry.fetchedAtMs <= ttlMs();
// Subscription-first routing (decision 3): an entry whose own `resetAt` has
// already passed describes a quota window that no longer exists, so it is
// stale REGARDLESS of its age. Without this, a plan that refilled at
// midnight keeps reading EXHAUSTED until the TTL happens to lapse, and
// routing stays on paid rungs for no reason. See `subscriptionLadder.ts`.
const resetElapsed = entry !== undefined && isStateStaleForReset(entry.state);
const fresh = entry && !resetElapsed && Date.now() - entry.fetchedAtMs <= ttlMs();
if (!fresh) {
void refresh(provider, connectionId);
}

View File

@@ -50,6 +50,7 @@
*/
import {
FREE_MODEL_BUDGETS,
grantsFreeAccess,
type FreeModelBudget,
} from "@omniroute/open-sse/config/freeModelCatalog.ts";
import { SYNTHETIC_NOAUTH_CONNECTION_ID } from "./resilienceCandidateFilter";
@@ -184,7 +185,7 @@ export function evaluateCandidateConnections(
// entries today, so it will correctly exclude).
if (isGenuineNoAuthCandidate) return [SYNTHETIC_NOAUTH_CONNECTION_ID];
}
if (budgetEntry.freeType === "discontinued") return [];
if (!grantsFreeAccess(budgetEntry.freeType)) return [];
if (isGenuineNoAuthCandidate) return []; // no-auth path but a non-keyless catalog entry: contradictory metadata, fail closed
// Every remaining freeType (recurring-*, one-time-initial, a keyless entry

View File

@@ -0,0 +1,411 @@
/**
* Subscription-first routing: the rung model, its two groupings, and the
* reset re-entry rules.
*
* OmniRoute already answers "is this model free?" (`paidModelFilter.ts`) and
* "can this connection ever bill me?" (`strictZeroCostFilter.ts`). Both fail
* CLOSED — an exhausted free pool is an empty pool, never a step up to a paid
* option. And every paid-side mechanism (`cost-optimized`, `budgetCap`,
* the `cost-saver` mode pack) is tier-agnostic. Neither side answers:
*
* "use the quota I already pay for; when it runs out either stop, or step up
* one rung at a time; and come back the moment it resets."
*
* This module supplies both halves of that, sharing one rung model:
*
* - `filterSubscriptionOnlyCandidates` — the strict grouping (`auto/subscription`).
* Rung 0 only, overage-safe connections only, verified live. Fails CLOSED.
* - `orderPoolByRung` — the escalating grouping (`auto/thrifty`). All rungs,
* ordered, with exhausted rungs gated out. Fails OPEN, one rung at a time.
*
* Design mirrors `strictZeroCostFilter.ts` deliberately: pure functions, the
* live quota lookup injected as a synchronous resolver, no DB or network
* import, and the SAME connection-safety invariant — every connection in a
* candidate's `allowedConnectionIds` is verified INDIVIDUALLY and the array is
* rewritten to exactly the surviving subset, never the full original list.
* `autoStrategy.ts` enforces `allowedConnectionIds` as a hard allowlist before
* selecting a connection at dispatch, so rewriting it here is sufficient to
* make "verified" and "actually used" the same set by construction.
*/
import {
classifyConnectionBilling,
isOverageSafe,
type BillableConnection,
} from "./connectionBilling";
import type { ConnectionBillingEntry } from "@omniroute/open-sse/config/connectionBillingCatalog.ts";
import type { FreeAccessState } from "./strictZeroCostFilter";
/**
* Rungs in escalation order. Index is the ordering key; membership is decided
* by `assignRung` below.
*
* The rungs differ in more than price — each has its OWN exhaustion signal,
* which is why this is not just a sort:
*
* subscription / keyless / free → exhausted on QUOTA (observable, tracked)
* cheap / premium → exhausted on BUDGET (no quota exists; a paid
* connection serves forever)
*/
export const RUNG_ORDER = ["subscription", "keyless", "free", "cheap", "premium"] as const;
export type LadderRung = (typeof RUNG_ORDER)[number];
/** Rungs whose exhaustion is observable from provider quota state. */
const QUOTA_BEARING_RUNGS: ReadonlySet<LadderRung> = new Set<LadderRung>([
"subscription",
"keyless",
"free",
]);
export function rungIndex(rung: LadderRung): number {
return RUNG_ORDER.indexOf(rung);
}
/** A candidate as this module needs to see it — a structural subset of
* `VirtualAutoComboCandidate` (`virtualFactory.ts`), so this file has no
* dependency on that module's full type. */
export interface LadderCandidate {
provider: string;
model: string;
connectionId: string | null;
allowedConnectionIds?: string[];
}
export interface LadderOptions {
/** Master switch. When false every exported filter is the identity function
* — the same off-by-default contract `filterPaidOnlyCandidates` holds. */
enabled: boolean;
/**
* Live allowance/quota state for ONE (provider, connection) pair, resolved
* from the cache in `freeAccessQuota.ts`. Synchronous by design: nothing in
* a candidate-pool build may await a network call.
*
* `undefined` means "no usage adapter for this provider, or nothing fresh
* cached". The two groupings interpret that OPPOSITELY on purpose — see
* `admitUnknownQuota` below.
*/
resolveFreeAccessState: (provider: string, connectionId: string) => FreeAccessState | undefined;
/**
* `authType` for a connection id (`provider_connections.auth_type`), needed
* to classify billing. Unknown ids resolve to `null` → the provider-wide
* catalog entry, or `unknown` billing.
*/
resolveAuthType: (connectionId: string) => string | null;
/** Economic tier of a (provider, model) pair — `classifyTier()` in
* production, injected so this module needs no registry/pricing import. */
resolveEconomicTier: (provider: string, model: string) => "free" | "cheap" | "premium";
/**
* Remaining-percent at or below which a quota-bearing connection counts as
* exhausted. Default 2, matching `quotaPreflight.defaultThresholdPercent`
* (`src/lib/resilience/settings/types.ts`) so the two agree.
*/
exitCutoffPercent?: number;
/**
* Remaining-percent a quota-bearing connection must EXCEED to be re-admitted
* after having been exhausted. Strictly greater than `exitCutoffPercent`;
* the gap is the hysteresis band that stops a connection hovering at the
* cutoff from oscillating between rungs on consecutive requests. Default 5.
*/
reentryMinRemainingPercent?: number;
/** Max age of a `FreeAccessState.checkedAt` before it is treated as stale. */
maxStateAgeMs: number;
/**
* Whether a connection with no usable quota reading is admitted.
*
* - `auto/thrifty` passes TRUE: trying a plan-included connection costs
* nothing, and if it turns out to be exhausted the dispatcher's
* fall-through reaches the next rung anyway. Refusing to try it would
* send a request to a PAID rung on missing telemetry — the exact
* outcome the grouping exists to avoid.
* - `auto/subscription` passes FALSE: its promise is that no request can
* cost extra, and an unverifiable connection cannot support that promise.
*/
admitUnknownQuota: boolean;
/**
* Budget consumed so far on a paid rung, in USD, for the operator's current
* budget window. `null`/`undefined` means no spend accounting is available,
* in which case paid rungs are NOT budget-gated (they still order after
* every plan-included rung). See the spec's open question on the ledger.
*/
resolveRungSpendUsd?: (rung: LadderRung) => number | null;
/** Per-rung budget in USD. A rung mapped to 0 is disabled outright. */
rungBudgetUsd?: Partial<Record<LadderRung, number>>;
/** `now` injection for deterministic tests. */
now?: () => number;
/** Catalog override for tests; production callers never pass this. */
catalog?: readonly ConnectionBillingEntry[];
}
const DEFAULT_EXIT_CUTOFF_PERCENT = 2;
const DEFAULT_REENTRY_MIN_REMAINING_PERCENT = 5;
/**
* Which rung a specific (candidate, connection) pair belongs to.
*
* Billing class decides first because it is the fact that actually determines
* whether money moves; only a genuinely metered connection falls through to
* the model's economic tier. `unknown` billing is metered by definition
* (`connectionBilling.ts`), so an uncurated provider lands on a paid rung
* rather than silently joining the subscription rung.
*/
export function assignRung(
candidate: Pick<LadderCandidate, "provider" | "model">,
connection: BillableConnection,
options: Pick<LadderOptions, "resolveEconomicTier" | "catalog">
): LadderRung {
const verdict = classifyConnectionBilling(connection, options.catalog);
if (verdict.billing === "subscription") return "subscription";
if (verdict.billing === "keyless") return "keyless";
return options.resolveEconomicTier(candidate.provider, candidate.model);
}
/**
* Is this connection's plan allowance usable right now?
*
* `hasBeenExhausted` selects which side of the hysteresis band applies: a
* connection that is currently in play only has to stay above the exit cutoff,
* while one that already dropped out has to climb back above the (higher)
* re-entry threshold before it is admitted again.
*/
export function isQuotaUsable(
state: FreeAccessState | undefined,
options: Pick<
LadderOptions,
| "exitCutoffPercent"
| "reentryMinRemainingPercent"
| "maxStateAgeMs"
| "admitUnknownQuota"
| "now"
>,
hasBeenExhausted = false
): boolean {
if (!state) return options.admitUnknownQuota;
if (state.status === "EXHAUSTED") return false;
if (state.status === "UNKNOWN") return options.admitUnknownQuota;
const now = (options.now ?? Date.now)();
const checkedAtMs = Date.parse(state.checkedAt);
if (!Number.isFinite(checkedAtMs) || now - checkedAtMs > options.maxStateAgeMs) {
return options.admitUnknownQuota;
}
if (state.remainingFreeAllowance === null) return options.admitUnknownQuota;
const exitCutoff = options.exitCutoffPercent ?? DEFAULT_EXIT_CUTOFF_PERCENT;
const reentryFloor = Math.max(
options.reentryMinRemainingPercent ?? DEFAULT_REENTRY_MIN_REMAINING_PERCENT,
exitCutoff
);
const threshold = hasBeenExhausted ? reentryFloor : exitCutoff;
return state.remainingFreeAllowance > threshold;
}
/**
* Decision 3 — re-entry after a plan quota resets.
*
* A cached state whose own `resetAt` has already passed describes a window
* that no longer exists. Waiting out the cache TTL before re-reading it is
* pure lag on the single transition subscription-first routing cares most
* about, so such an entry is stale REGARDLESS of its age.
*
* Consumed by `freeAccessQuota.ts`, which owns the cache; kept here so the
* rule sits with the rest of the ladder's semantics and is testable without
* touching the cache.
*/
export function isStateStaleForReset(
state: Pick<FreeAccessState, "resetAt"> | undefined,
now: number = Date.now()
): boolean {
if (!state?.resetAt) return false;
const resetAtMs = Date.parse(state.resetAt);
if (!Number.isFinite(resetAtMs)) return false;
return resetAtMs <= now;
}
/**
* Decision 3, second half — never hold a plan-included connection in cooldown
* past the moment its own upstream says the quota is back.
*
* The exhausting 429 sets `rateLimitedUntil` from exponential backoff
* (`baseCooldownMs * 2 ** failureIndex`, `src/sse/services/auth.ts`), which for
* a subscription connection routinely overshoots the real reset — leaving
* routing stuck on paid rungs long after the plan refilled.
*
* This only ever NARROWS a cooldown, and only when the upstream itself
* supplied the reset instant. An absent, unparseable, or already-past
* `resetAt` returns the original cooldown untouched.
*/
export function clampCooldownToReset(
cooldownMs: number,
resetAt: string | null | undefined,
now: number = Date.now()
): number {
if (!resetAt) return cooldownMs;
const resetAtMs = Date.parse(resetAt);
if (!Number.isFinite(resetAtMs)) return cooldownMs;
const untilResetMs = resetAtMs - now;
if (untilResetMs <= 0) return cooldownMs;
return Math.min(cooldownMs, untilResetMs);
}
/** True when a paid rung has consumed its configured budget for the window. */
export function isRungBudgetExhausted(rung: LadderRung, options: LadderOptions): boolean {
const budget = options.rungBudgetUsd?.[rung];
if (budget === undefined) return false;
if (budget <= 0) return true; // explicitly disabled
const spent = options.resolveRungSpendUsd?.(rung);
if (spent === null || spent === undefined) return false; // no accounting → not gated
return spent >= budget;
}
/**
* Connections on a candidate that are usable right now, paired with the rung
* each one sits on. Quota-bearing rungs are verified per connection; paid
* rungs have nothing per-connection to verify (they are gated per rung).
*/
function evaluateConnections(
candidate: LadderCandidate,
options: LadderOptions,
accept: (rung: LadderRung) => boolean
): { rung: LadderRung; connectionIds: string[] } | null {
const connectionIds = candidate.connectionId
? [candidate.connectionId]
: (candidate.allowedConnectionIds ?? []);
if (connectionIds.length === 0) return null;
let bestRung: LadderRung | null = null;
const usable: string[] = [];
for (const connectionId of connectionIds) {
const rung = assignRung(
candidate,
{
provider: candidate.provider,
authType: options.resolveAuthType(connectionId),
connectionId,
},
options
);
if (!accept(rung)) continue;
if (QUOTA_BEARING_RUNGS.has(rung)) {
const state = options.resolveFreeAccessState(candidate.provider, connectionId);
if (!isQuotaUsable(state, options)) continue;
} else if (isRungBudgetExhausted(rung, options)) {
continue;
}
usable.push(connectionId);
// A candidate reachable through several accounts is represented by its
// CHEAPEST usable rung: that is the rung a request through it would
// actually land on once dispatch picks from the surviving allowlist.
if (bestRung === null || rungIndex(rung) < rungIndex(bestRung)) bestRung = rung;
}
if (usable.length === 0 || bestRung === null) return null;
return { rung: bestRung, connectionIds: usable };
}
/** Rewrite a candidate's connection allowlist to the verified subset, keeping
* the identity-when-nothing-changed contract the sibling filters hold. */
function withVerifiedConnections<T extends LadderCandidate>(
candidate: T,
connectionIds: string[]
): { candidate: T; changed: boolean } {
if (candidate.connectionId !== null) return { candidate, changed: false };
const original = candidate.allowedConnectionIds ?? [];
const isSameSet =
original.length === connectionIds.length && connectionIds.every((id) => original.includes(id));
if (isSameSet) return { candidate, changed: false };
return { candidate: { ...candidate, allowedConnectionIds: connectionIds }, changed: true };
}
/**
* `auto/subscription` — the strict grouping. Keeps only candidates servable by
* a plan-included connection whose overage is a documented hard stop, with
* live quota headroom verified per connection.
*
* Fails CLOSED in every ambiguous case: uncurated provider, unverifiable
* quota, or an overage that meters to paid. An empty result is the correct,
* intended answer for an operator who asked never to spend extra — the
* caller's existing empty-pool path handles it, exactly as `hidePaidModels`
* already does.
*/
export function filterSubscriptionOnlyCandidates<T extends LadderCandidate>(
pool: T[],
options: LadderOptions
): T[] {
if (!options.enabled) return pool;
const strictOptions: LadderOptions = { ...options, admitUnknownQuota: false };
const kept: T[] = [];
let changed = false;
for (const candidate of pool) {
const connectionIds = candidate.connectionId
? [candidate.connectionId]
: (candidate.allowedConnectionIds ?? []);
const safe = connectionIds.filter((connectionId) => {
const connection: BillableConnection = {
provider: candidate.provider,
authType: options.resolveAuthType(connectionId),
connectionId,
};
const verdict = classifyConnectionBilling(connection, options.catalog);
// `keyless` is plan-included in the ladder's sense but is NOT a
// subscription: this grouping is "the plan I pay for", so a no-auth
// backend does not belong in it.
if (verdict.billing !== "subscription") return false;
if (!isOverageSafe(verdict)) return false;
const state = strictOptions.resolveFreeAccessState(candidate.provider, connectionId);
return isQuotaUsable(state, strictOptions);
});
if (safe.length === 0) {
changed = true;
continue;
}
const result = withVerifiedConnections(candidate, safe);
if (result.changed) changed = true;
kept.push(result.candidate);
}
return changed ? kept : pool;
}
/**
* `auto/thrifty` — the escalating grouping. Returns the pool ordered by rung,
* with candidates whose every connection is exhausted (quota) or whose rung is
* budget-exhausted removed.
*
* Ordering only — the `auto` engine still scores WITHIN the surviving pool, so
* this decides which rungs are in play, not which candidate wins on one. The
* combo dispatcher already walks targets in order and falls through on
* failure, so a runtime exhaustion the preflight did not catch still escalates
* to the next rung inside the same request.
*
* Rung eligibility is recomputed from live state on every pool build and
* nothing is persisted: there is deliberately no sticky "currently on rung 3"
* record that could outlive a quota reset and wedge routing on paid rungs.
*/
export function orderPoolByRung<T extends LadderCandidate>(pool: T[], options: LadderOptions): T[] {
if (!options.enabled) return pool;
const ranked: Array<{ candidate: T; rung: LadderRung; order: number }> = [];
for (const [order, candidate] of pool.entries()) {
const evaluated = evaluateConnections(candidate, options, () => true);
if (!evaluated) continue;
const result = withVerifiedConnections(candidate, evaluated.connectionIds);
ranked.push({ candidate: result.candidate, rung: evaluated.rung, order });
}
ranked.sort((a, b) => {
const byRung = rungIndex(a.rung) - rungIndex(b.rung);
// Stable within a rung: preserve the pool's incoming order so the auto
// scorer's own ranking is not reshuffled by this overlay.
return byRung !== 0 ? byRung : a.order - b.order;
});
return ranked.map((entry) => entry.candidate);
}

View File

@@ -23,7 +23,20 @@ import { isVisionModelId } from "@/shared/constants/visionModels";
import { isVisionBridgeForcedModel } from "@/shared/constants/visionBridgeDefaults";
export type AutoCategory = "coding" | "reasoning" | "vision" | "chat" | "multimodal";
export type AutoTier = "fast" | "cheap" | "floor" | "free" | "reliable" | "pro";
export type AutoTier =
| "fast"
| "cheap"
| "floor"
| "free"
| "reliable"
| "pro"
// Subscription-first routing. Unlike every tier above, these two narrow by
// the CONNECTION's billing class, not the model's price — so they are
// applied in `virtualFactory.ts` against live connection state rather than
// by `buildAutoCandidateFilter` below, which only sees (provider, model).
// See `subscriptionLadder.ts` and `docs/routing/SUBSCRIPTION_LADDER.md`.
| "subscription"
| "thrifty";
export const AUTO_CATEGORIES: readonly AutoCategory[] = [
"coding",
@@ -39,6 +52,8 @@ export const AUTO_TIERS: readonly AutoTier[] = [
"free",
"reliable",
"pro",
"subscription",
"thrifty",
];
const CATEGORY_SET = new Set<string>(AUTO_CATEGORIES);
@@ -84,6 +99,9 @@ export function tierToWeightVariant(tier?: AutoTier): AutoVariant | "reliability
return "fast";
case "cheap":
case "floor":
// The ladder already orders plan-included rungs first; within a rung it
// should still lean cheap rather than reach for the most expensive model.
case "thrifty":
return "cheap";
case "reliable":
return "reliability";
@@ -118,8 +136,7 @@ export function buildAutoCandidateFilter(
}
try {
const caps = getResolvedModelCapabilities({ provider: c.provider, model: c.model });
const capable =
caps.supportsVision === true || isVisionModelId(c.model);
const capable = caps.supportsVision === true || isVisionModelId(c.model);
if (!capable) return false;
// #vison-pool: registry entries whose catalog OVERSTATES vision support
// (opencode-go/opencode-zen/tokenrouter — the backend models are text-only)

View File

@@ -28,6 +28,11 @@ import { buildFamilyCandidateFilter, type ModelFamily } from "./modelFamily";
import { getHiddenModelsByProvider } from "@/models";
import { getSyncedAvailableModelsByConnection, getCustomModels } from "@/lib/db/models";
import { filterPaidOnlyCandidates } from "./paidModelFilter";
import {
filterSubscriptionOnlyCandidates,
orderPoolByRung,
type LadderOptions,
} from "./subscriptionLadder";
import { filterStrictZeroCostCandidates, filterTosAvoidCandidates } from "./strictZeroCostFilter";
import { resolveFreeAccessState } from "./freeAccessQuota";
import { isModelExcludedByConnection } from "@/domain/connectionModelRules";
@@ -145,6 +150,98 @@ type VirtualAutoCombo = AutoComboConfig & {
export interface PreparedVirtualAutoComboInputs {
readonly regularCandidates: readonly VirtualAutoComboCandidate[];
readonly familyCandidates: readonly VirtualAutoComboCandidate[];
/**
* `provider_connections.auth_type` per connection id. Subscription-first
* routing classifies billing per CONNECTION (`connectionBilling.ts`), and
* the candidate pool only carries connection ids — so the auth types are
* captured here, during the one bulk connection read this function already
* does, instead of re-reading the DB per pool narrowing.
*/
readonly authTypeByConnectionId?: ReadonlyMap<string, string | null>;
/** Operator settings for the subscription ladder; absent = feature off. */
readonly subscriptionLadder?: SubscriptionLadderSettings;
}
/**
* Operator-facing knobs for subscription-first routing. Mirrors the Zod shape
* in `src/shared/validation/settingsSchemas.ts`.
*
* Deliberately TUNING ONLY — there is no `enabled` flag here. `auto/subscription`
* and `auto/thrifty` are new ids that nothing routes through unless a caller
* asks for them by name, so requesting the id IS the opt-in. A settings toggle
* that could switch them off would be actively dangerous: it would leave an id
* whose whole promise is "plan-included only" quietly serving the full pool,
* paid models included.
*/
export interface SubscriptionLadderSettings {
exitCutoffPercent?: number;
reentryMinRemainingPercent?: number;
rungBudgetUsd?: Record<string, number>;
/** Staleness bound for a cached quota reading, derived from the existing
* `autoRefreshProviderQuotaInterval` exactly as STRICT_ZERO_COST does. */
maxStateAgeMs: number;
}
function readSubscriptionLadderSettings(
settings: Record<string, unknown>
): SubscriptionLadderSettings {
const maxStateAgeMs = (Number(settings.autoRefreshProviderQuotaInterval) || 180) * 1000;
const raw = settings.subscriptionLadder;
if (!raw || typeof raw !== "object") return { maxStateAgeMs };
const value = raw as Record<string, unknown>;
const numeric = (key: string): number | undefined =>
typeof value[key] === "number" && Number.isFinite(value[key] as number)
? (value[key] as number)
: undefined;
const exitCutoffPercent = numeric("exitCutoffPercent");
const reentryMinRemainingPercent = numeric("reentryMinRemainingPercent");
return {
maxStateAgeMs,
...(exitCutoffPercent === undefined ? {} : { exitCutoffPercent }),
...(reentryMinRemainingPercent === undefined ? {} : { reentryMinRemainingPercent }),
...(value.rungBudgetUsd && typeof value.rungBudgetUsd === "object"
? { rungBudgetUsd: value.rungBudgetUsd as Record<string, number> }
: {}),
};
}
/**
* Build the injected dependencies the pure ladder module needs. Everything it
* touches is resolved here — the live quota cache, connection auth types, and
* the economic tier resolver — so `subscriptionLadder.ts` itself stays free of
* DB, network, and registry imports.
*/
function buildLadderOptions(
prepared: PreparedVirtualAutoComboInputs,
tier: "subscription" | "thrifty"
): LadderOptions {
const tuning = prepared.subscriptionLadder;
const authTypes = prepared.authTypeByConnectionId;
return {
enabled: true,
resolveFreeAccessState,
resolveAuthType: (connectionId: string) => authTypes?.get(connectionId) ?? null,
resolveEconomicTier: (provider: string, model: string) => {
try {
const resolved = classifyTier(provider, model).tier;
return resolved === "free" || resolved === "premium" ? resolved : "cheap";
} catch {
// Same conservative default `safeClassifyTier` uses in suffixComposition.
return "cheap";
}
},
maxStateAgeMs: tuning?.maxStateAgeMs ?? 180_000,
// The two groupings read a missing quota reading OPPOSITELY on purpose —
// see `LadderOptions.admitUnknownQuota`.
admitUnknownQuota: tier === "thrifty",
...(tuning?.exitCutoffPercent === undefined
? {}
: { exitCutoffPercent: tuning.exitCutoffPercent }),
...(tuning?.reentryMinRemainingPercent === undefined
? {}
: { reentryMinRemainingPercent: tuning.reentryMinRemainingPercent }),
...(tuning?.rungBudgetUsd ? { rungBudgetUsd: tuning.rungBudgetUsd } : {}),
};
}
function toExpiryMs(value: unknown): number | null {
@@ -656,8 +753,15 @@ export async function prepareVirtualAutoComboInputs(
const regularCandidates = buildPreparedPool(false);
// #6453/#8183: family selectors bypass the reliability-curated no-auth allowlist.
const familyCandidates = buildPreparedPool(true);
// Subscription-first routing inputs, captured from the connection read above
// so no later stage has to touch the DB again.
const authTypeByConnectionId = new Map<string, string | null>();
for (const conn of connections) {
authTypeByConnectionId.set(conn.id, typeof conn.authType === "string" ? conn.authType : null);
}
const subscriptionLadder = readSubscriptionLadderSettings(settings);
if (!options.includeResolvedCapabilities) {
return { regularCandidates, familyCandidates };
return { regularCandidates, familyCandidates, authTypeByConnectionId, subscriptionLadder };
}
// One uninterrupted bulk read of all three capability tables for this prepare only.
@@ -671,6 +775,8 @@ export async function prepareVirtualAutoComboInputs(
return {
regularCandidates: await attachPreparedCapabilityValues(regularCandidates, capabilityState),
familyCandidates: await attachPreparedCapabilityValues(familyCandidates, capabilityState),
authTypeByConnectionId,
subscriptionLadder,
};
}
@@ -842,6 +948,32 @@ export async function createVirtualAutoComboFromPrepared(
}
}
// Subscription-first routing (`auto/subscription`, `auto/thrifty`). Applied
// AFTER the category/tier narrowing above because, unlike every other tier,
// these two select on the connection's billing class and its live quota
// state rather than on the model's catalog price — see
// `subscriptionLadder.ts` and `docs/routing/SUBSCRIPTION_LADDER.md`.
if (spec?.tier === "subscription" || spec?.tier === "thrifty") {
const ladderOptions = buildLadderOptions(prepared, spec.tier);
const beforeCount = effectivePool.length;
effectivePool =
spec.tier === "subscription"
? filterSubscriptionOnlyCandidates(effectivePool, ladderOptions)
: orderPoolByRung(effectivePool, ladderOptions);
if (spec.tier === "subscription" && effectivePool.length === 0 && beforeCount > 0) {
// Intended, not a defect: the operator asked for plan-included capacity
// only, and right now there is none with verified headroom. Failing
// closed here is the entire promise of the id — the caller's existing
// empty-pool path turns it into a clear error rather than a silent,
// billable fallback.
warnEmptyAutoPoolOnce(
"auto/subscription",
"auto/subscription: no plan-included connection has verified quota headroom; " +
"returning an empty pool rather than falling back to paid capacity."
);
}
}
let weights: ScoringWeights = { ...DEFAULT_WEIGHTS };
let explorationRate = 0.05; // Default exploration rate
let routerStrategy = "lkgp"; // All auto variants use LKGP

View File

@@ -1,7 +1,7 @@
{
"name": "omniroute",
"version": "3.8.51",
"description": "Unified AI router with 350 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"description": "Unified AI router with 353 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"type": "module",
"bin": {
"omniroute": "bin/omniroute.mjs",

View File

@@ -32,6 +32,30 @@ curl -X POST https://localhost:20128/api/v1/session-leases \
-d '{}'
```
### GET /api/v1/search
List search providers
Lists configured search providers and their supported search types.
```bash
curl https://localhost:20128/api/v1/search \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/v1/search
Run a unified search
Searches the web, news, or X through a configured provider. Set `provider` to `xquik-search` to use Xquik for X search. The aliases `xquik` and `xquik_search` resolve to the same provider.
```bash
curl -X POST https://localhost:20128/api/v1/search \
-H "Authorization: Bearer $OMNIROUTE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/chat/completions
Create chat completion

View File

@@ -2,19 +2,47 @@
import { useTranslations } from "next-intl";
import { useState, useEffect, useCallback } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
mergeDashboardSessions,
type DashboardSession,
type ExclusiveDashboardSession,
type RecentSessionForDashboard,
} from "@/lib/sessionObservability";
import { Card } from "@/shared/components";
type SessionsResponse = {
sessions: RecentSessionForDashboard[];
exclusiveSessions: ExclusiveDashboardSession[];
};
const EMPTY_DATA: SessionsResponse = {
sessions: [],
exclusiveSessions: [],
};
function isLeaseBackedSession(session: DashboardSession): session is ExclusiveDashboardSession {
return "leaseBacked" in session && session.leaseBacked;
}
export default function SessionsTab() {
const t = useTranslations("usage");
const [data, setData] = useState({ count: 0, sessions: [] });
const tCommon = useTranslations("common");
const [data, setData] = useState<SessionsResponse>(EMPTY_DATA);
const [loading, setLoading] = useState(true);
const loadSessions = useCallback(async () => {
try {
const res = await fetch("/api/sessions");
if (res.ok) setData(await res.json());
if (res.ok) {
const next = await res.json();
setData({
sessions: Array.isArray(next.sessions) ? next.sessions : [],
exclusiveSessions: Array.isArray(next.exclusiveSessions) ? next.exclusiveSessions : [],
});
}
} catch {
// A failed background poll leaves the last successful Sessions snapshot visible.
} finally {
setLoading(false);
}
@@ -26,7 +54,12 @@ export default function SessionsTab() {
return () => clearInterval(interval);
}, [loadSessions]);
const formatAge = (ms) => {
const displaySessions = useMemo(() => {
return mergeDashboardSessions(data.exclusiveSessions, data.sessions);
}, [data.exclusiveSessions, data.sessions]);
const formatAge = (ms: number | null) => {
if (ms == null) return t("notAvailableSymbol");
if (ms < 60000) return t("durationSecondsShort", { value: Math.floor(ms / 1000) });
if (ms < 3600000) return t("durationMinutesShort", { value: Math.floor(ms / 60000) });
return t("durationHoursShort", { value: Math.floor(ms / 3600000) });
@@ -47,12 +80,17 @@ export default function SessionsTab() {
<div className="flex items-center gap-2">
<span className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-cyan-500/10 border border-cyan-500/20">
<span className="w-2 h-2 rounded-full bg-cyan-500 animate-pulse" />
<span className="text-sm font-semibold tabular-nums text-cyan-400">{data.count}</span>
<span
className="text-sm font-semibold tabular-nums text-cyan-400"
data-testid="session-count"
>
{displaySessions.length}
</span>
</span>
</div>
</div>
{data.sessions.length === 0 ? (
{displaySessions.length === 0 ? (
<div className="text-center py-8 text-text-muted">
<span
className="material-symbols-outlined text-[40px] mb-2 block opacity-40"
@@ -83,31 +121,46 @@ export default function SessionsTab() {
</tr>
</thead>
<tbody>
{data.sessions.map((s) => (
<tr
key={s.sessionId}
className="border-b border-border/10 hover:bg-surface/20 transition-colors"
>
<td className="py-2.5 px-3">
<span className="font-mono text-xs px-2 py-1 rounded bg-surface/40 text-text-muted">
{s.sessionId.slice(0, 12)}
</span>
</td>
<td className="py-2.5 px-3 text-text-muted tabular-nums">{formatAge(s.ageMs)}</td>
<td className="py-2.5 px-3 text-right">
<span className="font-semibold tabular-nums">{s.requestCount}</span>
</td>
<td className="py-2.5 px-3">
{s.connectionId ? (
<span className="text-xs font-mono text-cyan-400">
{s.connectionId.slice(0, 10)}
</span>
) : (
<span className="text-text-muted/40">{t("notAvailableSymbol")}</span>
)}
</td>
</tr>
))}
{displaySessions.map((s) => {
const leaseBacked = isLeaseBackedSession(s);
return (
<tr
key={s.sessionId}
className="border-b border-border/10 hover:bg-surface/20 transition-colors"
>
<td className="py-2.5 px-3">
<div className="flex items-center gap-2">
<span
className="font-mono text-xs px-2 py-1 rounded bg-surface/40 text-text-muted"
title={s.sessionId}
>
{s.sessionId.slice(0, 12)}
</span>
{leaseBacked && s.active && (
<span className="text-[10px] font-semibold tracking-wide px-2 py-0.5 rounded-full border text-green-400 border-green-500/30 bg-green-500/10">
{tCommon("active")}
</span>
)}
</div>
</td>
<td className="py-2.5 px-3 text-text-muted tabular-nums">
{formatAge(s.ageMs)}
</td>
<td className="py-2.5 px-3 text-right">
<span className="font-semibold tabular-nums">{s.requestCount}</span>
</td>
<td className="py-2.5 px-3">
{s.connectionId ? (
<span className="text-xs font-mono text-cyan-400" title={s.connectionId}>
{(leaseBacked && s.connectionName) || s.connectionId.slice(0, 10)}
</span>
) : (
<span className="text-text-muted/40">{t("notAvailableSymbol")}</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>

View File

@@ -0,0 +1,10 @@
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { buildWebSessionContract } from "@/lib/providers/webSessionContract";
export async function GET(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
return NextResponse.json(buildWebSessionContract());
}

View File

@@ -5,13 +5,44 @@ import {
getAllActiveSessionCountsByKey,
} from "@omniroute/open-sse/services/sessionManager.ts";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { getExclusiveLeaseConnectionIds } from "@/lib/db/apiKeys";
import { getExclusiveLeaseOccupancy } from "@/lib/db/exclusiveConnectionLeases";
import { getProviderConnectionDisplayMetadata } from "@/lib/db/providers";
import { getAccountDisplayName } from "@/lib/display/names";
import { getPendingRequests } from "@/lib/usage/usageHistory";
import { buildExclusiveDashboardSessions } from "@/lib/sessionObservability";
export async function GET() {
try {
const sessions = getActiveSessions();
const count = getActiveSessionCount();
const byApiKey = getAllActiveSessionCountsByKey();
return NextResponse.json({ count, sessions, byApiKey });
// Reuse the hard-lease authority added by #10362. The API-key policy derives
// the managed candidate set; SQLite occupancy is the source of truth for
// which of those connections are actually leased right now.
const managedConnectionIds = Array.from(await getExclusiveLeaseConnectionIds());
const occupancy = getExclusiveLeaseOccupancy(managedConnectionIds);
const leasedConnectionIds = new Set(occupancy.keys());
const connectionNames = new Map(
getProviderConnectionDisplayMetadata([...leasedConnectionIds]).map((connection) => [
connection.id,
getAccountDisplayName(connection),
])
);
const exclusiveSessions = buildExclusiveDashboardSessions(
leasedConnectionIds,
getPendingRequests().byAccount,
sessions,
connectionNames
);
return NextResponse.json({
count,
sessions,
byApiKey,
exclusiveSessions,
});
} catch (error) {
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
}

View File

@@ -344,6 +344,43 @@ export async function getProviderConnectionById(id: string) {
);
}
export interface ProviderConnectionDisplayMetadata {
id: string;
name: string | null;
displayName: string | null;
email: string | null;
}
/**
* Reads only the non-credential fields needed by account display-name resolvers.
*
* This avoids decrypting provider credentials when a dashboard only needs labels.
*/
export function getProviderConnectionDisplayMetadata(
connectionIds: readonly string[]
): ProviderConnectionDisplayMetadata[] {
const ids = [...new Set(connectionIds.filter((id) => id.length > 0))];
if (ids.length === 0) return [];
const db = getDbInstance() as unknown as DbLike;
const rows = db
.prepare(
`SELECT id, name, display_name, email FROM provider_connections
WHERE id IN (${ids.map(() => "?").join(", ")})`
)
.all(...ids);
return rows.map((row) => {
const view = rowToCamel(row) as JsonRecord;
return {
id: toStringOrNull(view.id) || "",
name: toStringOrNull(view.name),
displayName: toStringOrNull(view.displayName),
email: toStringOrNull(view.email),
};
});
}
// #3368 PR6 — dedup web-session cookie/token credentials on connection create.
// Re-importing the same session (e.g. via bulk web-session import) under a
// different or blank name must update the existing connection instead of

View File

@@ -410,16 +410,24 @@ export async function validateOpenAICompatibleProvider({ apiKey, providerSpecifi
const chatSuffix = apiType === "responses" ? "/responses" : "/chat/completions";
const chatUrl = `${baseUrl}${chatSuffix}`;
const testModelId = validationModelId;
const testBody =
apiType === "responses"
? {
model: testModelId,
input: [{ role: "user", content: "test" }],
max_output_tokens: 1,
}
: {
model: testModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
};
try {
const chatRes = await validationWrite(chatUrl, {
method: "POST",
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: JSON.stringify({
model: testModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
body: JSON.stringify(testBody),
});
if (chatRes.ok) {

View File

@@ -167,6 +167,13 @@ export const SEARCH_VALIDATOR_CONFIGS: Record<
}),
},
}),
"xquik-search": (apiKey) => ({
url: "https://xquik.com/api/v1/x/tweets/search?q=test&limit=1",
init: {
method: "GET",
headers: { Accept: "application/json", "x-api-key": apiKey },
},
}),
"zai-search": (apiKey, providerSpecificData = {}) => {
const baseUrl =
typeof providerSpecificData?.baseUrl === "string" && providerSpecificData.baseUrl.trim()

View File

@@ -0,0 +1,58 @@
import {
listExtractionConfigs,
type TokenSource,
} from "@omniroute/open-sse/services/tokenExtractionConfig.ts";
import { getWebSessionCredentialRequirement } from "@/shared/providers/webSessionCredentials";
export const WEB_SESSION_CONTRACT_VERSION = 1;
export interface WebSessionContractProvider {
providerId: string;
displayName: string;
loginUrl: string;
homeUrl: string;
tokenSources: TokenSource[];
credential: {
kind: "cookie" | "token";
storageKeys: string[];
acceptsFullCookieHeader: boolean;
};
}
export interface WebSessionContract {
version: typeof WEB_SESSION_CONTRACT_VERSION;
providers: WebSessionContractProvider[];
}
/**
* Publish only the canonical, non-secret metadata needed by external
* credential brokers to capture credentials in the same shape OmniRoute
* accepts. Provider instructions, polling state, and credential values are
* intentionally excluded.
*/
export function buildWebSessionContract(): WebSessionContract {
const providers = listExtractionConfigs().flatMap<WebSessionContractProvider>((config) => {
const requirement = getWebSessionCredentialRequirement(config.providerId);
if (!requirement || requirement.kind === "none") return [];
return [
{
providerId: config.providerId,
displayName: config.displayName,
loginUrl: config.loginUrl,
homeUrl: config.homeUrl,
tokenSources: config.tokenSources.map((source) => ({ ...source })),
credential: {
kind: requirement.kind,
storageKeys: [...requirement.storageKeys],
acceptsFullCookieHeader: requirement.acceptsFullCookieHeader,
},
},
];
});
return {
version: WEB_SESSION_CONTRACT_VERSION,
providers,
};
}

View File

@@ -127,7 +127,10 @@ export async function executeWebSearch(
const log = input.log || defaultLog;
if (input.provider === "x_search") input.provider = "x-search";
if (input.provider === "x-search") input.search_type = "x";
if (input.provider === "xquik" || input.provider === "xquik_search") {
input.provider = "xquik-search";
}
if (input.provider === "x-search" || input.provider === "xquik-search") input.search_type = "x";
const searchType = input.search_type || "web";
if (input.provider) {

View File

@@ -0,0 +1,93 @@
export type RecentSessionForDashboard = {
sessionId: string;
ageMs: number;
requestCount: number;
connectionId: string | null;
};
export type PendingRequestsByAccount = Record<string, Record<string, number>>;
export type ExclusiveDashboardSession = {
sessionId: string;
ageMs: null;
requestCount: number;
connectionId: string;
connectionName: string | null;
leaseBacked: true;
active: boolean;
};
export type DashboardSession = RecentSessionForDashboard | ExclusiveDashboardSession;
function positiveCount(value: unknown): number {
const count = Number(value);
return Number.isFinite(count) && count > 0 ? count : 0;
}
function countInFlightRequests(
pendingByAccount: PendingRequestsByAccount,
connectionId: string
): number {
return Object.values(pendingByAccount[connectionId] ?? {}).reduce(
(total, count) => total + positiveCount(count),
0
);
}
/**
* Build the dashboard-only view of durable exclusive leases.
*
* The lease table remains the lifecycle authority. The request tracker is used
* only to flag work currently in flight for an already-held lease; it never
* creates, extends, or removes lease ownership.
*
* Deliberately does not expose the persisted owner hash, API-key id, or lease
* generation. The dashboard needs occupancy, connection binding, and activity
* state — not fencing material.
*/
export function buildExclusiveDashboardSessions(
leasedConnectionIds: ReadonlySet<string>,
pendingByAccount: PendingRequestsByAccount,
recentSessions: readonly RecentSessionForDashboard[],
connectionNames: ReadonlyMap<string, string> = new Map()
): ExclusiveDashboardSession[] {
const recentRequestsByConnection = new Map<string, number>();
for (const session of recentSessions) {
if (!session.connectionId) continue;
recentRequestsByConnection.set(
session.connectionId,
(recentRequestsByConnection.get(session.connectionId) ?? 0) +
positiveCount(session.requestCount)
);
}
return Array.from(leasedConnectionIds)
.map((connectionId) => ({
sessionId: `lease:${connectionId}`,
ageMs: null,
requestCount: recentRequestsByConnection.get(connectionId) ?? 0,
connectionId,
connectionName: connectionNames.get(connectionId) ?? null,
leaseBacked: true as const,
active: countInFlightRequests(pendingByAccount, connectionId) > 0,
}))
.sort((left, right) => {
if (left.active !== right.active) return left.active ? -1 : 1;
return left.connectionId.localeCompare(right.connectionId);
});
}
/**
* Lease-backed rows replace request-derived rows for the same connection.
* Sessions without a connection binding remain untouched.
*/
export function mergeDashboardSessions(
leaseSessions: readonly ExclusiveDashboardSession[],
recentSessions: readonly RecentSessionForDashboard[]
): DashboardSession[] {
const leasedConnectionIds = new Set(leaseSessions.map((session) => session.connectionId));
const unleasedRecentSessions = recentSessions.filter(
(session) => !session.connectionId || !leasedConnectionIds.has(session.connectionId)
);
return [...leaseSessions, ...unleasedRecentSessions];
}

View File

@@ -141,6 +141,18 @@ export const SEARCH_PROVIDERS = {
"SuperGrok OAuth (xai-oauth) or xAI API key. This is Grok X Search, not the X Developer MCP.",
serviceKinds: ["webSearch"],
},
"xquik-search": {
id: "xquik-search",
alias: "xquik",
name: "Xquik X Search",
icon: "tag",
color: "#111827",
textIcon: "XQ",
website: "https://docs.xquik.com",
authHint:
"Xquik API key (xq_...). Search is metered per returned post; the catalog estimate uses 5 results.",
serviceKinds: ["webSearch"],
},
"ollama-search": {
id: "ollama-search",
alias: "ollama-search",

View File

@@ -1,4 +1,4 @@
import { FREE_MODEL_BUDGETS } from "@omniroute/open-sse/config/freeModelCatalog";
import { FREE_MODEL_BUDGETS, grantsFreeAccess } from "@omniroute/open-sse/config/freeModelCatalog";
import { resolveProviderId } from "@/shared/constants/providers";
import { globToRegex } from "@/shared/utils/globPattern";
import { AI_MODELS } from "@/shared/constants/models";
@@ -12,16 +12,25 @@ import { AI_MODELS } from "@/shared/constants/models";
* considered free when its id carries the OpenRouter-style `:free` suffix, when
* both its prompt and completion prices are zero, or when its id is listed as a
* free model for that provider in the catalog.
*
* The catalog also records the regime of every entry via `freeType`
* (`FreeModelFreeType`). A regime can retire a free tier behind a paid key
* (`discontinued`); `grantsFreeAccess` is the single predicate that decides
* whether a regime still grants free access, and the two structures below are
* derived only from entries whose regime grants it — so a `discontinued` entry
* is never reported free, and a future regime that forgets to be classified
* fails to compile rather than defaulting silently.
*/
/** Catalogued entries whose regime still grants free access. */
const FREE_BUDGETS = FREE_MODEL_BUDGETS.filter((m) => grantsFreeAccess(m.freeType));
/** Provider ids that have at least one documented free model. */
export const PROVIDERS_WITH_FREE_MODELS: Set<string> = new Set(
FREE_MODEL_BUDGETS.map((m) => m.provider)
);
export const PROVIDERS_WITH_FREE_MODELS: Set<string> = new Set(FREE_BUDGETS.map((m) => m.provider));
const FREE_MODEL_IDS_BY_PROVIDER: Map<string, Set<string>> = (() => {
const map = new Map<string, Set<string>>();
for (const m of FREE_MODEL_BUDGETS) {
for (const m of FREE_BUDGETS) {
let set = map.get(m.provider);
if (!set) {
set = new Set<string>();

View File

@@ -20,10 +20,7 @@ import {
} from "@/shared/reasoning/effortStandardization";
import { modelIdSchema, nonEmptyStringSchema } from "./misc.ts";
import {
isCanonicalEmbeddingItem,
JINA_NATIVE_MEDIA_KEYS,
} from "../jinaNativeEmbeddingInput.ts";
import { isCanonicalEmbeddingItem, JINA_NATIVE_MEDIA_KEYS } from "../jinaNativeEmbeddingInput.ts";
import { isGeminiNativeEmbeddingItem } from "../geminiNativeEmbeddingInput.ts";
export const embeddingTokenArraySchema = z
@@ -215,7 +212,9 @@ const jinaNativeMediaStringSchema = z.string().trim().min(1).superRefine(refineJ
function exactlyOneJinaMediaKey(value: Record<string, unknown>, key: string): boolean {
if (isCanonicalEmbeddingItem(value)) return false;
return JINA_NATIVE_MEDIA_KEYS.filter((mediaKey) => mediaKey in value).length === 1 && key in value;
return (
JINA_NATIVE_MEDIA_KEYS.filter((mediaKey) => mediaKey in value).length === 1 && key in value
);
}
const jinaTextDocSchema = z
@@ -264,7 +263,9 @@ export const jinaNativeDocSchema = z.union([
export const jinaMergedContentGroupSchema = z
.object({
content: z
.array(z.union([jinaTextDocSchema, jinaImageDocSchema, jinaAudioDocSchema, jinaVideoDocSchema]))
.array(
z.union([jinaTextDocSchema, jinaImageDocSchema, jinaAudioDocSchema, jinaVideoDocSchema])
)
.min(1, "content must contain at least one chunk"),
})
.passthrough();
@@ -330,9 +331,12 @@ export const geminiNativePartSchema = z
fileData: geminiFileDataSchema.optional(),
})
.passthrough()
.refine((value) => isGeminiNativeEmbeddingItem(value) && !("parts" in value) && !("content" in value), {
message: "Gemini part must be { text }, { inline_data }, or { file_data }",
});
.refine(
(value) => isGeminiNativeEmbeddingItem(value) && !("parts" in value) && !("content" in value),
{
message: "Gemini part must be { text }, { inline_data }, or { file_data }",
}
);
export const geminiNativeContentSchema = z
.object({
@@ -445,7 +449,6 @@ export const v1ImageUpscaleSchema = z
})
.catchall(z.unknown());
export const v1AudioSpeechSchema = z
.object({
model: modelIdSchema,
@@ -565,71 +568,72 @@ export const v1SearchSchema = z.preprocess(
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
const o = { ...(raw as Record<string, unknown>) };
if (o.provider === "x_search") o.provider = "x-search";
if (o.provider === "x-search") o.search_type = "x";
if (o.provider === "xquik" || o.provider === "xquik_search") o.provider = "xquik-search";
if (o.provider === "x-search" || o.provider === "xquik-search") o.search_type = "x";
return o;
},
z
.object({
// Core
query: z
.string()
.trim()
.min(1, "Query is required")
.max(500, "Query must be 500 characters or fewer"),
// Not a z.enum: the runtime catalog (SEARCH_PROVIDERS + SEARCH_PROVIDER_ALIASES in
// open-sse/config/searchRegistry.ts) is the source of truth via resolveSearchProvider(),
// which already returns a named "Unknown search provider: <id>" error for bad ids (see
// src/app/api/v1/search/route.ts). A hard-coded enum here would 400 before that check
// ever runs, hiding the informative message behind a generic Zod failure (#10849).
// Known catalog ids as of this writing: serper-search, brave-search, perplexity-search,
// exa-search, tavily-search, firecrawl, google-pse-search, linkup-search, ollama-search,
// searchapi-search, youcom-search, searxng-search, zai-search, jina-search, jina-ai,
// jina, duckduckgo-free, x-search, x_search (plus short aliases resolved by
// SEARCH_PROVIDER_ALIASES).
provider: z.string().min(1).optional(),
max_results: z.coerce.number().int().min(1).max(100).default(5),
search_type: z.enum(["web", "news", "x"]).default("web"),
offset: z.coerce.number().int().min(0).default(0),
// Core
query: z
.string()
.trim()
.min(1, "Query is required")
.max(500, "Query must be 500 characters or fewer"),
// Not a z.enum: the runtime catalog (SEARCH_PROVIDERS + SEARCH_PROVIDER_ALIASES in
// open-sse/config/searchRegistry.ts) is the source of truth via resolveSearchProvider(),
// which already returns a named "Unknown search provider: <id>" error for bad ids (see
// src/app/api/v1/search/route.ts). A hard-coded enum here would 400 before that check
// ever runs, hiding the informative message behind a generic Zod failure (#10849).
// Known catalog ids as of this writing: serper-search, brave-search, perplexity-search,
// exa-search, tavily-search, firecrawl, google-pse-search, linkup-search, ollama-search,
// searchapi-search, youcom-search, searxng-search, zai-search, jina-search, jina-ai,
// jina, duckduckgo-free, x-search, x_search, xquik-search, xquik (plus short aliases resolved by
// SEARCH_PROVIDER_ALIASES).
provider: z.string().min(1).optional(),
max_results: z.coerce.number().int().min(1).max(100).default(5),
search_type: z.enum(["web", "news", "x"]).default("web"),
offset: z.coerce.number().int().min(0).default(0),
// Locale
country: z.string().max(2).toUpperCase().optional(),
language: z.string().min(2).max(5).optional(),
time_range: z.enum(["any", "hour", "day", "week", "month", "year"]).optional(),
// Locale
country: z.string().max(2).toUpperCase().optional(),
language: z.string().min(2).max(5).optional(),
time_range: z.enum(["any", "hour", "day", "week", "month", "year"]).optional(),
// Content control
content: z
.object({
snippet: z.boolean().default(true),
full_page: z.boolean().default(false),
format: z.enum(["text", "markdown"]).default("text"),
max_characters: z.coerce.number().int().min(100).max(100000).optional(),
})
.optional(),
// Content control
content: z
.object({
snippet: z.boolean().default(true),
full_page: z.boolean().default(false),
format: z.enum(["text", "markdown"]).default("text"),
max_characters: z.coerce.number().int().min(100).max(100000).optional(),
})
.optional(),
// Filters
filters: z
.object({
include_domains: z.array(z.string().max(253)).max(20).optional(),
exclude_domains: z.array(z.string().max(253)).max(20).optional(),
safe_search: z.enum(["off", "moderate", "strict"]).optional(),
})
.optional(),
// Filters
filters: z
.object({
include_domains: z.array(z.string().max(253)).max(20).optional(),
exclude_domains: z.array(z.string().max(253)).max(20).optional(),
safe_search: z.enum(["off", "moderate", "strict"]).optional(),
})
.optional(),
// Answer synthesis (Phase 2 — returns null until implemented)
synthesis: z
.object({
strategy: z.enum(["none", "auto", "provider", "internal"]).default("none"),
model: z.string().optional(),
max_tokens: z.coerce.number().int().min(1).max(4000).optional(),
})
.optional(),
// Answer synthesis (Phase 2 — returns null until implemented)
synthesis: z
.object({
strategy: z.enum(["none", "auto", "provider", "internal"]).default("none"),
model: z.string().optional(),
max_tokens: z.coerce.number().int().min(1).max(4000).optional(),
})
.optional(),
// Provider-specific passthrough
provider_options: z.record(z.string(), z.unknown()).optional(),
// Provider-specific passthrough
provider_options: z.record(z.string(), z.unknown()).optional(),
// Strict mode — reject if provider doesn't support a requested filter
strict_filters: z.boolean().default(false),
})
// Strict mode — reject if provider doesn't support a requested filter
strict_filters: z.boolean().default(false),
})
.catchall(z.unknown())
);

View File

@@ -138,6 +138,24 @@ export const updateSettingsSchema = z.object({
// curated `tos` verdict is "avoid" (proxy/self-hosted use conflicts with the
// provider's own terms) — a contractual concern, not an economic one.
excludeTosAvoid: z.boolean().optional(),
// Subscription-first routing tuning (`auto/subscription`, `auto/thrifty`).
// TUNING ONLY — there is deliberately no `enabled` flag: both ids are opt-in
// by being requested, and a toggle able to switch them off would leave
// `auto/subscription` silently serving paid capacity under a name that
// promises the opposite. See open-sse/services/autoCombo/subscriptionLadder.ts.
subscriptionLadder: z
.object({
// Remaining-% at or below which a plan-included connection counts as
// exhausted. Matches quotaPreflight.defaultThresholdPercent's default.
exitCutoffPercent: z.number().min(0).max(100).optional(),
// Remaining-% a connection must EXCEED to be re-admitted after having
// been exhausted. The gap above exitCutoffPercent is the hysteresis band
// that stops a connection hovering at the cutoff from oscillating.
reentryMinRemainingPercent: z.number().min(0).max(100).optional(),
// Per-rung spend ceiling in USD. 0 disables a rung outright.
rungBudgetUsd: z.record(z.string().max(32), z.number().min(0)).optional(),
})
.optional(),
hideHealthCheckLogs: z.boolean().optional(),
hideEndpointCloudflaredTunnel: z.boolean().optional(),
hideEndpointTailscaleFunnel: z.boolean().optional(),

View File

@@ -2,7 +2,7 @@
* Integration tests for GET /api/search/providers — extended catalog (F4).
*
* Tests:
* - Returns 20 items total (16 search + 4 fetch providers).
* - Returns 22 items total (18 search + 4 fetch providers).
* - Each item carries the correct `kind` field.
* - Status reflects actual DB credential state:
* - "configured" when an active, non-rate-limited connection exists.
@@ -48,10 +48,10 @@ const route = await import("../../src/app/api/search/providers/route.ts");
// Constants
// ---------------------------------------------------------------------------
// 17 search-kind providers: serper, brave, perplexity, exa, tavily, firecrawl,
// 18 search-kind providers: serper, brave, perplexity, exa, tavily, firecrawl,
// google-pse, linkup, searchapi, youcom, searxng, ollama, zai, jina-search,
// context7 (#11140), duckduckgo-free, x-search (registry open-sse/config/searchRegistry.ts).
const EXPECTED_SEARCH_COUNT = 17;
// context7 (#11140), duckduckgo-free, x-search, xquik-search.
const EXPECTED_SEARCH_COUNT = 18;
const EXPECTED_FETCH_COUNT = 4;
const EXPECTED_TOTAL = EXPECTED_SEARCH_COUNT + EXPECTED_FETCH_COUNT;
@@ -138,7 +138,7 @@ test("search-providers-catalog: returns 401 for unauthenticated requests when au
assert.ok(!bodyStr.includes(" at /"), "error body must not contain stack trace");
});
test("search-providers-catalog: returns 21 providers (17 search + 4 fetch)", async () => {
test("search-providers-catalog: returns 22 providers (18 search + 4 fetch)", async () => {
const req = await buildAuthRequest();
const res = await route.GET(req);
@@ -361,6 +361,11 @@ test("search-providers-catalog: search providers have correct fields", async ()
assert.ok(xSearch, "x-search must be in search providers");
assert.equal(xSearch.kind, "search");
assert.deepEqual(xSearch.searchTypes, ["x"]);
const xquikSearch = searchProviders.find((p: { id: string }) => p.id === "xquik-search");
assert.ok(xquikSearch, "xquik-search must be in search providers");
assert.equal(xquikSearch.kind, "search");
assert.deepEqual(xquikSearch.searchTypes, ["x"]);
});
test("search-providers-catalog: response validates against SearchProviderCatalogResponseSchema", async () => {

View File

@@ -87,6 +87,11 @@
"status": 400,
"throws": true
},
"xquik-search": {
"message": "Provider \"xquik-search\" is a search provider and does not support chat completions; use the /v1/search endpoint instead.",
"status": 400,
"throws": true
},
"youcom-search": {
"message": "Provider \"youcom-search\" is a search provider and does not support chat completions; use the /v1/search endpoint instead.",
"status": 400,

View File

@@ -0,0 +1,141 @@
/**
* Follow-up to #6328 / #6495 / #6512 — the shared free-model predicate ignored
* the catalog's own `freeType`, so entries a provider has since put behind a
* paid key were still reported free.
*
* The catalog already records the regime of every entry, and
* `strictZeroCostFilter` already reads it. These guards pin the same rule into
* the predicate that `hidePaidModels` and `/v1/models` go through.
*/
import { test } from "vitest";
import assert from "node:assert/strict";
import {
FREE_MODEL_BUDGETS,
grantsFreeAccess,
type FreeModelFreeType,
} from "../../../open-sse/config/freeModelCatalog.ts";
import { isFreeModel, providerHasFreeModels } from "../../../src/shared/utils/freeModels.ts";
import { filterPaidOnlyCandidates } from "../../../open-sse/services/autoCombo/paidModelFilter.ts";
import {
evaluateCandidateConnections,
findBudgetEntry,
} from "../../../open-sse/services/autoCombo/strictZeroCostFilter.ts";
/** Catalogued under `pollinations` as `discontinued`: the provider moved them
* behind an API key, and their `displayName` says so. */
const DISCONTINUED = [
"gemini",
"gemini-fast",
"midijourney",
"midijourney-large",
"claude-fast",
"claude",
"claude-large",
];
/** Same provider, still keyless — the guard against over-filtering. */
const STILL_FREE = ["openai", "openai-fast", "qwen-coder", "mistral", "deepseek"];
test("a model the catalog marks discontinued is not free", () => {
for (const id of DISCONTINUED) {
assert.equal(
isFreeModel("pollinations", { id }),
false,
`pollinations/${id} is catalogued discontinued and must not qualify as free`
);
}
});
test("the provider's still-free models are untouched", () => {
for (const id of STILL_FREE) {
assert.equal(
isFreeModel("pollinations", { id }),
true,
`pollinations/${id} is catalogued keyless and must stay free`
);
}
});
test("the provider itself still counts as having free models", () => {
assert.equal(
providerHasFreeModels("pollinations"),
true,
"pollinations keeps ten keyless entries; only the discontinued ones change"
);
});
test("hidePaidModels drops them from the auto/* candidate pool", () => {
const discontinued = { provider: "pollinations", model: "claude" };
const stillFree = { provider: "pollinations", model: "openai" };
assert.deepEqual(
filterPaidOnlyCandidates([discontinued, stillFree], true),
[stillFree],
"an operator who asked not to route to paid models must not get one that needs a paid key"
);
assert.deepEqual(
filterPaidOnlyCandidates([discontinued, stillFree], false),
[discontinued, stillFree],
"opt-in off stays an identity no-op"
);
});
test("no provider loses its free status", () => {
const withFreeRegime = new Set(
FREE_MODEL_BUDGETS.filter((m) => grantsFreeAccess(m.freeType)).map((m) => m.provider)
);
const lost = [...new Set(FREE_MODEL_BUDGETS.map((m) => m.provider))].filter(
(p) => !withFreeRegime.has(p)
);
assert.deepEqual(
lost,
[],
"no catalogued provider is discontinued across the board today; if one ever is, decide deliberately"
);
});
test("every regime is classified, with the expected verdict", () => {
const expected: Record<FreeModelFreeType, boolean> = {
"recurring-daily": true,
"recurring-monthly": true,
"recurring-credit": true,
"recurring-uncapped": true,
"one-time-initial": true,
keyless: true,
discontinued: false,
};
for (const [freeType, verdict] of Object.entries(expected)) {
assert.equal(
grantsFreeAccess(freeType as FreeModelFreeType),
verdict,
`${freeType} must be classified ${verdict}`
);
}
});
test("the strict filter (G1c) excludes a discontinued entry, matching its prior literal", () => {
const budgetEntry = findBudgetEntry({ provider: "pollinations", model: "claude" });
assert.ok(budgetEntry, "discontinued pollinations/claude must be in the catalog");
assert.equal(budgetEntry.freeType, "discontinued", "sanity: the entry this guard protects");
// A discontinued entry must be excluded by the strict filter regardless of
// connection safety — it collapses the regime to "no free access" before any
// quota lookup, exactly as the previous `freeType === "discontinued"` literal did.
const excluded = evaluateCandidateConnections(
{ provider: "pollinations", model: "claude", connectionId: "some-real-conn" },
budgetEntry,
() => ({
status: "SAFE",
remainingFreeAllowance: 1000,
resetAt: null,
checkedAt: new Date().toISOString(),
}),
{ minRemainingAllowance: 0, maxStateAgeMs: 1e9 }
);
assert.deepEqual(
excluded,
[],
"a discontinued entry is excluded by the strict filter, independent of connection safety"
);
});

View File

@@ -0,0 +1,407 @@
/**
* Subscription-first routing — regression guard for `connectionBilling.ts` and
* `subscriptionLadder.ts`, wired into
* `open-sse/services/autoCombo/virtualFactory.ts::createVirtualAutoComboFromPrepared`
* for the `auto/subscription` and `auto/thrifty` ids.
*
* Pure and dependency-light by design, mirroring
* `strict-zero-cost-filter.test.ts`: every side-effecting dependency (live
* quota state, connection auth types, the economic tier resolver, the billing
* catalog) is injected, so nothing here touches the DB, the network, or global
* state.
*/
import { test } from "vitest";
import assert from "node:assert/strict";
import type { ConnectionBillingEntry } from "@omniroute/open-sse/config/connectionBillingCatalog.ts";
import {
classifyConnectionBilling,
isOverageSafe,
isPlanIncluded,
} from "@omniroute/open-sse/services/autoCombo/connectionBilling.ts";
import { SYNTHETIC_NOAUTH_CONNECTION_ID } from "@omniroute/open-sse/services/autoCombo/resilienceCandidateFilter.ts";
import {
RUNG_ORDER,
assignRung,
clampCooldownToReset,
filterSubscriptionOnlyCandidates,
isQuotaUsable,
isStateStaleForReset,
orderPoolByRung,
type LadderCandidate,
type LadderOptions,
} from "@omniroute/open-sse/services/autoCombo/subscriptionLadder.ts";
import type { FreeAccessState } from "@omniroute/open-sse/services/autoCombo/strictZeroCostFilter.ts";
const NOW = Date.parse("2026-08-22T12:00:00.000Z");
/** Synthetic catalog — never the real one, so these tests keep passing when
* the curated entries are edited (the autodiscovery contract). */
const CATALOG: readonly ConnectionBillingEntry[] = [
{
provider: "planned",
authType: "oauth",
billing: "subscription",
overage: "hard-stop",
reason: "test fixture: plan-included, refuses past the allowance",
},
{
provider: "planned",
authType: "apikey",
billing: "metered",
overage: "meters-to-paid",
reason: "test fixture: same provider, metered credential",
},
{
provider: "overflowing",
billing: "subscription",
overage: "meters-to-paid",
reason: "test fixture: plan-included but bills past the allowance",
},
{
provider: "metered-co",
billing: "metered",
overage: "meters-to-paid",
reason: "test fixture: pay per token",
},
];
function state(overrides: Partial<FreeAccessState> = {}): FreeAccessState {
return {
status: "SAFE",
remainingFreeAllowance: 50,
resetAt: null,
checkedAt: new Date(NOW - 1000).toISOString(),
...overrides,
};
}
function options(overrides: Partial<LadderOptions> = {}): LadderOptions {
return {
enabled: true,
resolveFreeAccessState: () => state(),
resolveAuthType: () => "oauth",
resolveEconomicTier: () => "cheap",
maxStateAgeMs: 180_000,
admitUnknownQuota: false,
now: () => NOW,
catalog: CATALOG,
...overrides,
};
}
function candidate(overrides: Partial<LadderCandidate> = {}): LadderCandidate {
return {
provider: "planned",
model: "m1",
connectionId: "c1",
...overrides,
};
}
// ── classification ──────────────────────────────────────────────────────────
test("the synthetic no-auth connection classifies as keyless without consulting the catalog", () => {
const verdict = classifyConnectionBilling(
{ provider: "metered-co", connectionId: SYNTHETIC_NOAUTH_CONNECTION_ID },
CATALOG
);
assert.equal(verdict.billing, "keyless");
assert.equal(isPlanIncluded(verdict), true);
});
test("an authType-specific entry wins over the provider-wide one", () => {
const oauth = classifyConnectionBilling(
{ provider: "planned", authType: "oauth", connectionId: "c1" },
CATALOG
);
const apikey = classifyConnectionBilling(
{ provider: "planned", authType: "apikey", connectionId: "c2" },
CATALOG
);
assert.equal(oauth.billing, "subscription");
assert.equal(apikey.billing, "metered");
});
test("a provider-wide entry applies when no authType entry matches", () => {
const verdict = classifyConnectionBilling(
{ provider: "overflowing", authType: "cookie", connectionId: "c1" },
CATALOG
);
assert.equal(verdict.billing, "subscription");
assert.equal(isOverageSafe(verdict), false);
});
test("an uncurated provider is unknown — never silently plan-included", () => {
const verdict = classifyConnectionBilling(
{ provider: "brand-new", authType: "oauth", connectionId: "c1" },
CATALOG
);
assert.equal(verdict.billing, "unknown");
assert.equal(isPlanIncluded(verdict), false);
assert.equal(isOverageSafe(verdict), false);
});
test("rung assignment prefers billing class, falling back to the economic tier", () => {
const opts = options({ resolveEconomicTier: () => "premium" });
assert.equal(
assignRung(
{ provider: "planned", model: "m1" },
{ provider: "planned", authType: "oauth" },
opts
),
"subscription"
);
assert.equal(
assignRung(
{ provider: "metered-co", model: "m1" },
{ provider: "metered-co", authType: "apikey" },
opts
),
"premium"
);
assert.equal(
assignRung(
{ provider: "whatever", model: "m1" },
{ provider: "whatever", connectionId: SYNTHETIC_NOAUTH_CONNECTION_ID },
opts
),
"keyless"
);
});
// ── auto/subscription — fails closed ────────────────────────────────────────
test("disabled leaves the pool byte-identical (the opt-in contract)", () => {
const pool = [candidate()];
assert.equal(filterSubscriptionOnlyCandidates(pool, options({ enabled: false })), pool);
assert.equal(orderPoolByRung(pool, options({ enabled: false })), pool);
});
test("a plan-included, hard-stop connection with headroom is kept", () => {
const pool = [candidate()];
assert.deepEqual(filterSubscriptionOnlyCandidates(pool, options()), pool);
});
test("a subscription that meters past the plan is excluded", () => {
const pool = [candidate({ provider: "overflowing" })];
assert.deepEqual(filterSubscriptionOnlyCandidates(pool, options()), []);
});
test("a metered connection is excluded even on a provider that also sells a plan", () => {
const pool = [candidate({ connectionId: "c2" })];
const result = filterSubscriptionOnlyCandidates(
pool,
options({ resolveAuthType: () => "apikey" })
);
assert.deepEqual(result, []);
});
test("keyless is not a subscription — auto/subscription means the plan you pay for", () => {
const pool = [candidate({ provider: "freebie", connectionId: SYNTHETIC_NOAUTH_CONNECTION_ID })];
assert.deepEqual(filterSubscriptionOnlyCandidates(pool, options()), []);
});
test("an unverifiable quota reading fails closed", () => {
const pool = [candidate()];
assert.deepEqual(
filterSubscriptionOnlyCandidates(pool, options({ resolveFreeAccessState: () => undefined })),
[]
);
assert.deepEqual(
filterSubscriptionOnlyCandidates(
pool,
options({ resolveFreeAccessState: () => state({ status: "UNKNOWN" }) })
),
[]
);
});
test("a stale quota reading fails closed even when it says SAFE", () => {
const stale = state({ checkedAt: new Date(NOW - 10 * 60_000).toISOString() });
assert.deepEqual(
filterSubscriptionOnlyCandidates(
[candidate()],
options({ resolveFreeAccessState: () => stale })
),
[]
);
});
test("a multi-account candidate keeps only the connections proven safe", () => {
const pool = [candidate({ connectionId: null, allowedConnectionIds: ["a", "b", "c"] })];
const result = filterSubscriptionOnlyCandidates(
pool,
options({
resolveFreeAccessState: (_provider, connectionId) =>
connectionId === "b" ? state({ status: "EXHAUSTED", remainingFreeAllowance: 0 }) : state(),
})
);
assert.equal(result.length, 1);
assert.deepEqual(result[0].allowedConnectionIds, ["a", "c"]);
});
test("a multi-account candidate with no safe connection is dropped, not emptied", () => {
const pool = [candidate({ connectionId: null, allowedConnectionIds: ["a", "b"] })];
const result = filterSubscriptionOnlyCandidates(
pool,
options({
resolveFreeAccessState: () => state({ status: "EXHAUSTED", remainingFreeAllowance: 0 }),
})
);
assert.deepEqual(result, []);
});
// ── auto/thrifty — escalates ───────────────────────────────────────────────
test("rungs order plan-included capacity ahead of every paid rung", () => {
const pool: LadderCandidate[] = [
{ provider: "metered-co", model: "premium-model", connectionId: "p1" },
{ provider: "metered-co", model: "cheap-model", connectionId: "c1" },
{ provider: "planned", model: "plan-model", connectionId: "s1" },
{ provider: "anything", model: "keyless-model", connectionId: SYNTHETIC_NOAUTH_CONNECTION_ID },
];
const result = orderPoolByRung(
pool,
options({
admitUnknownQuota: true,
resolveAuthType: (id) => (id === "s1" ? "oauth" : "apikey"),
resolveEconomicTier: (_provider, model) => (model === "premium-model" ? "premium" : "cheap"),
})
);
assert.deepEqual(
result.map((c) => c.model),
["plan-model", "keyless-model", "cheap-model", "premium-model"]
);
});
test("an exhausted plan connection steps aside so a paid rung can serve", () => {
const pool: LadderCandidate[] = [
{ provider: "planned", model: "plan-model", connectionId: "s1" },
{ provider: "metered-co", model: "cheap-model", connectionId: "c1" },
];
const result = orderPoolByRung(
pool,
options({
admitUnknownQuota: true,
resolveAuthType: (id) => (id === "s1" ? "oauth" : "apikey"),
resolveFreeAccessState: (_provider, connectionId) =>
connectionId === "s1"
? state({ status: "EXHAUSTED", remainingFreeAllowance: 0 })
: undefined,
})
);
assert.deepEqual(
result.map((c) => c.model),
["cheap-model"]
);
});
test("the ladder admits an unverifiable plan connection rather than paying on missing telemetry", () => {
const pool = [candidate({ connectionId: "s1", model: "plan-model" })];
const result = orderPoolByRung(
pool,
options({ admitUnknownQuota: true, resolveFreeAccessState: () => undefined })
);
assert.equal(result.length, 1);
});
test("ordering is stable within a rung so the auto scorer is not reshuffled", () => {
const pool: LadderCandidate[] = [
{ provider: "metered-co", model: "first", connectionId: "a" },
{ provider: "metered-co", model: "second", connectionId: "b" },
{ provider: "metered-co", model: "third", connectionId: "c" },
];
const result = orderPoolByRung(
pool,
options({ admitUnknownQuota: true, resolveAuthType: () => "apikey" })
);
assert.deepEqual(
result.map((c) => c.model),
["first", "second", "third"]
);
});
test("a rung budgeted at zero is disabled outright", () => {
const pool: LadderCandidate[] = [
{ provider: "metered-co", model: "cheap-model", connectionId: "c1" },
{ provider: "metered-co", model: "premium-model", connectionId: "p1" },
];
const result = orderPoolByRung(
pool,
options({
admitUnknownQuota: true,
resolveAuthType: () => "apikey",
resolveEconomicTier: (_p, model) => (model === "premium-model" ? "premium" : "cheap"),
rungBudgetUsd: { premium: 0 },
})
);
assert.deepEqual(
result.map((c) => c.model),
["cheap-model"]
);
});
test("a paid rung drops out once its budget is spent, and is ungated without accounting", () => {
const pool: LadderCandidate[] = [
{ provider: "metered-co", model: "cheap-model", connectionId: "c1" },
];
const base = {
admitUnknownQuota: true,
resolveAuthType: () => "apikey",
rungBudgetUsd: { cheap: 5 },
};
assert.deepEqual(orderPoolByRung(pool, options({ ...base, resolveRungSpendUsd: () => 5 })), []);
assert.equal(orderPoolByRung(pool, options({ ...base, resolveRungSpendUsd: () => 1 })).length, 1);
// No spend accounting available → the rung is ordered, never gated.
assert.equal(
orderPoolByRung(pool, options({ ...base, resolveRungSpendUsd: () => null })).length,
1
);
});
// ── decision 3: returning to the plan after a reset ─────────────────────────
test("a cached reading whose own resetAt has passed is stale regardless of age", () => {
assert.equal(
isStateStaleForReset(state({ resetAt: new Date(NOW - 1).toISOString() }), NOW),
true
);
assert.equal(
isStateStaleForReset(state({ resetAt: new Date(NOW + 60_000).toISOString() }), NOW),
false
);
assert.equal(isStateStaleForReset(state({ resetAt: null }), NOW), false);
assert.equal(isStateStaleForReset(state({ resetAt: "not-a-date" }), NOW), false);
assert.equal(isStateStaleForReset(undefined, NOW), false);
});
test("hysteresis: re-entry needs more headroom than staying in did", () => {
const opts = options({ exitCutoffPercent: 2, reentryMinRemainingPercent: 5 });
const hovering = state({ remainingFreeAllowance: 3 });
// Still in play at 3% remaining…
assert.equal(isQuotaUsable(hovering, opts, false), true);
// …but not enough to climb back after having dropped out.
assert.equal(isQuotaUsable(hovering, opts, true), false);
assert.equal(isQuotaUsable(state({ remainingFreeAllowance: 6 }), opts, true), true);
});
test("a re-entry floor below the exit cutoff cannot create a re-entry gap", () => {
const opts = options({ exitCutoffPercent: 10, reentryMinRemainingPercent: 1 });
assert.equal(isQuotaUsable(state({ remainingFreeAllowance: 5 }), opts, true), false);
});
test("cooldown is clamped to the upstream's own reset instant, never extended", () => {
const resetIn60s = new Date(NOW + 60_000).toISOString();
assert.equal(clampCooldownToReset(600_000, resetIn60s, NOW), 60_000);
// Already shorter than the reset → untouched.
assert.equal(clampCooldownToReset(10_000, resetIn60s, NOW), 10_000);
// Absent / unparseable / already elapsed → untouched, never widened.
assert.equal(clampCooldownToReset(600_000, null, NOW), 600_000);
assert.equal(clampCooldownToReset(600_000, "nonsense", NOW), 600_000);
assert.equal(clampCooldownToReset(600_000, new Date(NOW - 1).toISOString(), NOW), 600_000);
});
test("rung order is the documented escalation order", () => {
assert.deepEqual([...RUNG_ORDER], ["subscription", "keyless", "free", "cheap", "premium"]);
});

View File

@@ -0,0 +1,332 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
buildExclusiveDashboardSessions,
mergeDashboardSessions,
} from "../../src/lib/sessionObservability.ts";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-session-observability-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
process.env.API_KEY_SECRET = "ab".repeat(32);
const core = await import("../../src/lib/db/core.ts");
const apiKeys = await import("../../src/lib/db/apiKeys.ts");
const leases = await import("../../src/lib/db/exclusiveConnectionLeases.ts");
const providers = await import("../../src/lib/db/providers.ts");
const sessionManager = await import("../../open-sse/services/sessionManager.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const sessionsRoute = await import("../../src/app/api/sessions/route.ts");
const OWNER_A = "vlo_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const OWNER_B = "vlo_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
const BASE_TIME = Date.parse("2026-08-24T12:00:00.000Z");
function at(offsetMs: number): string {
return new Date(BASE_TIME + offsetMs).toISOString();
}
function projectOfficialOccupancy(connectionIds: string[], now: string) {
const occupancy = leases.getExclusiveLeaseOccupancy(connectionIds, now);
return buildExclusiveDashboardSessions(new Set(occupancy.keys()), {}, []);
}
test.afterEach(() => {
sessionManager.clearSessions();
usageHistory.clearPendingRequests();
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("projects idle and active leases, distinct connections, legacy rows, and de-duplication", () => {
const leaseRows = buildExclusiveDashboardSessions(
new Set(["conn-idle", "conn-active"]),
{ "conn-active": { "gpt-5.6-sol (codex)": 2 }, "conn-idle": { ignored: 0 } },
[
{
sessionId: "legacy-duplicate-a",
ageMs: 10_000,
requestCount: 2,
connectionId: "conn-active",
},
{
sessionId: "legacy-duplicate-b",
ageMs: 5_000,
requestCount: 3,
connectionId: "conn-active",
},
],
new Map([
["conn-active", "Managed Active"],
["conn-idle", "Managed Idle"],
])
);
assert.equal(leaseRows.length, 2);
assert.deepEqual(
leaseRows.map((row) => [row.connectionId, row.active, row.requestCount]),
[
["conn-active", true, 5],
["conn-idle", false, 0],
]
);
assert.equal(leaseRows[0].connectionName, "Managed Active");
const displayed = mergeDashboardSessions(leaseRows, [
{
sessionId: "legacy-duplicate-a",
ageMs: 10_000,
requestCount: 2,
connectionId: "conn-active",
},
{
sessionId: "legacy-unmanaged",
ageMs: 2_000,
requestCount: 1,
connectionId: "conn-unmanaged",
},
{
sessionId: "legacy-unbound",
ageMs: 1_000,
requestCount: 1,
connectionId: null,
},
]);
assert.deepEqual(
displayed.map((row) => row.sessionId),
["lease:conn-active", "lease:conn-idle", "legacy-unmanaged", "legacy-unbound"]
);
});
test("lease projection is a minimum privacy-safe observability payload", () => {
const secretOwnerHash = "c".repeat(64);
const rows = buildExclusiveDashboardSessions(
new Set(["conn-private"]),
{},
[],
new Map([["conn-private", "Private account"]])
);
const payload = JSON.stringify(rows);
assert.deepEqual(Object.keys(rows[0]).sort(), [
"active",
"ageMs",
"connectionId",
"connectionName",
"leaseBacked",
"requestCount",
"sessionId",
]);
for (const forbidden of [
secretOwnerHash,
"leaseOwnerHash",
"lease_owner_hash",
"generation",
"apiKeyId",
"leaseOwnerId",
"expiresAt",
"IDLE",
]) {
assert.equal(payload.includes(forbidden), false, `payload must not contain ${forbidden}`);
}
});
test("official SQLite lease lifecycle remains visible through idle renew, release, and expiry", async () => {
const connectionA = "11111111-1111-4111-8111-111111111111";
const connectionB = "22222222-2222-4222-8222-222222222222";
const managedKey = await apiKeys.createApiKey(
"Lifecycle managed key",
"0123456789abcdef",
["lease:exclusive"],
{ allowedConnections: [connectionA, connectionB] }
);
const managed = await apiKeys.getExclusiveLeaseConnectionIds();
assert.equal(managed.has(connectionA), true);
assert.equal(managed.has(connectionB), true);
const acquired = leases.acquireExclusiveConnectionLease({
leaseOwnerId: OWNER_A,
apiKeyId: managedKey.id,
provider: "codex",
connectionId: connectionA,
now: at(0),
ttlMs: 120_000,
});
assert.equal(acquired.kind, "ACQUIRED");
if (acquired.kind !== "ACQUIRED") return;
assert.equal(projectOfficialOccupancy([connectionA], at(30_000)).length, 1);
assert.equal(projectOfficialOccupancy([connectionA], at(30_000))[0].active, false);
const renewed = leases.renewExclusiveConnectionLease({
leaseOwnerId: OWNER_A,
generation: acquired.lease.generation,
apiKeyId: managedKey.id,
now: at(60_000),
ttlMs: 120_000,
});
assert.equal(renewed.kind, "RENEWED");
if (renewed.kind !== "RENEWED") return;
assert.equal(renewed.lease.generation, acquired.lease.generation);
assert.equal(projectOfficialOccupancy([connectionA], at(150_000)).length, 1);
assert.equal(
leases.assertExclusiveConnectionLeaseFence({
leaseOwnerId: OWNER_A,
generation: acquired.lease.generation,
apiKeyId: managedKey.id,
connectionId: connectionA,
now: at(150_000),
}).kind,
"VALID"
);
assert.equal(
leases.releaseExclusiveConnectionLease({
leaseOwnerId: OWNER_A,
generation: acquired.lease.generation + 1,
apiKeyId: managedKey.id,
now: at(151_000),
}).kind,
"STALE"
);
assert.equal(projectOfficialOccupancy([connectionA], at(152_000)).length, 1);
assert.equal(
leases.releaseExclusiveConnectionLease({
leaseOwnerId: OWNER_A,
generation: acquired.lease.generation,
apiKeyId: managedKey.id,
now: at(153_000),
}).kind,
"RELEASED"
);
assert.equal(projectOfficialOccupancy([connectionA], at(154_000)).length, 0);
const expiring = leases.acquireExclusiveConnectionLease({
leaseOwnerId: OWNER_B,
apiKeyId: managedKey.id,
provider: "codex",
connectionId: connectionB,
now: at(200_000),
ttlMs: 1_000,
});
assert.equal(expiring.kind, "ACQUIRED");
if (expiring.kind !== "ACQUIRED") return;
assert.equal(projectOfficialOccupancy([connectionB], at(200_500)).length, 1);
assert.equal(leases.reconcileExpiredExclusiveConnectionLeases(at(202_000)), 1);
assert.equal(projectOfficialOccupancy([connectionB], at(202_000)).length, 0);
const reacquired = leases.acquireExclusiveConnectionLease({
leaseOwnerId: OWNER_B,
apiKeyId: managedKey.id,
provider: "codex",
connectionId: connectionB,
now: at(203_000),
});
assert.equal(reacquired.kind, "ACQUIRED");
if (reacquired.kind !== "ACQUIRED") return;
assert.equal(reacquired.lease.generation, expiring.lease.generation + 1);
assert.equal(
leases.assertExclusiveConnectionLeaseFence({
leaseOwnerId: OWNER_B,
generation: expiring.lease.generation,
apiKeyId: managedKey.id,
connectionId: connectionB,
now: at(204_000),
}).kind,
"STALE"
);
assert.equal(
leases.releaseExclusiveConnectionLease({
leaseOwnerId: OWNER_B,
generation: reacquired.lease.generation,
apiKeyId: managedKey.id,
now: at(205_000),
}).kind,
"RELEASED"
);
});
test("sessions API keeps legacy fields additive and decorates only in-flight leased work", async () => {
const connection = await providers.createProviderConnection({
provider: "codex",
authType: "oauth",
name: "Friendly Lease Account",
accessToken: "synthetic-local-token",
});
const managedKey = await apiKeys.createApiKey(
"Route managed key",
"fedcba9876543210",
["lease:exclusive"],
{ allowedConnections: [connection.id] }
);
const acquired = leases.acquireExclusiveConnectionLease({
leaseOwnerId: "vlo_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
apiKeyId: managedKey.id,
provider: "codex",
connectionId: connection.id,
});
assert.equal(acquired.kind, "ACQUIRED");
if (acquired.kind !== "ACQUIRED") return;
sessionManager.touchSession("legacy-unmanaged", "legacy-connection");
const idleResponse = await sessionsRoute.GET();
const idleBody = (await idleResponse.json()) as Record<string, unknown>;
assert.equal(idleResponse.status, 200);
assert.equal(idleBody.count, 1);
assert.equal(Array.isArray(idleBody.sessions), true);
assert.equal(
(idleBody.sessions as Array<{ sessionId: string }>)[0].sessionId,
"legacy-unmanaged"
);
assert.deepEqual(idleBody.byApiKey, {});
const idleLease = (idleBody.exclusiveSessions as Array<Record<string, unknown>>)[0];
assert.equal(idleLease.connectionId, connection.id);
assert.equal(idleLease.connectionName, "Friendly Lease Account");
assert.equal(idleLease.active, false);
for (const forbidden of ["leaseOwnerHash", "lease_owner_hash", "generation", "apiKeyId"]) {
assert.equal(JSON.stringify(idleBody).includes(forbidden), false);
}
usageHistory.trackPendingRequest("gpt-5.6-sol", "codex", connection.id, true);
const activeBody = (await (await sessionsRoute.GET()).json()) as {
exclusiveSessions: Array<{ active: boolean }>;
};
assert.equal(activeBody.exclusiveSessions[0].active, true);
usageHistory.trackPendingRequest("gpt-5.6-sol", "codex", connection.id, false);
assert.equal(
leases.releaseExclusiveConnectionLease({
leaseOwnerId: "vlo_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
generation: acquired.lease.generation,
apiKeyId: managedKey.id,
}).kind,
"RELEASED"
);
const releasedBody = (await (await sessionsRoute.GET()).json()) as {
count: number;
sessions: Array<{ sessionId: string }>;
exclusiveSessions: unknown[];
};
assert.equal(releasedBody.count, 1);
assert.equal(releasedBody.sessions[0].sessionId, "legacy-unmanaged");
assert.deepEqual(releasedBody.exclusiveSessions, []);
});
test("sessions route keeps raw lease SQL out of the API and sanitizes failures", () => {
const route = fs.readFileSync(
new URL("../../src/app/api/sessions/route.ts", import.meta.url),
"utf8"
);
assert.match(route, /getExclusiveLeaseConnectionIds/);
assert.match(route, /getExclusiveLeaseOccupancy/);
assert.match(route, /getPendingRequests/);
assert.match(route, /sanitizeErrorMessage\(error\)/);
assert.doesNotMatch(route, /SELECT\s|exclusive_connection_leases/i);
});

View File

@@ -16,7 +16,6 @@ const { __setTlsFetchOverrideForTesting: __setPplxTlsFetchOverride } =
const { __setTlsFetchOverrideForTesting: __setGrokTlsFetchOverride } =
await import("../../open-sse/services/grokTlsClient.ts");
const originalFetch = globalThis.fetch;
test.afterEach(() => {
@@ -1232,9 +1231,13 @@ test("local OpenAI-style providers validate without sending Authorization when a
});
test("OpenAI-compatible validator covers /responses mode and final ping fallback", async () => {
const calls = [];
const calls: Array<{ url: string; method: string; body: string | undefined }> = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), method: init.method || "GET" });
calls.push({
url: String(url),
method: init.method || "GET",
body: typeof init.body === "string" ? init.body : undefined,
});
if (String(url).endsWith("/models")) {
return new Response(JSON.stringify({ error: "no models" }), { status: 500 });
}
@@ -1282,6 +1285,11 @@ test("OpenAI-compatible validator covers /responses mode and final ping fallback
calls.map((call) => call.url),
["https://openai-like.example.com/v1/models", "https://openai-like.example.com/v1/responses"]
);
const responsesBody = JSON.parse(calls[1].body || "{}");
assert.deepEqual(responsesBody.input, [{ role: "user", content: "test" }]);
assert.equal(responsesBody.max_output_tokens, 1);
assert.equal(responsesBody.messages, undefined);
assert.equal(responsesBody.max_tokens, undefined);
assert.equal(pingFallback.valid, true);
assert.equal(pingFallback.error, null);
});

View File

@@ -36,9 +36,10 @@ test("SEARCH_PROVIDERS has all registered providers", () => {
assert.ok(SEARCH_PROVIDERS["jina-search"], "jina-search should exist");
assert.ok(SEARCH_PROVIDERS["duckduckgo-free"], "duckduckgo-free should exist");
assert.ok(SEARCH_PROVIDERS["x-search"], "x-search should exist");
// #11140: context7 (library-docs search) is the 17th registered provider
assert.ok(SEARCH_PROVIDERS["xquik-search"], "xquik-search should exist");
// #11140: context7 provides library-docs search
assert.ok(SEARCH_PROVIDERS["context7"], "context7 should exist");
assert.equal(Object.keys(SEARCH_PROVIDERS).length, 17);
assert.equal(Object.keys(SEARCH_PROVIDERS).length, 18);
});
test("duckduckgo-free config is a no-key, fallback-only provider", () => {
@@ -172,11 +173,11 @@ test("zai-search config is correct", () => {
test("getAllSearchProviders returns flat list", () => {
const all = getAllSearchProviders();
// #11140: 17 providers with context7 registered
assert.equal(all.length, 17);
assert.equal(all.length, 18);
assert.ok(all.some((p) => p.id === "duckduckgo-free"));
assert.ok(all.some((p) => p.id === "jina-search"));
assert.ok(all.some((p) => p.id === "x-search"));
assert.ok(all.some((p) => p.id === "xquik-search"));
assert.ok(all.some((p) => p.id === "serper-search"));
assert.ok(all.some((p) => p.id === "brave-search"));
assert.ok(all.some((p) => p.id === "perplexity-search"));
@@ -420,6 +421,7 @@ test("v1SearchSchema accepts new search providers", async () => {
"duckduckgo-free",
"firecrawl",
"x-search",
"xquik-search",
] as const;
for (const provider of providers) {

View File

@@ -52,7 +52,7 @@ test("v1 search GET lists all search providers", async () => {
assert.equal(response.status, 200);
assert.equal(body.object, "list");
assert.equal(body.data.length, 17);
assert.equal(body.data.length, 18);
assert.deepEqual(ids, [
"serper-search",
"brave-search",
@@ -71,6 +71,7 @@ test("v1 search GET lists all search providers", async () => {
"context7",
"duckduckgo-free",
"x-search",
"xquik-search",
]);
});

View File

@@ -0,0 +1,139 @@
// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, expect, it, vi } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: (namespace: string) => (key: string) => {
if (namespace === "common" && key === "active") return "Localized active";
if (namespace === "usage" && key === "noSessions") return "Localized empty state";
return key;
},
}));
vi.mock("@/shared/components", () => ({
Card: ({ children }: { children: React.ReactNode }) => <div data-testid="card">{children}</div>,
}));
const { default: SessionsTab } =
await import("../../../src/app/(dashboard)/dashboard/usage/components/SessionsTab");
let container: HTMLDivElement;
let root: ReturnType<typeof createRoot>;
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
vi.unstubAllGlobals();
vi.clearAllMocks();
});
async function renderPayload(payload: Record<string, unknown>): Promise<void> {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => payload,
}))
);
await act(async () => {
root.render(<SessionsTab />);
await Promise.resolve();
await Promise.resolve();
});
}
it("keeps idle leases visible, de-duplicates legacy rows, and localizes only active work", async () => {
await renderPayload({
count: 2,
sessions: [
{
sessionId: "legacy-active",
ageMs: 1_000,
requestCount: 4,
connectionId: "conn-active",
},
{
sessionId: "legacy-unmanaged",
ageMs: 2_000,
requestCount: 1,
connectionId: "conn-unmanaged",
},
],
exclusiveSessions: [
{
sessionId: "lease:conn-active",
ageMs: null,
requestCount: 4,
connectionId: "conn-active",
connectionName: "Friendly active account",
leaseBacked: true,
active: true,
},
{
sessionId: "lease:conn-idle",
ageMs: null,
requestCount: 0,
connectionId: "conn-idle",
connectionName: "Friendly idle account",
leaseBacked: true,
active: false,
},
],
});
expect(container.querySelector("[title='lease:conn-active']")).not.toBeNull();
expect(container.querySelector("[title='lease:conn-idle']")).not.toBeNull();
expect(container.querySelector("[title='legacy-unmanaged']")).not.toBeNull();
expect(container.querySelector("[title='legacy-active']")).toBeNull();
expect(container.textContent).toContain("Friendly active account");
expect(container.textContent).toContain("Friendly idle account");
const activeLabels = Array.from(container.querySelectorAll("span")).filter(
(node) => node.textContent === "Localized active"
);
expect(activeLabels).toHaveLength(1);
const idleRow = container.querySelector("[title='lease:conn-idle']")?.closest("tr");
expect(idleRow?.textContent).not.toContain("Localized active");
expect(container.textContent).not.toContain("IDLE");
expect(container.querySelector("[data-testid='session-count']")?.textContent).toBe("3");
});
it("preserves a legacy-only response when additive lease fields are absent", async () => {
await renderPayload({
count: 1,
sessions: [
{
sessionId: "legacy-only",
ageMs: 1_000,
requestCount: 1,
connectionId: null,
},
],
byApiKey: {},
});
expect(container.querySelector("[title='legacy-only']")).not.toBeNull();
expect(container.textContent).not.toContain("Localized empty state");
expect(container.querySelector("[data-testid='session-count']")?.textContent).toBe("1");
});
it("keeps the localized empty state and zero count", async () => {
await renderPayload({ count: 0, sessions: [], byApiKey: {} });
expect(container.textContent).toContain("Localized empty state");
expect(container.querySelector("tbody")).toBeNull();
expect(container.querySelector("[data-testid='session-count']")?.textContent).toBe("0");
});

View File

@@ -0,0 +1,99 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { listExtractionConfigs } from "../../open-sse/services/tokenExtractionConfig.ts";
import {
buildWebSessionContract,
WEB_SESSION_CONTRACT_VERSION,
} from "../../src/lib/providers/webSessionContract.ts";
import { getWebSessionCredentialRequirement } from "../../src/shared/providers/webSessionCredentials.ts";
test("web-session contract mirrors canonical extraction and credential metadata", () => {
const contract = buildWebSessionContract();
assert.equal(contract.version, WEB_SESSION_CONTRACT_VERSION);
const expected = listExtractionConfigs().flatMap((config) => {
const requirement = getWebSessionCredentialRequirement(config.providerId);
return requirement && requirement.kind !== "none" ? [{ config, requirement }] : [];
});
assert.equal(contract.providers.length, expected.length);
assert.equal(
new Set(contract.providers.map((provider) => provider.providerId)).size,
expected.length
);
for (const { config, requirement } of expected) {
const published = contract.providers.find(
(provider) => provider.providerId === config.providerId
);
assert.ok(published, `${config.providerId} must be published`);
assert.equal(published.displayName, config.displayName);
assert.equal(published.loginUrl, config.loginUrl);
assert.equal(published.homeUrl, config.homeUrl);
assert.deepEqual(published.tokenSources, config.tokenSources);
assert.equal(published.credential.kind, requirement.kind);
assert.deepEqual(published.credential.storageKeys, [...requirement.storageKeys]);
assert.equal(published.credential.acceptsFullCookieHeader, requirement.acceptsFullCookieHeader);
}
});
test("web-session contract preserves representative token and cookie semantics", () => {
const providers = new Map(
buildWebSessionContract().providers.map((provider) => [provider.providerId, provider])
);
assert.equal(providers.get("deepseek-web")?.credential.kind, "token");
assert.equal(providers.get("zai-web")?.credential.kind, "token");
assert.equal(providers.get("gemini-web")?.credential.kind, "cookie");
assert.equal(providers.get("qwen-web")?.credential.kind, "cookie");
assert.ok(
providers
.get("deepseek-web")
?.tokenSources.some((source) => source.type === "localStorage" && source.key === "userToken")
);
assert.ok(
providers
.get("gemini-web")
?.tokenSources.some(
(source) =>
source.type === "cookie" &&
source.name === "__Secure-1PSID" &&
source.domain === ".google.com"
)
);
});
test("web-session contract excludes credential values and operator-only guidance", () => {
const serialized = JSON.stringify(buildWebSessionContract());
for (const forbidden of [
"placeholder",
"instructions",
"pollingConfig",
"credentialName",
"guideSteps",
"guideNote",
]) {
assert.equal(
serialized.includes(`\"${forbidden}\"`),
false,
`${forbidden} must not be published`
);
}
});
test("web-session contract route remains management-authenticated", () => {
const source = readFileSync(
new URL("../../src/app/api/providers/web-session-contract/route.ts", import.meta.url),
"utf8"
);
const authCall = source.indexOf("requireManagementAuth(request)");
const responseCall = source.indexOf("NextResponse.json(buildWebSessionContract())");
assert.ok(authCall >= 0, "route must require management authentication");
assert.ok(responseCall > authCall, "authentication must run before contract publication");
});

Some files were not shown because too many files have changed in this diff Show More