Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fa6fd64
fix(claude): restore canonical tool names so Claude Code accepts tool…
Aug 22, 2026
7846dbc
docs(changelog): fragment for #11085 claude code tool name casing
Aug 22, 2026
614a0e5
fix(db): add missing @/lib/db/connections module breaking dashboard b…
Aug 21, 2026
ae96cae
chore(sse): drop temporary canary logging from response translator
Aug 22, 2026
52af7f5
fix(claude): keep canonical tool casing when upstream echoes TitleCas…
Aug 22, 2026
742ccb9
fix(ci): register oauth-autoimport-local-only test in stryker tap.tes…
hartmark Aug 22, 2026
9629693
fix(providers): filter Perplexity model import to the Sonar family (#…
diegosouzapw Aug 22, 2026
d91238b
fix(install): make ONNX chain optional for Android/Termux installs (#…
diegosouzapw Aug 22, 2026
da490a7
fix(sse): resume stream recovery after a completed tool call (#11109)
maxmad64bis Aug 22, 2026
d9b3ce2
test(stream): direct coverage + comment for splitConcatenatedToolCall…
maxmad64bis Aug 22, 2026
02a078e
fix(sse): default summary on freshly-built Chat->Responses reasoning …
maxmad64bis Aug 22, 2026
b44f22a
fix(webhooks): followup ghost-event dispatcher tests + vi i18n (#1105…
maxmad64bis Aug 22, 2026
efc7134
fix(registry): restore models[0] default + guards; note muse-spark ov…
maxmad64bis Aug 22, 2026
78b4082
fix(autopilot): show real suggestion count and link dashboard (#11102)
maxmad64bis Aug 22, 2026
d021423
fix(sse): default summary + strip malformed id on kept Responses inpu…
maxmad64bis Aug 22, 2026
f3b190b
fix(providers): reject silent validation degradation with 400 and rej…
maxmad64bis Aug 22, 2026
84c9dfd
fix(config-audit): persist config audit log to SQLite with retention …
maxmad64bis Aug 22, 2026
c89fc6b
feat(providers): let operators add per-provider error rules via setti…
maxmad64bis Aug 22, 2026
1dd0173
fix(sse): assign a stable index/id to parallel function_call items in…
maxmad64bis Aug 22, 2026
5631e91
fix(providers): learn reasoning_effort capability from upstream 4xx i…
maxmad64bis Aug 22, 2026
dae3a72
fix(i18n): translate providers.harImport* keys into Vietnamese (#1106…
hartmark Aug 22, 2026
a51b8ba
fix(cline): use valid modelType/model format for Cline provider model…
rqzbeh Aug 22, 2026
3157e8a
fix(providers): require API key for Pollinations and fix optional key…
rqzbeh Aug 22, 2026
9a67185
fix(cli): support OpenCode V2 config format in setup-opencode (#11070…
rqzbeh Aug 22, 2026
50fc0d7
fix(resilience): keep Ollama model-not-found failures scoped to conne…
rqzbeh Aug 22, 2026
4220c81
fix(resilience): include RFC1918, CGNAT, and mDNS hosts in isLocalPro…
rqzbeh Aug 22, 2026
367ae2f
fix(search): enforce blockedProviders setting on search endpoint (#11…
rqzbeh Aug 22, 2026
b2509bc
fix(release): drain v3.8.50 base-reds — getTokenLimit contract, vi i1…
diegosouzapw Aug 22, 2026
aa12873
fix(providers): remove Hack Club AI provider (#11118) (#11123)
rqzbeh Aug 22, 2026
56540f2
fix(api): save call logs and add endpoint fallback for local rerank p…
AndrianBalanescu Aug 22, 2026
80d931a
fix(logs): apply filter predicates to merged in-memory call-log rows …
AndrianBalanescu Aug 22, 2026
e15af18
fix(kimi): point kimi-web to international www.kimi.ai (#11045)
MeRezaRezaei Aug 22, 2026
7360ca4
feat(redis): add configurable key namespace prefix (#11042)
MeRezaRezaei Aug 22, 2026
445121f
docs: DEFAULT_RATE_LIMIT_PER_DAY unset is unlimited (#11031)
Prajeeth-12 Aug 22, 2026
c73d8d0
board #11085 (utilization route: keep tip shape)
hartmark Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ DISABLE_SQLITE_AUTO_BACKUP=false
# Used by: src/shared/utils/rateLimiter.ts
# Example: redis://localhost:6379 (or redis://redis:6379 in Docker)
# REDIS_URL=redis://localhost:6379
# Namespace prefix for ALL OmniRoute Redis keys (rate limiter + auth cache +
# quota store). Prevents key collisions when OmniRoute shares a Redis instance
# with other apps (e.g. on 127.0.0.1:6379). Default when unset: omniroute:
# REDIS_KEY_PREFIX=omniroute:
# Host interface docker-compose publishes the Redis sidecar on.
# Default: 127.0.0.1 (loopback only). The compose Redis runs WITHOUT
# `requirepass`, and app containers reach it over the compose network
Expand Down Expand Up @@ -372,9 +376,8 @@ ALLOW_API_KEY_REVEAL=false
# NO_LOG_API_KEY_IDS=key_abc123,key_def456

# Fallback per-day request budget applied to API keys whose `rate_limits`
# column is null. Default (unset/empty/malformed) preserves the legacy
# 1000/day, 5000/week, 20000/month windows so existing deployments do not
# silently lose rate limiting on upgrade.
# column is null. Default (unset/empty) is unlimited (no implicit caps).
# Malformed values preserve the legacy 1000/day, 5000/week, 20000/month windows.
# Set explicitly to "0" to opt out entirely (unlimited fallback). Any
# positive integer N enables N/day, 5N/week, 20N/month.
# Used by: src/shared/utils/apiKeyPolicy.ts — checkRateLimit() fallback.
Expand Down
1 change: 1 addition & 0 deletions changelog.d/features/11104-operator-error-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(providers):** let operators declare per-provider error rules through `settings.providerErrorRules` instead of patching the catalog — an operator-supplied rule for a provider is consulted before the built-in `providerRuleRegistry`, receives the raw error text, and has its declared scope/cooldown/reason actually honored end to end, for any provider (declaring the rule is the opt-in — no extra allowlist entry needed). Matches are plain case-insensitive substrings (never RegExp) and bounded to 50 rules to keep the hot path safe ([#11104](https://github.com/diegosouzapw/OmniRoute/pull/11104))
1 change: 1 addition & 0 deletions changelog.d/fixes/11060-perplexity-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(providers): filter Perplexity model import to the Sonar family so Agent-API catalog ids stop surfacing as routable chat models (#11060)
1 change: 1 addition & 0 deletions changelog.d/fixes/11085-claude-code-tool-name-casing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(claude):** restore canonical tool names (`bash` → `Bash`, `croncreate` → `CronCreate`) on non-streaming OpenAI→Claude conversion and through identity-echo alias maps, so Claude Code stops rejecting tool calls with "No such tool available" ([#11085](https://github.com/diegosouzapw/OmniRoute/pull/11085)) — thanks @linhdmn
1 change: 1 addition & 0 deletions changelog.d/fixes/11095-termux-onnx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(install): make the ONNX dependency chain optional so Termux/Android installs succeed again (#11095)
1 change: 1 addition & 0 deletions changelog.d/fixes/11101-reject-silent-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(providers):** Reject silent validation degradation on provider connection patch — unknown `rateLimitOverrides` keys (e.g. a typo'd `tpm`) and empty/non-numeric values now return `400` with the rejected key list instead of being silently dropped ([#11101](https://github.com/diegosouzapw/OmniRoute/pull/11101))
1 change: 1 addition & 0 deletions changelog.d/fixes/11102-combo-suggestion-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Autopilot suggestion counter:** the combo health autopilot summary now reports `suggestionCount` (the real number of suggested actions across all issues) instead of conflating it with link counts, while keeping `actionableCount` as a deprecated alias for backward compatibility. The `run_combo_test` action now links to the dashboard with the combo id (`/dashboard/combos?test=<comboId>`) rather than the read-only API route, so operators can actually trigger a test from the UI ([#11102](https://github.com/diegosouzapw/OmniRoute/pull/11102)).
1 change: 1 addition & 0 deletions changelog.d/fixes/11103-persist-config-audit-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Config audit persistence:** persist the configuration audit trail to SQLite (`config_audit_log`) instead of an in-memory buffer capped at 1000 volatile entries, and bound its growth with `cleanupConfigAudit()` driven by the `retention.configAudit` setting (default 30 days), wired into `runAutoCleanup` ([#11103](https://github.com/diegosouzapw/OmniRoute/pull/11103)).
1 change: 1 addition & 0 deletions changelog.d/fixes/11109-stream-recovery-toolcall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(sse): resume mid-stream recovery after a _completed_ tool call — `finish_reason: "tool_calls"` is now tracked per-call instead of as a general terminal marker, so truncation of trailing prose after a fully-delivered tool call is recoverable while in-flight calls stay blocked ([#11109](https://github.com/diegosouzapw/OmniRoute/pull/11109))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(providers):** `reasoning_effort` now learns the accepted values from a provider's own 400/422 response and clamps to the highest one instead of forwarding an unsupported `xhigh`/`max` (or a hardcoded `"high"` fallback) — fixes custom OpenAI-compatible connections and registered providers with no reasoning metadata ([#11116](https://github.com/diegosouzapw/OmniRoute/pull/11116)) — thanks @maxmad64bis
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(sse):** parallel `function_call` items in a Responses API stream (e.g. several tool calls dispatched in the same turn) now each get a stable, distinct `index`/`id` when translated to Chat Completions streaming deltas, instead of colliding on index 0 and tripping strict stream parsers with `Expected 'id' to be a string.` ([#11144](https://github.com/diegosouzapw/OmniRoute/pull/11144))
1 change: 1 addition & 0 deletions changelog.d/fixes/release-v3850-basereds-tests-i18n.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(i18n): complete Vietnamese translations for recently added UI strings (#9985)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(quality): register `tests/unit/authz/oauth-autoimport-local-only.test.ts` in stryker `tap.testFiles` (residual of #11053)
1 change: 1 addition & 0 deletions changelog.d/maintenance/vi-harimport-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(i18n): translate the 14 `providers.harImport*` keys into Vietnamese (parity gap left by #11069)
41 changes: 33 additions & 8 deletions docs/architecture/RESILIENCE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,14 +448,14 @@ classification rules pick the fallback `reason` and lock `scope`
Classification rules only see full error **text** (needed to match body
markers like `额度不足`) for providers listed in the `FULL_TEXT_RULE_PROVIDERS`
allowlist in `providerErrorRules.ts` — currently only `"agentrouter"`. For
every other provider, `checkFallbackError` hands `getProviderErrorRuleMatch`
only the structured error (`{code, type}`), which is enough for
header/status/code-based rules but blind to body-text markers. The helper
`resolveRuleMatchBody()` performs this selection: full error text for
allowlisted providers, the structured error otherwise. Adding a provider to
`FULL_TEXT_RULE_PROVIDERS` is an explicit per-provider opt-in — it exists so
that the default path for every provider not on the list stays
byte-for-byte unchanged.
every other **built-in catalog** provider, `checkFallbackError` hands
`getProviderErrorRuleMatch` only the structured error (`{code, type}`), which
is enough for header/status/code-based rules but blind to body-text markers.
The helper `resolveRuleMatchBody()` performs this selection: full error text
for allowlisted providers, the structured error otherwise. Adding a
**built-in** provider to `FULL_TEXT_RULE_PROVIDERS` is an explicit per-provider
opt-in — it exists so that the default path for every provider not on the
list stays byte-for-byte unchanged.

A rule's `scope` (`model` / `provider` / `connection`) is a separate opt-in
from `FULL_TEXT_RULE_PROVIDERS`: `checkFallbackError` only surfaces it as
Expand All @@ -466,6 +466,31 @@ honorsRuleLockScope()` — today only `"agentrouter"`). See "Restated quota
errors" above for what a `scope: "connection"` match actually does once a
provider is on that allowlist.

**#11104 — operator-declared rules bypass both allowlists.** An operator can
declare a per-provider rule at runtime via `settings.providerErrorRules`
(`open-sse/config/providerErrorRules.ts::setOperatorProviderErrorRules`)
without editing this file. Gating an operator rule behind
`FULL_TEXT_RULE_PROVIDERS`/`HONORS_RULE_LOCK_SCOPE_PROVIDERS` — allowlists
meant to protect the **default** behavior of built-in catalog rules — would
make the settings mechanism inert for every provider except the ones already
listed there, since declaring the rule is already the operator's explicit
opt-in. `resolveRuleMatchBody()` and `honorsRuleLockScope()` both check
`hasOperatorRuleForProvider()` first: a provider with an operator rule gets
the raw error text and has its declared `scope` honored, regardless of
whether it also appears in either allowlist.

**Known gap — `providerRuleRegistry` is never consulted for HTTP 400.**
`checkFallbackError`'s `BAD_REQUEST` branch classifies status 400 entirely
through its own pattern arrays (`MODEL_ACCESS_DENIED_PATTERNS`,
`CONTEXT_OVERFLOW_PATTERNS`, etc. in `accountFallback.ts`) and returns before
the `configuredRule`/`getProviderErrorRuleMatch` branch above it is reached.
A built-in catalog rule (or an operator rule) with `status: 400` is
syntactically valid but will never fire. No existing rule targets 400 today,
so nothing in production is affected — but a future 400 rule needs this
branch touched first, which is a larger change than adding a rule (it
reclassifies 400 for every provider already relying on the pattern-array
behavior) and is out of scope for a single-provider rule addition.

### Adding a new quota-misstating gateway

1. Register one rule array in `statusRestatementRegistry`
Expand Down
33 changes: 30 additions & 3 deletions docs/ops/REDIS_PRODUCTION_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ workloads:

| Workload | Driver | Client Factory | Key Pattern |
|---|---|---|---|
| Rate limiting | `rateLimiter.ts` | `getRedisClient()` — lazy `ioredis` singleton | Lua‑atomic rate limit windows |
| Auth cache | `apiKeys.ts` | Reuses `rateLimiter`'s client | `auth:api_key:<sha256>` with TTL |
| Quota store | `redisQuotaStore.ts` | Separate `getRedisClient(url)` singleton | Configurable per-instance |
| Rate limiting | `rateLimiter.ts` | `getRedisClient()` — lazy `ioredis` singleton | `<prefix>rl:*` Lua‑atomic rate limit windows |
| Auth cache | `apiKeys.ts` | Reuses `rateLimiter`'s client | `<prefix>auth:api_key:<sha256>` with TTL |
| Quota store | `redisQuotaStore.ts` | Separate `getRedisClient(url)` singleton | `<prefix>quota:*` configurable per-instance |

All three workloads share one namespace prefix so OmniRoute can co-exist with other apps on a
single Redis instance (e.g. `127.0.0.1:6379`). See [Key Namespacing](#key-namespacing).

---

Expand All @@ -25,6 +28,7 @@ workloads:
| Setting | Value | Where |
|---|---|---|
| `REDIS_URL` env var | `redis://redis:6379` (compose), optional | `rateLimiter.ts:5`, `.env.example` |
| `REDIS_KEY_PREFIX` env var | `omniroute:` (default) | `rateLimiter.ts`, `redisQuotaStore.ts`, `.env.example` |
| `QUOTA_STORE_REDIS_URL` env var | separate, can differ from `REDIS_URL` | `quota/storeFactory.ts` |
| `QUOTA_STORE_DRIVER` | `"sqlite"` (default), `"redis"` optional | `quota/storeFactory.ts` |
| ioredis `maxRetriesPerRequest` | `3` | `rateLimiter.ts` client creation |
Expand All @@ -36,6 +40,29 @@ workloads:

---

## Key Namespacing

OmniRoute shares a Redis instance with whatever else runs on the host. Without a namespace,
keys like `auth:api_key:<sha256>` or `rl:*` could collide with keys from other applications
using the same Redis (this instance runs Redis on `127.0.0.1:6379` alongside other services).

Set `REDIS_KEY_PREFIX` to a non-empty string to prefix **every** OmniRoute key:

```bash
# .env — all OmniRoute keys become omniroute:rl:*, omniroute:auth:*, omniroute:quota:*
REDIS_KEY_PREFIX=omniroute:
```

- **Default:** `omniroute:` (applied when `REDIS_KEY_PREFIX` is unset or blank).
- **Applied to:** rate limiter + auth cache (shared `ioredis` client via `keyPrefix`) and the
quota store (`KEY_PREFIX = "${REDIS_KEY_PREFIX}quota"`).
- **Changing the prefix** when keys already exist in Redis orphans the old keys (they expire
via TTL / LRU). Safe to change; no migration needed.
- **ioredis `keyPrefix`** automatically prepends the prefix on writes **and** strips it on reads,
so application code never sees the prefix.

---

## Recommended Production Tuning

### 1. Connection Pool / Client Options (ioredis `Redis` constructor)
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,7 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `OMNIROUTE_REDIS_BIND_HOST` | `127.0.0.1` | `bin/cli/commands/redis.mjs` | Host interface the 1-click Redis launcher publishes on. The launcher starts Redis WITHOUT a password, so binding `0.0.0.0` hands every host on your LAN an unauthenticated Redis — only widen this if you also set a password on the instance yourself. |
| `REDIS_BIND_HOST` | `127.0.0.1` | `docker-compose.yml` | Host interface docker-compose publishes the Redis sidecar on (#9286). The compose Redis runs without `requirepass`; app containers reach it over the compose network (`redis:6379`) — the published port exists only for host-side tooling. `0.0.0.0` exposes an unauthenticated Redis to the whole LAN. |
| `REDIS_PORT` | `6379` | `docker-compose.yml` | Host port for the compose Redis sidecar. |
| `REDIS_KEY_PREFIX` | `omniroute:` | `src/shared/utils/rateLimiter.ts` | Namespace prefix applied to every OmniRoute Redis key (rate limiter, auth cache, quota store). Prevents key collisions when the Redis instance is shared with other apps (#11042). |
| `OMNIROUTE_INTERNAL_SERVICE_TOKEN` | _(unset — mechanism disabled)_ | `src/lib/api/internalServiceAuth.ts` | Shared secret for identity-preserving internal REST hops (#9260): OmniRoute components calling other local OmniRoute routes send it as `x-omniroute-internal-service-token` so the original caller identity is preserved. Compared with `timingSafeEqual`. |
| `OMNIROUTE_INTERNAL_SERVICE_TOKEN_FILE` | _(unset)_ | `src/lib/api/internalServiceAuth.ts` | Secret-file variant of the internal service token: path to a file whose trimmed content is the token. Only consulted when the inline var is unset. |
| `OPENROUTER_PROVIDER_STATS_ENABLED` | `true` | `src/lib/catalog/openrouterProviderStats.ts` | Enrich the dashboard providers list with OpenRouter weekly ranking stats (#9324). On by default; set `false` to skip the background fetch entirely (non-blocking, never fatal). |
Expand Down
3 changes: 0 additions & 3 deletions docs/reference/FREE_TIERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve
| `firecrawl` | caution | Cloud API ToS has no explicit personal-proxy prohibition found, but the open-source self-hosted version is AGPL-3.0 (re… |
| `gemini` | caution | ToS explicitly states the free tier is for "developers building with Google AI models for professional or business purp… |
| `groq` | caution | Services Agreement §6.3 prohibits reselling, sublicensing, or distributing API access; §3.2 bars reselling/leasing acco… |
| `hackclub` | caution | Service is explicitly scoped to Hack Club teen members building projects/learning; no public ToS found explicitly permi… |
| `huggingchat` | caution | Hugging Face ToS does not explicitly ban personal self-hosted proxies, but supplemental terms (referenced but not fully… |
| `huggingface` | caution | ToS grants a limited license to access/use the service; the document does not explicitly permit or forbid a single-user… |
| `hyperbolic` | caution | ToS grants API access "solely for your own personal or internal business purposes" and explicitly prohibits licensing, … |
Expand Down Expand Up @@ -222,7 +221,6 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve
| `duckduckgo-web` | keyless | — | — | avoid | 6 |
| `freemodel-dev` | keyless | — | — | unknown | 4 |
| `friendliai` | keyless | — | — | avoid | 2 |
| `hackclub` | keyless | — | — | caution | 3 |
| `iflytek` | keyless | — | — | avoid | 1 |
| `inference-net` | keyless | — | — | caution | 3 |
| `liquid` | keyless | — | — | unknown | 1 |
Expand Down Expand Up @@ -280,7 +278,6 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve
- **`gitlawb`** — The shipped freeNote "Free tier available" is effectively stale. The original free MiMo access was removed in May 2026; the only remaining "free" option is a temporary promotional model (Nemotron 3 U…
- **`gitlawb-gmi`** — Partially still accurate — free tier exists but is now narrowed to a single model (Nemotron 3 Ultra) after MiMo free access was revoked in late May 2026. The shipped note "Free tier available" unders…
- **`groq`** — The shipped freeNote "30 RPM / 14.4K RPD" is accurate only for llama-3.1-8b-instant. Most other models (including llama-3.3-70b-versatile) have a much lower 1K RPD cap. The note omits model-specific …
- **`hackclub`** — The "30+ models" count appears accurate and still matches. The core offering remains free for Hack Club members. No evidence of tightening — still "$0 ALWAYS FREE" per the homepage. The freeNote omit…
- **`huggingchat`** — The shipped freeNote ("Free LLM chat — no subscription required. Rate limits apply.") is partially accurate but significantly understates the restrictions. The free tier now operates on a hard $0.10/…
- **`huggingface`** — Significantly tightened. The shipped freeNote ("Free Inference API for thousands of models") implied unlimited/generous free access, but as of mid-2025 the free tier is capped at $0.10/month in recur…
- **`hyperbolic`** — Our shipped freeNote says "$1-5 trial credits on signup" — the $1 trial credit portion is accurate, but the "$5" figure refers to the minimum deposit required to unlock GPU rental (not free credits g…
Expand Down
Loading
Loading