* chore(release): open v3.8.20 development cycle
* fix(images): prefer bare combos over image aliases (#3527)
Integrated into release/v3.8.20
* fix(translator): map Codex local_shell tool (#3534)
Integrated into release/v3.8.20
* fix(usage): make opencode-go quota fetcher fail-open instead of throwing 500 (#3522)
Integrated into release/v3.8.20
* Fix Runtime page breaker state rendering (#3533)
Integrated into release/v3.8.20
* Expose provider breaker degradation threshold setting (#3535)
Integrated into release/v3.8.20
* fix(executor): strip provider prefix from versioned built-in tool model field (#3532)
Integrated into release/v3.8.20
* feat(providers): add Claude Fable 5 support (#3524)
Integrated into release/v3.8.20
* feat(resilience): add global provider cooldown tracking to prevent combo re-walking (#3556)
Integrated into release/v3.8.20 (default OFF, opt-in)
* fix(translator): scope thoughtSignature bypass to Antigravity/CLI only (#3560)
Integrated into release/v3.8.20. Co-authored-by: Six7Day <six7day@gmail.com>
* fix(routing): normalize thinking:disabled for combo-substituted models that reject it (#3554) (#3563)
Integrated into release/v3.8.20
* fix(usage): accept 0/empty budget limits so the dashboard can save and clear (#3537) (#3564)
Integrated into release/v3.8.20
* docs(changelog): credit @Six7Day for #3560 thoughtSignature fix (#3414)
The #3560 squash co-author trailer landed inline (unparsed by GitHub), so add
an explicit CHANGELOG credit ensuring @Six7Day (original #3414) and @oyi77 are
on the public record for the Gemini thoughtSignature fix.
* fix(gamification): dedup badge unlock via user_badges so events don't re-fire every request (#3472) (#3565)
Integrated into release/v3.8.20
* fix(routing): pass through 'auto' keyword on codex /v1/responses instead of rewriting to codex/auto (#3509) (#3566)
Integrated into release/v3.8.20
* fix(cli-tools): normalize apiKey null in guide-settings schema so cloud-mode config saves (#3552) (#3567)
Integrated into release/v3.8.20
* fix(catalog): reclassify PublicAI from keyless to one-time-initial (requires API key) (#3558) (#3568)
Integrated into release/v3.8.20
* fix(gemini-web): surface missing Playwright browser as actionable 503 + cooldown hint, not a retryable 500 loop (#3516) (#3570)
Integrated into release/v3.8.20
* fix(security): sanitize raw err.message in web executors + embeddings/search response bodies (Rule #12) (#3494, #3495) (#3573)
Integrated into release/v3.8.20
* fix(dashboard): point CustomHostsManager + FeatureFlagsGrid at real routes (#3486, #3487) (#3574)
Integrated into release/v3.8.20
* chore(providers): remove dead krutrim entry (#3483) + docs(api): fix agent-bridge per-agent state route (#3489) (#3575)
Integrated into release/v3.8.20
* docs(api): correct API_REFERENCE.md paths for skills/plugins/admin/cache/acp/system-info (#3497) (#3577)
Integrated into release/v3.8.20
* fix(proxy): drive SOCKS5 UI option from runtime ENABLE_SOCKS5_PROXY, not build-time NEXT_PUBLIC (#3508) (#3579)
Integrated into release/v3.8.20
* fix(playground): filter playground models by node prefix so custom-endpoint models appear (#3505) (#3581)
Integrated into release/v3.8.20
* fix(usage): show an informative message instead of a blank Kiro quota card when no usage breakdown (#3506) (#3582)
Integrated into release/v3.8.20
* docs(changelog): add the #3506 Kiro quota entry (missed in #3582 due to a stale-base CHANGELOG anchor) (#3583)
Integrated into release/v3.8.20
* fix(auto-update): use stable PROJECT_ROOT walker, not frozen process.cwd() (#3561)
Integrated into release/v3.8.20. Auto-update PROJECT_ROOT now uses a stable __dirname-anchored upward walker instead of the no-op process.cwd() resolver.
* fix: address PR #3518 review comments (lifecycle hooks, regex, indentation, route params) (#3562)
Integrated into release/v3.8.20. Addresses #3518 review: regex literals, logs/[id] route params (Next 16), indentation, and wires plugin lifecycle hooks (onInstall/onActivate/onDeactivate/onUninstall) in the loader so manager.ts can register them. Adds Rule #18 regression test.
* docs(changelog): credit @ViFigueiredo (#3423) for PROJECT_ROOT + log #3561/#3562 (v3.8.20)
* fix: openai to gemini incorrectly translates historical tool calls into text (#3569)
Integrated into release/v3.8.20. Standard Gemini direct path now maps historical tool calls to native functionCall/functionResponse parts (signaturelessToolCallMode: native) instead of inert text — validated against the real Gemini API (gemini-2.5-flash returns 200 for signatureless native functionCall, even with tools+thinking; Hard Rule #18). Eliminates the text-serialization leak. Antigravity/CLI sentinel path (#3560) untouched.
* docs(changelog)+test: reconcile standard-Gemini native mode (#3569) — update round-2 rationale comment + log VPS validation
* docs(changelog): reconcile v3.8.20 — add 9 missing bullets + move [Unreleased] to versioned section
* docs(changelog): complete v3.8.20 reconciliation — 27 bullets, 11 contributors
---------
Co-authored-by: Alexander Averyanov <alex@averyan.ru>
Co-authored-by: Hakan Kurşun <bykamaka@gmail.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
* chore(release): open v3.8.18 development cycle
* fix(catalog): stop Codex CLI model-catalog refresh from erroring (#3481)
Codex's model-catalog refresh (codex_models_manager) does
GET /v1/models?client_version=<v> and decodes a JSON object with a
TOP-LEVEL `models` array. OmniRoute answers in the OpenAI-standard
`{object,data}` shape, so codex fails with "missing field `models`"
and logs "failed to refresh available models" on every startup.
Detect codex clients via the `originator` / `user-agent` = `codex_*`
headers they send and add an EMPTY top-level `models: []` so the decode
succeeds. Non-codex OpenAI clients keep the byte-identical `{object,data}`
response.
The array is intentionally empty: codex replaces its built-in per-model
agent prompt (`base_instructions`, ~21k chars) with whatever a populated
entry carries for the selected model, so emitting our catalog would drop
the agent prompt to nothing and break codex's agent behaviour (verified
empirically against codex 0.137). An empty list keeps codex on its
built-in model info — same inference as before, minus the error.
Validated end-to-end with the real handler against codex 0.137:
"failed to refresh available models" → 0 occurrences, instructions
preserved (built-in Codex agent prompt, not empty).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: ignore quality reports and local prompt artifacts
Add generated quality gate reports, metrics files, and local setup prompt
artifacts to .gitignore to prevent committing environment-specific or
temporary files.
* fix(provider): detect Responses API format when body has `input` but … (#3490)
Integrated into release/v3.8.18
* fix(sse): normalize numeric provider ids to strings (#3451)
Integrated into release/v3.8.18
* feat(browserPool): resolve Playwright proxy from proxy_registry DB (#3492)
Integrated into release/v3.8.18
* fix(theoldllm): generate X-Request-Token server-side, drop Playwright (#3491)
Integrated into release/v3.8.18
* feat(plugins): add lifecycle hooks and theme-manager plugin (#3473)
Integrated into release/v3.8.18
* fix(combo): parallel pre-screen + circuit-breaker fast-exit for priority combos (#3169)
Integrated into release/v3.8.18
* feat(ui): unifi active and finished requests into single view #1422 (#3401)
Integrated into release/v3.8.18
* docs(changelog): record #3401, #3473, #3492, #3490, #3451, #3491, #3169 under v3.8.18
* feat(docs): add doc accuracy gate + refresh AGENTS.md counts (#3510)
Integrated into release/v3.8.18
* fix(sse): drop empty-choices chunks without usage instead of injecting retry text (#3513)
PR #3422 ('allow OpenAI usage-only empty choices chunks') reintroduced the
assistant-content injection '[OmniRoute] Upstream returned an empty response.
Please retry.' for empty `choices: []` chunks that carry no valid usage. Clients
(Goose/opencode) feed that text back as a turn and spin in a retry loop -- the
exact regression #3400 had fixed by dropping the chunk.
Restore the drop behavior for the no-usage case while preserving #3422's
standards-compliant forwarding of usage-only `include_usage` final chunks.
Realign the mislabeled stream-utils test (it asserted the injection) and add a
dedicated regression guard.
Reported-by: @mochizzan
Refs: #3502, #3388, #3400, #3422
* fix(authz): fall back to URL token when Authorization isn't a usable Bearer (#3504)
Integrated into release/v3.8.18
* fix(playground): authenticate via session, test key policy by id (#3503)
Integrated into release/v3.8.18
* docs(changelog): record #3510, #3504, #3503 under v3.8.18
* fix: llama base url normalization (#3519)
* docs(changelog): reconcile v3.8.18 — add #3519, #3513, #3435-repair, gitignore chore (full commit↔changelog coverage)
* fix(opencode-plugin): bound regex quantifiers in normaliseFreeLabel (polynomial-ReDoS)
CodeQL js/polynomial-redos: unbounded \s* before an anchored \s*$ allowed
O(n²) backtracking on attacker-influenced display names. Bounded to {0,8}/{1,8}
(ample for any real label spacing). Plugin builds + 254 tests green.
* fix(types): restore clean typecheck:core for v3.8.18 release gate
- getPendingRequests() typed to real shape (was widened to object) → fixes
unknown 'count' in the unified-requests view (#3401)
- streamChunks log payload cast to its declared type (callLogs.ts)
- preScreenTargets aligned to canonical IsModelAvailable signature (#3169),
Promise.resolve-normalized so .catch never hits a bare boolean
All 5 gates green: lint(0 err) + typecheck:core + cycles + docs-all + unit + vitest(146).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Andrey Borodulin <borodulin@gmail.com>
Co-authored-by: Dmitrii Safronov <zimniy@cyberbrain.cc>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com>
* test(settings): add unit tests for debugMode and hiddenSidebarItems
Tests cover:
- PATCH debugMode=true/false
- PATCH hiddenSidebarItems with array values
- Combined updates with both fields
* test(e2e): add Playwright tests for settings toggles
Tests cover:
- Debug mode toggle on/off
- Sidebar visibility toggle
- Settings persistence after page reload
* fix(tests): address code review issues
- Unit tests: fix async/await for getSettings, use direct db functions
- E2E tests: remove conditional logic, use Playwright auto-waiting assertions
* feat(logging): unify request log retention and artifacts
* docs: add dashboard settings toggles to CONTRIBUTING
Add section documenting:
- Debug Mode toggle (Settings → Advanced)
- Sidebar Visibility toggle (Settings → General)
* fix(cache): only inject prompt_cache_key for supported providers
Only inject prompt_cache_key for providers that support prompt caching
(Claude, Anthropic, ZAI, Qwen, DeepSeek). This fixes issue #848 where
NVIDIA API rejected the parameter.
* fix(model-sync): log only channel-level model changes
* feat(providers): add 4 free models to opencode-zen
* feat(providers): add explicit contextLength for opencode-zen free models
* feat(providers): add contextLength for all opencode-zen models
* feat: Improve the Chinese translation
* fix: preserve client cache_control for all Claude-protocol providers
Previously, the cache control preservation logic only recognized a
hardcoded list of providers (claude, anthropic, zai, qwen, deepseek).
This caused OmniRoute to inject its own cache_control markers for
Claude-protocol providers not in that list (bailian-coding-plan, glm,
minimax, minimax-cn, etc.), overwriting the client's cache markers.
The fix checks both:
1. Known caching providers list (existing behavior)
2. Whether targetFormat === 'claude' (all Claude-protocol providers)
This ensures all Claude-compatible providers properly preserve client
cache_control headers when appropriate (Claude Code client, deterministic
routing, etc.).
Also removes unused CacheStatsCard from settings/components (duplicate
of the one in cache/ page).
Fixes cache token calculation for GLM, Minimax, and other Claude-compatible providers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: pure passthrough for Claude→Claude when cache_control preserved
The Claude passthrough path round-trips through OpenAI format
(claude→openai→claude) for structural normalization. This strips
cache_control markers from every content block since OpenAI format
has no equivalent, causing ~42k cache creation tokens per request
with zero cache reads.
When preserveCacheControl is true (Claude Code client, "always"
setting, or deterministic combo), skip the round-trip entirely and
forward the body as-is. Claude Code sends well-formed Messages API
payloads — the normalization was only needed for non-Code clients.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: restore CacheStatsCard — was not a duplicate
The first commit incorrectly deleted CacheStatsCard from
settings/components/ as a "duplicate". It's the only copy — both
settings/page.tsx and cache/page.tsx import from this location.
Restored the i18n-ized version from main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(429): parse long quota reset times from error body
- Parse XhYmZs format from antigravity error messages (e.g., 27h41m36s)
- Dynamic retry-after threshold (60s default) instead of hardcoded 10s
- Add parseRetryFromErrorText() in accountFallback.ts for body parsing
- Fix 403 'verify your account' to trigger permanent deactivation
- Add keyword matching for 'quota will reset', 'exhausted capacity'
- Add unit tests for retry parsing and keyword matching
Fixes#858 (Antigravity 429 handling)
Fixes#832 (Qwen quota 429 - same underlying bug)
* chore: bump version to v3.4.0-dev
* fix(migrations): rename 013 to 014 to avoid collision with v3.3.11
* chore(docs): update CHANGELOG for v3.4.0 integrations
* fix: Claude token refresh, Antigravity quota, and 429 rate-limit handling
- Fix Claude OAuth token refresh to use form-urlencoded format (standard OAuth2)
- Add anthropic-beta header required by Claude OAuth API
- Switch Antigravity quota to use retrieveUserQuota API (same as Gemini CLI)
- Parse quota reset time for all providers (not just Antigravity)
- Add quota reset keywords to error classifier
- Cap maximum retry time at 24 hours to prevent infinite wait
Closes#836, #857, #858, #832
* fix(dashboard): resolve /dashboard/limits hanging UI with 70+ accounts via chunk parallelization (#784)
---------
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: R.D. <rogerproself@gmail.com>
Co-authored-by: kang-heewon <heewon.dev@gmail.com>
Co-authored-by: gmw <rorschach1167@qq.com>
Co-authored-by: tombii <github@tombii.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single
OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies,
multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers,
semantic caching, combo fallback chains, real-time health monitoring, and a full
dashboard with provider management, analytics, and CLI tool integration.
Key highlights:
- 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.)
- 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized)
- Export/Import database backup with full archive support
- Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor)
- 100% TypeScript across src/ and open-sse/
- Docker support with multi-stage builds
- Comprehensive documentation and 9 dashboard screenshots