Compare commits

..

30 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
cab0b0fbd4 docs(changelog): link video transcript PR 2026-08-28 02:00:30 -03:00
Diego Rodrigues de Sa e Souza
af53c8289d fix(video-bridge): harden transcript retention boundaries 2026-08-28 02:00:30 -03:00
Diego Rodrigues de Sa e Souza
d715081f50 feat(video-bridge): derive embedded transcript provenance 2026-08-28 02:00:29 -03:00
Ravi Tharuma
825f8fe425 fix(providers): honor model auto-fetch opt-in on create (#11798)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Honra o contrato `autoFetchModels` default-off nos dois caminhos de criação de provider; zero chamadas de auto-discovery quando a flag está ausente/false.
- Roda exatamente um sync quando habilitado explicitamente (clientes API usam o sync em background server-owned; o dashboard assume quando precisa de UI de progresso).
- Testes focados verdes: `tests/unit/providers-route-model-autofetch-optin.test.ts` (node) + `useApiKeySaveSkipsFullSync.test.tsx` (vitest).
- Gates estáticos do lote OK.
2026-08-27 22:15:23 -03:00
Ravi Tharuma
cfeed516e8 fix(dashboard): make model auto-fetch order independent (#11805)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- TDD claro: implementação antiga falhava 1/5 no caso "enabled-first mixed" (postava `/sync-models?mode=sync` incorretamente); implementação corrigida passa 5/5.
- Avalia todas as conexões ativas antes de tratar o auto-fetch como habilitado, tornando o resultado independente da ordem de conexões API/DB.
- `tests/unit/ui/use-provider-models-auto-fetch.test.tsx` — verde via vitest.
- Gates estáticos do lote OK (incluindo confirmação de que o único erro de lint pré-existente no arquivo tocado apenas mudou de linha 130→137 por causa das linhas adicionadas por esta PR — sem regressão real).
2026-08-27 22:15:18 -03:00
Ravi Tharuma
d937b5229e fix(sse): preserve CLIProxyAPI wrappers per connection (#11797)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Roteia corretamente o override `cliproxyapiMode: "claude-native"` por conexão através dos wrappers de credencial/mapeamento já existentes, evitando vazamento de credencial nativa e modelo não mapeado no caminho de passthrough.
- Regressão de wire-level cobrindo headers e body: `tests/unit/cliproxyapi-dedicated-credential-7645.test.ts` — verde, incluindo os testes irmãos `cliproxyapi-model-mapping-dispatch` e `cliproxyapi-fallback-wiring`.
- Gates estáticos do lote OK.
2026-08-27 22:15:10 -03:00
Ravi Tharuma
321935d261 fix(services): separate CLIProxyAPI health and model auth (#11811)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Separação correta de responsabilidades: probe de saúde via `/healthz` público vs. autenticação de `/v1/models` com `settings.cliproxyapi_api_key` dedicada; `MANAGEMENT_PASSWORD` mantida estritamente no plano de gestão do CLIProxyAPI.
- Evidência RED→GREEN documentada e reproduzida: `tests/unit/services/cliproxy-health-model-auth.test.ts` — verde no lote.
- `⚠️ base-red inherited: #11449` reconhecido — não é causado por esta PR.
- Gates estáticos do lote OK.
2026-08-27 22:15:06 -03:00
Ravi Tharuma
a14a91dcfe perf(compression): avoid cloning memo misses twice (#11727)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- TDD claro: sem o fix, o teste focado falha nas asserções de contagem exata de lookup sync/async; com o fix, `tests/unit/compression/result-memo.test.ts` passa (34/34).
- Remove supressão eslint agora obsoleta (`no-unused-vars` no arquivo de teste).
- `⚠️ base-red inherited: #11449` reconhecido e verificado — não é responsabilidade desta PR (confirmado via probe-worktree do tip puro).
- Gates estáticos do lote OK.
2026-08-27 22:14:57 -03:00
Ravi Tharuma
09fa818365 fix(docker): make GHA cache exports best effort (#11806)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Incidente real documentado com link da run do GHA (`404 BlobNotFound` no cache exporter do Azure Actions), publicação Docker Hub/GHCR já bem-sucedida.
- Regressão estática nova: inventário dos 4 escopos de cache GHA garantindo `ignore-error=true`.
- Não altera falhas de build/push — só isola falha opcional de cache.
- Gates estáticos do lote OK.
2026-08-27 22:14:53 -03:00
Ravi Tharuma
9be4fd0a0e fix(models): hide unsupported Gemini Live discovery (#11710)
Obrigado! Validado em lote combinado (8 PRs, release/v3.8.51):

- Follow-up correto de #10590/#10591 — omite modelos Gemini Live-only da descoberta dinâmica, preserva chat para modelos híbridos.
- Teste focado atualizado: `tests/unit/gemini-models-parser.test.ts` — 6/6 verde.
- Gates estáticos do lote OK (typecheck, lint sem regressão, complexity, cognitive, file-size, changelog-integrity).
2026-08-27 22:14:45 -03:00
Ravi Tharuma
a45e80af43 fix(db): skip absent compression telemetry cleanup table (#11802)
Obrigado pela correção! Validado em lote combinado (8 PRs, release/v3.8.51):

- Reprodução com imagem pública confirmada no corpo da PR (`compression_run_telemetry` ausente após fresh install).
- Reutiliza o helper `tableExists()` já existente — sem SQL cru novo.
- Testes focados: 7/7 em `tests/unit/telemetry-auto-cleanup-6848.test.ts` (verde).
- Gates estáticos do lote: typecheck, lint (228 erros pré-existentes na tip pura, confirmado via probe-worktree — zero regressão), complexity, cognitive-complexity, file-size e changelog-integrity — todos OK.
2026-08-27 22:14:42 -03:00
Diego Rodrigues de Sa e Souza
2acbfc6fa6 Merge pull request #11751 from diegosouzapw/test/v3851-tiered-rotation-timeout
test(auto-combo): tolerate full-suite rotation load
2026-08-27 02:02:24 -03:00
diegosouzapw
ad3e293f9f test(auto-combo): tolerate full-suite rotation load 2026-08-27 01:41:30 -03:00
Diego Rodrigues de Sa e Souza
c9f11d86b5 Merge pull request #11733 from diegosouzapw/chore/v3851-prune-stale-eslint-suppressions-2
chore(lint): drain remaining release base-reds
2026-08-27 01:15:48 -03:00
diegosouzapw
21ed68d8ac test: drain inherited quality snapshot reds 2026-08-27 00:43:57 -03:00
diegosouzapw
d35b3f9779 chore(quality): rebaseline measured CLI bundle growth 2026-08-27 00:11:28 -03:00
diegosouzapw
9587e07b69 fix(ui): fall back from invalid AnySearch icon alias 2026-08-27 00:05:47 -03:00
diegosouzapw
12cc6ca834 test(ui): reproduce invalid AnySearch icon alias 2026-08-27 00:01:12 -03:00
diegosouzapw
4f11b2ae3d docs: sync provider catalog count 2026-08-26 23:20:31 -03:00
diegosouzapw
b4e76a7ed9 docs(skills): sync omni-inference search fallback 2026-08-26 23:08:56 -03:00
diegosouzapw
b85d0abb0b fix(test): capture hook result after render 2026-08-26 22:45:24 -03:00
diegosouzapw
1325047d56 chore(lint): prune two stale base suppressions 2026-08-26 22:42:38 -03:00
Diego Rodrigues de Sa e Souza
2e8326d531 fix(db): dedupe lazy-decrypt-view failure logging across sync cycles (#11500) (#11678) 2026-08-26 20:50:15 -03:00
Diego Rodrigues de Sa e Souza
7133585d1d fix(combos): use the routing-alias prefix, not raw providerId, when serializing combo model steps (#11433) (#11670) 2026-08-26 20:50:09 -03:00
Diego Rodrigues de Sa e Souza
bcda889f84 fix(sse): keep each functionCall's own thoughtSignature on parallel Gemini tool-call turns (#11510) (#11669) 2026-08-26 20:50:02 -03:00
Diego Rodrigues de Sa e Souza
91ecb6be9c fix(sse): cap streaming headers-wait timeout to a client-realistic ceiling (#11526) (#11668) 2026-08-26 20:49:55 -03:00
Diego Rodrigues de Sa e Souza
e02f11984f fix(kie): correct 12 more mismatched KIE Market model ids (#11296) (#11667) 2026-08-26 20:49:48 -03:00
Diego Rodrigues de Sa e Souza
ca4df9bef8 fix(combo): attach combo diagnostics to round-robin and runtime-unit retry-limit 503s (#11462) (#11665) 2026-08-26 20:49:42 -03:00
Diego Rodrigues de Sa e Souza
0ce2b83005 fix(dashboard): skip full /sync-models catalog fetch when caller opts out (#11324) (#11664) 2026-08-26 20:49:34 -03:00
Diego Rodrigues de Sa e Souza
124f4cf761 fix(docker): re-derive build memory budget from measured per-process RSS (#7518) (#11663) 2026-08-26 20:49:27 -03:00
165 changed files with 7446 additions and 925 deletions

View File

@@ -171,7 +171,7 @@ jobs:
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-${{ matrix.arch }}
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
@@ -188,7 +188,7 @@ jobs:
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-web-${{ matrix.arch }}
cache-to: type=gha,scope=docker-web-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-web-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
@@ -206,7 +206,7 @@ jobs:
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-bun-base-${{ matrix.arch }}
cache-to: type=gha,scope=docker-bun-base-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-bun-base-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1
@@ -224,7 +224,7 @@ jobs:
${{ env.IMAGE_NAME }}
${{ env.GHCR_IMAGE_NAME }}
cache-from: type=gha,scope=docker-bun-web-${{ matrix.arch }}
cache-to: type=gha,scope=docker-bun-web-${{ matrix.arch }},mode=max
cache-to: type=gha,scope=docker-bun-web-${{ matrix.arch }},mode=max,ignore-error=true
no-cache: false
env:
DOCKER_BUILDKIT_INLINE_CACHE: 1

View File

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

View File

@@ -184,19 +184,29 @@ ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_BUILD_MEMORY_MB}"
# silently leaving no standalone bundle. Next derives the worker count from
# CIRCLE_NODE_TOTAL (workers = N-1). (#10060)
#
# Lowered 8 → 3 (7 workers → 2). Every page-data worker inherits NODE_OPTIONS
# above, so the ceiling is per PROCESS, not per build: 7 workers on a 16 GB
# GitHub runner (ubuntu-24.04 / ubuntu-24.04-arm, 4 vCPU) exhausted the host and
# buildkit failed the whole step with `ResourceExhausted: ... cannot allocate
# memory`. The compile phase always finished ("✓ Compiled successfully in
# 4.2min"); the kernel killed the build right after "Collecting page data using
# 7 workers". It was intermittent for a while and went 100% on 2026-08-22, which
# is what a threshold being crossed by ordinary codebase growth looks like.
# tests/unit/docker-build-memory-budget.test.ts does the arithmetic and fails if
# either knob is raised past what a 16 GB runner holds. 2 workers also stops
# oversubscribing the runner's 4 vCPU, which 7 did. Override for a big builder:
# `--build-arg OMNIROUTE_BUILD_WORKERS=8`.
ARG OMNIROUTE_BUILD_WORKERS=3
# Lowered 8 → 3 (7 workers → 2) in #11419, then 3 → 2 (2 workers → 1) in #7518.
# Every page-data worker inherits NODE_OPTIONS above, so the ceiling is per
# PROCESS, not per build: 7 workers on a 16 GB GitHub runner (ubuntu-24.04 /
# ubuntu-24.04-arm, 4 vCPU) exhausted the host and buildkit failed the whole
# step with `ResourceExhausted: ... cannot allocate memory`. The compile phase
# always finished ("✓ Compiled successfully in 4.2min"); the kernel killed the
# build right after "Collecting page data using N workers".
#
# #11419's first fix (8 → 3) modeled the per-worker peak as an INFERENCE
# (2560 MB, guessed from "7 workers didn't fit") and assumed the parent
# process's RSS tracked the V8 heap ceiling. Both assumptions were wrong: a
# live VPS reproduction (issue #7518, dmesg OOM-killer report) measured the
# real per-process RSS directly at ~4.5 GB, independent of the NODE_OPTIONS
# heap flag (Turbopack itself is native/Rust, outside the V8 heap) — and it
# applies to the parent process too, not just workers. 2 workers (3 processes
# × 4.5 GB = 13.5 GB) still didn't fit the 12.288 GB (75%) budget on a 16 GB
# runner, matching the still-live publish failures after #11419 merged. 1
# worker (2 processes × 4.5 GB = 9 GB) fits with headroom to spare.
# tests/unit/docker-build-memory-budget.test.ts does the arithmetic against
# the measured figure and fails if either knob is raised past what a 16 GB
# runner holds. Override for a big builder: `--build-arg
# OMNIROUTE_BUILD_WORKERS=8`.
ARG OMNIROUTE_BUILD_WORKERS=2
ENV CIRCLE_NODE_TOTAL=${OMNIROUTE_BUILD_WORKERS}
COPY . ./

View File

@@ -7,7 +7,7 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 356 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 356 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 → 357 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 357 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 356 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 356 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 357 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 357 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: 356 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: 357 providers, 90+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -559,7 +559,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [API Reference](docs/reference/API_REFERENCE.md)
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **356-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 **357-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,7 +642,7 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<div align="center">
## 🌐 356 AI Providers — 154 Catalog-Marked Free
## 🌐 357 AI Providers — 154 Catalog-Marked Free
</div>

View File

@@ -0,0 +1 @@
- **feat(modality-bridge):** derive bounded embedded text subtitles from local Video Bridge bytes with authenticated provenance, focused source-aware reconciliation, cache-safe fingerprints, call-log carrier redaction, Memory fact-extraction isolation, and fail-open FFmpeg cleanup ([#11680](https://github.com/diegosouzapw/OmniRoute/pull/11680))

View File

@@ -0,0 +1 @@
- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol.

View File

@@ -0,0 +1 @@
- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification.

View File

@@ -0,0 +1 @@
- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324)

View File

@@ -0,0 +1 @@
- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)).

View File

@@ -0,0 +1 @@
- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449)

View File

@@ -0,0 +1 @@
- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)).

View File

@@ -0,0 +1 @@
- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)).

View File

@@ -0,0 +1 @@
- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)).

View File

@@ -0,0 +1 @@
- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526)

View File

@@ -0,0 +1 @@
- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma

View File

@@ -0,0 +1 @@
- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811))

View File

@@ -0,0 +1 @@
- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518).

View File

@@ -3433,7 +3433,7 @@
"count": 15
},
"@typescript-eslint/no-unused-vars": {
"count": 2
"count": 1
}
},
"tests/integration/traffic-inspector-error-sanitization.test.ts": {
@@ -3680,11 +3680,6 @@
"count": 15
}
},
"tests/unit/authz/probe-9033-repro.test.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
}
},
"tests/unit/autoCombo/tieredRotation.test.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 3
@@ -4544,11 +4539,6 @@
"count": 1
}
},
"tests/unit/compression/result-memo.test.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
}
},
"tests/unit/compression/rtk-grouping.test.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1

View File

@@ -188,14 +188,15 @@
"dedicatedGate": true
},
"bundleSize": {
"value": 8461,
"value": 8653,
"direction": "down",
"dedicatedGate": true,
"_rebaseline_2026_07_07_v3846_release_close": "5601->6534 (+933). v3.8.46 release close: gzip of the 4 bin/*.mjs entrypoints (size-limit + @size-limit/file) grew from this cycle's feature/fix merges pulled transitively into the CLI entrypoints (new providers, combo pipeline strategy #6396, effort/thinking standardization #6241, catalog cache-invalidation #6408). Measured 6534 locally via `check:bundle-size --ratchet` (deterministic gzip, matches CI). Legitimate cycle growth; shrink is separate debt.",
"_rebaseline_2026_07_19_7808_codeql_alias_resolver_hook": "6534->6762 (+228). PR #7808 (CodeQL js/incomplete-url-substring-sanitization fix): the ESM loader hook source moved out of the inline `HOOK_SOURCE` template literal in bin/aliasResolver.mjs into a real file bin/aliasResolverHook.mjs, loaded via pathToFileURL() instead of a dynamically-built `data:text/javascript,...` URL. The new file is now counted by size-limit as a 5th bin/*.mjs entrypoint. Net +228 = the hook's gzip size (previously hidden inside aliasResolver.mjs because the template literal was compressed away). Security-driven; no shrink opportunity.",
"_rebaseline_2026_07_28_v3849_release_preflight": "6762 -> 7666 (+904). Fechamento do ciclo v3.8.49: gzip dos entrypoints bin/*.mjs (size-limit + @size-limit/file) cresceu com o que os merges do ciclo puxam transitivamente para o CLI (novos provedores — 271->290, seletor de protocolo por conexão #8861, catálogos de busca #8814, resiliência). Crescimento legítimo de ciclo, medido localmente com `npm run check:bundle-size` = 7666 (gzip determinístico, bate com o CI). Encolher é dívida separada.",
"_rebaseline_2026_08_09_v3850_release_close": "7666 -> 8045 (+379 gzip bytes, +4.9%). Release v3.8.50 close reconciliation measured twice with the real size-limit + @size-limit/file path on tip e0ce95c592. Per-entry measurements remain below their absolute budgets: omniroute.mjs 4380/15000, mcp-server.mjs 1195/5000, nodeRuntimeSupport.mjs 887/8000, reset-password.mjs 1583/6000. The growth accumulated through legitimate CLI/runtime work in this cycle, including global-install ESM alias resolution, Termux cache preparation, and MCP stdio startup hardening; no entrypoint is near its absolute ceiling. The direction:down ratchet stays blocking from this exact measured tip.",
"_rebaseline_2026_08_24_ci_green_gates_f95b03d7": "8045 -> 8461 (+416 gzip bytes, +5.2%). CI run 32786966560 (release PR #8875, tip f95b03d7) measured bundleSize=8461 via check:bundle-size --ratchet, above the 8045 baseline left at the v3.8.50 close. The growth comes from the post-freeze back-merge cycle landing in the CLI entrypoints (Synthetic + Kilo Gateway providers, kilo-gateway routing surface). Re-baseline per the gate's own instruction (legitimate cycle growth); shrinking the entrypoints remains separate debt; direction:down ratchet stays blocking from this measured tip."
"_rebaseline_2026_08_24_ci_green_gates_f95b03d7": "8045 -> 8461 (+416 gzip bytes, +5.2%). CI run 32786966560 (release PR #8875, tip f95b03d7) measured bundleSize=8461 via check:bundle-size --ratchet, above the 8045 baseline left at the v3.8.50 close. The growth comes from the post-freeze back-merge cycle landing in the CLI entrypoints (Synthetic + Kilo Gateway providers, kilo-gateway routing surface). Re-baseline per the gate's own instruction (legitimate cycle growth); shrinking the entrypoints remains separate debt; direction:down ratchet stays blocking from this measured tip.",
"_rebaseline_2026_08_27_v3851_volatile_env_warning_11437": "8461 -> 8653 (+192 gzip bytes, +2.3%). Exact paired size-limit measurements on the VPS compared f95b03d709 with release/v3.8.51: only bin/omniroute.mjs changed, 4700 -> 4892; the other three entries remained 1195/983/1583. The growth originates in 943b9aaa84 (#11437), which warns users before a package-local .env is lost on the next global install. The CLI entry remains 4892/15000 bytes (32.6% of its absolute budget). Legitimate bug-fix growth; shrinking stays separate debt and direction:down remains blocking from this measured tip."
},
"openapiBreaking": {
"value": 4,

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (356 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 (357 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

View File

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

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 356 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 357 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">356 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">357 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 356 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 357 providers in</text>
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 356 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: 356 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 357 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: 357 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">356 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">357 providers</tspan><tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
<!-- plug line -->
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity&#160;&#160;&#160;&#160;<tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -226,16 +226,21 @@ Three build args control what the `builder` stage costs. They are build-time onl
| --------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `OMNIROUTE_USE_TURBOPACK` | `1` | `0` builds with webpack instead. Lower peak memory, slower. |
| `OMNIROUTE_BUILD_MEMORY_MB` | `6144` | V8 heap ceiling (`--max-old-space-size`) for the spawned `next build`. |
| `OMNIROUTE_BUILD_WORKERS` | `3` | Feeds `CIRCLE_NODE_TOTAL`; Next derives `workers = N - 1` for page-data collection. |
| `OMNIROUTE_BUILD_WORKERS` | `2` | Feeds `CIRCLE_NODE_TOTAL`; Next derives `workers = N - 1` for page-data collection. |
`OMNIROUTE_BUILD_WORKERS` is the one to raise on a big builder and the one to
suspect when a constrained build dies **after** `✓ Compiled successfully`. Each
page-data worker is its own process and inherits `NODE_OPTIONS`, so the heap
ceiling is per process, not per build: the default of `3` (→ 2 workers) is sized
for the 16 GB / 4 vCPU GitHub-hosted runners the publish pipeline uses. At `8`
(→ 7 workers) that runner ran out of memory and buildkit failed the step with
`ResourceExhausted: ... cannot allocate memory`. `tests/unit/docker-build-memory-budget.test.ts`
does the arithmetic and fails if either knob outgrows the runner.
page-data worker is its own process, and so is the parent `next build` itself;
a live VPS reproduction (issue #7518) measured each process's peak RSS at
~4.5 GB independent of the `NODE_OPTIONS` heap flag (Turbopack compiles in
native/Rust memory outside the V8 heap). The default of `2` (→ 1 worker, 2
processes total) is sized for the 16 GB / 4 vCPU GitHub-hosted runners the
publish pipeline uses. At `8` (→ 7 workers) that runner ran out of memory and
buildkit failed the step with `ResourceExhausted: ... cannot allocate memory`;
`3` (→ 2 workers) still didn't fit once the per-process RSS was measured
directly instead of inferred. `tests/unit/docker-build-memory-budget.test.ts`
does the arithmetic against the measured figure and fails if either knob
outgrows the runner.
Turbopack compiles in native Rust memory that lives **outside** the V8 heap, so
`OMNIROUTE_BUILD_MEMORY_MB` does not bound it. On a host with a memory ceiling the

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -4,8 +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 356 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 357 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
@@ -282,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **356 AI providers** with automatic format translation
- **357 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
@@ -480,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
## v3.8.x Highlights
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.51
lastUpdated: 2026-08-26
lastUpdated: 2026-08-27
---
# 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-26
> **Last generated:** 2026-08-27
Total providers: **356**. See category breakdown below.
Total providers: **357**. See category breakdown below.
## Categories
@@ -381,10 +381,11 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `vllm` | `vllm` | vLLM | Local, self-hosted | [link](https://github.com/vllm-project/vllm) | API key optional. Configure the local vLLM OpenAI-compatible base URL (default: http://localhost:8000/v1). |
| `xinference` | `xinference` | XInference | Local, self-hosted | [link](https://inference.readthedocs.io) | API key optional. Configure the local XInference OpenAI-compatible base URL (default: http://localhost:9997/v1). |
## Search Providers (16)
## Search Providers (17)
| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
| `anysearch-search` | `anysearch` | AnySearch | Search | [link](https://anysearch.com) | Optional API key from anysearch.com (as_sk_...) - free 1000/day; keyless tier has lower limits |
| `brave-search` | `brave-search` | Brave Search | Search | [link](https://brave.com/search/api) | Subscription token from Brave Search API dashboard |
| `context7` | `context7` | Context7 (library docs) | Search | [link](https://context7.com) | API key optional (ctx7sk-...) — anonymous tier works without a key; a key raises the rate limit |
| `exa-search` | `exa-search` | Exa Search | Search | [link](https://exa.ai) | API key from dashboard.exa.ai |
@@ -398,7 +399,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `searxng-search` | `searxng` | SearXNG Search | Search | [link](https://docs.searxng.org) | API key is optional. Set your SearXNG base URL. Some instances may require a bearer token for access. |
| `serper-search` | `serper-search` | Serper Search | Search | [link](https://serper.dev) | API key from serper.dev dashboard |
| `tavily-search` | `tavily-search` | Tavily Search | Search | [link](https://tavily.com) | API key from app.tavily.com (format: tvly-...) |
| `anysearch-search` | `anysearch` | AnySearch | Search | [link](https://anysearch.com/docs) | Optional API key (as_sk_...). Free public web search for agents; 1000 req/day per key, shared with extract. Fallback-only. |
| `x-search` | `x_search` | X Search (Grok) | Search | [link](https://docs.x.ai/developers/tools/x-search) | SuperGrok OAuth (xai-oauth) or xAI API key. This is Grok X Search, not the X Developer MCP. |
| `xquik-search` | `xquik` | Xquik X Search | Search | [link](https://docs.xquik.com) | Xquik API key (xq_...). Search is metered per returned post; the catalog estimate uses 5 results. |
| `youcom-search` | `youcom-search` | You.com Search | Search | [link](https://you.com/business/api/) | X-API-Key from the You.com platform dashboard |
@@ -445,7 +445,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (111 implementations)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (112 implementations)
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
## See Also

View File

@@ -1,13 +1,13 @@
---
title: "Guardrails"
version: 3.8.50
lastUpdated: 2026-08-24
lastUpdated: 2026-08-26
---
# Guardrails
> **Source of truth:** `src/lib/guardrails/`
> **Last updated:** 2026-08-24 — v3.8.50 (Video Bridge visual dedup hardening + focused captions)
> **Last updated:** 2026-08-26 — v3.8.50 (Video Bridge dedup, focus, and embedded transcript provenance)
Guardrails enforce safety, policy, and content transformations at the boundary
between OmniRoute and upstream providers. Each guardrail can inspect (and
@@ -428,30 +428,85 @@ harness makes no network or paid model call unless `--execute-real` is passed an
that explicit real run, its machine-readable verdict remains `HOLD`; synthetic
payload/call-count measurements alone are not promotion evidence.
Callers may attach an optional `transcript.cues` array to a supported video
part when they already possess aligned text. Each cue must carry `text`, a
finite `start`/`end` interval inside the probed duration, and a whitelisted
`source` (`client`, `embedded`, or `audio-bridge`); `confidence` defaults to
`1` and must remain between `0` and `1`. Exact duplicate cues are collapsed.
OmniRoute never starts transcription from this metadata: validated cues are
copied into the described result with source, confidence, and interval, and
are rendered as untrusted observations alongside the frame captions. Invalid,
out-of-range, or provenance-free text is rejected rather than mixed into the
caption stream.
Callers may attach an optional `transcript.cues` array when they already possess
aligned text. Every cue must carry `text`, a finite `start`/`end` interval
inside the probed duration, and `source: "client"`; an external request cannot
self-assert `embedded` provenance. `confidence` defaults to `1` and must remain
between `0` and `1`. Invalid, out-of-range, over-budget, incorrectly sourced,
or provenance-free caller text is rejected rather than mixed into the caption
stream.
An advanced caller may provide an already-authorized `audioTranscript` track
for the same video. The fusion seam runs visual and audio observations under
one deadline and abort signal, orders them on a common timeline, collapses
exact duplicates, and reports a partial result when only one side succeeds.
The broker also attempts one **server-derived embedded text track** from the
same validated private file and within the same deadline, abort signal, and
temporary-directory lifecycle as frame extraction. It never accepts a URL,
sidecar path, network protocol, manifest, custom executable, or caller-declared
subtitle stream. FFprobe may offer only `mov_text`, `subrip`, or `webvtt` text
streams; the explicit default stream is tried first, then stream index order,
with at most two attempts. FFmpeg receives fixed argv, the `file`-only protocol
whitelist, and converts the selected local stream to bounded UTF-8 WebVTT.
All subtitle stream attempts share one aggregate 10-second ceiling, further
bounded by the caller timeout and enclosing request abort signal, and each
attempt has a 256 KiB output cap. Malformed WebVTT, invalid UTF-8, unsupported
codecs, missing/empty tracks, and subtitle timeouts fail open to the already
extracted video frames; request abort still propagates and cleanup still runs.
Clean absence is cacheable, but a bounded decoder, process, or timeout failure is
classified as transient and the whole-video result is not cached, so a later identical
request retries embedded-text extraction instead of reusing a degraded result.
This embedded-caption capability is deliberately format-limited, not universal.
It is attempted only after the container has passed the Video runtime's exact
format allowlist: `3g2`, `3gp`, `avi`, `flac`, `flv`, `m4a`, `matroska`, `mj2`,
`mov`, `mp4`, `ogg`, or `webm`, and only when that allowed container also has a
playable video stream plus one of the three supported text-subtitle codecs.
Bitmap subtitle codecs, attachments, sidecars, speech transcription, provider
STT, and container-specific subtitle formats outside that set remain
unverified and are not claimed.
Client and embedded tracks accept at most 256 cues, 4 KiB of UTF-8 text per
cue, and 64 KiB of cue text per track. The final combined timeline retains at
most 256 cues and 64 KiB of canonical cue text. The inherited audio-fusion
seam is intentionally stricter: `audioTranscript` becomes a partial-invalid
audio branch above 128 observations or 32 KiB, without discarding the visual
result. Text is NFC-normalized; malformed Unicode (replacement characters or
unpaired surrogates) is rejected, C0/C1 controls are rejected or normalized,
and whitespace is collapsed. Timestamps are millisecond-quantized where
representable, then clamped to the raw probed duration; a valid sub-millisecond
cue that would collapse expands outward within that bound. Broker-derived
WebVTT endpoints are also clamped before this shared normalization. When a
focus window is present, every client, embedded, and audio track is filtered
for positive overlap and clamped to that window before reconciliation; scoped
embedded count/fingerprint metadata covers only the retained embedded cues.
Cross-source duplicates require both a canonical text match (NFKC,
case-insensitive, punctuation/symbol-insensitive, whitespace-collapsed) and a
positive time overlap. If that canonical identity is empty, as for symbol-only
cues, exact normalized text is used instead so distinct observations such as
music and bell symbols are not collapsed. Repeated text at disjoint times remains separate.
Duplicate priority is deterministic: `client` > `embedded` > `audio-bridge`;
the canonical cue retains every contributing source, the aggregate union
interval, and highest confidence, plus source-specific contribution intervals
and confidence values. Cue text is JSON-quoted and literal square brackets are
escaped as `\u005b`/`\u005d` inside the stable untrusted transcript delimiter,
so cue punctuation, quotes, or line breaks cannot create a literal delimiter.
This structural quoting does not make media text trusted or prevent semantic
prompt injection; the outer untrusted-media instruction remains authoritative.
An advanced caller may provide an explicit `audioTranscript` track for the
same video. Those cues must use the distinct caller-declared `audio-bridge`
lane; the request cannot relabel them as client or embedded text, and this lane
does not receive the server-derived trust assigned only to `embedded`. The fusion seam
runs visual and audio observations under one deadline and abort signal, reconciles
overlapping transcript duplicates with the policy above, and reports a partial
result when only one side succeeds. Preservation of the fused observation order
in the final rendered description remains follow-up work rather than a completed claim.
An invalid `audioTranscript` degrades to that partial result — the visual
description is kept and the audio branch records a sanitized failure code —
instead of failing the whole video. Per-branch availability, the partial flag,
and the sanitized failure codes are preserved in the described result, in the
guardrail metadata (`audioFusionRuns`/`audioFusionPartials`/
`audioFusionFailureCodes`), in the result-cache metadata, and in the bridge
fusion counters. The default Video Bridge path does not invoke speech-to-text
or download a second media copy; without that explicit track, it remains
video-only.
fusion counters. The default Video Bridge path does not invoke speech-to-text,
require a remote transcription provider, or download a second media copy.
Embedded text is derived only from the already materialized local video bytes.
The internal `/api/modality-bridge/video/drilldown` lifecycle is a separate,
loopback/token-authenticated cache substrate. Every operation also requires a
@@ -499,17 +554,61 @@ captions are cached. Cache entries retain the actual successful producer model,
including a fallback model; the bridge reports `mixed` when different frames
were produced by different models. A cache hit reuses that producer identity
instead of relabeling it as the requested routing plan. The whole-video result
cache is keyed on every input that changes the output — prompt, effective
model, sampling policy, frame count, semantic analysis mode, the SHA-256
fingerprint of the normalized focus hint, focus window, `transcript`,
`audioTranscript`, and the contact-sheet flag — so changing any of those
dimensions is a cache miss, never a stale reuse. The visual dedup policy
version, threshold, and bounded candidate-frame count are also explicit in the
result-cache key and metadata; a policy change therefore cannot reuse a stale
whole-video description. Result-cache v4 metadata keeps the mode and
fingerprint, never the raw user task. Guardrail metadata reports both the
requested and effective analysis modes; a requested `focused` mode without
usable user text is reported as effectively `full`.
cache is keyed on every input that changes the output — the protected
video-byte SHA-256, embedded-text extractor version, prompt, effective model,
sampling policy, frame count, semantic analysis mode, the SHA-256 fingerprint
of the normalized focus hint, focus window, `transcript`, `audioTranscript`,
and the contact-sheet flag — so changing the bytes or any of those dimensions
is a cache miss, never a stale reuse. The visual dedup policy version,
threshold, and bounded candidate-frame count are also explicit in the key and
metadata. Embedded cues add only their SHA-256 fingerprint and cue count to
cache metadata. Caller cue text contributes to the one-way SHA-256 cache-key
construction, while embedded cue identity follows from the protected
video-byte digest plus extractor version; raw cue text and the raw focused task
are not present in the final key string or metadata. Guardrail metadata reports
both requested and effective analysis modes; a requested `focused` mode
without usable user text is reported as effectively `full`. The in-memory
result-cache value is the already-produced bounded description and therefore
contains the text sent to the model.
Call-log copies omit structured `transcript`/`audioTranscript` fields before
lossy truncation. Server-side sensitivity is derived from a recognized structured
video carrier, a successful Video Bridge result, or a bounded detector overflow
treated as unknown-sensitive; delimiter-shaped caller prose alone never enables it.
A successful bridge rewrite carries only the exact
SHA-256 fingerprints plus bounded code-unit lengths of its generated transcript
descriptions as out-of-band trust metadata; the raw cue text is not present in
those identities. Log redaction verifies each exact generated segment, including
inside a provider string that concatenates translated text blocks, so adding a
real video carrier cannot make an adjacent delimiter-shaped caller string trusted
or omitted while the bounded scan stays within 128 description prefixes and 512
candidate hashes. Above either CPU-work cap, the retained copy fails closed to one
omission marker; the live request remains unchanged. Structured traversal uses a
separate security depth of 32 and an aggregate 10,000-entry budget, both above the
ordinary log-depth policy; crossing either bound also fails closed to one marker.
Sensitive requests also omit provider/client response and error bodies and
suppress detailed and active stream-chunk capture even when pipeline logging is
disabled. Retained stream-controller and transform callback diagnostics use the
same omission marker while the real error remains available to client handling and
fallback classification. A plain request that merely spells a Video-description or
transcript delimiter does not acquire those logging privileges. Because the
retained call artifact no longer contains replay-complete media context, Responses
`previous_response_id` lookup fails closed only when a trusted pipeline flag
records Video-transcript redaction; caller text that spells the public omission
marker cannot assert that provenance. The client must then resend full history.
Video Bridge request-
observation requests skip both request and response durable Memory fact
extraction, preventing a model echo from turning cue text into a stored fact.
The live provider response and semantic-cache value continue to follow the
operator's existing non-log retention policy.
FFmpeg receives the same private temporary `input.video`, which necessarily
contains any embedded subtitle bytes, but no separate subtitle file is
materialized: bounded WebVTT is consumed in memory from the FFmpeg child-process
stdout and is not emitted to application logs. The whole private temporary tree
is deleted in `finally` on success, timeout, failure, or abort. This is a
temporary processing boundary, not a claim that the original video bytes never
touch local disk.
The guardrail extracts every supported video part but describes no more than
`modalityBridgeVideoMaxVideos`. For a target proven to have

View File

@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 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 357 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
- **356 AI providers** with automatic format translation
- **357 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
- **356-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **357-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)

View File

@@ -1,5 +1,6 @@
import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts";
import { getRegistryEntry } from "../config/providerRegistry.ts";
import { resolveFetchStartTimeout } from "../utils/fetchStartTimeoutPolicy.ts";
import {
resolveAlternateFormat,
type AlternateFormat,
@@ -902,9 +903,24 @@ export class BaseExecutor {
clampNestedThinkingBudget(transformedBody, thinkingBudgetClampedMax);
}
// Timeout only covers response start; stream stalls are handled downstream.
// #11526: streaming requests cap the headers-wait phase to a client-realistic
// ceiling (see fetchStartTimeoutPolicy.ts) — non-streaming keeps the flat default.
// Declared outside the try/catch below so the catch's TIMEOUT log (on the
// error path) reports the same effective value the fetch actually used.
const fetchStartTimeoutPolicy = resolveFetchStartTimeout({
baseTimeoutMs: this.getTimeoutMs(),
stream,
});
const fetchStartTimeoutMs = fetchStartTimeoutPolicy.timeoutMs;
if (fetchStartTimeoutPolicy.capped) {
log?.debug?.(
"TIMEOUT",
`fetch-start timeout capped ${fetchStartTimeoutPolicy.baseTimeoutMs}ms -> ${fetchStartTimeoutMs}ms (streaming)`
);
}
try {
// Timeout only covers response start; stream stalls are handled downstream.
const fetchStartTimeoutMs = this.getTimeoutMs();
const fetchWithStartTimeout = async (requestUrl: string, requestOptions: RequestInit) => {
// GHSA-4f49: guard here (not only next to the first buildUrl) so retries
// and fallback URLs are validated too, before any bytes leave the host.
@@ -1713,7 +1729,7 @@ export class BaseExecutor {
// Distinguish timeout errors from other abort errors
const err = error instanceof Error ? error : new Error(String(error));
if (err.name === "TimeoutError") {
log?.warn?.("TIMEOUT", `Fetch timeout after ${this.getTimeoutMs()}ms on ${url}`);
log?.warn?.("TIMEOUT", `Fetch timeout after ${fetchStartTimeoutMs}ms on ${url}`);
}
lastError = err;
if (!skipUpstreamRetry && urlIndex + 1 < fallbackCount) {

View File

@@ -166,6 +166,7 @@ import {
runWithCasGuard,
} from "../services/tokenRefresh.ts";
import { createRequestLogger } from "../utils/requestLogger.ts";
import { redactVideoTranscriptSensitiveText } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts";
import { summarizeToolSources } from "../utils/toolSources.ts";
import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts";
@@ -524,6 +525,8 @@ export async function handleChatCore({
skipResourcePressureGuard = false,
reasoningTransportFallback = "drop",
managedLease = null,
videoTranscriptSensitive = false,
videoTranscriptDescriptionFingerprints = [],
}) {
let { provider, model, extendedContext } = modelInfo;
const resilienceSettings = resolveResilienceSettings(cachedSettings);
@@ -902,6 +905,8 @@ export async function handleChatCore({
stage: "registered",
correlationId,
sessionTag: conversationId || null,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
}) || generateRequestId();
// Initialize rate limit settings from persisted DB (once, lazy)
@@ -1034,6 +1039,8 @@ export async function handleChatCore({
noLogEnabled,
correlationId,
modelPinned,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
// Resolved conversationId (open-sse/services/conversationTracker.ts) wins when
// present — it's populated for every request now, not just ones where the
// client explicitly sent x-omniroute-session-id. The raw header remains a
@@ -1156,8 +1163,17 @@ export async function handleChatCore({
model,
provider: provider || undefined,
connectionId: connectionId || credentials?.connectionId || undefined,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
});
const pendingScope = { id: pendingRequestId, model, provider, connectionId: pendingConnId };
const pendingScope = {
id: pendingRequestId,
model,
provider,
connectionId: pendingConnId,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
};
const providerRequestCapture = createPreparedRequestLogger(reqLogger, pendingScope);
// 0. Log client raw request (before format conversion)
if (clientRawRequest) {
@@ -2949,6 +2965,8 @@ export async function handleChatCore({
let onPipelineStreamError: streamFailure.PipelineStreamErrorHandler | null = null;
let onClientDisconnectFinalize:
((event: { reason: string; duration: number }) => boolean) | null = null;
const redactStreamDiagnosticsForLog =
videoTranscriptSensitive || reqLogger.isVideoTranscriptSensitive();
// Create stream controller for disconnect detection
const streamController = createStreamController({
@@ -2980,6 +2998,7 @@ export async function handleChatCore({
clientAbortSignal: clientRawRequest?.signal,
allowCompletedToolHandoffGrace: isCodexResponsesEcho,
clientDisconnectGracePeriodMs: STREAM_DISCONNECT_GRACE_PERIOD_MS,
redactStreamDiagnosticsForLog,
});
const dedupRequestBody = { ...translatedBody, model: `${provider}/${model}`, stream };
@@ -3846,7 +3865,12 @@ export async function handleChatCore({
failureStatus,
upstreamErrorCode || (error instanceof Error && error.name ? error.name : "upstream_error")
);
console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`);
console.log(
`${COLORS.red}[ERROR] ${redactVideoTranscriptSensitiveText(
failureMessage,
videoTranscriptSensitive
)}${COLORS.reset}`
);
if (stream && upstreamErrorCode) {
const result = createStreamingErrorResult(
failureStatus,
@@ -4012,9 +4036,13 @@ export async function handleChatCore({
// executor throw). Don't swallow — the operator-visible signal "the user
// saw 401 even though auth was actually fixed" is much more confusing
// than the original 401 alone. Surface at error level with sanitization.
const retainedRetryError = redactVideoTranscriptSensitiveText(
sanitizeErrorMessage(retryErr),
videoTranscriptSensitive
);
log?.error?.(
"TOKEN",
`${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}`
`${provider?.toUpperCase()} | retry after refresh failed: ${retainedRetryError}`
);
}
} else {
@@ -4131,6 +4159,11 @@ export async function handleChatCore({
if (signatureRecovery.succeeded) break providerFailure;
const retainedProviderMessage = redactVideoTranscriptSensitiveText(
message,
videoTranscriptSensitive
);
// #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check
// sends `max_tokens: 1`): the model burns the whole budget on thinking, and
// some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty
@@ -4153,7 +4186,7 @@ export async function handleChatCore({
});
log?.warn?.(
"PROBE",
`Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"`
`Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${retainedProviderMessage}"`
);
break providerFailure;
}
@@ -4182,7 +4215,7 @@ export async function handleChatCore({
{
testStatus: "banned",
isActive: false,
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4213,7 +4246,7 @@ export async function handleChatCore({
) {
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4226,7 +4259,7 @@ export async function handleChatCore({
{
testStatus: "deactivated",
isActive: false,
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4250,7 +4283,7 @@ export async function handleChatCore({
errorConnectionId,
{
testStatus: "credits_exhausted",
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4298,7 +4331,7 @@ export async function handleChatCore({
rateLimitedUntil: kimiRateLimitResetAt,
backoffLevel: 0,
lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4328,7 +4361,7 @@ export async function handleChatCore({
errorConnectionId,
{
testStatus: "credits_exhausted",
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4344,14 +4377,14 @@ export async function handleChatCore({
// Normal 401 (token/session auth issue): keep account active for refresh/re-auth.
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
} else if (errorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN) {
// OAuth 401 with invalid credentials - token refresh can recover
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4361,7 +4394,7 @@ export async function handleChatCore({
// Cloud Code 403 with stale project: not a ban, keep account active.
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4377,7 +4410,7 @@ export async function handleChatCore({
const geoCooldownMs = COOLDOWN_MS.geoBlocked ?? 24 * 60 * 60 * 1000;
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
// T-PROBE: the 24h exclusion is a routing mutation — a probe must
@@ -4402,7 +4435,7 @@ export async function handleChatCore({
const byopCooldownMs = COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000;
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
try {
@@ -4448,7 +4481,13 @@ export async function handleChatCore({
}).catch(() => {});
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
const retainedErrMsg = formatProviderError(
new Error(retainedProviderMessage),
provider,
model,
statusCode
);
console.log(`${COLORS.red}[ERROR] ${retainedErrMsg}${COLORS.reset}`);
// Log Antigravity retry time if available
if (retryAfterMs && provider === "antigravity") {
@@ -4775,12 +4814,11 @@ export async function handleChatCore({
}
}
} catch (retryErr) {
log?.warn?.(
"RETRY",
`clinepass retry failed: ${
retryErr instanceof Error ? retryErr.message : String(retryErr)
}`
const retainedRetryError = redactVideoTranscriptSensitiveText(
retryErr instanceof Error ? retryErr.message : String(retryErr),
videoTranscriptSensitive
);
log?.warn?.("RETRY", `clinepass retry failed: ${retainedRetryError}`);
}
}
if (envError) {
@@ -5050,12 +5088,17 @@ export async function handleChatCore({
);
if (memoryOwnerId && memorySettings?.enabled && memorySettings.maxTokens > 0) {
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
const requestMemoryText = extractMemoryTextFromRequestBody(
body as Record<string, unknown>,
videoTranscriptSensitive
);
if (requestMemoryText) {
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
}
const memoryText = extractMemoryTextFromResponse(memoryExtractionResponse);
const memoryText = videoTranscriptSensitive
? ""
: extractMemoryTextFromResponse(memoryExtractionResponse);
if (memoryText) {
extractFacts(memoryText, memoryOwnerId, pipelineSessionId);
}
@@ -5393,6 +5436,7 @@ export async function handleChatCore({
provider,
model,
log,
redactUpstreamDiagnosticForLog: videoTranscriptSensitive,
});
if (streamReadiness.ok === false) {
const { response: failureResponse, reason } = streamReadiness;
@@ -5556,6 +5600,7 @@ export async function handleChatCore({
status: normalizedStreamStatus,
error: streamError,
errorCode: streamErrorCode,
videoTranscriptSensitive: videoTranscriptSensitive || reqLogger.isVideoTranscriptSensitive(),
});
// Track cache token metrics for streaming responses
@@ -5678,14 +5723,19 @@ export async function handleChatCore({
memorySettings.maxTokens > 0 &&
streamStatus === 200
) {
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
const requestMemoryText = extractMemoryTextFromRequestBody(
body as Record<string, unknown>,
videoTranscriptSensitive
);
if (requestMemoryText) {
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
}
const streamedMemoryText = extractMemoryTextFromResponse(
(streamResponseBody ?? null) as Record<string, unknown> | null
);
const streamedMemoryText = videoTranscriptSensitive
? ""
: extractMemoryTextFromResponse(
(streamResponseBody ?? null) as Record<string, unknown> | null
);
if (streamedMemoryText) {
extractFacts(streamedMemoryText, memoryOwnerId, pipelineSessionId);
}
@@ -5770,7 +5820,8 @@ export async function handleChatCore({
// openai-responses → openai translation still wants the namespace identity
// map for #7936-style round-trip closure when the client also speaks
// Responses (Codex CLI).
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
} else if (needsTranslation(targetFormat, clientResponseFormat)) {
// Standard translation for other providers
@@ -5800,7 +5851,8 @@ export async function handleChatCore({
clientResponseFormat,
}),
customToolNames,
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
} else {
log?.debug?.("STREAM", `Standard passthrough mode`);
@@ -5815,7 +5867,8 @@ export async function handleChatCore({
apiKeyInfo,
handleStreamFailure,
clientResponseFormat,
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
}
@@ -5827,6 +5880,7 @@ export async function handleChatCore({
clientRawRequestHeaders: clientRawRequest?.headers,
clientResponseFormat,
echoModel,
redactStreamDiagnosticsForLog,
responseHeaders,
});

View File

@@ -15,6 +15,10 @@ import { logAuditEvent } from "@/lib/compliance";
import { emit } from "@/lib/events/eventBus";
import type { RequestCompletedPayload, RequestFailedPayload } from "@/lib/events/types";
import { saveCallLog } from "@/lib/usageDb";
import {
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
VIDEO_TRANSCRIPT_REDACTION_PIPELINE_KEY,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { FORMATS } from "../../translator/formats.ts";
import { takeEarlyKeepaliveBytes } from "../../utils/earlyKeepaliveByteBuffer.ts";
import { cloneBoundedChatLogPayload, truncateForLog } from "./logTruncation.ts";
@@ -68,7 +72,13 @@ export type PersistAttemptLogsContext = {
model: string | null | undefined;
skillRequestId: string;
detailedLoggingEnabled: boolean;
reqLogger: { getPipelinePayloads?: () => Record<string, unknown> | undefined } | null | undefined;
reqLogger:
| {
getPipelinePayloads?: () => Record<string, unknown> | undefined;
isVideoTranscriptSensitive?: () => boolean;
}
| null
| undefined;
pendingRequestId: unknown;
clientRawRequest: { endpoint?: string } | null | undefined;
requestedModel: unknown;
@@ -89,12 +99,34 @@ export type PersistAttemptLogsContext = {
* explicitly present (never synthesized from skillRequestId) — persisted as call_logs.session_tag
* for per-session cost attribution. */
sessionTag?: string | null;
/** Trusted request state derived from the Video Bridge guardrail result. */
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of transcript descriptions generated by the guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
};
function toConnectionId(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function omitSensitivePipelineResponse(value: unknown): Record<string, unknown> {
const record =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
return {
...(typeof record.timestamp === "string" ? { timestamp: record.timestamp } : {}),
...(typeof record.status === "number" && Number.isFinite(record.status)
? { status: record.status }
: {}),
...(record.headers !== undefined ? { headers: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } : {}),
...(record.statusText !== undefined
? { statusText: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER }
: {}),
body: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
};
}
function buildAccountRotationMeta(
provider: string | null | undefined,
initialConnectionId: string | null,
@@ -204,6 +236,8 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
correlationId,
modelPinned,
sessionTag,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
} = ctx;
const initialConnectionId = toConnectionId(connectionId);
const finalConnectionId = toConnectionId(credentials?.connectionId) || initialConnectionId;
@@ -212,8 +246,21 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
initialConnectionId,
finalConnectionId
);
const transcriptSensitive =
videoTranscriptSensitive === true || reqLogger?.isVideoTranscriptSensitive?.() === true;
const descriptionLogContext = {
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints ?? [],
};
const providerWarnings = extractProviderWarnings(providerResponse, clientResponse, responseBody);
const detectedProviderWarnings = extractProviderWarnings(
providerResponse,
clientResponse,
responseBody
);
const providerWarnings =
transcriptSensitive && detectedProviderWarnings.length > 0
? [VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER]
: detectedProviderWarnings;
if (providerWarnings.length > 0) {
logAuditEvent({
action: "provider.warning",
@@ -227,6 +274,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
model,
connectionId: finalConnectionId,
httpStatus: status,
warningCount: detectedProviderWarnings.length,
warnings: providerWarnings,
},
});
@@ -273,8 +321,51 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
};
}
}
if (transcriptSensitive) {
pipelinePayloads[VIDEO_TRANSCRIPT_REDACTION_PIPELINE_KEY] = true;
for (const requestStage of [
"clientRawRequest",
"openaiRequest",
"providerRequest",
] as const) {
if (pipelinePayloads[requestStage] !== undefined) {
pipelinePayloads[requestStage] = cloneBoundedChatLogPayload(
pipelinePayloads[requestStage],
0,
requestStage !== "clientRawRequest" ? descriptionLogContext : {}
) as Record<string, unknown>;
}
}
if (pipelinePayloads.providerResponse !== undefined || providerResponse !== undefined) {
pipelinePayloads.providerResponse = omitSensitivePipelineResponse(
pipelinePayloads.providerResponse ?? providerResponse
);
}
if (pipelinePayloads.clientResponse !== undefined || clientResponse !== undefined) {
pipelinePayloads.clientResponse = omitSensitivePipelineResponse(
pipelinePayloads.clientResponse ?? clientResponse
);
}
if (pipelinePayloads.error !== undefined || error) {
const errorRecord =
pipelinePayloads.error && typeof pipelinePayloads.error === "object"
? pipelinePayloads.error
: {};
pipelinePayloads.error = {
...(typeof errorRecord.timestamp === "string"
? { timestamp: errorRecord.timestamp }
: {}),
message: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
};
}
delete pipelinePayloads.streamChunks;
}
}
const responseBodyForLog = transcriptSensitive
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: responseBody;
saveCallLog({
id: pendingRequestId,
method: "POST",
@@ -290,10 +381,12 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
attachLogMeta(truncateForLog(body as Record<string, unknown>), {
...accountRotationMeta,
claudePromptCache: claudeCacheMeta,
})
}),
0,
descriptionLogContext
),
responseBody: cloneBoundedChatLogPayload(
attachLogMeta(truncateForLog(responseBody as Record<string, unknown>), {
attachLogMeta(truncateForLog(responseBodyForLog as Record<string, unknown>), {
...accountRotationMeta,
claudePromptCache: claudeCacheMeta
? {
@@ -305,7 +398,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
claudePromptCacheUsage: claudeCacheUsageMeta,
})
),
error: error || null,
error: transcriptSensitive && error ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : error || null,
sourceFormat,
targetFormat,
comboName,
@@ -332,7 +425,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
const lifecycle = resolveRequestLifecycleEvent({
traceId,
status,
error,
error: transcriptSensitive && error ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : error,
model,
provider,
comboName,

View File

@@ -105,7 +105,11 @@ export async function resolveExecutorWithProxy(
"UPSTREAM_PROXY",
`${prov} routed through CLIProxyAPI (per-connection claude-native override)`
);
return getExecutor("cliproxyapi");
const [cfg, { dedicatedApiKey }] = await Promise.all([
getUpstreamProxyConfigCached(prov),
loadCliproxyapiSettings(),
]);
return resolveCliproxyapiExecutor(cfg.cliproxyapiModelMapping, dedicatedApiKey);
}
// Sibling per-connection override for Dario (#dario). Checked AFTER the

View File

@@ -5,6 +5,10 @@ import {
getChatLogMaxObjectKeys,
getChatLogMaxBodyBytes,
} from "@/lib/logEnv";
import {
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { estimateSizeFast } from "../../utils/estimateSize.ts";
export const MEMORY_EXTRACTION_TEXT_LIMIT = 64 * 1024;
@@ -22,7 +26,7 @@ export function truncateChatLogText(value: string): string {
return `${head}\n[...truncated ${value.length - limit} chars...]\n${tail}`;
}
export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
function cloneBoundedChatLogPayloadValue(value: unknown, depth = 0): unknown {
if (value === null || value === undefined) return value;
if (typeof value === "string") return truncateChatLogText(value);
if (typeof value !== "object") return value;
@@ -32,7 +36,7 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
if (Array.isArray(value)) {
const retained = value.length > maxTailItems ? value.slice(-maxTailItems) : value;
const cloned = retained.map((item) => cloneBoundedChatLogPayload(item, depth + 1));
const cloned = retained.map((item) => cloneBoundedChatLogPayloadValue(item, depth + 1));
if (value.length > maxTailItems) {
return [
{
@@ -47,10 +51,11 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
}
const result: Record<string, unknown> = {};
const entries = Object.entries(value as Record<string, unknown>);
const record = value as Record<string, unknown>;
const entries = Object.entries(record);
const maxKeys = getChatLogMaxObjectKeys();
for (const [key, item] of maxKeys > 0 ? entries.slice(0, maxKeys) : entries) {
result[key] = cloneBoundedChatLogPayload(item, depth + 1);
result[key] = cloneBoundedChatLogPayloadValue(item, depth + 1);
}
if (maxKeys > 0 && entries.length > maxKeys) {
result._omniroute_truncated_keys = entries.length - maxKeys;
@@ -58,6 +63,16 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
return result;
}
export function cloneBoundedChatLogPayload(
value: unknown,
depth = 0,
descriptionContext: VideoTranscriptLogContext = {}
): unknown {
const transcriptSafeValue =
depth === 0 ? omitVideoTranscriptForLog(value, descriptionContext) : value;
return cloneBoundedChatLogPayloadValue(transcriptSafeValue, depth);
}
/**
* Truncate a large object for logging. If its JSON representation exceeds
* getChatLogMaxBodyBytes() (default 1MB; CHAT_LOG_MAX_BODY_KB env override),

View File

@@ -1,5 +1,10 @@
import { capMemoryExtractionText, MEMORY_EXTRACTION_TEXT_LIMIT } from "./logTruncation.ts";
function normalizeMemoryInputText(value: unknown): string {
if (typeof value !== "string") return "";
return value.trim();
}
export function extractMemoryTextFromResponse(
response: Record<string, unknown> | null | undefined
): string {
@@ -29,8 +34,13 @@ export function extractMemoryTextFromResponse(
}
export function extractMemoryTextFromRequestBody(
body: Record<string, unknown> | null | undefined
body: Record<string, unknown> | null | undefined,
videoTranscriptSensitive = false
): string {
// This bit is derived from the guardrail result. Caller-shaped lookalike text
// cannot suppress Memory extraction, while real media-derived cues cannot
// become durable facts (including through an adjacent response echo).
if (videoTranscriptSensitive) return "";
if (!body || typeof body !== "object") return "";
const messages = Array.isArray(body.messages) ? body.messages : null;
@@ -39,16 +49,16 @@ export function extractMemoryTextFromRequestBody(
const msg = messages[i] as Record<string, unknown>;
if (msg?.role !== "user") continue;
if (typeof msg.content === "string" && msg.content.trim().length > 0) {
return capMemoryExtractionText(msg.content.trim());
const messageText = normalizeMemoryInputText(msg.content);
if (messageText) {
return capMemoryExtractionText(messageText);
}
if (Array.isArray(msg.content)) {
const text = msg.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
if (typeof part?.text === "string") return normalizeMemoryInputText(part.text);
if (part?.type === "input_text") return normalizeMemoryInputText(part.text);
return "";
})
.filter(Boolean)
@@ -68,15 +78,15 @@ export function extractMemoryTextFromRequestBody(
if (role && role !== "user") continue;
if (itemType && itemType !== "message") continue;
if (typeof item?.content === "string" && item.content.trim()) {
return capMemoryExtractionText(item.content.trim());
const itemText = normalizeMemoryInputText(item?.content);
if (itemText) {
return capMemoryExtractionText(itemText);
}
if (Array.isArray(item?.content)) {
const text = item.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
if (typeof part?.text === "string") return normalizeMemoryInputText(part.text);
if (part?.type === "input_text") return normalizeMemoryInputText(part.text);
return "";
})
.filter(Boolean)
@@ -96,13 +106,12 @@ export function extractMemoryTextFromRequestBody(
if (role && role !== "user") return "";
if (itemType && itemType !== "message") return "";
if (typeof item?.content === "string") return item.content.trim();
if (typeof item?.content === "string") return normalizeMemoryInputText(item.content);
if (Array.isArray(item?.content)) {
return item.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
if (typeof part?.text === "string") return normalizeMemoryInputText(part.text);
if (part?.type === "input_text") return normalizeMemoryInputText(part.text);
return "";
})
.filter(Boolean)

View File

@@ -68,6 +68,7 @@ export function assembleStreamingPipeline(
clientRawRequestHeaders: HeadersLike;
clientResponseFormat: Parameters<typeof defaultShape>[0];
echoModel: string | null | undefined;
redactStreamDiagnosticsForLog?: boolean;
responseHeaders: Record<string, string>;
},
deps: StreamingPipelineDeps = DEFAULT_DEPS
@@ -83,7 +84,8 @@ export function assembleStreamingPipeline(
let piiStream = deps.pipeWithDisconnect(
args.providerResponse,
args.transformStream,
args.streamController
args.streamController,
{ redactStreamDiagnosticsForLog: args.redactStreamDiagnosticsForLog }
);
if (typeof args.createPiiTransform === "function") {
piiStream = piiStream.pipeThrough((args.createPiiTransform as () => TransformStream)());

View File

@@ -92,19 +92,65 @@ interface KieImageOptions {
}
// KIE Market catalog ids are namespaced for OmniRoute's catalog
// (`google-imagen/<model>`), but the KIE Market createTask API expects
// (`<vendor>/<model>`), but the KIE Market createTask API expects
// vendor-specific upstream ids that do not follow a single consistent
// pattern (confirmed against docs.kie.ai/market/google/* — see #11225,
// #11296): nano-banana-2 and nano-banana-pro drop the vendor namespace
// entirely, while nano-banana and nano-banana-edit use a `google/` prefix
// instead of `google-imagen/`. Every other KIE Market namespace (seedream,
// flux, ideogram, qwen, wan, grok-imagine, gpt) already matches its real
// upstream id byte-for-byte, so this map stays scoped to google-imagen.
// pattern. Every entry below was confirmed individually against the literal
// example request JSON published on docs.kie.ai (never inferred by pattern —
// see #11326's false "everything else already matches" claim and #11296's
// follow-up correction):
// - google-imagen: nano-banana-2 and nano-banana-pro drop the vendor
// namespace entirely; nano-banana and nano-banana-edit use a `google/`
// prefix instead of `google-imagen/` (docs.kie.ai/market/google/*).
// - gpt: gpt-image-2-* drops the `gpt/` namespace entirely
// (docs.kie.ai/market/gpt/gpt-image-2-*); gpt-image-1.5-* uses a
// `gpt-image/` namespace instead of `gpt/gpt-image-1.5-`, and keeps the
// dot in "1.5" (docs.kie.ai/market/gpt-image/1-5-*).
// - seedream: 5.0-lite-* drops the ".0" — real id is `5-lite-*`
// (docs.kie.ai/market/seedream/5-lite-text-to-image); seedream 4.5 (T2I
// and edit) already matches byte-for-byte.
// - flux: `flux/2-*` uses a `flux-2/` namespace (dash, not slash); the
// generic (non-"pro") variant is named `flex` upstream, not `2`
// (docs.kie.ai/market/flux2/pro-*, .../flex-*).
// - wan: `wan/2.7-*` keeps the dot in our catalog, but KIE's documented
// enum uses a dash — real id is `wan/2-7-*`
// (docs.kie.ai/market/wan/2-7-image[-pro]).
// - ideogram (v3-text-to-image, v3-edit, v3-remix), qwen, qwen2, and
// grok-imagine already match byte-for-byte
// (docs.kie.ai/market/{ideogram,qwen,qwen2,grok-imagine}/*).
// ideogram/v3-reframe has no dedicated docs.kie.ai page as of this sweep
// (its 3 siblings above are all direct id matches, so it is assumed
// correct by pattern, not independently confirmed).
// Two catalog entries remain UNRESOLVED after this sweep and are
// deliberately left untouched pending a follow-up (see #11296 discussion):
// - z-image/4.0-text-to-image and z-image/4.5-text-to-image: the only
// documented Z-Image Market page (docs.kie.ai/market/z-image/z-image)
// shows a single fixed `model` enum value `"z-image"` with no
// version-specific id or "version" input field found — unclear whether
// both catalog ids should collapse to the same upstream call.
// - flux/kontext: no `docs.kie.ai/market/flux2/kontext` (or similar)
// Market page exists; Flux Kontext is documented under the separate
// `/flux-kontext-api/*` docs tree with its own endpoint
// (`POST /api/v1/flux/kontext/generate`, models `flux-kontext-pro`/
// `flux-kontext-max`), not the Market `createTask` flow this map feeds.
// This entry may be miscatalogued as `isMarket: true` and need a
// dedicated reroute rather than an id rewrite.
export const KIE_MARKET_UPSTREAM_MODEL_IDS: ReadonlyMap<string, string> = new Map([
["google-imagen/nano-banana", "google/nano-banana"],
["google-imagen/nano-banana-2", "nano-banana-2"],
["google-imagen/nano-banana-pro", "nano-banana-pro"],
["google-imagen/nano-banana-edit", "google/nano-banana-edit"],
["gpt/gpt-image-2-text-to-image", "gpt-image-2-text-to-image"],
["gpt/gpt-image-2-image-to-image", "gpt-image-2-image-to-image"],
["gpt/gpt-image-1.5-text-to-image", "gpt-image/1.5-text-to-image"],
["gpt/gpt-image-1.5-image-to-image", "gpt-image/1.5-image-to-image"],
["seedream/5.0-lite-text-to-image", "seedream/5-lite-text-to-image"],
["seedream/5.0-lite-image-to-image", "seedream/5-lite-image-to-image"],
["flux/2-pro-text-to-image", "flux-2/pro-text-to-image"],
["flux/2-pro-image-to-image", "flux-2/pro-image-to-image"],
["flux/2-text-to-image", "flux-2/flex-text-to-image"],
["flux/2-image-to-image", "flux-2/flex-image-to-image"],
["wan/2.7-image", "wan/2-7-image"],
["wan/2.7-image-pro", "wan/2-7-image-pro"],
]);
export function resolveKieMarketUpstreamModelId(publicModelId: string): string {

View File

@@ -3286,7 +3286,24 @@ async function handleRoundRobinCombo({
"COMBO-RR",
`Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.`
);
return errorResponse(503, "Maximum combo retry limit reached");
return errorResponseWithComboDiagnostics(
503,
"Maximum combo retry limit reached",
{
poolSize: modelCount,
attempted: globalAttempts,
excluded: [
...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })),
...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))),
],
attemptOrder: rrOutcomes.map((o) => ({
provider: o.model.split("/")[0] || "unknown",
model: o.model,
})),
terminalReason: "max_attempts_exceeded",
recovery: buildRecoveryHint("max_attempts_exceeded"),
}
);
}
if (retry > 0) {
log.info(

View File

@@ -5,7 +5,8 @@
* @changes
* - [2026-07-24] [Composer] - Skip execute-mode units at concurrency cap before dispatch
*/
import { errorResponse } from "../../utils/error.ts";
import { errorResponse, errorResponseWithComboDiagnostics } from "../../utils/error.ts";
import type { ComboDiagnostics } from "../../utils/error.ts";
import { recordComboRequest } from "../comboMetrics.ts";
import { resolveDelayMs } from "./comboPredicates.ts";
import { isRuntimeUnitAtConcurrencyCap } from "./runtimeUnitCapacity.ts";
@@ -216,6 +217,17 @@ export async function executeRuntimeUnitCombo(args: {
};
const finalFailure = (response: Response): Response =>
withQuotaExhaustionClassification(response, observedFailure ? allObservedFailuresQuota : null);
// #11462: attempts already made this loop, tracked for the attempt-budget-exceeded
// diagnostics trace below (mirrors the poolSize/attemptOrder shape combo.ts already
// attaches for the priority/round-robin strategies).
const attemptedUnits: Array<{ provider: string; model: string }> = [];
const buildAttemptBudgetDiag = (): ComboDiagnostics => ({
poolSize: orderedUnits.length,
attempted: args.nesting.attemptBudget.count,
excluded: [],
attemptOrder: attemptedUnits,
terminalReason: "max_attempts_exceeded",
});
for (const unit of orderedUnits) {
const protectedPriorityUnit =
@@ -247,13 +259,21 @@ export async function executeRuntimeUnitCombo(args: {
}
args.nesting.attemptBudget.count += 1;
if (args.nesting.attemptBudget.count > args.nesting.attemptBudget.limit) {
lastResponse = errorResponse(503, "Maximum combo retry limit reached");
lastResponse = errorResponseWithComboDiagnostics(
503,
"Maximum combo retry limit reached",
buildAttemptBudgetDiag()
);
await observeFailure(lastResponse, unit);
return { response: finalFailure(lastResponse), unit };
}
if (retry > 0) {
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
attemptedUnits.push({
provider: unit.kind === "model" ? unit.provider : "combo-ref",
model: unitDisplayName(unit),
});
args.log.info(
"COMBO",
`Trying ${unit.kind} ${unitDisplayName(unit)}${retry > 0 ? ` (retry ${retry})` : ""}`

View File

@@ -4,6 +4,7 @@ import type { CompressionConfig, CompressionMode, CompressionResult } from "./ty
export const MEMO_CAP = 5_000;
const memoMap = new Map<string, CompressionResult>();
let lookupCountForTests = 0;
// Opt-IN whitelist (NOT opt-out): cache only engines proven pure + STATELESS across
// requests. Excluded on purpose: `ccr` and `session-dedup` write to the cross-request
@@ -94,6 +95,7 @@ function boundedSet(key: string, value: CompressionResult): void {
}
export function memoLookup(key: string): CompressionResult | null {
lookupCountForTests++;
const hit = memoMap.get(key);
if (!hit) return null;
// Return a clone so downstream mutation cannot corrupt the cached value.
@@ -110,4 +112,10 @@ export function memoStore(key: string, result: CompressionResult): void {
/** For tests only — clears the in-process memo store. */
export function clearMemoStore(): void {
memoMap.clear();
lookupCountForTests = 0;
}
export const resultMemoForTests = {
get lookupCount(): number {
return lookupCountForTests;
},
};

View File

@@ -332,7 +332,7 @@ function runCompression(
config: { ...options.config, memoizeCompressionResults: false },
});
memoStore(key, result);
return memoLookup(key)!;
return result;
}
if (mode === "rtk") {
return applyRtkCompression(body, {
@@ -565,7 +565,7 @@ async function runCompressionAsync(
config: { ...options.config, memoizeCompressionResults: false },
});
memoStore(key, result);
return memoLookup(key)!;
return result;
}
// Single-mode omniglyph (async-only) — resolution lives in engines/omniglyphSingleMode.ts.
if (mode === "omniglyph") return applyOmniglyphSingleMode(body, options);

View File

@@ -141,16 +141,8 @@ function extractSystemContent(body: Record<string, unknown>): unknown {
*
* It is a PLAINTEXT prefix rather than digest input, matching
* `semanticCache.generateSignature` (#3740): the id is an internal namespace
* key, not a credential, and a namespace you can read off the key is worth more
* than one you cannot when debugging a dedup collision.
*
* It does NOT dodge the CodeQL js/insufficient-password-hash false positive,
* which the #3740 comment claims for its own version and which this comment
* claimed too until alert #874 was raised on the `createHash` below anyway.
* Once an API-key-derived value reaches this file at all, the query flags the
* sibling digest regardless of what actually goes into it. Dismissed per HR#14;
* expect it to come back on any edit here, and do not "fix" it with a KDF —
* that would break the determinism dedup depends on.
* key, not a credential, and keeping it out of the digest avoids the
* false-positive CodeQL js/insufficient-password-hash on a cache/dedup key.
*
* Omitting `tenantId` keeps the un-namespaced hash. Keyless local-first
* deployments have no tenant boundary to preserve, and every such install would

View File

@@ -136,7 +136,6 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
const omittedToolCallIds = new Set<string>();
for (const msg of body.messages) {
const parts = [];
let shouldUseEmbeddedSignature = true;
if (Array.isArray(msg.content)) {
for (const block of msg.content) {
@@ -160,15 +159,15 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
break;
}
const embeddedThoughtSignature = shouldUseEmbeddedSignature
? signatureForToolCall
: undefined;
if (embeddedThoughtSignature) {
shouldUseEmbeddedSignature = false;
}
// #11510: each functionCall part carries its OWN resolved
// thoughtSignature — a parallel (multi tool_use) turn can have a
// real, individually-valid signature per tool call, and Gemini
// 3.x rejects the request if any functionCall in the turn is
// missing one. Previously only the first functionCall of the
// message kept its signature; this dropped valid signatures for
// every subsequent parallel tool call in the same turn.
parts.push({
...(embeddedThoughtSignature ? { thoughtSignature: embeddedThoughtSignature } : {}),
...(signatureForToolCall ? { thoughtSignature: signatureForToolCall } : {}),
functionCall: {
...(stripFunctionCallId ? {} : { id: block.id }),
name: sanitizeToolName(block.name),

View File

@@ -383,7 +383,6 @@ function openaiToGeminiBase(
if (toolCalls && Array.isArray(toolCalls)) {
const toolCallIds: string[] = [];
const resolvedSignatures = new Map<string, string>();
let firstPersistedSignature: string | undefined;
for (const tc of toolCalls) {
const id = tc.id as string;
const resolved = resolveGeminiThoughtSignature(
@@ -392,11 +391,9 @@ function openaiToGeminiBase(
);
if (typeof resolved === "string" && resolved.length > 0) {
resolvedSignatures.set(id, resolved);
firstPersistedSignature ??= resolved;
}
}
let shouldUseEmbeddedSignature = !parts.some((p) => p.thoughtSignature);
const signaturelessToolCallMode = toolNameOptions.signaturelessToolCallMode;
const stringifySignaturelessToolCalls = signaturelessToolCallMode === "text";
const contextualizeSignaturelessToolResponses =
@@ -433,13 +430,14 @@ function openaiToGeminiBase(
}
const args = tryParseJSON(fn.arguments || "{}");
const embeddedThoughtSignature = shouldUseEmbeddedSignature
? firstPersistedSignature || signatureForToolCall
: undefined;
if (embeddedThoughtSignature) {
shouldUseEmbeddedSignature = false;
}
// #11510: each functionCall part carries its OWN resolved
// thoughtSignature — a parallel (multi tool_calls) turn can have a
// real, individually-valid signature per tool call, and Gemini 3.x
// rejects the request if any functionCall in the turn is missing
// one. Previously only the first functionCall of the message kept
// its signature; this dropped valid signatures for every
// subsequent parallel tool call in the same turn.
const embeddedThoughtSignature = signatureForToolCall;
// Gemini expects the signature on the functionCall part itself.
// If we are in a mode where missing signatures cause 400s (and we couldn't find one),

View File

@@ -0,0 +1,52 @@
// #11526: the fetch-start (headers-wait) phase had no ceiling comparable to a
// real client's patience for STREAMING requests — it inherited the flat,
// non-adaptive FETCH_TIMEOUT_MS (default 600_000ms / 10 minutes), five times
// longer than Codex's own ~120s hard client-abort window. When an upstream
// never returns a response at all (not even headers), OmniRoute kept the
// connection open with nothing but keepalives, guaranteeing the client gave
// up first with an opaque 499 instead of OmniRoute detecting the stall and
// failing fast/over within a client-realistic window.
//
// This mirrors the adaptive philosophy of streamReadinessPolicy.ts's
// resolveStreamReadinessTimeout (which already protects the BODY phase, after
// headers arrive) but inverted: instead of bumping a small base timeout up for
// heavy payloads, it caps an oversized base timeout down for the HEADERS
// phase of streaming requests specifically. Non-streaming requests are left
// on the existing flat default — providers that are legitimately slow to
// accept a connection (but not streaming SSE) are unaffected.
export type FetchStartTimeoutPolicyInput = {
baseTimeoutMs: number;
/** Only streaming requests are capped — non-streaming keeps the flat default. */
stream?: boolean | null;
capMs?: number;
};
export type FetchStartTimeoutPolicyResult = {
timeoutMs: number;
baseTimeoutMs: number;
/** True when the base timeout was reduced by the streaming cap. */
capped: boolean;
};
// Codex's documented hard client-abort window for a stalled turn (nothing but
// keepalives in flight) is ~120s. Keep the cap safely under that so OmniRoute's
// own headers-phase watchdog always fires before the client gives up on its own.
export const CODEX_CLIENT_ABORT_MS = 120_000;
export const DEFAULT_FETCH_START_TIMEOUT_CAP_MS = 110_000;
export function resolveFetchStartTimeout(
input: FetchStartTimeoutPolicyInput
): FetchStartTimeoutPolicyResult {
const baseTimeoutMs = Math.max(0, Math.floor(input.baseTimeoutMs || 0));
if (baseTimeoutMs <= 0 || !input.stream) {
return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false };
}
const capMs = Math.max(0, Math.floor(input.capMs ?? DEFAULT_FETCH_START_TIMEOUT_CAP_MS));
if (capMs <= 0 || baseTimeoutMs <= capMs) {
return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false };
}
return { timeoutMs: capMs, baseTimeoutMs, capped: true };
}

View File

@@ -1,5 +1,11 @@
import { getPendingById } from "@/lib/usage/usageHistory";
import { getChatLogMaxDepth, getChatLogArrayTailItems } from "@/lib/logEnv";
import {
containsVideoTranscriptForLog,
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { sanitizeErrorMessage } from "./error.ts";
type JsonRecord = Record<string, unknown>;
@@ -12,6 +18,7 @@ type HeaderInput =
| undefined;
export type RequestPipelinePayloads = {
_omnirouteVideoTranscriptRedacted?: true;
routeDecision?: JsonRecord;
clientRawRequest?: JsonRecord;
openaiRequest?: JsonRecord;
@@ -44,6 +51,7 @@ type RequestLogger = {
appendConvertedChunk: (chunk: string) => void;
logError: (error: unknown, requestBody?: unknown) => void;
getPipelinePayloads: () => RequestPipelinePayloads | null;
isVideoTranscriptSensitive: () => boolean;
};
type RequestLoggerOptions = {
@@ -55,6 +63,13 @@ type RequestLoggerOptions = {
model?: string;
provider?: string;
connectionId?: string | null;
/**
* Server-derived from a recognized video carrier, successful bridge result, or bounded
* unknown-sensitive detector overflow; never from delimiter-shaped prose alone.
*/
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of transcript descriptions generated by the guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
};
const DEFAULT_MAX_STREAM_CHUNK_BYTES = 128 * 1024;
@@ -159,7 +174,7 @@ function truncateLogString(value: string, maxLength = MAX_LOG_STRING_LENGTH): st
* recursing into an object's values, enabling the per-field exemption above.
* Top-level arrays (no key context) remain subject to truncation.
*/
export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null = null): unknown {
function cloneBoundedForLogValue(value: unknown, depth = 0, key: string | null = null): unknown {
if (value === null || value === undefined) return value;
if (typeof value === "string") return truncateLogString(value);
if (typeof value !== "object") return value;
@@ -178,12 +193,15 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
// item and rewrite originalLength with the truncated length (25 instead of the true 800), so
// the log would misreport how much was cut. Keep the original marker, re-bound only the tail.
if (isTruncatedArrayMarker(value[0])) {
return [value[0], ...value.slice(1).map((item) => cloneBoundedForLog(item, depth + 1))];
return [
value[0],
...value.slice(1).map((item) => cloneBoundedForLogValue(item, depth + 1, null)),
];
}
const exempt = key === "tools";
const shouldTruncate = !exempt && value.length > MAX_LOG_ARRAY_ITEMS;
const source = shouldTruncate ? value.slice(-MAX_LOG_ARRAY_ITEMS) : value;
const mapped = source.map((item) => cloneBoundedForLog(item, depth + 1));
const mapped = source.map((item) => cloneBoundedForLogValue(item, depth + 1, null));
if (shouldTruncate) {
return [
{
@@ -206,7 +224,7 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
([k]) => !(carried > 0 && k === TRUNCATED_KEYS_MARKER)
);
for (const [k, item] of entries.slice(0, MAX_LOG_OBJECT_KEYS)) {
result[k] = cloneBoundedForLog(item, depth + 1, k);
result[k] = cloneBoundedForLogValue(item, depth + 1, k);
}
const dropped = Math.max(0, entries.length - MAX_LOG_OBJECT_KEYS) + carried;
if (dropped > 0) {
@@ -215,6 +233,17 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
return result;
}
export function cloneBoundedForLog(
value: unknown,
depth = 0,
key: string | null = null,
descriptionContext: VideoTranscriptLogContext = {}
): unknown {
const transcriptSafeValue =
depth === 0 ? omitVideoTranscriptForLog(value, descriptionContext) : value;
return cloneBoundedForLogValue(transcriptSafeValue, depth, key);
}
function appendBoundedChunk(
chunks: string[],
bytes: { value: number; truncated: boolean },
@@ -277,7 +306,7 @@ function compactPipelinePayloads(
continue;
}
result[key as keyof RequestPipelinePayloads] = value;
(result as Record<string, unknown>)[key] = value;
}
return hasOwnValues(result) ? result : null;
@@ -298,6 +327,7 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
? Number(options.maxStreamChunkItems)
: DEFAULT_MAX_STREAM_CHUNK_ITEMS;
let pendingPushed = false;
let videoTranscriptSensitive = options.videoTranscriptSensitive === true;
const push = () => {
if (pendingPushed) return;
@@ -330,7 +360,7 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
};
const append = (arr: string[], bytes: { value: number; truncated: boolean }, chunk: string) => {
if (!captureChunks) return;
if (!captureChunks || videoTranscriptSensitive) return;
push();
const ts = new Date().toISOString().slice(11, 23);
appendBoundedChunk(arr, bytes, `[${ts}] ${chunk}`, maxBytes, maxItems);
@@ -348,6 +378,17 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
appendConvertedChunk(chunk: string) {
append(streamChunks.client, streamChunkBytes.client, chunk);
},
suppressVideoTranscriptChunks() {
videoTranscriptSensitive = true;
for (const chunks of Object.values(streamChunks)) chunks.splice(0, chunks.length);
for (const state of Object.values(streamChunkBytes)) {
state.value = 0;
state.truncated = false;
}
},
isVideoTranscriptSensitive() {
return videoTranscriptSensitive;
},
};
}
@@ -362,26 +403,58 @@ export async function createRequestLogger(
// so that active requests always have real-time stream data available via
// the /api/logs/active endpoint.
const chunkMethods = makeStreamChunkMethods(options, captureStreamChunks);
const descriptionLogContext: VideoTranscriptLogContext = {
trustedDescriptionFingerprints: options.videoTranscriptDescriptionFingerprints ?? [],
};
const suppressTranscriptChunksIfNeeded = (
descriptionContext: VideoTranscriptLogContext,
...values: unknown[]
): void => {
if (values.some((value) => containsVideoTranscriptForLog(value, descriptionContext))) {
chunkMethods.suppressVideoTranscriptChunks();
}
};
const cloneRequestBody = (
value: unknown,
descriptionContext: VideoTranscriptLogContext
): unknown => cloneBoundedForLog(value, 0, null, descriptionContext);
const cloneResponseBody = (value: unknown): unknown =>
chunkMethods.isVideoTranscriptSensitive()
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: cloneBoundedForLog(value);
if (options.enabled === false) {
let routeDecision: JsonRecord | null = null;
return {
sessionPath: null,
logClientRawRequest() {},
logClientRawRequest(_endpoint, body) {
suppressTranscriptChunksIfNeeded({}, body);
},
logRouteDecision(decision) {
routeDecision = cloneBoundedForLog(decision) as JsonRecord;
},
logOpenAIRequest() {},
logTargetRequest() {},
logProviderResponse() {},
logOpenAIRequest(body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
},
logTargetRequest(_url, _headers, body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
},
logProviderResponse(_status, _statusText, _headers, body) {
void body;
},
appendProviderChunk: chunkMethods.appendProviderChunk,
appendOpenAIChunk: chunkMethods.appendOpenAIChunk,
logConvertedResponse() {},
logConvertedResponse(body) {
void body;
},
appendConvertedChunk: chunkMethods.appendConvertedChunk,
logError() {},
logError(_error, requestBody) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, requestBody);
},
getPipelinePayloads() {
return routeDecision ? { routeDecision } : null;
},
isVideoTranscriptSensitive: chunkMethods.isVideoTranscriptSensitive,
};
}
@@ -393,11 +466,12 @@ export async function createRequestLogger(
sessionPath: null,
logClientRawRequest(endpoint, body, headers = {}) {
suppressTranscriptChunksIfNeeded({}, body);
payloads.clientRawRequest = {
timestamp: new Date().toISOString(),
endpoint,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, {}),
};
},
@@ -406,18 +480,20 @@ export async function createRequestLogger(
},
logOpenAIRequest(body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
payloads.openaiRequest = {
timestamp: new Date().toISOString(),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, descriptionLogContext),
};
},
logTargetRequest(url, headers, body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
payloads.providerRequest = {
timestamp: new Date().toISOString(),
url,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, descriptionLogContext),
};
},
@@ -427,7 +503,7 @@ export async function createRequestLogger(
status,
statusText,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneResponseBody(body),
};
},
@@ -436,21 +512,25 @@ export async function createRequestLogger(
logConvertedResponse(body) {
payloads.clientResponse = {
timestamp: new Date().toISOString(),
body: cloneBoundedForLog(body),
body: cloneResponseBody(body),
};
},
appendConvertedChunk: chunkMethods.appendConvertedChunk,
logError(error, requestBody = null) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, requestBody);
payloads.error = {
timestamp: new Date().toISOString(),
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
requestBody: cloneBoundedForLog(requestBody),
error: chunkMethods.isVideoTranscriptSensitive()
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
requestBody: cloneRequestBody(requestBody, descriptionLogContext),
};
},
getPipelinePayloads() {
return compactPipelinePayloads(payloads);
},
isVideoTranscriptSensitive: chunkMethods.isVideoTranscriptSensitive,
};
}

View File

@@ -1,6 +1,7 @@
import { VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb";
import { translateResponse, initState } from "../translator/index.ts";
import { FORMATS } from "../translator/formats.ts";
import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb";
import {
extractUsage,
hasValidUsage,
@@ -177,6 +178,8 @@ type StreamOptions = {
* codex-compatible `namespace` + `name` fields.
*/
requestToolIdentityMap?: Map<string, { namespace: string; name: string }> | null;
/** Omit request-sensitive transcript text from retained stream diagnostics only. */
redactStreamDiagnosticsForLog?: boolean;
};
type TranslateState = ReturnType<typeof initState> & {
@@ -654,7 +657,10 @@ export function createSSEStream(options: StreamOptions = {}) {
dropResponsesCommentary,
customToolNames = new Set<string>(),
requestToolIdentityMap = null,
redactStreamDiagnosticsForLog = false,
} = options;
const retainDiagnosticForLog = (value: unknown): unknown =>
redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
const signatureNamespace = connectionId;
// Request-body-size metric (for monitoring payload size distribution & correlation with TTFT).
// The size is JSON-serialised byte count; stored as a performance mark detail so monitoring
@@ -1005,7 +1011,10 @@ export function createSSEStream(options: StreamOptions = {}) {
try {
failureHandled = onFailure({ status: 502, message: msg, code: "empty_response" }) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (empty_response):`, e);
console.debug(
`[STREAM] onFailure callback error (empty_response):`,
retainDiagnosticForLog(e)
);
}
}
if (decrementPendingRequest && !failureHandled) {
@@ -1199,7 +1208,10 @@ export function createSSEStream(options: StreamOptions = {}) {
type: "timeout_error",
}) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (idle_timeout):`, e);
console.debug(
`[STREAM] onFailure callback error (idle_timeout):`,
retainDiagnosticForLog(e)
);
}
}
if (!failureHandled) {
@@ -1641,10 +1653,7 @@ export function createSSEStream(options: StreamOptions = {}) {
isResponsesCommentaryMessageItem
).items
: passthroughResponsesOutputItems;
const backfilled = backfillResponsesCompletedOutput(
parsed,
backfillCandidates
);
const backfilled = backfillResponsesCompletedOutput(parsed, backfillCandidates);
const usageNormalized = normalizeUsage(parsed);
if (
stripped ||
@@ -2040,7 +2049,7 @@ export function createSSEStream(options: StreamOptions = {}) {
try {
failureHandled = onFailure(failurePayload) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error:`, e);
console.debug(`[STREAM] onFailure callback error:`, retainDiagnosticForLog(e));
}
}
clearIdleTimer();
@@ -2624,7 +2633,10 @@ export function createSSEStream(options: StreamOptions = {}) {
),
});
} catch (e) {
console.debug(`[STREAM] onComplete callback error (${model || "unknown"}):`, e);
console.debug(
`[STREAM] onComplete callback error (${model || "unknown"}):`,
retainDiagnosticForLog(e)
);
}
} else {
clearPendingRequestFromStream();
@@ -2712,7 +2724,10 @@ export function createSSEStream(options: StreamOptions = {}) {
type: err.type,
}) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (${model || "unknown"}):`, e);
console.debug(
`[STREAM] onFailure callback error (${model || "unknown"}):`,
retainDiagnosticForLog(e)
);
}
}
@@ -2740,7 +2755,7 @@ export function createSSEStream(options: StreamOptions = {}) {
} catch (e) {
console.debug(
`[STREAM] onComplete callback error in error path (${model || "unknown"}):`,
e
retainDiagnosticForLog(e)
);
}
}
@@ -2945,14 +2960,18 @@ export function createSSEStream(options: StreamOptions = {}) {
} catch (e) {
console.debug(
`[STREAM] onComplete callback error in flush (${model || "unknown"}):`,
e
retainDiagnosticForLog(e)
);
}
} else {
clearPendingRequestFromStream();
}
} catch (error) {
console.log(`[STREAM] Error in flush (${model || "unknown"}):`, error.message || error);
const diagnostic = error instanceof Error ? error.message : error;
console.log(
`[STREAM] Error in flush (${model || "unknown"}):`,
retainDiagnosticForLog(diagnostic)
);
}
},
cancel(reason) {
@@ -2982,7 +3001,8 @@ export function createSSETransformStreamWithLogger(
copilotCompatibleReasoning = false,
suppressThinkClose = false,
customToolNames: ReadonlySet<string> = new Set(),
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null,
redactStreamDiagnosticsForLog = false
) {
return createSSEStream({
mode: STREAM_MODE.TRANSLATE,
@@ -3001,6 +3021,7 @@ export function createSSETransformStreamWithLogger(
suppressThinkClose,
customToolNames,
requestToolIdentityMap,
redactStreamDiagnosticsForLog,
});
}
@@ -3015,7 +3036,8 @@ export function createPassthroughStreamWithLogger(
apiKeyInfo: unknown = null,
onFailure: ((payload: StreamFailurePayload) => boolean | void | Promise<void>) | null = null,
clientResponseFormat: string | null = null,
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null,
redactStreamDiagnosticsForLog = false
) {
return createSSEStream({
mode: STREAM_MODE.PASSTHROUGH,
@@ -3030,6 +3052,7 @@ export function createPassthroughStreamWithLogger(
onFailure,
clientResponseFormat,
requestToolIdentityMap,
redactStreamDiagnosticsForLog,
});
}

View File

@@ -92,6 +92,7 @@ export function finalizeStreamRequestLog({
status,
error,
errorCode,
videoTranscriptSensitive = false,
onWarn,
}: {
pendingRequestId: string;
@@ -103,6 +104,7 @@ export function finalizeStreamRequestLog({
status: number;
error?: string | null;
errorCode?: string | null;
videoTranscriptSensitive?: boolean;
onWarn?: (error: unknown) => void;
}) {
try {
@@ -112,6 +114,7 @@ export function finalizeStreamRequestLog({
status,
error: error || null,
errorCode: errorCode || null,
videoTranscriptSensitive,
});
if (!completedById) {
finalizeMostRecentPendingRequest(model, provider, connectionId, {
@@ -120,6 +123,7 @@ export function finalizeStreamRequestLog({
status,
error: error || null,
errorCode: errorCode || null,
videoTranscriptSensitive,
});
}
} catch (error) {

View File

@@ -1,3 +1,4 @@
import { VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { trackPendingRequest } from "@/lib/usageDb";
import { STREAM_IDLE_TIMEOUT_MS } from "../config/constants.ts";
import { FORMATS } from "../translator/formats.ts";
@@ -39,6 +40,7 @@ type StreamControllerOptions = {
clientAbortSignal?: AbortSignal | null;
allowCompletedToolHandoffGrace?: boolean;
clientDisconnectGracePeriodMs?: number;
redactStreamDiagnosticsForLog?: boolean;
};
type StreamController = ReturnType<typeof createStreamController>;
@@ -243,6 +245,7 @@ export function createStreamController({
clientAbortSignal,
allowCompletedToolHandoffGrace = false,
clientDisconnectGracePeriodMs = 0,
redactStreamDiagnosticsForLog = false,
}: StreamControllerOptions = {}) {
const abortController = new AbortController();
const startTime = Date.now();
@@ -253,6 +256,9 @@ export function createStreamController({
let pendingRequestCleared = false;
let cleanupClientAbortSignal: (() => void) | null = null;
const retainDiagnosticForLog = (value: unknown): unknown =>
redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
const logStream = (status) => {
const duration = Date.now() - startTime;
const p = provider?.toUpperCase() || "UNKNOWN";
@@ -279,7 +285,7 @@ export function createStreamController({
} catch (e) {
console.error(
`[${getTimeString()}] [streamHandler] trackPendingRequest decrement failed — counter may drift`,
e
retainDiagnosticForLog(e)
);
}
};
@@ -317,7 +323,7 @@ export function createStreamController({
disconnected = true;
cleanupClientAbortListener();
logStream(`disconnect: ${reason}`);
logStream(`disconnect: ${String(retainDiagnosticForLog(reason))}`);
// Decrement pending request counter — the TransformStream flush() won't
// fire when the client aborts mid-stream, so we must clean up here.
@@ -390,7 +396,7 @@ export function createStreamController({
duration: Date.now() - startTime,
}) === true;
} catch (e) {
console.debug(`[STREAM-HANDLER] onError callback error:`, e);
console.debug(`[STREAM-HANDLER] onError callback error:`, retainDiagnosticForLog(e));
}
}
@@ -406,7 +412,7 @@ export function createStreamController({
}
if (error instanceof Error) {
logStream(`error: ${error.message}`);
logStream(`error: ${String(retainDiagnosticForLog(error.message))}`);
return;
}
logStream("error: unknown");
@@ -845,9 +851,11 @@ export function pipeWithDisconnect(
providerResponse: Response,
transformStream: TransformStream<Uint8Array, Uint8Array>,
streamController: StreamController,
opts: { stallTimeoutMs?: number } = {}
opts: { redactStreamDiagnosticsForLog?: boolean; stallTimeoutMs?: number } = {}
) {
const stallTimeoutMs = opts.stallTimeoutMs ?? DEFAULT_STREAM_STALL_TIMEOUT_MS;
const retainDiagnosticForLog = (value: unknown): unknown =>
opts.redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
// Watchdog disabled — preserve legacy behavior verbatim.
if (!stallTimeoutMs || stallTimeoutMs <= 0) {
@@ -887,7 +895,10 @@ export function pipeWithDisconnect(
try {
streamController.handleError?.(stallError);
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog handleError failed:`, e);
console.debug(
`[STREAM-HANDLER] stall watchdog handleError failed:`,
retainDiagnosticForLog(e)
);
}
// Error the pipeline so the downstream reader unblocks. createDisconnect-
// AwareStream's catch block translates this into buildStreamErrorChunks
@@ -895,13 +906,16 @@ export function pipeWithDisconnect(
try {
upstreamTapController?.error(stallError);
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog upstream tap error failed:`, e);
console.debug(
`[STREAM-HANDLER] stall watchdog upstream tap error failed:`,
retainDiagnosticForLog(e)
);
}
// Abort the underlying fetch so upstream releases the connection.
try {
streamController.abort?.();
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog abort failed:`, e);
console.debug(`[STREAM-HANDLER] stall watchdog abort failed:`, retainDiagnosticForLog(e));
}
}, stallTimeoutMs);
};

View File

@@ -474,6 +474,8 @@ export async function ensureStreamReadiness(
provider?: string | null;
model?: string | null;
log?: StreamReadinessLogger | null;
/** Keep the diagnostic transient while retaining only its existence in logs. */
redactUpstreamDiagnosticForLog?: boolean;
}
): Promise<StreamReadinessResult> {
if (!response.body || options.timeoutMs <= 0) return { ok: true, response };
@@ -568,9 +570,13 @@ export async function ensureStreamReadiness(
const reason = upstreamDiagnostic
? `${classificationReason}: ${upstreamDiagnostic}`
: classificationReason;
const retainedReason =
upstreamDiagnostic && options.redactUpstreamDiagnosticForLog
? `${classificationReason}: [upstream diagnostic omitted]`
: reason;
options.log?.warn?.(
"STREAM",
`${reason} (${options.provider || "provider"}/${options.model || "unknown"})`
`${retainedReason} (${options.provider || "provider"}/${options.model || "unknown"})`
);
return {
ok: false,

View File

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

View File

@@ -47,7 +47,7 @@ curl https://localhost:20128/api/v1/search \
Run a unified search
Searches the web, news, or X through a configured provider. Set `provider` to `xquik-search` to use Xquik for X search. The aliases `xquik` and `xquik_search` resolve to the same provider.
Searches the web, news, or X through a configured provider. Set `provider` to `xquik-search` to use Xquik for X search. The aliases `xquik` and `xquik_search` resolve to the same provider. AnySearch (`anysearch-search`, aliases `anysearch` / `anysearch_search`) provides free fallback-only web search.
```bash
curl -X POST https://localhost:20128/api/v1/search \

View File

@@ -2177,6 +2177,9 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
builderConnectionId !== COMBO_BUILDER_AUTO_CONNECTION ? builderConnectionId : null,
connectionLabel: selectedBuilderConnection?.label || null,
allowedConnectionIds: builderEffectiveAllowedConnectionIds,
// #11433: use the already-corrected routing prefix (e.g. "oc" for
// OpenCode Free) instead of letting it default to the raw providerId.
modelPrefix: parseQualifiedModel(selectedBuilderModel.qualifiedModel)?.providerId,
})
: null;
const builderHasDuplicate =
@@ -2501,6 +2504,9 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
builderConnectionId !== COMBO_BUILDER_AUTO_CONNECTION ? builderConnectionId : null,
connectionLabel: selectedBuilderConnection?.label || null,
allowedConnectionIds: builderEffectiveAllowedConnectionIds,
// #11433: use the already-corrected routing prefix (e.g. "oc" for
// OpenCode Free) instead of letting it default to the raw providerId.
modelPrefix: parseQualifiedModel(selectedBuilderModel.qualifiedModel)?.providerId,
});
if (hasExactModelStepDuplicate(models, nextStep)) {

View File

@@ -0,0 +1,163 @@
// @vitest-environment jsdom
// Regression for issue #11324 and the autoFetchModels opt-in contract: adding a
// connection must not force a full upstream /models catalog sync unless the
// connection explicitly enables it.
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useApiKeySave } from "../hooks/useApiKeySave";
const t = ((key: string) => key) as Parameters<typeof useApiKeySave>[0]["t"];
function response(ok: boolean, body: unknown): Response {
return { ok, json: async () => body } as Response;
}
function renderApiKeySaveHook(): {
hookResult: () => ReturnType<typeof useApiKeySave>;
root: ReturnType<typeof createRoot>;
container: HTMLDivElement;
} {
const container = document.createElement("div");
document.body.appendChild(container);
let hookResult: ReturnType<typeof useApiKeySave> | null = null;
function Wrapper() {
const result = useApiKeySave({
providerId: "huge-catalog-openai-compatible",
fetchConnections: vi.fn().mockResolvedValue(undefined),
fetchProviderModelMeta: vi.fn().mockResolvedValue(undefined),
setImportProgress: vi.fn(),
setShowImportModal: vi.fn(),
setShowAddApiKeyModal: vi.fn(),
setSiliconFlowInitialBaseUrl: vi.fn(),
notify: { success: vi.fn(), error: vi.fn() },
t,
});
React.useEffect(() => {
hookResult = result;
}, [result]);
return null;
}
const root = createRoot(container);
act(() => root.render(<Wrapper />));
return { hookResult: () => hookResult as ReturnType<typeof useApiKeySave>, root, container };
}
describe("useApiKeySave.handleSaveApiKey — full-sync opt-out (#11324)", () => {
let roots: ReturnType<typeof createRoot>[] = [];
let containers: HTMLDivElement[] = [];
beforeEach(() => {
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
roots = [];
containers = [];
});
afterEach(() => {
for (const root of roots) act(() => root.unmount());
for (const container of containers) container.remove();
roots = [];
containers = [];
vi.unstubAllGlobals();
});
it("does not auto-trigger a full /sync-models catalog fetch when the caller asks to add just one manual model", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
const url = String(input);
if (url === "/api/providers") return response(true, { connection: { id: "conn-1" } });
if (url.includes("/sync-models")) {
return response(true, {
syncedModels: 1200,
availableModelsCount: 1200,
models: Array.from({ length: 1200 }, (_, i) => ({ id: `model-${i}` })),
});
}
throw new Error(`Unexpected fetch: ${url}`);
});
vi.stubGlobal("fetch", fetchMock);
const { hookResult, root, container } = renderApiKeySaveHook();
roots.push(root);
containers.push(container);
await act(async () => {
await hookResult().handleSaveApiKey({ apiKey: "sk-test", skipModelSync: true });
});
const syncCalls = fetchMock.mock.calls.filter(([input]) =>
String(input).includes("/sync-models")
);
expect(syncCalls).toHaveLength(0);
// The opt-out is a client-side intent signal only — it must never leak into the
// persisted connection payload sent to the server.
const providersCall = fetchMock.mock.calls.find(
([input]) => String(input) === "/api/providers"
);
const postedBody = JSON.parse((providersCall?.[1] as RequestInit).body as string);
expect(postedBody).not.toHaveProperty("skipModelSync");
expect(new Headers((providersCall?.[1] as RequestInit).headers).get("x-skip-model-sync")).toBe(
"true"
);
});
it("keeps the full /sync-models catalog fetch off when autoFetchModels is omitted", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
const url = String(input);
if (url === "/api/providers") return response(true, { connection: { id: "conn-1" } });
if (url.includes("/sync-models")) {
return response(true, { syncedModels: 3, availableModelsCount: 3, models: [] });
}
throw new Error(`Unexpected fetch: ${url}`);
});
vi.stubGlobal("fetch", fetchMock);
const { hookResult, root, container } = renderApiKeySaveHook();
roots.push(root);
containers.push(container);
await act(async () => {
await hookResult().handleSaveApiKey({ apiKey: "sk-test" });
});
const syncCalls = fetchMock.mock.calls.filter(([input]) =>
String(input).includes("/sync-models")
);
expect(syncCalls).toHaveLength(0);
});
it("auto-triggers one client-owned sync when autoFetchModels is true", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
const url = String(input);
if (url === "/api/providers") return response(true, { connection: { id: "conn-1" } });
if (url.includes("/sync-models")) {
return response(true, { syncedModels: 3, availableModelsCount: 3, models: [] });
}
throw new Error(`Unexpected fetch: ${url}`);
});
vi.stubGlobal("fetch", fetchMock);
const { hookResult, root, container } = renderApiKeySaveHook();
roots.push(root);
containers.push(container);
await act(async () => {
await hookResult().handleSaveApiKey({
apiKey: "sk-test",
providerSpecificData: { autoFetchModels: true },
});
});
const syncCalls = fetchMock.mock.calls.filter(([input]) =>
String(input).includes("/sync-models")
);
expect(syncCalls).toHaveLength(1);
const providersCall = fetchMock.mock.calls.find(
([input]) => String(input) === "/api/providers"
);
expect(new Headers((providersCall?.[1] as RequestInit).headers).get("x-skip-model-sync")).toBe(
"true"
);
});
});

View File

@@ -57,13 +57,29 @@ export function useApiKeySave({
}: UseApiKeySaveParams) {
const handleSaveApiKey = useCallback(
async (formData: Record<string, unknown>) => {
// Issue #11324: callers that only want to add one manual model (rather than
// importing an upstream provider's entire catalog) can pass `skipModelSync: true`
// to opt out of the automatic post-save full /sync-models call. This flag is a
// client-side intent signal only — keep it out of the persisted connection
// payload and relay it only through the non-persisted request header below.
const { skipModelSync, ...connectionFormData } = formData;
const autoFetchModels =
(
connectionFormData.providerSpecificData as
| Record<string, unknown>
| null
| undefined
)?.autoFetchModels === true;
try {
const res = await fetch("/api/providers", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...(autoFetchModels || skipModelSync ? { "X-Skip-Model-Sync": "true" } : {}),
},
body: JSON.stringify({
provider: resolveApiKeySaveProviderId(providerId),
...formData,
...connectionFormData,
}),
});
if (res.ok) {
@@ -75,7 +91,13 @@ export function useApiKeySave({
// Most providers sync their live catalog after connection creation. Curated-only
// providers intentionally use the registry list and must not show an import flow.
if (newConnection?.id && !providerUsesCuratedModelsOnly(providerId)) {
// Issue #11324: callers may also opt out explicitly via `skipModelSync`.
if (
newConnection?.id &&
!providerUsesCuratedModelsOnly(providerId) &&
autoFetchModels &&
!skipModelSync
) {
setShowImportModal(true);
setImportProgress({
current: 0,

View File

@@ -77,11 +77,18 @@ export function useProviderModels(providerId: string): UseProviderModelsResult {
}>;
};
if (cancelled) return;
const providerConn = connData.connections?.find(
const providerConnections = connData.connections?.filter(
(c) => (c.provider === providerId || c.id === providerId) && c.isActive !== false
);
const providerConn = providerConnections?.[0];
if (providerConn?.providerSpecificData?.autoFetchModels === true && !cancelled) {
if (
providerConn &&
providerConnections.every(
(connection) => connection.providerSpecificData?.autoFetchModels === true
) &&
!cancelled
) {
const syncRes = await fetch(
`/api/providers/${encodeURIComponent(providerConn.id)}/sync-models?mode=sync`,
{ method: "POST" }

View File

@@ -48,6 +48,7 @@ import {
getModelSyncInternalBaseUrl,
} from "@/shared/services/modelSyncScheduler";
import { finalizeValidatedChatGptWebCodexSecrets } from "@omniroute/open-sse/services/chatgptWebCodexAdmin.ts";
import { isAutoFetchModelsEnabled } from "@/lib/providerModels/modelDiscovery";
import { testSingleConnection } from "./[id]/test/route";
function projectCodexAccountPoolWithRoutingQuota(
@@ -282,50 +283,56 @@ export async function POST(request: Request) {
testStatus: testStatus || "unknown",
});
// Auto-trigger model discovery for the newly created connection.
// Auto-trigger model discovery only for an explicit autoFetchModels opt-in.
// Fire-and-forget: model sync can take seconds and should NOT block the
// POST response. If it fails, we log and move on — the connection itself
// is already persisted and the user can manually trigger a sync later.
// We use a self-fetch against our own /sync-models route, forwarding the
// incoming cookies (preserves management auth) plus the internal sync
// auth header (defense in depth) and an X-Internal-Auto-Sync marker for
// log correlation.
try {
// SECURITY: use the trusted loopback/env-pinned origin, NOT
// `new URL(request.url).origin` — the latter comes from the client-
// controlled Host header, which would let a caller redirect this
// credential-bearing internal self-fetch to an arbitrary host
// (SSRF + internal-auth-header exfiltration; CodeQL js/request-forgery).
const internalOrigin = getModelSyncInternalBaseUrl();
const cookieHeader = request.headers.get("cookie") || "";
const syncHeaders: Record<string, string> = {
"Content-Type": "application/json",
"X-Internal-Auto-Sync": "true",
...(cookieHeader ? { cookie: cookieHeader } : {}),
...buildModelSyncInternalHeaders(),
};
const syncUrl = `${internalOrigin}/api/providers/${encodeURIComponent(newConnection.id)}/sync-models?mode=import`;
// Intentionally not awaited: this is async/non-blocking work.
void fetchModelSyncInternal(syncUrl, {
method: "POST",
headers: syncHeaders,
redirect: "error",
})
.then((syncRes) => {
if (!syncRes.ok) {
console.log(`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`);
}
// log correlation. The dashboard skips this server-owned copy when it
// performs the same sync itself so it can render progress.
if (
isAutoFetchModelsEnabled(providerSpecificData) &&
request.headers.get("x-skip-model-sync") !== "true"
) {
try {
// SECURITY: use the trusted loopback/env-pinned origin, NOT
// `new URL(request.url).origin` — the latter comes from the client-
// controlled Host header, which would let a caller redirect this
// credential-bearing internal self-fetch to an arbitrary host
// (SSRF + internal-auth-header exfiltration; CodeQL js/request-forgery).
const internalOrigin = getModelSyncInternalBaseUrl();
const cookieHeader = request.headers.get("cookie") || "";
const syncHeaders: Record<string, string> = {
"Content-Type": "application/json",
"X-Internal-Auto-Sync": "true",
...(cookieHeader ? { cookie: cookieHeader } : {}),
...buildModelSyncInternalHeaders(),
};
const syncUrl = `${internalOrigin}/api/providers/${encodeURIComponent(newConnection.id)}/sync-models?mode=import`;
// Intentionally not awaited: this is async/non-blocking work.
void fetchModelSyncInternal(syncUrl, {
method: "POST",
headers: syncHeaders,
redirect: "error",
})
.catch((err) => {
console.log(`[providers] Auto-sync error for ${newConnection.id}:`, err?.message || err);
});
} catch (syncSetupError) {
// Defensive: if URL parsing or header construction itself throws, do
// not let it break the (already successful) POST response.
console.log(
`[providers] Auto-sync setup failed for ${newConnection.id}:`,
syncSetupError?.message || syncSetupError
);
.then((syncRes) => {
if (!syncRes.ok) {
console.log(`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`);
}
})
.catch((err) => {
console.log(`[providers] Auto-sync error for ${newConnection.id}:`, err?.message || err);
});
} catch (syncSetupError) {
// Defensive: if URL parsing or header construction itself throws, do
// not let it break the (already successful) POST response.
console.log(
`[providers] Auto-sync setup failed for ${newConnection.id}:`,
syncSetupError?.message || syncSetupError
);
}
}
// Auto-test the newly created connection so `testStatus` reflects reality

View File

@@ -20,7 +20,7 @@ export async function getOrInitSupervisor(): Promise<ServiceSupervisor> {
tool: TOOL,
port: PORT,
spawnArgs: () => resolveSpawnArgs(PORT, managementKey),
healthUrl: () => `http://127.0.0.1:${PORT}/v1/models`,
healthUrl: () => `http://127.0.0.1:${PORT}/healthz`,
healthIntervalMs: 5_000,
stopTimeoutMs: 15_000,
logsBufferBytes: 5_242_880,

View File

@@ -83,6 +83,7 @@ export function buildPrecisionComboModelStep({
connectionLabel,
allowedConnectionIds = null,
weight = 0,
modelPrefix,
}: {
providerId: string;
modelId: string;
@@ -91,9 +92,22 @@ export function buildPrecisionComboModelStep({
/** #3266: account allowlist scoping round-robin to a subset of connections. */
allowedConnectionIds?: string[] | null;
weight?: number;
/**
* #11433: the routing-prefix segment to serialize into `model` (e.g. "oc"
* for the no-auth OpenCode Free provider), when it differs from the
* canonical `providerId`. Some canonical provider ids collide with an
* unrelated manual `ALIAS_TO_PROVIDER_ID` routing override (`opencode` →
* `opencode-zen`), so reconstructing `model` from the raw `providerId`
* alone can round-trip to the wrong provider on request routing. Falls
* back to `providerId` when omitted/blank. `step.providerId` always stays
* the canonical id regardless, so routing/duplicate-detection identity is
* unaffected.
*/
modelPrefix?: string | null;
}): ComboModelStep {
const normalizedProviderId = toTrimmedString(providerId) || "provider";
const normalizedModelId = toTrimmedString(modelId) || "model";
const normalizedModelPrefix = toTrimmedString(modelPrefix) || normalizedProviderId;
const normalizedConnectionId = toTrimmedString(connectionId);
const normalizedConnectionLabel = toTrimmedString(connectionLabel);
// A pinned single connection wins over an allowlist, so only carry the allowlist
@@ -110,7 +124,7 @@ export function buildPrecisionComboModelStep({
return {
kind: "model",
providerId: normalizedProviderId,
model: `${normalizedProviderId}/${normalizedModelId}`,
model: `${normalizedModelPrefix}/${normalizedModelId}`,
...(normalizedConnectionId ? { connectionId: normalizedConnectionId } : {}),
...(normalizedConnectionLabel ? { label: normalizedConnectionLabel } : {}),
...(normalizedAllowed.length > 0 ? { allowedConnectionIds: normalizedAllowed } : {}),
@@ -160,10 +174,15 @@ export function buildManualComboModelStep({
const providerId = resolveComboBuilderProviderId(parsed.providerId, providers);
if (!providerId) return null;
// #11433: preserve the user-typed prefix (e.g. "oc") as the routing prefix
// instead of letting buildPrecisionComboModelStep rebuild `model` from the
// resolved canonical providerId, which can collide with an unrelated
// manual alias override (e.g. "opencode" -> "opencode-zen").
return buildPrecisionComboModelStep({
providerId,
modelId: parsed.modelId,
weight,
modelPrefix: parsed.providerId,
});
}
@@ -225,7 +244,7 @@ type ComboBuilderGlobalProvider = {
displayName?: unknown;
connectionCount?: unknown;
connections?: unknown[];
models?: Array<{ id?: unknown; name?: unknown }>;
models?: Array<{ id?: unknown; name?: unknown; qualifiedModel?: unknown }>;
};
/**
@@ -252,12 +271,18 @@ export function buildGlobalModelList(
const modelId = toTrimmedString(model?.id);
if (!modelId) return;
const modelName = toTrimmedString(model?.name) || modelId;
// #11433: derive the routing prefix from the model's already-corrected
// `qualifiedModel` (e.g. "oc/<model>" for the OpenCode Free provider)
// instead of defaulting to the raw providerId, which can collide with
// an unrelated manual alias override.
const modelPrefix = parseQualifiedModel(model?.qualifiedModel)?.providerId || providerId;
const step = buildPrecisionComboModelStep({
providerId,
modelId,
connectionId: null,
connectionLabel: null,
allowedConnectionIds: [],
modelPrefix,
});
list.push({
providerId,

View File

@@ -13,6 +13,7 @@ import {
deleteAllFromTable,
deleteCallLogArtifacts,
deleteFromTableBefore,
tableExists,
type DeleteByPeriodTarget,
} from "./cleanup/usagePurge";
@@ -196,7 +197,9 @@ export async function cleanupMcpAudit(): Promise<CleanupResult> {
/**
* Clean up old config_audit_log based on retention settings.
*/
export async function cleanupConfigAudit(retentionDays = getRetentionSettings().configAudit): Promise<CleanupResult> {
export async function cleanupConfigAudit(
retentionDays = getRetentionSettings().configAudit
): Promise<CleanupResult> {
const db = getDbInstance();
const result: CleanupResult = { deleted: 0, errors: 0 };
@@ -237,7 +240,9 @@ export async function cleanupA2aEvents(): Promise<CleanupResult> {
const runResult = stmt.run(cutoffISO);
result.deleted = runResult.changes;
console.log(`[Cleanup] Deleted ${result.deleted} a2a_task_events older than ${retentionDays} days`);
console.log(
`[Cleanup] Deleted ${result.deleted} a2a_task_events older than ${retentionDays} days`
);
} catch (err: unknown) {
console.error("[Cleanup] Error cleaning a2a_task_events:", err);
result.errors++;
@@ -383,6 +388,8 @@ export async function cleanupCompressionRunTelemetry(): Promise<CleanupResult> {
const result: CleanupResult = { deleted: 0, errors: 0 };
try {
if (!tableExists("compression_run_telemetry")) return result;
const stmt = db.prepare("DELETE FROM compression_run_telemetry WHERE timestamp < ?");
const runResult = stmt.run(cutoffEpoch);
result.deleted = runResult.changes;
@@ -600,16 +607,56 @@ function isResetUsageHistoryPeriod(period: string): period is ResetUsageHistoryP
*/
const RESET_TARGETS: Array<DeleteByPeriodTarget & { resultKey: keyof ResetUsageHistoryResult }> = [
{ table: "usage_history", column: "timestamp", cutoff: "iso", resultKey: "deletedUsageHistory" },
{ table: "daily_usage_summary", column: "date", cutoff: "date", resultKey: "deletedDailySummary" },
{ table: "hourly_usage_summary", column: "date_hour", cutoff: "dateHour", resultKey: "deletedHourlySummary" },
{
table: "daily_usage_summary",
column: "date",
cutoff: "date",
resultKey: "deletedDailySummary",
},
{
table: "hourly_usage_summary",
column: "date_hour",
cutoff: "dateHour",
resultKey: "deletedHourlySummary",
},
{ table: "call_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedCallLogs" },
{ table: "request_detail_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedRequestDetailLogs" },
{
table: "request_detail_logs",
column: "timestamp",
cutoff: "iso",
resultKey: "deletedRequestDetailLogs",
},
{ table: "proxy_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedProxyLogs" },
{ table: "relay_logs", column: "created_at", cutoff: "epochSeconds", resultKey: "deletedRelayLogs" },
{ table: "compression_analytics", column: "timestamp", cutoff: "iso", resultKey: "deletedCompressionAnalytics" },
{ table: "compression_run_telemetry", column: "timestamp", cutoff: "epochMs", resultKey: "deletedCompressionRunTelemetry" },
{ table: "routing_decisions", column: "created_at", cutoff: "iso", resultKey: "deletedRoutingDecisions" },
{ table: "quota_consumption", column: "updated_at", cutoff: "epochMs", resultKey: "deletedQuotaConsumption" },
{
table: "relay_logs",
column: "created_at",
cutoff: "epochSeconds",
resultKey: "deletedRelayLogs",
},
{
table: "compression_analytics",
column: "timestamp",
cutoff: "iso",
resultKey: "deletedCompressionAnalytics",
},
{
table: "compression_run_telemetry",
column: "timestamp",
cutoff: "epochMs",
resultKey: "deletedCompressionRunTelemetry",
},
{
table: "routing_decisions",
column: "created_at",
cutoff: "iso",
resultKey: "deletedRoutingDecisions",
},
{
table: "quota_consumption",
column: "updated_at",
cutoff: "epochMs",
resultKey: "deletedQuotaConsumption",
},
{ table: "token_ledger", column: "created_at", cutoff: "iso", resultKey: "deletedTokenLedger" },
];

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