* 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>
The webpack build failed: route.ts imported '../internal/codex-responses-ws/
modelResolution' (resolves to api/v1/internal/, which doesn't exist). The module
lives at api/internal/codex-responses-ws/. Switched to the @/app/api/... alias.
typecheck/tests passed (tsx resolves leniently; tests import the module directly),
only the production build caught it.
When the Codex CLI falls back from WebSocket to HTTP (after 1008 Policy
Violation or reconnect exhaustion), it POSTs to /v1/responses with the
bare model id it was configured with (e.g. "gpt-5.5") — never the
provider-prefixed form. OmniRoute's normal routing resolved that bare id
to openrouter, not codex, producing:
"No credentials for provider: openrouter"
Fix: add resolveResponsesApiModel() (extending modelResolution.ts) and
call it in /v1/responses before delegating to handleChat. The function
applies the same codex-preference logic as resolveCodexWsModelInfo:
bare "gpt-5.5" → codex/gpt-5.5 has a codex provider → rewrite ✅
bare "gpt-4o" → codex/gpt-4o not in registry → pass through ✅
"anthropic/x" → has "/" → skip resolution → pass through ✅
Errors are caught; original request is returned on any failure.
8 unit tests added (TDD — watched each fail before implementing).
Refs: openai/codex#15492, openai/codex#13041, openai/codex#13039
No-auth providers (OpenCode Free) have no connection row, so handleImportModels returned early and /api/providers/[id]/models 404'd — the import button silently no-op'd. The route now serves the provider's registry/static model catalog when called with a no-auth provider id, and handleImportModels falls back to the provider id when there is no connection. Test: route returns the opencode catalog (source local_catalog) and still 404s for unknown ids.
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.
Support MiniMax Coding Plan quota responses that expose remaining usage as
percentages instead of request counts, including the text quota surfaced under
the `general` model.
Also document Codex CLI WebSocket configuration, clarifying that bare ChatGPT
model ids must be used instead of `codex/`-prefixed ids.
Symptom: freshly-added Codex accounts (e.g. davi/gabriel) showed "No quota data"
even when healthy. Root cause: the quota path reuses the access_token without
refreshing rotating providers (#3019, anti Auth0 family-revocation cascade), so a
Codex account whose short-lived access_token has expired can never surface quota
from the sync — the live fetch returns "Codex token expired".
Fix (opt-in, cascade-safe):
- refreshAndUpdateCredentials gains `allowRotatingRefresh` + a pure exported gate
`shouldAttemptRotatingRefresh`. The actual token mint is wrapped in
`serializeRefresh` (one refresh at a time per Auth0 rotation group) — so even N
concurrent per-account requests can never refresh siblings in parallel.
- The BULK scheduler (syncAllProviderLimits, concurrent) keeps the flag OFF →
#3019 fully preserved (guardian test codex-quota-sync-no-proactive-refresh stays
green). Only the on-demand, per-connection path (`GET /api/usage/[connectionId]`)
opts in.
- Frontend: the quota page auto-fetches LIVE on open for the VISIBLE connections
that have no cached quota (scoped to what's on screen — not all connections —
and skips entries already cached), so expired-token Codex accounts surface real
quota automatically and cascade-safely.
Adds unit coverage for the gate (bulk skips rotating, on-demand allows; non-rotating
always eligible). typecheck / lint clean.
Remove the Petals executor from registration and exports.
Improve type safety by replacing broad any usage in MCP tool registration
with inferred types and documenting dynamic handler type limitations.
Add request validation for the agent bridge cert route and expand tests to
ensure switch buttons explicitly declare type="button", preventing implicit
form submissions.
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!
POST /api/providers/[id]/refresh was the last unguarded proactive-refresh
entry point for rotating-refresh providers. The dashboard auto-refreshes every
expiring connection on a page load (and an old cached frontend bulk-calls this
endpoint), so each Codex account's single-use refresh_token got rotated; Auth0
then revoked the whole token family (openai/codex#9648) — every account but the
last died with [403] <!DOCTYPE. refreshAndUpdateCredentials (quota-sync) and the
connection-test route were already guarded; this closes the gap.
The route now skips proactive rotation when rotationGroupFor(provider) !== null
and returns {success,skipped,message}, deferring genuine expiry to the reactive,
serialized 401 path. Non-rotating providers keep refreshing on demand.
Test: codex-manual-refresh-rotating-guard (source-assertion, matches the
token-refresh-race-comprehensive #2941 style).
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.
Guard PUT and DELETE on /api/combos/[id] to return 409 when the target combo name
starts with QUOTA_MODEL_PREFIX; filter isHidden combos from the Combos page state
so quota-managed combos never appear as editable rows there.
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
Add a standalone OAuth provider `agy` (Antigravity CLI) next to gemini-cli/antigravity.
It reuses the antigravity inference backend (identical Google client_id +
daily-cloudcode-pa.googleapis.com endpoint, executor and token-refresh) but ships its own
model catalog — including the Claude models the backend exposes (claude-opus-4-6-thinking,
claude-sonnet-4-6) — its own account pool, and four ways to connect:
- token-file import (paste/upload the agy oauth token JSON)
- auto-detect a local CLI login (~/.gemini/antigravity-cli/antigravity-oauth-token)
- browser OAuth (via the shared OAuthModal Google loopback flow)
- bulk / ZIP import
New routes: POST /api/providers/agy-auth/{import,import-bulk,zip-extract,apply-local}.
Catalog pinned from the live :fetchAvailableModels endpoint. Docs (openapi.yaml,
ENVIRONMENT.md, .env.example, CHANGELOG) updated; new unit tests for registration,
the token parser, and route auth-hardening.