Commit Graph

2213 Commits

Author SHA1 Message Date
Bob.Hou
65fbba4893 fix(antigravity): wrap Pro fallback chain in try/catch for timeout resilience (#7290)
* fix(antigravity): wrap executeOnce in try/catch for Pro fallback chain

When a Pro-tier candidate times out or throws a network error, the

exception now continues to the next candidate instead of aborting

the entire chain. Includes diagnostic logging and unit tests.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(antigravity): propagate abort signal in Pro fallback catch block

Re-throw AbortError and signal.aborted immediately instead of
retrying the next candidate. Prevents wasted upstream requests
after client disconnect.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(mitm): skip DNS modification when sudo unavailable (container)

In containers (USER node, no sudo, not root) provisionDnsEntries() now detects the condition up-front and logs a clear message instead of attempting sudo and silently swallowing the error. Adds canElevate() to the injectable deps interface for testability, and supports SKIP_ANTIGRAVITY_DNS=true for explicit opt-out.

* fix(antigravity): improve abort detection and fallback error handling

Check Error.name === 'AbortError' for non-DOMException environments

(polyfills, test harnesses). Capture first 400 from any candidate

(not just i===0) so mixed paths surface the 400 instead of a

generic error. Return firstResult when last candidate throws,

consistent with the all-400 case.

* test(mitm): add coverage for container-skip DNS provisioning

* test(mitm): harden container-skip DNS test assertions

The SKIP_ANTIGRAVITY_DNS=true and canElevate()=false tests used empty
agentStates/customHosts, so they could not distinguish 'all steps
skipped' from 'only the default step skipped'.  Provide non-empty mocks
and assert addHostsDns was NOT called.

Also add a SKIP_ANTIGRAVITY_DNS=false boundary test confirming the
strict === "true" comparison does not block normal provisioning, and
verify sudoPassword passthrough in the canElevate=true happy-path test.

* refactor(mitm): split provisionDnsEntries below complexity gate

provisionDnsEntries() (complexity ~18, this PR's try/catch/log
additions pushed it over check-complexity.mjs's threshold of 15)
and execute()'s Pro-fallback loop (complexity 27, from wrapping
executeOnce() in try/catch for timeout resilience) were both over
the gate. Decomposed each into small named helpers, no behavior
change:

- provision.ts: split into provisionDefaultDns/provisionAgentDns/
  provisionCustomHostsDns, each wrapping one best-effort DNS step.
- antigravity.ts: extracted the fallback-chain catch/400-handling
  decisions (handleAntigravityFallbackChainError,
  isAntigravityAbortError, handleAntigravityFallback400) into a new
  antigravity/proFallbackChain.ts submodule (pure, no executor
  instance state), mirroring the existing antigravity/sseCollect.ts
  submodule pattern. Also fixes the antigravity.ts file-size cap
  (was pushed to 1854 lines > 1813 frozen ceiling by this PR's own
  try/catch addition; now 1771).

execute/provisionDnsEntries no longer appear with ruleId complexity
or max-lines-per-function in the check-complexity.mjs report.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(antigravity): drop redundant loop continue (cognitive-complexity gate)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(antigravity): fold fallback outcome dispatch into switch (cognitive gate)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:14:14 -03:00
Bob.Hou
88c28428e1 feat(providers): add Agnes AI native provider support (#7035)
Add built-in provider registry entry for Agnes AI (agnes-ai.com),
a permanently free OpenAI-compatible API by Sapiens AI.

Models:
- agnes-2.0-flash: 256K context, 64K output, thinking mode
  (reasoning_content), vision, tool calling
- agnes-1.5-flash: 256K context, 64K output, vision

The baseUrl uses the full /v1/chat/completions path. This is the
standard pattern for registry entries (115 built-in providers use
the same convention). The default executor's buildUrl() routes
registry entries through normalizeOpenAIChatUrl(), which detects
the existing /chat/completions suffix and returns the URL as-is
without appending. Only openai-compatible-* connections (dashboard-
added custom providers) unconditionally append the path.

Specs verified against MODEL_CATALOG.md v2026.06.28
(github.com/AgnesAI-Labs/AgnesAI-Models) and live API
testing at apihub.agnes-ai.com/v1 (2026-07-13).

Fixes #5580

Signed-off-by: Minxi Hou <houminxi@gmail.com>
2026-07-18 15:13:52 -03:00
KooshaPari
f8e56e4615 fix(router-eval): retained-optimization gate cleanup (#7318)
* feat(eval): add router-eval harness (AIQ scoring, regression gate, Pareto search)

Extracts a standalone router-eval evaluation tool that replays routing
decisions (from NDJSON corpora or the usage_history/call_logs SQLite
tables) into an AIQ (success/latency/cost) score, compares baseline vs.
candidate router configs with a retained-run regression gate, and ranks
Pareto-optimal candidates across a search space — a sibling to the
existing eval:compression harness.

New scripts: scripts/router-eval/{index,compare,patch-compare,search,
trends}.ts, scripts/check/check-router-eval-regression.ts, and
src/lib/routerEval/index.ts, wired via 6 new package.json entries
(eval:router, eval:router:compare, eval:router:patch-compare,
eval:router:search, eval:router:trends, check:router-eval).

Reconstructed onto current release/v3.8.49 from the original ~142-commit
stale PR branch: only the genuinely new router-eval payload (17 files)
was extracted — the other ~560 changed files in the original diff were
base-drift already present on release in newer form. The new package.json
scripts now invoke `node --import tsx` instead of `bun`, matching the
`eval:compression` precedent (Bun is reserved for a closed 5-script
allowlist). The runtime-detection shim (`"Bun" in globalThis`) already
present in the harness gracefully falls back to better-sqlite3 under
Node, so no logic changes were needed there; the retained-run manifest's
previously-hardcoded `runtime: "bun"` field and matching CLI help text
were corrected to reflect the actual invocation.

All 27 existing router-eval unit tests pass unchanged.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(router-eval): decompose toRouterObservation below complexity gate

toRouterObservation had cyclomatic complexity 18 (gate max is 15). Extract
the per-field parsing/normalization into pure helpers (sampleId, model
fields, latency, cost derivation, success) so the entry point is a plain
sequential assembly of a RouterObservation. Behavior is unchanged — same
tests pass, same fallbacks, same precedence between input aliases.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:39 -03:00
KooshaPari
f657c7865a feat(issue-agent): surface RecordedTriageTimeoutError as 504 (#7315)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168)

* feat: scaffold issue agent and router eval provenance

* feat: wire recorded issue triage runner

* feat: ingest recorded issue context

* feat: persist issue agent audit log

* feat: import recorded github issue exports

* docs: document issue agent env toggle

* fix(issue-agent): validate run requests

* fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179)

* fix: validate issue agent run requests

* docs: add issue agent execution traceability

* feat(issue-agent): route recorded triage through chat

* test(issue-agent): verify recorded triage through chat route

* docs(issue-agent): add executable triage session artifacts

* fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (#7216)

* fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (#7220)

* feat(issue-agent): surface RecordedTriageTimeoutError as 504

When the recorded-triage chat completion times out, the AbortController
fires an AbortError that previously surfaced as a generic 400 to the
caller. This change:

  * Adds a `RecordedTriageTimeoutError` that wraps the AbortError
    with the timeoutMs context.
  * Re-throws it from `executeRecordedTriageChatCompletion` so the
    caller can distinguish timeouts from other failures.
  * In the runs route, catches it and returns a 504 with code
    `ISSUE_AGENT_TIMEOUT` so clients can render a useful error.

Tests:
  * issue-agent-execution.test.ts        — verifies the typed error
  * issue-agent-route-execution.test.ts  — covers timeout path
  * issue-agent-runs-route.test.ts       — verifies 504 mapping

* fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (#7225)

* test(ci): make the #6634 selfref guard hermetic — main's copy hard-fails every PR (#7341)

main's copy of this test still does git I/O inside a unit test:

    const baseSrc = git(['show', 'origin/main:' + FILE]);

Runners check out a shallow single ref, so origin/main does not resolve and the
test dies with 'fatal: invalid object name origin/main'. Every PR into main
fails Unit Tests (7/8) on it — today that is #7313, #7315, #7316, #7334, #7336
and #7337, six PRs red on a defect none of them introduced. #7313 has no other
red at all.

release/v3.8.49 already carries a fix (2e42b8efc, #7174: try/catch, fetch
origin/main on demand, t.skip() when unreachable), but it only reaches main at
release time — so main stays broken for the whole cycle. Cherry-picking it would
also import a new problem: PR Test Policy classifies t.skip() as a silenced
assertion, which we watched it correctly catch on #7300 today.

This is the hermetic version instead (ported from #7327, which does the same for
the release branch): read the file straight off disk, compare against an empty
base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point —
and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the
runner's checkout depth can break.

The #6634 regression stays covered: the guard's logic lives in
SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both
ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and
the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left
byte-identical.

Co-authored-by: growab <nekron@icloud.com>

* chore(quality): tighten main's coverage baseline to the CI's real numbers (#7347)

main's ratchet had been failing --require-tighten on every PR: 11 metrics
improved but the baseline was never tightened. Same class as the #6634
selfref guard — an infra fix that lands only on the release branch leaves
main red for the whole cycle, and every PR into main pays for it.

Values are the merged-coverage numbers from a run on main itself (a local
run measures ~68% vs CI's ~80%; the baseline's own note warns about that
gap). Only the 11 coverage values change — gitleaks and semgrepFindings
keep main's own state.

No changelog fragment: #7326 carries it on release/v3.8.49, and a second
one here would double the entry at release time.

* fix(issue-agent): dryRun-explicit test fixture + sanitize the generic error catch

Two independent Hard Rule #12/#18 fixes on the recorded-triage runs route:

1. tests/unit/issue-agent-route-execution.test.ts's "preserves the normal
   chat route provider failure response" test omitted dryRun from its
   request body. createRecordedTriageRun() computes `dryRun: input.dryRun
   !== false`, so an omitted dryRun defaults to true (dry-run mode) and the
   route returns the deterministic dry-run summary WITHOUT ever calling
   executeRecordedTriageChatCompletion() -- the mocked 429 fetch was never
   invoked, so the test always observed the 200 dry-run response instead.
   Add the missing `dryRun: false`, matching the sibling test above it. The
   normal chat-completions route also enriches upstream errors with a
   "[provider/model] [status]:" prefix and a connection-cooldown hint
   (RESILIENCE_GUIDE.md) rather than passing them through byte-for-byte, so
   the assertion now checks the original message survives (status +
   substring) instead of exact-matching the mocked JSON shape.

2. src/app/api/issue-agent/runs/route.ts's generic (non-timeout) catch
   returned raw `error.message` straight to the client. Validation failures
   thrown by this module (bad issue URL, malformed GitHub export) are safe,
   curated messages -- but appendIssueAgentAuditRecord()'s mkdir/appendFile
   under DATA_DIR can throw a real Node fs error (ENOENT/EACCES/EEXIST, ...)
   whose raw `.message` embeds the server's absolute filesystem path. Add
   isNodeSystemError() (keys off NodeJS.ErrnoException's `.code`, which only
   Node's own fs/system errors set) to replace that class of error with a
   generic message, and route everything else through sanitizeErrorMessage()
   per Hard Rule #12. Add a regression test that forces a real audit-write
   failure (pre-creating a file where audit.ts expects to mkdir) and asserts
   the response contains neither the errno code nor the DATA_DIR path.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: KooshaPari <koosha@example.com>
Co-authored-by: growab <nekron@icloud.com>
2026-07-18 15:13:35 -03:00
KooshaPari
8febd55e44 feat(sidecar): support conditional provider manifest refresh (#7130)
* feat(sidecar): support conditional provider manifest refresh

* fix(sidecar): accept weak manifest validators

* perf(sidecar): cache provider manifest payload

* docs(sidecar): describe manifest conditional refresh

* test(sidecar): restore CORS preflight and manifest-content coverage

The ETag/conditional-refresh rewrite of this test file dropped two
pieces of coverage without replacing them: the CORS OPTIONS-preflight
test, and the 200-response test's providers.length>100 /
clientSecret-not-leaked assertions. This is the only test file for
the provider-plugin-manifest route, so none of that was covered
anywhere else afterward.

Restore both: fold the providers.length/openai-presence/clientSecret
assertions back into the "stable ETag" 200-response test alongside
the new ETag checks, and add back a dedicated OPTIONS test asserting
the CORS preflight headers.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:31 -03:00
KooshaPari
0bbdb839d9 Explain effective auto-combo scoring weights (#7087)
* fix(inspector): report effective auto scoring weights

* fix(inspector): check options.combos before the health-signal short-circuit

resolveConfiguredCombos() returned [] unconditionally whenever
healthResponse, forecastResponse, and either skipAutopilot or
autopilotReport were all supplied -- before it ever looked at
options.combos. That is exactly the call shape
comboHealthDashboard.ts::buildComboHealthDashboardResponse() always
uses (it resolves combos/health/forecast/autopilot once, then passes
all of them into buildComboScoringInspectorResponse together), so
through the real dashboard integration the caller-supplied combos
were silently discarded every time. Since combosById/combosByName
(built from resolveConfiguredCombos()'s return value) are what
resolveInspectorWeights() uses to report a combo's actual configured
modePack/weights, this meant the dashboard's weightSource/modePack
fields always came back "default", even for a combo with an explicit
mode pack configured.

Check options.combos first, unconditionally, and only fall back to
the health-signals short-circuit (skip an unnecessary getCombos() DB
round-trip) or a fresh getCombos() call when the caller didn't supply
combos at all.

Adds a regression test that drives the real
buildComboHealthDashboardResponse() end-to-end with a combo configured
for modePack "ship-fast", proving the inspector now reports the
correct weightSource/modePack through that call path -- the exact
scenario the PR's own tests didn't cover (they only exercised
buildComboScoringInspectorResponse() directly with comboId+combos,
never combos alongside a pre-resolved healthResponse+forecastResponse).

Also reconciles the existing "skipAutopilot avoids rebuilding
autopilot report" test: it asserted options.combos was never even
read in this scenario via a throwing property getter, which pinned
the exact short-circuit-wins-always bug this fix removes. The test's
options object never supplied a real combos array in the first place,
so the fixed code still takes the same DB-free path -- only the
poison-pill mechanism (which trapped a mere property read rather than
an actual getCombos() call) no longer applies.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(usage): split resolveInspectorWeights below complexity gate

resolveInspectorWeights had cyclomatic complexity 16 (gate max is 15).
Extract the auto-config precedence resolution (autoConfig -> config.auto
-> config -> {}), the mode-pack name lookup, and the explicit-weights
validation into small pure helpers, each returning early instead of
nesting ternaries. Behavior is unchanged, including the fallback warning
that fires when a mode pack or explicit weights were configured but
could not be resolved.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:26 -03:00
Jan Leon
c711ed257b Restore proxy navigation and sidebar accordion state (#7381)
* fix(sidebar): restore proxy navigation and accordion state

* fix(proxy): prevent free pool translation crash

* fix(settings): guard protected sidebar items and hydration state

* test(sidebar): cover proxy visibility and expansion state

* chore: restart pull request checks

* refactor(sidebar): extract group item visibility control

* refactor(sidebar): move group visibility control to module scope

* fix(sidebar): preserve collapsed state on initial load

* fix(proxy): collect free pool UI regression test

* chore(test): unfreeze free-pool-tab.test.tsx from test-discovery baseline

The move to tests/unit/ui/free-pool-tab.test.tsx (this branch) relinks
it to the test:vitest:ui runner, so the tests/unit/free-pool-tab.test.tsx
entry in the frozen orphan baseline is now stale and fails
check:test-discovery. Removes the stale entry (60 -> 59 known orphans).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:13:19 -03:00
Jan Leon
d470526031 Honor provider proxies for The Old LLM Vercel blocks (#7380)
* fix(theoldllm): honor provider proxy for Vercel blocks

* fix(theoldllm): fail closed when assigned proxy is unavailable

* refactor(theoldllm): extract proxy guards into dedicated module
2026-07-18 15:13:13 -03:00
Jan Leon
78d2eee914 Add per-connection Provider Quota visibility (#7360)
* feat(dashboard): add Provider Quota visibility toggle per connection

* refactor(dashboard): extract provider quota visibility controls

Move quota visibility UI and update logic into reusable components, add Portuguese translations, and remove the stale migration gap allowlist entry.

* Hide quota visibility controls for unsupported providers

* chore(ci): retrigger GitHub checks

* fix(db): renumber quota-visibility migration past release tip (121→125)

122_free_proxy_sync_errors.sql, 123_quota_auto_ping.sql, and
124_generic_session_affinity_ttl.sql have since landed on
release/v3.8.49, so 121 is now out-of-sequence and would not apply on
databases already past 122+. Renumbers to 125 (the next free slot past
the current release tip) and restores "121" in check-migration-numbering's
KNOWN_GAPS allowlist, since 121 remains a genuine unfilled gap once this
migration moves off that number.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* chore(quality): rebaseline file-size + complexity for resync merge

The release-resync merge unions two already-compliant features in the
same god-component (ConnectionRow.tsx/ConnectionsListPanel.tsx): this
PR's per-connection quota-visibility wiring and release's confirm-
delete-account wiring (#7361). Both were individually within budget
(785/786 lines); combined they land at 791. Complexity count moves
2058->2059 for the same reason (2 previously-compliant .map() render
callbacks in ConnectionsListPanel.tsx now marginally exceed the
80-line function cap). No new logic was written — see the
_rebaseline_2026_07_18_pr7360_quota_visibility_resync justification
entries in both baseline files for the full accounting. Verified via
a byte-for-byte diff of the violation lists between origin/release/
v3.8.49 tip and this merge. Structural shrink stays tracked in #3501.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(db): split _updateConnectionRow update assembly (complexity gate)

_updateConnectionRow grew past the 80-line max-lines-per-function ceiling
after this branch added quota_visible column handling. Extract the
`.run()` params assembly (field mapping/normalization, unchanged) into a
module-private `_buildUpdateConnectionRowParams` helper in the same file
so the SQL statement + call site stay in `_updateConnectionRow` while the
function itself drops back under the gate. No behavior change.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:52 -03:00
Jan Leon
5f04d5bcbd feat: add principal-scoped CCR MCP lifecycle (#7282)
* feat: add principal-scoped CCR MCP lifecycle

* refactor: extract CCR MCP schemas

* refactor: reduce CCR store complexity

* fix: preserve CCR retrieval feedback

* fix: keep CCR expiry/accounting scoped to accessed entries

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:47 -03:00
Jan Leon
c78f150ac3 feat(compression): support RTK TOML schema v1 filters (#7281)
* feat(compression): support RTK TOML filters

* chore(ci): sync RTK skill and dependency allowlist

* refactor(compression): reduce RTK import complexity

* fix(i18n): add Portuguese RTK import translations

* fix(compression): improve RTK TOML import validation feedback
2026-07-18 15:12:43 -03:00
Jan Leon
dc0dec46c7 Add cache-aligned Live Zone compression (#7280)
* feat(compression): add cache-aligned live-zone processing

* refactor(compression): satisfy complexity ratchets

* fix(compression): handle tool_result outputs in live zone

* fix(i18n): add live-zone pt-BR strings
2026-07-18 15:12:38 -03:00
Jan Leon
9088151043 Fix Codex Responses compression analytics (#7273)
* fix compression analytics for Codex responses

* preserve Responses tool output fields

* Test compression analytics cost failure isolation
2026-07-18 15:12:33 -03:00
Jan Leon
c1a3d83c27 fix(combo): reject known context overflow without exhausting providers (#7177)
* Fix context-window exhaustion classification

* fix(combo): keep chat.ts/comboStructure.ts under the file-size ratchet + fix context-overflow boundary bug

- Extract getKnownContextOverflow (+ its KnownContextOverflow type) out of
  comboStructure.ts into a new open-sse/services/combo/knownContextOverflow.ts
  leaf, so the file-size ratchet (cap 800 for new files) passes.
- Extract the skipConnectionDisable predicate out of handleSingleModelChat in
  chat.ts into open-sse/services/combo/comboPredicates.ts::shouldSkipConnDisable,
  and consolidate the new combo-failure-handling imports, to keep chat.ts under
  its frozen file-size baseline (1796) after the #7177 request-scoped-failure
  wiring.
- Fix a real boundary bug in getKnownContextOverflow surfaced by the merge:
  estimateRequestInputTokens counted a caller-omitted `messages: []` (which some
  combo entrypoints default in) as real content, charging a few phantom
  "structural" JSON.stringify tokens toward the estimate. That was enough to
  falsely trip the new known-context-overflow rejection for a request with no
  real input when max_tokens exactly equals the target's context window (a
  common config where limit_input === limit_output === limit_context),
  regressing tests/unit/combo-routing-engine.test.ts's pre-existing #3587
  "non-reasoning model does not get max_tokens buffer" case. Empty
  arrays/objects no longer count as estimable content.
- Add a regression test for the exact-boundary empty-content case.

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

* refactor(combo): move overflow logic into knownContextOverflow module (file-size cap)

comboStructure.ts is not frozen in the file-size baseline but is capped at 800
lines; this PR's net +29 on that file alone would push it over once merged.

knownContextOverflow.ts already exists in this PR as the dedicated home for
"known context limit" logic, so move the genuinely new pieces there instead
of leaving them in comboStructure.ts:

- hasEstimableContent (new): its own doc comment already frames it purely in
  terms of the known-context-overflow boundary check, so it belongs next to
  that check, not in the general request-compatibility file.
- getKnownContextLimit (new, requestedOutputTokens-aware): this *is* the
  "how big is a target's known context window" primitive knownContextOverflow
  already consumes; hosting it there is a better fit than comboStructure.ts.
- getLegacyKnownContextLimit: kept alongside its sibling rather than split
  across two files, since both are alternate implementations of the same
  concept (used only by comboStructure.ts's hasKnownCompatibleContextLimit).

comboStructure.ts now imports all three back for its own internal callers
(estimateRequestInputTokens, getTargetCompatibilityFailures,
hasKnownCompatibleContextLimit). deriveRequestCompatibilityRequirements and
the RequestCompatibilityRequirements type stay in comboStructure.ts exactly
as this PR already has them (still consumed internally there), so
knownContextOverflow.ts keeps importing those two, same as before.

No behavior change — pure relocation, verified by the existing PR test
suite (combo-context-window-filter, combo-breaker-429, combo-failure-log-message,
combo-target-exhaustion, diagnostics) plus the pre-existing
combo-vision-aware-routing/combo-context-requirements/combo-roundrobin-compat-fallback-6238
suites, all green.

Net effect on open-sse/services/combo/comboStructure.ts vs. this PR's merge
base: -2 lines (was +29). typecheck:core, lint, and the complexity ratchets
(check:complexity, check:cognitive-complexity) are unchanged from this PR's
current HEAD — the 4 pre-existing complexity/max-lines findings in
valueContainsImagePart/filterTargetsByRequestCompatibility are untouched by
this move (same violations, same total ratchet counts, just shifted line
numbers).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:24 -03:00
Jan Leon
d760169d9a feat(usage): add Codex reset credit picker (#7154) 2026-07-18 15:12:19 -03:00
Jan Leon
95e537307b fix(models): preserve direct-model combo metadata (#6993) 2026-07-18 15:12:15 -03:00
Ronaldo Davi
0730eee9f2 fix(api): await params in Agent Bridge DNS route (Next.js 16) (#7271) (#7492) 2026-07-18 11:35:26 -03:00
danscMax
858d762918 fix(oauth): repair qwen + codebuddy-cn device-code endpoints (#7517)
Both device-code providers failed at the upstream request (surfacing in the
companion extension as 'ошибка сервера OmniRoute'). Diagnosed by calling the
upstream endpoints directly:

- qwen: QWEN_CONFIG used the bare host qwen.ai, whose /api/v1/oauth2/device/code
  and /token paths return 404 Not Found. The working qwen-code device flow lives
  at chat.qwen.ai (verified: 200 + a valid device_code/user_code). Point both
  URLs at chat.qwen.ai.
- codebuddy-cn: the Tencent state endpoint reads 'platform' from the QUERY
  string, not the JSON body. Sending it only in the body returned
  400 {"code":10001,"msg":"platform is empty"}. Passing ?platform=CLI
  returned 200 with {code:0, data:{state, authUrl}}. Build the stateUrl with the
  platform query param (body kept as-is).

Validation: live upstream calls returned 200 for both corrected requests (device
flow can't be hit from CI). Regression guard: tests/unit/oauth-device-code-endpoints.test.ts.
2026-07-18 11:35:09 -03:00
danscMax
7ff2e5c0b5 fix(oauth): surface sanitized device-code error instead of a generic 500 (#7511)
The dynamic OAuth GET handler swallowed every thrown error into a generic
`{ error: "Internal server error" }` 500, so a device-code upstream failure
(qwen → qwen.ai, codebuddy-cn → copilot.tencent.com — geo-block / outage / bad
client) surfaced in the extension as an indistinguishable 'ошибка сервера
OmniRoute', hiding WHY it failed.

Route the caught error through sanitizeErrorMessage() (already imported, hard
rule #12) so the real reason ("Device code request failed: …", "CodeBuddy
state request failed (403)") reaches the client, falling back to the generic
only when the sanitizer yields nothing.

Regression guard: tests/unit/oauth-device-code-error-transparency.test.ts
(source-level — the route needs the full Next request/auth/upstream graph, so
behavioural validation belongs on a real build/VPS).
2026-07-18 11:35:05 -03:00
danscMax
d7726ef80a fix(db): stop a 'latest' path segment from disabling backups and migrations (#7359)
Eleven subsystems answered "am I running under a test runner?" with
`process.argv.some((arg) => arg.includes("test"))`. JavaScript agrees that
'latest'.includes('test') is true, so ANY argv carrying a `latest` segment — a release symlink
like /opt/omniroute/latest/server.js, an npm/npx cache path, a `--model=latest` flag — silently
put the process into test mode.

The worst consequence is src/lib/db/backup.ts: isSqliteAutoBackupDisabled() returns true, so
SQLite auto-backup simply never runs — no warning, no log. The same substring decides whether
migrationRunner runs its pending-migration check, whether cloud sync initialises, and whether
the local/token health checks, quota recovery, model-lockout settings and the WS live server
consider themselves live. All of them fail silent, which is the dangerous kind.

Replaces the eleven copies with one helper, src/shared/utils/testProcess.ts:
- env first (NODE_ENV=test, VITEST) — unchanged;
- argv: `test`/`tests` only as a WHOLE token delimited by a path separator, dot or dash, so
  `--test`, `tests/unit/x.test.ts` and `src/x.test.ts` still match, while `latest`, `protest`,
  `contest` and `attestation` no longer do;
- argv: runner binaries (vitest/jest/mocha/ava/tap), which have no delimiter before "test";
- execArgv as well as argv — `node --test x.js` puts `--test` in execArgv, and
  modelLockoutSettings was the only copy that remembered to look there.

argv/env are parameters rather than globals so the negative cases are testable: under a test
runner the globals always say "test", which is precisely why this bug could never be caught.

tests/unit/test-process-detection.test.ts guards the regression (a `latest` path is not a test
run) alongside the positives that must keep working.
2026-07-18 11:35:00 -03:00
Adam
b71790bb7e fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078) (#7166)
* fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078)

* test: regression for #7078 m365.cloud.microsoft token extraction

* fix(7078): match on url.hostname and anchor path with startsWith

* test(7078): cover explicit :443 port via url.hostname
2026-07-18 11:34:56 -03:00
Adam
178496fd92 fix(providers): AgentRouter model import applies Claude Code wire image to /v1/models (#7016) (#7060)
* fix(providers): AgentRouter model import applies Claude Code wire image to /v1/models (#7016)

* test: regression guard for AgentRouter /v1/models discovery (#7016)

* fix(7016): case-insensitive Authorization strip + bare-array parseResponse

* test(7016): assert no Authorization variant + bare-array parse
2026-07-18 11:34:47 -03:00
Paijo
f287f42f15 perf: wrap ComboCard, HeroSection in React.memo (#7070)
* perf: wrap ComboCard, HeroSection in React.memo

* fix(#7070): add test coverage for React.memo changes; fix selfref test in fork CI

- Add smoke tests for combos page and EvalsTab to satisfy PR Test Policy
  requiring tests for production code changes
- Fix selfref test (check-test-masking-selfref-6634) to try upstream/main
  first, falling back to origin/main, since origin/main may not exist in
  fork CI environments

* fix(#7070): bump frozen baseline for combos/page.tsx 4655->4656 after React.memo wrapping

The file-size checker's split('\n').length convention now counts 4656
for src/app/(dashboard)/dashboard/combos/page.tsx after wrapping
ComboCard in React.memo (+1 effective line).

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-07-18 11:34:06 -03:00
Paijo
a0cff84339 perf(db): add temp_store=MEMORY pragma to SQLite init (#6921)
* perf(db): add temp_store=MEMORY pragma to SQLite init

Store temp tables/indices in memory instead of disk for faster
query execution (GROUP BY, ORDER BY, subquery materialization).
The two other optimized PRAGMAs (synchronous=NORMAL, cache_size=-16384)
were already set.

* test(db): add temp_store MEMORY pragma test

Verifies PRAGMA temp_store = 2 (MEMORY) after initDb() runs.

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-07-18 11:33:49 -03:00
Paijo
e79de5c294 perf(startup): warm model catalog cache at module init (#6920)
* perf(startup): warm model catalog cache at module init

Fire-and-forget call to getUnifiedModelsResponse after DB ready so first
GET /v1/models request doesn't pay cold-build cost (15-30s). Non-fatal —
if warmup fails the next request builds fresh.

* test: add warm catalog cache source-pattern test

Verifies registerNodejs() includes the model catalog warmup import
and call to getUnifiedModelsResponse.

* fix(perf): warm the durable OpenRouter catalog cache, not just the 1.5s TTL Response cache

The warmup called getUnifiedModelsResponse() with no Authorization header,
so it only ever populated the top-level per-key Response cache
(catalogCache in catalog.ts) at key "|0|" — a real client sending an
apiKey gets a different key and misses that cache entry. But that cache
also has only a 1.5s TTL (CATALOG_CACHE_TTL_MS, a #6408 burst-dedup
window for concurrent requests, not a startup-warm cache), so even a
perfectly key-matched entry would almost always have expired before real
traffic arrives regardless.

The one genuinely durable, apiKey-independent cost in the catalog build
is getOpenRouterCatalog()'s 24h disk-cached network fetch
(src/lib/catalog/openrouterCatalog.ts) — buildUnifiedModelsResponseCore()
calls it unconditionally whenever an OpenRouter connection is configured,
fully decoupled from the per-key Response cache. Extract the warmup into
an exported warmModelCatalogCache() (testable in isolation, without
exercising all of registerNodejs()) that explicitly warms this cache too,
guarded on an OpenRouter connection actually existing so deployments that
never use OpenRouter don't pay an unconditional third-party network call
at every boot.

Replace the source-text-grep test with a behavioral one: warm once with a
mocked fetch, confirm exactly one network call, then confirm a real
request using a DIFFERENT apiKey than the warmup reuses the cache instead
of re-fetching — the actual, durable, apiKey-independent benefit. Also
covers the no-connection-configured and fetch-failure-is-non-fatal cases.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:44 -03:00
Paijo
6e71553797 perf(db): project columns + composite index in getProviderConnections (#6918)
* perf(db): project columns + composite index in getProviderConnections

- Add  param to avoid  (scans ~200MB/query)
- Add  WHERE clause support (was silently ignored)
- Add composite index  on
  (auth_type, is_active, refresh_token)
- Update health check caller to request only needed columns
- Test: authType filter, column projection, default full fetch

* fix(db): dedupe authType filter, allowlist columns projection in getProviderConnections

The branch was rebased on top of #6946 (already merged, same author,
same authType-filter fix), leaving a duplicate `if (filter.authType)`
block in getProviderConnections. Harmless at runtime (SQLite tolerates
the repeated named param) but dead code — remove the newer duplicate,
keep the one already merged via #6946.

The `columns` projection param is interpolated directly into the SQL
SELECT clause via `.join(", ")` with no validation. No current caller
passes untrusted input, but it's a live SQL-injection footgun for
whichever future caller wires it up: reproduced a working exfiltration
via a single-statement subquery column name (no semicolon/stacked-query
needed, so better-sqlite3's single-statement restriction doesn't help)
that leaked an unrelated connection's api_key through the response.
Add an allowlist validated against the real provider_connections schema
(core.ts's SCHEMA_SQL) — rejects any non-listed column, and re-quotes
the reserved "group" keyword so it stays usable. Verified the fix
blocks the exact reproduced exfiltration.

Add a regression test asserting invalid/injection-shaped column names
are rejected, a mixed valid+invalid list still rejects (fail-closed,
not a silent partial projection), and the legitimate "group" column
still round-trips correctly when requested.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:32 -03:00
Paijo
a2ebc343d4 fix: add re-entrancy guard to token health check sweep (#6917)
* fix: add re-entrancy guard to token health check sweep

Adds an in-flight guard to prevent overlapping sweep() executions.
Uses global state sweeping flag that is set before the first await
and cleared in a finally block. Subsequent calls while sweeping
return early with a debug log line.

Test coverage:
- skips when a previous sweep is still in flight
- resets sweeping flag after normal completion
- resets sweeping flag on empty connections

* fix(test): move sweep re-entrancy test to node:test, wire CI correctly

tests/unit/token-health-check-sweep.test.ts used vitest syntax while
living directly under tests/unit/, which is exactly the glob
`npm run test:unit` (node's native runner) scans — running it there
threw "Vitest mocker was not initialized" and failed the file outright.

Separately, the vitest.config.ts include-array edit didn't wire the
test into any CI-blocking script either: `npm run test:vitest` runs
vitest.mcp.config.ts (a different config, not this path), and
test:vitest:ui is scoped to tests/unit/ui only — so the 3 tests never
ran in CI at all while node's runner actively failed on the file.

Rewrite the test to node:test, matching the
tests/unit/apikey-connection-health-check.test.ts /
tests/unit/token-health-check.test.ts convention (real temp-dir SQLite
DB rather than vi.mock, since mock.module() is unavailable in this
tsx/ESM + Node native test-runner setup). The re-entrancy scenario now
drives the real, unmocked sweep() with real OAuth connections
(healthCheckInterval: 0 keeps checkConnection() a fast no-op) and
asserts on wall-clock elapsed time + the shared sweeping flag instead
of a mocked call count. Verified this fails without the guard (909ms,
~3x the stagger) and passes with it restored.

Remove the now-unused vitest.config.ts include entry since the test no
longer needs it.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(health): compact sweep guard + restore one-line stagger delay (file-size cap)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 11:33:28 -03:00
Diego Rodrigues de Sa e Souza
cab9e5f0c0 fix(dashboard): cut UI import chain from connection persist module (CI shard base-red) (#7677)
Base-red unblock (CI Unit shard 2/4 red on EVERY PR since #7653). Validated locally: test 6/6 under the exact shard harness; persist module proven to load without the UI chain; full static-gate set green (complexity 2056≤2058, cognitive 889≤890, file-size/test-discovery/dashboard-typecheck/changelog OK).
2026-07-18 07:15:56 -03:00
Diego Rodrigues de Sa e Souza
d9f3699b4e feat(sse): quota tracking for AgentRouter, v0 (Vercel), FreeModel (#6850, #6845, #7075) (#7653)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:17:09 -03:00
Diego Rodrigues de Sa e Souza
60955975e4 feat(usage): add TTFT/E2E-latency/tokens-per-second to model latency stats (#6875) (#7635)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:17:04 -03:00
Diego Rodrigues de Sa e Souza
38dd62819b feat(providers): Speechmatics STT, gTTS, VibeProxy preset (#6659, #6667, #6874) (#7655)
Validated in merge-train --fast @ 6cafcbb (static gates + 9 changed test files + vitest green, 2m35s; full suite ran today on train 2c tip)
2026-07-18 03:16:58 -03:00
Diego Rodrigues de Sa e Souza
9e084e18a7 feat: OpenRouter quota tracking (key/credits + free-window counter) (#6842) (#7651)
Validated in merge-train --fast @ 4ed4498 (static gates + changed tests 29/29 + vitest green, 2m39s; full suite ran today on trains 1/2c)
2026-07-18 03:11:01 -03:00
Diego Rodrigues de Sa e Souza
b28331307e feat: per-model default reasoning_effort + no-think none on OpenAI path (#6879) (#7631)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:37 -03:00
Diego Rodrigues de Sa e Souza
2cca081b3c feat: import providers from CSV/JSON file (#6836) (#7636)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:32 -03:00
Diego Rodrigues de Sa e Souza
94509c0b5f feat: confirm before removing a single connection (#7361) (#7640)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:25 -03:00
Diego Rodrigues de Sa e Souza
13e57b2b35 feat: rate-limit queue admission control (maxQueueDepth + 15s default) (#6593) (#7649)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:20 -03:00
Diego Rodrigues de Sa e Souza
735e2d0783 feat(sse): generalize session affinity TTL to all providers (#7274) (#7650)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:44 -03:00
Diego Rodrigues de Sa e Souza
8bf2e6929f feat(providers): add g4f.space no-key gateway (groq/gemini/pollinations/ollama/nvidia) (#6650) (#7647)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:37 -03:00
Diego Rodrigues de Sa e Souza
f5d705c277 feat(dashboard): in-product guidance for prompt compression engines (#7530) (#7634)
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
2026-07-17 22:09:21 -03:00
Diego Rodrigues de Sa e Souza
fb612867fd feat: add Segmind image+video provider (#6656) (#7608)
* feat(providers): add Segmind image+video provider (#6656)

Segmind exposes 200+ hosted image/video models under a single
`POST https://api.segmind.com/v1/{model}` REST shape: x-api-key auth,
JSON request body, raw media bytes response (no JSON envelope).

- New IMAGE_PROVIDERS + VIDEO_PROVIDERS registry entries (format:
  "segmind") with a curated starter model list (Flux, SDXL, SD3.5,
  Kandinsky for image; Wan, Hunyuan, LTX, Kling for video).
- New connection-metadata entry in specialty-media.ts; segmind added
  to IMAGE_ONLY_PROVIDER_IDS and VIDEO_PROVIDER_IDS.
- Dedicated handlers (imageGeneration/providers/segmind.ts,
  videoGeneration/providers/segmind.ts) built on a shared REST client
  (utils/segmindClient.ts) that centralizes the fetch/error/log path
  so both stay under the complexity/max-lines ratchets.
- Extracted the pre-existing Alibaba DashScope video handler out of
  the frozen videoGeneration.ts into videoGeneration/providers/
  dashscope.ts (no behavior change) to make room for the new Segmind
  dispatch branch under the frozen file-size baseline.
- Error responses route through sanitizeErrorMessage() (Hard Rule
  #12) — verified by dedicated no-leak tests.
- Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251
  providers) and synced the plain-text provider counts in README.md/
  AGENTS.md/CLAUDE.md (anchors/badges left untouched).

Tests: tests/unit/segmind-image-video-provider-6656.test.ts (11
cases — registry shape, connection metadata, IMAGE_ONLY/VIDEO_
PROVIDER_IDS membership, mocked-fetch request mapping for both
image and video, and sanitized-error-path assertions for both
upstream error bodies and network exceptions). No live Segmind key
required; response shape (raw media bytes, x-api-key auth) is
sourced from https://docs.segmind.com/ and corroborated against
https://www.segmind.com/models/flux-schnell/api,
https://www.segmind.com/models/sdxl1.0-txt2img/api, and
https://www.segmind.com/models/wan2.1-t2v/api.

Gates run clean: check-file-size, check:complexity-ratchets
(2055/889, both under baseline), typecheck:core,
typecheck:noimplicit:core (no new errors), lint (targeted files),
check:cycles, check:docs-counts (STRICT provider-count drift
resolved), check:docs-sync, check:any-budget:t11,
check:tracked-artifacts, check:provider-consistency,
check:known-symbols.

* test(providers): align APIKEY_PROVIDERS count 167→168 for the new segmind provider (#6656)

Adding segmind to specialty-media.ts grows APIKEY_PROVIDERS by one;
providers-constants-split.test.ts hardcodes the family-partition total.
Legitimate count alignment, not a weakened assertion — all 4 partition/
dedup checks still enforced.
2026-07-17 12:13:47 -03:00
Diego Rodrigues de Sa e Souza
5bacb719d3 feat: add Microsoft Designer as image provider (#6672) (#7609)
* feat(sse): add Microsoft Designer as image provider (#6672)

Adds `microsoft-designer-web` — an unofficial, reverse-engineered
Bearer-token web-session image provider, modeled on the existing
`chatgpt-web`/`copilot-m365-web` "-web" provider category.

- Registers the provider in WEB_COOKIE_PROVIDERS (src/shared/constants/
  providers/web-cookie.ts) and IMAGE_PROVIDERS (open-sse/config/
  imageRegistry.ts, new "designer-web" format).
- New handler open-sse/handlers/imageGeneration/providers/designerWeb.ts
  implements the submit-then-poll DallE.ashx flow (Bearer access_token +
  ClientId/SessionId/UserId headers -> form POST -> poll for
  image_urls_thumbnail), wired into handleImageGeneration()'s dispatch.
- The upstream ClientId header is a fixed, publicly-shared value (not a
  secret) — routed through resolvePublicCred() per Hard Rule #11, never
  as a string literal.
- Registers the token-based credential requirement in
  webSessionCredentials.ts so the provider-connect UI asks for the
  right field; connection validation falls back to the existing generic
  web-cookie session-ping validator (no dedicated validator needed).
- Extracted the KIE image-model catalog into a co-located
  open-sse/config/providers/registry/kie/models.ts module (mirrors the
  existing lmarena/directModels.ts pattern) to keep imageRegistry.ts
  under the file-size cap while adding the new provider entry.
- Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251
  providers) and updated the plain-text counts in README.md, AGENTS.md,
  CLAUDE.md.

Tests (tests/unit/microsoft-designer-web-6672.test.ts, 16 cases):
registry-entry shape assertions, the resolvePublicCred() shape
assertion (Hard Rule #11), and the pure header/form-body/response-
parsing helpers plus the handler's submit/poll/error/timeout paths
against a mocked fetch — no live Designer session required.

Reverse-engineered from the g4f MicrosoftDesigner.py provider reference
(researched during #6672 triage); the exact upstream response shape has
not been validated against a live Designer session, so the poll-loop
implementation follows the documented g4f contract as closely as
possible without a live capture.

* fix(providers): satisfy web-cookie executor contract + document designer-web env vars (#6672)
2026-07-17 12:09:12 -03:00
Diego Rodrigues de Sa e Souza
93e217e763 feat(video): add Novita AI as video-generation provider (#6658) (#7606)
Adds Novita AI to the video-generation subsystem (VIDEO_PROVIDERS), alongside
its existing text/chat gateway registration. Novita's async video APIs are
per-model (POST /v3/async/<model-slug>, e.g. wan-t2v, kling-v1.6-t2v) sharing
one poll endpoint (GET /v3/async/task-result?task_id=...) — confirmed against
Novita's published API reference. Seeds Wan 2.1 T2V and Kling V1.6 T2V models;
reuses the stored novita provider Bearer apiKey (no separate credential flow).

To stay under the frozen videoGeneration.ts file-size cap, extracted the
existing Alibaba/DashScope handler into a co-located sibling module
(videoGeneration/dashscopeHandler.ts) alongside the new Novita handler
(videoGeneration/novitaHandler.ts) and its pure helpers (videoGeneration/novita.ts).

Also tags novita in VIDEO_PROVIDER_IDS (src/shared/constants/providers.ts) so
it surfaces as a video-capable provider in PROVIDER_REFERENCE.md and A2A
provider-discovery, and regenerates the provider reference doc.

Tests: tests/unit/video-novita-6658.test.ts (18 cases) covering registry
shape, pure helpers (URL building, param normalization, task-id/result
parsing), and full handler wiring (submit->poll->mp4, missing credentials,
missing task_id, task FAILED, task timeout).
2026-07-17 11:54:56 -03:00
Diego Rodrigues de Sa e Souza
76c3b3b8d3 feat: add Freepik (Magnific Mystic) image generation provider (#6654) (#7597)
* feat(providers): add Freepik (Magnific Mystic) image generation provider (#6654)

Adds an official, API-key-based Freepik image-gen provider using the Mystic
endpoint (POST /v1/ai/mystic -> async task_id -> GET /v1/ai/mystic/{id}
polling), modeled on the existing leonardo.ts generationId adapter pattern.

- open-sse/config/providers/registry/freepik/index.ts: new registry module
  (kept separate to avoid pushing the frozen imageRegistry.ts over the
  file-size cap) with the 6 real Mystic style models (realism, fluid, zen,
  flexible, super_real, editorial_portraits) — not the "Flux/Imagen3" list
  from the original feature request, which independent research showed was
  stale.
- open-sse/handlers/imageGeneration/providers/freepik.ts: submit+poll
  adapter; all error paths route through sanitizeErrorMessage() (Hard Rule
  #12), configurable poll interval/timeout via body.poll_interval_ms /
  poll_timeout_ms for fast, deterministic tests.
- Registered in providers.ts (IMAGE_ONLY_PROVIDER_IDS) and
  apikey/specialty-media.ts (catalog metadata), with the corrected free-tier
  note (one-time ~€5 credit, not a recurring "100/month" allotment).

Drops the "100 free credits/month" and "Flux/Imagen3 selectable models"
claims from the original issue - verification showed the free tier is a
one-time ~€5 API credit and Imagen 3 only underlies the `fluid` style, not a
separately selectable model. Domain: api.freepik.com is still live as of
this writing despite Freepik's April-2026 API-docs rebrand to Magnific
(docs.freepik.com -> docs.magnific.com); noted inline for future
re-verification.

Closes #6654

* test: align APIKEY_PROVIDERS count to 171 after freepik + release merge (#7597)
2026-07-17 11:49:50 -03:00
Diego Rodrigues de Sa e Souza
9b5415a414 feat: add Gladia as an async speech-to-text provider (#6657) (#7603)
* feat(providers): add Gladia as an async speech-to-text provider (#6657)

Adds Gladia's async pre-recorded transcription API (upload → POST
/v2/pre-recorded → poll result_url) following the existing
AssemblyAI/Kie.ai async-STT pattern:

- New `gladia` entry in AUDIO_TRANSCRIPTION_PROVIDERS
  (open-sse/config/audioRegistry.ts), authenticated via the
  `x-gladia-key` custom header.
- New `handleGladiaTranscription()` handler
  (open-sse/handlers/audioTranscription.ts) wired into the
  format dispatch table.
- New `x-gladia-key` case in `buildAuthHeaders()`
  (open-sse/config/registryUtils.ts).
- Registered `gladia` in AUDIO_ONLY_PROVIDERS
  (src/shared/constants/providers/audio.ts) so it appears in the
  auto-generated provider catalog; regenerated
  docs/reference/PROVIDER_REFERENCE.md (250 -> 251 providers) and
  synced the plain-text provider counts in README.md, AGENTS.md,
  and CLAUDE.md.

Real-time/streaming transcription is explicitly out of scope for
this change — OmniRoute has no WebSocket audio-ingestion layer
today; only the async/pre-recorded path (which covers every other
async STT provider already wired in) is implemented.

Tests: 5 new node:test cases in
tests/unit/audio-transcription-handler.test.ts covering the
upload→submit→poll happy path, a terminal Gladia error, and a
missing result_url guard, plus a buildAuthHeaders case in
tests/unit/registry-utils.test.ts for the new x-gladia-key header.

* chore(providers): sync provider counts to 253 + fix base-red APIKEY partition count 168→169 (#6657)

Rebasing gladia onto the advanced release surfaced two count drifts the
RUN_ALL suite trips on: (1) docs provider count is now 253 (multiple
providers merged since this branch was cut); (2) providers-constants-split
already expects 168 but the release has 169 APIKEY entries — a pre-existing
base-red from an earlier provider merge that didn't update the test. Gladia
is STT (adds no APIKEY entry), so 169 is the correct value; aligning it here
also un-reds the release. All 4 partition/dedup checks still enforced.
2026-07-17 11:45:00 -03:00
Diego Rodrigues de Sa e Souza
7eb0204901 feat: add FreeTheAi as OpenAI-compatible gateway provider (#6670) (#7602)
* feat(providers): add FreeTheAi as an OpenAI-compatible gateway provider (#6670)

FreeTheAi is a free-tier, Discord-signup gateway aggregator — same shape
as hackclub/chutes: OpenAI-compatible chat/completions + /v1/models
discovery, no custom executor/translator needed.

- Registry entry: open-sse/config/providers/registry/freetheai/index.ts
  (format: openai, executor: default, apikey/bearer auth, passthroughModels)
- Provider metadata: src/shared/constants/providers/apikey/gateways.ts
- Listed in AGGREGATOR_PROVIDER_IDS (src/shared/constants/providers.ts)
- Unit test verifying registry entry, getExecutor() resolution, aggregator
  classification, and provider metadata (tests/unit/provider-registry-freetheai.test.ts)

* chore(providers): sync counts (APIKEY 170, providers 253) after rebase onto advanced release (#6670)

The release advanced heavily since this branch was cut; realign the
family-partition count to the true post-rebase value (170) and the doc
provider totals to 253. freetheai adds exactly one gateway; the rest of
the delta is pre-existing release drift. All partition/dedup checks enforced.
2026-07-17 11:36:58 -03:00
Diego Rodrigues de Sa e Souza
6695cbbf7a feat: add EdgeTTS audio-tts provider (#6668) (#7605)
* feat(sse): add EdgeTTS audio-tts provider (#6668)

Registers Microsoft Edge "Read Aloud" as a new no-API-key AUDIO_SPEECH_PROVIDERS
entry — the first WebSocket-transport TTS provider in the registry. Reverse-
engineered/unofficial endpoint, same class of integration already accepted for
other "-web" style providers (chatgpt-web.ts, copilot-web.ts).

- open-sse/executors/edgeTts.ts: pure Sec-MS-GEC token construction (SHA-256
  over a public trusted-client-token + rounded Windows file-time ticks, ported
  from rany2/edge-tts drm.py), WS message framing (speech.config/ssml),
  binary-chunk demuxing, SSML building/escaping, and the WS synth call itself
  (injectable WebSocket ctor for tests, lazy `import("ws")` in production so
  it never enters esbuild's top-level CJS bundle graph). Per-client-IP
  sliding-window throttle (SlidingWindowLimiter) since there's no per-user key
  — one abusive deployment could otherwise get the shared trusted token
  rate-limited for everyone.
- open-sse/utils/publicCreds.ts: embeds the trusted-client-token via
  resolvePublicCred() (Hard Rule #11) — it's a constant hardcoded in every
  Edge build and every open-source edge-tts port, not a per-user secret.
- Extracted open-sse/utils/audioResponse.ts (shared response helpers) and
  open-sse/executors/awsPollyTts.ts (AWS Polly handler) out of
  open-sse/handlers/audioSpeech.ts to stay under its frozen file-size ratchet
  baseline while making room for the new branch — no behavior change to
  either extracted piece.
- src/app/api/v1/audio/speech/route.ts: thread the caller's IP through to the
  handler for the new throttle.

Tests: tests/unit/edgetts-provider.test.ts (23 cases) — Sec-MS-GEC determinism
and cross-check against a hand-derived reference vector, message framing,
binary demux, SSML escaping/injection-safety, registry lookup, publicCreds
shape, and the error path via an injected fake WebSocket (upstream failure ->
sanitized 502, no stack/path leak; Hard Rule #12), plus the per-IP rate limit.
No live upstream is required or used — the reverse-engineered protocol can't
be validated against real credentials, but every pure/testable seam is
covered per the TDD path in the bug/feature validation gate.

* test(mutation): register edgetts-provider.test.ts in stryker tap.testFiles (#6668)

The new provider's unit test covers a mutated module, so the strict
mutation-test-coverage gate requires it in stryker.conf.json's
tap.testFiles. Single-line addition (kept the file's existing formatting).
2026-07-17 11:32:25 -03:00
Diego Rodrigues de Sa e Souza
df1ed57876 feat(sse): add Notion AI Web (Unofficial/Experimental) provider (#6758) (#7600)
Notion AI has no public inference API (see closed request #3272), so this
adds it as a new entry in the established web-cookie provider category
(chatgpt-web, claude-web, grok-web, ...): cookie-based auth via the
token_v2 session cookie posted to Notion's undocumented internal
POST /api/v3/runInferenceTranscript endpoint, translating its NDJSON
transcript-patch stream into OpenAI-compatible chat completions.

- NotionWebExecutor (open-sse/executors/notion-web.ts): resolves the
  token_v2 cookie (+ optional space_id/notion_browser_id), builds a
  Notion transcript from the chat messages, parses the NDJSON response
  (cumulative-snapshot semantics, mirroring gemini-web.ts's handling of
  #7163), and returns a chat.completion or pseudo-streamed SSE response.
  All error paths route through makeExecutorErrorResult (sanitized).
- RegistryEntry under open-sse/config/providers/registry/notion-web/,
  registered in providers/index.ts REGISTRY and executors/index.ts
  (alias "nw").
- WEB_COOKIE_PROVIDERS entry (src/shared/constants/providers/web-cookie.ts)
  with subscriptionRisk + webCookie risk notice, clearly labeled
  "(Unofficial/Experimental)".
- Cookie-probe validator (validateNotionWebProvider) against Notion's
  getSpaces endpoint, and a webSessionCredentials.ts UI entry for the
  "Add session cookie" flow.
- Regenerated docs/reference/PROVIDER_REFERENCE.md and the
  provider/translate-path golden snapshot (purely additive diffs); synced
  the "251 providers" count across README/AGENTS/CLAUDE.md
  (check:docs-counts STRICT gate).

Tests: tests/unit/executor-notion-web.test.ts (22 cases — registry
consistency, mocked-upstream request/response translation, NDJSON
snapshot parsing, cookie resolution, sanitized error paths) plus the
existing executor-web-cookie-sweep, provider-alias-uniqueness,
check-provider-consistency, web-session-credentials, and
provider-translate-path-golden suites all pass with notion-web included.
2026-07-17 11:32:09 -03:00
Diego Rodrigues de Sa e Souza
7b564ab5db feat(providers): add Felo chat-aggregator provider (#6666) (#7599)
Adds felo-web, a free no-signup no-API-key chat/search-agent aggregator
(felo.ai), following the same architectural pattern as the existing
duckduckgo-web/blackbox-web "-web" scrape family:

- POST /api-proxy/main/search/threads opens a search thread and returns a
  stream_key.
- GET /api/message/v1/stream/{stream_key} streams Felo's bespoke
  data:{...}-line SSE, translated into OpenAI-compatible chunks.
- 5 models (felo-chat/search/scholar/social/document) map to Felo's
  chat/google/scholar/social/document search categories.

Registered in providers.ts (noauth.ts, no-auth like duckduckgo-web),
providerRegistry.ts, and executors/index.ts. Free-tier catalog entries
added with tos: "avoid" (reverse-engineered endpoint, no published API —
same ToS posture as the other -web scrape providers).

No live network access was available in this environment to smoke-test
against the real felo.ai endpoint, so validation is TDD via mocked fetch
(tests/unit/felo-web-executor.test.ts): thread-creation payload shape,
SSE parsing (answer-snapshot diffing + final_contexts drop), streaming
and non-streaming response translation, and error/timeout paths that
route through sanitizeErrorMessage() per the error-sanitization rule.
2026-07-17 11:31:54 -03:00
Diego Rodrigues de Sa e Souza
a6d19cc4ba feat(providers): add Rev AI speech-to-text provider (#6655) (#7596)
Registers Rev AI as a 13th async-job STT provider, mirroring the
AssemblyAI/Kie.ai upload -> submit -> poll pattern already used by the
audio transcription handler:

- audioRegistry.ts: new "rev-ai" entry (bearer auth, async: true,
  format: "rev-ai") with machine/low_cost/fusion transcriber models.
- audioTranscription.ts: handleRevAiTranscription() submits the job
  with the media file inline in the multipart body (field "media"),
  polls GET /jobs/{id} until "transcribed"/"failed", then fetches the
  plain-text transcript. buildMultipartBody() gained an optional
  fileFieldName param (default "file") so Rev AI's "media" field name
  doesn't require a bespoke multipart builder. Errors route through
  the existing upstreamErrorResponse()/errorResponse() helpers.
- providers/audio.ts: catalog entry (id/alias/name/icon/color/website)
  for the dashboard connection UI.
- validation/audioMiscProviders.ts + validation.ts: validateRevAiProvider
  wired into the provider "Test Connection" dispatcher.

Streaming (WebSocket) STT is scoped as a follow-up per the analyzed
plan — no existing precedent to extend, needs its own design pass.

Closes #6655.
2026-07-17 11:31:31 -03:00
Diego Rodrigues de Sa e Souza
873e3da62e feat(dashboard): add 180D and 365D usage/cost analytics periods (#7213) (#7213)
Rebuilt onto release/v3.8.49 feature-only: the branch's original file-size
decomposition of CostOverviewTab collided with the release's own component
extraction (#7272 TopListCard). Kept just the 180D/365D range delta —
CostRange/COST_RANGE_VALUES, the RANGE_OPTIONS selector, the getRangeStartIso
handlers in the analytics and requests-by-provider-date usage routes, and the
range180d/range365d i18n labels across all locales.

Inspired-by: 9router#2361
2026-07-17 11:03:13 -03:00