github diegosouzapw/OmniRoute v3.8.14

4 hours ago

✨ 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 customHeaders field on provider nodes (custom_headers_json column, migration 095). Hardened on merge: values/names validated through the canonical upstreamHeadersRecordSchema (CRLF/control-char/length/16-max) with a single shared isForbiddenCustomHeaderName() denylist (hop-by-hop + auth), applied case-insensitively, and honored for anthropic-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 of new URL(request.url).origin, so a management-authenticated caller can no longer redirect the credential-bearing internal request to an arbitrary host via the Host header (CodeQL js/request-forgery, critical). Shipped to Docker/Electron in v3.8.13; reaches npm here (npm 3.8.13 was immutable). (#3336, CodeQL #323 — thanks @diegosouzapw)

🔧 Bug Fixes

  • fix(translator): every Gemini/Vertex functionDeclaration.parameters is now coerced to an OBJECT-typed schema before cleaning. Clients like GitHub Copilot send some tools (e.g. terminal_last_command) whose parameters is present but lacks a top-level type: "object" (just { properties }, a scalar type, or {}); these slipped through buildGeminiTools' params || default guard 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 shared open-sse/utils/textualToolCall.ts (new isValidToolCallHeaderPrefix) and adds gemini-2.5-flash/gemini-3.5-flash-low model 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 Windows ChildProcess.kill() only terminates the direct child, so survivors kept omniroute.exe locked — the process "hung in memory" after Exit and updates failed with "file in use". New killProcessTree() helper uses taskkill /PID <pid> /T /F on Windows (signal-based on POSIX); wired into stopNextServer, the waitForServerExit force-kill, and installUpdate. (#3347 — thanks @Flexible78)
  • fix(proxy): proxy auto-selection is now opt-in (new PROXY_AUTO_SELECT_ENABLED flag, 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-connection proxy_enabled). It now no-ops unless the operator enables the flag. (#3332 — thanks @hertznsk)
  • fix(cli): write the OpenCode config to ~/.config/opencode/opencode.json on 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-m3 from the NVIDIA NIM tier — NVIDIA does not host it yet, so every request 404'd (404 page not found), while sibling minimax-m2.7 on 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 modelSupportsVision now keeps images for minimax-m3* (see also the registry supportsVision alignment in Maintenance). (#3328 — thanks @diegosouzapw)
  • fix(oauth): Kiro Builder ID token import no longer fails with "Bad credentials" — validateImportToken only ever tried the social-auth refresh; it now uses the cached AWS SSO clientId/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 = false is 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 Response contract 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.js in the packaged app — #3292 added it (and a require("./loginManager") in main.js) without adding it to electron-builder's build.files, so the packaged app crashed at startup with "Cannot find module" on the Linux/macOS smoke tests. Plus a regression test asserting every local require("./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 to src/, 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 in src/. (#3335 — thanks @diegosouzapw)
  • fix(electron): wrap autoUpdater.checkForUpdates() so a 404/offline/rate-limited update check can no longer surface as an unhandled rejection (the error event 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-agentHermesAgentToolCard listed currentRoles in the config-load effect's deps while loadCurrentConfig() set currentRoles to a fresh object on every fetch, so the effect re-fired → refetched → re-set forever (the page spun and spammed GET /api/cli-tools/hermes-agent-settings in the console; it manifested only on the always-expanded detail page). loadCurrentConfig is now memoized and the batch-seed reads currentRoles via 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/analytics fails — a new shared fetchError.ts helper extracts a useful message. (#3356 — thanks @diegosouzapw)

📝 Maintenance

  • fix(review): harden the per-provider custom-headers feature surfaced by the /review-reviews battery — updateProviderNode no longer wipes stored custom_headers_json on a partial update that omits the field; customHeadersSchema reuses the canonical upstreamHeadersRecordSchema guards (CRLF/control-char/length/16-max) and rejects auth header names via a single shared isForbiddenCustomHeaderName() denylist (executor + schema no longer keep divergent copies); custom headers now reach the wire for anthropic-compatible-cc-* nodes and override the executor's own Content-Type/Accept case-insensitively instead of duplicating them; and rowToCamel normalizes a NULL _json column to baseKey: null. (#3350 — thanks @diegosouzapw)
  • fix(catalog): flag every minimax-m3 registry entry supportsVision (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 region to the OIDC validation refresh (no longer pinned to us-east-1), prefers the region-matching cached SSO client registration over the first file found, and falls expiresIn back to 3600 on the OIDC path. (thanks @diegosouzapw)
  • fix(db): migration 095 gains an isSchemaAlreadyApplied guard so a fresh DB (where SCHEMA_SQL already creates custom_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-m3 removal (#3329), the 29th feature flag (PROXY_AUTO_SELECT_ENABLED, #3332), and the OpenCode ~/.config path on Windows (#3330). (thanks @diegosouzapw)
  • docs: add a documentation comment to the exported GET handler 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

Full Changelog: v3.8.13...v3.8.14

Don't miss a new OmniRoute release

NewReleases is sending notifications on new releases.