Upload the Next.js build from the build job and reuse it across E2E
shards to avoid rebuilding in each shard. Increase Playwright sharding
from 6 to 9, cache Chromium browsers, and lower the E2E timeout to match
the faster expected runtime.
Add a Codex CLI configuration skill for OmniRoute setup and ignore local
credential-bearing setup prompts.
* chore(release): open v3.8.15 development cycle
Version bump 3.8.14 -> 3.8.15 (root + electron + open-sse + openapi + lockfiles)
and seed the v3.8.15 changelog placeholder (root + 41 i18n mirrors).
* fix(catalog): add getTokenLimit fallback for combo targets with unknown context (#3369)
Integrated into release/v3.8.15. Fixes applied on the contributor's branch: removed duplicate JSDoc opening in accountFallback.ts and dropped a test asserting unreachable catalog behavior (models with no registry/spec/synced source are filtered before the getTokenLimit fallback at catalog.ts:499).
* fix(combo): add 429 to PROVIDER_FAILURE_ERROR_CODES to prevent infinite retry loop (#3366)
Integrated into release/v3.8.15. Comment block reconciled on the contributor's branch to remove the contradictory 'intentionally excluded' text that remained from the original code.
* fix(auto-combo): include no-auth providers declaratively (#3365)
Integrated into release/v3.8.15. Cleanup applied on contributor's branch: removed duplicate migration 095 (already exists from PR #3338), reverted CHANGELOG.md and i18n changelogs to release versions (release process owns these), dropped package version-bump noise from stale fork base. Core feature — declarative no-auth via serviceKinds metadata, declarative VEO as 'video' provider, anonymousFallback flag for opencode-zen/opencode-go — integrated cleanly.
* fix(migrations): restore 095_provider_node_custom_headers migration
The squash merge of PR #3365 accidentally deleted this migration because
the cleanup commit on the contributor's branch included 'git rm' for the
file (which was a duplicate on their branch). The migration was merged
in v3.8.14 via PR #3338 and must be present in the release branch.
Restoring from git history.
* fix: update Command Code base URL from /alpha/ to /provider/v1/ (#3372)
Integrated into release/v3.8.15.
* feat(error-rules): provider-specific error classification with scope (#3370)
Integrated into release/v3.8.15. PR has genuine value beyond #3369: (1) getProviderErrorRuleMatch now accepts native Headers objects from fetch(); (2) checkFallbackError also uses the provider rule registry — the real end-to-end wiring in the combo fallback path; (3) S4 end-to-end test proving the wiring fires. Merge commit on contributor branch resolved the add/add conflict by taking the #3370 version throughout.
* fix(auto-combo): validate web-session credentials (#3371)
Integrated into release/v3.8.15. Core feature: provider-aware web-session credential validation — hasUsableWebSessionCredential() replaces the broad Object.keys check in virtualFactory.ts, ensuring only sessions with the required storageKeys are included in auto-combo. Cleanup: removed duplicate 095 migration, reverted CHANGELOG/i18n, dropped package bump noise.
* fix(migrations): restore 095_provider_node_custom_headers (deleted again by #3371 squash)
Same issue as after #3365: git rm in the contributor cleanup commit
was included in the squash, deleting this migration from release.
Permanent fix needed: use 'git checkout origin/release -- <file>'
instead of 'git rm' when cleaning up duplicate files in contributor branches.
* fix(kiro): probe Windows %APPDATA%\kiro\storage.db in auto-import (#3363) (#3375)
Integrated into release/v3.8.15. Test fix applied: kiro-windows-auto-import-3363.test.ts now sets DATA_DIR to a fresh temp dir before importing app modules, ensuring isAuthRequired() sees an empty settings DB (no password → auth not required). This fixed test 4 (synthetic SQLite) which was getting 401 due to settings DB state leakage.
* chore(release): finalize v3.8.15 changelog — 2026-06-07
---------
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Muhammad Nabil Muyassar Rahman <65392758+TapZe@users.noreply.github.com>
Co-authored-by: kiro-agent[bot] <245459735+kiro-agent[bot]@users.noreply.github.com>
* chore(release): open v3.8.14 development cycle
Version bump 3.8.13 -> 3.8.14 (root + electron + open-sse + openapi + lockfiles).
Seed the v3.8.14 changelog with the four post-tag hotfixes that shipped to
Docker/Electron in v3.8.13 but missed the immutable npm 3.8.13 (#3336 SSRF /
CodeQL #323, #3334/#3335/#3339 Electron packaging). i18n CHANGELOG mirrors get
the in-progress placeholder section.
* feat: add per-provider custom headers support for OpenAI/Anthropic-compatible nodes (#3338)
Integrated into release/v3.8.14
* fix: Kiro Builder ID token import fails with Bad credentials (#3333)
Integrated into release/v3.8.14 — adds Builder ID cached-creds + OIDC refresh path for Kiro token import, with regression tests (#3333).
* Improve code quality: auto-pr/docstrings-1780792063 (#3337)
Integrated into release/v3.8.14 — docstring for context analytics route re-export.
* fix(catalog): remove minimaxai/minimax-m3 from NVIDIA NIM tier (404 upstream) (#3329) (#3341)
NVIDIA NIM does not host minimaxai/minimax-m3 — every request returns
404 page not found, while sibling minimaxai/minimax-m2.7 on the same provider
works. Advertising a model that 404s is a catalog bug; remove it from the nvidia
tier (it remains on the tiers that actually serve MiniMax M3). Re-add only once
NVIDIA serves it.
Co-authored-by: mikmaneggahommie <mikmaneggahommie@users.noreply.github.com>
* fix(cli): write OpenCode config to ~/.config on all platforms incl. Windows (#3330) (#3343)
resolveOpencodeConfigDir used %APPDATA% on Windows, but OpenCode reads its
config from XDG ~/.config/opencode/ on every platform (on Windows:
%USERPROFILE%\.config\opencode\, NOT %APPDATA%). So a Windows user who
configured OpenCode via the dashboard had the file written where OpenCode never
looks — it silently had no effect.
Use the XDG path (XDG_CONFIG_HOME || ~/.config) unconditionally. Update the UI
note + route JSDoc, and flip the three tests that encoded the old %APPDATA%
behavior (t40 per-platform + card-note, cli-runtime-extended getCliConfigPaths).
Co-authored-by: abdulkadirozyurt <abdulkadirozyurt@users.noreply.github.com>
* fix(proxy): make auto-selection fallback opt-in (#3332) (#3344)
selectWorkingProxyFallback (Step 11 of resolveProxyForConnection) listed ALL
registry proxies, ignoring assignments and per-connection proxy_enabled, and
returned the first working one with level:'autoSelect'. So a single proxy added
to the registry silently became a global fallback for every connection's traffic.
Gate it behind a new PROXY_AUTO_SELECT_ENABLED feature flag (default off): the
fallback now no-ops unless the operator opts in. No registry proxy becomes a
silent global default anymore.
Co-authored-by: hertznsk <hertznsk@users.noreply.github.com>
* fix(sse): treat MiniMax M3 as multimodal so vision isn't stripped (#3328) (#3342)
MiniMax M3 via the opencode provider (oc/minimax-m3-free) appeared blind:
image inputs didn't reach the model, while the same model in Cline could
see them. Verified empirically that MiniMax M3 on the opencode upstream IS
multimodal -- a base64 image is described correctly (it returns 403 only
for remote image URLs, which it doesn't accept).
Root cause: OmniRoute treated MiniMax M3 as a non-vision model in two
places, so when compression was active the image was replaced with a text
placeholder before dispatch:
- compression's modelSupportsVision() heuristic (lite.ts) only matched
gpt-4/4o/claude-3/gemini/vision -- minimax was absent -> replaceImageUrls
stripped the image.
- the opencode minimax-m3-free catalog entry lacked supportsVision, so the
combo vision-capability gate could also exclude/mishandle it.
Add 'minimax-m3' to the vision heuristic and supportsVision: true to the
opencode minimax-m3-free entry. TDD: a failing-then-passing test in
compression/lite.test.ts proves replaceImageUrls now keeps images for
minimax-m3 ids, plus a registry assertion mirroring the #2822 qwen test.
Reported-by: @mikmaneggahommie
* docs(i18n): translate 25 core documentation files to Indonesian (#3348)
Integrated into release/v3.8.14 — Indonesian i18n docs.
* fix(review): resolve /review-reviews battery findings (LEDGER-1..11) on v3.8.14 (#3350)
Integrated into release/v3.8.14 — /review-reviews battery hardening (LEDGER-1..11) for #3338 custom-headers + #3333 kiro, plus cycle-test drift fixes (#3329/#3330/#3332).
* fix(provider-proxy): honor per-account proxy toggles (#3349)
Integrated into release/v3.8.14 — honor per-account proxy toggles + auto-fallback opt-in via PROXY_AUTO_SELECT_ENABLED.
* fix(dashboard): remove duplicate Distribute Proxies button on provider page (#3352)
* fix(providers): reduce proxy label noise (#3346)
Integrated into release/v3.8.14 — reduce proxy label noise + a11y (aria-label/sr-only).
* fix(duckduckgo): restore bare Response contract and rebase onto release/v3.8.14 (#3323)
Integrated into release/v3.8.14 — browser-backed cookie providers (duckduckgo/claude-web) with restored executor contract + unit tests.
* fix(noauth): expose only usable model aliases (#3345)
Integrated into release/v3.8.14 — noauth usable-alias filtering + registry alias plumbing (veo-free).
* fix(dashboard): stop infinite config-load loop on Hermes Agent detail page (#3353)
* fix(electron): tree-kill the server on exit/update to release the omniroute.exe lock (#3347) (#3354)
* chore(release): finalize v3.8.14 changelog + clear release-gate drift
- CHANGELOG: finalize the v3.8.14 section (date, full New Features/Bug Fixes/
Maintenance coverage of all 16 cycle commits, Contributors hall of 12).
- docs: document OMNIROUTE_BROWSER_POOL + WEB_COOKIE_USE_BROWSER (#3323) in
.env.example + ENVIRONMENT.md; regenerate the id/llm.txt strict mirror (#3348
had translated it; llm.txt mirrors must match root).
- test(proxy-fetch): #3323 made tlsClient.available a computed getter — stub it
via Object.defineProperty instead of assignment (5 tests were red on the base).
* fix(translator): coerce Gemini functionDeclaration parameters to an OBJECT schema (#3357) (#3360)
* fix(gemini): resolve truncation/suppression of false positive textual tool call markers in backticks (#3358)
Integrated into release/v3.8.14 — Gemini/Antigravity textual tool-call marker normalization (no false-positive suppression + split-chunk buffering).
* docs(changelog): add #3358 Gemini textual tool-call normalization to v3.8.14
* fix(dashboard): surface real analytics error instead of generic placeholder (#3356) (#3361)
The Analytics page discarded the server's error body on a non-OK response and
rendered a generic "An error occurred", so users (and maintainers) could not see
why /api/usage/analytics 500'd after an upgrade. Now the route returns the real
reason via buildErrorBody (sanitized, Hard Rule #12) and the page surfaces it via
a new readFetchErrorMessage helper that handles both the OpenAI-style and legacy
error shapes.
Reported-by: @superti4r
---------
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Someres <168349709+quanturbo@users.noreply.github.com>
Co-authored-by: Dong Mengzhe <154944819+Lang-Qiu@users.noreply.github.com>
Co-authored-by: mikmaneggahommie <mikmaneggahommie@users.noreply.github.com>
Co-authored-by: abdulkadirozyurt <abdulkadirozyurt@users.noreply.github.com>
Co-authored-by: hertznsk <hertznsk@users.noreply.github.com>
Co-authored-by: Krisna Santosa <54174372+KrisnaSantosa15@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Ardem2025 <ardemb22@gmail.com>
POST /api/providers fires a credential-bearing self-fetch to the new
connection's /sync-models route (forwarding the management cookie + internal
sync auth headers). #3267 built that origin from new URL(request.url).origin —
the client-controlled Host header — so a (management-authenticated) caller
could redirect the internal request to an arbitrary host, exfiltrating the
internal sync auth token (CodeQL js/request-forgery, critical, alert #323).
Derive the origin from the trusted loopback/env-pinned base URL via a new
getModelSyncInternalBaseUrl() helper (same source the model-sync scheduler
already uses), never from the incoming request. Adds a regression test.
* chore(release): open v3.8.13 development cycle
Bump 3.8.12 → 3.8.13 across package.json, lockfile, electron/, open-sse/, and
docs/reference/openapi.yaml; add the [3.8.13] cycle placeholder to the root
CHANGELOG and the 41 i18n mirrors. Integration branch for the v3.8.13 cycle —
fixes/features land here via per-issue PRs and it merges to main at release time.
* fix(ci): skip auto-deploy when VPS host is unreachable from the runner (#3299)
Integrated into release/v3.8.13
* fix(dev): auto-rebuild better-sqlite3 on Node ABI mismatch at dev startup (#3301)
Integrated into release/v3.8.13
* feat(api): accept path-scoped API keys on client API routes (#3300)
Integrated into release/v3.8.13
* fix(sse): harden against empty responses causing Copilot Chat failures (#3297)
Integrated into release/v3.8.13
* fix(api): remove Completions.me rickroll provider (discussion #3293) (#3302)
Integrated into release/v3.8.13
* fix(opencode-provider): extract contextLength from live model catalog (#3298)
Integrated into release/v3.8.13
* feat(web-cookie): self-service login infrastructure + auto-refresh daemon (#3292)
Integrated into release/v3.8.13
* docs(changelog): record the v3.8.13 PRs merged this round (#3292/#3300/#3297/#3298/#3301/#3302/#3299)
* fix(auth): harden URL token extraction — drop query-string fallback, gate to client routes (security follow-up to #3300) (#3309)
Security follow-up to #3300 — integrated into release/v3.8.13
* docs: rename resolve-issues → review-issues skill references
* fix(dashboard): keep no-auth providers visible under 'Show configured only' (#3290) (#3312)
no-auth providers (opencode, duckduckgo-web, theoldllm, veoaifree-web) never
create a DB connection row so stats.total stays 0, which the configured-only
filter treated as 'unconfigured' and hid them — even though they are always
usable and appear unconditionally in /v1/models. filterConfiguredProviderEntries
now treats displayAuthType === 'no-auth' as configured.
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
* fix(cli): resolve update paths relative to script + recursive backup (#3295) (#3313)
omniroute update always failed on a global install:
- getCurrentVersion() read package.json from process.cwd(), which on a global
npm/brew install is the user's working dir, not the package root → null →
'Could not determine current version'.
- createBackup() resolved bin/ from cwd too, and passed the 'cli' directory to
copyFileSync → EISDIR, swallowed by the catch → 'Failed to create backup'.
Both now resolve package.json/bin relative to the script via import.meta.url,
and the backup uses cpSync({recursive:true}) so the cli/ directory is copied.
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
* fix(theoldllm): read upstream body once to avoid [502] body-already-read (#3296) (#3314)
On the cached-token path the executor never enters the refresh branch, so the
same upstream Response was read with .text() twice (token-rejection check +
final body). A Response body is single-use, so the second read threw
'Body is unusable: Body has already been read', caught and surfaced as [502].
Read the body once into finalBody and only re-read after a token-rejection
refetch.
Co-authored-by: onizukashonan14-png <onizukashonan14-png@users.noreply.github.com>
* fix(sse): strip leaked internal tool envelopes from streaming output (#3311)
Integrated into release/v3.8.13
* fix(sse): expose Claude + Gemini budget tiers in the antigravity catalog (#3184) (#3303)
Integrated into release/v3.8.13 (#3184)
* fix(catalog): compute combo context_length from known targets only (#3304)
Integrated into release/v3.8.13 — live contextLength + known-targets combo context (#3298 follow-up)
* chore(i18n): add message keys for proxy UI + vscode/ollama endpoint (#3307)
Integrated into release/v3.8.13 — i18n message keys for proxy UI + vscode/ollama
* feat(dashboard): i18n the proxy settings UI (#3310)
Integrated into release/v3.8.13 — i18n the proxy settings UI
* feat(api): model catalog enrichment + MCP model-catalog tools (#3306)
Integrated into release/v3.8.13 — model catalog enrichment + MCP model-catalog tools, reconciled with #3309 URL-token hardening
* test(catalog): align Antigravity preview-alias test with #3303 budget tiers
#3303 added the Gemini `-high`/`-low` budget tiers to ANTIGRAVITY_PUBLIC_MODELS
(user-callable on the Antigravity OAuth backend, verified via #3184), but did
not update the catalog-route test that asserted `antigravity/gemini-3.1-pro-high`
must NOT be exposed. The assertion now reflects the intended behavior — the
client-visible budget alias IS surfaced — while keeping the legacy
`gemini-claude-*` alias keys unexposed. Caught running the full catalog suite
on the merged release HEAD (the #3303 round only ran the antigravity-aliases
and usage-hardening files).
* docs(changelog): record the 6 PRs merged this review round into v3.8.13
#3306/#3307/#3310 (New Features — VS Code split: catalog+MCP, i18n keys, proxy
UI i18n), #3311/#3303/#3304 (Bug Fixes — SSE envelope sanitizer, antigravity
budget tiers, combo known-targets context_length).
* chore(release): finalize v3.8.13 changelog and cleanup
Finalize the v3.8.13 changelog with release date, maintenance notes,
and contributor credits. Update MCP docs to reference the correct tool
inventory diagram, exclude nested .claude worktrees from ESLint scans,
and tighten a response sanitizer type guard.
* fix(dashboard): refresh connections after provider auth import (#3320)
Integrated into release/v3.8.13 — refresh connections after provider auth import
* fix(codex): strip client-only params on native /responses passthrough (#3317) (#3325)
A /v1/responses request against the built-in codex/ provider does an
openai-responses -> openai-responses passthrough (CodexExecutor.transformRequest
returns the body early for _nativeCodexPassthrough). It forwarded client-only
fields verbatim and the Codex upstream rejected them with 400 Unsupported
parameter: prompt_cache_retention / safety_identifier / user — breaking Factory
Droid (which injects all three). The chat-completions path already strips these
(base.ts #1884, openai-responses translator #2770) but the passthrough skips
translation. Strip the three fields in the shared block before the passthrough
return; user is removed unconditionally since Codex /responses always rejects it.
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
* fix(dashboard): normalize agent-bridge /state response to stop page crash (#3318) (#3326)
The Agent Bridge page seeded a well-shaped initialData default then replaced it
wholesale with the raw /api/tools/agent-bridge/state response. The route returns
{ server, agents } but the UI reads { serverState, agentStates, bypassPatterns,
mappings }, so serverState became undefined and AgentBridgeServerCard crashed on
serverState.running — surfaced as the full-page 'Internal Server Error' boundary
(client render error, not a real 5xx).
Add a shared normalizeAgentBridgeState() that maps the route shape into the page
contract (server.running/certExists -> serverState) and always returns safe
defaults (never undefined serverState). Wired into both the SSR loader (page.tsx)
and the polling hook. The legacy 'agents' entry shape differs from AgentStateEntry
so it is not coerced; full route<->page contract reconciliation (port, upstreamCa,
bypassPatterns, mappings, agentStates) is a follow-up.
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
* docs: VS Code/Ollama endpoints + env & i18n tooling (#3319)
Integrated into release/v3.8.13 — VS Code/Ollama docs + env & i18n tooling
* feat(provider): test-all endpoint, rate-limit overrides, visibility f… (#3267)
Integrated into release/v3.8.13 — provider test-all endpoint, rate-limit overrides, model visibility
* feat: auto-combo optimization, playground model dropdown, only-configured toggle (#3322)
Integrated into release/v3.8.13 — auto-combo candidate expansion + playground dropdown + only-configured toggle
* feat(api): VS Code Copilot Ollama-compatible BYOK endpoint (#3316)
Integrated into release/v3.8.13 — VS Code Copilot Ollama-compatible BYOK endpoint (reconciled with #3306/#3309 auth hardening)
* chore(release): document #3320 in the v3.8.13 changelog + contributor credits
---------
Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
Co-authored-by: onizukashonan14-png <onizukashonan14-png@users.noreply.github.com>
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
Co-authored-by: Vinayrnani <vinayrnani@gmail.com>
Follow-up to the #3269 private-webhook opt-in. With the opt-in on, the private-host
check was bypassed entirely, leaving cloud-metadata endpoints (169.254.169.254,
metadata.google.internal, 100.100.100.200, link-local 169.254.0.0/16) reachable — the
classic SSRF -> IAM-credential pivot — and the webhook test endpoint returned the
upstream body, making it a content-exfiltration primitive against internal services.
- outboundUrlGuard: add isCloudMetadataHost(); parseAndValidateWebhookUrl blocks those
hosts UNCONDITIONALLY, even when private targets are opted in.
- webhooks/[id]/test: redact responseBody for private targets (status + latency only).
Regression test: tests/unit/webhook-metadata-guard-3269.test.ts (RED before, GREEN after);
existing webhook SSRF/opt-in suites stay green (34/34).
Webhooks hardcoded parseAndValidatePublicUrl, which blocks any RFC1918/loopback host —
breaking self-hosted setups that legitimately point webhooks at internal services
(n8n, Home Assistant, a LAN box). Provider URLs already had an opt-in
(OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS); webhooks now reuse it.
- outboundUrlGuard: add parseAndValidateWebhookUrl — gates the private-host check on
arePrivateProviderUrlsAllowed() (default OFF); protocol + embedded-credential checks
stay unconditional.
- swap all webhook call sites (create/update/test/validate-url + dispatcher x2) to it.
Regression test: tests/unit/webhook-private-optin-3269.test.ts (RED before, GREEN after);
existing webhook SSRF/dispatcher suites stay green (33/33).
* chore(release): open v3.8.10 development cycle
Bump 3.8.9 → 3.8.10 across package.json, lockfile, electron, open-sse, and
docs/reference/openapi.yaml; add the [3.8.10] CHANGELOG section (root + 41 i18n
mirrors) as the integration target for the cycle. Entries land here as work
merges into release/v3.8.10; finalized by the release flow.
* fix(providers): resolve web provider alias collisions
Assign unique aliases to HuggingChat, Kimi Web, and Qwen Web so they no longer shadow primary providers or trigger startup warnings.
Add a unit test to enforce provider alias uniqueness and prevent future collisions. Also expand local ignore and VS Code exclude rules for agent, build, and worktree artifacts.
* fix(responses): normalize image_url parts across input paths (#3150)
Normalize image_url parts across all Responses input paths. Integrated into release/v3.8.10.
* fix(api-manager): preserve API key expiration local time (#3146)
Preserve API key expiration local time + clear button. Integrated into release/v3.8.10.
* Strip previous_response_id for stateless Responses upstreams (#3143)
Strip previous_response_id for stateless Responses upstreams (auto/strip/preserve). Integrated into release/v3.8.10.
* fix(opencode-plugin): map thinking cap to interleaved in model+combo (#3138)
Map caps.thinking to ModelV2.capabilities.interleaved for opencode-plugin. Integrated into release/v3.8.10.
* fix(providers): use synced models as fallback for all providers (#3148)
Use synced models as authoritative local catalog for all providers (+regression test). Integrated into release/v3.8.10.
* fix(qoder): bifurcate validation by token type — PAT→Cosy, regular API key→dashscope (#3149)
Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10.
* fix(antigravity): dynamic model resolution via MITM alias table (#3144)
Dynamic antigravity MITM model resolution in the executor (+bug fix +regression test; DB import dropped from client-reachable config). Integrated into release/v3.8.10.
* Feature/batch allow big (#3128)
Podman deployment options + larger upload body-size limits (+CONTAINER_HOST docs). Integrated into release/v3.8.10.
* fix(fireworks): preserve fully-qualified router/model IDs (#3133) (#3160)
Fireworks router IDs (accounts/fireworks/routers/...) were double-prefixed
with accounts/fireworks/models/ → upstream 404. Add optional
acceptedModelIdPrefixes to the registry entry and skip the prepend when the
model already starts with an accepted prefix.
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
* fix(llama-cpp): route to configured local baseUrl instead of OpenAI (#3136) (#3161)
llama-cpp was missing from the local-provider group in buildUrl(), so it
fell through to the OpenAI baseUrl and returned an OpenAI 401. Add the
case to resolve the connection's providerSpecificData.baseUrl.
Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com>
* fix(t3-chat-web): parse cookies + convexSessionId from stored credential (#3007) (#3162)
The executor read credentials.cookies/convexSessionId, but the pipeline
only stores the pasted string under apiKey → t3.chat always 400'd. Parse
both values from apiKey (fallback accessToken), mirroring validation.ts.
Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com>
* fix(minimax): stop capping MiniMax-M3 / M2.7 max_tokens at 8192 (#3141) (#3163)
MiniMax-M3 had no MODEL_SPECS entry and capitalized MiniMax-M2.7 missed
its lowercase spec (case-sensitive lookup) → both fell to the 8192 default
cap. Add the M3 spec (512K output), alias the capitalized ids, and make
getModelSpec lookups case-insensitive.
Co-authored-by: totaltube <totaltube@users.noreply.github.com>
* fix(github-copilot): discover model catalog live from api.githubcopilot.com (#3120, #3121) (#3164)
The github (Copilot) provider had a static hardcoded catalog with no
discovery source, so Import Models never refreshed (#3120) and advertised
non-entitled models that 400 on use (#3121). Add a live /models fetch with
fallback to the static list.
Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com>
* fix(combo): invalidate nested-combo cache on edits + log DATA_DIR (#3147) (#3165)
Editing a combo did not invalidate the 10s nested-combo expansion caches
(chat.ts getCombosCachedForChat + chatCore.ts getCombosCached; the exported
clearCombosCache was dead code), so a removed nested target/model could be
served as a phantom for up to 10s. Wire a shared monotonic combos-cache
version in readCache (bumped by invalidateDbCache("combos") on every combo
write); both cache layers treat a version mismatch as a miss.
Also log the resolved DATA_DIR/SQLITE_FILE absolute path at DB init so the
reporter's 'persists across restart + volume wipe' symptom (a multi-replica
Docker volume/DATA_DIR mismatch, not a routing bug) is diagnosable from logs.
Includes consolidated CHANGELOG entries for #3133/#3136/#3007/#3141/#3120/#3121.
Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com>
* fix(web-tools): parse bare JSON tool calls (#3157)
Parse bare JSON tool calls for deepseek-web (#2820) + fuzzy tool-name matching. Integrated into release/v3.8.10.
* fix(misc): minor fixes across reasoning cache, account fallback, binary manager (#3177)
Misc: ProviderProfile export, DeepSeek reasoning regex, binary guard. Integrated into release/v3.8.10.
* fix(kiro): minor OAuth social exchange tweaks (#3176)
Kiro social OAuth: optional targetProvider passthrough. Integrated into release/v3.8.10.
* deps: bump hono from 4.12.18 to 4.12.23 (#3179)
Bump hono to 4.12.23. Integrated into release/v3.8.10.
* fix(providerRegistry): update kilocode format and executor (#3166)
kilocode: openai format + default executor (matches kilo-gateway) + registry test. Integrated into release/v3.8.10.
* feat(metrics): cross-request TTFT and gap latency after tool calls (#3173)
Cross-request TTFT + gap-after-tool latency metrics (+test). Integrated into release/v3.8.10.
* feat(dashboard): provider stats API endpoint and dashboard page (#3175)
Provider stats dashboard + API (SQL moved to db module per Hard Rule #5, +test). Integrated into release/v3.8.10.
* fix(usage): sequential+spaced OAuth quota sync, reactive force-refresh, actionable 401 (#3156)
Sequential+spaced OAuth quota sync, reactive force-refresh on 401, actionable 401 in UI. Integrated into release/v3.8.10.
* fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth) (#3159)
Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10.
* feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (#3178)
Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10.
* fix(providers): empty refresh must not resurface just-cleared synced models (#3181)
Empty refresh must not resurface just-cleared synced models (fixes the release-blocking provider-models-route test). Integrated into release/v3.8.10.
* chore(release): v3.8.10 — 2026-06-04 (finalize CHANGELOG)
---------
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: M.M <mr.maatoug@gmail.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com>
Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com>
Co-authored-by: totaltube <totaltube@users.noreply.github.com>
Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com>
Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
Kiro (AWS CodeWhisperer) tops out at Claude Opus 4.7 in the registry, but the latest Opus 4.8 is already served by the Claude Code provider and Kiro's executor passes the model id through to CodeWhisperer verbatim. Expose claude-opus-4.8 on the kiro provider so clients can select it via kiro/claude-opus-4.8.
- providerRegistry: add claude-opus-4.8 (1M context, 128k output) above 4.7
- pricing: add claude-opus-4.8 (and the previously-missing 4.7) to the kiro pricing block at Kiro's standard Opus rate so usage cost is non-zero
- tests: assert kiro exposes claude-opus-4.8 with matching context/output + pricing
* feat(providers): implement bulk paste for extra API keys
Adds `parseExtraApiKeys` utility to process multi-line key inputs.
Integrates bulk paste functionality into the provider connection modal.
Users can now paste multiple API keys, one per line, into the input field.
Provides notifications for successfully added keys and ignored duplicates.
Adds a "Delete all" button to clear all extra API keys.
Updates i18n messages for new features and improved key masking/pluralization.
* refactor(providers): streamline API key bulk paste and i18n
Remove unused return from `handleAddParsedExtraKeys` to clean up code.
Adjust `onPaste` to allow default paste for single-line input, improving UX.
Remove obsolete bulk paste UI translation keys to reduce bundle size.
Update pluralization for bulk paste messages to ensure correct grammar.
Refine Portuguese (Brazil) API key translations for clarity.
* fix(logs): apply code review feedback - robust signature, immediate fetch on tab restore, reuse memoized apiKeyCount
* test(logs): extract pure polling/signature helpers + cover them (#3109)
Hard rule #8: the perf fix touched src/ without tests. Extract computeLogsSignature,
shouldAutoRefresh and resolveInitialVisibility into a pure module and unit-test
them (change-detection, first-page polling guard, SSR/hidden-tab visibility init).
Also fixes visibleRef to honor the real visibilityState on mount instead of
hardcoding true (no poll when mounted in a hidden tab).
---------
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix(cli): handle Windows exe healthchecks with spaces
* Update src/shared/services/cliRuntime.ts
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Update tests/unit/cli-runtime-extended.test.ts
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* fix(cli): pass command to spawn unquoted; export shouldUseShellForCommand
Remove the manual "${command}" interpolation into the shell command (hard rule
#13 violation, and redundant — Node quotes for cmd.exe when shell:true; .exe runs
with shell:false where the OS handles spaces via argv). Export the helper and add
a cross-platform test asserting non-Windows never uses the shell.
---------
Co-authored-by: Empire Rider <anuruddhawijesiri@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
* fix(sse): bound Antigravity short-retry 429 loop per endpoint
A persistent 429 on the short-retry branch (retryAfterMs ≤ 60s) looped
forever on the same endpoint because the branch did `urlIndex--; continue`
without checking the shared retry counter. Production log showed 77
consecutive 429s on one daily endpoint/account with zero fallback.
Gate the short-retry branch on `retryAttemptsByUrl[urlIndex] < MAX_AUTO_RETRIES`
(mirroring the already-bounded sibling), so a persistent 429 retries at most
3× per endpoint across all 3 base URLs then returns the 429 to the account-
fallback layer.
Regression test: 'bounds a persistent short-retry 429' in
tests/unit/executor-antigravity.test.ts — asserts 12 total attempts
(3 endpoints × 4) and a returned 429 with zero hang.
* fix(sse): lock Antigravity quota-exhausted account for full reset window
After the retry-loop bound, OmniRoute fell over to the next account but
re-selected the exhausted one first on every subsequent request (~60s wasted
per request). Root cause: the 429 body 'Individual quota reached. Contact
your administrator to enable overages. Resets in 164h27m24s.' was not
recognized as quota exhaustion, so the model was locked for only ~5s instead
of the real 6.8-day reset window.
Two detector fixes (mirrors Antigravity-Manager rate_limit.rs set_lockout_until):
1. classify429.ts — add QUOTA_PATTERNS: /individual quota reached/i,
/quota reached/i, /enable overages/i so looksLikeQuotaExhausted() fires.
2. accountFallback.ts — same patterns in classifyErrorText(); extend
parseRetryFromErrorText() to parse 'Resets? in XhYmZs' (reusing the
existing computeDurationMs helper) so the exact reset duration reaches
recordModelLockoutFailure as exactCooldownMs (uncapped, per user choice).
The lockout machinery already stores until = now + cooldownMs with no clamp,
bypasses getScaledCooldown when exactCooldownMs > 0, and keeps the longer of
existing/new, so the full 164h window flows through intact.
New patterns stay specific — plain 'too many requests'/'rate limit exceeded'
messages still classify as rate_limit.
Verified end-to-end against the real message:
- classify429 → quota_exhausted
- parseRetryFromErrorText → 592044000 ms (164h27m24s exactly)
- checkFallbackError → usedUpstreamRetryHint: true, cooldownMs: 592044000
* refactor(account-fallback): simplify error parsing and add cooldown safety
- Implement a 30-day cap on parsed retry durations to prevent indefinite account lockouts.
- Replace manual string matching with `looksLikeQuotaExhausted` for more robust quota detection.
- Streamline regex logic in `parseRetryFromErrorText` for better readability.
- Add unit tests for extreme cooldown values and free-tier exhaustion scenarios.
* fix(429): drop over-broad /quota reached/ pattern, keep specific matches
The bare /quota reached/ would also flag transient per-minute limits like
'request quota reached, retry in 60s' as quota_exhausted (multi-hour lock).
The Antigravity message is still caught by /individual quota reached/. Added a
regression assertion proving the transient case stays a rate_limit.
---------
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
grok-web only sent the 'sso' cookie; Grok's anti-bot now rejects (403 code 7) requests missing the paired 'sso-rw' write cookie. Adds buildGrokCookieHeader() which emits sso plus sso-rw when the pasted blob carries it (never a phantom sso-rw from a bare value), used by both the executor and the connection validator. Updates the grok-web authHint. Tests: 6 new buildGrokCookieHeader cases; grok-web 62/62 unchanged.
Add a global OMNIROUTE_CODEX_WS_ENABLED kill-switch for Codex WebSocket transport, defaulting to enabled if feature flag lookup fails.
Track Xiaomi MiMo monthly token usage from OmniRoute usage history and expose it through the usage fetcher provider list.
E2E testing on the VPS showed a normal key (empty allowedQuotas) could call a
qtSd/<group>/<provider>/<model> virtual model and route through a shared quota
pool — because the quota-exclusive enforcement (Check 3) only ran when
allowedQuotas was non-empty, so an unallocated key fell through to the normal
model checks and qtSd was served. This is the "empty allowedQuotas = all pools"
gap from the redesign.
Add Check 2.9 in enforceApiKeyPolicy: if the requested model is a qtSd model and
the key is NOT allocated to any quota pool (allowedQuotas empty), reject 403
QUOTA_NOT_ALLOCATED. Allocated keys are unchanged (Check 3 still validates scope).
This matches the owner's rule: only a key selected in a pool may use its qtSd
models. Normal (non-qtSd) model access for normal keys is unchanged.
Test: tests/unit/apikeypolicy-quota-only.test.ts — new case asserts a non-quota
key is blocked from qtSd (QUOTA_NOT_ALLOCATED) yet still uses normal models.
#3054 ("remove 9 dead/unreachable free providers") removed the petals/nanobanana
configs, registry entries and validators but left dangling references that broke
the build and the unit suite on release/v3.8.8:
- open-sse/executors/petals.ts imported the deleted ../config/petals.ts
(webpack "Module not found" → `next build` failed). Removed the executor, its
registration + re-export in executors/index.ts, and the leftover
`providerId === "petals"` branch in providerAllowsOptionalApiKey.
- Removed tests for the now-deleted providers: executor-petals.test.ts and
poolside-provider.test.ts (REGISTRY.poolside was removed), and the petals /
nanobanana validator assertions in provider-validation-specialty.test.ts,
plus the stale petals catalog assertions in providers-page-utils.test.ts,
proxy-connection-test.test.ts and providers-route-managed-catalog.test.ts.
The image/video/embed registries for nanobanana/replicate/nomic are real and
untouched — only the dead chat/api-key surfaces were removed. 146/146 affected
tests pass; typecheck / build clean.
* chore: remove 9 dead/unreachable free providers
Verified via HTTP probe — API endpoints return 000/404/empty:
- freetheai, enally, replicate, lepton, poolside, nomic
- astraflow, petals, nanobanana (phantom: catalog but no registry)
Also removed from: providerRegistry.ts, validation.ts,
staticModels.ts, imageValidation.ts, open-sse/config/petals.ts
* chore: remove dead astraflow providers
Remove astraflow and astraflow-cn (UCloud) — API endpoints unreachable.
Remaining dead providers (enally, freetheai, nanobanana, replicate,
lepton, petals, poolside, nomic) have working main sites but dead API
endpoints — need API keys. Will remove in follow-up.
* chore: remove 9 dead/unreachable free providers
Removed: freetheai, enally, replicate, lepton, poolside, nomic,
astraflow, petals, nanobanana
All verified as dead via live API probes (000/404/empty responses).
Cleaned from providers.ts, providerRegistry.ts, validation.ts,
staticModels.ts, and imageValidation.ts.
---------
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Integrated into release/v3.8.8. Applied review fixes: moved the SELECT 1 into a pingDb() db helper (no raw SQL in route, Hard Rule #5) + the 503 catch no longer leaks err.message (Hard Rule #12). Thanks @herjarsa!
Task B9: QuotaSharePageClient fetches /api/quota/groups, renders a group bar
(select + New group + Rename group actions), and filters pool cards by the
selected group. PoolWizard adds a group picker in step 1 and POSTs groupId;
default pool name now uses provider slug instead of the raw connection
label/email. EditAllocationsModal adds a group allocation note. i18n parity
for 7 new quotaShare keys in en + pt-BR. PoolCreateSchema gains optional
groupId field. Test: tests/unit/quota-groups-ui.test.ts (21 source-scan
assertions, all green).
Add GET/POST /api/quota/groups and PATCH/DELETE /api/quota/groups/[id].
PATCH rename calls renameGroup then re-syncs quotaShared-* combos (via
dynamic-import syncQuotaCombos) for every pool in the group, since combo
names embed the group slug. DELETE maps the deleteGroup throw (protected
group-demo or pools still referencing the group) to 409 Conflict via
buildErrorBody — never 500. All handlers are management-gated via
requireManagementAuth and route all errors through buildErrorBody (Hard
Rule #12). 37-assertion source-scan test verifies auth, error sanitisation,
response shapes, Zod validation, PATCH re-sync wiring, and DELETE 409 logic.
Replace the old quotaShared-<poolSlug>-<provider>/<model> format with
qtSd/<groupSlug>/<provider>/<model>. Adds quotaGroupSlug() as the canonical
helper; keeps quotaPoolSlug() as a delegating alias so existing callers
(quotaKey.ts, quotaCombos.ts) compile without changes. parseQuotaModelName
now returns { groupSlug, provider, model }. Updates quotaCombos.ts,
apiKeyPolicy.ts, and all tests that referenced the old field name or the
old hardcoded prefix literal.
Mark duckduckgo-web in WEB_COOKIE_PROVIDERS with noAuth and include it in providerAllowsOptionalApiKey. Update getProviderCredentials to check both NOAUTH_PROVIDERS and WEB_COOKIE_PROVIDERS for noAuth providers (returning synthetic credentials) and import WEB_COOKIE_PROVIDERS accordingly. This enables anonymous/anonymous-cookie web providers to be handled without DB lookups.
Custom models (added via the UI) on opencode-go / openai-compatible nodes always
routed as OpenAI-compatible because there was no per-model targetFormat: addCustomModel
didn't accept it, the API schema stripped it, and getModelTargetFormat (static-registry
only) never saw it — so a custom model needing the Anthropic Messages shape fell back to
the provider default 'openai'.
Thread an optional targetFormat through addCustomModel / replaceCustomModels /
updateCustomModel + the providerModelMutationSchema + the POST/PUT route, surface it from
getModelInfo (one combined custom-model metadata lookup alongside apiFormat), and use it
in chatCore's targetFormat resolution before the provider default.
Closes#2905