github diegosouzapw/OmniRoute v3.8.20

7 hours ago

✨ New Features

  • feat(providers): add Claude Fable 5 (claude-fable-5) — wires the new flagship model across the full pipeline: cc and kiro provider registries (1M context, 128k output), pricing at $15/$75 per 1M tokens, model spec (adaptive thinking, vision, tool use), fast mode, 1M-context beta header, fallback chain (claude-fable-5 → claude-opus-4-8 → claude-opus-4-7 → claude-sonnet-4-6), and cost data. (#3524 — thanks @ggiak)
  • feat(resilience): add global provider cooldown tracking to prevent combo re-walking — after a provider fails in a combo request, subsequent requests skip it for a configurable exponential backoff (default 5s min, 5min max, doubling per failure), reducing wasted time on known-failing providers. Configurable and opt-out via Settings → Resilience. (#3556 — thanks @pizzav-xyz)
  • feat(resilience): expose provider breaker degradation threshold setting — the consecutive-failure count before a provider enters the DEGRADED state is now configurable in Settings → Resilience alongside the existing open/half-open thresholds. (#3535 — thanks @rdself)

🔧 Bug Fixes

  • fix(translator): scope the Gemini thoughtSignature bypass to the Antigravity/CLI path and unwrap array-shaped Gemini error bodies — signature-less historical tool calls on Antigravity/CLI are emitted as native parts carrying the skip_thought_signature_validator sentinel (preventing upstream 400s), while the standard Gemini direct path keeps its existing text/context representation untouched. (#3560 — thanks @oyi77 and @Six7Day via #3414)

  • fix(routing): combo model substitution no longer forwards a client thinking:{type:"disabled"} to a target model that rejects it — when a combo/route swaps the upstream model (e.g. claude-opus-4-8claude-fable-5), OmniRoute now strips the now-invalid thinking.type:"disabled" for models flagged rejectsThinkingDisabled (Fable 5 defaults to adaptive and rejects it), preventing the upstream 400 that silently broke Claude Code's internal title/name-generation calls. Models that accept disabled (opus/sonnet) are untouched. (#3554)

  • fix(usage): the budget dashboard can now save a budget with some limit fields left empty and clear all limits — setBudgetSchema used .positive() (rejecting the 0 the form sends for blank fields) plus a superRefine requiring at least one limit > 0, so saving with one field filled 400'd and clearing all limits was impossible. Limits now accept 0 (= "no limit for this period"; enforcement only kicks in above 0) and the cross-field minimum was removed; negatives are still rejected. (#3537)

  • fix(gamification): badge-unlock events no longer re-fire on every request — the "already unlocked?" guard used getBadges(), which INNER-JOINs badge_definitions (empty until seeded), so it always reported "not earned" and re-emitted events.badge_unlocked per request. Added a hasBadge() helper that reads user_badges directly, so dedup is correct regardless of whether definitions are seeded. (#3472)

  • fix(routing): the auto model keyword now works on the Codex /v1/responses path — resolveResponsesApiModel rewrote the bare auto keyword to codex/auto, which ChatGPT rejects (The 'auto' model is not supported when using Codex with a ChatGPT account). auto (OmniRoute's zero-config auto-routing keyword) now passes through untouched so combo routing handles it. (#3509)

  • fix(cli-tools): saving the OpenCode/CLI tool config no longer 400s in cloud mode — every CLI tool card posts apiKey: null (the real key is resolved server-side from keyId), but guideSettingsSaveSchema used z.string().optional(), which rejects null. The schema now normalizes nullundefined, so the save succeeds and the keyId/default path is used. (#3552)

  • fix(catalog): PublicAI is no longer miscatalogued as keyless/free — it requires an API key (registry authType:"apikey"; signup grants a one-time credit, then it bills). The three PublicAI models moved from freeType:"keyless" (which could pick them into the no-auth pool and dispatch with no Authorization header) to "one-time-initial", and the provider's hasFree flag is now false — matching freeTierCatalog.ts, which already excluded publicai. (#3558)

  • fix(gemini-web): a missing Playwright Chromium browser no longer loops and trips the provider breaker — when the browser binary is not installed, chromium.launch() threw an error surfaced as a retryable 500, so accountFallback marked the account unavailable and retry-looped. It is now classified as a host/config problem and returns 503 with an actionable message (npx playwright install chromium) and the X-Omni-Fallback-Hint: connection_cooldown header, which skips the provider circuit breaker and applies a short non-exponential cooldown. (#3516)

  • fix(proxy): the SOCKS5 proxy option now follows the runtime ENABLE_SOCKS5_PROXY env instead of the build-time NEXT_PUBLIC_ENABLE_SOCKS5_PROXY — Next.js inlines NEXT_PUBLIC_* at build time, so a prebuilt Docker image ignored a runtime setting and the SOCKS5 type stayed hidden. The proxy modal now reads socks5Enabled from GET /api/settings/proxies (server-side ENABLE_SOCKS5_PROXY), with the build-time value kept only as a static-deploy fallback. (#3508)

  • fix(playground): the playground model selector now lists models from custom-endpoint (OpenAI/Anthropic-compatible) providers — it filtered /v1/models by the provider's connection id, but the catalog emits compatible-provider models under the node's custom prefix (prefix/model), so the list came up empty ("None"/"-"). The selector now filters by the node prefix (exposed additively as modelPrefix on provider options; the connection id is unchanged, so translator send/translate and connection lookups are unaffected). (#3505)

  • fix(usage): the Kiro quota card no longer renders a blank when the account returns no usage breakdown — getKiroUsage returned quotas:{} for a successful GetUsageLimits response without a usageBreakdownList (observed with some AWS IAM / Builder ID accounts), which the dashboard showed as an unexplained empty card. It now returns an informative message (surfaced via the card's connection-message path). (#3506)

  • fix(security): route raw err.message through sanitizeErrorMessage() in five web executors (adapta-web, deepseek-web, perplexity-web, qoder, veoaifree-web) and the embeddings + search handlers (Hard Rule #12) — these built error response bodies from the raw upstream/exception message, which could leak internal detail. (#3494, #3495)

  • fix(dashboard): correct two dashboard fetches that hit non-existent routes (404) — CustomHostsManager called /api/tools/traffic-inspector/custom-hosts (the real route is /hosts), and FeatureFlagsGrid's post-restart liveness probe called /api/health (the real lightweight endpoint is /api/health/ping). (#3486, #3487)

  • chore(providers): remove the dead krutrim registry entry — it was half-registered (present in providerRegistry.ts with a baseUrl + one model, but absent from providers.ts, with no executor/translator/OAuth), so it was never selectable. Dropped its ProviderIcon entry and the KNOWN_REGISTRY_ONLY exception. (#3483)

  • docs(api): fix the agent-bridge per-agent state route in openapi.yaml and AGENTBRIDGE.md — both documented /api/tools/agent-bridge/agents/{id}/state, which has no route; corrected to the real per-agent /api/tools/agent-bridge/agents/{id} (global state remains /api/tools/agent-bridge/state). (#3489)

  • docs(api): correct API_REFERENCE.md endpoints that documented non-existent routes — skills (PUT /api/skills/[id], POST/GET /api/skills/executions), plugins ([id][name], activate/deactivate), ACP (DELETE/POST /api/acp/agents via ?id/{action:"refresh"}), cache (DELETE /api/cache/reasoning, /api/cache/entries), and removed the fabricated /api/admin/circuit-breaker, /api/admin/rate-limits, and /api/system-info (admin only exposes /concurrency). (#3497)

  • fix(executor): strip provider prefix from versioned built-in tool model field — Anthropic rejects tools[N].model: "cc/claude-opus-4-8" from Claude Code's advisor_20260301 and similar versioned built-in tools; the native Claude OAuth execute path now strips any provider prefix from model on tools whose name matches name_YYYYMMDD. (#3532 — thanks @ggiak)

  • fix(dashboard): handle DEGRADED and unknown provider breaker states on the Runtime page — an unrecognised breaker state (e.g. DEGRADED) caused a crash because the styling map had no entry for it; now falls back to a neutral style so the page never throws on unknown states. (#3533 — thanks @rdself)

  • fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 — the quota API rejects chat API keys with a JSON-401 body even though the same key works for chat; previously this threw and crashed the dashboard with a red error banner. It now returns an informative message and keeps rendering like other connection-message cases. (#3522 — thanks @wilsonicdev)

  • fix(translator): map the Codex local_shell tool type — local_shell was absent from the translator's tool-type map, causing it to fall through as an unknown type; it is now forwarded correctly to the upstream. (#3534 — thanks @kamaka)

  • fix(images): prefer bare combo names over built-in image model aliases — a user combo named gpt-image-2 can now shadow the native OpenAI alias so image requests route through the combo; provider-qualified IDs like openai/gpt-image-2 still resolve via the built-in path. (#3527 — thanks @AveryanAlex)

  • fix(translator): fix OpenAI→Gemini translation of historical tool calls — tool results from earlier turns were being converted to text, causing Gemini to pattern-match the response as prose rather than structured content; they now use the native Gemini functionResponse part format. (#3569 — thanks @hartmark)

  • fix(plugins): forward plugin lifecycle hooks (onInstall, onActivate, onDeactivate, onUninstall) via IPC and wrap onDeactivate/onUninstall in try/catch so a buggy plugin handler can no longer brick teardown; also removes redundant RegExp() wrappers in accountFallback.ts and fixes indentation in requestLogger.ts. (#3562 — thanks @oyi77)

  • fix(auto-update): use a stable PROJECT_ROOT walker instead of frozen process.cwd()resolveProjectRoot now walks up from __dirname to find the nearest directory containing package.json or .git (bounded at 16 levels), preventing ENOENT errors when the working directory is not the project root. (#3561 — thanks @oyi77 / @ViFigueiredo via #3423)

  • fix(resilience): expose providerCooldown in GET /api/resilience and accept it in PATCH — PR #3556 added the global provider cooldown tracker to the settings model and ResilienceTab UI, but the API route never returned the field (causing a crash when the tab loaded) and updateResilienceSchema (.strict()) rejected PATCH bodies containing it with 400. providerCooldownSettingsSchema is now wired into the Zod schema, returned in the GET response, and merged in the PATCH handler. Caught during v3.8.20 VPS homologation (Hard Rule #18 — 5 TDD tests). (#3590 — thanks @diegosouzapw)


🙌 Contributors

Thanks to all who contributed to this release:
@ggiak · @pizzav-xyz · @rdself · @hartmark · @oyi77 · @Six7Day · @ViFigueiredo · @wilsonicdev · @kamaka · @AveryanAlex · @diegosouzapw

What's Changed

Full Changelog: v3.8.19...v3.8.20

Don't miss a new OmniRoute release

NewReleases is sending notifications on new releases.