Commit Graph

5334 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
25ef8d05c0 test(combo): classify dispatches by host and close the auto test pool
providerFromUrl() classified the upstream by URL path shape. With hundreds of
providers in the catalog that is wrong in both directions: '/chat/completions'
matched every OpenAI-compatible upstream (opencode.ai/zen was being reported as
'openai'), while OpenAI's own GPT-5.6 dispatches go to '/v1/responses' and came
back 'unknown'. Classify by hostname instead, and return 'host:<hostname>' for
unrecognised hosts so an unexpected dispatch names itself in the failure message.

combo-matrix/auto.test.ts additionally assumed the auto pool contained only the
connections it seeded, but no-auth providers (#6557/#7622) legitimately join the
pool without a seeded connection. Block those in beforeEach so the pool is closed
to the seeded connections — preserving what the assertions are actually about
(LKGP pinning, variant pool resolution) rather than relaxing them to accept
whatever an open pool picks.

Verified: auto.test.ts 2/2, and all 9 combo-matrix files 27/27 with no
regressions (no test was passing on the old helper's false 'openai' label).
2026-07-19 16:10:12 -03:00
Diego Rodrigues de Sa e Souza
5924eb2d86 feat(providers): zai-web live model discovery with local-catalog fallback (#7678) (#7766) 2026-07-19 16:06:34 -03:00
Xiangzhe
f19a452a21 fix(kimi): expose K3 reasoning effort levels (#7776) 2026-07-19 16:06:31 -03:00
backryun
2ea12ef6b7 [Emergency Fix] fix(build): repair release build blockers (#7772)
* fix(build): repair release build blockers

* test(icons): cover Stepfun Mono fallback
2026-07-19 15:51:23 -03:00
Diego Rodrigues de Sa e Souza
4cd40a6919 docs(readme): audit every number against the live code + refresh contributors and acknowledgments (#7795)
- contributors: 280+ -> 360+ (real union of authors + co-authors across git history)
- top contributors: expand to 10 ordered by commits; fix the zenobit link, which
  pointed at an unrelated account instead of zen0bit; refresh commit/line counts
  sourced from merged-PR stats
- free tier: hero and free-tier-budget.svg claimed ~1.6B/mo and 40+ pools / 500+ models;
  computeFreeModelTotals() returns 1.37B steady, 2.00B first month, 39 pools, 462 models
- compression: 10-engine stack -> 11 (omniglyph was missing from the pipeline diagram)
- mcp: 30 -> 31 scopes; sync CLAUDE.md/AGENTS.md from 94 to the real 104 tools
- cli tools: 26 (20+6) -> 33 (25+8); add the 7 catalog entries missing from CLI-TOOLS.md
- acknowledgments: refresh 29 stale star counts, all verified via the GitHub API
2026-07-19 15:41:03 -03:00
Diego Rodrigues de Sa e Souza
f3277f267a feat(gemini-web): emulate OpenAI tool calling via the webTools prompt shim (#7286) (#7727)
Level 2 of the staged approach in #7286: wire the existing webTools.ts
prompt-emulation shim (already proven across 11 other web-cookie
executors) into gemini-web.ts. The client's tools[] array is now
serialized into the prompt typed into the Gemini web UI, and
<tool>{...}</tool> blocks in the response are parsed back into OpenAI
tool_calls -- including for streaming requests, replayed as a single
terminal SSE chunk since gemini-web buffers the whole response by
construction. Malformed tool JSON degrades to ordinary chat content,
never an error, matching the existing behavior of the other 11
executors. The no-tools code path is unchanged (regression guard).

Also Level 1: adds a "Tool calling" column (native/emulated/none) to
docs/reference/PROVIDER_REFERENCE.md for providers with confirmed
ground truth (the 11 already-wired web-cookie executors + gemini-web
-> emulated, claude-web -> none pending its own Level 3 decision).

Level 3 (claude-web) and Level 4 (supportsTools capability flag) are
explicitly out of scope -- claude-web/payload.ts is untouched.
2026-07-19 14:51:06 -03:00
Xiangzhe
a9028e9571 fix(stream): suppress </think> close marker for Responses API clients (#7747)
* fix(stream): suppress `</think>` close marker for Responses API clients

The Claude→OpenAI `</think>` close marker (#4633) exists for Chat
Completions clients that scan content for the marker (Claude Code /
Cursor). On the openai-responses path the responsesTransformer already
maps reasoning_content to structured reasoning items, so the marker has
no consumer and leaks verbatim into response.output_text.delta —
observed in production with kimi-coding (k3): thinking renders
correctly while a stray `</think>` sits at the start of the assistant
text (up to 6 consecutive markers when the upstream also emits stray
close-tag text deltas).

resolveSuppressThinkClose() gains a clientResponseFormat option that
always suppresses the marker for openai-responses, winning over both
the UA allowlist and an explicit keep header (no legitimate marker
consumer exists in the Responses format). chatCore passes the format
through, and ExecuteInput now carries clientResponseFormat so the two
executors that do their own Claude→OpenAI translation apply the same
policy: GLM's Anthropic transport and zed-hosted's Anthropic backend
(which previously applied no suppression at all, not even the #5245
UA/header policy).

Chat Completions behavior is unchanged (#4633 / #5123 / #5245 / #5312).

* refactor(executors): extract helpers to keep execute/executeTransport under the complexity cap

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: xz-dev <xz-dev@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-19 14:46:37 -03:00
Ry
ad5e67dc49 fix(quota): fix antigravity/agy multi-model quota skipping in combos (#7695)
* fix(quota): fix antigravity/agy multi-model quota skipping in combos

* fix(quota): preserve exact-model scoping for unknown models in 'other' family

* refactor(quota): extract helper to keep isQuotaExhaustedForRequest under the cognitive-complexity cap

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

---------

Co-authored-by: irvandikky <irvandikky@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-19 14:46:33 -03:00
Mustafa Mahdi
0842bade67 Completing Arabic language (#7686)
* Add files via upload

* update ar.json

* update ar.json

* update ar.json

* update ar.json

---------

Co-authored-by: mustafa-phd <mustafa-phd@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-19 14:46:30 -03:00
Daniel Dsouza
474e4f5db9 fix(icons): fall back to Stepfun Mono when Color component is absent … (#7743)
* fix(icons): fall back to Stepfun Mono when Color component is absent in @lobehub/icons v5.13

@lobehub/icons v5.13.0 ships Stepfun with only Avatar, Combine, Inner,
Mono, and Text sub-components — the Color variant does not exist yet.
Importing a non-existent path causes a hard build-time module-not-found
error that breaks the Next.js dashboard for all users on this version.

Changes:
- Remove the broken import StepfunColorIcon from
  '@lobehub/icons/es/Stepfun/components/Color'
- Replace with a comment explaining the fallback
- Point both mono and color slots in the icon map to StepfunMonoIcon

This is a purely cosmetic fallback; the Stepfun provider icon will render
in mono style instead of colour until the upstream package adds the Color
component. No API, routing, or DB changes.

* fix(ui): resolve Next.js hydration mismatch on sidebar collapsed state

Reading localStorage in the useState initializer for collapsed causes a hydration mismatch on SSR. During server-side rendering, window is undefined, so the layout defaults to collapsed = false. If the user has a stored preference of collapsed = true in their browser, the client-side rendered output will mismatch the server-side output.

Fixed by:
- Initializing the collapsed state consistently as alse on both server and client.
- Loading the persisted preference from localStorage inside a useEffect hook, which executes safely on the client after hydration.
- Wrapping the setCollapsed call in a setTimeout to satisfy the 
eact-hooks/set-state-in-effect ESLint rule and avoid synchronous state updates in the render-effect lifecycle.

* test(ui): add regression guards for Stepfun mono fallback and sidebar hydration fix

Locks in the two production fixes in this PR: the removed
@lobehub/icons Stepfun Color import must never come back, and
DashboardLayout's collapsed-sidebar state must stay a constant
useState initializer (localStorage read deferred to useEffect) to
avoid a server/client hydration mismatch.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
2026-07-19 14:37:56 -03:00
Floze
0c9578dc1d fix(db): update proxies on password rotation (#7707)
* fix(db): update proxies on password rotation

* docs(changelog): link proxy rotation fix

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-19 14:37:52 -03:00
Dongwook
dd773dcd05 test(codex): cover image tool output replay (#7698) (#7704)
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-19 14:37:48 -03:00
backryun
649e5d09e7 feat(perplexity): refresh provider integrations (#7687) 2026-07-19 14:37:44 -03:00
Diego Rodrigues de Sa e Souza
a95da4a902 feat(routing): wire interceptFetch tool interception into the chat pipeline (#7339) (#7736)
Phases 3-4 of #3384 (Phases 1-2 shipped DB schema + resolveInterceptSearch in
release/v3.8.47). Adds resolveInterceptFetch(provider, model) as a structural
twin of resolveInterceptSearch, and open-sse/services/webFetchInterception.ts
(mirroring webSearchFallback.ts) to rewrite a provider-native web_fetch tool
declaration into a synthetic omniroute_web_fetch function tool. The synthetic
tool call is dispatched through the existing handleToolCallExecution path
(same as omniroute_web_search) to a new web_fetch builtin skill handler that
resolves credentials and calls handleWebFetch() against /v1/web/fetch.

Strictly opt-in: with no interceptFetch DB row configured (the default), the
outgoing request body is byte-identical to pre-change behavior — no heuristic
default bypass like the interceptSearch sibling, to guarantee zero overhead
when disabled (Hard Rule #20).

Also ships the dashboard toggle (owner decision, overriding the plan's
backend-only recommendation): ProviderInterceptionSection.tsx on the provider
detail page, backed by GET/PUT/DELETE /api/providers/[id]/interception-rules,
covering both interceptSearch and interceptFetch from one control.

chatCore.ts touch is minimal (frozen file): one resolver call + one
prepareWebFetchFallbackBody call mirroring the existing interceptSearch block,
plus threading provider/model into the existing handleToolCallExecution call.
2026-07-19 14:37:30 -03:00
Diego Rodrigues de Sa e Souza
a2004060c5 feat(mitm): root-CA + per-host leaf certs for AgentBridge static server (#6684) (#7731)
Replace the AgentBridge static server's single self-signed leaf cert
(scoped only to the 4 antigravity hosts) with a persisted local root CA
+ per-SNI leaf certs, reusing the CA/leaf crypto already proven for the
TPROXY capture mode (tproxy/dynamicCert.ts). server.cjs switches from a
static key/cert to an SNICallback so every host in MITM_TOOL_HOSTS gets
a matching leaf, not just antigravity.

- src/mitm/cert/rootCa.ts: load-or-generate-once CA persistence
  (ca.key/ca.crt under <DATA_DIR>/mitm/), private key chmod 0o600.
- src/mitm/cert/migration.ts: pure migration gate — an already-trusted
  legacy leaf install stays on the old leaf until the operator opts in
  via MITM_ROOT_CA_ENABLED=true; a fresh install gets the CA model
  automatically. A CA that can sign a leaf for any host is materially
  more powerful than the old fixed-SAN leaf, so the switch is never
  silent for an already-trusted install.
- src/mitm/cert/install.ts: installCaCert() — thin wrapper over the
  existing cert-path-agnostic installCertResult(), same
  omniroute-mitm.crt trust-store slot the old leaf used (supersedes it,
  no dual-trust cleanup needed).
- src/mitm/manager.ts: wires the migration gate + CA load/install into
  the bridge-start sequence, passes the resolved MITM_CERT_MODE to the
  spawned server.cjs child so it can't drift from manager.ts's decision.
- src/mitm/server.cjs: async-bootstraps server creation behind the same
  MITM_CERT_MODE gate; default ("legacy") reproduces the exact prior
  synchronous behavior. The CJS/ESM boundary (server.cjs is spawned via
  plain `node`, no TS loader) is crossed via a new
  _internal/rootCaShim.cjs CJS twin of the CA/leaf crypto, matching the
  established pattern of the sibling _internal/*.cjs shims in this file.

Validated: 14 new unit tests (CA generate-once, 0o600 key perms, CA
basicConstraints, leaf issuance across every MITM_TOOL_HOSTS host, SAN
match, chain validation against the CA, leaf caching, migration-gate
branches) plus a manual live smoke test spawning server.cjs in both
legacy and root-ca mode (confirmed a real TLS handshake with SNI
api.githubcopilot.com returns a CA-issued leaf for that host).

Deferred to VPS live validation (OS-trust-store mutation is not
unit-testable): actual OS trust-store install of the CA cert via
installCaCert() on Linux/macOS/Windows.
2026-07-19 14:37:27 -03:00
Diego Rodrigues de Sa e Souza
2ae40611b2 feat(services): introduce pluggable service-provider contract, migrate 9router (#7333) (#7730) 2026-07-19 14:37:24 -03:00
Diego Rodrigues de Sa e Souza
26fa0fc753 docs: fix three stale references failing the fabricated-docs gate (#7728) 2026-07-19 14:37:21 -03:00
Diego Rodrigues de Sa e Souza
0ecc380928 feat(sse): add nvidia NIM local RPM budget + concurrency cap (#6846) (#7726)
Phase 1 of client-side quota tracking for NVIDIA NIM (no rate-limit
headers, no usage API):

- Register nvidia in PROVIDER_DEFAULT_RATE_LIMITS (40 RPM sliding
  window, matching the documented free-tier note), operator-overridable
  via a new ResilienceSettings.providerQuotaOverrides map.
- Per-connection concurrency cap (default 6) via a new
  nvidiaConcurrencyGate leaf module wrapping rateLimitSemaphore,
  wired into DefaultExecutor.execute().
- Per-model 429 lockout: confirmed already satisfied by #6773's
  passthroughModels flag on the nvidia registry entry (no new code
  needed) — added as a regression-guard test instead.

Phase 2 (AIMD adaptive ceiling learning) and Phase 3 (dashboard quota
card + combo-routing headroom preference) are explicitly deferred to
follow-up issues, per the plan's own scope note.
2026-07-19 14:37:18 -03:00
Diego Rodrigues de Sa e Souza
2741cc5a66 feat(oauth): accept full ChatGPT session JSON for Codex manual import (#6636) (#7725) 2026-07-19 14:37:15 -03:00
Diego Rodrigues de Sa e Souza
7951b60bc3 feat(cli): add auth export command for decrypted provider credentials (#6683) (#7724) 2026-07-19 14:37:12 -03:00
Diego Rodrigues de Sa e Souza
63716adc0b feat(dashboard): show proxy name in badge, sort saved-proxy picker, default to Saved tab (#7643) (#7720) 2026-07-19 14:37:08 -03:00
Diego Rodrigues de Sa e Souza
b6ba14455a feat(api): add opt-in auto-sync scheduler for free-proxy sources (#7079) (#7716) 2026-07-19 14:37:05 -03:00
Diego Rodrigues de Sa e Souza
e5842c75ec feat(providers): add proactive cf_clearance/User-Agent hint to grok-web connection dialog (#7567) (#7713) 2026-07-19 14:37:02 -03:00
Diego Rodrigues de Sa e Souza
d1668a7c3c chore(quality): rebaseline testFrozen for providers-page-utils after #7775
#7775 (pin Kimi providers first + supporter card accent) grew
tests/unit/providers-page-utils.test.ts from 1107 to 1294 lines without
updating config/quality/file-size-baseline.json, leaving the test-file-size
gate red on the release tip and blocking the whole PR queue.
2026-07-19 14:21:36 -03:00
Diego Rodrigues de Sa e Souza
7a22f2d411 feat(dashboard): pin Kimi providers first in category + official supporter card accent (#7775)
* feat(dashboard): pin Kimi providers first in category + official supporter card accent

Kimi (Moonshot AI) official-partnership highlight on the providers dashboard:
Kimi-family providers (kimi-coding, kimi-web, moonshot) now render first
within whichever category/group they already appear in, and their
ProviderCard shows a Kimi-blue (#1783FF) accent border/glow plus an
"Official Supporter" badge. Presentation-only — routing/fallback order is
untouched.

* docs(changelog): add fragment for Kimi provider card highlight (#7775)

* feat(dashboard): strengthen Kimi card accent + prove featured-first per real section

Owner refinement: make the official Kimi blue (#1783FF) border clearly
legible (2px, higher opacity) and add a subtle whole-card tint alongside
the existing glow, so the accent reads unmistakably as "the official Kimi
color" in both light and dark theme, not just a faint hairline.

Also adds concrete section-scoped regression tests against the REAL
provider catalog (not synthetic mocks), mirroring page.tsx's exact
category-building call chain, proving where each Kimi-family card actually
lands today:
  - OAuth section -> kimi-coding first
  - Web Cookie section -> kimi-web first
  - API Key -> LLM subsection -> moonshot first (kimi-k3's home provider)
  - kimi-coding-apikey and kimi (both hiddenFromDashboard) never render as
    their own card in any section, confirmed across all 9 categories.
2026-07-19 14:13:33 -03:00
Diego Rodrigues de Sa e Souza
636a1e7ff2 docs(readme): add Kimi (Moonshot AI) official supporter section (#7770) 2026-07-19 13:56:14 -03:00
Diego Rodrigues de Sa e Souza
227e382d64 test(antigravity): assert converted chat.completion for non-stream 429 retry
The executor's non-streaming path collects the upstream SSE and returns a
finished OpenAI chat.completion payload. The test still treated the body as
raw SSE and piped it through parseSSEToGeminiResponse, which correctly
returns null for non-SSE input — failing the release-tip unit suite.

Verified the production output is exactly what the test's own assertions
expect (content 'Hello again', usage 2/3/5, finish_reason stop, 2 fetch
calls incl. the 429 retry), so this realigns the test with the real
contract rather than weakening it: 3 pass/1 fail -> 4 pass/0 fail.
2026-07-19 13:51:33 -03:00
Diego Rodrigues de Sa e Souza
d29eae4685 test(mitm): assert effective hosts-write spawn instead of hardcoding sudo
resolveSudoSpawn() drops the `sudo -S` prefix when already root, when sudo
is not installed (slim containers) or under OMNIROUTE_NO_SUDO (#6122), so the
spawned command is `tee` rather than `sudo` in those environments. The three
addDNSEntries assertions hardcoded `sudo` and failed whenever the suite ran
as root. Assert the effective invocation (tee -a <hosts file>) instead, still
checking the -S password flag when elevation is actually in play.

Proof: with OMNIROUTE_NO_SUDO=1 the file went 3 failing -> 8 passing; the
unelevated (sudo) path stays 8 passing.
2026-07-19 13:11:08 -03:00
Diego Rodrigues de Sa e Souza
6360b2514e test(router-eval): assert regression reasons instead of counting entries
The test named 'captures AIQ and cost regressions' only asserted
regressions.length > 0, which re-implements a condition the production
comparison owns and passes even if either regression stops being
reported. Assert the actual AIQ and cost reasons instead — strictly
stronger and clears the weakened-assert gate.
2026-07-19 13:04:35 -03:00
Diego Rodrigues de Sa e Souza
a856e3dd20 docs(readme): unified animated card system — audited v3.8.49 numbers, style contract across all cards, 5 new cards + rebuilt terminal (#7769)
* docs(readme): width + content overhaul — uniform tables, full CLI grid, condensed What's New

- All remaining spacer-calibrated tables re-targeted +100px so every table
  clamps to the same full column width as the Why OmniRoute table.
- Free-tier section: the 4 text bullets are gone — the animated budget card
  already carries all of it.
- What's New: every highlight condensed to a 1-2 line bullet (links kept).
- Compatible CLIs: the grid now lists all 25 tools from the dashboard
  registries (19 CLI Code's + 6 CLI Agents — Cline, Roo Code, Aider,
  ForgeCode, jcode, DeepSeek TUI, CodeWhale, Smelt, Pi, Grok Build, Hermes
  Agent, Goose, Open Interpreter, Warp AI, Agent Deck…) in 2 full-width
  rows; tools without a brand asset use a neutral terminal glyph
  (public/providers/cli-generic.svg) — no invented logos.
- Major-labs providers grid: 3x6 -> 2x9 full-width rows.
- Free Forever: 2 rows -> a single 7-card full-width row.
- Explore More section removed; Dashboard screenshots promoted to their own
  top-level section.

* docs(readme): force full-width card grids via in-cell spacers (GitHub strips td width)

* docs(readme): CLI grid 3 balanced rows, dark-safe Cline/Roo icons, fix 251->259 heading

* docs(readme): replace img spacers with NBSP runs — img max-width:100% collapses all-or-nothing past the container; text min-content never does

* docs(readme): calibrate card-grid NBSP runs to measured 3.14px (match Why table width); Roo icon via gh-dark-mode-only

* docs(readme): fine-tune markdown-table NBSP runs to measured widths (all ~1000px)

* docs(readme): sync stale counts to v3.8.49 reality — 268 providers (regen reference), 104 MCP tools, 25k+ tests, 26 CLIs, 40+ free-forever, 43 locales, 84 executors; fix 251-era anchors + nav

* docs(readme): animated hero card + The Promise six-pillar card — embed replaces hero text block, six static badges and the promise HTML table; all numbers from the v3.8.49 audit

* docs(diagrams): make hero/promise card reveals resilient — resting state is the final composition, entrance animates via 0s-begin hold pattern (GitHub camo drops offset-begin one-shots)

* docs(diagrams): pause-safe animation cycles — first frame is the finished composition (Chrome pause-animated-images freezes SVG imgs at t=0, where animation values override static attrs); hero/promise drop entrance reveals, budget bar/strike/dot cycles start at rest state

* docs(readme): unify all animated cards on the flat family style (no outer border/rounded frame/top strip) + fuse hero with the budget card at the top — star CTA back to text, money section moved under the hero, cli-terminal flattened with a t0 poster of the completed screen

* docs(readme): Why OmniRoute as an animated 10-row pain-vs-fix ledger card — extends the 6 original rows with resilience, key pools, local-first privacy and live analytics

* docs(readme): animated 18-strategy flow grid under the strategies table — one micro-stage per routing strategy, static tracks readable on the first frame

* docs(readme): blank line between strategies-grid img and the auto-combo sub note — the img HTML block was swallowing the note, rendering its markdown raw

* docs(readme): Private & Local-First as an 11-row guarantee ledger card — the 5 original bullets plus no-signup, loopback-only routes, header scrubbing, opt-in PII, sanitized errors and local audit trail, each with a receipt chip

* docs(readme): rebuild the resilience card — 3 self-healing layers with real mechanics (breaker states + thresholds, key cooldown with x2 backoff, model lockout) replacing the always-on combo card and the 3-row table

* docs(diagrams): rebuild cli-terminal as a compact half-height real terminal (1200x350) — pure terminal theme, real CLI commands and data tied to live counts, scrolling ticker of real subcommands

* docs(changelog): maintenance fragment for the README animated-card overhaul (#7769)
2026-07-19 12:11:13 -03:00
Diego Rodrigues de Sa e Souza
3c30607d30 fix(security): bump adm-zip >=0.6.0 + exact host matching in mitm DNS test (#7732) 2026-07-19 11:14:56 -03:00
Diego Rodrigues de Sa e Souza
07b2cf9b7e test(ci): register 5 covering unit tests in stryker tap.testFiles (base-red unblock)
Clears the release base-red where account-fallback-lockout-eviction,
cliproxyapi-dedicated-credential-7645, combo-least-used-account,
combo/recovery-hint and route-guard-forge-jcode-settings-local-only
were covering mutated modules but missing from tap.testFiles.
2026-07-19 10:25:01 -03:00
Diego Rodrigues de Sa e Souza
491f9472b8 fix(cli): load DATA_DIR/server.env as fallback for .env on Electron migration (#7302) (#7759)
Electron persists secrets (JWT_SECRET, API_KEY_SECRET, STORAGE_ENCRYPTION_KEY) to
<DATA_DIR>/server.env, but the CLI bootstrap only ever loaded <DATA_DIR>/.env. Copying
storage.sqlite + server.env from an Electron install to the CLI (exactly as the app's own
UI text instructs) silently lost STORAGE_ENCRYPTION_KEY, permanently corrupting every
encrypted provider credential.

bin/omniroute.mjs now does a one-time, one-directory migration: if <DATA_DIR>/.env is
absent but <DATA_DIR>/server.env is present, copy it to .env before the normal env-file
loading loop runs. An existing .env is never overwritten -- it always wins over a legacy
server.env.
2026-07-19 09:55:44 -03:00
Diego Rodrigues de Sa e Souza
f7e88f4792 fix(cli): split outboundUrlGuard's DB helpers so setup-opencode packages cleanly (#7682) (#7760) 2026-07-19 09:55:38 -03:00
Diego Rodrigues de Sa e Souza
d1730f5b8a fix(ci): build API-only smoke workflows backend-only to fix dast-smoke timeouts (#7226) (#7758)
dast-smoke.yml and 3 nightly API-only smoke workflows (nightly-schemathesis,
nightly-resilience, nightly-llm-security) ran "npm run build:cli" with no
preceding full build or downloaded .build/next artifact. scripts/build/
prepublish.ts silently falls back to a full Next.js production build
(dashboard UI + ~126 leaf pages + prerender) whenever the standalone
server.js is missing, which is always the case in these jobs. That inline
full build is the actual thing varying 6-29min on GitHub-hosted runners.

These workflows only exercise API routes (schemathesis/promptfoo hit
/api/monitoring/health, /v1/chat/completions, /v1/models, /api/auth,
/api/keys) and never touch the dashboard UI, so set
OMNIROUTE_BUILD_BACKEND_ONLY=1 on their "Build CLI bundle" step — an
existing, previously-unused escape hatch (scripts/build/backendOnlyPages.mjs)
that stubs the dashboard pages before the build and restores them after,
leaving every route.ts API handler intact.

npm-publish.yml is intentionally left untouched: it legitimately ships the
full dashboard UI in the published npm package.

Regression guard: tests/unit/build/backend-only-smoke-workflows.test.ts
asserts OMNIROUTE_BUILD_BACKEND_ONLY=1/OMNIROUTE_BUILD_PROFILE=backend on
all 5 "Build CLI bundle" steps across the 4 fixed workflows, and asserts
npm-publish.yml's build step is NOT backend-only.
2026-07-19 09:55:31 -03:00
Diego Rodrigues de Sa e Souza
0c6041a34e fix(mcp): copy undici into dist/node_modules to prevent hollow-package shadowing crash (#7701) (#7756) 2026-07-19 09:55:25 -03:00
Diego Rodrigues de Sa e Souza
425dbc9614 fix(packaging): move fumadocs-mdx to devDependencies (#7661) (#7757) 2026-07-19 09:55:19 -03:00
Diego Rodrigues de Sa e Souza
d03fc19c58 fix(sse): wire settings.wildcardAliases into model resolution (#7693) (#7748)
Wildcard model aliases created via the Settings UI's "Wildcard Pattern"
mode were persisted to settings.wildcardAliases but getCombinedModelAliases()
never read that store, so the wildcard-matching step in getModelInfoCore()
never saw the user's patterns. Every request fell through to provider
inference and threw "Ambiguous model" for models multiple providers claim.

Fold settings.wildcardAliases entries into the merged alias map (keyed by
pattern string, folded in last so it never shadows exact aliases).
2026-07-19 09:39:17 -03:00
Diego Rodrigues de Sa e Souza
a19f86b8ca fix(authz): classify forge/jcode CLI settings routes as LOCAL_ONLY (#7263) (#7749) 2026-07-19 09:39:13 -03:00
Diego Rodrigues de Sa e Souza
ded4ac830e fix(routing): honor eye-icon hidden models for no-auth providers in auto-combo (#7620) (#7750)
getNoAuthCandidates() in open-sse/services/autoCombo/virtualFactory.ts built the
candidate pool for no-auth providers (opencode/mimocode/etc.) without ever
consulting getHiddenModelsByProvider(), unlike the credentialed-connection loop
a few lines above it. A model hidden via the dashboard eye icon stayed in every
auto/* candidate pool forever and could still be selected.

Wire hiddenModelsMap into getNoAuthCandidates() the same way #7622 wired
noAuthProviderSpecificData in, mirroring the existing credentialed-connection
check.
2026-07-19 09:39:08 -03:00
Diego Rodrigues de Sa e Souza
c95a161709 fix(sse): persist rotated Gemini web-session cookies via onCredentialsRefreshed (#7676) (#7751) 2026-07-19 09:39:02 -03:00
Diego Rodrigues de Sa e Souza
45698736e3 fix(docs): heal release-green docs drift + eslint any-suppression drift (#7253) (#7755)
- docs/routing/REASONING_ROUTING.md: migration renumbered 125->126
- docs/INCIDENT_RESPONSE.md, docs/PERF_BUDGETS.md: /api/version renamed to /api/system/version
- config/quality/eslint-suppressions.json: rebaseline no-explicit-any counts for
  tests/unit/combo-routing-engine.test.ts (261->269) and
  tests/unit/base-executor-sanitize-effort.test.ts (45->48), drifted by the prior
  base-red full-suite realignment commits (dbc9f6081, 764a4aee0) whose sibling
  test-file-size ratchet was already rebaselined in 00b853969 but this gate was missed
- tests/unit/call-log-provider-display.test.ts, tests/unit/m365-web-token-extraction-7078.test.ts:
  removed the never-baselined explicit any usages (typed via inference instead)
2026-07-19 09:38:57 -03:00
NOXX - Commiter
dffff5d656 feat(providers): notion-web live model discovery via getAvailableModels (#7696)
* feat(providers): notion-web live models via getAvailableModels

Cookie-auth discovery against POST /api/v3/getAvailableModels (spaceId from
cookie or getSpaces) so /api/providers/{id}/models and /v1/models can surface
the real Notion AI picker catalog instead of a single stub notion-ai id.

Also injects a config transcript entry with the selected model codename on
runInferenceTranscript, seeds an offline fallback catalog, and documents that
space_id is needed for reliable discovery.

* fix(providers): address notion-web review + docs provider count

- Safe decodeURIComponent for malformed cookie values
- Use extractSpaceIdFromNotionCookie instead of case-sensitive space_id= includes
- Single trim in buildNotionTranscript
- Sync STRICT docs counts to 265 providers (README/AGENTS/CLAUDE)

* refactor(notion-web): extract helpers to keep parseNotionAvailableModels/pickFirstSpaceId under complexity cap

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: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-19 04:26:26 -03:00
Makcim Ivanov
390e88ca1e fix(cursor): discover models via official CLI command (#7692)
Co-authored-by: Makcim Ivanov <makcimbx@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-19 04:20:39 -03:00
danscMax
fbbc695efa fix(sse): start credential-health sweep at boot so stale web sessions recover (#7689)
The credential-health scheduler (src/lib/credentialHealth/scheduler.ts) auto-inits on
import, but nothing imported it at startup — only the on-demand credentialGate
(open-sse/services/credentialGate.ts) does, lazily on the first gated request. So the
boot-time sweep never ran, and web-session connections whose cookies expired overnight
stayed red/unavailable until a real request re-tripped the failure (the "*-web providers
go red on restart" complaint).

Wire initCredentialHealthCheck() into src/instrumentation-node.ts (the real Next.js
instrumentation startup) right after the runtime-settings restore, in its own try/catch
with a [STARTUP] log line. Idempotent and self-disabling via
OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK; cadence tunable via
CREDENTIAL_HEALTH_CHECK_INTERVAL.

The wiring MUST live in instrumentation-node.ts, NOT the unused src/server-init.ts —
the latter never runs in production, which is why the earlier attempt (closed PR #7432)
was a no-op.

Test: tests/unit/credential-health-boot-wiring.test.ts asserts the boot wiring is present
in instrumentation-node.ts and absent from the dead server-init.ts.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-19 04:20:28 -03:00
Diego Rodrigues de Sa e Souza
313cbefda4 fix(sse): proactively refresh Grok Build OAuth token before dispatch (#7610) (#7715)
GrokCliExecutor.execute() dispatches via raw https.request (nativePost)
instead of the shared fetch path, so it never inherited (nor delegated to)
BaseExecutor.execute()'s proactive-refresh gate the way codex.ts does via
super.execute(). The only refresh that ever fired was the reactive one on a
401/403 from upstream — the rotating xAI refresh_token idled until real
expiry, matching the "unusable within minutes, must delete/re-add" report.

Wires in the same needsRefresh()/refreshCredentials() gate, using
runWithOnPersist + isUnrecoverableRefreshError to keep the [refresh +
persist] atomic under the same per-connection mutex Codex/Claude rely on
for rotating refresh tokens (base.ts:592-644).

Also fixes the smaller, separate bug #2 from the same report: grok-cli was
absent from OAUTH_TEST_CONFIG in the connection-test route, so "Test
Connection" always reported "Provider test not supported" regardless of
token health. Added a checkExpiry entry (same pattern as qwen/cline/
kilocode — Grok Build's proxy doesn't expose a lightweight probe endpoint
with the cli-specific headers this shared prober sends). Extracted
OAUTH_TEST_CONFIG into its own module (oauthTestConfig.ts) so the new entry
doesn't grow the frozen route.ts past its file-size cap.

Bug #3 (no browser/device-code login for Grok Build) and bug #4 (quota
display) from the same issue are feature gaps, not regressions — left as
follow-ups per the triage plan-file.

Refs #7610
2026-07-19 02:34:49 -03:00
Diego Rodrigues de Sa e Souza
69bbcafcb4 fix(providers): classify ambiguous Mistral 401 instead of hard auth error (#7638) (#7718)
Mistral's quota-exhausted response is a bare 401 with a contentless
{"detail":"Unauthorized"} body — byte-identical to a genuinely revoked
key. classifyFailure() in the connection-test route always resolved
this to upstream_auth_error ("Invalid API key"), hiding the real
quota-exhaustion cause and misleading operators into rotating a still-
valid key.

classifyFailure() now accepts an optional `provider` and, for a bare
Mistral 401 with no explicit auth signal in the message (no "invalid
api key" / "token invalid" / "revoked" / "access denied" text), returns
`upstream_ambiguous_auth_or_quota` instead. A Mistral 401 that DOES
carry an explicit auth signal, and any non-Mistral 401, are unaffected
and still classify as upstream_auth_error (baseline preserved).

The new branching logic lives in a new module
(mistralAmbiguousAuth.ts) rather than inline in route.ts, keeping that
frozen file's line count within its file-size-baseline.json budget.

TDD: tests/unit/provider-test-mistral-401-classify.test.ts reproduces
the bug (RED against unfixed classifyFailure), proves the fix (GREEN),
and pins the baseline non-Mistral-401 behavior per the owner's
explicit requirement.
2026-07-19 02:31:36 -03:00
Diego Rodrigues de Sa e Souza
a9eb25b93c fix(claude-web): unify Turnstile/executor/fast-path User-Agents behind one fingerprint (#7548) (#7711) 2026-07-19 02:31:26 -03:00
Diego Rodrigues de Sa e Souza
b4ee34fa02 fix(sse): authenticate CLIProxyAPI fallback/passthrough legs with a dedicated credential (#7645) (#7712)
CLIProxyAPI requires its own separately-configured api-keys credential and
rejects any other token with 401. Both the direct mode:"cliproxyapi"
passthrough leg and the mode:"fallback" retry leg reused the resolved
connection's own credentials (the native provider's key) unchanged, making
the fallback path a permanent no-op for every provider configured this way.

Adds a dedicated cliproxyapi_api_key setting (settingsSchemas.ts) and a new
credential-resolution module (cliproxyapiCredentials.ts) that substitutes it
in at the executorProxy.ts choke point for both CLIProxyAPI-bound legs, so
CliproxyapiExecutor itself stays credential-source-agnostic. Falls back to
the connection's own credential when no dedicated key is configured,
preserving prior (workaround) behavior.
2026-07-19 02:31:23 -03:00
Diego Rodrigues de Sa e Souza
ebd6afd59a fix(providers): degrade Arena (lmarena) cookie validation redirect to unsupported (#7542) (#7710)
- validateWebCookieProvider's /models probe against lmarena's registered
  baseUrl (a POST-only streaming endpoint from #6280) triggers a 307
  REDIRECT_BLOCKED from safeOutboundFetch, which was surfaced as a raw
  "Redirect blocked" error (unsupported:false) instead of the honest
  "unsupported" signal — the dashboard rendered a hard Invalid state for a
  perfectly valid cookie.
- Add toWebCookieValidationErrorResult() in validation/transport.ts: for
  providers whose /models probe is known-unreliable (lmarena for now),
  REDIRECT_BLOCKED now degrades to {valid:false, unsupported:true}, mirroring
  the same REDIRECT_BLOCKED degrade already applied on the discovery path by
  #6267. Deliberately scoped to lmarena only (see code comment) — other
  web-cookie providers with a similarly-shaped baseUrl need their own proven
  repro before joining the allowlist.
- Remove the now-stale comment at validation.ts claiming lmarena has no
  providerRegistry entry (false since #6280 registered one).
- Regression test: tests/unit/arena-cookie-validation-redirect-7542.test.ts
  (RED confirmed against unfixed code, GREEN after the fix).
2026-07-19 02:31:19 -03:00