Compare commits
113 Commits
release/v3
...
chore/reco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a582bef675 | ||
|
|
f77254da79 | ||
|
|
b1474b992f | ||
|
|
86514aa481 | ||
|
|
a150c6bc40 | ||
|
|
c59661cfd6 | ||
|
|
b76866fa12 | ||
|
|
8a14cefcb1 | ||
|
|
158fb1a806 | ||
|
|
77ea656b12 | ||
|
|
471052b904 | ||
|
|
b8553c8f0d | ||
|
|
1255b5869b | ||
|
|
ea2746e594 | ||
|
|
c264c24de9 | ||
|
|
15cbc1ede7 | ||
|
|
a65666519d | ||
|
|
829634b741 | ||
|
|
09666c50e7 | ||
|
|
2b9f25accb | ||
|
|
9dc7711dc2 | ||
|
|
42c209797c | ||
|
|
8bbe92c692 | ||
|
|
bbc7bf4351 | ||
|
|
38527e4a21 | ||
|
|
56d64e29a4 | ||
|
|
7b36e45df8 | ||
|
|
ddee064f1b | ||
|
|
b1fdfd5ea4 | ||
|
|
71eeaf293c | ||
|
|
406f4524ff | ||
|
|
dfc5b5eec4 | ||
|
|
0a53c8a2ce | ||
|
|
93da24cd79 | ||
|
|
815c7c2864 | ||
|
|
8f15b79a84 | ||
|
|
07a378c86c | ||
|
|
34150506f2 | ||
|
|
76ac1c8b7e | ||
|
|
d732cf615d | ||
|
|
f58e8bef6f | ||
|
|
243445f210 | ||
|
|
13e29f2f39 | ||
|
|
2544ee9498 | ||
|
|
440113c8e8 | ||
|
|
3c2906a80e | ||
|
|
095f424658 | ||
|
|
20de0d9c79 | ||
|
|
9f30b76057 | ||
|
|
019ad33a61 | ||
|
|
dfc9257b07 | ||
|
|
37e71915db | ||
|
|
077bc1a8a2 | ||
|
|
378eff0f75 | ||
|
|
6de542b9b6 | ||
|
|
315b0a94e1 | ||
|
|
e1c2b347f9 | ||
|
|
f88aa48847 | ||
|
|
8301984734 | ||
|
|
028f1b91e4 | ||
|
|
2af1326adf | ||
|
|
644dd32d3f | ||
|
|
9df3f8923d | ||
|
|
fa7ccaf679 | ||
|
|
85b2a06fad | ||
|
|
0b7ac870ef | ||
|
|
9fedc1c411 | ||
|
|
e589831952 | ||
|
|
04d2a60331 | ||
|
|
d23bfefec0 | ||
|
|
c8ad44e018 | ||
|
|
c83116e634 | ||
|
|
7715825cb8 | ||
|
|
761d38f433 | ||
|
|
c6963ca5dd | ||
|
|
b010d8bf86 | ||
|
|
fdcd15e6a9 | ||
|
|
12b8df02dd | ||
|
|
38d21afc2d | ||
|
|
05e76d6e76 | ||
|
|
93135f8e18 | ||
|
|
22086a73fa | ||
|
|
d4ade9d1d3 | ||
|
|
f54c93c879 | ||
|
|
e2e48fdab8 | ||
|
|
d2cea0811a | ||
|
|
2f18a85310 | ||
|
|
38969ad16b | ||
|
|
dafb4ae808 | ||
|
|
c89bdb21a2 | ||
|
|
c5e694131a | ||
|
|
338c05dc6a | ||
|
|
6945bbaaba | ||
|
|
690f684bfc | ||
|
|
c3cd1f94c0 | ||
|
|
c21460f22a | ||
|
|
9b14896a6c | ||
|
|
29f26293c3 | ||
|
|
cb11592441 | ||
|
|
5ee646e68e | ||
|
|
6984676d95 | ||
|
|
79f8ae9d1e | ||
|
|
04b2c47940 | ||
|
|
24ac71465e | ||
|
|
8d6f91b558 | ||
|
|
c3698eedcb | ||
|
|
9e7096a048 | ||
|
|
a84f0b683e | ||
|
|
ae0b79ab7c | ||
|
|
6daa31de0b | ||
|
|
adca3b881c | ||
|
|
ac02c5b42f | ||
|
|
07d1816a45 |
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 353 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 351 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ── Multi-stage Dockerfile for Native Bun Runtime (web-latest-bun) ───────────
|
||||
FROM oven/bun:1.3.14-slim AS base
|
||||
FROM oven/bun:1.4.0-slim AS base
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -50,7 +50,7 @@ ENV NODE_ENV=production
|
||||
RUN bun run --quiet build
|
||||
|
||||
# ── Runner Base stage (100% Bun Native Production Runtime) ──────────────────
|
||||
FROM oven/bun:1.3.14-slim AS runner-base
|
||||
FROM oven/bun:1.4.0-slim AS runner-base
|
||||
|
||||
LABEL org.opencontainers.image.title="omniroute" \
|
||||
org.opencontainers.image.description="Unified AI proxy — route any LLM through one endpoint (Bun Native)" \
|
||||
|
||||
12
README.md
@@ -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 → 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 15–95% tokens (~89% avg) — never hit limits. 353 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 → 351 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 15–95% tokens (~89% avg) — never hit limits. 351 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 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."/>
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 351 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 351 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: 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."/>
|
||||
<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: 351 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 & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & 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 **353-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 **351-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">
|
||||
|
||||
## 🌐 353 AI Providers — 154 Catalog-Marked Free
|
||||
## 🌐 351 AI Providers — 154 Catalog-Marked Free
|
||||
|
||||
</div>
|
||||
|
||||
> **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).
|
||||
> **351 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">
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- **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))
|
||||
@@ -1 +0,0 @@
|
||||
- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)).
|
||||
1
changelog.d/fixes/7592-windows-packaged-smoke.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\<name>` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override
|
||||
@@ -12,6 +12,7 @@
|
||||
"@dnd-kit/core",
|
||||
"@dnd-kit/sortable",
|
||||
"@dnd-kit/utilities",
|
||||
"@eslint/compat",
|
||||
"@huggingface/transformers",
|
||||
"@lobehub/icons",
|
||||
"@modelcontextprotocol/sdk",
|
||||
@@ -60,6 +61,7 @@
|
||||
"eslint",
|
||||
"eslint-config-next",
|
||||
"eslint-plugin-sonarjs",
|
||||
"espree",
|
||||
"express",
|
||||
"fast-check",
|
||||
"fetch-socks",
|
||||
|
||||
@@ -2372,16 +2372,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/lib/guardrails/videoBridgeContactSheet.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/guardrails/videoBridgeRuntime.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/guardrails/visionBridgeHelpers.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
@@ -2422,11 +2412,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/memory/embedding/index.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/lib/memory/genericBackend.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
|
||||
@@ -478,8 +478,7 @@
|
||||
"_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).",
|
||||
"open-sse/services/autoCombo/virtualFactory.ts": 1130
|
||||
"_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_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).",
|
||||
@@ -647,6 +646,5 @@
|
||||
"_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_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."
|
||||
"_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."
|
||||
}
|
||||
|
||||
98
docs/design/EXTERNAL-DB-BACKEND.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Design: Pluggable External Durable-State DB Backend (PostgreSQL / MySQL)
|
||||
|
||||
Status: PROPOSAL · Discussion issue: [#8075](https://github.com/diegosouzapw/OmniRoute/issues/8075) · Author: @oyi77
|
||||
|
||||
Grounded in the tree at `release/v3.8.51` (`3192eb88d`). Every number below was measured on that tree, not estimated.
|
||||
|
||||
---
|
||||
|
||||
## 0. Why this document exists
|
||||
|
||||
#8075 asks five questions (backends & order, abstraction contract, what stays SQLite-only, config surface, migration path). This document answers them with a census of how deeply the current data layer is coupled to SQLite, and proposes a repository-contract seam that lets external backends be adopted per domain instead of via a big-bang driver swap.
|
||||
|
||||
## 1. Measured coupling census (`release/v3.8.51`)
|
||||
|
||||
| Surface | Count | Implication for external backends |
|
||||
|---|---|---|
|
||||
| `src/lib/db/*.ts` modules calling `.prepare()` / `.transaction()` | **126 files** | The synchronous SQLite call shape is the de-facto data API; every module is a translation candidate |
|
||||
| `.pragma()` call sites | **36** | No equivalent in PG/MySQL — each needs an audit (many are performance tuning that becomes server config) |
|
||||
| SQLite migrations on disk | **159** | A second dialect needs its own migration lineage, not a translator |
|
||||
| `sqlite-vec` / `vec0` referencing files | **9** | Vector search must move to an external store (Qdrant proposed in #8075) |
|
||||
| FTS5 referencing files | **8** | Maps to Postgres `tsvector` / MySQL `FULLTEXT` — dialect work |
|
||||
| Existing driver adapters | **4** (`better-sqlite3`, `node:sqlite`, `bun:sqlite`, `sql.js`) behind `SqliteAdapter` (`src/lib/db/adapters/types.ts`) | Proves a seam exists at the DRIVER level — but all four are SQLite; the seam OmniRoute actually needs is one level up |
|
||||
|
||||
The decisive fact: **the adapter interface is synchronous** (`prepare().run/get/all`, sync closure transactions). PostgreSQL and MySQL drivers are inherently async. A faithful external backend behind `SqliteAdapter` is therefore impossible without faking sync over async — which is exactly the trap #8037/#8073 already flagged ("not a drop-in ORM/driver swap"). The contract must live ABOVE the adapter layer.
|
||||
|
||||
## 2. Proposed contract: durable-state repositories, not a driver swap
|
||||
|
||||
Introduce a `DurableStateStore` port with per-domain repositories, defined once and implemented per dialect:
|
||||
|
||||
```ts
|
||||
// src/lib/db/ports/ — dialect-agnostic, async, transactional by unit-of-work
|
||||
interface UnitOfWork {
|
||||
connections: ConnectionRepository; // provider connections + credentials
|
||||
apiKeys: ApiKeyRepository; // keys, groups, usage-limit fields
|
||||
combos: ComboRepository; // combo defs + routing policies
|
||||
quotaState: QuotaStateRepository; // quota/account state, cooldowns
|
||||
sessionAffinity: AffinityRepository; // sticky routing state
|
||||
audit: AuditRepository; // config audit log
|
||||
}
|
||||
interface DurableStateStore {
|
||||
readonly driver: "sqlite" | "postgres" | "mysql";
|
||||
withUnitOfWork<T>(fn: (uow: UnitOfWork) => Promise<T>): Promise<T>;
|
||||
healthCheck(): Promise<{ ok: boolean; latencyMs: number }>;
|
||||
}
|
||||
```
|
||||
|
||||
Deliberate exclusions from v1 (stay SQLite-local, matching #8075 §3): WAL/VACUUM/PRAGMA lifecycle, file backup/import/export, `sqlite-vec` vector storage (external backends delegate vectors to Qdrant), FTS (dialect-native fulltext later).
|
||||
|
||||
### What makes each repository shippable
|
||||
|
||||
The single-writer pattern OmniRoute already uses internally (targeted raw-SQL stat bumps like `touchConnectionLastUsed`, single-writer caches) maps cleanly onto `UnitOfWork`. Repositories are adopted **per domain**: a deployment can run `postgres` for connections+keys while hot-path ephemeral state stays local — because the port is injected at the repository boundary, not under 126 modules.
|
||||
|
||||
## 3. Sequencing: PostgreSQL first, MySQL second
|
||||
|
||||
1. **Postgres-first** (`pg` + `pg-pool`): richer native JSONB (several modules persist JSON blobs), `tsvector`, `INSERT ... ON CONFLICT` semantics closest to `INSERT OR REPLACE` usage.
|
||||
2. **MySQL 8+ after**, gated by a shared **dialect-conformance suite**: the same behavioral test pack runs against both implementations (schema bootstrap, UoW rollback, credential round-trip incl. encryption-at-rest fields, pagination/ordering contracts). A backend is "supported" only when the suite is green.
|
||||
|
||||
Rationale: one conformance suite prevents the MySQL implementation from drifting into a shim that passes smoke tests but corrupts ordering/collation edge cases.
|
||||
|
||||
## 4. Config surface
|
||||
|
||||
```
|
||||
DATABASE_DRIVER=sqlite # sqlite (default) | postgres | mysql
|
||||
DATABASE_URL=postgres://... # required when driver != sqlite
|
||||
DATABASE_POOL_MAX=10
|
||||
DATABASE_TLS_REJECT_UNAUTHORIZED=1
|
||||
DATABASE_MIGRATIONS_TABLE=_omniroute_migrations
|
||||
```
|
||||
|
||||
- Default stays `sqlite` — zero-config npm/Electron/Termux behavior is non-negotiable (#8075 §6).
|
||||
- Health check surfaces in the existing `/api/monitoring/health` payload next to `inflightRequests`.
|
||||
- Migrations: per-dialect lineage directories (`migrations-postgres/`, `migrations-mysql/`) with their own runner sharing the safety checks (mass-migration abort, pre-migration backup → pg_dump/mysqldump equivalent) already proven in `migrationRunner.ts`.
|
||||
|
||||
## 5. Migration path (one-way export tool)
|
||||
|
||||
`omniroute db export --to postgres --dsn ...`:
|
||||
1. Snapshot `storage.sqlite` (reuse `createPreMigrationBackup`).
|
||||
2. Replay schema from the target dialect's lineage to head.
|
||||
3. Copy tables row-by-row through the new repositories (not raw SQL translation), preserving IDs; re-encrypt nothing (credential ciphertexts are dialect-neutral strings today).
|
||||
4. Verify with row-count + checksum comparison per table; write a receipt file.
|
||||
5. Refuse to continue on any mismatch (fail-closed).
|
||||
|
||||
## 6. Phased delivery plan
|
||||
|
||||
| Phase | Deliverable | Risk gate |
|
||||
|---|---|---|
|
||||
| P1 | `ports/` contract + SQLite implementation of `ConnectionRepository` + conformance-suite skeleton running against SQLite itself | Suite green on the reference driver |
|
||||
| P2 | Postgres implementation of the same repo + suite green on PG | Suite green on PG |
|
||||
| P3 | Remaining domains (apiKeys, combos, quotaState, affinity, audit) on PG | Full-suite parity vs SQLite run |
|
||||
| P4 | MySQL via conformance suite | Suite green on MySQL |
|
||||
| P5 | Export tool + docs | Round-trip receipt on a 1GB-scale fixture |
|
||||
|
||||
P1–P2 alone deliver value: a clustered deployment can share connection/key state while everything else stays local.
|
||||
|
||||
## 7. Explicitly out of scope
|
||||
|
||||
- Translating the remaining ~120 SQLite-shaped modules verbatim ("just make prepare() hit Postgres") — rejected: it fakes sync over async and preserves dialect bugs forever.
|
||||
- Replacing `sqlite-vec` with pgvector — #8075 names Qdrant; keeping vector search out-of-process also keeps the lean proxy core lean (4.0 modular direction).
|
||||
@@ -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 (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.">
|
||||
<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 (351 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"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -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: 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.">
|
||||
<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: 351 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 |
@@ -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 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.">
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 351 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">353 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">351 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 353 providers in</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 351 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 |
@@ -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 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.">
|
||||
<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 351 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: 351 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">353 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">351 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  →  <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 |
@@ -24,11 +24,8 @@ it expires.
|
||||
| Antigravity / Devin Desktop | Built-in quotas |
|
||||
|
||||
**Strategy**: route here first for every request that fits the model's
|
||||
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).
|
||||
strengths. Quota tracker monitors approaching reset; combo strategies
|
||||
`reset-aware` and `subscription` prioritize accordingly.
|
||||
|
||||
## Tier 2 — Cheap
|
||||
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: "Provider Reference"
|
||||
version: 3.8.51
|
||||
lastUpdated: 2026-08-25
|
||||
version: 3.8.50
|
||||
lastUpdated: 2026-08-24
|
||||
---
|
||||
|
||||
# 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-25
|
||||
> **Last generated:** 2026-08-24
|
||||
|
||||
Total providers: **353**. See category breakdown below.
|
||||
Total providers: **351**. 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) (233)
|
||||
## API Key Providers (paid / paid-with-free-credits) (231)
|
||||
|
||||
| ID | Alias | Name | Tags | Website | Notes |
|
||||
|----|-------|------|------|---------|-------|
|
||||
@@ -342,8 +342,6 @@ 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) | — |
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
---
|
||||
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` |
|
||||
@@ -45,6 +45,7 @@
|
||||
"copyright": "Copyright © 2025 OmniRoute",
|
||||
"buildDependenciesFromSource": false,
|
||||
"npmRebuild": false,
|
||||
"afterPack": "../scripts/build/afterpack-copy-node-modules.mjs",
|
||||
"directories": {
|
||||
"output": "dist-electron",
|
||||
"buildResources": "assets"
|
||||
@@ -58,6 +59,7 @@
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"loginManager.js",
|
||||
"lib/loginHeaderCapture.js",
|
||||
"processTree.js",
|
||||
"sqlite-inspection.js",
|
||||
"remoteServerPromptPreload.js",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { fixupConfigRules } from "@eslint/compat";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import * as espree from "espree";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
// #7879: bar NEW local `toNumber` definitions outside the canonical helper.
|
||||
@@ -37,7 +39,22 @@ const IMPORT_BOUNDARY_RESTRICTIONS = {
|
||||
|
||||
/** @type {import("eslint").Linter.Config[]} */
|
||||
const eslintConfig = [
|
||||
...nextVitals,
|
||||
...fixupConfigRules(nextVitals),
|
||||
// eslint-config-next's Babel parser has not adopted ESLint 10's ScopeManager
|
||||
// finalize contract yet. Plain JS/JSX does not need that parser, so use the
|
||||
// ESLint-native parser while retaining Next's plugins and rules.
|
||||
{
|
||||
files: ["**/*.{js,jsx,mjs,cjs}"],
|
||||
languageOptions: {
|
||||
parser: espree,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{mts,cts}"],
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
},
|
||||
},
|
||||
// Pacote 4 (plano mestre testes+CI, 2026-07-04) — zero-warning policy: TODA regra roda
|
||||
// como "error" e a dívida pré-existente vive congelada por arquivo+regra em
|
||||
// config/quality/eslint-suppressions.json (ESLint bulk suppressions nativo). Violação
|
||||
@@ -133,28 +150,45 @@ const eslintConfig = [
|
||||
"no-restricted-syntax": "off",
|
||||
},
|
||||
},
|
||||
// Relaxed rules for open-sse and tests (incremental adoption)
|
||||
// Relaxed rules for TypeScript in open-sse and tests (incremental adoption).
|
||||
// eslint-config-next already registers @typescript-eslint for every TS file;
|
||||
// registering a second plugin object is rejected by ESLint 10.
|
||||
{
|
||||
files: ["open-sse/**/*.ts", "tests/**/*.mjs", "tests/**/*.ts"],
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
files: ["open-sse/**/*.ts", "tests/**/*.ts"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@next/next/no-assign-module-variable": "off",
|
||||
"react-hooks/rules-of-hooks": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["tests/**/*.mjs"],
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
rules: {
|
||||
"@next/next/no-assign-module-variable": "off",
|
||||
"react-hooks/rules-of-hooks": "off",
|
||||
},
|
||||
},
|
||||
// JS/JSX files do not match eslint-config-next's TypeScript block. Register
|
||||
// the plugin only for that disjoint scope so the shared unused-vars ratchet
|
||||
// works without redefining the plugin for TS/TSX under ESLint 10.
|
||||
{
|
||||
files: ["src/**/*.{js,jsx}"],
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
},
|
||||
// Ratchet: bar NEW unused vars/args/catches outside the `_` escape hatch.
|
||||
// Pre-existing violations are frozen via config/quality/eslint-suppressions.json
|
||||
// (same pattern as #7879 toNumber); only genuinely NEW unused bindings fail
|
||||
// lint. `args: "all"` (not `after-used`) so a leading unused param is never
|
||||
// silently skipped, e.g. `function handle(req, _opts, next)` must flag `req`.
|
||||
// eslint-config-next already registers @typescript-eslint; registering it again
|
||||
// in this block is rejected by ESLint 10.
|
||||
{
|
||||
files: ["src/**/*.{ts,tsx,js,jsx}", "open-sse/**/*.ts", "tests/**/*.{ts,tsx,mjs}"],
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
|
||||
6
llm.txt
@@ -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 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.
|
||||
> 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 351 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
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **351 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
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **351-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)
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* 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.",
|
||||
},
|
||||
];
|
||||
@@ -408,6 +408,7 @@ export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const EMBEDDING_PROVIDER_ALIASES: Record<string, string> = {
|
||||
@@ -470,6 +471,38 @@ export function getEmbeddingProvider(providerId: string): EmbeddingProvider | nu
|
||||
return EMBEDDING_PROVIDERS[resolveEmbeddingProviderId(providerId)] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive an OpenAI-compatible embeddings config for a chat provider that has NO
|
||||
* curated EMBEDDING_PROVIDERS entry. Works for any registry provider whose base
|
||||
* URL ends in /chat/completions by swapping that suffix for /embeddings (groq,
|
||||
* mistral, together, upstage, fireworks, nvidia, vercel-ai-gateway, ...).
|
||||
* Dynamic-URL providers (no usable static base) derive to
|
||||
* null — they need bespoke URL handling, not a bogus endpoint.
|
||||
*
|
||||
* This is a FALLBACK only: callers must check getEmbeddingProvider() first so
|
||||
* curated entries keep their specialized configuration.
|
||||
*/
|
||||
export function deriveEmbeddingProviderForChatProvider(
|
||||
providerId: string,
|
||||
chatEntry: { id?: string; baseUrl?: string | string[] } | null | undefined
|
||||
): EmbeddingProvider | null {
|
||||
if (!chatEntry) return null;
|
||||
const rawBase = Array.isArray(chatEntry.baseUrl)
|
||||
? chatEntry.baseUrl[0]
|
||||
: chatEntry.baseUrl;
|
||||
if (!rawBase || typeof rawBase !== "string") return null;
|
||||
// stripTrailingSlashes-equivalent without importing open-sse utils here:
|
||||
const base = rawBase.replace(/\/+$/, "");
|
||||
if (!base.endsWith("/chat/completions")) return null;
|
||||
return {
|
||||
id: providerId,
|
||||
baseUrl: `${base.slice(0, -"/chat/completions".length)}/embeddings`,
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse embedding model string (format: "provider/model" or just "model")
|
||||
* Returns { provider, model }
|
||||
@@ -485,6 +518,18 @@ export function parseEmbeddingModel(
|
||||
const slashIdx = modelStr.indexOf("/");
|
||||
if (slashIdx > 0) {
|
||||
const rawProvider = modelStr.slice(0, slashIdx);
|
||||
|
||||
// A configured provider_node whose prefix exactly equals the requested
|
||||
// provider segment always wins — even when that segment is also an alias
|
||||
// of a curated provider (a local node must not be hijacked by a registry
|
||||
// alias). Same exact-match precedence documented for
|
||||
// EMBEDDING_MODEL_ALIASES above.
|
||||
const dynamicExact =
|
||||
dynamicProviders && dynamicProviders.find((dp) => dp.id === rawProvider);
|
||||
if (dynamicExact) {
|
||||
return { provider: rawProvider, model: modelStr.slice(slashIdx + 1) };
|
||||
}
|
||||
|
||||
const resolvedProvider = resolveEmbeddingProviderId(rawProvider);
|
||||
|
||||
if (EMBEDDING_PROVIDERS[resolvedProvider]) {
|
||||
|
||||
@@ -12,5 +12,15 @@
|
||||
export const OPENCODE_ZEN_GO_SHARED_MODELS = Object.freeze([
|
||||
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code" },
|
||||
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude", supportsVision: false },
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude", supportsVision: false },
|
||||
{
|
||||
id: "qwen3.6-plus",
|
||||
name: "Qwen3.6 Plus",
|
||||
targetFormat: "claude",
|
||||
supportsVision: false,
|
||||
// #10788: effort-tier aliases exist as explicit registry rows; declare the
|
||||
// vocabulary on the shared base row so variant synthesis and the sanitizer
|
||||
// agree on it for both opencode-go and opencode-zen.
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -18,7 +18,17 @@ export const nvidiaProvider: RegistryEntry = {
|
||||
passthroughModels: true,
|
||||
models: [
|
||||
// #6108: z-ai/glm-5.1 EOL'd 2026-07-02 (direct probe returns 410) — dropped.
|
||||
{ id: "z-ai/glm-5.2", name: "GLM 5.2" },
|
||||
// #10788: NVIDIA's hosted GLM-5.2 exposes a BINARY thinking switch
|
||||
// (chat_template_kwargs.enable_thinking), not effort tiers — see
|
||||
// mapNvidiaGlm52ReasoningParams. Declaring an empty tier list keeps the
|
||||
// catalog from synthesizing unresolvable -low/-high/-max variant ids while
|
||||
// still marking the model reasoning-capable.
|
||||
{
|
||||
id: "z-ai/glm-5.2",
|
||||
name: "GLM 5.2",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: [],
|
||||
},
|
||||
// #3329/#6108: minimaxai/minimax-m3 stays excluded from the nvidia tier — it
|
||||
// still 404s here for most callers; the single 200 probe in #6108 was not
|
||||
// reproducible enough to override the #3329 guard. Re-add only once NVIDIA
|
||||
|
||||
@@ -19,9 +19,15 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
// `kimi-k2.7-code` (the live API rejects the plain `kimi-k2.7` alias for
|
||||
// `/chat/completions`, even though the docs config example uses it).
|
||||
// GLM-5.2 — base model + effort-tier aliases (#6922).
|
||||
// OpencodeExecutor rewrites the alias to the canonical id and injects
|
||||
// reasoning_effort, mirroring the deepseek-v4-pro-* pattern.
|
||||
{ id: "glm-5.2", name: "GLM-5.2", supportsReasoning: true },
|
||||
// #10788: the tier vocabulary is declared on the base row so the catalog's
|
||||
// variant synthesis (#9485) and the effort sanitizer share one source of
|
||||
// truth with OpencodeExecutor's EFFORT_TIERS.
|
||||
{
|
||||
id: "glm-5.2",
|
||||
name: "GLM-5.2",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
{ id: "glm-5.2-high", name: "GLM-5.2 (high effort)", supportsReasoning: true },
|
||||
{ id: "glm-5.2-max", name: "GLM-5.2 (max effort)", supportsReasoning: true },
|
||||
|
||||
@@ -34,11 +40,16 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6" },
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
// #8353: Kimi K3 base + max-effort alias from the OpenCode Go registry.
|
||||
{ id: "kimi-k3", name: "Kimi K3", supportsReasoning: true },
|
||||
{ id: "kimi-k3", name: "Kimi K3", supportsReasoning: true, supportedThinkingEfforts: ["max"] },
|
||||
{ id: "kimi-k3-max", name: "Kimi K3 (max effort)", supportsReasoning: true },
|
||||
// MiMo-V2.5 — base model + effort-tier aliases (#6922).
|
||||
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", supportsReasoning: true },
|
||||
{ id: "mimo-v2.5", name: "MiMo-V2.5", supportsReasoning: true },
|
||||
{
|
||||
id: "mimo-v2.5",
|
||||
name: "MiMo-V2.5",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
{ id: "mimo-v2.5-high", name: "MiMo-V2.5 (high effort)", supportsReasoning: true },
|
||||
{ id: "mimo-v2.5-max", name: "MiMo-V2.5 (max effort)", supportsReasoning: true },
|
||||
// #3110: MiniMax M3 via OpenCode Go tier
|
||||
@@ -59,7 +70,14 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
// so combo routing skips them when the request contains image blocks,
|
||||
// preventing image content from reaching a vision-incapable upstream.
|
||||
// #8353: effort-tier aliases from the OpenCode Go registry.
|
||||
{ id: "qwen3.7-max", name: "Qwen3.7 Max", targetFormat: "claude", supportsVision: false },
|
||||
{
|
||||
id: "qwen3.7-max",
|
||||
name: "Qwen3.7 Max",
|
||||
targetFormat: "claude",
|
||||
supportsVision: false,
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
{
|
||||
id: "qwen3.7-max-high",
|
||||
name: "Qwen3.7 Max (high effort)",
|
||||
@@ -79,6 +97,8 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
name: "Qwen3.7 Plus",
|
||||
targetFormat: "claude",
|
||||
supportsVision: false,
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
{
|
||||
id: "qwen3.7-plus-high",
|
||||
@@ -111,7 +131,13 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
supportsReasoning: true,
|
||||
},
|
||||
// #8353: hy3 is the Go-tier base id (distinct from hy3-preview / hy3-free).
|
||||
{ id: "hy3", name: "Hunyuan3", contextLength: 256000, supportsReasoning: true },
|
||||
{
|
||||
id: "hy3",
|
||||
name: "Hunyuan3",
|
||||
contextLength: 256000,
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["none", "low", "high"],
|
||||
},
|
||||
{
|
||||
id: "hy3-none",
|
||||
name: "Hunyuan3 (none effort)",
|
||||
@@ -201,7 +227,12 @@ export const opencode_goProvider: RegistryEntry = {
|
||||
targetFormat: "openai-responses",
|
||||
},
|
||||
// #8353: Grok 4.5 + effort tiers from the OpenCode Go registry.
|
||||
{ id: "grok-4.5", name: "Grok 4.5", supportsReasoning: true },
|
||||
{
|
||||
id: "grok-4.5",
|
||||
name: "Grok 4.5",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["low", "medium", "high"],
|
||||
},
|
||||
{ id: "grok-4.5-low", name: "Grok 4.5 (low effort)", supportsReasoning: true },
|
||||
{ id: "grok-4.5-medium", name: "Grok 4.5 (medium effort)", supportsReasoning: true },
|
||||
{ id: "grok-4.5-high", name: "Grok 4.5 (high effort)", supportsReasoning: true },
|
||||
|
||||
@@ -79,18 +79,35 @@ export const opencode_zenProvider: RegistryEntry = {
|
||||
},
|
||||
|
||||
// ── DeepSeek ────────────────────────────────────────────────
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" },
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" },
|
||||
// #10788: same tier vocabulary as opencode-go's DeepSeek rows — the Zen
|
||||
// upstream accepts the identical effort set on these models.
|
||||
{
|
||||
id: "deepseek-v4-pro",
|
||||
name: "DeepSeek V4 Pro",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["none", "low", "high", "max"],
|
||||
},
|
||||
{
|
||||
id: "deepseek-v4-flash",
|
||||
name: "DeepSeek V4 Flash",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["none", "low", "high", "max"],
|
||||
},
|
||||
|
||||
// ── GLM / Z.AI ─────────────────────────────────────────────
|
||||
{ id: "glm-5.2", name: "GLM-5.2" },
|
||||
{
|
||||
id: "glm-5.2",
|
||||
name: "GLM-5.2",
|
||||
supportsReasoning: true,
|
||||
supportedThinkingEfforts: ["high", "max"],
|
||||
},
|
||||
|
||||
// ── MiniMax ────────────────────────────────────────────────
|
||||
// #3110: MiniMax M3 — frontier coding model with 1M context
|
||||
{ id: "minimax-m3", name: "MiniMax M3", contextLength: 1048576, supportsVision: true },
|
||||
|
||||
// ── Kimi / Moonshot ────────────────────────────────────────
|
||||
{ id: "kimi-k3", name: "Kimi K3" },
|
||||
{ id: "kimi-k3", name: "Kimi K3", supportsReasoning: true, supportedThinkingEfforts: ["max"] },
|
||||
// kimi-k2.7-code declared identically on opencode-go — see OPENCODE_ZEN_GO_SHARED_MODELS.
|
||||
|
||||
// ── Qwen ───────────────────────────────────────────────────
|
||||
|
||||
@@ -218,3 +218,29 @@ export function getAllRerankModels() {
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a Cohere-compatible rerank config for a chat provider that has NO
|
||||
* curated RERANK_PROVIDERS entry. Works for any registry provider whose base
|
||||
* URL ends in /chat/completions by swapping that suffix for /rerank (groq,
|
||||
* mistral, vercel-ai-gateway, ...). Dynamic-URL providers (no usable static
|
||||
* base, e.g. dynamic account-scoped hosts) derive to null — they need bespoke
|
||||
* URL handling.
|
||||
*
|
||||
* This is a FALLBACK only: callers must check getRerankProvider() first so
|
||||
* curated entries keep their specialized configuration and format adapters.
|
||||
*/
|
||||
export function deriveRerankProviderForChatProvider(providerId, chatEntry) {
|
||||
if (!chatEntry) return null;
|
||||
const rawBase = Array.isArray(chatEntry.baseUrl) ? chatEntry.baseUrl[0] : chatEntry.baseUrl;
|
||||
if (!rawBase || typeof rawBase !== "string") return null;
|
||||
const base = rawBase.replace(/\/+$/, "");
|
||||
if (!base.endsWith("/chat/completions")) return null;
|
||||
return {
|
||||
id: providerId,
|
||||
baseUrl: `${base.slice(0, -"/chat/completions".length)}/rerank`,
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,246 +1,218 @@
|
||||
import { SEARCH_PROVIDERS } from "../config/searchRegistry.ts";
|
||||
import { registerExecutor, getRegisteredExecutor, hasRegisteredExecutor } from "./registry.ts";
|
||||
import {
|
||||
registerLazyExecutor,
|
||||
loadRegisteredExecutor,
|
||||
hasRegisteredExecutor,
|
||||
} from "./registry.ts";
|
||||
// Type-only: pulls no runtime code, keeps DefaultExecutor the only eager class.
|
||||
import type { BaseExecutor } from "./base.ts";
|
||||
import { AntigravityExecutor } from "./antigravity.ts";
|
||||
import { GithubExecutor } from "./github.ts";
|
||||
import { GheCopilotExecutor } from "./ghe-copilot.ts";
|
||||
import { QoderExecutor } from "./qoder.ts";
|
||||
import { KiroExecutor } from "./kiro.ts";
|
||||
import { CodexExecutor } from "./codex.ts";
|
||||
import { CodexAppServerExecutor } from "./codex-app-server.ts";
|
||||
import { CursorExecutor } from "./cursor.ts";
|
||||
import { TraeExecutor } from "./trae.ts";
|
||||
import { DefaultExecutor } from "./default.ts";
|
||||
import { BedrockExecutor } from "./bedrock.ts";
|
||||
import { GlmExecutor } from "./glm.ts";
|
||||
import { PollinationsExecutor } from "./pollinations.ts";
|
||||
import { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
import { FreebuffExecutor } from "./freebuff.ts";
|
||||
import { OpencodeExecutor } from "./opencode.ts";
|
||||
import { VertexExecutor } from "./vertex.ts";
|
||||
import { CliproxyapiExecutor } from "./cliproxyapi.ts";
|
||||
import { DarioExecutor } from "./dario.ts";
|
||||
import { NineRouterExecutor } from "./ninerouter.ts";
|
||||
import { PerplexityWebExecutor } from "./perplexity-web.ts";
|
||||
import { GrokWebExecutor } from "./grok-web.ts";
|
||||
import { GeminiWebExecutor } from "./gemini-web.ts";
|
||||
import { TencentAIStudioWebExecutor } from "./tencent-aistudio-web.ts";
|
||||
import { GeminiBusinessExecutor } from "./gemini-business.ts";
|
||||
import { ChatGptWebExecutor } from "./chatgpt-web.ts";
|
||||
import { ChatGptWebCodexExecutor } from "./chatgpt-web-codex.ts";
|
||||
import { BlackboxWebExecutor } from "./blackbox-web.ts";
|
||||
import { MuseSparkWebExecutor } from "./muse-spark-web.ts";
|
||||
import { AzureOpenAIExecutor } from "./azure-openai.ts";
|
||||
import { AzureAiExecutor } from "./azure-ai.ts";
|
||||
import { CommandCodeExecutor } from "./commandCode.ts";
|
||||
import { GitlabExecutor } from "./gitlab.ts";
|
||||
import { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
import { DevinDesktopExecutor } from "./devin-desktop.ts";
|
||||
import { ZedHostedExecutor } from "./zed-hosted.ts";
|
||||
import { DevinCliExecutor } from "./devin-cli.ts";
|
||||
import { ZcodeExecutor } from "./zcode.ts";
|
||||
import { DevinCliAgenticExecutor } from "./devin-cli-agentic.ts";
|
||||
import { AuggieExecutor } from "./auggie.ts";
|
||||
import { DeepSeekWebExecutor } from "./deepseek-web.ts";
|
||||
import { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
|
||||
import { AdaptaWebExecutor } from "./adapta-web.ts";
|
||||
import { CopilotWebExecutor } from "./copilot-web.ts";
|
||||
import { CopilotM365WebExecutor } from "./copilot-m365-web.ts";
|
||||
import { MicrosoftDesignerWebExecutor } from "./microsoft-designer-web.ts";
|
||||
import { AdobeFireflyExecutor } from "./adobe-firefly.ts";
|
||||
import { VeoAIFreeWebExecutor } from "./veoaifree-web.ts";
|
||||
import { DuckDuckGoWebExecutor } from "./duckduckgo-web.ts";
|
||||
import { FeloWebExecutor } from "./felo-web.ts";
|
||||
import { T3ChatWebExecutor } from "./t3-chat-web.ts";
|
||||
import { ClaudeWebExecutor } from "./claude-web.ts";
|
||||
import { InnerAiExecutor } from "./inner-ai.ts";
|
||||
import { HuggingChatExecutor } from "./huggingchat.ts";
|
||||
import { YuanbaoWebExecutor } from "./yuanbao-web.ts";
|
||||
import { PoeWebExecutor } from "./poe-web.ts";
|
||||
import { VeniceWebExecutor } from "./venice-web.ts";
|
||||
import { NotionWebExecutor } from "./notion-web.ts";
|
||||
import { V0VercelWebExecutor } from "./v0-vercel-web.ts";
|
||||
import { CheaperInferenceExecutor } from "./cheaperinference.ts";
|
||||
import { KimiWebExecutor } from "./kimi-web.ts";
|
||||
import { DoubaoWebExecutor } from "./doubao-web.ts";
|
||||
import { QwenWebExecutor } from "./qwen-web.ts";
|
||||
import { RaycastExecutor } from "./raycast.ts";
|
||||
import { HailuoWebExecutor } from "./hailuo-web.ts";
|
||||
import { ZaiWebExecutor } from "./zai-web.ts";
|
||||
import { KimiExecutor } from "./kimi.ts";
|
||||
import { MoonshotExecutor } from "./moonshot.ts";
|
||||
import { TheOldLlmExecutor } from "./theoldllm.ts";
|
||||
import { ChipotleExecutor } from "./chipotle.ts";
|
||||
import { LMArenaExecutor } from "./lmarena.ts";
|
||||
import { GrokCliExecutor } from "./grok-cli.ts";
|
||||
import { CodeBuddyCnExecutor } from "./codebuddy-cn.ts";
|
||||
import { ZenmuxFreeExecutor } from "./zenmux-free.ts";
|
||||
import { CloudflarePlaygroundExecutor } from "./cloudflare-playground.ts";
|
||||
import { TinyCmsExecutor } from "./tinycms.ts";
|
||||
import { HyperAgentExecutor } from "./hyperagent.ts";
|
||||
import { XaiExecutor } from "./xai.ts";
|
||||
import { PromptQlExecutor } from "./promptql.ts";
|
||||
import { ConolWebExecutor } from "./conol-web.ts";
|
||||
|
||||
// R0.3 — declarative built-in table. The object literal stays as the single
|
||||
// place built-ins are declared (compile-time duplicate-key safety; the
|
||||
// check:known-symbols gate parses this literal from source), but lookup goes
|
||||
// through the ExecutorRegistry (./registry.ts): every entry is registered at
|
||||
// module load below, and getExecutor()/hasSpecializedExecutor() consult the
|
||||
// registry — the literal is never read at request time.
|
||||
const executors = {
|
||||
antigravity: new AntigravityExecutor(),
|
||||
agy: new AntigravityExecutor(),
|
||||
github: new GithubExecutor(),
|
||||
"ghe-copilot": new GheCopilotExecutor(),
|
||||
qoder: new QoderExecutor(),
|
||||
kiro: new KiroExecutor(),
|
||||
"amazon-q": new KiroExecutor("amazon-q"),
|
||||
bedrock: new BedrockExecutor(),
|
||||
codex: new CodexExecutor(),
|
||||
"codex-app-server": new CodexAppServerExecutor({}, "codex-app-server"),
|
||||
"chatgpt-web-codex": new ChatGptWebCodexExecutor(),
|
||||
"cgpt-codex": new ChatGptWebCodexExecutor(),
|
||||
cursor: new CursorExecutor(),
|
||||
trae: new TraeExecutor(),
|
||||
glm: new GlmExecutor("glm"),
|
||||
"glm-cn": new GlmExecutor("glm-cn"),
|
||||
glmt: new GlmExecutor("glmt"),
|
||||
cu: new CursorExecutor(), // Alias for cursor
|
||||
"cursor-api": new CursorExecutor("cursor-api"),
|
||||
cua: new CursorExecutor("cursor-api"),
|
||||
"azure-openai": new AzureOpenAIExecutor(),
|
||||
"azure-ai": new AzureAiExecutor(),
|
||||
"command-code": new CommandCodeExecutor(),
|
||||
cmd: new CommandCodeExecutor(), // Alias
|
||||
gitlab: new GitlabExecutor(),
|
||||
"gitlab-duo": new GitlabExecutor("gitlab-duo"),
|
||||
nlpcloud: new NlpCloudExecutor(),
|
||||
pollinations: new PollinationsExecutor(),
|
||||
pol: new PollinationsExecutor(), // Alias
|
||||
"cloudflare-ai": new CloudflareAIExecutor(),
|
||||
cf: new CloudflareAIExecutor(), // Alias
|
||||
freebuff: new FreebuffExecutor(),
|
||||
fb: new FreebuffExecutor(), // Alias
|
||||
"opencode-zen": new OpencodeExecutor("opencode-zen"),
|
||||
"opencode-go": new OpencodeExecutor("opencode-go"),
|
||||
opencode: new OpencodeExecutor("opencode-zen"), // Alias for opencode-zen
|
||||
vertex: new VertexExecutor(),
|
||||
"vertex-partner": new VertexExecutor(),
|
||||
cliproxyapi: new CliproxyapiExecutor(),
|
||||
cpa: new CliproxyapiExecutor(), // Alias
|
||||
dario: new DarioExecutor(),
|
||||
dr: new DarioExecutor(), // Alias
|
||||
"9router": new NineRouterExecutor(),
|
||||
nr: new NineRouterExecutor(), // Alias
|
||||
"perplexity-web": new PerplexityWebExecutor(),
|
||||
"pplx-web": new PerplexityWebExecutor(), // Alias
|
||||
"grok-web": new GrokWebExecutor(),
|
||||
"claude-web": new ClaudeWebExecutor(),
|
||||
"cw-web": new ClaudeWebExecutor(), // Alias
|
||||
"gemini-web": new GeminiWebExecutor(),
|
||||
gweb: new GeminiWebExecutor(), // Alias
|
||||
"gemini-business": new GeminiBusinessExecutor(),
|
||||
gembiz: new GeminiBusinessExecutor(), // Alias
|
||||
"chatgpt-web": new ChatGptWebExecutor(),
|
||||
"cgpt-web": new ChatGptWebExecutor(), // Alias
|
||||
"blackbox-web": new BlackboxWebExecutor(),
|
||||
"bb-web": new BlackboxWebExecutor(), // Alias
|
||||
"muse-spark-web": new MuseSparkWebExecutor(),
|
||||
"ms-web": new MuseSparkWebExecutor(), // Alias
|
||||
"devin-desktop": new DevinDesktopExecutor(),
|
||||
"zed-hosted": new ZedHostedExecutor(),
|
||||
"devin-cli": new DevinCliExecutor(),
|
||||
zcode: new ZcodeExecutor(),
|
||||
zc: new ZcodeExecutor(), // Alias
|
||||
"devin-cli-agentic": new DevinCliAgenticExecutor(),
|
||||
devin: new DevinCliExecutor(), // Alias
|
||||
"deepseek-web": new DeepSeekWebWithAutoRefreshExecutor(),
|
||||
"ds-web": new DeepSeekWebWithAutoRefreshExecutor(), // Alias
|
||||
"adapta-web": new AdaptaWebExecutor(),
|
||||
"adp-web": new AdaptaWebExecutor(), // Alias
|
||||
"copilot-web": new CopilotWebExecutor(),
|
||||
"copilot-m365-web": new CopilotM365WebExecutor(),
|
||||
copilot: new CopilotWebExecutor(), // Alias
|
||||
"microsoft-designer-web": new MicrosoftDesignerWebExecutor(),
|
||||
msdesigner: new MicrosoftDesignerWebExecutor(), // Alias
|
||||
"adobe-firefly": new AdobeFireflyExecutor(),
|
||||
firefly: new AdobeFireflyExecutor(), // Alias
|
||||
"veoaifree-web": new VeoAIFreeWebExecutor(),
|
||||
"veo-free": new VeoAIFreeWebExecutor(), // Alias
|
||||
"duckduckgo-web": new DuckDuckGoWebExecutor(),
|
||||
ddgw: new DuckDuckGoWebExecutor(), // Alias
|
||||
"felo-web": new FeloWebExecutor(),
|
||||
felo: new FeloWebExecutor(), // Alias
|
||||
"t3-web": new T3ChatWebExecutor(),
|
||||
t3chat: new T3ChatWebExecutor(), // Alias
|
||||
"inner-ai": new InnerAiExecutor(),
|
||||
"in-ai": new InnerAiExecutor(), // Alias
|
||||
huggingchat: new HuggingChatExecutor(),
|
||||
hc: new HuggingChatExecutor(), // Alias
|
||||
"yuanbao-web": new YuanbaoWebExecutor(),
|
||||
"tencent-aistudio-web": new TencentAIStudioWebExecutor(),
|
||||
tasw: new TencentAIStudioWebExecutor(),
|
||||
ybw: new YuanbaoWebExecutor(), // Alias
|
||||
"poe-web": new PoeWebExecutor(),
|
||||
// R0.3 — declarative built-in table, made LAZY by #11220.
|
||||
//
|
||||
// The object literal below stays as the single place built-ins are declared
|
||||
// (compile-time duplicate-key safety; the check:known-symbols gate parses this
|
||||
// literal from source), but its values are now deferred loaders instead of
|
||||
// eagerly-constructed instances. At module load every ALIAS is registered in
|
||||
// declaration order; the class import + construction happen on first use via
|
||||
// loadRegisteredExecutor() and are cached in the same registry a static
|
||||
// registration would have populated.
|
||||
//
|
||||
// Why: importing this barrel previously pulled all ~100 executor modules and
|
||||
// constructed every instance at boot — ~0.7–1.2s and ~35MB of heap measured on
|
||||
// top of the minimal set — even for deployments that use a handful of
|
||||
// providers. Bundlers split the dynamic imports into on-demand chunks.
|
||||
//
|
||||
// Contract preserved (pinned by tests/unit/executor-map-golden.test.ts):
|
||||
// - keys and their ORDER are byte-stable
|
||||
// - each alias still gets its OWN instance (aliases never share)
|
||||
// - ctor arguments are unchanged
|
||||
const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
|
||||
antigravity: () => import("./antigravity.ts").then((m) => new m.AntigravityExecutor()),
|
||||
agy: () => import("./antigravity.ts").then((m) => new m.AntigravityExecutor()),
|
||||
github: () => import("./github.ts").then((m) => new m.GithubExecutor()),
|
||||
"ghe-copilot": () => import("./ghe-copilot.ts").then((m) => new m.GheCopilotExecutor()),
|
||||
qoder: () => import("./qoder.ts").then((m) => new m.QoderExecutor()),
|
||||
kiro: () => import("./kiro.ts").then((m) => new m.KiroExecutor()),
|
||||
"amazon-q": () => import("./kiro.ts").then((m) => new m.KiroExecutor("amazon-q")),
|
||||
bedrock: () => import("./bedrock.ts").then((m) => new m.BedrockExecutor()),
|
||||
codex: () => import("./codex.ts").then((m) => new m.CodexExecutor()),
|
||||
"codex-app-server": () =>
|
||||
import("./codex-app-server.ts").then(
|
||||
(m) => new m.CodexAppServerExecutor({}, "codex-app-server")
|
||||
),
|
||||
"chatgpt-web-codex": () =>
|
||||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||||
"cgpt-codex": () =>
|
||||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||||
cursor: () => import("./cursor.ts").then((m) => new m.CursorExecutor()),
|
||||
trae: () => import("./trae.ts").then((m) => new m.TraeExecutor()),
|
||||
glm: () => import("./glm.ts").then((m) => new m.GlmExecutor("glm")),
|
||||
"glm-cn": () => import("./glm.ts").then((m) => new m.GlmExecutor("glm-cn")),
|
||||
glmt: () => import("./glm.ts").then((m) => new m.GlmExecutor("glmt")),
|
||||
cu: () => import("./cursor.ts").then((m) => new m.CursorExecutor()), // Alias for cursor
|
||||
"cursor-api": () => import("./cursor.ts").then((m) => new m.CursorExecutor("cursor-api")),
|
||||
cua: () => import("./cursor.ts").then((m) => new m.CursorExecutor("cursor-api")),
|
||||
"azure-openai": () => import("./azure-openai.ts").then((m) => new m.AzureOpenAIExecutor()),
|
||||
"azure-ai": () => import("./azure-ai.ts").then((m) => new m.AzureAiExecutor()),
|
||||
"command-code": () => import("./commandCode.ts").then((m) => new m.CommandCodeExecutor()),
|
||||
cmd: () => import("./commandCode.ts").then((m) => new m.CommandCodeExecutor()), // Alias
|
||||
gitlab: () => import("./gitlab.ts").then((m) => new m.GitlabExecutor()),
|
||||
"gitlab-duo": () => import("./gitlab.ts").then((m) => new m.GitlabExecutor("gitlab-duo")),
|
||||
nlpcloud: () => import("./nlpcloud.ts").then((m) => new m.NlpCloudExecutor()),
|
||||
pollinations: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()),
|
||||
pol: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()), // Alias
|
||||
"cloudflare-ai": () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()),
|
||||
cf: () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()), // Alias
|
||||
freebuff: () => import("./freebuff.ts").then((m) => new m.FreebuffExecutor()),
|
||||
fb: () => import("./freebuff.ts").then((m) => new m.FreebuffExecutor()), // Alias
|
||||
"opencode-zen": () =>
|
||||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-zen")),
|
||||
"opencode-go": () =>
|
||||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-go")),
|
||||
opencode: () =>
|
||||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-zen")), // Alias for opencode-zen
|
||||
vertex: () => import("./vertex.ts").then((m) => new m.VertexExecutor()),
|
||||
"vertex-partner": () => import("./vertex.ts").then((m) => new m.VertexExecutor()),
|
||||
cliproxyapi: () => import("./cliproxyapi.ts").then((m) => new m.CliproxyapiExecutor()),
|
||||
cpa: () => import("./cliproxyapi.ts").then((m) => new m.CliproxyapiExecutor()), // Alias
|
||||
dario: () => import("./dario.ts").then((m) => new m.DarioExecutor()),
|
||||
dr: () => import("./dario.ts").then((m) => new m.DarioExecutor()), // Alias
|
||||
"9router": () => import("./ninerouter.ts").then((m) => new m.NineRouterExecutor()),
|
||||
nr: () => import("./ninerouter.ts").then((m) => new m.NineRouterExecutor()), // Alias
|
||||
"perplexity-web": () =>
|
||||
import("./perplexity-web.ts").then((m) => new m.PerplexityWebExecutor()),
|
||||
"pplx-web": () =>
|
||||
import("./perplexity-web.ts").then((m) => new m.PerplexityWebExecutor()), // Alias
|
||||
"grok-web": () => import("./grok-web.ts").then((m) => new m.GrokWebExecutor()),
|
||||
"claude-web": () => import("./claude-web.ts").then((m) => new m.ClaudeWebExecutor()),
|
||||
"cw-web": () => import("./claude-web.ts").then((m) => new m.ClaudeWebExecutor()), // Alias
|
||||
"gemini-web": () => import("./gemini-web.ts").then((m) => new m.GeminiWebExecutor()),
|
||||
gweb: () => import("./gemini-web.ts").then((m) => new m.GeminiWebExecutor()), // Alias
|
||||
"gemini-business": () =>
|
||||
import("./gemini-business.ts").then((m) => new m.GeminiBusinessExecutor()),
|
||||
gembiz: () =>
|
||||
import("./gemini-business.ts").then((m) => new m.GeminiBusinessExecutor()), // Alias
|
||||
"chatgpt-web": () => import("./chatgpt-web.ts").then((m) => new m.ChatGptWebExecutor()),
|
||||
"cgpt-web": () => import("./chatgpt-web.ts").then((m) => new m.ChatGptWebExecutor()), // Alias
|
||||
"blackbox-web": () => import("./blackbox-web.ts").then((m) => new m.BlackboxWebExecutor()),
|
||||
"bb-web": () => import("./blackbox-web.ts").then((m) => new m.BlackboxWebExecutor()), // Alias
|
||||
"muse-spark-web": () =>
|
||||
import("./muse-spark-web.ts").then((m) => new m.MuseSparkWebExecutor()),
|
||||
"ms-web": () => import("./muse-spark-web.ts").then((m) => new m.MuseSparkWebExecutor()), // Alias
|
||||
"devin-desktop": () => import("./devin-desktop.ts").then((m) => new m.DevinDesktopExecutor()),
|
||||
"zed-hosted": () => import("./zed-hosted.ts").then((m) => new m.ZedHostedExecutor()),
|
||||
"devin-cli": () => import("./devin-cli.ts").then((m) => new m.DevinCliExecutor()),
|
||||
zcode: () => import("./zcode.ts").then((m) => new m.ZcodeExecutor()),
|
||||
zc: () => import("./zcode.ts").then((m) => new m.ZcodeExecutor()), // Alias
|
||||
"devin-cli-agentic": () =>
|
||||
import("./devin-cli-agentic.ts").then((m) => new m.DevinCliAgenticExecutor()),
|
||||
devin: () => import("./devin-cli.ts").then((m) => new m.DevinCliExecutor()), // Alias
|
||||
"deepseek-web": () =>
|
||||
import("./deepseek-web-with-auto-refresh.ts").then(
|
||||
(m) => new m.DeepSeekWebWithAutoRefreshExecutor()
|
||||
),
|
||||
"ds-web": () =>
|
||||
import("./deepseek-web-with-auto-refresh.ts").then(
|
||||
(m) => new m.DeepSeekWebWithAutoRefreshExecutor()
|
||||
), // Alias
|
||||
"adapta-web": () => import("./adapta-web.ts").then((m) => new m.AdaptaWebExecutor()),
|
||||
"adp-web": () => import("./adapta-web.ts").then((m) => new m.AdaptaWebExecutor()), // Alias
|
||||
"copilot-web": () => import("./copilot-web.ts").then((m) => new m.CopilotWebExecutor()),
|
||||
"copilot-m365-web": () =>
|
||||
import("./copilot-m365-web.ts").then((m) => new m.CopilotM365WebExecutor()),
|
||||
copilot: () => import("./copilot-web.ts").then((m) => new m.CopilotWebExecutor()), // Alias
|
||||
"microsoft-designer-web": () =>
|
||||
import("./microsoft-designer-web.ts").then((m) => new m.MicrosoftDesignerWebExecutor()),
|
||||
msdesigner: () =>
|
||||
import("./microsoft-designer-web.ts").then((m) => new m.MicrosoftDesignerWebExecutor()), // Alias
|
||||
"adobe-firefly": () => import("./adobe-firefly.ts").then((m) => new m.AdobeFireflyExecutor()),
|
||||
firefly: () => import("./adobe-firefly.ts").then((m) => new m.AdobeFireflyExecutor()), // Alias
|
||||
"veoaifree-web": () => import("./veoaifree-web.ts").then((m) => new m.VeoAIFreeWebExecutor()),
|
||||
"veo-free": () => import("./veoaifree-web.ts").then((m) => new m.VeoAIFreeWebExecutor()), // Alias
|
||||
"duckduckgo-web": () =>
|
||||
import("./duckduckgo-web.ts").then((m) => new m.DuckDuckGoWebExecutor()),
|
||||
ddgw: () => import("./duckduckgo-web.ts").then((m) => new m.DuckDuckGoWebExecutor()), // Alias
|
||||
"felo-web": () => import("./felo-web.ts").then((m) => new m.FeloWebExecutor()),
|
||||
felo: () => import("./felo-web.ts").then((m) => new m.FeloWebExecutor()), // Alias
|
||||
"t3-web": () => import("./t3-chat-web.ts").then((m) => new m.T3ChatWebExecutor()),
|
||||
t3chat: () => import("./t3-chat-web.ts").then((m) => new m.T3ChatWebExecutor()), // Alias
|
||||
"inner-ai": () => import("./inner-ai.ts").then((m) => new m.InnerAiExecutor()),
|
||||
"in-ai": () => import("./inner-ai.ts").then((m) => new m.InnerAiExecutor()), // Alias
|
||||
huggingchat: () => import("./huggingchat.ts").then((m) => new m.HuggingChatExecutor()),
|
||||
hc: () => import("./huggingchat.ts").then((m) => new m.HuggingChatExecutor()), // Alias
|
||||
"yuanbao-web": () => import("./yuanbao-web.ts").then((m) => new m.YuanbaoWebExecutor()),
|
||||
"tencent-aistudio-web": () =>
|
||||
import("./tencent-aistudio-web.ts").then((m) => new m.TencentAIStudioWebExecutor()),
|
||||
tasw: () =>
|
||||
import("./tencent-aistudio-web.ts").then((m) => new m.TencentAIStudioWebExecutor()), // Alias
|
||||
ybw: () => import("./yuanbao-web.ts").then((m) => new m.YuanbaoWebExecutor()), // Alias
|
||||
"poe-web": () => import("./poe-web.ts").then((m) => new m.PoeWebExecutor()),
|
||||
// #8969: do NOT alias canonical `poe` (API-key / api.poe.com) to PoeWebExecutor.
|
||||
// Registry declares executor:"default"; the hard-coded map previously won and
|
||||
// routed API-key traffic to GraphQL /api/gql_POST → HTTP 405.
|
||||
"venice-web": new VeniceWebExecutor(),
|
||||
ven: new VeniceWebExecutor(), // Alias
|
||||
"notion-web": new NotionWebExecutor(),
|
||||
nw: new NotionWebExecutor(), // Alias
|
||||
promptql: new PromptQlExecutor(),
|
||||
pql: new PromptQlExecutor(), // Alias
|
||||
"v0-vercel-web": new V0VercelWebExecutor(),
|
||||
v0: new V0VercelWebExecutor(), // Alias
|
||||
"kimi-web": new KimiWebExecutor(),
|
||||
"kimi-coding-apikey": new KimiExecutor("kimi-coding-apikey"), // Legacy alias
|
||||
"kimi-coding": new KimiExecutor(), // Alias
|
||||
moonshot: new MoonshotExecutor(),
|
||||
kimi: new MoonshotExecutor("kimi"), // Hidden legacy Moonshot provider id
|
||||
cheaperinference: new CheaperInferenceExecutor(),
|
||||
cinf: new CheaperInferenceExecutor("cheaperinference"), // Alias
|
||||
"doubao-web": new DoubaoWebExecutor(),
|
||||
db: new DoubaoWebExecutor(), // Alias
|
||||
"qwen-web": new QwenWebExecutor(),
|
||||
raycast: new RaycastExecutor(),
|
||||
rc: new RaycastExecutor(), // Alias
|
||||
"hailuo-web": new HailuoWebExecutor(),
|
||||
"zai-web": new ZaiWebExecutor(),
|
||||
zw: new ZaiWebExecutor(), // Alias
|
||||
theoldllm: new TheOldLlmExecutor(),
|
||||
tllm: new TheOldLlmExecutor(), // Alias
|
||||
chipotle: new ChipotleExecutor(),
|
||||
pepper: new ChipotleExecutor(), // Alias
|
||||
lmarena: new LMArenaExecutor(),
|
||||
lma: new LMArenaExecutor(), // Alias
|
||||
"grok-cli": new GrokCliExecutor(),
|
||||
gc: new GrokCliExecutor(), // Alias
|
||||
"codebuddy-cn": new CodeBuddyCnExecutor(),
|
||||
cbcn: new CodeBuddyCnExecutor(), // Alias for codebuddy-cn
|
||||
"zenmux-free": new ZenmuxFreeExecutor(),
|
||||
"cloudflare-playground": new CloudflarePlaygroundExecutor(),
|
||||
cfp: new CloudflarePlaygroundExecutor(), // Alias for cloudflare-playground
|
||||
"tinycms-web": new TinyCmsExecutor(),
|
||||
tcw: new TinyCmsExecutor(), // Alias
|
||||
hyperagent: new HyperAgentExecutor(),
|
||||
ha: new HyperAgentExecutor(), // Alias
|
||||
zmf: new ZenmuxFreeExecutor(), // Alias for zenmux-free
|
||||
auggie: new AuggieExecutor(),
|
||||
xai: new XaiExecutor(),
|
||||
"xai-oauth": new XaiExecutor("xai-oauth"),
|
||||
xao: new XaiExecutor("xai-oauth"),
|
||||
qw: new QwenWebExecutor(), // Alias
|
||||
"conol-web": new ConolWebExecutor(),
|
||||
cnl: new ConolWebExecutor(), // Alias
|
||||
"venice-web": () => import("./venice-web.ts").then((m) => new m.VeniceWebExecutor()),
|
||||
ven: () => import("./venice-web.ts").then((m) => new m.VeniceWebExecutor()), // Alias
|
||||
"notion-web": () => import("./notion-web.ts").then((m) => new m.NotionWebExecutor()),
|
||||
nw: () => import("./notion-web.ts").then((m) => new m.NotionWebExecutor()), // Alias
|
||||
promptql: () => import("./promptql.ts").then((m) => new m.PromptQlExecutor()),
|
||||
pql: () => import("./promptql.ts").then((m) => new m.PromptQlExecutor()), // Alias
|
||||
"v0-vercel-web": () => import("./v0-vercel-web.ts").then((m) => new m.V0VercelWebExecutor()),
|
||||
v0: () => import("./v0-vercel-web.ts").then((m) => new m.V0VercelWebExecutor()), // Alias
|
||||
"kimi-web": () => import("./kimi-web.ts").then((m) => new m.KimiWebExecutor()),
|
||||
"kimi-coding-apikey": () =>
|
||||
import("./kimi.ts").then((m) => new m.KimiExecutor("kimi-coding-apikey")), // Legacy alias
|
||||
"kimi-coding": () => import("./kimi.ts").then((m) => new m.KimiExecutor()), // Alias
|
||||
moonshot: () => import("./moonshot.ts").then((m) => new m.MoonshotExecutor()),
|
||||
kimi: () => import("./moonshot.ts").then((m) => new m.MoonshotExecutor("kimi")), // Hidden legacy Moonshot provider id
|
||||
cheaperinference: () =>
|
||||
import("./cheaperinference.ts").then((m) => new m.CheaperInferenceExecutor()),
|
||||
cinf: () =>
|
||||
import("./cheaperinference.ts").then(
|
||||
(m) => new m.CheaperInferenceExecutor("cheaperinference")
|
||||
), // Alias
|
||||
"doubao-web": () => import("./doubao-web.ts").then((m) => new m.DoubaoWebExecutor()),
|
||||
db: () => import("./doubao-web.ts").then((m) => new m.DoubaoWebExecutor()), // Alias
|
||||
"qwen-web": () => import("./qwen-web.ts").then((m) => new m.QwenWebExecutor()),
|
||||
raycast: () => import("./raycast.ts").then((m) => new m.RaycastExecutor()),
|
||||
rc: () => import("./raycast.ts").then((m) => new m.RaycastExecutor()), // Alias
|
||||
"hailuo-web": () => import("./hailuo-web.ts").then((m) => new m.HailuoWebExecutor()),
|
||||
"zai-web": () => import("./zai-web.ts").then((m) => new m.ZaiWebExecutor()),
|
||||
zw: () => import("./zai-web.ts").then((m) => new m.ZaiWebExecutor()), // Alias
|
||||
theoldllm: () => import("./theoldllm.ts").then((m) => new m.TheOldLlmExecutor()),
|
||||
tllm: () => import("./theoldllm.ts").then((m) => new m.TheOldLlmExecutor()), // Alias
|
||||
chipotle: () => import("./chipotle.ts").then((m) => new m.ChipotleExecutor()),
|
||||
pepper: () => import("./chipotle.ts").then((m) => new m.ChipotleExecutor()), // Alias
|
||||
lmarena: () => import("./lmarena.ts").then((m) => new m.LMArenaExecutor()),
|
||||
lma: () => import("./lmarena.ts").then((m) => new m.LMArenaExecutor()), // Alias
|
||||
"grok-cli": () => import("./grok-cli.ts").then((m) => new m.GrokCliExecutor()),
|
||||
gc: () => import("./grok-cli.ts").then((m) => new m.GrokCliExecutor()), // Alias
|
||||
"codebuddy-cn": () => import("./codebuddy-cn.ts").then((m) => new m.CodeBuddyCnExecutor()),
|
||||
cbcn: () => import("./codebuddy-cn.ts").then((m) => new m.CodeBuddyCnExecutor()), // Alias for codebuddy-cn
|
||||
"zenmux-free": () => import("./zenmux-free.ts").then((m) => new m.ZenmuxFreeExecutor()),
|
||||
"cloudflare-playground": () =>
|
||||
import("./cloudflare-playground.ts").then((m) => new m.CloudflarePlaygroundExecutor()),
|
||||
cfp: () =>
|
||||
import("./cloudflare-playground.ts").then((m) => new m.CloudflarePlaygroundExecutor()), // Alias for cloudflare-playground
|
||||
"tinycms-web": () => import("./tinycms.ts").then((m) => new m.TinyCmsExecutor()),
|
||||
tcw: () => import("./tinycms.ts").then((m) => new m.TinyCmsExecutor()), // Alias
|
||||
hyperagent: () => import("./hyperagent.ts").then((m) => new m.HyperAgentExecutor()),
|
||||
ha: () => import("./hyperagent.ts").then((m) => new m.HyperAgentExecutor()), // Alias
|
||||
zmf: () => import("./zenmux-free.ts").then((m) => new m.ZenmuxFreeExecutor()), // Alias for zenmux-free
|
||||
auggie: () => import("./auggie.ts").then((m) => new m.AuggieExecutor()),
|
||||
xai: () => import("./xai.ts").then((m) => new m.XaiExecutor()),
|
||||
"xai-oauth": () => import("./xai.ts").then((m) => new m.XaiExecutor("xai-oauth")),
|
||||
xao: () => import("./xai.ts").then((m) => new m.XaiExecutor("xai-oauth")),
|
||||
qw: () => import("./qwen-web.ts").then((m) => new m.QwenWebExecutor()), // Alias
|
||||
"conol-web": () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()),
|
||||
cnl: () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()), // Alias
|
||||
};
|
||||
|
||||
// Bootstrap: register every built-in in the ExecutorRegistry. registerExecutor
|
||||
// throws on duplicates, so an alias collision fails at module load, exactly as
|
||||
// loudly as a duplicate object key would have failed at lint time.
|
||||
for (const [alias, executor] of Object.entries(executors) as [string, BaseExecutor][]) {
|
||||
registerExecutor(alias, executor);
|
||||
// Bootstrap: declare every built-in alias in the ExecutorRegistry. Duplicate
|
||||
// aliases fail at module load, exactly as loudly as a duplicate object key
|
||||
// would have failed at lint time. Instances materialize on first use (#11220).
|
||||
for (const [alias, load] of Object.entries(lazyExecutors)) {
|
||||
registerLazyExecutor(alias, load);
|
||||
}
|
||||
|
||||
const defaultCache = new Map();
|
||||
@@ -267,9 +239,9 @@ const CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS = new Set(["jules"]);
|
||||
// providers must be executed through /v1/search, never the chat-completions path.
|
||||
const CHAT_UNSUPPORTED_SEARCH_PROVIDERS = new Set(Object.keys(SEARCH_PROVIDERS));
|
||||
|
||||
export function getExecutor(provider) {
|
||||
const registered = getRegisteredExecutor(provider);
|
||||
if (registered) return registered;
|
||||
export async function getExecutor(provider: string): Promise<BaseExecutor> {
|
||||
const loaded = await loadRegisteredExecutor(provider);
|
||||
if (loaded) return loaded;
|
||||
if (CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS.has(provider)) {
|
||||
const err = new Error(
|
||||
`Provider "${provider}" is a cloud-agent provider and does not support direct chat completions; use the Cloud Agents task API instead.`
|
||||
@@ -285,77 +257,19 @@ export function getExecutor(provider) {
|
||||
throw err;
|
||||
}
|
||||
if (!defaultCache.has(provider)) defaultCache.set(provider, new DefaultExecutor(provider));
|
||||
return defaultCache.get(provider);
|
||||
return defaultCache.get(provider)!;
|
||||
}
|
||||
|
||||
export function hasSpecializedExecutor(provider) {
|
||||
export function hasSpecializedExecutor(provider: string): boolean {
|
||||
return hasRegisteredExecutor(provider);
|
||||
}
|
||||
|
||||
export { registerExecutor, listExecutorAliases } from "./registry.ts";
|
||||
|
||||
export {
|
||||
registerExecutor,
|
||||
registerLazyExecutor,
|
||||
listExecutorAliases,
|
||||
} from "./registry.ts";
|
||||
// Value re-export: base.ts is already eager (DefaultExecutor extends it), and
|
||||
// scripts/check/check-known-symbols.ts reads this export from the module.
|
||||
export { BaseExecutor } from "./base.ts";
|
||||
export { AntigravityExecutor } from "./antigravity.ts";
|
||||
export { GithubExecutor } from "./github.ts";
|
||||
export { QoderExecutor } from "./qoder.ts";
|
||||
export { KiroExecutor } from "./kiro.ts";
|
||||
export { CodexExecutor } from "./codex.ts";
|
||||
export { CursorExecutor } from "./cursor.ts";
|
||||
export { TraeExecutor } from "./trae.ts";
|
||||
export { DefaultExecutor } from "./default.ts";
|
||||
export { BedrockExecutor } from "./bedrock.ts";
|
||||
export { GlmExecutor } from "./glm.ts";
|
||||
export { PollinationsExecutor } from "./pollinations.ts";
|
||||
export { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
export { OpencodeExecutor } from "./opencode.ts";
|
||||
export { CliproxyapiExecutor } from "./cliproxyapi.ts";
|
||||
export { DarioExecutor } from "./dario.ts";
|
||||
export { NineRouterExecutor } from "./ninerouter.ts";
|
||||
export { VertexExecutor } from "./vertex.ts";
|
||||
export { PerplexityWebExecutor } from "./perplexity-web.ts";
|
||||
export { GrokWebExecutor } from "./grok-web.ts";
|
||||
export { GeminiWebExecutor } from "./gemini-web.ts";
|
||||
export { KieExecutor } from "./kie.ts";
|
||||
export { ChatGptWebExecutor } from "./chatgpt-web.ts";
|
||||
export { BlackboxWebExecutor } from "./blackbox-web.ts";
|
||||
export { MuseSparkWebExecutor } from "./muse-spark-web.ts";
|
||||
export { AzureOpenAIExecutor } from "./azure-openai.ts";
|
||||
export { AzureAiExecutor } from "./azure-ai.ts";
|
||||
export { CommandCodeExecutor } from "./commandCode.ts";
|
||||
export { GitlabExecutor } from "./gitlab.ts";
|
||||
export { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
export { DevinDesktopExecutor } from "./devin-desktop.ts";
|
||||
export { ZedHostedExecutor } from "./zed-hosted.ts";
|
||||
export { DevinCliExecutor } from "./devin-cli.ts";
|
||||
export { DevinCliAgenticExecutor } from "./devin-cli-agentic.ts";
|
||||
export { AuggieExecutor } from "./auggie.ts";
|
||||
export { CopilotWebExecutor } from "./copilot-web.ts";
|
||||
export { CopilotM365WebExecutor } from "./copilot-m365-web.ts";
|
||||
export { MicrosoftDesignerWebExecutor } from "./microsoft-designer-web.ts";
|
||||
export { AdobeFireflyExecutor } from "./adobe-firefly.ts";
|
||||
export { VeoAIFreeWebExecutor } from "./veoaifree-web.ts";
|
||||
export { DuckDuckGoWebExecutor } from "./duckduckgo-web.ts";
|
||||
export { FeloWebExecutor } from "./felo-web.ts";
|
||||
export { ClaudeWebExecutor } from "./claude-web.ts";
|
||||
export { DeepSeekWebExecutor } from "./deepseek-web.ts";
|
||||
export { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
|
||||
export { AdaptaWebExecutor } from "./adapta-web.ts";
|
||||
export { YuanbaoWebExecutor } from "./yuanbao-web.ts";
|
||||
export { T3ChatWebExecutor } from "./t3-chat-web.ts";
|
||||
export { InnerAiExecutor } from "./inner-ai.ts";
|
||||
export { QwenWebExecutor } from "./qwen-web.ts";
|
||||
export { HailuoWebExecutor } from "./hailuo-web.ts";
|
||||
export { TheOldLlmExecutor } from "./theoldllm.ts";
|
||||
export { ChipotleExecutor } from "./chipotle.ts";
|
||||
export { LMArenaExecutor } from "./lmarena.ts";
|
||||
export { GrokCliExecutor } from "./grok-cli.ts";
|
||||
export { CodeBuddyCnExecutor } from "./codebuddy-cn.ts";
|
||||
export { ZenmuxFreeExecutor } from "./zenmux-free.ts";
|
||||
export { CloudflarePlaygroundExecutor } from "./cloudflare-playground.ts";
|
||||
export { TinyCmsExecutor } from "./tinycms.ts";
|
||||
export { HyperAgentExecutor } from "./hyperagent.ts";
|
||||
export { XaiExecutor } from "./xai.ts";
|
||||
export { MoonshotExecutor } from "./moonshot.ts";
|
||||
export { CheaperInferenceExecutor } from "./cheaperinference.ts";
|
||||
export { PromptQlExecutor } from "./promptql.ts";
|
||||
export { ConolWebExecutor } from "./conol-web.ts";
|
||||
|
||||
@@ -895,10 +895,21 @@ export class OpencodeExecutor extends BaseExecutor {
|
||||
const mb = modifiedBody as Record<string, unknown>;
|
||||
const parsed = parseEffortLevel(model);
|
||||
if (parsed) {
|
||||
mb.model = parsed.baseModel;
|
||||
if (mb.reasoning_effort === undefined) {
|
||||
mb.reasoning_effort = parsed.effort;
|
||||
const deepseekFamily =
|
||||
parsed.baseModel === "deepseek-v4-pro" || parsed.baseModel === "deepseek-v4-flash";
|
||||
if (deepseekFamily) {
|
||||
// DeepSeek via opencode-go proxies the native DeepSeek contract, which
|
||||
// accepts a flat reasoning_effort field (#4647).
|
||||
mb.model = parsed.baseModel;
|
||||
if (mb.reasoning_effort === undefined) {
|
||||
mb.reasoning_effort = parsed.effort;
|
||||
}
|
||||
}
|
||||
// #10788: every other family's ONLY native effort mechanism is the
|
||||
// -<tier> suffix in the model id itself (the ids `opencode models
|
||||
// opencode-go --verbose` lists). The opencode-go ChatCompletionRequest
|
||||
// carries no flat reasoning_effort field, so rewriting to the base id
|
||||
// silently dropped the tier — forward the aliased id verbatim instead.
|
||||
}
|
||||
}
|
||||
// #1543 / upstream PR #1099: thinking-mode upstreams routed through OpenCode
|
||||
|
||||
@@ -28,11 +28,46 @@ export function getRegisteredExecutor(alias: string): BaseExecutor | undefined {
|
||||
return registry.get(alias);
|
||||
}
|
||||
|
||||
export function hasRegisteredExecutor(alias: string): boolean {
|
||||
return registry.has(alias);
|
||||
// ── #11220: lazy registration ───────────────────────────────────────────────
|
||||
// Aliases may register a deferred loader instead of an instance. The alias and
|
||||
// its registration ORDER are declared eagerly — hasRegisteredExecutor() and
|
||||
// listExecutorAliases() stay synchronous and the golden snapshot keeps its
|
||||
// shape — while the class import + construction happen on first use. A
|
||||
// completed load caches into `registry`, so later resolution is identical to a
|
||||
// static registration.
|
||||
const lazyLoaders = new Map<string, () => Promise<BaseExecutor>>();
|
||||
const lazyInFlight = new Map<string, Promise<BaseExecutor>>();
|
||||
|
||||
export function registerLazyExecutor(alias: string, load: () => Promise<BaseExecutor>): void {
|
||||
if (registry.has(alias) || lazyLoaders.has(alias)) {
|
||||
throw new Error(`executor alias already registered: "${alias}"`);
|
||||
}
|
||||
lazyLoaders.set(alias, load);
|
||||
}
|
||||
|
||||
/** All registered aliases, in registration order. */
|
||||
export function loadRegisteredExecutor(alias: string): Promise<BaseExecutor> | undefined {
|
||||
const cached = registry.get(alias);
|
||||
if (cached) return Promise.resolve(cached);
|
||||
const load = lazyLoaders.get(alias);
|
||||
if (!load) return undefined;
|
||||
let inFlight = lazyInFlight.get(alias);
|
||||
if (!inFlight) {
|
||||
inFlight = load().then((executor) => {
|
||||
registerExecutor(alias, executor);
|
||||
lazyLoaders.delete(alias);
|
||||
lazyInFlight.delete(alias);
|
||||
return executor;
|
||||
});
|
||||
lazyInFlight.set(alias, inFlight);
|
||||
}
|
||||
return inFlight;
|
||||
}
|
||||
|
||||
export function hasRegisteredExecutor(alias: string): boolean {
|
||||
return registry.has(alias) || lazyLoaders.has(alias);
|
||||
}
|
||||
|
||||
/** All registered aliases — static and lazy — in registration order. */
|
||||
export function listExecutorAliases(): string[] {
|
||||
return [...registry.keys()];
|
||||
return [...registry.keys(), ...lazyLoaders.keys()];
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
type ExecutorInput = {
|
||||
model: string;
|
||||
body: unknown;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
// No index signature: executors (BaseExecutor subclasses) must satisfy this
|
||||
// structurally, and class instances don't carry index signatures.
|
||||
type ExecutorLike = {
|
||||
execute: (input: ExecutorInput) => Promise<unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type CliproxyapiModelMapping = Record<string, unknown> | null | undefined;
|
||||
|
||||
@@ -19,12 +19,12 @@ import type { ProviderCredentials } from "../../executors/base.ts";
|
||||
|
||||
type ExecutorInput = {
|
||||
credentials: ProviderCredentials;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
// No index signature: executors (BaseExecutor subclasses) must satisfy this
|
||||
// structurally, and class instances don't carry index signatures.
|
||||
type ExecutorLike = {
|
||||
execute: (input: ExecutorInput) => Promise<unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,12 +76,15 @@ async function loadCliproxyapiSettings(): Promise<{
|
||||
* dedicated-credential wrappers applied. Used by the direct `cliproxyapi` leg
|
||||
* and the CLIProxyAPI branch of `fallback`.
|
||||
*/
|
||||
function resolveCliproxyapiExecutor(
|
||||
async function resolveCliproxyapiExecutor(
|
||||
cliproxyapiModelMapping: Record<string, unknown> | null,
|
||||
dedicatedApiKey: string | null
|
||||
) {
|
||||
return wrapExecutorWithCliproxyapiCredentials(
|
||||
wrapExecutorWithCliproxyapiModelMapping(getExecutor("cliproxyapi"), cliproxyapiModelMapping),
|
||||
wrapExecutorWithCliproxyapiModelMapping(
|
||||
await getExecutor("cliproxyapi"),
|
||||
cliproxyapiModelMapping
|
||||
),
|
||||
dedicatedApiKey
|
||||
);
|
||||
}
|
||||
@@ -138,7 +141,7 @@ export async function resolveExecutorWithProxy(
|
||||
// backend on specific failures. The backend defaults to CLIProxyAPI so every
|
||||
// pre-existing fallback config behaves exactly as before; fallbackBackend
|
||||
// === "dario" opts the retry leg over to Dario instead.
|
||||
const nativeExec = getExecutor(prov);
|
||||
const nativeExec = await getExecutor(prov);
|
||||
const fallbackBackend: FallbackBackend = cfg.fallbackBackend;
|
||||
const { fallbackCodes, dedicatedApiKey } = await loadCliproxyapiSettings();
|
||||
|
||||
@@ -146,8 +149,8 @@ export async function resolveExecutorWithProxy(
|
||||
// the native leg must keep seeing the original, unmapped model.
|
||||
const proxyExec =
|
||||
fallbackBackend === "dario"
|
||||
? getExecutor("dario")
|
||||
: resolveCliproxyapiExecutor(cfg.cliproxyapiModelMapping, dedicatedApiKey);
|
||||
? await getExecutor("dario")
|
||||
: await resolveCliproxyapiExecutor(cfg.cliproxyapiModelMapping, dedicatedApiKey);
|
||||
const backendLabel = fallbackBackend === "dario" ? "Dario" : "CLIProxyAPI";
|
||||
const isRetryableStatus = (s: number) => fallbackCodes.includes(s) || s === 0;
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ export async function handleRerank({
|
||||
connectionId = null,
|
||||
apiKeyId = null,
|
||||
apiKeyName = null,
|
||||
resolvedProvider = null,
|
||||
}) {
|
||||
const startTime = Date.now();
|
||||
if (!model) return errorResponse(400, "model is required");
|
||||
@@ -210,7 +211,8 @@ export async function handleRerank({
|
||||
}
|
||||
|
||||
const { provider: providerId, model: modelId } = parseRerankModel(model);
|
||||
const providerConfig = providerId ? getRerankProvider(providerId) : null;
|
||||
const providerConfig =
|
||||
resolvedProvider || (providerId ? getRerankProvider(providerId) : null);
|
||||
|
||||
if (!providerConfig) {
|
||||
const availableProviders = Object.keys(RERANK_PROVIDERS).join(", ");
|
||||
@@ -219,10 +221,13 @@ export async function handleRerank({
|
||||
`No rerank provider found for model "${model}". Available: ${availableProviders}`
|
||||
);
|
||||
}
|
||||
// When a derived/generic provider is injected, its id is authoritative for
|
||||
// logging and cost attribution even though parseRerankModel returned null.
|
||||
const effectiveProviderId = providerConfig.id || providerId;
|
||||
|
||||
const token = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!token) {
|
||||
return errorResponse(401, `No credentials for rerank provider: ${providerId}`);
|
||||
return errorResponse(401, `No credentials for rerank provider: ${effectiveProviderId}`);
|
||||
}
|
||||
|
||||
const requestBody = transformRequestForProvider(providerConfig, {
|
||||
@@ -275,8 +280,8 @@ export async function handleRerank({
|
||||
method: "POST",
|
||||
path: "/v1/rerank",
|
||||
status: res.status,
|
||||
model: `${providerId}/${modelId}`,
|
||||
provider: providerId,
|
||||
model: `${effectiveProviderId}/${modelId}`,
|
||||
provider: effectiveProviderId,
|
||||
connectionId: connectionId || undefined,
|
||||
duration: Date.now() - startTime,
|
||||
requestBody,
|
||||
@@ -296,14 +301,14 @@ export async function handleRerank({
|
||||
});
|
||||
|
||||
const searchUnits = Number(result?.meta?.billed_units?.search_units) || 0;
|
||||
const costUsd = await calculateModalCost("rerank", providerId, modelId, { searchUnits });
|
||||
const costUsd = await calculateModalCost("rerank", effectiveProviderId, modelId, { searchUnits });
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/rerank",
|
||||
status: 200,
|
||||
model: `${providerId}/${modelId}`,
|
||||
provider: providerId,
|
||||
model: `${effectiveProviderId}/${modelId}`,
|
||||
provider: effectiveProviderId,
|
||||
connectionId: connectionId || undefined,
|
||||
duration: Date.now() - startTime,
|
||||
tokens: { prompt_tokens: 0, completion_tokens: 0 },
|
||||
@@ -315,7 +320,7 @@ export async function handleRerank({
|
||||
|
||||
const headers = new Headers({ ...CORS_HEADERS, "Content-Type": "application/json" });
|
||||
attachOmniRouteMetaHeaders(headers, {
|
||||
provider: providerId,
|
||||
provider: effectiveProviderId,
|
||||
model: modelId,
|
||||
costUsd,
|
||||
latencyMs: Date.now() - startTime,
|
||||
|
||||
@@ -374,7 +374,7 @@ async function handleVertexVeoGeneration({ model, body, credentials, log }) {
|
||||
* Submits an AnimateDiff or SVD workflow, polls for completion, fetches output video
|
||||
*/
|
||||
async function handleVeoAiFreeVideoGeneration({ model, provider, body, credentials, log }) {
|
||||
const executor = getExecutor(provider);
|
||||
const executor = await getExecutor(provider);
|
||||
if (!executor) {
|
||||
return { success: false, status: 400, error: `Unknown video provider: ${provider}` };
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ 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",
|
||||
@@ -73,18 +67,6 @@ 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 };
|
||||
|
||||
@@ -217,9 +199,8 @@ export async function createBuiltinAutoCombo(
|
||||
}
|
||||
|
||||
if ("variant" in spec && spec.variant !== undefined) {
|
||||
const overlayTier = FLAT_TIER_OVERLAY_IDS[modelStr];
|
||||
const virtualCombo = await materialize(spec.variant, {
|
||||
...(overlayTier ? { tier: overlayTier } : {}),
|
||||
...(modelStr === "auto/best-free" ? { tier: "free" as const } : {}),
|
||||
});
|
||||
virtualCombo.name = modelStr;
|
||||
virtualCombo.id = modelStr;
|
||||
@@ -230,11 +211,7 @@ 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 overlayTier = FLAT_TIER_OVERLAY_IDS[modelStr];
|
||||
const virtualCombo = await materialize(
|
||||
undefined,
|
||||
overlayTier ? { tier: overlayTier } : undefined
|
||||
);
|
||||
const virtualCombo = await materialize(undefined);
|
||||
virtualCombo.name = modelStr;
|
||||
virtualCombo.id = modelStr;
|
||||
return virtualCombo;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
@@ -22,7 +22,6 @@ 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);
|
||||
|
||||
@@ -192,13 +191,7 @@ export function resolveFreeAccessState(
|
||||
|
||||
const key = cacheKey(provider, connectionId);
|
||||
const entry = cache.get(key);
|
||||
// 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();
|
||||
const fresh = entry && Date.now() - entry.fetchedAtMs <= ttlMs();
|
||||
if (!fresh) {
|
||||
void refresh(provider, connectionId);
|
||||
}
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -23,20 +23,7 @@ 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"
|
||||
// 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 type AutoTier = "fast" | "cheap" | "floor" | "free" | "reliable" | "pro";
|
||||
|
||||
export const AUTO_CATEGORIES: readonly AutoCategory[] = [
|
||||
"coding",
|
||||
@@ -52,8 +39,6 @@ export const AUTO_TIERS: readonly AutoTier[] = [
|
||||
"free",
|
||||
"reliable",
|
||||
"pro",
|
||||
"subscription",
|
||||
"thrifty",
|
||||
];
|
||||
|
||||
const CATEGORY_SET = new Set<string>(AUTO_CATEGORIES);
|
||||
@@ -99,9 +84,6 @@ 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";
|
||||
@@ -136,7 +118,8 @@ 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)
|
||||
|
||||
@@ -28,11 +28,6 @@ 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";
|
||||
@@ -150,98 +145,6 @@ 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 {
|
||||
@@ -753,15 +656,8 @@ 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, authTypeByConnectionId, subscriptionLadder };
|
||||
return { regularCandidates, familyCandidates };
|
||||
}
|
||||
|
||||
// One uninterrupted bulk read of all three capability tables for this prepare only.
|
||||
@@ -775,8 +671,6 @@ export async function prepareVirtualAutoComboInputs(
|
||||
return {
|
||||
regularCandidates: await attachPreparedCapabilityValues(regularCandidates, capabilityState),
|
||||
familyCandidates: await attachPreparedCapabilityValues(familyCandidates, capabilityState),
|
||||
authTypeByConnectionId,
|
||||
subscriptionLadder,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -948,32 +842,6 @@ 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
|
||||
|
||||
@@ -926,9 +926,6 @@ async function handleComboChatInner({
|
||||
if (activeNativeTurnPin) {
|
||||
orderedTargets = applyNativeCodexTurnPin(orderedTargets, activeNativeTurnPin);
|
||||
if (orderedTargets.length === 0) {
|
||||
// #11371: quota-share ordering already reserved a winner slot; release it on
|
||||
// this early exit (idempotent).
|
||||
targetResolution.quotaShareRelease?.();
|
||||
return errorResponse(
|
||||
409,
|
||||
"The pinned native Codex turn target is no longer available; the turn cannot be moved to another provider"
|
||||
@@ -956,8 +953,6 @@ async function handleComboChatInner({
|
||||
// no-target failures (silent-stop fix). Threshold of 3 prevents a one-off account
|
||||
// wipe from destroying the prompt-cache pin benefit on the next request.
|
||||
recordComboFailure(effectiveSessionId, combo.name);
|
||||
// #11371: same early-exit release as the pinned-turn path above.
|
||||
targetResolution.quotaShareRelease?.();
|
||||
return errorResponseWithComboDiagnostics(
|
||||
404,
|
||||
"Combo has no executable targets",
|
||||
@@ -2846,9 +2841,6 @@ async function handleComboChatInner({
|
||||
return await dispatchWithCooldownRetry();
|
||||
} finally {
|
||||
quotaShareConcurrencyRelease?.();
|
||||
// #11371: release the in-flight slot quota-share ordering reserved for its
|
||||
// winner — the counter must not leak monotonically upward across requests.
|
||||
targetResolution.quotaShareRelease?.();
|
||||
// G2: Clean up candidate registry to prevent unbounded memory growth.
|
||||
_unregisterExecutionCandidates(_registeredExecutionKeys);
|
||||
}
|
||||
|
||||
@@ -20,21 +20,6 @@ import {
|
||||
} from "./targetSorters.ts";
|
||||
import type { ComboLike, ComboLogger, ResolvedComboTarget } from "./types.ts";
|
||||
|
||||
/**
|
||||
* Result of {@link applyStrategyOrdering}.
|
||||
*
|
||||
* `quotaShareRelease` carries the idempotent release for the in-flight slot that
|
||||
* quota-share ordering reserves for its winner (#11371). It is non-null only when
|
||||
* the `quota-share` strategy ran; every other strategy leaves it null. The caller
|
||||
* MUST invoke it exactly once when the request settles — selection reserves the
|
||||
* slot, so dropping the callback leaks the counter monotonically upward and
|
||||
* degenerates P2C into "fewest lifetime dispatches".
|
||||
*/
|
||||
export interface ApplyStrategyOrderingResult {
|
||||
orderedTargets: ResolvedComboTarget[];
|
||||
quotaShareRelease: (() => void) | null;
|
||||
}
|
||||
|
||||
export interface ApplyStrategyOrderingDeps {
|
||||
combo: ComboLike;
|
||||
config: Record<string, unknown>;
|
||||
@@ -60,10 +45,9 @@ export async function applyStrategyOrdering(
|
||||
strategy: string,
|
||||
initialOrderedTargets: ResolvedComboTarget[],
|
||||
deps: ApplyStrategyOrderingDeps
|
||||
): Promise<ApplyStrategyOrderingResult> {
|
||||
): Promise<ResolvedComboTarget[]> {
|
||||
const { combo, config, body, log, apiKeyAllowedConnections, sessionKey } = deps;
|
||||
let orderedTargets = initialOrderedTargets;
|
||||
let quotaShareRelease: (() => void) | null = null;
|
||||
|
||||
if (strategy === "lkgp") {
|
||||
try {
|
||||
@@ -245,18 +229,14 @@ export async function applyStrategyOrdering(
|
||||
const qsModel =
|
||||
typeof body?.model === "string" ? body.model : (orderedTargets[0]?.modelStr ?? "");
|
||||
const qsMaxConcurrent = await resolveMaxConcurrentByConnection(orderedTargets);
|
||||
const qsSelection = selectQuotaShareTarget(orderedTargets, combo.name, qsModel, Date.now(), {
|
||||
orderedTargets = selectQuotaShareTarget(orderedTargets, combo.name, qsModel, Date.now(), {
|
||||
maxConcurrentByConnection: qsMaxConcurrent,
|
||||
});
|
||||
orderedTargets = qsSelection.orderedTargets;
|
||||
// #11371: the reservation made inside selectQuotaShareTarget must outlive this
|
||||
// call — hand the release to the host so it can fire it when the request settles.
|
||||
quotaShareRelease = qsSelection.decrementInflight;
|
||||
}).orderedTargets;
|
||||
log.info(
|
||||
"COMBO",
|
||||
`Quota-share ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} selected (DRR+P2C)`
|
||||
);
|
||||
}
|
||||
|
||||
return { orderedTargets, quotaShareRelease };
|
||||
return orderedTargets;
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ function getWindowsMapQuotaWindow(
|
||||
);
|
||||
}
|
||||
|
||||
function resolveQuotaWindowByName(
|
||||
export function resolveQuotaWindowByName(
|
||||
quota: unknown,
|
||||
windowName: ResetWindowName
|
||||
): QuotaWindowSnapshot | null {
|
||||
@@ -321,8 +321,8 @@ export function scoreResetAwareQuota(
|
||||
if (quota.limitReached === true) return { score: -Infinity };
|
||||
|
||||
const overallPercentUsed = clamp01(finiteNumberOrNull(quota.percentUsed) ?? 0.5);
|
||||
const sessionWindow = getQuotaWindow(quota, "window5h");
|
||||
const weeklyWindow = getQuotaWindow(quota, "window7d") || getQuotaWindow(quota, "windowWeekly");
|
||||
const sessionWindow = resolveQuotaWindowByName(quota, "session");
|
||||
const weeklyWindow = resolveQuotaWindowByName(quota, "weekly");
|
||||
const sessionRemaining = clamp01(1 - (sessionWindow?.percentUsed ?? overallPercentUsed));
|
||||
const weeklyRemaining = clamp01(1 - (weeklyWindow?.percentUsed ?? overallPercentUsed));
|
||||
const sessionScore = scoreQuotaWindow(
|
||||
|
||||
@@ -122,13 +122,6 @@ export interface ResolvedComboTargetPipeline {
|
||||
/** Session-stickiness result — the attempt loop reads `.messageHash` on success/failure. */
|
||||
sticky: ApplyStickinessResult;
|
||||
preScreenMap: Map<string, PreScreenResult>;
|
||||
/**
|
||||
* Idempotent release for the in-flight slot quota-share ordering reserved for
|
||||
* its winner (#11371). Null unless the `quota-share` strategy ran. The host MUST
|
||||
* invoke it when the request settles; this pipeline already releases it on any
|
||||
* earlyResponse it produces after selection.
|
||||
*/
|
||||
quotaShareRelease: (() => void) | null;
|
||||
}
|
||||
|
||||
export type ResolveComboTargetPipelineResult =
|
||||
@@ -401,11 +394,7 @@ async function orderByStrategy(
|
||||
initialOrderedTargets: ResolvedComboTarget[]
|
||||
): Promise<
|
||||
| { earlyResponse: Response }
|
||||
| {
|
||||
orderedTargets: ResolvedComboTarget[];
|
||||
autoUsedExplicitRouter: boolean;
|
||||
quotaShareRelease: (() => void) | null;
|
||||
}
|
||||
| { orderedTargets: ResolvedComboTarget[]; autoUsedExplicitRouter: boolean }
|
||||
> {
|
||||
const { strategy, body, combo, settings, config, log } = deps;
|
||||
if (strategy === "auto") {
|
||||
@@ -424,22 +413,17 @@ async function orderByStrategy(
|
||||
return {
|
||||
orderedTargets: autoResult.orderedTargets,
|
||||
autoUsedExplicitRouter: autoResult.autoUsedExplicitRouter,
|
||||
quotaShareRelease: null,
|
||||
};
|
||||
}
|
||||
const { orderedTargets, quotaShareRelease } = await applyStrategyOrdering(
|
||||
strategy,
|
||||
initialOrderedTargets,
|
||||
{
|
||||
combo,
|
||||
config,
|
||||
body,
|
||||
log,
|
||||
apiKeyAllowedConnections: deps.apiKeyAllowedConnections,
|
||||
sessionKey: deps.relayOptions?.sessionId,
|
||||
}
|
||||
);
|
||||
return { orderedTargets, autoUsedExplicitRouter: false, quotaShareRelease };
|
||||
const orderedTargets = await applyStrategyOrdering(strategy, initialOrderedTargets, {
|
||||
combo,
|
||||
config,
|
||||
body,
|
||||
log,
|
||||
apiKeyAllowedConnections: deps.apiKeyAllowedConnections,
|
||||
sessionKey: deps.relayOptions?.sessionId,
|
||||
});
|
||||
return { orderedTargets, autoUsedExplicitRouter: false };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -730,15 +714,10 @@ export async function resolveComboTargetPipeline(
|
||||
|
||||
const ordering = await orderByStrategy(deps, orderedTargets);
|
||||
if ("earlyResponse" in ordering) return ordering;
|
||||
const { autoUsedExplicitRouter, quotaShareRelease } = ordering;
|
||||
const { autoUsedExplicitRouter } = ordering;
|
||||
|
||||
const continuity = await applyContinuityFilters(deps, ordering.orderedTargets);
|
||||
if ("earlyResponse" in continuity) {
|
||||
// #11371: selection already reserved the winner's in-flight slot; a hard
|
||||
// filter exhausting the pool must not leak it.
|
||||
quotaShareRelease?.();
|
||||
return continuity;
|
||||
}
|
||||
if ("earlyResponse" in continuity) return continuity;
|
||||
orderedTargets = applyTaskAwareOrdering(deps, continuity.orderedTargets, autoUsedExplicitRouter);
|
||||
orderedTargets = await applyPromptCacheStage(
|
||||
deps,
|
||||
@@ -762,6 +741,5 @@ export async function resolveComboTargetPipeline(
|
||||
getWeightedStepKeyForTarget,
|
||||
sticky: continuity.sticky,
|
||||
preScreenMap,
|
||||
quotaShareRelease,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ export function createExecutorModelClient(
|
||||
credentials: ProviderCredentials,
|
||||
costPerKTokenOut?: number
|
||||
): ModelClient {
|
||||
const executor = getExecutor(provider);
|
||||
return {
|
||||
async complete(model: string, messages: ChatTurn[]): Promise<ModelCallResult> {
|
||||
// #11220: getExecutor is async (lazy registry) — resolve per call.
|
||||
const executor = await getExecutor(provider);
|
||||
const body = { model, messages, stream: false };
|
||||
const input: ExecuteInput = {
|
||||
model,
|
||||
|
||||
@@ -150,16 +150,65 @@ export function convertUsageToQuotaInfo(usage: unknown): QuotaInfo | null {
|
||||
|
||||
if (Object.keys(windows).length === 0) return null;
|
||||
|
||||
const normalized = normalizeQuotaWindows(windows);
|
||||
Object.assign(windows, normalized);
|
||||
|
||||
return {
|
||||
used: 0,
|
||||
total: 0,
|
||||
percentUsed: worstPercent,
|
||||
resetAt: worstResetAt,
|
||||
windows,
|
||||
...normalized,
|
||||
limitReached: worstPercent >= 1 - 1e-9,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map provider-native window keys to canonical structural windows so that
|
||||
* reset-aware / reset-window scoring works without knowing every provider's
|
||||
* naming convention.
|
||||
*
|
||||
* - Claude: "session (5h)" → window5h, "weekly (7d)" → window7d
|
||||
* - Antigravity: worst per-model quota → window5h; worst *_weekly quota → window7d
|
||||
*/
|
||||
function normalizeQuotaWindows(
|
||||
windows: Record<string, { percentUsed: number; resetAt: string | null }>
|
||||
): Record<string, { percentUsed: number; resetAt: string | null }> {
|
||||
const normalized: Record<string, { percentUsed: number; resetAt: string | null }> = {};
|
||||
|
||||
// Claude-style explicit time windows.
|
||||
if (windows["session (5h)"] && !normalized.window5h) {
|
||||
normalized.window5h = windows["session (5h)"];
|
||||
}
|
||||
if (windows["weekly (7d)"] && !normalized.window7d) {
|
||||
normalized.window7d = windows["weekly (7d)"];
|
||||
}
|
||||
|
||||
// Antigravity-style per-model 5h windows: pick the worst (most used) model quota.
|
||||
const modelWindows = Object.entries(windows).filter(
|
||||
([key]) =>
|
||||
key !== "credits" &&
|
||||
!key.endsWith("_weekly") &&
|
||||
!key.startsWith("window") &&
|
||||
!key.includes("(5h)") &&
|
||||
!key.includes("(7d)")
|
||||
);
|
||||
if (modelWindows.length > 0 && !normalized.window5h) {
|
||||
const worst = modelWindows.reduce((a, b) => (a[1].percentUsed > b[1].percentUsed ? a : b));
|
||||
normalized.window5h = worst[1];
|
||||
}
|
||||
|
||||
// Antigravity-style weekly family buckets: pick the worst *_weekly quota.
|
||||
const weeklyWindows = Object.entries(windows).filter(([key]) => key.endsWith("_weekly"));
|
||||
if (weeklyWindows.length > 0 && !normalized.window7d) {
|
||||
const worst = weeklyWindows.reduce((a, b) => (a[1].percentUsed > b[1].percentUsed ? a : b));
|
||||
normalized.window7d = worst[1];
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch quota for a connection by delegating to the appropriate
|
||||
* provider-specific usage fetcher and reshaping its output into the
|
||||
|
||||
@@ -48,6 +48,16 @@ export interface QuotaInfo {
|
||||
* (e.g. "session", "weekly", "monthly").
|
||||
*/
|
||||
windows?: Record<string, QuotaWindowInfo>;
|
||||
/**
|
||||
* Structural, canonical window snapshots used by reset-aware / reset-window
|
||||
* scoring. Providers that expose time-based windows (5h, weekly, monthly)
|
||||
* populate these in addition to the provider-native `windows` map so the
|
||||
* scorer does not need to know every provider's key naming convention.
|
||||
*/
|
||||
window5h?: QuotaWindowInfo;
|
||||
window7d?: QuotaWindowInfo;
|
||||
windowWeekly?: QuotaWindowInfo;
|
||||
windowMonthly?: QuotaWindowInfo;
|
||||
/** True when the upstream usage endpoint explicitly reports exhausted quota. */
|
||||
limitReached?: boolean;
|
||||
}
|
||||
|
||||
@@ -96,13 +96,6 @@ const ARK_CONSOLE_URL =
|
||||
/** Cookie names required for a valid console session (mirrors tokenExtractionConfig) */
|
||||
const REQUIRED_COOKIES = ["digest", "AccountID", "csrfToken", "userInfo"] as const;
|
||||
|
||||
/** Exact-domain match for session cookies — substring checks would also accept
|
||||
* look-alike hosts (e.g. `volcengine.com.evil.test`). Playwright may report the
|
||||
* domain with or without a leading dot. */
|
||||
function isVolcengineCookieDomain(domain: string): boolean {
|
||||
return domain === "volcengine.com" || domain.endsWith(".volcengine.com");
|
||||
}
|
||||
|
||||
const DEFAULT_SESSION_TIMEOUT = 300_000;
|
||||
const SUBMIT_COOKIE_TIMEOUT = 90_000;
|
||||
const CAPTURE_POLL_INTERVAL = 1_000;
|
||||
@@ -625,7 +618,7 @@ export class VolcengineConsoleAutoLoginService {
|
||||
for (const cookie of cookies as Array<{ name: string; domain: string; value: string }>) {
|
||||
if (
|
||||
REQUIRED_COOKIES.includes(cookie.name as (typeof REQUIRED_COOKIES)[number]) &&
|
||||
isVolcengineCookieDomain(cookie.domain)
|
||||
cookie.domain.includes("volcengine.com")
|
||||
) {
|
||||
credentials[cookie.name] = cookie.value;
|
||||
}
|
||||
@@ -770,7 +763,7 @@ export class VolcengineConsoleAutoLoginService {
|
||||
domain: string;
|
||||
}>;
|
||||
const present = REQUIRED_COOKIES.filter((name) =>
|
||||
cookies.some((c) => c.name === name && isVolcengineCookieDomain(c.domain))
|
||||
cookies.some((c) => c.name === name && c.domain.includes("volcengine.com"))
|
||||
);
|
||||
parts.push(
|
||||
`cookies=[${present.join(",") || "none of digest/AccountID/csrfToken/userInfo"}]`
|
||||
|
||||
2436
package-lock.json
generated
51
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.8.51",
|
||||
"description": "Unified AI router with 353 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
|
||||
"description": "Unified AI router with 350 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"omniroute": "bin/omniroute.mjs",
|
||||
@@ -261,7 +261,7 @@
|
||||
"alibaba:sync-allowlist": "node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.1112.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.1116.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -280,29 +280,29 @@
|
||||
"commander": "^15.0.0",
|
||||
"cron-parser": "^5.10.0",
|
||||
"csv-stringify": "^6.8.3",
|
||||
"dompurify": "^3.4.13",
|
||||
"dompurify": "^3.4.14",
|
||||
"express": "^5.2.1",
|
||||
"fetch-socks": "^1.3.3",
|
||||
"fflate": "^0.8.3",
|
||||
"fumadocs-core": "^16.14.4",
|
||||
"fumadocs-ui": "^16.14.4",
|
||||
"fumadocs-core": "^16.15.1",
|
||||
"fumadocs-ui": "^16.15.1",
|
||||
"http-proxy-middleware": "^4.0.0",
|
||||
"https-proxy-agent": "^9.0.0",
|
||||
"ink": "^7.0.3",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"ioredis": "^5.10.1",
|
||||
"jose": "^6.2.9",
|
||||
"jose": "^6.2.10",
|
||||
"js-yaml": "^5.3.0",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"lowdb": "^7.0.1",
|
||||
"lucide-react": "^1.21.0",
|
||||
"marked": "^18.0.9",
|
||||
"lucide-react": "^1.33.0",
|
||||
"marked": "^18.0.10",
|
||||
"marked-terminal": "^7.3.0",
|
||||
"material-symbols": "^0.46.0",
|
||||
"mermaid": "^11.15.0",
|
||||
"mermaid": "^11.17.0",
|
||||
"monaco-editor": "^0.56.0",
|
||||
"next": "16.3.1",
|
||||
"next": "16.3.2",
|
||||
"next-intl": "^4.13.7",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-machine-id": "^1.1.12",
|
||||
@@ -332,7 +332,7 @@
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"undici": "^8.10.0",
|
||||
"update-notifier": "^7.3.1",
|
||||
"uuid": "^14.0.0",
|
||||
"uuid": "^14.0.2",
|
||||
"ws": "^8.21.3",
|
||||
"xxhash-wasm": "^1.1.0",
|
||||
"yazl": "^3.3.1",
|
||||
@@ -348,11 +348,12 @@
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"sqlite-vec": "^0.1.9",
|
||||
"tls-client-node": "^0.2.0",
|
||||
"wreq-js": "^3.0.0"
|
||||
"wreq-js": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.13.0",
|
||||
"@cyclonedx/cyclonedx-npm": "6.0.1",
|
||||
"@eslint/compat": "^2.1.0",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@size-limit/file": "^13.0.3",
|
||||
"@stryker-mutator/core": "^10.0.0",
|
||||
@@ -363,24 +364,25 @@
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.6",
|
||||
"@types/better-sqlite3": "^9.6.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "1.4.0",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@types/safe-regex": "^1.1.6",
|
||||
"@types/ws": "^8.18.0",
|
||||
"@vitejs/plugin-react": "^6.0.5",
|
||||
"bun": "1.3.14",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"bun": "1.4.0",
|
||||
"c8": "^12.0.0",
|
||||
"concurrently": "^10.0.5",
|
||||
"cross-env": "^10.1.0",
|
||||
"ctrf": "^0.3.0",
|
||||
"dpdm": "^4.3.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-next": "16.3.1",
|
||||
"eslint": "^10.9.0",
|
||||
"eslint-config-next": "16.3.2",
|
||||
"eslint-plugin-sonarjs": "^4.1.0",
|
||||
"espree": "^11.2.0",
|
||||
"fast-check": "^4.8.0",
|
||||
"fumadocs-mdx": "^15.2.3",
|
||||
"fumadocs-mdx": "^15.3.1",
|
||||
"glob": "^13.0.6",
|
||||
"httpyac": "^6.16.7",
|
||||
"husky": "^9.1.7",
|
||||
@@ -392,7 +394,7 @@
|
||||
"lint-staged": "^17.3.0",
|
||||
"lockfile-lint": "^5.0.1",
|
||||
"node-loader": "^2.1.0",
|
||||
"opencode-ai": "1.18.18",
|
||||
"opencode-ai": "1.18.21",
|
||||
"playwright-ctrf-json-reporter": "^0.0.29",
|
||||
"prettier": "^3.9.6",
|
||||
"promptfoo": "^0.122.0",
|
||||
@@ -401,7 +403,7 @@
|
||||
"type-coverage": "^2.30.1",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
"vitest": "^4.1.7",
|
||||
"vitest": "^4.1.11",
|
||||
"wait-on": "^9.1.0",
|
||||
"wtfnode": "^0.10.1"
|
||||
},
|
||||
@@ -435,12 +437,13 @@
|
||||
},
|
||||
"overrides": {
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"fast-xml-parser": "^5.10.1",
|
||||
"sharp": "^0.35.3",
|
||||
"postcss": "^8.5.18",
|
||||
"ip-address": "^10.3.1",
|
||||
"qs": "^6.15.2",
|
||||
"uuid": "^14.0.0",
|
||||
"uuid": "^14.0.2",
|
||||
"form-data": "^4.0.6",
|
||||
"vite": "^8.0.16",
|
||||
"protobufjs": "^7.6.5",
|
||||
@@ -468,10 +471,6 @@
|
||||
},
|
||||
"socket.io-parser": "^4.2.7",
|
||||
"tar": "^7.5.21",
|
||||
"brace-expansion": "^5.0.9",
|
||||
"minimatch": {
|
||||
"brace-expansion": "^1.1.18"
|
||||
},
|
||||
"libxmljs2": {
|
||||
"minimatch": {
|
||||
"brace-expansion": "^2.1.4"
|
||||
@@ -496,7 +495,7 @@
|
||||
},
|
||||
"nanoid": "^3.3.17",
|
||||
"monaco-editor": {
|
||||
"dompurify": "^3.4.13"
|
||||
"dompurify": "^3.4.14"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
scripts/build/afterpack-copy-node-modules.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
// electron-builder >=26 injects an "!**/node_modules/**" ignore into every
|
||||
// extraResources/extraFiles pattern list (app-builder-lib/out/fileMatcher.js),
|
||||
// and that ignore cannot be overridden by any later positive filter pattern —
|
||||
// verified empirically with a minimal fixture on 26.15.3. The standalone
|
||||
// server resolves better-sqlite3 (and other runtime deps) from the *primary*
|
||||
// node_modules at resources/app/node_modules (see
|
||||
// prepare-electron-standalone.mjs: "Verify better-sqlite3 Node-API prebuilds in
|
||||
// the primary node_modules"), so without this hook the packaged desktop app silently loses its native
|
||||
// SQLite driver and falls back to sql.js — the exact regression guarded by
|
||||
// issue #7592's cold-restart smoke check.
|
||||
export default async function afterPack(context) {
|
||||
const stagingNodeModules = join(
|
||||
context.packager.projectDir,
|
||||
"..",
|
||||
".build",
|
||||
"electron-standalone",
|
||||
"node_modules"
|
||||
);
|
||||
const destNodeModules = join(context.appOutDir, "resources", "app", "node_modules");
|
||||
|
||||
if (!existsSync(stagingNodeModules)) {
|
||||
console.warn(`[afterpack] no staged node_modules at ${stagingNodeModules} — skipping restore`);
|
||||
return;
|
||||
}
|
||||
|
||||
rmSync(destNodeModules, { recursive: true, force: true });
|
||||
cpSync(stagingNodeModules, destNodeModules, { recursive: true });
|
||||
console.log(
|
||||
`[afterpack] restored ${readdirSync(destNodeModules).length} runtime module(s) into resources/app/node_modules`
|
||||
);
|
||||
}
|
||||
@@ -89,16 +89,20 @@ function moveTree(src, dest) {
|
||||
return { removedFiles: files, removedBytes: bytes };
|
||||
}
|
||||
|
||||
function tarPack(packOutDir, tarballPath) {
|
||||
export function tarPack(packOutDir, tarballPath) {
|
||||
// bsdtar ships with macOS, Linux images, and Windows runners (System32\tar.exe).
|
||||
// GNU tar (common in Git-for-Windows environments) treats `C:\...` in `-f` as a
|
||||
// remote rsh target ("Cannot connect to C:"), so always pass a bare filename
|
||||
// and point cwd at the tarball directory instead.
|
||||
const result = spawnSync(
|
||||
process.platform === "win32" ? "tar.exe" : "tar",
|
||||
["-czf", tarballPath, "-C", packOutDir, "node_modules"],
|
||||
{ stdio: "pipe" }
|
||||
["-czf", path.basename(tarballPath), "-C", packOutDir, "node_modules"],
|
||||
{ stdio: "pipe", cwd: path.dirname(tarballPath) }
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`optional-pack tar failed for ${path.basename(tarballPath)} (exit ${result.status})`
|
||||
`optional-pack tar failed for ${path.basename(tarballPath)} (exit ${result.status})` +
|
||||
`: ${(result.stderr || result.stdout || "").toString().slice(-600)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ function isInsideDir(parentDir, candidateDir) {
|
||||
return candidate === parent || candidate.startsWith(parent + sep);
|
||||
}
|
||||
|
||||
async function ensureSmokeEnvDirs(smokeEnv, dataDir) {
|
||||
export async function ensureSmokeEnvDirs(smokeEnv, dataDir) {
|
||||
const dirNames = [
|
||||
"DATA_DIR",
|
||||
"HOME",
|
||||
@@ -388,6 +388,16 @@ async function ensureSmokeEnvDirs(smokeEnv, dataDir) {
|
||||
dirs.push(join(smokeEnv.APPDATA, subdir));
|
||||
}
|
||||
}
|
||||
// Electron resolves the Roaming profile from %USERPROFILE%\AppData\Roaming
|
||||
// (USERPROFILE takes precedence over the APPDATA env var) and the path
|
||||
// service throws — rather than creates — when that directory is missing,
|
||||
// which makes requestSingleInstanceLock() return false and the app exit(0)
|
||||
// before app.whenReady(). Pre-create the derived tree as well.
|
||||
if (platform() === "win32" && smokeEnv.USERPROFILE) {
|
||||
for (const subdir of ["omniroute-desktop", "OmniRoute", "omniroute"]) {
|
||||
dirs.push(join(smokeEnv.USERPROFILE, "AppData", "Roaming", subdir));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(dirs.map((dir) => mkdir(dir, { recursive: true })));
|
||||
}
|
||||
@@ -506,7 +516,14 @@ async function waitForReady({ logs, smokeUrl, timeoutMs, settleMs, exitState })
|
||||
* by the single-launch path and the cold-restart (two-launch) path so both
|
||||
* exercise identical spawn/readiness/shutdown behavior.
|
||||
*/
|
||||
async function launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs }) {
|
||||
async function launchAndCollectLogs({
|
||||
appExecutable,
|
||||
smokeUrl,
|
||||
dataDir,
|
||||
timeoutMs,
|
||||
settleMs,
|
||||
streamLogs,
|
||||
}) {
|
||||
const smokeEnv = buildSmokeEnv({ dataDir });
|
||||
await assertPortIsFree(smokeUrl);
|
||||
await ensureSmokeEnvDirs(smokeEnv, dataDir);
|
||||
@@ -568,7 +585,14 @@ async function main() {
|
||||
!process.env.ELECTRON_SMOKE_DATA_DIR && process.env.ELECTRON_SMOKE_KEEP_DATA !== "1";
|
||||
|
||||
try {
|
||||
await launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs });
|
||||
await launchAndCollectLogs({
|
||||
appExecutable,
|
||||
smokeUrl,
|
||||
dataDir,
|
||||
timeoutMs,
|
||||
settleMs,
|
||||
streamLogs,
|
||||
});
|
||||
|
||||
if (!coldRestart) return;
|
||||
|
||||
|
||||
@@ -89,22 +89,46 @@ export default function EmbeddingSourceSelector({ settings, providers, onSave, s
|
||||
{t("embedding.noRemoteProviders")}
|
||||
</p>
|
||||
) : (
|
||||
<select
|
||||
value={currentProviderModel}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
data-testid="embedding-provider-model-select"
|
||||
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
>
|
||||
<option value="">{t("embedding.selectProviderModel")}</option>
|
||||
{remoteProviders.map((p) =>
|
||||
p.models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.dimensions ? `${m.dimensions}d` : "?"})
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
<>
|
||||
<select
|
||||
value={
|
||||
remoteProviders.some((p) => p.models.some((m) => m.id === currentProviderModel))
|
||||
? currentProviderModel
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
data-testid="embedding-provider-model-select"
|
||||
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
>
|
||||
<option value="">{t("embedding.selectProviderModel")}</option>
|
||||
{remoteProviders.map((p) =>
|
||||
p.models.length > 0 ? (
|
||||
p.models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name} ({m.dimensions ? `${m.dimensions}d` : "?"})
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<optgroup key={p.provider} label={p.provider}>
|
||||
<option value="">{`— ${p.provider} (no curated models)`}</option>
|
||||
</optgroup>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
{/* Free-text override: the runtime accepts any configured provider's
|
||||
OpenAI-compatible model id, including ones without a curated
|
||||
registry entry (e.g. groq/, mistral/, cf/...). */}
|
||||
<input
|
||||
type="text"
|
||||
value={currentProviderModel}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
placeholder="provider/model — e.g. mistral/mistral-embed"
|
||||
data-testid="embedding-provider-model-input"
|
||||
className="w-full mt-2 px-3 py-2 rounded-lg bg-background border border-border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<CustomEmbeddingEndpointFields settings={settings} onSave={onSave} saving={saving} />
|
||||
</div>
|
||||
|
||||
@@ -43,11 +43,7 @@ export default function RerankConfigCard({ settings, providers, onSave, saving }
|
||||
}}
|
||||
disabled={saving || (!rerankEnabled && !hasProvider)}
|
||||
aria-disabled={saving || (!rerankEnabled && !hasProvider)}
|
||||
title={
|
||||
!rerankEnabled && !hasProvider
|
||||
? t("rerank.noProviderWithKey")
|
||||
: undefined
|
||||
}
|
||||
title={!rerankEnabled && !hasProvider ? t("rerank.noProviderWithKey") : undefined}
|
||||
role="switch"
|
||||
aria-checked={rerankEnabled}
|
||||
className={`relative w-11 h-6 rounded-full transition-colors shrink-0 disabled:opacity-50 disabled:cursor-not-allowed ${
|
||||
@@ -83,22 +79,45 @@ export default function RerankConfigCard({ settings, providers, onSave, saving }
|
||||
{t("rerank.noProviderWithKey")}
|
||||
</p>
|
||||
) : (
|
||||
<select
|
||||
value={rerankProviderModel}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
data-testid="rerank-provider-model-select"
|
||||
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
>
|
||||
<option value="">{t("rerank.selectProviderModel")}</option>
|
||||
{rerankProviders.map((p) =>
|
||||
p.models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
)),
|
||||
)}
|
||||
</select>
|
||||
<>
|
||||
<select
|
||||
value={
|
||||
rerankProviders.some((p) => p.models.some((m) => m.id === rerankProviderModel))
|
||||
? rerankProviderModel
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
data-testid="rerank-provider-model-select"
|
||||
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
>
|
||||
<option value="">{t("rerank.selectProviderModel")}</option>
|
||||
{rerankProviders.map((p) =>
|
||||
p.models.length > 0 ? (
|
||||
p.models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<optgroup key={p.provider} label={p.provider}>
|
||||
<option value="">{`— ${p.provider} (no curated models)`}</option>
|
||||
</optgroup>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
{/* Free-text override: any configured provider's Cohere-compatible
|
||||
model id is accepted by the runtime even without a curated entry. */}
|
||||
<input
|
||||
type="text"
|
||||
value={rerankProviderModel}
|
||||
onChange={(e) => handleProviderModelChange(e.target.value)}
|
||||
disabled={saving}
|
||||
placeholder="provider/model — e.g. groq/my-reranker"
|
||||
data-testid="rerank-provider-model-input"
|
||||
className="w-full mt-2 px-3 py-2 rounded-lg bg-background border border-border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-violet-500"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function EngineTab() {
|
||||
const { status, isLoading: statusLoading } = useEngineStatus();
|
||||
const { settings, save: saveSettings, isLoading: settingsLoading } = useMemorySettings();
|
||||
const [providers, setProviders] = useState<EmbeddingProviderListing[]>([]);
|
||||
const [rerankProviders, setRerankProviders] = useState<EmbeddingProviderListing[]>([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [reindexing, setReindexing] = useState(false);
|
||||
const [reindexMsg, setReindexMsg] = useState("");
|
||||
@@ -32,6 +33,14 @@ export default function EngineTab() {
|
||||
if (!cancelled && data?.providers) setProviders(data.providers);
|
||||
})
|
||||
.catch(() => {});
|
||||
// Rerank has its own curated registry — the embedding listing does not
|
||||
// include rerank-only providers (cohere rerank SKUs, siliconflow, ...).
|
||||
fetch("/api/memory/rerank-providers")
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data) => {
|
||||
if (!cancelled && data?.providers) setRerankProviders(data.providers);
|
||||
})
|
||||
.catch(() => {});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
@@ -139,7 +148,7 @@ export default function EngineTab() {
|
||||
</h3>
|
||||
<RerankConfigCard
|
||||
settings={settings}
|
||||
providers={providers}
|
||||
providers={rerankProviders}
|
||||
onSave={handleSaveSettings}
|
||||
saving={saving}
|
||||
/>
|
||||
|
||||