✨ New Features
- feat(api): per-provider custom headers for OpenAI/Anthropic-compatible provider nodes — attach operator-defined headers (e.g. tenant/routing headers) to upstream requests via a new
customHeadersfield on provider nodes (custom_headers_jsoncolumn, migration 095). Hardened on merge: values/names validated through the canonicalupstreamHeadersRecordSchema(CRLF/control-char/length/16-max) with a single sharedisForbiddenCustomHeaderName()denylist (hop-by-hop + auth), applied case-insensitively, and honored foranthropic-compatible-cc-*nodes too. (#3338 — thanks @pizzav-xyz / @diegosouzapw)
🔒 Security
- fix(security): provider auto-sync self-fetch now uses a trusted loopback/env-pinned origin (
getModelSyncInternalBaseUrl()) instead ofnew URL(request.url).origin, so a management-authenticated caller can no longer redirect the credential-bearing internal request to an arbitrary host via theHostheader (CodeQLjs/request-forgery, critical). Shipped to Docker/Electron in v3.8.13; reaches npm here (npm3.8.13was immutable). (#3336, CodeQL #323 — thanks @diegosouzapw)
🔧 Bug Fixes
- fix(translator): every Gemini/Vertex
functionDeclaration.parametersis now coerced to an OBJECT-typed schema before cleaning. Clients like GitHub Copilot send some tools (e.g.terminal_last_command) whoseparametersis present but lacks a top-leveltype: "object"(just{ properties }, a scalar type, or{}); these slipped throughbuildGeminiTools'params || defaultguard and Vertex rejected them with[400] ... functionDeclaration parameters schema should be of type OBJECT. Hardens every OpenAI→Gemini tool request (Vertex / antigravity / agy / gemini). (#3357 — thanks @nullbytef0x) - fix(gemini): normalize Gemini/Antigravity textual
[Tool call: ...]markers — stop suppressing false positives (legitimate assistant prose that merely mentions[Tool call: terminal], e.g. in backticks, is preserved instead of being swallowed) and correctly buffer markers split across streaming chunks ([Tool+call: terminal]+Arguments: {...}), flushing the text when it turns out not to be a tool call. Dedups the parsing/validation into a sharedopen-sse/utils/textualToolCall.ts(newisValidToolCallHeaderPrefix) and addsgemini-2.5-flash/gemini-3.5-flash-lowmodel specs. (#3358 — thanks @Ardem2025 / @diegosouzapw) - fix(electron): clicking "Exit" (or applying an update) now terminates the whole server process tree, not just the direct child. The embedded server runs as
omniroute.exe-as-node (ELECTRON_RUN_AS_NODE) and spawns grandchildren (embedded services, MITM proxy, tunnels); on WindowsChildProcess.kill()only terminates the direct child, so survivors keptomniroute.exelocked — the process "hung in memory" after Exit and updates failed with "file in use". NewkillProcessTree()helper usestaskkill /PID <pid> /T /Fon Windows (signal-based on POSIX); wired intostopNextServer, thewaitForServerExitforce-kill, andinstallUpdate. (#3347 — thanks @Flexible78) - fix(proxy): proxy auto-selection is now opt-in (new
PROXY_AUTO_SELECT_ENABLEDflag, default off). Previously a single proxy in the registry silently became a global fallback for all provider connections (the Step-11 fallback listed every registry proxy, ignoring assignments and per-connectionproxy_enabled). It now no-ops unless the operator enables the flag. (#3332 — thanks @hertznsk) - fix(cli): write the OpenCode config to
~/.config/opencode/opencode.jsonon all platforms — on Windows OmniRoute wrote to%APPDATA%\opencode\but OpenCode reads from%USERPROFILE%\.config\opencode\(XDG), so dashboard-saved config silently had no effect. (#3330 — thanks @abdulkadirozyurt) - fix(catalog): remove
minimaxai/minimax-m3from the NVIDIA NIM tier — NVIDIA does not host it yet, so every request 404'd (404 page not found), while siblingminimax-m2.7on the same provider works. MiniMax M3 stays available on the tiers that actually serve it. (#3329 — thanks @mikmaneggahommie) - fix(sse): treat MiniMax M3 as multimodal so the compression layer no longer strips image parts from vision requests —
lite.ts modelSupportsVisionnow keeps images forminimax-m3*(see also the registrysupportsVisionalignment in Maintenance). (#3328 — thanks @diegosouzapw) - fix(oauth): Kiro Builder ID token import no longer fails with "Bad credentials" —
validateImportTokenonly ever tried the social-auth refresh; it now uses the cached AWS SSOclientId/clientSecret(~/.aws/sso/cache/*.json) and the OIDC refresh path (authMethod: "builder-id"), with a TDD harness. (#3333 — thanks @quanturbo / @diegosouzapw) - fix(provider-proxy): honor per-account proxy toggles — a connection with
proxy_enabled = falseis no longer forced through an assigned/registry proxy. (#3349 — thanks @rdself) - fix(providers): reduce proxy label noise on the provider page (clearer proxy assignment/state display). (#3346 — thanks @wilsonicdev)
- fix(noauth): expose only usable model aliases for no-auth providers, so the catalog no longer advertises aliases that can't actually be called. (#3345 — thanks @oyi77)
- fix(duckduckgo): restore the bare
Responsecontract for the DuckDuckGo/browser-backed executor (rebased onto the cycle), fixing a wrapping-contract regression. (#3323 — thanks @oyi77 / @diegosouzapw) - fix(dashboard): drop the duplicate "Distribute Proxies" button on the provider page — it rendered twice at once (provider toolbar + accounts-list header) whenever connections existed and none were selected. The toolbar button (global) and the per-tag-group buttons remain. (#3352 — thanks @diegosouzapw)
- fix(electron): ship
loginManager.jsin the packaged app — #3292 added it (and arequire("./loginManager")inmain.js) without adding it to electron-builder'sbuild.files, so the packaged app crashed at startup with "Cannot find module" on the Linux/macOS smoke tests. Plus a regression test asserting every localrequire("./x")in the Electron entry points is shipped. (#3334 — thanks @diegosouzapw) - fix(startup): correct the #3292 auto-refresh daemon import (
@/open-sse/...→@omniroute/open-sse/services/autoRefreshDaemon); the@/alias maps tosrc/, so the daemon silently never ran in the built standalone (non-fatal "Cannot find module", caught at runtime). Adds a regression test banning@/open-sse/*imports insrc/. (#3335 — thanks @diegosouzapw) - fix(electron): wrap
autoUpdater.checkForUpdates()so a 404/offline/rate-limited update check can no longer surface as an unhandled rejection (theerrorevent still notifies the user); fixes the macOS-intel packaged-app smoke failure. (#3339 — thanks @diegosouzapw) - fix(dashboard): stop the infinite render loop on
/dashboard/cli-agents/hermes-agent—HermesAgentToolCardlistedcurrentRolesin the config-load effect's deps whileloadCurrentConfig()setcurrentRolesto a fresh object on every fetch, so the effect re-fired → refetched → re-set forever (the page spun and spammedGET /api/cli-tools/hermes-agent-settingsin the console; it manifested only on the always-expanded detail page).loadCurrentConfigis now memoized and the batch-seed readscurrentRolesvia a functional update, so the effect runs once. Adds a jsdom regression test asserting the settings endpoint is fetched a bounded number of times. (#3353 — thanks @diegosouzapw) - fix(dashboard): the Usage Analytics card now surfaces the real backend error (status + message) instead of a generic placeholder when
/api/usage/analyticsfails — a new sharedfetchError.tshelper extracts a useful message. (#3356 — thanks @diegosouzapw)
📝 Maintenance
- fix(review): harden the per-provider custom-headers feature surfaced by the
/review-reviewsbattery —updateProviderNodeno longer wipes storedcustom_headers_jsonon a partial update that omits the field;customHeadersSchemareuses the canonicalupstreamHeadersRecordSchemaguards (CRLF/control-char/length/16-max) and rejects auth header names via a single sharedisForbiddenCustomHeaderName()denylist (executor + schema no longer keep divergent copies); custom headers now reach the wire foranthropic-compatible-cc-*nodes and override the executor's ownContent-Type/Acceptcase-insensitively instead of duplicating them; androwToCamelnormalizes a NULL_jsoncolumn tobaseKey: null. (#3350 — thanks @diegosouzapw) - fix(catalog): flag every
minimax-m3registry entrysupportsVision(not just the opencode free tier) so the vision-bridge guardrail and the compression layer agree the model is multimodal on all tiers (completes #3328). (thanks @diegosouzapw) - fix(oauth): Kiro Builder ID import forwards the requested
regionto the OIDC validation refresh (no longer pinned tous-east-1), prefers the region-matching cached SSO client registration over the first file found, and fallsexpiresInback to 3600 on the OIDC path. (thanks @diegosouzapw) - fix(db): migration
095gains anisSchemaAlreadyAppliedguard so a fresh DB (whereSCHEMA_SQLalready createscustom_headers_json) skips it cleanly instead of throwing-then-catching a duplicate-column error. (thanks @diegosouzapw) - test: align stale cycle tests with shipped behavior — NVIDIA
minimaxai/minimax-m3removal (#3329), the 29th feature flag (PROXY_AUTO_SELECT_ENABLED, #3332), and the OpenCode~/.configpath on Windows (#3330). (thanks @diegosouzapw) - docs: add a documentation comment to the exported
GEThandler in the context-analytics route. (#3337 — thanks @Lang-Qiu) - docs(i18n): translate 25 core documentation files to Indonesian. (#3348 — thanks @KrisnaSantosa15)
🙌 Contributors
Thanks to everyone whose work landed in v3.8.14:
| Contributor | PRs / Issues |
|---|---|
| @pizzav-xyz | #3338 |
| @quanturbo | #3333 |
| @oyi77 | #3323, #3345 |
| @rdself | #3349 |
| @wilsonicdev | #3346 |
| @hertznsk | #3332 |
| @abdulkadirozyurt | #3330 |
| @mikmaneggahommie | #3329 |
| @Flexible78 | #3347 |
| @Lang-Qiu | #3337 |
| @KrisnaSantosa15 | #3348 |
| @nullbytef0x | #3357 |
| @Ardem2025 | #3358 |
| @diegosouzapw | maintainer — #3334, #3335, #3336, #3339, #3350, #3352, #3353, #3356; review/hardening across the cycle |
What's Changed
- Release v3.8.14 by @diegosouzapw in #3340
- docs(changelog): complete v3.8.14 (#3356 + @nullbytef0x/@Ardem2025 credits) by @diegosouzapw in #3362
- fix(ci): drop explicit any pushing chatCore over the t11 any-budget by @diegosouzapw in #3364
- test(translator): align gemini-2.5-flash maxOutputTokens cap to 65536 (#3358 drift) by @diegosouzapw in #3367
Full Changelog: v3.8.13...v3.8.14