Commit Graph

7 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
b1d710d45b fix(video-bridge): route captions through provider connections 2026-08-15 17:44:47 -03:00
Diego Rodrigues de Sa e Souza
5379493bed feat: add Video Bridge frame sampling (#10483)
Implements the secure, opt-in Video Bridge for issue #9760, including bounded FFmpeg frame extraction, capability-aware routing, telemetry, settings UI, localization, documentation, and regression coverage.
2026-08-15 14:23:29 -03:00
rinseaid
813dbb6e03 fix(vision): prevent bridge streaming and normalize OMP effort (#8945)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip)
2026-08-06 11:08:31 -03:00
Diego Rodrigues de Sa e Souza
8180b49ce1 fix(quality): 2 production bugs + 24 unit base-reds + measured gate ceilings (#9529)
* fix(quality): resolve net-new lint errors and allowlist #9343 assert rewrite

Two `no-explicit-any` errors landed with #9407 and #9320 after the
suppressions inventory was generated. Project policy is to fix new
violations rather than freeze them, so both are typed instead:
  - #9407: `executor as unknown as Record<string, unknown>`
  - #9320: `(k: { name?: string })`

Also allowlists the net-assert reduction in web-tools-translation-2820
(39->35). #9343 inverted the contract — bare JSON must no longer be
promoted to tool_calls without an explicit <tool> envelope — so the
tests were rewritten to assert non-promotion, which costs fewer asserts
than validating a promoted object. More restrictive, not weaker.

* fix(quality): raise integration ceiling to 40min and unpin codex-cli version in test

The integration gate's 20min ceiling killed a healthy run: measured 22m08s
hermetic on an idle 16-core box (935 tests across 112 files, strictly serial
at --test-concurrency=1 because ~16 of them bind a port or share a DB). The
"~3-10min" estimate in the code was stale by ~3x. 40min keeps the ceiling's
real purpose — turning a genuine hang into a visible failure — without
failing a long-but-healthy suite.

Also fixes a base-red in chat-pipeline: 564c204efe bumped
DEFAULT_CODEX_CLIENT_VERSION to 0.146.0 but the User-Agent assertion still
pinned 0.144.1. The line two above already read the constant via
getCodexClientVersion(); this one duplicated the literal. Deriving it from
the same source stops the next bump from breaking the test again.

* fix(ratelimit): re-arm Bottleneck reservoir heartbeat after updateSettings

Bottleneck 2.19.5 (frozen upstream dependency, no release since 2019) has a
bug in LocalDatastore#_startHeartbeat() (node_modules/bottleneck/lib/
LocalDatastore.js:29,56): the guard `if (this.heartbeat == null && ...)`
only (re)creates the periodic reservoir-refresh interval the first time it
runs. Every later call -- including the one updateSettings() itself
triggers internally -- falls into the else branch and does
clearInterval(this.heartbeat) WITHOUT resetting the reference back to
null. Because the stale reference sticks around, every future
_startHeartbeat() call keeps taking the same dead else branch: the
periodic reservoir refresh is gone forever after the first manual
updateSettings() call on a limiter.

Every limiter created by this file starts with a live heartbeat
(buildLimiterDefaults() always sets reservoirRefreshInterval/
reservoirRefreshAmount), so the very first updateFromHeaders() /
updateFromResponseBody() / applyRequestQueueSettings() call against a
limiter permanently kills its refresh. In production this wedges the
request queue once the reservoir hits 0: an auto-enrolled apikey
connection accumulates its default 60 requests, the reservoir zeroes, the
queue freezes for ~120s, the watchdog fires a synthetic 502
(RATE_LIMIT_QUEUE_WEDGED), the connection cools down and gets excluded
from weighted combo pools -- turning a configured 70/30 split into
~50/50.

Add applyLimiterSettings(), a module-local wrapper around
limiter.updateSettings() that nulls the stale heartbeat reference and
re-invokes _startHeartbeat() afterward so it takes the "start a fresh
interval" branch again. Route all 5 updateSettings() call sites through
it (updateAllLimiterSettings, both updateFromHeaders() branches,
loadPersistedLimits(), and updateFromResponseBody()). updateAllLimiterSettings
is now async and awaited by its two callers (initializeRateLimits,
applyRequestQueueSettings); the sync call sites use the existing
trackAsyncOperation() fire-and-forget tracking pattern.

tests/integration/combo-matrix/weighted.test.ts is the E2E proof: the
"weighted: 70/30" case now passes with zero WEDGED/RATE_LIMIT_QUEUE/502
log lines across 200 sequential requests (previously the wedge/recovery
cycle inflated its runtime and skewed the distribution toward ~50/50).

Refs #8213

* fix(tests): remove stray TDD probes committed by accident in f4e93f339d

Three TDD repro/probe test files landed on the release tip via
f4e93f339d (docs: add management authentication terminology guide,
files from a worktree. Each file is a pre-fix TDD probe that belongs
to a *different*, still-in-flight fix branch/PR and duplicates a file
path that PR already owns and will properly update on merge:

- tests/unit/authz/probe-9033-repro.test.ts: probe for #9033 (IP
  blacklist direct-connection bypass). 3/4 asserts fail against this
  tree (D1, D2, Bonus — all assert the not-yet-implemented target
  behavior); D3 passes (pre-existing behavior). Owned by PR #9385
  (open, unmerged), which modifies this exact path.
- tests/unit/repro-8522.test.ts: probe for #8522 (absolute file-size
  baseline reds innocent PRs on inherited drift). First test fails
  against this tree's evaluateFileSizes (still absolute-only); second
  (sanity: real growth still flags) passes. #8522 is actually CLOSED
  upstream — PR #9355 merged the real fix into release/v3.8.50 today
  (2026-08-05T15:53Z) modifying this exact path — but this branch's
  merge-base with release/v3.8.50 (6b0e11e378) predates that merge,
  so the fix has not synced into this tree yet.
- tests/unit/repro-8956.test.ts: probe for #8956 (resolveProjectRoot
  stops at synthetic Next.js standalone package.json). First test
  fails against this tree; second (sanity: named package.json still
  resolves) passes. Owned by PR #9354 (open, unmerged), which
  modifies this exact path.

Each deleted file's real implementation + passing version already
exists in its owning PR and will land normally through that PR's own
merge — deleting the premature copy here does not lose any coverage.

No config/quality/test-masking-allowlist.json entry was added: the
_deletedWithReplacement schema only supports `replacement` (a test
file that must already exist in this tree's HEAD — none does, the
real versions live in the unmerged sibling PRs above) or `sourceRemoved`
(production files that must be absent from HEAD — they are not, none
of the three issues are implemented in this tree). Neither shape fits
an "owned by an in-flight sibling PR" deletion, so the CI test-masking
gate will flag these 3 deletions for mandatory human review on this
branch's next PR diff against release/v3.8.50 — flagged for the owner
rather than inventing a new allowlist shape.

Refs #9033, #8522, #8956, #7786

* fix(tests): align 8189-classifier-compat with #9276 always-mode semantics

tests/unit/8189-classifier-compat-auto-narrow.test.ts was a test-sibling
forgotten when #9276 (commit 6b531fbacd) removed the unconditional
`if (mode === "always") return true` branch from
shouldDefaultAllowClassifier(). tests/unit/claude-classifier-compat.test.ts
was updated in that same commit; this file was not.

Old contract: 'always' mode short-circuited every Claude-format request
unconditionally (operator opt-in was treated as sufficient on its own).
New contract: 'always' now requires the same SECURITY_MONITOR_MARKER
system-prompt text as 'auto' — the marker-optional behavior let a normal
chat request through /v1/messages be silently swallowed by an operator's
'always' opt-in.

The single 'always' test (1 assert, no-marker body expecting true) is
replaced by two tests mirroring the depth already used for 'auto' mode
in the same file: no-marker/false and marker-present/true. Net effect is
+1 assert, not a reduction — the new pair verifies both directions of
the narrowed contract instead of only the now-incorrect unconditional
case.

Before: 3/4 pass (the 'always' test failed: expected true, got false).
After: 5/5 pass.

Refs #9276

* fix(tests): align deepseek-web-tools-execute with #9343 tool envelope contract

tests/unit/deepseek-web-tools-execute-2820.test.ts (executor level) was a
test-sibling forgotten when #9343 (commit d969555417) hardened tool-call
parsing: bare JSON with no explicit <tool>/<tool_call> envelope is never
promoted to tool_calls anymore (previously it was, whenever a tools[] set
was requested — a security gap allowing prose/code-fenced JSON echoed
back by the model, or a copy-attack, to trigger real tool execution).

Three siblings were updated in the same commit: web-tools-translation.test.ts
and web-tools-translation-2820.test.ts (parseToolCallsFromText, the shared
translator), and deepseek-web-tools-variants.test.ts (parseDeepSeekToolCalls,
deepseek-specific parser) — all inverted their bare-JSON assertions to
`toolCalls === null` + `content === text` (preserved verbatim, not stripped).
This file calls the executor's execute() (full HTTP round trip through
buildToolAwareResult), so it was not touched by that diff and kept
asserting the old contract (finish_reason: "tool_calls", content: null).

Verified against source (open-sse/executors/deepseek-web.ts
buildToolAwareResult): when parseDeepSeekToolCalls returns toolCalls=null,
hasCalls is false, so finish_reason is "stop", message.tool_calls is never
set, and message.content is the parser's returned content — which for text
with no <tool>/<tool_call> tag at all is the original string, unchanged
(parseToolCallsFromText's early-return branch). The test now asserts
exactly that shape, at the same executor level as the rest of the file's
tool_calls that make sense at that level as the rest of the file's tool_calls
Refs #9343

* fix(tests): align visionBridge tests with #8430 contract (partial — see note)

Two test-siblings were forgotten when #8430 (commit 7e55abbc41) hardened
Vision Bridge's vision-model selection: getBestVisionModel() now validates
that a candidate has a usable active connection (hasUsableCredentialsForModel,
DB-backed) before returning it, instead of unconditionally returning the
fixedModel or a hardcoded "openai/gpt-4o-mini" default. Three siblings were
updated in the same commit (visionBridgeRouter.test.ts, the new
repro-8430.test.ts, vision-bridge-preserve-on-failure-4012.test.ts); these two
were not.

tests/unit/guardrails/visionBridgeHelpers.callVisionModel.test.ts (8 failures,
all "No vision-capable provider connected"): callVisionModel()'s `routerConfig`
param only merges into getBestVisionModel's CONFIG argument, never its `deps`
argument, so there is no way to inject a credentials stub through this
function's public signature (unlike the guardrail class and getBestVisionModel
itself, which do accept an injectable `hasUsableCredentials`). These tests
exercise callVisionModel's own request/response handling, not credential
routing (already covered elsewhere), so the fix seeds one real usable
`provider_connections` row per provider the file exercises (openai, anthropic)
via createProviderConnection in a test.before() hook, with resetDbInstance()
in test.after() per the DB-handle-cleanup convention. All 8 now pass.

tests/unit/guardrails/visionBridge.test.ts (7 failures): 1 of the 7 (VB-S03)
is a genuine forgotten-contract case, fixed here — same semantic flip already
applied to vision-bridge-preserve-on-failure-4012.test.ts: in the combo
describe path, when EVERY describe call fails, the raw image is now replaced
with an "(unavailable)" stub instead of preserved, because that path is only
reached for confirmed non-vision targets. Assertions inverted to match
(imagePart undefined, unavailable-stub present), same assert count, no
weakening.

*** THE OTHER 6 (VB-S12, VB-S12b, VB-S01, VB-S13, VB-S07, VB-S10) ARE
DELIBERATELY LEFT FAILING. *** These are NOT a #8430 contract change — root-
caused to what looks like a separate, unintentional regression: the ONE call
to getBestVisionModel() in visionBridge.ts's whole-request-reroute path
(line 244, `getBestVisionModel({ fixedModel: configuredModel })`) does not
pass a `deps` second argument, so it always uses the real DB-backed
hasUsableCredentialsForModel instead of this.deps.hasUsableCredentials — even
though the two adjacent checks in the very same function (`checkCreds(model)`
at line 226, `checkCreds(bestModel)` at line 246) DO honor the injectable
override. In this suite's empty-but-readable isolated test DB, that real
check deterministically returns `false` (not the indeterminate `null` the
file's own createGuardrail() comment says these tests rely on: "Fail-open
(null) so classic VB-S01/S07/S10 reroute tests keep working without a live
credential DB"), so getBestVisionModel silently returns null, the reroute
branch's `if (bestModel && ...)` guard never fires, and every test that
expects a reroute observes a silent no-op instead.

Evidence this is a source gap, not a test that needs updating:
- The file's own pre-existing comment names VB-S01/S07/S10 as tests the
  `null` fail-open default is SUPPOSED to keep green.
- VB-CRED-01/02 (the file's only two tests that actually inject a non-default
  hasUsableCredentials mock) both pass today, but neither one's assertions
  distinguish "mock honored" from "mock ignored, real check also says no" —
  they don't prove the threading works, they just don't happen to notice it's
  missing.
- visionBridgeRouter.test.ts, repro-8430.test.ts, and
  vision-bridge-preserve-on-failure-4012.test.ts (22 tests, all green) all
  either call getBestVisionModel directly with explicit deps, or mock
  callVisionModel wholesale (bypassing getBestVisionModel entirely) — none of
  them exercises this exact call site through the guardrail's own deps.

Per instructions, this was intentionally NOT "fixed" by weakening these 6
tests' assertions (that would mask the gap) or by seeding fake DB credentials
to route around it (that would hide a real production DI inconsistency behind
a test-only workaround) or by touching src/lib/guardrails/visionBridge.ts
(a production behavior change outside a test-alignment task's scope, and
Hard Rule #18 requires its own TDD/validation cycle). Flagging for the owner:
the likely one-line fix is threading `{ hasUsableCredentials:
this.deps.hasUsableCredentials }` as getBestVisionModel's second argument at
visionBridge.ts:244, mirroring the two adjacent call sites in the same
function.

Before: 15 failures (7 + 8). After: 9 pass added (1 + 8), 6 still fail
(unchanged, by design).

Refs #8430

* feat(quality): add strayFromCommit deletion allowlist form to test-masking gate

The deletion allowlist supported two shapes: replacement (test rewritten
elsewhere) and sourceRemoved (feature deleted). Neither fits a third
legitimate case surfaced today: test files that entered the repo BY
ACCIDENT — commit f4e93f339d (#7786 docs) swept another session's
worktree artifacts into the release, including TDD probes owned by open
fix PRs (probe-9033-repro -> PR #9385, repro-8956 -> PR #9354,
repro-8522 -> PR #9355). Those probes fail by design until their owning
PR merges, so every unit run on the release tip broke on them.

The new strayFromCommit form is verified, not trusted: the gate asks git
which commit actually ADDED the file (git log --diff-filter=A) and only
exempts the deletion when it matches the declared hash; a non-empty
reason naming the owning PR/issue is mandatory. Also allowlists the
deepseek-web-tools-execute assert reduction (23->21) from ed661f2126 —
same #9343 contract-inversion class as the existing
web-tools-translation entry.

Gate unit tests: 55/55 pass. Full gate vs main: OK.

* fix(guardrails): pass credential deps to getBestVisionModel at reroute call site

The individual-model reroute path in VisionBridgeGuardrail.preCall() calls
getBestVisionModel({ fixedModel: configuredModel }) without its second
`deps` argument, so the router always falls back to the real DB-backed
hasUsableCredentialsForModel instead of an injected
`deps.hasUsableCredentials` override. The two adjacent credential checks in
the same function (the original-model check and the best-model check,
both via the local `checkCreds` binding) already thread deps correctly —
only this middle call, added in #8430, was left out.

Pass the same resolved `checkCreds` used by those two adjacent checks as
`getBestVisionModel`'s deps argument so all three credential checks in this
reroute path stay consistent.

Fixes 6 tests in tests/unit/guardrails/visionBridge.test.ts that depended
on the injected hasUsableCredentials mock being honored on this path:
VB-S12, VB-S12b, VB-S01, VB-S13, VB-S07, VB-S10.

Refs #8430

* fix(quality): raise unit ceiling to 100min and align 2 more forgotten sibling tests

Unit ceiling 45->100min: a hermetic-env measurement on the loaded devbox
(load 7-26) was still inside invocation 1 of 3 at 76min when killed;
contention factor 2-3x measured, no idle measurement exists. The
pre-flight's real condition is exactly that contended one (unit runs in
Promise.all with integration+vitest), and there 45min provably killed a
healthy suite and fabricated a false base-red. The 45min value came from
v3.8.43 as an estimate never validated by measurement. TODO in-code:
re-tighten after an idle run on the .113 box.

Also aligns the 6th and 7th occurrences of the same systemic pattern
(behavior change merged updating only part of the sibling tests):
- issue-7859-gemini-web-redirect-valid: #9407 refined ServiceLogin
  redirects to mean expired session; the #7859 regression coverage is
  preserved via a non-ServiceLogin public redirect variant.
- provider-validation-specialty claude-web 429: #9406 inverted the
  contract (rate-limited session is unhealthy); the dedicated repro file
  owns the full contract, this sibling now matches it.

Also carries the file-size rebaseline for #9323's base.ts growth
(1578->1623, WAF retry + burst guard) and the eslintWarnings baseline
tightened 5000->0 (real measured value with the TS7 suppressions in
place — 5000 left the ratchet inert).

Refs #9407, #9406, #9323

* fix(tests): restore the 3 TDD probes now owned by merged fixes and drop their stray allowlist entries

The base advanced while this PR was open: the real fixes for the three
issues behind the stray probes all merged into release/v3.8.50 —
#9385 (issue 9033), #9355 (issue 8522) and #9354 (issue 8956).

- probe-9033-repro / repro-8522: the base rewrote both probes into the
  regression tests of their merged fixes, so the delete side of the
  rebase conflict was dropped and the base versions kept.
- repro-8956: #9354 only realigned one fixture line in
  auto-update.test.ts (package.json marker now needs a name field) and
  added no test for the new skip-synthetic behavior — the probe is the
  ONLY regression coverage of that merged fix (2/2 green on the base),
  so deleting it would remove real coverage. Restored.

With no test-file deletions left in the PR diff, the three
strayFromCommit allowlist entries are stale and removed. The
strayFromCommit form support in check-test-masking.mjs stays (covered
by its own fixtures).

* fix(quality): rebaseline file-size for PR #9529 own growth

The base sits exactly at the old frozen values, so the base-relative
mode (#8522) does not cover this growth — it is this PR's own:

- open-sse/services/rateLimitManager.ts 1060->1105: the
  applyLimiterSettings() helper that re-arms the reservoir heartbeat
  after updateSettings (Bottleneck 2.19.5 fix, TDD in
  ratelimit-reservoir-refresh.test.ts).
- tests/integration/chat-pipeline.test.ts 1592->1598: codex User-Agent
  derived from getCodexClientVersion() instead of a pinned literal.
- tests/unit/provider-validation-specialty.test.ts 2980->2985: new
  claude-web 429 -> valid:false coverage (#9406).

* fix(docs): sync provider count to 291 in README and CLAUDE

The live catalog counts 291 providers but README.md/CLAUDE.md still
said 290, so the STRICT 'Docs Gates (fast-path)' check reds EVERY open
PR against release/v3.8.50 (verified on #9537/#9539 as well — inherited
base-red, not introduced by this PR). Updated all provider-count
mentions including the section anchor.

* fix(tests): align launch-codex 6312 guard with the async #9454 spawn contract

#9454 made resolveCodexSpawn async (PATH-probes a native codex.exe before
the .cmd shim) and updated its own tests, but left this older sibling
calling the function synchronously — destructuring the Promise yields
undefined and reds Unit fast-path (1/4) for EVERY open PR against the
release (verified on #9537/#9539; inherited base-red). Realigned to the
async contract with an injected probe; keeps the original #6312 fallback
guard plus the only non-Windows codex coverage (now also asserting the
probe never runs off Windows).

* fix(translator): move state-mutating reasoning summary helper out of the pure leaf

#9500 added buildResponsesReasoningSummaryDelta(state, ...) to
pureHelpers.ts, but the function reads AND mutates stream state
(reasoningSummaryIndex map) — violating the leaf contract declared in
the file header ('no host imports, no stream state') and guarded by
response-openai-responses-purehelpers-split.test.ts, which reds Unit
fast-path (4/4) for every open PR (inherited base-red, verified on
#9537/#9539). Moved verbatim to the host next to the other stream-state
helpers (markResponsesReasoningDeltaEmitted); the host was its only
consumer. Behavior unchanged: repro-9500-reasoning-separator 3/3 green,
leaf/host architecture tests green.

* fix(quality): rebaseline openai-responses.ts for the leaf-state relocation

The #9500 helper moved from pureHelpers.ts into the host (previous
commit) grows the host file 1174->1204 while the leaf shrinks by the
same amount — net-zero LOC across the pair, but the per-file frozen
ratchet only sees the growing side.

* fix(tests): let the 9442 cert-mode test see past the harness trust-store guard

tests/_setup/isolateDataDir.ts sets OMNIROUTE_SKIP_SYSTEM_TRUST=1
globally, which makes installCert() return before issuing any command —
so the #9442 install-gap test captured nothing and could NEVER pass
under npm run test:unit (it only passed invoked directly, harness-less;
inherited base-red on Unit fast-path 3/4, verified on #9537/#9539).
Clear the flag for this file only (restored in test.after): safe because
every spawned command is a logging stub on PATH and OMNIROUTE_NO_SUDO=1
strips sudo, so nothing touches the real trust store. 6/6 under the CI
harness including system-trust-test-guard.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-05 22:52:50 -03:00
Diego Rodrigues de Sa e Souza
a7ae9550bd Release v3.8.36 (#4854)
* chore(release): open v3.8.36 development cycle

* refactor(chatCore): extrai resolveCompressionSettings (#3501) (#4826)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 1/13)

* refactor(chatCore): extrai predicados puros de combo de compressão (#3501) (#4824)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 2/13)

* refactor(chatCore): extrai emitOutputStyleTelemetry (#3501) (#4811)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 3/13)

* refactor(chatCore): extrai writeCompressionAnalytics (bloco analytics completo, #3501) (#4817)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 4/13)

* refactor(chatCore): extrai runPluginOnRequestHook (#3501) (#4827)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 5/13)

* refactor(chatCore): extrai applyClientUsageBuffer (buffer/estimate de usage non-streaming, #3501) (#4832)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 6/13)

* refactor(chatCore): extrai buildPostCallGuardrailContext (contexto guardrail post-call, #3501) (#4831)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 7/13)

* refactor(chatCore): extrai storeSemanticCacheResponse (cache-store non-streaming, #3501) (#4828)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 8/13)

* refactor(chatCore): extrai buildNonStreamingResponseHeaders (headers de resposta non-streaming, #3501) (#4835)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 9/13)

* refactor(chatCore): extrai maybeConvertJsonBodyToSse (#3089 JSON→SSE streaming, #3501) (#4833)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 10/13)

* refactor(chatCore): extrai assembleStreamingResponseHeaders (headers de resposta streaming, #3501) (#4836)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 11/13)

* refactor(chatCore): extrai storeStreamingSemanticCacheResponse (cache-store streaming, #3501) (#4829)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 12/13)

* refactor(chatCore): extrai assembleStreamingPipeline (chain de transforms streaming, #3501) (#4837)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 13/13)

* ci(quality): shift heavy validations to the PR→release fast-path (release-acceleration) (#4857)

* feat(quality): add check:test-runner-api gate (vitest-only dirs must use vitest API)

* feat(release): reusable CHANGELOG i18n-mirror sync script

* chore(ops): add prune-stale-worktrees.sh (dry-run by default)

* ci(quality): run test-runner-api + docs-all + vitest + full unit suite on PR->release fast-path

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(quota): cota exclusiva lista qtSd/ no /v1/models (#4806) + limite EPSILON não bloqueia (#4830)

Integrated into release/v3.8.36 — quota-exclusive qtSd/ listing (#4806) + EPSILON placeholder no longer blocks; rebuilt from stale base (3 defining commits cherry-picked clean over release tip)

* feat(sse): add Google Flow video-generation provider (#4569) (#4769)

Integrated into release/v3.8.36 — Google Flow video-generation provider (#4569), release-green validated (typecheck + 21 tests + file-size)

* fix(api): auth on compression run-telemetry + document OMNIROUTE_EVAL_CREDENTIALS (#4694, #4720) (#4796)

Integrated into release/v3.8.36 — auth on compression run-telemetry + OMNIROUTE_EVAL_CREDENTIALS doc, release-green validated (typecheck + 3 tests + env-doc-sync)

* fix(translator): strip top-level client_metadata on the OpenAI passthrough (port from 9router#1157) (#4624)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize `developer` role to `system` for OpenAI-format providers (#4625)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): emit </think> close marker for Anthropic thinking blocks (#4633)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize tools to Anthropic-native shape for non-Anthropic providers (#4650)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(gemini): preserve `pattern` in antigravity tool schema sanitizer (#4651)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(perplexity): validate API keys via /v1/models endpoint (#4654)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(image): prevent compatible nodes from shadowing provider aliases (#4656)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(cli-tools): tolerate JSONC (comments, trailing commas) in tool settings (#4659)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(security): validate kiro region to prevent SSRF (GHSA-6mwv-4mrm-5p3m) (#4629)

Integrated into release/v3.8.36 — kiro region SSRF guard (GHSA-6mwv-4mrm-5p3m), port rebuilt clean over release tip

* fix(cli): harden the systray2 tray runtime (port of 9router#1080) (#4628)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(test): validate anthropic-compatible connections via POST /v1/messages (#4657)

Integrated into release/v3.8.36 — anthropic-compat validation via POST /v1/messages (port 584cf66a), rebuilt clean + baseline; release-green

* fix(executors): strip params unsupported by the target provider/model (#4658)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(claude-oauth): respect 429 backoff on usage endpoint to reduce spam (#4655)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* feat(api/v1): include alias-backed models in /v1/models listing (#4630)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* chore(quality): rebaseline catalog.ts 1574->1577 (#4630 aliases sobre quota-exclusive da release) (#4879)

rebaseline

* feat(compression): Kiro/CodeWhisperer tool-result compression engine (#4635)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(security): don't trust loopback socket as local when behind reverse proxy (#4632)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(opencode): preserve DeepSeek reasoning content in streamed responses (#4631)

Integrated into release/v3.8.36 — DeepSeek reasoning_content injection (port #1099); release-green

* fix(copilot,antigravity): cap maxOutputTokens at 16384 to stop "Invalid Argument" 400 (#4636)

Integrated into release/v3.8.36 — cap maxOutputTokens 16384 antigravity (port #779); release-green

* fix(dashboard): show custom vision models in LLM selector (#4653)

Integrated into release/v3.8.36 — custom vision models in LLM selector (port 5e5e78d3); release-green

* fix(claude): omit adaptive thinking + output_config.effort for haiku (#4661)

Integrated into release/v3.8.36 — haiku adaptive-thinking omit (port); release-green

* feat(provider): CodeBuddy CN (copilot.tencent.com) — full stack (#4664)

Integrated into release/v3.8.36 — CodeBuddy CN provider (port efd20be8); usage.ts import + public-creds allowlist line reconciled; release-green

* feat(combo): Fusion strategy — parallel panel + judge synthesis (16th strategy) (#4652)

Integrated into release/v3.8.36 — Fusion combo strategy (16th, port 87e5c1c6); combo.ts baseline reconciled; release-green

* feat(proxy-pool): Deno Deploy relays + group action buttons (#4643)

Integrated into release/v3.8.36 — Deno Deploy relays (port #1437); proxies.ts baseline reconciled + env docs restored; release-green

* fix(security): pin image fetch DNS resolution to prevent SSRF rebinding (GHSA-cmhj-wh2f-9cgx) (#4634)

Integrated into release/v3.8.36 — pin DNS for image fetch SSRF rebinding guard (GHSA-cmhj-wh2f-9cgx, port c7d07448); caller DNS stubs + test-file baseline reconciled; release-green

* fix(github): route Copilot Codex models to /responses (port from 9router#102) (#4626)

Integrated into release/v3.8.36 — route Copilot Codex models to /responses (port #102); release-green

* fix(copilot): never route Gemini/Claude variants to /responses (chat-completions only) (#4627)

Integrated into release/v3.8.36 — never route Gemini/Claude to /responses (port #1536); fused with #4626 codex routing via supportsResponsesEndpoint gate; release-green

* docs(ops): add canonical incident response runbook (#4868)

Integrated into release/v3.8.36

* docs(perf): add per-endpoint p50/p95/p99 latency + cost budgets (#4867)

Integrated into release/v3.8.36

* fix(proxy): fan out direct dispatcher streams (#4803)

Integrated into release/v3.8.36

* fix(antigravity): exclude standard Gemini rate limit message from quota exhaustion keywords (#4810)

Integrated into release/v3.8.36

* fix(sse): skip third-party tool-name cloak for Anthropic server tools (#4808)

Integrated into release/v3.8.36

* fix(install): make transformers optional for CUDA-host installs (#4807)

Integrated into release/v3.8.36

* fix(combo): propagate selected connection ID to fallback error responses for correct model lockout (#4809)

Integrated into release/v3.8.36

* fix db storage tuning settings (#4834)

Integrated into release/v3.8.36

* fix(sse): drop ccp pin when pinned provider is durably unhealthy (failover + anti-flap) (#4864)

Integrated into release/v3.8.36

* fix(claude): skip mcp__ tool-name cloak + guard missing connectionId (#4861)

Integrated into release/v3.8.36

* chore(quality): reconcile env-doc + file-size base-reds in release/v3.8.36 (#4886)

- env-doc-sync: document PIN_DROP_BACKOFF_LEVEL / PIN_DROP_GRACE_MS (added by the
  ccp-pin health gate #4864) in .env.example + ENVIRONMENT.md.
- file-size: rebaseline image-generation-handler.test.ts 1996 -> 2019 to its actual
  size (pre-existing drift).

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(codex): drop non-standard codex.* events that break responses.stream (env-gated, #4602) (#4715)

Integrated into release/v3.8.36

* feat(routing): honor X-Route-Model header to override body.model (#4863)

Integrated into release/v3.8.36

* feat(live-ws): allow non-loopback clients via LIVE_WS_ALLOWED_HOSTS (closes #4873) (#4877)

Integrated into release/v3.8.36 (live-ws + combo-api commits; Tailscale CGNAT commit held pending opt-in/opt-out decision)

* chore(claude,codex): bump pinned CLI identity — Claude 2.1.158→2.1.187, Codex 0.132.0→0.142.0 (#4883)

Integrated into release/v3.8.36

* fix(security): SSRF allowlist bypass via x-relay-path nos relays Deno/Vercel (#4899)

Integrated into release/v3.8.36

* feat(quota): recuperação proativa de conexões em cooldown (cron heal) [Fase 3 #8] (#4900)

Integrated into release/v3.8.36

* fix(quota): policy inválida não vaza allow + guard connectionIds vazio [Fase 3 #10] (#4901)

Integrated into release/v3.8.36

* feat(quota): saturação real do Claude no fair-share via /api/oauth/usage (#4885)

Integrated into release/v3.8.36

* chore(dashboard): rename Qoder display label from "Qoder AI" to "Qoder" (#4733)

Integrated into release/v3.8.36

* fix(ci): include coverage/lcov.info in coverage-report artifact for SonarQube (#4670)

Integrated into release/v3.8.36

* fix(cli): bump better-sqlite3 runtime pin to 12.10.1 for Node 26 (#4685)

Integrated into release/v3.8.36

* docs: clarify Kiro is ~50 credits/month per account, not unlimited (#4690)

Integrated into release/v3.8.36

* docs(agentbridge): document Electron NODE_EXTRA_CA_CERTS, real model IDs, identity caveat (#4718)

Integrated into release/v3.8.36

* docs(ops): document the release-green family (green-prs, check:release-green, babysit, nightly) (#4679)

Integrated into release/v3.8.36

* fix(translator): replay reasoning_content on plain Xiaomi MiMo turns (port from 9router#1321) (#4639)

Integrated into release/v3.8.36

* feat(opencode-go): advertise glm-5.2 and kimi-k2.7-code (align with official Go endpoints) (#4711)

Integrated into release/v3.8.36

* feat(db): track API endpoint dimension on usage_history (#4676)

Integrated into release/v3.8.36 (migration renumbered 103→105; endpoint plumbed through extracted usage-stats helpers)

* fix(cli): SIGKILL systray child PID before IPC close to avoid macOS NSStatusItem orphan (#4732)

Integrated into release/v3.8.36

* feat(proxy-pool): Cloudflare Workers proxy deployer + pool integration (#4640)

Integrated into release/v3.8.36 (relay type added to RELAY_TYPES set; dropdown UX preserved + Cloudflare item added; proxies.ts file-size rebaselined 1057→1060)

* chore(quality): conserta base-red de release/v3.8.36 (gates + 7 testes + build MDX) (#4915)

A base tinha base-red sistêmica herdada de PRs de outras sessões, bloqueando
TODOS os PRs do ciclo (o TIA roda a suíte full em fail-safe p/ diffs hub).

4 Fast Quality Gates:
- test-discovery (#4877): live-server-allowlist.test.ts em tests/unit/server/
  (não-coletado) + vitest → nunca rodava. Convertido p/ node:test em tests/unit/security/.
- any-budget:t11 (#4664): 3 explicit-any em tokenRefresh.ts tipados (sem crescer file-size).
- docs-symbols (#4868): rotas inexistentes → /api/system/version e
  PUT /api/providers/{id} {isActive:false}.
- docs-all fabricated-claim (#4868 + #4718): 5 bin/*.sh reais criados (rollback,
  snapshot-data, restore-data, restore-policies, cold-start-bench) + _ops-common.sh
  (snapshot VACUUM INTO, guards de confirmação/TTY, testes de contrato); NODE_EXTRA_CA_CERTS
  (env de runtime Node) na allowlist do checker.

7 testes unit base-red (de features alheias à quota):
- oauth-providers-config (#4664): teste alinhado ao provider codebuddy-cn do registry.
- antigravity-model-aliases (#4636): maxOutputTokens esperado 32769→16384 (cap intencional).
- provider-request-capture #4091 (#4861): exemplo do teste trocado de mcp__ (que #4861
  isenta de cloak por causa dos 400s de assimetria de histórico) para um tool de terceiro
  cloakável — preserva o invariante de #4091 SEM reverter #4861.
- combo-error-response: convertido de vitest p/ node:test (era coletado pelo glob node:test
  e crashava); api/** e server/** removidos do vitest.config (config morta).

Build MDX (dast-smoke, #4679):
- docs/ops/RELEASE_GREEN.md não tinha frontmatter `title` → fumadocs-mdx rejeitava no
  webpack compile ("invalid frontmatter: title expected string"), quebrando o next build
  (e o deploy). Frontmatter title adicionado (único doc do collection sem ele).

17/17 Fast Quality Gates + suíte unit completa (17737 testes, 0 fail) + vitest verdes localmente.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): saturação proativa por headers de tokens (universal) [Fase 3 #2] (#4907)

storeRateLimitHeaders só capturava os headers de REQUESTS (RPM/min), que não
refletem a pressão de TOKENS. Agora também parseia os headers de tokens (em toda
resposta, sucesso também) para throttle proativo antes do 429:
- Anthropic: anthropic-ratelimit-tokens-{limit,remaining,reset} (+ input/output), RFC3339.
- OpenAI: x-ratelimit-{limit,remaining,reset}-tokens, reset em duração (6m0s).

saturation = 1 − remaining/limit; resetAt normalizado a epoch (parse de duração
ReDoS-safe). getTokenHeaderSaturation por (provider, connectionId). fetchGeneric-
Saturation passa a usar esse sinal (complementa o oauth/usage do #1, que segue
primário p/ Claude). Fail-open, cache mantido, request-path inalterado.

16 testes novos + regressão (oauth/usage #1 8/8, signals 6/6) = 30/30;
typecheck:core + eslint limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): estratégia de combo "headroom" — seleção por folga de cota [Fase 3 #4] (#4908)

Nova estratégia de roteamento que escolhe a conexão com MAIS folga de plano:
headroom = 1 − max(util_5h, util_7d) (técnica do dario), via getSaturation
(melhorado p/ Claude no #1). Proativo em vez de só fill-first reativo.

- Helper PURO headroomRanking.ts (computeHeadroom + rankByHeadroom; saturação
  injetada, não-mutante, tie-break estável, fail-open).
- Orderer async em combo/quotaStrategies.ts (reusa a maquinaria reset-aware de
  expansão de conexões + concorrência limitada; seam injetável).
- Registrada como "headroom" em routingStrategies (combo-only); fill-first segue
  default — nenhuma estratégia existente tocada.
- baseline file-size combo.ts 3168->3180 (só +12L de dispatch; lógica fora do god-file).

16 testes novos + combo-strategies 15/15 = 31/31; typecheck:core + eslint +
file-size limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): cap per-(key,model) — quota_allocation_model_caps [Fase 3 #7] (#4927)

* feat(quota): cap per-(key,model) com tabela quota_allocation_model_caps [Fase 3 #7]

Fecha o buraco onde uma API key pode drenar o pool inteiro consumindo um único modelo.

Tabela nova: quota_allocation_model_caps(pool_id, api_key_id, model, cap_value, cap_unit)
PK composta (pool_id, api_key_id, model). cap_unit alinhado ao QuotaUnit existente.

Comportamento: keyA acima do cap para modelo M → bloqueada somente em M; ainda
permitida em qualquer outro modelo no mesmo pool. Cap <= EPSILON → ignorado (seed).

Consumo por-(key,model) usa bucket segregado no quota_consumption existente
(poolId mangled ':model:<model>') com window fixa 'hourly'; nenhuma nova tabela
ou método de store necessário.

Módulo novo: src/lib/db/quotaModelCaps.ts (getModelCap/setModelCap/deleteModelCap/listModelCaps)
enforce.ts ganha o pre-check em enforceQuotaShare + recording em recordConsumption.
EnforceInput e RecordConsumptionInput ganham model?: string (backward-compatible).
localDb.ts re-exporta os 4 helpers (Hard Rule #2).

TDD: tests/unit/quota-per-key-model.test.ts — 4 cenários (bloqueia em M, permite em M2,
sem cap → sem bloqueio, EPSILON → ignorado). Todos os gates de qualidade passam.

* feat(quota): plumba model resolvido no hot path para ativar o per-(key,model) cap [Fase 3 #7]

A tabela/enforce do commit anterior estavam INERTES: o hot path não passava `model`
ao enforce nem ao record, então nenhum model-cap disparava em produção.

Plumbagem (model resolvido = mesma var usada no log/roteamento, pós background-redirect/alias):
- chatCore.ts: enforceQuotaShare ganha `model`; scheduleQuotaShareConsumption recebe `model`.
- chatCore/quotaShareConsumption.ts: threade `model` no RecordConsumptionInput (non-streaming).
- spendRecorder.ts: recordStreamingConsumption já recebia `model` — agora o coloca no
  RecordConsumptionInput (streaming accrue por-modelo).
- embeddings.ts: enforce + record ganham `model`.

Namespace do cap = id do modelo RESOLVIDO (o mesmo de modelForScope/pendingScope/getUnsupportedParams),
não o requestedModel cru nem o finalModelToUpstream (sem prefixo de provider). Operador configura
o cap contra esse id. `model || undefined` em todos os pontos: vazio/null → check pulado (fail-safe,
zero latência — só um campo no objeto).

Teste de integração novo (tests/unit/quota-per-key-model-hotpath.test.ts): prova end-to-end que
N consumos via scheduleQuotaShareConsumption({model}) → enforceQuotaShare({model}) bloqueia, e que
outro modelo no mesmo pool ainda passa; + guard de que enforce SEM model nunca dispara model-cap.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5] (#4929)

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5]

Adiciona stickiness de sessão ao roteamento de combo: uma conversa multi-turno
é roteada para a MESMA conexão enquanto ela permanecer saudável, evitando a
perda do prompt-cache do provider (custo 5-10× sem stickiness, efeito conhecido
no dario/clewdr).

Implementação:
- `open-sse/services/combo/sessionStickiness.ts` (novo, <800 linhas):
  mapa em memória (messageHash → connectionId) com TTL 15 min + cap 500 entradas;
  `applySessionStickiness` promove a conexão sticky ao índice 0 dos targets
  ordenados pelo strategy, guardado por `computeHeadroom > 0.15` (threshold);
  quando saturada (headroom ≤ 0.15), o binding é limpo e a seleção normal reage.
  Hash da sessão = SHA-256 dos primeiros chars da 1ª mensagem user → 16 hex chars.
  Seam de teste: `__setStickinessHeadroomFetcherForTests`.
- `open-sse/services/combo.ts`: import + 2 pontos de integração (pré-eval-scores
  e pós-success), dentro do orçamento congelado de 3180 linhas.
- `tests/unit/combo-session-stickiness.test.ts`: 19 testes node:test + assert/strict,
  todos via injeção de fetcher (zero rede/DB).

Threshold 0.15: conexão a >85% de utilização está a um burst de rate-limit;
o benefício de cache não compensa manter-se numa conexão degradada. Valor
alinhado com a zona de soft-penalty do restante do engine de quota-share.

* test(combo): isola combo-strategies da session stickiness (#5)

selectedConnectionFor reusa o mesmo body, então o sticky map (#5) fixava a
connection após a 1ª chamada e quebrava o round-robin tie-break do teste
reset-aware. Limpa o sticky map no início da helper — a stickiness tem suíte
própria (combo-session-stickiness). Sem enfraquecer asserts.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): buckets multi-janela por conexão (5h/7d/per-model) [Fase 3 #3] (#4928)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(providers): decompõe catálogo providers.ts em módulos de dados (godfile sweep, #3501) (#4917)

Integrado em release/v3.8.36 (godfile sweep providers.ts, #3501)

* refactor(pricing): decompõe pricing.ts em shared-tiers + DEFAULT_PRICING particionado (godfile sweep, #3501) (#4918)

Integrado em release/v3.8.36 (godfile sweep pricing.ts, #3501)

* refactor(api): extrai camada-folha pura de validation.ts (URL/headers/transport) (#4921)

Integrado em release/v3.8.36 (validation.ts split fatia 1 — leaf layer)

* refactor(api): extrai validators web-cookie + Meta AI de validation.ts (#4922)

Integrado em release/v3.8.36 (validation.ts split fatia 2 — web-cookie + Meta AI)

* refactor(api): extrai validators enterprise-cloud + probe compartilhado de validation.ts (#4923)

Integrado em release/v3.8.36 (validation.ts split fatia 3 — enterprise-cloud + probe)

* refactor(api): extrai validators áudio/speech + misc apikey de validation.ts (#4930)

Integrado em release/v3.8.36 (validation.ts split fatia 4 — áudio/speech + misc apikey)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9] (#4939)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9]

Estratégia interna "quota-share" isolada num módulo dedicado — NÃO toca a seleção/
fair-share genérica (decisão do dono: não mexer no que já funciona). Os combos qtSd/
(quotaCombos.ts) passam de fill-first para essa strategy; combo.ts ganha só 1 branch
de dispatch que delega 100% ao módulo (nenhum case existente alterado).

- quotaShareStrategy.ts: gating per-model (isBucketSaturated do #3) + DRR (quantum
  proporcional ao weight) + P2C sobre carga in-flight.
- quotaShareInflight.ts: contador in-flight com TTL/lease de 120s — fallback do
  decrement-on-abort sem precisar instrumentar o combo genérico.
- "quota-share" registrada como strategy INTERNA (não exposta na UI).
- testes de síntese (quota-combo-balancing, quota-multiprovider) alinhados: a strategy
  esperada dos combos qtSd/ passa de "fill-first" para "quota-share" (alinhamento ao novo
  comportamento intencional, não mascaramento — os 73 testes de qtSd/ seguem verdes).

* test(quota-share): alinha 2 scope-guards ao godfile sweep (base-reds que bloqueavam o CI)

Dois testes de "arquivo contém X" quebraram por decomposições de godfile que outras
sessões mergearam no release DURANTE a validação de #9 — NÃO são regressão de #9
(que não toca validation/oauth). Alinhados ao novo layout, asserts preservados:
- proxy-bypass-scope-guard #3226: bypassProxyPatch foi extraído de validation.ts para
  validation/headers.ts (split #4921–#4930) → o teste lê a camada de validação.
- sse-error-passthrough #3324: a windsurf authHint foi extraída de providers.ts para
  providers/oauth.ts → o teste lê o novo local.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(api): extrai validators search + embedding/rerank de validation.ts (#4932)

Integrated into release/v3.8.36

* refactor(api): extrai format-validators (OpenAI/Anthropic) de validation.ts (#4933)

Integrated into release/v3.8.36

* refactor(db): extrai model-permission matching de db/apiKeys.ts (#4936)

Integrated into release/v3.8.36

* refactor(db): extrai row-parsers + tipos compartilhados de db/apiKeys.ts (#4943)

Integrated into release/v3.8.36

* refactor(db): extrai column-mapping (snake↔camel) de db/core.ts (#4947)

Integrated into release/v3.8.36

* refactor(db): extrai schema-column reconciliation de db/core.ts (#4948)

Integrated into release/v3.8.36

* refactor(sse): extrai scalar/format helpers de services/usage.ts (#4949)

Integrated into release/v3.8.36

* refactor(sse): extrai quota-core (UsageQuota + builders) de services/usage.ts (#4950)

Integrated into release/v3.8.36

* fix(translator): regroup parallel tool results adjacent to their assistant (#4714) (#4882)

Integrated into release/v3.8.36 (fixes #4714)

* fix(qoder): exchange PAT for jt-* job token before Cosy chat (#4683) (#4884)

Integrated into release/v3.8.36 (fixes #4683)

* refactor(sse): dedup fallback tool_call id helper (#4736)

Integrated into release/v3.8.36

* refactor(open-sse): extract safeParseJSON util, dedup tryParseJSON (#4735)

Integrated into release/v3.8.36

* fix(compression): eliminate ReDoS in math_inline preservation pattern (#4795) (#4838)

Integrated into release/v3.8.36 (fixes #4795)

* fix(combo): fetch models dynamically from custom provider endpoints (#4860)

Integrated into release/v3.8.36

* feat(providers): update volcengine-ark model list with DeepSeek V4 (#4905)

Integrated into release/v3.8.36

* fix(translator): provider thinking compatibility (DeepSeek/Gemini) (#4946)

Integrated into release/v3.8.36

* feat(combo): task-aware routing strategy (#4945)

Integrated into release/v3.8.36

* refactor(sse): extrai a família MiniMax de services/usage.ts (#4952)

Integrated into release/v3.8.36

* refactor(sse): extrai a família GLM de services/usage.ts (#4953)

Integrated into release/v3.8.36

* refactor(sse): extrai a família Antigravity de services/usage.ts (#4956)

Integrated into release/v3.8.36

* fix(dashboard): show custom provider given-name instead of internal id across dashboard pages (#4603) (#4960)

Integrated into release/v3.8.36 (fixes #4603)

* fix(api): evict stale in-memory rate-limit windows to stop slow heap leak (#4041) (#4957)

Integrated into release/v3.8.36 (fixes #4041)

* fix(api): parse /v1/responses body once instead of 3-4x on the hot path (#4041) (#4958)

Integrated into release/v3.8.36 (fixes #4041)

* fix(translator): preserve legitimate empty-string tool arguments in openai-to-claude streaming (#4951) (#4959)

Integrated into release/v3.8.36 (fixes #4951)

* chore(quality): reconcile file-size baseline for #4960 provider-display-name (#4961)

Integrated into release/v3.8.36

* fix(dashboard): restore home provider-topology card hidden by #4596 default (#4963)

Integrated into release/v3.8.36 — restores home topology card (#4596 regression)

* fix(build): drop @omniroute/open-sse from optimizePackageImports (build OOM) (#4968)

Integrated into release/v3.8.36 — fixes build OOM (optimizePackageImports open-sse)

* fix(quota): migração 107 ativa estratégia quota-share nos combos qtSd/ existentes [Fase 3 #9] (#4962)

Integrated into release/v3.8.36

* feat(quota): respeita max_concurrent por conexão no roteamento (#4965)

Integrated into release/v3.8.36

* feat(quota): combo quota-share espera cooldown curto e re-despacha (Variante A) (#4967)

Integrated into release/v3.8.36

* fix(quality): resolve base-reds da release — db-rules allowlist + task-aware router precedence (#4973)

Dois base-reds pré-existentes que reprovavam o CI da release v3.8.36 (Fast
Quality Gates + Unit Tests fast-path), independentes de qualquer feature em voo:

1. check:db-rules / allowlist: os módulos db-internal caseMapping (#4947) e
   schemaColumns (#4948), extraídos de db/core.ts e importados só por ele, não
   estavam em INTENTIONALLY_INTERNAL. Registrados na allowlist (correção
   canônica — são internos legítimos, não re-exportados pelo localDb).

2. auto-strategy honra LKGP/cost (combo-routing-engine.test.ts, 2 testes): o
   task-aware reordering (#4945, reorderByTaskWeight) roda para strategy "auto" e
   era aplicado DEPOIS do router explícito (selectWithStrategy: lkgp/cost),
   sobrescrevendo o orderedTargets[0] que o operador escolheu. Instrumentação
   provou: post-filter [0]=claude (LKGP) → post-task [0]=gpt-oss. Correção: quando
   o auto usa router explícito, preserva o [0] dele e deixa o task-aware refinar
   só a cauda de fallback. gpt-oss-120b PERMANECE tool-capable (não é mudança de
   catálogo; o model-capabilities-registry test segue verde).

Validado: 121 testes (combo-routing-engine + combo-task-aware + registry) verdes,
red-check confirmado, db-rules/file-size/typecheck/lint/prettier OK.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): serializa concorrência por conexão no caminho quota-share (FASE 2.1) (#4970)

O gating de quota-share em selectQuotaShareTarget é fail-open: uma conexão
at-cap só é despriorizada, nunca bloqueada. Com 1 conexão por conta de
assinatura (caso comum), chamadas concorrentes ainda floodam a conta (→ 429 +
cooldown) — provado live na .15: 3 chamadas concorrentes com max_concurrent=1
despacharam todas em 94ms.

Adiciona um semáforo POR CONEXÃO em torno do dispatch quota-share: chamadas
excedentes esperam na fila em vez de floodar (key qsconn:<connectionId>, cap =
max_concurrent da conexão). Fail-open em fila saturada/timeout para nunca
piorar disponibilidade. Gated por strategy===quota-share + kill-switch
resilienceSettings.quotaShareConcurrencyLimit (default on; UI no ResilienceTab).

Lógica extraível isolada no leaf puro combo/quotaShareConcurrency.ts
(unit-testado: estabilidade da key, no-op sem cap, serialização real,
fail-open). Settings + schema + UI espelham comboCooldownWait.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* docs(resilience): document Quota-Share Concurrency Control (max_concurrent + serialization + cooldown-wait) (#4980)

Documents the v3.8.36 quota-share concurrency layers in RESILIENCE_GUIDE.md:
per-connection max_concurrent cap, the quota-share request serialization semaphore
(FASE 2.1, qsconn:<connectionId>, fail-open, kill-switch), and the combo
cooldown-aware retry — so operators know how to cap a subscription account's
concurrency and why the routing gate alone cannot contain a single-connection flood.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): proxy-pool success gating, sync timestamp, opt-in Redis (#4878) (#4988)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976) (#4986)

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976)

* chore(quality): rebaseline accountFallback.ts file-size for #4976 fix

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(compression): stop RTK over-truncating file-read tool results (#4559) (#4987)

* fix(compression): stop RTK over-truncating file-read tool results (#4559)

* chore(quality): trim #4559 comment to keep rtk/index.ts within size cap

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954) (#4989)

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954)

* chore(quality): rebaseline auth.ts file-size for #4954 (+39: synthetic no-auth providerSpecificData hydration of fingerprints/accountProxies; irreducible credential-path wiring, covered by opencode-proxy-rotation-4954.test.ts + 159 auth/noauth regression)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540) (#4990)

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540)

* chore(quality): document STATUS_SOFT_DEPRIORITIZE_FACTOR + rebaseline combo.ts for #4540

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): switch to visible filter after auto-hiding failed models in test-all (#4887) (#4991)

* fix(dashboard): switch to visible filter after auto-hiding failed models in OAuth provider test-all (#4887)

* test(dashboard): move #4887 test into tests/unit/ui so a CI runner collects it

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(pollinations): only enable jsonMode when JSON output is requested (#3981) (#5009)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003) (#5008)

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003)

* docs(changelog): restore #3981 pollinations entry eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665) (#5010)

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665)

MODEL_MAP was missing the advertised catalog ids gpt-5.5, gpt-5.5-pro,
gpt-5.4-pro and gpt-5.2-pro, so MODEL_MAP[model] ?? model sent the dot-form
id verbatim to the ChatGPT backend-api, which silently rejected it and served
the default Plus model. Map each to its dash-form slug. gpt-4-5 is already
dash-form and falls through correctly, so it is intentionally left unmapped.

Extends the executor MODEL_MAP test with the four ids and adds a drift guard
asserting every advertised dot-form catalog id reaches the backend in dash-form
(never verbatim), guarding future catalog<->map drift.

file-size: tests/unit/chatgpt-web.test.ts frozen baseline 2809->2855 (+46) for
the added test cases and drift-guard test; executor source unchanged in baseline.

* docs(changelog): restore #3981/#5003 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005) (#5011)

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005)

* docs(changelog): restore #3981/#5003/#4665 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* Fix Ollama Cloud max reasoning effort (#4993)

Integrated into release/v3.8.36

* fix(copilot): replace execSync with execFile to prevent command injection (#5024)

Integrated into release/v3.8.36

* fix(plugin): auth.json dual-key fallback for auto-prefix migration (#5027)

Integrated into release/v3.8.36

* feat(endpoint): per-endpoint custom system prompt injection (#5022)

Integrated into release/v3.8.36

* fix(headroom): translate openai-responses input through OpenAI for compression (#5023)

Integrated into release/v3.8.36

* docs(changelog): add entries for #4993, #5024, #5027 (release notes credit)

* fix(api): stop /api/system/env/repair 500 on packaged install (#5006) (#5028)

* fix(api): stop /api/system/env/repair 500 on packaged install — lazy createRequire in sync-env.mjs (#5006)

scripts/dev/sync-env.mjs ran createRequire(import.meta.url) at module
top-level. When webpack bundles it into the standalone env-repair route,
import.meta.url is frozen to the build-machine path (file:///home/runner/...)
and createRequire throws during module evaluation, so the whole route
module fails to load and every GET returns HTTP 500 — breaking the
onboarding wizard on packaged/global installs.

- Move createRequire into the guarded better-sqlite3 block (only place
  that needs it); a bad import.meta.url now returns the safe default.
- resolveRootDir() falls back to process.cwd() when fileURLToPath throws.
- route.ts passes an explicit rootDir (process.cwd()) so the helper never
  derives the root from the frozen import.meta.url, matching the .env
  target used by createEnvBackup().
- Regression guard: assert sync-env.mjs has no top-level createRequire +
  getEnvSyncPlan(oauth) works with explicit rootDir without throwing.

* docs(changelog): restore #4993/#5023/#5024/#5027 + custom-system-prompt/headroom entries eaten by release merge

* chore(quality): rebaseline 3 inherited base-reds from release merge

Files NOT touched by this PR — grew on release/v3.8.36 via --admin merges and
inherited here through 'git merge origin/release':
- open-sse/executors/base.ts 1414->1416 (#4993 Ollama Cloud max-effort)
- src/lib/db/settings.ts 1149->1151 (#5023 custom system prompt)
- src/app/(dashboard)/.../endpoint/EndpointPageClient.tsx 2570->2612 (custom system prompt UI)

* chore(release): finalize v3.8.36 CHANGELOG + docs (2026-06-25)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Makcim Ivanov <makcimbx@gmail.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: Jefferson Felizardo <jeffer1312@gmail.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
2026-06-25 13:17:40 -03:00
diegosouzapw
16b0499192 fix(api): harden batch and file endpoints for auth and recovery
Reject invalid API keys even when authentication is optional and add
OPTIONS handlers for batch and file routes to support CORS preflights.

Also recover orphaned batches stuck in finalizing after restarts,
include finalizing in pending batch queries, preserve multipart upload
content handling, and fetch remote vision images as data URIs for
Anthropic requests.
2026-04-22 17:10:47 -03:00
Hernan Javier Ardila Sanchez
b709dca2d6 feat(vision-bridge): add automatic image description fallback for non-vision models (#1476)
* feat(vision-bridge): add automatic image description fallback for non-vision models

Implements VisionBridgeGuardrail (priority 5) that intercepts image-bearing
requests to non-vision models, extracts descriptions via a configurable
vision model (default: gpt-4o-mini), and replaces images with text before
forwarding. Fails open on any error.

- Add VisionBridgeGuardrail class extending BaseGuardrail
- Add visionBridgeHelpers: extractImageParts, callVisionModel, replaceImageParts, resolveImageAsDataUri
- Add visionBridgeDefaults with configurable settings
- Register VisionBridgeGuardrail in GuardrailRegistry at priority 5
- Add 51 unit tests covering all spec scenarios (VB-S01 through VB-S10)
- Dependency injection for getSettings and callVisionModel (testable without SQLite)

Closes diegosouzapw/OmniRoute#1424

* fix(vision-bridge): resolve Anthropic API, parallel processing, provider keys, structuredClone
2026-04-21 18:14:24 -03:00