mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
2c0fd047049751e73acdbc97e6982e6c19b8e0c7
25 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2c0fd04704 |
feat: implement 5 harvested feature requests (#4239, #4155, #3841, #3266, #4240) (#4313)
* feat(providers): add OpenAdapter, dit.ai and TokenRouter OpenAI-compatible providers (#4239, #4155, #3841) Three community-requested OpenAI-compatible aggregators register as standard named OpenAI-style providers (the zenmux pattern): live /v1/models discovery via NAMED_OPENAI_STYLE_PROVIDERS, falling back to a seeded catalog on upstream error. No custom executor/translator — default OpenAI passthrough. - OpenAdapter https://api.openadapter.in/v1 (free tier) #4239 - dit.ai https://api.dit.ai/v1 (dynamic-pricing) #4155 - TokenRouter https://api.tokenrouter.com/v1 (free MiniMax model) #3841 Base paths confirmed live (each returns a 401 OpenAI-style error body). Seed catalogs are intentionally minimal (author/doc-cited ids only; TokenRouter deepseek ids come from production via #3946); full upstream model lists arrive through live discovery once a key is configured. * feat(combo): per-step account allowlist for round-robin over a connection subset (#3266) A combo model step can now carry a first-class `allowedConnectionIds` so a round-robin / weighted strategy is scoped to a subset of a provider's connections (e.g. {foo1, foo2}) without hand-pinning one step per account. - steps.ts: parse `allowedConnectionIds` on the model step (trim + drop empty) - comboStructure.ts: second writer — propagate the step allowlist onto the resolved target (tag routing is the first writer) - autoStrategy.ts: when a step allowlist AND tag routing both apply, intersect them (most-restrictive wins); empty intersection drops the target - builderDraft.ts + combos UI: optional 'Restrict to accounts' picker in the Precision step editor (a pinned single account still takes precedence) The downstream credential-selection filter (auth.ts) already honours allowedConnectionIds, so a round-robin scoped to {foo1, foo2} provably never selects foo3/foo4 (regression test included). Ships the enhancement only; the #2829 bug-triage half stays open pending the reporter. * feat(dashboard): category (media serviceKind) filter on the providers page (#4240) Add a media-category filter row (Image / Video / Music / Text→Speech / Speech→Text / Embedding) to /dashboard/providers that composes with the existing search, free-only and 'show configured only' filters. - serviceKindIndex.ts: client-side resolver unioning a provider's declared serviceKinds with the registry-derived media kinds (memoised) - providerPageUtils: filterConfiguredProviderEntries gains a serviceKindFilter argument; threaded through every provider section on the page - ProviderSummaryCard: a second chip row drives the serviceKind filter Membership is derived from the backend media registries, so a provider that serves a kind is surfaced even when it never declared serviceKinds — keeping the UI in lockstep with the backend (mirrors the media-providers pages). * chore(quality): rebaseline file-size for the v3.8.30 harvested features Four frozen files grew from their own additive feature wiring (#4239/#4155/#3841 providers, #3266 combo allowlist UI, #4240 serviceKind filter): - src/shared/constants/providers.ts 3169->3213 (3 provider entries) - src/app/api/providers/[id]/models/route.ts 2554->2560 (3 NAMED set entries) - src/app/(dashboard)/dashboard/combos/page.tsx 4350->4385 (allowlist picker) - src/app/(dashboard)/dashboard/providers/page.tsx 1925->1927 (serviceKind state) All cohesive additive wiring at existing chokepoints; rationale recorded in the _rebaseline_2026_06_19_v3830_harvest_features key. |
||
|
|
7ce875f404 |
feat(providers): provider model sweep — live discovery, refreshed catalogs, dead-provider cleanup (#4324)
* feat(providers): refresh core official model catalogs (sweep lote 1) Adiciona modelos GA atuais (verificados online) aos provedores oficiais core: - openai: gpt-5.5-pro, gpt-5.4-pro - anthropic: claude-opus-4.8 + claude-fable-5 (sampling fixo 4.7+, espelha 4.7), claude-opus-4.5 - groq: qwen/qwen3.6-27b, openai/gpt-oss-safeguard-20b - xai: grok-build-0.1 Fase 4 do provider-model-sweep. provider-consistency/file-size/typecheck:core verdes. * feat(providers): wire live /models discovery for 7 openai-style providers (sweep lote 2) venice, deepinfra, wandb, pollinations, nscale, inference-net and moonshot each expose a real live `<baseUrl>/models` catalog (the sweep probed each upstream), but were classified fixed-official, so import served their small hardcoded seed and re-staled the catalog. Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does a live `<baseUrl>/models` fetch, keeping the registry seed only as the offline fallback — same fix shape as #4249 (vercel-ai-gateway) / #4202 (zenmux) / #3976 (llm7/byteplus). siliconflow was already classified. TDD regression in tests/unit/provider-sweep-live-discovery.test.ts pins each derived /models URL + the local-seed fallback path. file-size baseline bumped 2538->2548 (+10 = 7 Set entries + 3-line comment; not extractable). * feat(providers): wire live /models discovery for 12 aggregator marketplaces (sweep lote 3) crof, featherless-ai, ovhcloud, sambanova, orcarouter, uncloseai, opencode-go, baseten, hyperbolic, nebius, scaleway and together are GPU-cloud / aggregator marketplaces hosting large, volatile OSS catalogs. The sweep probed each and confirmed a live `<baseUrl>/v1/models` endpoint (200 public or 401/403 = exists + keyed), yet they were classified fixed-official and served a small hardcoded seed. Add them to NAMED_OPENAI_STYLE_PROVIDERS so import does a live `<baseUrl>/models` fetch (graceful fallback to the registry seed on any upstream error), keeping the catalog fresh instead of re-staling a hardcoded list. Extends tests/unit/provider-sweep-live-discovery.test.ts to 20 cases pinning each derived /models URL. file-size baseline bumped 2548->2564 (+16; not extractable). * feat(providers): add verified new models to nvidia, meta-llama, morph (sweep lote 4) Curated first-party / specialist menus (kept hardcoded — their per-model flags like toolCalling/supportsReasoning can't be inferred from a live catalog): - nvidia: + stepfun-ai/step-3.7-flash, deepseek-ai/deepseek-v4-flash (supportsReasoning), moonshotai/kimi-k2.6 — all confirmed present in the live NIM /v1/models catalog. minimaxai/minimax-m3 deliberately left out per #3329 (now listed, but its inference still needs confirmation before re-adding). - meta-llama: + Llama-3.3-8B-Instruct. - morph: + morph-qwen35-397b, morph-minimax27-230b, morph-qwen36-27b, morph-dsv4flash (Morph-hosted fast models, with context lengths). Skipped this batch after review: upstage solar-pro2 (older than the solar-pro3 already in the registry); longcat LongCat-2.0-Preview (deliberately commented out). * feat(providers): refresh Chinese first-party model catalogs, online-verified (sweep lote 5) Each registry held a single stale id; refreshed against official docs after per-id online verification (subagent research, cross-checked against first-party sources). Rejected/omitted entries are documented inline. - baidu: + 15 ERNIE ids (5.0/5.1 are the current flagships, confirmed live on Qianfan). - doubao: + 8 Seed-2.0/1.x dated Ark ids (Seed 2.0 GA 2026-02-14, confirmed real). - sensenova: + 8 SenseChat/SenseNova ids (V6.5-Pro flagship; 6.7-flash-lite lowercase). - tencent: + hunyuan-turbos-latest/t1-latest/vision/functioncall/lite. Dropped legacy standard/-256K/code/role + pinned turbos-20250226. NOTE: legacy Hunyuan platform EOLs turbos/t1 on 2026-06-22 (migrating to TokenHub/hy3-preview) — revisit. - baichuan: + Baichuan4-Turbo/Air, Baichuan3-Turbo/-128k (official pricing page). - stepfun: + step-3.7-flash (flagship), step-3.5-flash(-2603), step-1o-turbo-vision. - iflytek: + 4.0Ultra, max-32k, generalv3, pro-128k, lite (exact HTTP domains). - sparkdesk: + 4.0Ultra, generalv3, pro-128k. Rejected spark-x (separate /v2|/x2 endpoint). - volcengine: + doubao-seed-2-0-pro-260215, kimi-k2-5-260127 (Ark-hosted). * feat(providers): add verified models to kie, nlpcloud, publicai (sweep lote 6) - kie: + claude-opus-4-8, gemini-3-5-flash (current flagships the proxy surfaces; gemini-3-pro skipped — registry already carries the newer gemini-3-1-pro). - nlpcloud: + chatdolphin, dolphin (branded models), finetuned-llama-3-70b, llama-3-1-405b. Host confirmed reachable. - publicai: + Apertus-8B, Gemma-SEA-LION-v4-27B, Olmo-3-7B, EuroLLM-22B (open models). Skipped after review: minimax M2/M2.1 (older than the M2.5 floor the registry curates); yi (api.lingyiwanwu.com degraded + 01.AI exited foundation models); llamagate (host llamagate.ai unreachable, code 000) — both flagged for Track C. * feat(providers): finish Track B tail — cloudflare-ai, bailian, suno, +5 (sweep lote 7) - cloudflare-ai: + 7 Workers AI catalog ids (llama-3.3-70b-fp8-fast, qwen2.5-coder-32b, qwq-32b, llama-3.2-3b, glm-4.7-flash, kimi-k2.6, gemma-4-26b). - bailian-coding-plan: + qwen3.7-plus, qwen3-coder-plus, qwen3-coder-next, glm-4.7. - suno: + chirp-fenix (V5.5), chirp-crow (V5). - monsterapi: + Meta-Llama-3.1-8B, Llama-3.3-70B. - huggingchat: + Qwen3-235B-A22B, Mistral-Small-3.1-24B. - vertex-partner: + claude-opus-4-8, claude-opus-4-6. - puter: + google/gemini-3.5-flash. - codestral: + codestral-2508. Skipped after verification: windsurf + devin-cli — docs.devin.ai exposes DASHED ids (claude-opus-4-8-low, MODEL_PRIVATE_4 for "Grok Code Fast 1", minimax-m2-5) while the registry uses DOTTED (claude-opus-4.7-max); id-form ambiguity needs owner confirmation before adding 13+ entries. leonardo/ideogram (image UUID-vs-friendly convention), glmt (shared GLM_SHARED_MODELS, redundant with the live `glm` provider). * fix(providers): drop retired models, add codestral-2405 forward (sweep lote 8, Track C C1) Confirmed removals that interacted with the sweep's adds: - codestral: drop codestral-2405 (retired 2025-06-16, Mistral official docs) from the menu + add a codestral-2405 -> codestral-2508 deprecation alias so old configs forward. - monsterapi: drop llama-3-8b-fuse (no longer evidenced in the catalog). - volcengine: drop kimi-k2-thinking-251104 (retired on Ark; superseded by kimi-k2-5-260127). * chore(providers): mark 6 dead providers deprecated (sweep lote 9, Track C C2) The sweep verified these providers are no longer reachable/operational, so flag them with the existing deprecation mechanism (deprecated:true + a deprecation risk notice) instead of silently offering non-working options. Conservative — plumbing (executors/icons/free-catalogs) is left intact; only the UI-facing metadata changes. - kluster, glhf, predibase, inclusionai, galadriel: api host DNS no longer resolves. - phind: API shut down 2026-01 (www.phind.com/api/chat no longer serves). Not touched: gemini-cli (Google OAuth infra still live), qwen (already deprecated), chipotle (easter-egg, out of scope). file-size baseline bumped 3169->3198. * fix(providers): replace retired LongCat-Flash line with LongCat-2.0-Preview (sweep lote 10) The LongCat-Flash-* models (Lite/Chat/Thinking/Omni-2603) were officially retired 2026-05-29; the current longcat.chat/platform docs expose only LongCat-2.0-Preview (confirmed via WebFetch of the live API docs). Swap the stale 4-model seed for the single current model so the provider stops offering dead ids. * chore(quality): reconcile antigravity.ts file-size baseline 1664->1680 #4309 (Undici socket-leak fix) grew antigravity.ts by +26 lines but its file-size baseline was not bumped at merge time; reconcile it here on the combined tree so the release file-size gate stays green (Rule #9, release-volatile reconciliation). |
||
|
|
831bd0a7b3 |
feat(quality): make the a11y gate real (@axe-core/playwright in nightly) (#4321)
* feat(quality): instala @axe-core/playwright + allowlist (T13)
Pré-requisito do gate de a11y real. O spec tests/e2e/a11y.spec.ts já tem o
mecanismo REQUIRE_AXE=1 que falha se o pacote estiver ausente quando exigido —
faltava só o pacote + o job nightly + o baseline real.
- @axe-core/playwright@^4.11.3 em devDependencies (via --package-lock-only,
não toca o node_modules compartilhado das worktrees)
- adicionado à dependency-allowlist (check:deps OK, 127 deps)
- vuln-ratchet OK (1 moderate pré-existente, baseline 10 — axe não regride)
* feat(quality): job a11y nightly (REQUIRE_AXE=1) + gate per-PR (T13)
- novo job 'a11y' em nightly-resilience.yml: o webServer do Playwright builda o
Next (build-next-isolated.mjs) e sobe o standalone sozinho (sem artefato pré-
buildado como o test-e2e do ci.yml), REQUIRE_AXE=1 roda a análise axe real.
- gate de nightly no spec: a11y.spec.ts é casado pelo glob 'tests/e2e/*.spec.ts'
do job per-PR (9 shards); sem gate, instalar @axe-core/playwright ligaria axe
em TODO PR (e falharia em baseline 0). Os 4 testes de página agora skipam a
menos que REQUIRE_AXE=1 — per-PR fica rápido/verde, nightly roda real.
- zizmor 139->145: +3 drift pré-existente da base
|
||
|
|
facbb1964f |
feat(quality): unblock R1 — test-redundancy measurement via disableBail (#4322)
* feat(quality): config stryker disableBail para medir redundância (R1) Estende stryker.conf.json com disableBail:true (killedBy lista TODOS os killers, não só o primeiro) + incremental:false (medição limpa). One-off, não toca o nightly — só roda via mutation-redundancy.yml. * feat(quality): workflow on-demand de mutação disableBail (R1) mutation-redundancy.yml (workflow_dispatch): roda os 6 leaf-batches combo+chatCore do nightly com o override disableBail e sobe os reports. Batches granulares (d/e/f/ g/h/i) em vez de 2 mega-batches: disableBail é mais caro e Stryker só grava o report se COMPLETAR, então cada batch cabe nos 300min. zizmor 139->145: +3 drift de base + 3 do workflow (@vN, convenção do repo). Ver nota. * feat(quality): mutation-radiography --candidates (lista de prune R1) Reusa aggregateRadiography (DRY) em vez de um script novo: redundancyCandidates() retorna 🔴 empty ∪ 🟠 redundant (zero kills únicos) = candidatos a prune. Sob disableBail o killedBy é completo, então 🟠 redundant fica ACURADO. CLI --candidates emite a lista com o aviso do gate humano (excluir segurança/contrato/repro). |
||
|
|
ad4338449c |
fix(quality): complexity gate covers bin/+electron + tracked-artifacts in pre-commit (#4318)
* fix(quality): complexity gate varre bin/ + electron (6A.11, fake-green fix)
ESLINT_ARGS passava só 'src open-sse', mas o config eslint.complexity.config.mjs
e o complexity-baseline.json já documentavam o escopo src+open-sse+electron+bin.
A edição do scan nunca tinha sido aplicada: o gate alegava cobrir bin/electron e
nunca os varria (fake-green — uma god-function nova em bin/ passava verde).
- exporta ESLINT_ARGS + adiciona 'electron' e 'bin' (casa o config)
- teste de build trava o escopo do scan
- baseline 1887->1888: electron+bin medem 0 (widening 0-custo); o +1 é drift
pré-existente de src/open-sse da base
|
||
|
|
0abc00cf2a |
fix(translator): clamp Responses API call_id to 64 chars (#4317)
* fix(translator): clamp Responses API call_id to 64 chars (port from 9router#396) The OpenAI Responses API rejects call_id values longer than 64 characters with a 400. Long upstream tool-call ids (some clients emit ids well over the limit) were forwarded verbatim. Clamp the id deterministically on both the function_call item and its matching function_call_output, so the pair stays matched through the orphaned-output filter and the request is accepted. Reported-by: ngapngap (https://github.com/decolua/9router/issues/393) Co-authored-by: Anurag Saxena <17893081+anuragg-saxenaa@users.noreply.github.com> Co-authored-by: ngapngap <27039619+ngapngap@users.noreply.github.com> * chore(quality): bump translator-openai-responses-req file-size baseline 1011->1047 The clamp-call_id regression test (+36 lines) grew the test file past its frozen baseline; bump it in the same change (Rule #9). --------- Co-authored-by: Anurag Saxena <17893081+anuragg-saxenaa@users.noreply.github.com> Co-authored-by: ngapngap <27039619+ngapngap@users.noreply.github.com> |
||
|
|
9a678497ad |
fix(sse): stop combo at the first body-specific 400 (#4279) (#4316)
The #2101 guard that detects a body-specific 400 (context overflow / malformed / model-access-denied) logged "stopping combo" but executed a bare `break`, which only exits the inner retry loop. executeTarget then returns null, and the outer target loop treats null as "this target produced nothing" and advances to the next model — so the guard never actually stopped fallback, and a combo of N targets that all reject the same request body tried all N (the report shows a 143-model Codex combo marching through every target). Surface the 400 via the {ok,response} contract (mirrors the 499 client-disconnect path) so the outer loop resolves the combo and stops. Regression test: a 3-target priority combo whose targets all return a body-specific 400 must stop after target 1 (RED before, GREEN after). Closes #4279 |
||
|
|
db7c8c5edc |
fix(pollinations): handle auth-required premium models (#4266)
Pollinations now requires API keys for premium models (claude, gemini, midijourney). The executor surfaces an actionable 401 with the keyless-model list, chatCore preserves the upstream HTTP status (401 -> authentication_error instead of 502), and the free catalog marks the premium models as key-required. Rebased onto the release tip and reconciled the file-size baseline (chatCore 5128). Thanks @oyi77. |
||
|
|
84bf5dc7de |
fix(sse): recover reconstructed message when Responses terminal output is textless (#3948) (#4315)
A Responses-API target (codex/cx) streams from upstream even on stream:false. Its terminal `response.completed` snapshot can carry a non-empty `output` that lacks the assistant message item (e.g. only a reasoning item) even though the streamed output_text deltas reconstructed a full message. parseSSEToResponsesOutput preferred the terminal output wholesale, dropping the reconstructed text → empty content on stream:false (hit via n8n, which defaults to stream:false). Fall back to the reconstructed delta output when the terminal output has no message item but the reconstruction does; the terminal snapshot still wins when it already carries the message. Regression test feeds a synthetic codex SSE (reasoning-only terminal + message deltas) and asserts the assistant text survives, plus a control case where the terminal carries the message. Closes #3948 |
||
|
|
6103288c48 |
fix(executors): preserve tool-name casing on native Claude OAuth (#4307) (#4314)
The native-Claude OAuth anti-fingerprint cloak renames a tool named `read` to `Read` on the wire and records the reverse alias on a non-enumerable `_toolNameMap`, which the response side un-cloaks to restore the client's original casing. Since v3.8.27 (#3941/#3968) `execute()` returned a JSON-round-tripped `serializedBody` as `transformedBody`; the round-trip drops the non-enumerable map, so the restore saw an empty map and the cloaked `Read` streamed verbatim to the client. Re-attach the live `_toolNameMap` onto the serialized body before returning (non-enumerable, mirrors antigravity.ts::attachToolNameMap) so tool-name casing round-trips correctly. Regression test exercises base.ts execute() through the claude-OAuth cloak path and asserts the returned transformedBody carries the reverse map. Closes #4307 |
||
|
|
bbc9d1e1c5 |
feat(quality): seed per-module mutationScore floors + blocking aggregation ratchet (T3) (#4305)
First full mutation measurement landed (run 27823984918, the split nightly from #4272): 31 modules now have a COVERED mutation score. T3 turns that into an enforced gate. Seed: 31 `mutationScore.<path>` floors in quality-baseline.json at ~2pt below the measured score (absorbs run-to-run variance), direction:up, dedicatedGate:true. dedicatedGate means the generic check-quality-ratchet SKIPS them (check-quality-ratchet.mjs:62) — they are enforced only by check-mutation-ratchet.mjs. Range: memorySkillsInjection 13.49 (weakest) to headers 94.29 (strongest); the security/critical floors: auth 52.57, accountFallback 68.38, routeGuard 76.08, circuitBreaker 56.94, error 43.83, publicCreds 59.76. Gate: a new `mutation-ratchet` job in nightly-mutation.yml runs AFTER all batches (needs: stryker, if: always()), downloads every mutation report, and ratchets the MERGED per-module scores with `check-mutation-ratchet --ratchet` (blocking). It must aggregate because the split batches each emit a PARTIAL view of a file (auth.ts in a1+a2, accountFallback in b1+b2) — a per-batch ratchet would compare half a file against the whole-file floor. check-mutation-ratchet unions same-file mutants across reports (#4272). A module dropping below its floor fails the run; missing reports (upload flake) are skipped. Verified: ratchet exits 0 on the seeded measurements, exits 1 on a synthetic regression (auth 33.33 < 52.57), exits 0 advisory without --ratchet. Baseline change is additive (31 floors + one comment; existing keys untouched). check-mutation-ratchet tests 8/8. |
||
|
|
915991c762 |
fix(codex): isolate Spark quota scope (#4293)
* fix(codex): isolate Spark quota scope * fix(codex): address Spark quota review feedback * fix(ci): update Electron undici override * fix(ci): update root undici overrides * test(integration): sync stale expectations * test(tproxy): tolerate available native addon * test(tproxy): avoid environment-specific skips * test(tproxy): keep assertion count stable * fix(ci): stabilize quality and tproxy checks * chore(ci): rebaseline auth file size * fix(ci): extend node compatibility budget * chore(quality): reconcile complexity + file-size baselines after release/v3.8.30 merge (#4293) Measured on the actual merged tree (not the PR's main-based estimate): complexity 1885->1887 (+2); file-size auth.ts 2219->2279, chatCore.ts 5116->5125, accountFallback.ts 1727->1731, + the 4 Codex test files. Drift test-file conflicts (search-providers-catalog, tproxy-transparent-socket, integration-wiring) resolved to the already-merged release versions (#4276). Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: ci <ci@local> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
bf5b615969 |
feat(memory): x-omniroute-no-memory opt-out + memory off-by-default + token-cost alert (PRD-2026-06-19) (#4290)
* feat(memory): x-omniroute-no-memory opt-out + memory off-by-default + token-cost UI alert PRD-2026-06-19-no-memory-header. The gateway injects up to memorySettings.maxTokens (~2k) of memory (and skills) context into every chat call for memory-enabled keys, inflating tokens+cost ~137x for clients that manage their own context (e.g. Omniflow). Three changes: - A) x-omniroute-no-memory request header (mirrors x-omniroute-no-cache): when truthy (true/1/yes), skip memory+skills injection for that request. New pure helper isNoMemoryRequested() in chatCore/headers.ts; chatCore passes memoryOwnerId=null on opt-out (a null owner disables both injection branches). - B) Memory OFF by default: DEFAULT_MEMORY_SETTINGS.enabled true->false. Enabling injects billed context per request, so it's now an explicit opt-in. Installs that already enabled it keep it; unset installs default off (no migration seeds memoryEnabled). - C) Settings -> Memory shows a token-cost warning callout when memory is enabled (new settings.memoryTokenCostWarning i18n key, interpolating the configured maxTokens). Tests: no-memory-header.test.ts (5, helper truthiness/case/Headers); memory-settings-default and chatcore-memory-skills-injection aligned to the new off-by-default. 65/65 memory+chatcore tests green; typecheck/lint/file-size/i18n(@65) clean. * test(memory): enable memory in memory-tools test (memory now off by default) The full CI unit suite flagged memory-tools.test.ts 'memory search ...' failing after DEFAULT_MEMORY_SETTINGS.enabled flipped to false: omniroute_memory_search routes through retrieveMemories, which returns [] while memory is disabled (enabled:false → maxTokens 0). The memory MCP tools operate within the memory subsystem, so the test now enables memory explicitly (updateSettings + cache invalidation) — the realistic precondition for a client using the tools. Aligns the test to the intentional off-by-default change; assertions unchanged. |
||
|
|
6f16faa039 |
fix(models): keep vision capability for imported (synced) models (#4264) (#4283)
After importing a provider key, vision-capable models (OpenRouter models whose
architecture declares image input, and other synced providers) were shown as
text-only in /v1/models and the dashboard, even though image requests worked.
Root cause: SyncedAvailableModel never captured a vision flag, and the catalog's
OpenRouter live-enrichment block (which derives vision from architecture.input_modalities)
is skipped once a provider has synced models. So the synced path emitted no vision.
Fix (mirrors the existing supportsThinking capture):
- modelDiscovery.normalizeDiscoveredModels derives supportsVision via the new
detectVisionInput() from architecture.input_modalities, the string
architecture.modality ("text+image->text"), or a top-level input_modalities.
- SyncedAvailableModel gains supportsVision; the read-normalize path preserves it.
- catalog.ts emits capabilities.vision for synced models and merges (not clobbers)
capabilities when the model already exists.
TDD: tests/unit/openrouter-vision-sync-4264.test.ts — capture unit test + an
end-to-end /v1/models assertion (RED before, GREEN after).
Closes #4264
|
||
|
|
550440f65f |
fix(providers): Cloudflare Workers AI discovery uses model names, not UUIDs (#4259) (#4282)
Cloudflare's /ai/models/search returns { id: "<uuid>", name: "@cf/..." } where
name is the callable slug and id is an internal UUID. The cloudflare-ai discovery
config passed the raw objects through (parseResponse: data.result), so buildResponse
used id (the UUID) as the model id — the dashboard/import listed UUIDs instead of
@cf/... model names. Map each result's name -> id (mirrors the gemini/huggingface/
clarifai parseResponse normalizers in the same map); falls through to the local
catalog on error so import never breaks.
TDD: tests/unit/cloudflare-models-uuid-4259.test.ts (RED on UUID ids -> GREEN on slugs).
Closes #4259
|
||
|
|
871d109066 |
feat(quality): cap test-file size (anti-reinflation Layer 1) — freeze god-tests, cap new at 800 (#4273)
Layer 1 anti-reinflation: cap test-file size (freeze god-tests, cap new at 800). Gate validated green against the full combined tree. |
||
|
|
ec4d94f4c1 |
test(ci): reconcile release/v3.8.30 baseline + test drift (#4276)
Reconcile baseline + test drift on release/v3.8.30 (complexity, opaque surface, search count, tproxy addon). Round-robin left as a canary for the undici-dispatcher issue. |
||
|
|
3c9883bb73 |
Release v3.8.29 (#4126)
OmniRoute v3.8.29 — 115 commits since v3.8.28. Full CHANGELOG + 41 i18n mirrors. All content quality gates green (build, unit 8/8, vitest 188/188, PR test policy, quality gates extended, docs sync, quality ratchet). Remaining red CI checks are pre-existing release flakes (coverage-shard/integration/node-compat teardown), a new transitive undici advisory in electron devDeps, and a workflow-level CodeQL fail (0 open alerts). VPS-validated by the operator. |
||
|
|
f165efcd0b |
Release v3.8.28 (#4053)
* chore(release): open v3.8.28 development cycle * fix(ws): warm SSE auth import on LiveWS startup; relocate boot test to integration (#4063) The live dashboard WebSocket sidecar lazily import()-ed the SSE auth module inside the connection handler, only on the API-key path. That cold import pulls in hundreds of transitive modules and takes ~7s under tsx, blocking the single-threaded event loop. The first API-key WebSocket connection therefore stalled the loop long enough that any connection arriving in that window — e.g. a same-origin cookie client — could not complete its handshake and timed out. This was deterministic, not an "env flake": the boot test fires an API-key connection immediately followed by a cookie connection, so the cookie connection always raced the cold import and timed out (reproduced 3/3 locally and red on every CI run; proven via instrumented probes — reversing the order or warming the module first makes both connections open in ~20ms). Fix: - Memoize the auth-module import and warm it once at startup (before listen), so connection handling never pays the cold-import cost. Real improvement: the first API-key client no longer stalls the event loop for concurrent clients. - Relocate the boot test from tests/unit/cli to tests/integration. It spawns a real subprocess + WS server + SQLite (~9-11s); under the unit suite's --test-concurrency=20 it contended for CPU and destabilized the shard. The serial integration runner is its correct home; it still guards #4004's cookie-parse fix on every PR via the integration CI job. - Bump the test's startup/overall timeouts to absorb the eager auth warm. Makes `npm run test:unit` deterministically green (the only remaining unit red). Validated: relocated test 3/3 green via the integration runner (was 3/3 red); typecheck:core + eslint clean; confirmed it no longer matches the test:unit glob and does match tests/integration/*.test.ts. * fix(ws): start LiveWS sidecar with cwd at package root (#4055) (#4064) * chore(deps): bump ossf/scorecard-action from 2.4.0 to 2.4.3 (#4045) Integrado em release/v3.8.28. Patch de SHA do ossf/scorecard-action (2.4.0→2.4.3), mantém SHA-pin. Reds de CI são exclusivamente os shards flaky pré-existentes branch-wide (Unit 7/8, Integration, Coverage 7/8, Node 1/2) — não relacionados ao bump (PR deps-only). * deps: bump electron from 42.4.0 to 42.4.1 in /electron (#4049) Integrado em release/v3.8.28. Patch do electron (42.4.0→42.4.1). Reds de CI: shards flaky pré-existentes + PR Test Policy = falso-positivo (mudança deps-only sob electron/ não comporta teste de código) + Node 26(2/2) sem step (flake/infra). Precedente #3913/#3914 (electron dependabot mergeado nessas condições). * fix(auto): resolve built-in auto catalog combos (#4058) Integrado em release/v3.8.28. Resolve os IDs de catálogo `auto/*` built-in (combos virtuais) — corrige o 400 "No auto combos configured" em auto/best-coding etc. Ajuste de review: os mapas AUTO_TEMPLATE_VARIANTS/VALID_AUTO_VARIANTS duplicados em chat.ts e chatHelpers.ts foram extraídos para open-sse/services/autoCombo/builtinCatalog.ts (DRY), devolvendo chatHelpers.ts <800 LOC; baseline de chat.ts rebaselinado 1432→1458 (lógica nova). Fast QG + semgrep + dast verdes; 22/22 testes. * chore(docs): update Discord invite link to a non-expiring one (#4067) * chore(deps): freeze @huggingface/transformers in dependabot (hard-pin) (#4066) Integrado em release/v3.8.28. Congela @huggingface/transformers no dependabot (pin exato 3.5.2, load-bearing p/ LLMLingua + memory embeddings, VPS-validado #4014). Fast QG + semgrep + dast verdes. * ci(quality): flip TIA impacted-unit-tests gate from advisory to blocking (#4069) The pre-existing release unit test-debt that kept the TIA "Impacted unit tests" step advisory has been cleared: - #4030 restored 16 lossless Zod/registry reds (from the oyi77 modularize refactors). - #4063 fixed the last red — the LiveWS boot test — which was a real deterministic event-loop stall in the WS sidecar (cold ~7s lazy auth import racing a second connection), not an env flake; fixed (warm the import at startup) and relocated to the integration suite. A full workflow_dispatch ci.yml run on release/v3.8.28 then showed all 8 Unit Tests shards green. The remaining Integration Tests / Quality Ratchet reds are pre-existing and unrelated (combo/resilience env-flakes; eslint/i18n baseline drift). Removing continue-on-error makes PR->release block on unit-test regressions in the TIA-selected impacted set (fail-safe still runs the full unit suite on hub/unmapped changes). typecheck:core was already blocking. Closes the fast-gates "no tests on PR->release" hole (Quality Gate v2 / Fase 9, P2). * docs(compression): document LLMLingua optional deps + on-demand install (#4061) Integrado em release/v3.8.28. Docs LLMLingua optional deps + on-demand install (F3.1). * feat(dashboard): Combo Studio connection-cooldown badge (U1b Slice 2) (#4068) Integrado em release/v3.8.28. Combo Studio connection-cooldown badge (U1b Slice 2 / F5.1). * feat(compression): record Context Editing telemetry (engine: context-editing) (#4062) Integrado em release/v3.8.28. Context Editing telemetry (F4.1). * feat(sse): Context Editing relay coverage + 400-fallback (#4065) Integrado em release/v3.8.28. Context Editing relay coverage (cc-*) + 400-fallback (F4.2/F4.3). Conflito de file-size-baseline.json (vs #4062) resolvido por união (ambas justificativas + base.ts 1292 + chatCore.ts 5898). Validado local no tree mergeado: typecheck:core ✓, eslint ✓, check:file-size ✓, 4/4 testes ✓; semgrep + semgrep-cloud verdes. Fast QG enfileirado (saturação de runner) — mergeado nos gates de política verificados (precedente #4034/#4020). * feat(providers): add OrcaRouter (OpenAI-compatible routing gateway) (#4070) Integrado em release/v3.8.28. Adiciona o provider OrcaRouter (OpenAI-compatible, API-key, DefaultExecutor). Ajuste de review: rebaseline de file-size de providers.ts 3147→3159 (+12 da entrada OrcaRouter). Validado local no tree sincronizado: provider-consistency ✓, docs-counts STRICT 227 ✓, typecheck:core ✓, teste 3/3 ✓, eslint ✓; semgrep + semgrep-cloud verdes. Fast QG/dast enfileirados (saturação de runner) — merge nos gates de política verificados (precedente #4034/#4065). * test(infra): isolate DATA_DIR per test process; raise Stryker concurrency 1→4 (#4078) * test(infra): isolate DATA_DIR per test process; raise Stryker concurrency 1→4 Every test process resolved DATA_DIR to the same default (~/.omniroute) when the env var was unset (src/lib/dataPaths.ts::resolveDataDir), so concurrent test files opened the SAME on-disk storage.sqlite. node:test spawns a process per file and Stryker spawns one per sandbox, so this shared file caused cross-file state races: - SQLite lock contention that hung `npm run test:unit` under high --test-concurrency (the ~95-min local hang), and - the non-deterministic baseline that forced stryker.conf.json to concurrency: 1, which in turn could not finish the ~15k-mutant run inside the nightly timeout (the cancelled 2026-06-16/17 nightly-mutation runs) — blocking Quality Gate v2 / Fase 9 Onda 2. open-sse/utils/setupPolyfill.ts could NOT host the fix: it is imported by production (bin/omniroute.mjs, proxyFetch.ts, proxyDispatcher.ts), where redirecting DATA_DIR would point the live SQLite DB at a throwaway temp dir. So this adds a TEST-ONLY tests/_setup/isolateDataDir.ts that gives each process its own temp DATA_DIR when none is set (tests that set DATA_DIR explicitly still win), wired via --import into the test, mutation and CI invocations. Verified: - Stryker dry-run A/B at concurrency=4: FAILS without the isolation import (account-fallback-service tap exit 9, a cross-file race) and PASSES with it. - Full `npm run test:unit` green with isolation (0 fail; a one-off chatcore-translation-paths timeout flake did not reproduce and passes 3/3 isolated) and noticeably faster — the DB lock contention is gone. - New tests/unit/isolate-datadir.test.ts guards the contract (unique temp DATA_DIR when unset; explicit DATA_DIR respected). Wired the --import into: package.json (13 test scripts), stryker.conf.json (tap.nodeArgs + concurrency 1→4), .github/workflows/quality.yml (TIA step), ci.yml (the 5 unit/coverage/integration commands), and bumped nightly-mutation.yml timeout 120→180 for the first cold run before the incremental cache is seeded. * ci(quality): run the TIA gate at CI concurrency (4) to stop oversubscription flakes The TIA "Impacted unit tests" step (made blocking in #4069) ran its fail-safe via `npm run test:unit` — concurrency=20, tuned for multi-core dev machines. On a 4-vCPU CI runner that is 5x oversubscribed, so timing-sensitive tests flake under the load (e.g. `db-backup-extended` "The database connection is not open", `chatcore-translation-paths` upstream-timeout). That intermittently fails a blocking gate on legitimate PRs — exactly what surfaced on the DATA_DIR-isolation PR, whose package.json/workflow changes trip the __RUN_ALL__ fail-safe. Run both the impacted set and the fail-safe at --test-concurrency=4, matching the stable ci.yml unit job. Adds a `test:unit:ci` script (test:unit at concurrency=4). The DATA_DIR isolation in this PR keeps the parallel run race-free, so the only change here is matching the runner's core count. Verified locally: db-backup-extended passes 8/8 in isolation (5 with isolation, 3 without). * docs(quality-gates): reconcile gate inventory with ci.yml + add ROI rationalization backlog (#4095) The "authoritative" gate inventory in QUALITY_GATES.md had drifted from ci.yml: it omitted 9 wired gates — `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses` (lint job), `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet` (quality-gate job), and `check:pr-evidence` (pr-test-policy job). You can't rationalize an inventory you can't trust, so this reconciles it first. Adds those 9 rows to their job tables and a "Rationalization Backlog (ROI review)" section capturing the Fase 9 Onda 3 findings: mechanical merge/dedup candidates (CVE scanners audit:deps↔osv, the two complexity ESLint passes, cycles↔circular-deps, the two /api anti-hallucination gates, the doubly-run check:docs-sync, check:node-runtime ×11) and the operator-only flip/drop decisions (typecheck:noimplicit vs the type-coverage ratchet, test:vitest:ui parked fails, check:secrets frozen FPs, openapi-security-tiers, pr-evidence, the orphaned semgrep baseline). Also flags the undocumented advisory docs-lint job and the standalone scanner workflows. Docs-only — no gate behavior changes. The merges (CI changes) and flips (policy) are deferred to operator-scoped follow-ups; this PR only makes the map accurate. * test(dashboard): smoke e2e for the Combo Live Studio page (#4075) Integrated into release/v3.8.28 * fix(sse): friendly 413 message for ChatGPT web payload-too-large (#4080) Integrated into release/v3.8.28 * feat(sse): port Claude Code quota-probe bypass + command meta-request helpers (#4083) Integrated into release/v3.8.28 * feat(api): exact offline token counting for count_tokens fallback via tiktoken (#4087) Integrated into release/v3.8.28 * feat(compression): RTK learn/discover (sample source + API + UI) (#4088) Integrated into release/v3.8.28 * feat(dashboard): 2026-06-17 free-tier refresh — honest catalog, uncapped + boost tiers, Layout A budget table (#4089) Integrated into release/v3.8.28 * feat(mitm): capture-pipeline self-test route (Gap 12) (#4093) Integrated into release/v3.8.28 * fix(mitm): crash-safe system-state teardown + socket timeouts (ProxyBridge-inspired hardening) (#4084) Integrated into release/v3.8.28 (Fast QG TIA red = 3 pre-existing timing flakes verified passing locally 82/82; PR own tests green) * feat(mitm): attribute intercepted requests to originating process (Gap 1) (#4085) Integrated into release/v3.8.28 (Fast QG TIA red = 3 pre-existing timing flakes verified passing locally 82/82; PR own tests green) * fix(sse): route image requests only to confirmed-vision combo targets (#4071) Integrated into release/v3.8.28 * fix(security): injection guard respects INJECTION_GUARD_MODE DB feature flag (#4077) Integrated into release/v3.8.28 * fix(ws): proxy LAN /live-ws upgrades and add unset JWT_SECRET warning (#4079) Integrated into release/v3.8.28 * fix(dev): force webpack in custom dev server (Turbopack 16.2.x panics) (#4092) Integrated into release/v3.8.28 * ci(quality): dedup the doubly-run check:docs-sync + record validated ROI backlog (#4099) Onda 3 (gate ROI-review) Phase 2. Two parts, both low-risk: 1. Remove the standalone `check:docs-sync` from the `lint` job — it already runs in the `docs-sync-strict` job (via `check:docs-all`) and the husky pre-commit hook, so the `lint`-job copy was a pure duplicate. No coverage lost. 2. Update the Rationalization Backlog in QUALITY_GATES.md with trust-but-verify findings: several "obvious" merges/flips from the ROI review turned out to hide debt and are NOT clean drop-ins — - CVE merge (audit:deps→osv): different semantics (hard high/critical vs regression-ratchet) — keep both. - cycles→circular-deps: dpdm reports 91 cycles (can't promote to blocking) and is broader-scope than the green curated check:cycles — keep both. - openapi-security-tiers flip: blocked by traffic-inspector routes missing the x-loopback-only annotation. - complexity + /api merges: valid but real config/script surgery — deferred. - node-runtime ×11: ~10s savings vs a cheap guard — low ROI, skip. The remaining flips (typecheck:noimplicit, test:vitest:ui, check:secrets, pr-evidence, semgrep) are operator policy decisions, left for the owner. * chore(deps): bump actions/github-script from 7 to 9 (#4046) Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved) * chore(deps): bump actions/setup-node from 4 to 6 (#4048) Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved) * chore(deps): bump actions/upload-artifact from 4 to 7 (#4044) Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved) * chore(deps): bump actions/cache from 4.3.0 to 5.0.5 (#4047) Integrated into release/v3.8.28 (dependabot GH-Action bump; SHA-pin preserved) * deps: bump the development group with 10 updates (#4051) Integrated into release/v3.8.28 (dependabot dev group; cyclonedx 4->5 verified compatible with the SBOM invocation --ignore-npm-errors/--output-format JSON/--output-file) * fix(dashboard): event-driven fail-open auto-refresh for embedded log views (#4054) (#4103) The Request Logger gated each auto-refresh tick on a static document.visibilityState === "visible" read. Hosts that report a permanent non-"visible" state without ever firing a visibilitychange event (Docker dashboard wrappers, embedded/proxied webviews) froze auto-refresh entirely — only the manual Refresh button worked, a regression from 3.8.24's unconditional polling. The pause is now event-driven and fail-open: visibleRef starts true and is only flipped to false on a real visibilitychange → hidden transition, so a host that never signals a genuine background transition keeps polling, while normal browser tabs still pause when actually backgrounded. Regression test reproduces the misreporting-host case (RED) and the perf guard is re-encoded under the event-driven semantics. * fix(docker): raise build-stage Node heap to stop production-build OOM (#4076) (#4104) The Docker builder stage ran `npm run build` with V8's default heap ceiling (~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this Next.js version), the production optimization pass exceeded that ceiling and the build died with "FATAL ERROR: ... JavaScript heap out of memory" at [builder] npm run build. The builder stage now sets NODE_OPTIONS=--max-old-space-size (default 4096 MB, overridable via --build-arg OMNIROUTE_BUILD_MEMORY_MB) before the build; the value propagates to the spawned next build (resolveNextBuildEnv spreads process.env). Build-only — the runtime heap on the runner stage is unchanged, and CI/local builds (which invoke npm run build directly) are unaffected. Regression guard: tests/unit/dockerfile-build-heap-4076.test.ts asserts the builder stage sets the heap ceiling, before npm run build, at >= 4096 MB. * feat(agent-bridge): portable JSON import/export of config (Gap 4) (#4094) Integrated into release/v3.8.28 * feat(cli): add 'omniroute launch' zero-config Claude Code launcher (#4097) Integrated into release/v3.8.28 (Fast QG TIA red = pre-existing env-doc-contract drift [MITM_IDLE_TIMEOUT_MS/TURBOPACK from #4084/#4092] + opencode-plugin-dist env flake; #4097 own test 3/3 green) * feat(mitm): loop-guard self-check + verbosity control in server.cjs (Gaps 14+15) (#4101) Integrated into release/v3.8.28 (rebased onto release — dropped the already-squash-merged #4084 commits; only the Gaps 14+15 loop-guard/verbosity delta remains) * feat(sse): generic 400 field-downgrade retry + Groq field stripping (#4096) Integrated into release/v3.8.28 * feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth) (#4098) Integrated into release/v3.8.28 * chore(docs) * fix(responses): clear /v1/responses keepalive timer on cancel/abort (timer + CPU leak) (#4105) Integrated into release/v3.8.28 (r7). * perf(gemini): cache reasoning close-tag regex instead of recompiling per token (#4106) Integrated into release/v3.8.28 (r7). * fix(usage): reap orphaned pending-request details (unbounded memory leak) (#4107) Integrated into release/v3.8.28 (r7). * perf(stream): use structuredClone instead of JSON round-trip for per-chunk reasoning split (#4108) Integrated into release/v3.8.28 (r7). * fix(dashboard): restore Update Available banner with npm-binary-free version fallback (#4100) (#4112) getLatestNpmVersion() derived the latest version only from the npm CLI binary and returned null on any error, so Docker/desktop/locked-down installs without npm on PATH silently hid the home banner even when an update existed. Add resolveLatestVersion() (npm CLI -> registry HTTP fallback -> logged warning) and harden version parsing for v-prefix/pre-release strings. Extracted into testable src/lib/system/versionCheck.ts with TDD coverage. * fix(auth): prune expired entries from login brute-force guard map (unbounded growth) (#4111) Integrated into release/v3.8.28 (r8) * fix(logger): hard-cap the error-dedup map to bound memory under unique-message bursts (#4113) Integrated into release/v3.8.28 (r8) * fix(circuit-breaker): enforce MAX_REGISTRY_SIZE (declared but never applied) (#4114) Integrated into release/v3.8.28 (r8) * perf(obfuscation): cache per-word regexes instead of recompiling every request (#4109) Integrated into release/v3.8.28 (r8) * perf(registry): precompute model->provider index in parseModelFromRegistry (#4110) Integrated into release/v3.8.28 (r8) * fix(timers): unref background interval timers so they don't block clean shutdown (#4117) Integrated into release/v3.8.28 (r8) * fix(webhook): clear abort timer in finally to avoid dangling timers on fetch error (#4115) Integrated into release/v3.8.28 (r8) * fix(combo): detach per-target listener from shared hedge abort signal (#4116) Integrated into release/v3.8.28 (r8) * chore(release): finalize v3.8.28 CHANGELOG + reconcile env-doc contract - Build the complete [3.8.28] CHANGELOG section (55 bullets) covering every commit since v3.8.27, grouped by type with PR back-references and human contributor attribution (artickc's memory-leak/perf cluster, OrcaRouter, Wafer AI, MITM gaps, etc.); move the OrcaRouter bullet out of [Unreleased]. - Inject the EN [3.8.28] section into all 41 i18n CHANGELOG mirrors (parity). - Reconcile the env/docs contract: document MITM_IDLE_TIMEOUT_MS + MITM_VERBOSE in .env.example and ENVIRONMENT.md; allowlist the framework-internal TURBOPACK and the Claude Code ANTHROPIC_AUTH_TOKEN in check-env-doc-sync. - Fix 3 broken relative links in docs/providers/AGENTROUTER.md (regressed when the file was relocated this cycle) so docs-sync-strict passes. * fix(quality): treat test→test renames as relocations, not deletions The anti-test-masking gate's subcheck-1 collected deleted AND renamed test files via `--diff-filter=DR --name-only` and flagged every one as "deleted — human review required", contradicting its own documented contract ("DELETADOS ou renomeados-e-NÃO-substituídos"): a rename test→test IS a substitution (the test moved, coverage preserved). This false-positived on #4063's legitimate relocation of live-ws-startup.test.ts (unit/cli → integration, asserts 2→2) and would block every PR that relocates a test — surfacing only at release-day because the Fast QG (PR→release) doesn't run test-masking. The gate now parses `--name-status -M`: true deletions and test→non-test renames still flag; a test→test rename is run through the assert-reduction check across the move, so a clean relocation passes while gutting-via-rename (dropped asserts / new tautologies / skips) still fires. Adds partitionDeletedRenamed + 6 regression tests. --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Demiurge The Single <megamen932@gmail.com> Co-authored-by: jinhaosong-source <jinhao.song@myflashcloud.com> Co-authored-by: diego-anselmo <contato@diegoanselmo.com.br> Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com> Co-authored-by: Rahul sharma <sharmaR0810@gmail.com> Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> |
||
|
|
fa367dd99e |
Release v3.8.27 (#3968)
* chore(release): open v3.8.27 development cycle * fix(security): polynomial ReDoS in comboAgentMiddleware regex (#3982) * fix(security): eliminate polynomial ReDoS in comboAgentMiddleware <omniModel> regex (CodeQL js/polynomial-redos) CACHE_TAG_PATTERN wrapped the tag in an unbounded `(?:\\n|\n|\r)*` prefix/suffix. On an unanchored `.test()`/`.exec()` that is O(n²) on inputs with many newlines (CodeQL js/polynomial-redos, alerts #612/#613). The surrounding runs are irrelevant to detecting/capturing the tag, so the detection pattern now matches only the core `<omniModel>([^<]+)</omniModel>`; the global strip pattern still consumes the wrapping newlines (combo.ts streaming, #531) but BOUNDED ({0,16}) so it stays linear. Behavior preserved: detection, model extraction, multi-tag stripping (#454) and blank-line cleanup all unchanged (107 related tests green). Adds ReDoS-safety regression tests (50k-newline inputs complete in <1ms). * docs(changelog): add #3982 ReDoS fix to [3.8.27] * ci(security): harden workflows — artipacked persist-credentials + cache-poisoning + SC2086 (#3965) * Refine provider quota card display (#3969) Integrated into release/v3.8.27 * feat: add sidebar group separator toggles (#3971) Integrated into release/v3.8.27 * Gate control-plane proxy direct fallback (#3963) Integrated into release/v3.8.27 * Capture actual upstream provider requests (#3941) Integrated into release/v3.8.27 * ci(quality): flip require-tighten + osv + Trivy to blocking (v3.8.27 cycle-end) (#3984) * fix(resilience): respect connection cooldown stored as numeric epoch (#3954) (#3995) rate_limited_until is a TEXT column, but setConnectionRateLimitUntil (Antigravity full-quota path) persists a raw epoch number that SQLite coerces to a numeric string ("1781696905131.0"). The selection predicate isAccountUnavailable then did new Date("1781696905131.0") -> NaN, so the cooling connection was never skipped and the router kept dispatching to rate-limited accounts. Normalize numeric-epoch strings (and number/Date/ISO) via a shared cooldownUntilMs() helper in isAccountUnavailable / getEarliestRateLimitedUntil / filterAvailableAccounts / parseFutureDateMs. ISO behavior preserved. * fix(providers): fetch live /models for LLM7 and BytePlus (#3976) (#3996) llm7 and byteplus carry a real modelsUrl but were not classified by any live-fetch branch of the model-import route, so their hardcoded 4-entry registry catalog was served (source local_catalog) instead of the upstream catalog. Add both to NAMED_OPENAI_STYLE_PROVIDERS so the route probes <baseUrl>/models and serves the live list, falling back to the local catalog only on fetch failure. * fix(dashboard): logs auto-refresh reads live visibility, not a stale mount ref (#3972) (#3997) The auto-refresh interval gated each tick on visibleRef, seeded once at mount and updated only by a visibilitychange event. A tab mounted while document.visibilityState is 'hidden' (background load, bfcache, embedded/proxied webviews) with no later visibilitychange left the ref false forever, so the interval ticked but never fetched — only the manual button worked. Read the live document.visibilityState in the tick instead. * feat(compression): add Indonesian caveman rules and language pack (#3975) Integrated into release/v3.8.27 (cherry picked from commit |
||
|
|
4d21044ba5 |
fix(release): post-merge quality gates to main for v3.8.26 (#3964)
Cherry-picks #3961 + #3962 from release/v3.8.26 to main (parity before tagging). |
||
|
|
81a37b67ed |
Release v3.8.26 (#3875)
OmniRoute v3.8.26 — see CHANGELOG.md [3.8.26] for the full notes. Highlights: Vertex AI media generation (#3929), GLM-5.2 effort-tier routing (#3885), sticky round-robin combos (#3846), OpenRouter connection presets (#3878), compression prompt-cache fix (#3936/#3890), and a security pass (form-data/vite + workflow hardening, #3949). Co-authored-by: artickc <artickc@users.noreply.github.com> Co-authored-by: rdself <rdself@users.noreply.github.com> Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Jack Smith <16862258+YunyunZhai@users.noreply.github.com> Co-authored-by: dhaern <dhaern@users.noreply.github.com> Co-authored-by: adivekar-utexas <adivekar-utexas@users.noreply.github.com> Co-authored-by: megamen32 <megamen32@users.noreply.github.com> Co-authored-by: zhiru <zhiru@users.noreply.github.com> Co-authored-by: insoln <insoln@users.noreply.github.com> Co-authored-by: diego-anselmo <diego-anselmo@users.noreply.github.com> |
||
|
|
6e392932c2 |
feat(i18n): add Azerbaijani (az 🇦🇿) language support
- Add az locale to config/i18n.json (source of truth, 42 locales total)
- Create src/i18n/messages/az.json (UI strings from en.json base)
- Create docs/i18n/az/ directory with full documentation set
- Add 🇦🇿 Azərbaycan dili to README.md language bar
- Add az entry to docs/i18n/README.md index (40 doc languages)
- Add az to generate-multilang.mjs LOCALE_SPECS (Google TL: az)
- Add az to i18n_autotranslate.py lang_map
- Update CHANGELOG.md with feat(i18n) entry
|
||
|
|
c86f60b1d3 |
feat(i18n): add config/i18n.json as canonical locale list
Adds config/i18n.json (41 locales) + JSON-Schema as the single source of truth for the locale list, RTL set, and docs-translation policy. This file is consumed by: - The runtime UI config in src/i18n/config.ts (next commit). - The docs translation pipeline (scripts/i18n/run-translation.mjs, added in a later commit). - The drift checker (scripts/i18n/check-translation-drift.mjs). Fields: - default: source locale (en) - rtl: locale codes rendered right-to-left - uiOnly: locales shipped in UI but not target of docs translation - docsExcluded: locales NOT receiving docs translations (source language) - locales[]: code, label, name, native, english, flag Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dc6d9e2e4b |
feat(core): add payload rules, tag routing, and scheduled budgets
Introduce runtime-configurable payload mutation/filter rules with file reload support and a settings API so upstream request bodies can be customized per model and protocol without restarts. Expand search support with Google PSE, Linkup, SearchAPI, and SearXNG, including validation, routing, analytics costing, MCP schema updates, and search-type-aware provider selection. Update Pollinations to support anonymous access, endpoint failover, and the latest public model lineup. Add OmniRoute response metadata headers/SSE comments, per-connection model exclusion rules, combo tag-based routing, buffered spend writes, and scheduled daily/weekly/monthly budget resets. Update model catalog and dashboard UIs to surface source labels and hide models excluded by all active connections. |