mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
docs(wiki): auto-sync pages + cover counts with docs
136
Ban-Detection.md
Normal file
136
Ban-Detection.md
Normal file
@@ -0,0 +1,136 @@
|
||||
> 🌍 [View in other languages](Languages)
|
||||
|
||||
|
||||
# Account-Ban / Banned-Keyword Detection
|
||||
|
||||
OmniRoute scans upstream error responses for signals that indicate a provider
|
||||
**account is permanently dead** (suspended / deactivated / ToS-banned) and, when
|
||||
matched, moves that connection into a **terminal `banned` state** so it is no
|
||||
longer selected for requests. This is what the **Security → Banned Keywords**
|
||||
settings card configures ("Additional keywords that trigger permanent account
|
||||
ban detection. Built-in keywords always apply.").
|
||||
|
||||
This page documents the built-in list, the detection flow, its scope, how to add
|
||||
custom keywords safely, and how to recover a flagged connection. The terminal
|
||||
state itself is part of the resilience model — see
|
||||
[RESILIENCE_GUIDE](../architecture/RESILIENCE_GUIDE.md) ("Terminal states").
|
||||
|
||||
**Source of truth:** `open-sse/services/accountFallback.ts`
|
||||
(`ACCOUNT_DEACTIVATED_SIGNALS`, `getMergedBannedSignals()`, `isAccountDeactivated()`).
|
||||
|
||||
## Built-in keywords
|
||||
|
||||
These 8 substrings always apply (case-insensitive), regardless of any custom list:
|
||||
|
||||
```
|
||||
account_deactivated
|
||||
account has been deactivated
|
||||
account has been disabled
|
||||
your account has been suspended
|
||||
this account is deactivated
|
||||
verify your account to continue (Antigravity / Google Cloud Code)
|
||||
this service has been disabled in this account for violation (Antigravity)
|
||||
this service has been disabled in this account (Antigravity)
|
||||
```
|
||||
|
||||
> This list evolves as providers change their ban wording. The authoritative
|
||||
> copy is `ACCOUNT_DEACTIVATED_SIGNALS` in `open-sse/services/accountFallback.ts`;
|
||||
> treat the block above as a snapshot.
|
||||
|
||||
Two adjacent, **separate** signal tables live in the same file and are *not* part
|
||||
of banned-keyword detection:
|
||||
|
||||
- `CREDITS_EXHAUSTED_SIGNALS` — billing/quota depleted (`insufficient_quota`,
|
||||
`credit_balance_too_low`, `payment required`, …) → terminal `credits_exhausted`.
|
||||
- `OAUTH_INVALID_TOKEN_SIGNALS` — **non-terminal**; a token refresh can recover.
|
||||
|
||||
Note: common transient phrases like **`rate limit`** / `429` are handled by the
|
||||
rate-limit / connection-cooldown path and are **not** ban signals.
|
||||
|
||||
## Detection flow
|
||||
|
||||
```
|
||||
upstream error response
|
||||
→ body stringified + lowercased
|
||||
→ isAccountDeactivated(body): getMergedBannedSignals().some(sig => body.includes(sig)) [substring match]
|
||||
→ match?
|
||||
→ connection testStatus = "banned" (permanent — 1-year cooldown, never auto-recovers)
|
||||
→ if setting `autoDisableBannedAccounts` is on → also isActive = false
|
||||
→ connection is skipped during account selection (combo QUOTA_BLOCKING statuses)
|
||||
```
|
||||
|
||||
- The match is a **case-insensitive substring** search on the response **body**
|
||||
(`isAccountDeactivated`, `accountFallback.ts`).
|
||||
- The permanent `banned` terminalization fires on a banned-signal body at **any
|
||||
HTTP status** (via `markAccountUnavailable` → `checkFallbackError`). The
|
||||
narrower **`deactivated`** label (`isActive=false` when the connection has no
|
||||
spare API keys) is written by the inline `chatCore.ts` path on **HTTP 401 / 403**
|
||||
(classified via `classifyProviderError` → `ACCOUNT_DEACTIVATED`). Note the
|
||||
`markAccountUnavailable()` path writes a *different* terminal status —
|
||||
**`expired`** — for the same `ACCOUNT_DEACTIVATED` signal (via
|
||||
`resolveTerminalConnectionStatus`), so the same ban can surface as either
|
||||
`deactivated` or `expired` depending on which path handled the response. (The
|
||||
older code comment says "when a 401 body contains these strings" — that
|
||||
understates the current behavior.)
|
||||
- A `banned` connection is excluded from selection everywhere terminal statuses
|
||||
are filtered (`isTerminalConnectionStatus`, combo `QUOTA_BLOCKING_CONNECTION_STATUSES`).
|
||||
|
||||
## Scope — which providers are scanned
|
||||
|
||||
**All providers.** The check runs in the generic error-handling pipeline that
|
||||
every failed upstream request flows through — it is **not** gated to
|
||||
OAuth/subscription scrapers. The resulting terminal state is per **connection**,
|
||||
not per provider.
|
||||
|
||||
That said, the built-in *strings* are oriented toward subscription/OAuth
|
||||
providers with real ban risk (ChatGPT Web, Claude Web, Codex, Muse Spark,
|
||||
Antigravity). An API-key provider will only trip the detector if its error body
|
||||
literally contains one of the substrings.
|
||||
|
||||
## Custom banned keywords
|
||||
|
||||
Add or remove keywords in **Security → Banned Keywords** (persisted as the global
|
||||
`customBannedSignals` setting via `PATCH /api/settings`). They are **added to**
|
||||
the built-in list — never a replacement — and hot-reload on save (and at startup)
|
||||
via `setCustomBannedSignals()`. Each keyword is capped at 200 characters; there is
|
||||
no array-length limit.
|
||||
|
||||
**⚠ False-positive risk — choose specific phrases.** Detection is a raw substring
|
||||
match on the whole response body, and a match is **permanent** (1-year cooldown,
|
||||
manual recovery). A broad keyword can ban a perfectly healthy connection:
|
||||
|
||||
- **Bad:** `quota`, `limit`, `error`, `denied` — appear in many transient errors.
|
||||
- **Good:** full ban sentences, e.g. `your account has been suspended for`,
|
||||
`account permanently banned`, `violation of our terms`.
|
||||
|
||||
Prefer the longest unambiguous phrase the provider returns on a real ban. When in
|
||||
doubt, watch the connection's `lastError` first, then add the exact wording.
|
||||
|
||||
## Recovering a flagged connection
|
||||
|
||||
Terminal `banned` / `deactivated` states **never auto-recover** (they are excluded
|
||||
from the proactive-recovery tick — only `unavailable` cooldowns recover on their
|
||||
own). An operator must clear them explicitly:
|
||||
|
||||
1. **Re-test the connection** — the dashboard **Test** action
|
||||
(`POST /api/providers/{id}/test`); a successful probe resets `testStatus` to
|
||||
`active` and clears the error fields.
|
||||
2. **Re-authenticate / edit credentials** — for OAuth providers, re-run the login
|
||||
/ refresh flow; provider create/import routes set `isActive = true`.
|
||||
3. **Re-enable the connection** — if `autoDisableBannedAccounts` set
|
||||
`isActive = false`, toggle it back on after fixing the account.
|
||||
|
||||
There is no separate "clear ban flag" button — recovery is re-test, re-auth, or
|
||||
re-enable, matching the general terminal-state rule in
|
||||
[RESILIENCE_GUIDE](../architecture/RESILIENCE_GUIDE.md).
|
||||
|
||||
## Source files
|
||||
|
||||
| Concern | File |
|
||||
| --- | --- |
|
||||
| Signal tables + match | `open-sse/services/accountFallback.ts` |
|
||||
| Terminalization / persistence | `src/sse/services/auth.ts` (`markAccountUnavailable`, `resolveTerminalConnectionStatus`, `clearAccountError`) |
|
||||
| Inline classification | `open-sse/handlers/chatCore.ts`, `open-sse/services/errorClassifier.ts` |
|
||||
| Terminal-state recovery exclusion | `src/lib/quota/connectionRecovery.ts` |
|
||||
| Custom-keyword runtime load | `src/lib/config/runtimeSettings.ts` (`setCustomBannedSignals`) |
|
||||
| Settings UI | `src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx` |
|
||||
6
Home.md
6
Home.md
@@ -1,6 +1,6 @@
|
||||
# 🚀 OmniRoute — The Free AI Gateway
|
||||
|
||||
**Never stop coding. Connect every AI tool to 236 providers — 50+ free — through one endpoint.**
|
||||
**Never stop coding. Connect every AI tool to 237 providers — 50+ free — through one endpoint.**
|
||||
|
||||
Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini. Auto-fallback.
|
||||
|
||||
@@ -12,7 +12,7 @@ Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude /
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **236 AI Providers** | OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, and many more |
|
||||
| **237 AI Providers** | OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, and many more |
|
||||
| **50+ Free Tiers** | OAuth + free-tier providers for zero-cost AI access |
|
||||
| **17 Routing Strategies** | Priority, weighted, round-robin, cost-optimized, context-relay, and more |
|
||||
| **Token Compression** | RTK + Caveman engines save 15–95% tokens automatically |
|
||||
@@ -42,7 +42,7 @@ Use the **sidebar** to navigate through all documentation sections:
|
||||
- **[Features](Features)** — All features explained
|
||||
- **[Architecture](Architecture)** — System design and internals
|
||||
- **[API Reference](API-Reference)** — REST API documentation
|
||||
- **[Providers](Provider-Reference)** — All 236 supported providers
|
||||
- **[Providers](Provider-Reference)** — All 237 supported providers
|
||||
- **[Combos & Routing](Auto-Combo)** — Routing strategies
|
||||
- **[Compression](Compression-Guide)** — Token compression pipeline
|
||||
- **[MCP Server](MCP-Server)** — MCP tools and transports
|
||||
|
||||
107
Maturity-Reeval.md
Normal file
107
Maturity-Reeval.md
Normal file
@@ -0,0 +1,107 @@
|
||||
> 🌍 [View in other languages](Languages)
|
||||
|
||||
|
||||
# Reavaliação de Maturidade — pós-Ondas 0–3 (Quality-Gate v2)
|
||||
|
||||
> **O que é este documento.** Uma re-medição da maturidade do sistema de quality-gates
|
||||
> **após** as Ondas 0–3 do programa Quality-Gate v2, comparada ao baseline registrado em
|
||||
> [`QUALITY_GATE_PLAYBOOK.md`](./QUALITY_GATE_PLAYBOOK.md) (2026-06-16). Mede o que mudou,
|
||||
> contra DSOMM L5 / OpenSSF Scorecard 9 / SLSA L3, separando o que é **CI-mensurável**
|
||||
> (já entregue / entregável por código) do que é **processo/owner** (settings de organização).
|
||||
>
|
||||
> **Data:** 2026-06-30. Gerado do estado real do repositório, não da memória.
|
||||
> **Régua:** OWASP DSOMM · OpenSSF Scorecard · SLSA · SonarQube "Clean as You Code".
|
||||
|
||||
---
|
||||
|
||||
## 1. Veredito atualizado
|
||||
|
||||
**Nota geral: A− → A ("Avançado", topo ~5%).** As **duas maiores fraquezas estruturais**
|
||||
do baseline 06-16 — o *buraco fast-gates* e o *mutation-score-não-catraca* — foram **fechadas**.
|
||||
Os gaps residuais para "máximo absoluto" são quase todos **owner/infra-gated** (branch-protection,
|
||||
SLSA L3, CodeQL advanced); o lado-código do programa está essencialmente completo.
|
||||
|
||||
| Framework de referência | Baseline 06-16 | Agora 06-30 | Movimento | Evidência |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **OWASP DSOMM** (5 níveis) | L3→L4 | **L4** em *Test Intensity* e *Static Depth*; L3 sólido nas demais | ▲ | mutation-ratchet bloqueante + suíte determinística no gate-de-merge |
|
||||
| **OpenSSF Scorecard** | ~7–8/10 | ~7–8/10 (inalterado — gate é o **owner**) | = | falta Branch-Protection na `main` (setting do dono) + pin de actions |
|
||||
| **SLSA** | L2→L3 | **L2** (encostando em L3) | = | falta builder hermético/reprodutível (infra/owner) |
|
||||
| **SonarQube "Clean as You Code"** | Alinhado c/ ressalva | Alinhado c/ ressalva | = | ressalva de *sprawl* (~46+ gates) permanece — review de ROI pendente |
|
||||
| **Quality-Ratchet pattern** | Exemplar | **Exemplar+** | ▲ | novo `dedicatedGate` de `mutationScore` (direction up) |
|
||||
| **Mutation testing** | "Quase lá" (não-catraca) | **Catraca ativa** | ▲▲ | `check-mutation-ratchet.mjs` + baseline semeado + job nightly bloqueante |
|
||||
|
||||
---
|
||||
|
||||
## 2. Deltas desde 2026-06-16 (o que as Ondas 0–3 entregaram)
|
||||
|
||||
### 2.1 🔴→✅ Buraco fast-gates FECHADO (era a fraqueza estrutural #1)
|
||||
O baseline alertava: `quality.yml` (PR→`release/**`) rodava **só gates de filesystem** — sem
|
||||
typecheck, testes ou build —, então regressões determinísticas só explodiam no PR→`main`.
|
||||
**Hoje** `.github/workflows/quality.yml` roda, no job *Fast Quality Gates*: `typecheck:core`,
|
||||
**testes unitários impactados (TIA) bloqueantes com fail-safe para a suíte completa**, o
|
||||
fast-path do **vitest**, e shards de unit. O gate agora roda **onde o merge acontece** (shift-left),
|
||||
exatamente o princípio transversal que o playbook prescreve.
|
||||
|
||||
### 2.2 🟠→✅ Mutation score virou CATRACA (era a fraqueza #3 / P0 #1)
|
||||
O antídoto mais forte contra coverage-gaming estava **advisory**. **Hoje**:
|
||||
- `scripts/check/check-mutation-ratchet.mjs` (advisory por default, `--ratchet` bloqueante, skip gracioso);
|
||||
- `config/quality/quality-baseline.json` tem entradas `mutationScore.<módulo>` semeadas (`direction: up`, `dedicatedGate`);
|
||||
- `.github/workflows/nightly-mutation.yml` tem o job **"Mutation score ratchet (blocking)"** que unifica os relatórios por-batch e ratcheteia os scores merged por-módulo.
|
||||
|
||||
Resultado: o score de mutação por-módulo **não pode regredir** — cobertura deixou de ser vanity-metric.
|
||||
|
||||
### 2.3 ✅ Quick-wins de gate (Fase 6A/7) entregues
|
||||
- **a11y axe-core "fake-green" corrigido:** `@axe-core/playwright` em devDeps; `a11y.spec.ts` com skip condicional `REQUIRE_AXE`; job no `nightly-resilience.yml`.
|
||||
- **complexity varre `bin/`+`electron`:** `check-complexity.mjs` inclui esses diretórios no `ESLINT_ARGS`.
|
||||
- **tracked-artifacts no pre-commit + pre-push:** `.husky/pre-commit` + `pre-push` bloqueiam artefato rastreado por engano.
|
||||
|
||||
---
|
||||
|
||||
## 3. As 12 categorias — situação (delta-focada)
|
||||
|
||||
| # | Categoria | Situação 06-30 |
|
||||
| --- | --- | --- |
|
||||
| 1 | Estilo & formatação | ✅ inalterado (Prettier+ESLint lint-staged) |
|
||||
| 2 | Tipos | ✅ **reforçado** — `typecheck:core` agora também no gate PR→release |
|
||||
| 3 | Testes (intensidade) | ✅ **reforçado** — mutation testing virou catraca; suíte determinística no gate-de-merge |
|
||||
| 4 | Política de testes (anti-gaming) | ✅ inalterado (pr-test-policy/test-masking/pr-evidence) |
|
||||
| 5 | Complexidade & saúde | ✅ **reforçado** — complexity varre bin/electron |
|
||||
| 6 | Segurança estática (SAST+segredos) | 🟡 CodeQL default-setup (advanced = owner); semgrep cloud não-versionado |
|
||||
| 7 | Supply-chain (deps) | ✅ inalterado (osv/audit/Trivy/Dependabot + allowlist) |
|
||||
| 8 | Supply-chain (build/release) | 🟡 SLSA L2 (L3 = builder hermético, owner/infra) |
|
||||
| 9 | Contratos & API | 🟡 oasdiff/osv advisory (candidatos a bloqueante-com-escopo, P1) |
|
||||
| 10 | Docs & i18n (anti-rot) | ✅ **reforçado** — `fabricated-docs --strict` bloqueante (verificado exit 0) |
|
||||
| 11 | Anti-alucinação / consistência | ✅ inalterado (known-symbols/fetch-targets/docs-symbols/db-rules) |
|
||||
| 12 | Resiliência & domínio | ✅ inalterado (chaos/heap/k6/promptfoo/garak nightly) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Gap residual para "máximo absoluto"
|
||||
|
||||
### 4.1 CI-mensurável / entregável por código (backlog deste programa)
|
||||
- **P1 — osv/oasdiff → bloqueante com escopo certo:** osv só `CRITICAL`+fixable (two-step como o Trivy); oasdiff bloqueia breaking-change de contrato.
|
||||
- **P1 — `require-tighten` bloqueante (fim de ciclo):** trava ganhos de métrica (impede afrouxar baseline sem registrar).
|
||||
- **P1/P2 — review de ROI / sprawl de gates:** consolidar micro-gates de doc-sync; medir timing por-gate no `ci-summary` (combate a fadiga — ressalva do SonarQube/DORA). Os merges ROI deferidos (complexity unificada; `/api` anti-alucinação unificada) entram aqui.
|
||||
- **P2 — CodeQL config commitado + semgrep versionado:** mais controle/reprodutibilidade.
|
||||
|
||||
### 4.2 Processo / owner (CI não move — settings de organização)
|
||||
- **Branch-protection na `main`** (sobe Scorecard, fecha o gap DSOMM). Ver [`BRANCH_PROTECTION_MAIN.md`](./BRANCH_PROTECTION_MAIN.md).
|
||||
- **CodeQL Default → Advanced setup.**
|
||||
- **SLSA L3** — builder hermético/reprodutível (gerador SLSA do GitHub). Stretch (diminishing returns).
|
||||
|
||||
### 4.3 Explicitamente fora-de-escopo
|
||||
- **DSOMM L5** é majoritariamente **org-level / processo** (não CI-codificável).
|
||||
- **SLSA L4** (bit-a-bit reprodutível) é stretch declarado.
|
||||
|
||||
---
|
||||
|
||||
## 5. Itens deferidos / removidos (housekeeping da cauda)
|
||||
|
||||
- **`semcheck.yaml` (camada LLM de drift semântico docs↔code) — REMOVIDO.** Estava **órfão**
|
||||
(nenhum workflow/script o invocava) e com contagens stale nas regras. A cobertura determinística
|
||||
já existe (`check:fabricated-docs --strict` + `check:docs-counts-sync` + `check:docs-symbols`),
|
||||
e a ressalva de *gate sprawl* desaconselha adicionar um gate LLM advisory de custo recorrente.
|
||||
Pode ser re-introduzido no futuro como job nightly opt-in se o drift semântico virar problema real.
|
||||
- **`agent-lsp` scaffold — DEFERIDO / opt-in não-ativado.** Existe como menção em docs
|
||||
(`docs/architecture/QUALITY_GATES.md`, CHANGELOG) mas **sem wiring** e sem `.mcp.json.example`
|
||||
no repo. Permanece como scaffold opt-in documentado; não é um gate ativo nem um gap de maturidade.
|
||||
146
Router-Backends.md
Normal file
146
Router-Backends.md
Normal file
@@ -0,0 +1,146 @@
|
||||
> 🌍 [View in other languages](Languages)
|
||||
|
||||
|
||||
# Router Backends & Embedded Services — architecture contract (ADR)
|
||||
|
||||
> **Status:** Accepted · **Context:** [#5670](https://github.com/diegosouzapw/OmniRoute/issues/5670),
|
||||
> [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) · **Contract:** `domain/routing/routerBackends.ts`
|
||||
> (typed registry — code lands with [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868))
|
||||
|
||||
This ADR pins down how `ts` (native), `bifrost`, `cliproxy`, `9router`, and
|
||||
VibeProxy-compatible engines relate to each other, so contributors stop
|
||||
conflating two things that are architecturally distinct. It documents the typed
|
||||
registry introduced by the router-backend-registry work as the single source of
|
||||
truth for that model.
|
||||
|
||||
## The core distinction — two orthogonal axes
|
||||
|
||||
An engine's role is described by **two independent axes**, encoded together in the
|
||||
registry's `RouterBackendDefinition`:
|
||||
|
||||
1. **Lifecycle** (`RouterBackendLifecycle`) — _how the engine runs_:
|
||||
- `in-process` — runs inside the OmniRoute Node process (the native TS pipeline).
|
||||
- `supervised` — a local child process OmniRoute installs/starts/stops/health-checks
|
||||
via `ServiceSupervisor`, then consumes as a provider connection.
|
||||
- `external` — an HTTP endpoint OmniRoute dispatches to but does **not** manage
|
||||
(configured by an env base URL).
|
||||
- `disabled` — registered but not selectable.
|
||||
2. **Selection axis** (relay routing backend) — _whether the relay dispatches to it_:
|
||||
`RelayRoutingBackend = "ts" | "bifrost" | "auto"` in
|
||||
`src/app/api/v1/relay/chat/completions/routingBackend.ts`.
|
||||
|
||||
The mistake to avoid: treating "embedded service" and "routing backend" as one
|
||||
list. They are not. A `supervised` engine (9router/cliproxy) is a **provider
|
||||
connection consumed by the native pipeline**, not an alternate relay dispatch
|
||||
backend. `bifrost` is the reverse — a relay dispatch backend that (historically)
|
||||
was `external`-only.
|
||||
|
||||
## The registry — single source of truth
|
||||
|
||||
The `domain/routing/routerBackends.ts` contract (code lands with
|
||||
[#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) declares every engine once, with its
|
||||
lifecycle, capabilities, service identity, default port, health config, and
|
||||
telemetry support. Consumers look engines up via `getRouterBackend(id)`,
|
||||
`listRouterBackends()`, and `listRouterBackendsByCapability(cap)` instead of
|
||||
special-casing each sidecar.
|
||||
|
||||
| Backend | Lifecycle | Service (axis A) | Relay backend (axis B) | Health | Default port |
|
||||
| ----------- | ------------ | ---------------- | ---------------------- | ------------- | ------------ |
|
||||
| `ts` | `in-process` | — | `ts` (native) | — | — |
|
||||
| `bifrost` | `external`¹ | —¹ | `bifrost` / `auto` | `/health` | — |
|
||||
| `cliproxy` | `supervised` | `cliproxy` | — (provider) | `/v1/models` | 8317 |
|
||||
| `9router` | `supervised` | `9router` | — (provider) | `/api/health` | 20130 |
|
||||
| `vibeproxy` | `external` | — | — (provider adapter) | `/v1/models` | — |
|
||||
|
||||
¹ Bifrost's promotion to a `supervised` embedded service (installable/startable
|
||||
from `/api/services/bifrost/`) is tracked in
|
||||
[#5817](https://github.com/diegosouzapw/OmniRoute/pull/5817); until it merges,
|
||||
Bifrost is `external`-only (reachable solely via `BIFROST_BASE_URL`).
|
||||
|
||||
`capabilities` (`chat`, `responses`, `streaming`, `tools`, `vision`,
|
||||
`oauth-backed`, `dashboard-embed`, `model-sync`, `native-hot-path`) let callers
|
||||
filter by what an engine can actually do rather than hard-coding per-id branches.
|
||||
|
||||
## Axis A — embedded services (supervised process side)
|
||||
|
||||
- **Registry of supervised processes:** `src/lib/services/bootstrap.ts` `SERVICES[]`
|
||||
(today: `9router`, `cliproxy`).
|
||||
- **Lifecycle owner:** `src/lib/services/ServiceSupervisor.ts` — `start()` spawns the
|
||||
child, gates on `waitForHealthy()`, taps stdout/stderr into a ring buffer;
|
||||
`stop()` SIGTERM→SIGKILL; all serialized under a lock.
|
||||
- **State union** (`src/lib/services/types.ts`):
|
||||
`not_installed | stopped | starting | running | stopping | error`, plus an
|
||||
orthogonal `HealthState = healthy | unhealthy | unknown`.
|
||||
- **Why a separate process (not an in-proc SDK)?** Process isolation is what makes
|
||||
install/start/stop/health/logs independently controllable per sidecar and lets the
|
||||
loopback spawn-guard apply. Modeling an in-proc adapter is future work — the
|
||||
`native-hot-path` capability flag is where that would be expressed.
|
||||
|
||||
### Lifecycle route contract (`/api/services/<tool>/…`)
|
||||
|
||||
Status codes are **state/verb/path-specific by design** — this is the contract, not
|
||||
inconsistency:
|
||||
|
||||
| Call | Condition | Status |
|
||||
| ---------------------------- | ------------------------------- | ------------------------------------ |
|
||||
| `POST .../start` | service `not_installed` | **409** (precondition) |
|
||||
| `POST .../stop` | already stopped | **200** (idempotent no-op) |
|
||||
| `GET .../status` | OK | **200** (`live ?? row ?? "unknown"`) |
|
||||
| `POST .../start` | spawn failure | **503** (transient) |
|
||||
| `GET .../status`, `.../stop` | uncaught error | **500** |
|
||||
| `GET /api/services/<x>/logs` | unknown tool `<x>` | **404** `Service '<x>' not found` |
|
||||
| `GET .../status?reveal=key` | missing `X-Reveal-Confirm: yes` | **403** (9router only) |
|
||||
| **any** `/api/services/*` | caller not loopback/private-LAN | **403 LOCAL_ONLY** |
|
||||
|
||||
All error bodies are shaped by `createErrorResponse()` →
|
||||
`{ error: { message, type }, requestId }`, where `type` is derived from the status
|
||||
(`500→server_error`, `404→not_found`, `409→conflict`, else `invalid_request`) and is
|
||||
the machine-actionable discriminator. Messages are pre-sanitized
|
||||
(`sanitizeErrorMessage()`, Hard Rule #12).
|
||||
|
||||
**The loopback guard** is the most common source of a `403`: `/api/services/` is in
|
||||
`LOCAL_ONLY_API_PREFIXES` (`src/server/authz/routeGuard.ts`) and
|
||||
`src/server/authz/policies/management.ts` rejects any non-loopback / non-private-LAN
|
||||
caller **before auth**, because these routes spawn child processes (Hard Rules 15
|
||||
and 17). Reaching them through a public tunnel is `403` by design.
|
||||
|
||||
## Axis B — relay routing backend (dispatch side)
|
||||
|
||||
Only the relay proxy path `/api/v1/relay/chat/completions` selects a dispatch
|
||||
backend; the main `/api/v1/chat/completions` surface never consults
|
||||
`routingBackend.ts`.
|
||||
|
||||
- **Selection** (`resolveRelayRoutingBackend`): a single global env toggle —
|
||||
`OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` ∈ {`ts`, `bifrost`, `auto`}.
|
||||
If unset, `auto` when Bifrost is configured+enabled, else `ts`.
|
||||
- **Behavior:**
|
||||
- `bifrost` (forced): Bifrost failure → hard `502`, no fallback.
|
||||
- `auto`: try Bifrost, on failure/cooldown silently fall through to native.
|
||||
- `ts` / post-fallback: the native `open-sse` translator/executor pipeline.
|
||||
- **Cooldown:** per-`baseUrl` failure cooldown in `bifrostCooldown.ts`.
|
||||
|
||||
Selection is **all-or-nothing at the relay level today** — there is no per-provider
|
||||
or per-request engine swap on `release/v3.8.43`. The per-request gate is being added
|
||||
by the sidecar-manifest work
|
||||
([#5869](https://github.com/diegosouzapw/OmniRoute/pull/5869) manifest +
|
||||
[#5870](https://github.com/diegosouzapw/OmniRoute/pull/5870) `shouldTryBifrostForRequest`),
|
||||
which lets `auto` route only manifest-eligible providers through Bifrost.
|
||||
|
||||
## Dashboard integration
|
||||
|
||||
The services dashboard polls `GET /api/services/<tool>/status` every 5s via
|
||||
`src/app/(dashboard)/dashboard/providers/services/hooks/useServiceStatus.ts`,
|
||||
returning `{ tool, state, pid, port, health, installedVersion, latestVersion,
|
||||
updateAvailable, autoStart, … }`. There is no shared availability-context provider —
|
||||
each component calls the hook per tool. On `!res.ok` the hook currently surfaces a
|
||||
bare `HTTP <status>`; mapping the `error.type` field to a human explanation is a
|
||||
tracked UX improvement, not a contract change.
|
||||
|
||||
## Consequences
|
||||
|
||||
- New engines register once in `ROUTER_BACKENDS`; consumers gain them via capability
|
||||
queries without new per-id branches.
|
||||
- "Is this a service or a routing backend?" is answered by the `lifecycle` field, not
|
||||
by which list an id happens to appear in.
|
||||
- The Bifrost supervision (#5817) and native hot-path migration (#5670) build on this
|
||||
shared contract instead of special-casing each sidecar.
|
||||
Reference in New Issue
Block a user