mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-16 20:22:21 +03:00
e25f6062d0baf65e33cea11aade76bccdaf9d2e4
5306 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e25f6062d0 | merge release/v3.8.49 into feat/6683-cli-auth-export | ||
|
|
3c30607d30 | fix(security): bump adm-zip >=0.6.0 + exact host matching in mitm DNS test (#7732) | ||
|
|
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. |
||
|
|
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. |
||
|
|
f7e88f4792 | fix(cli): split outboundUrlGuard's DB helpers so setup-opencode packages cleanly (#7682) (#7760) | ||
|
|
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. |
||
|
|
0c6041a34e | fix(mcp): copy undici into dist/node_modules to prevent hollow-package shadowing crash (#7701) (#7756) | ||
|
|
425dbc9614 | fix(packaging): move fumadocs-mdx to devDependencies (#7661) (#7757) | ||
|
|
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). |
||
|
|
a19f86b8ca | fix(authz): classify forge/jcode CLI settings routes as LOCAL_ONLY (#7263) (#7749) | ||
|
|
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. |
||
|
|
c95a161709 | fix(sse): persist rotated Gemini web-session cookies via onCredentialsRefreshed (#7676) (#7751) | ||
|
|
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 ( |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
100a27f2b2 | feat(cli): add auth export command for decrypted provider credentials (#6683) | ||
|
|
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.
|
||
|
|
a9eb25b93c | fix(claude-web): unify Turnstile/executor/fast-path User-Agents behind one fingerprint (#7548) (#7711) | ||
|
|
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. |
||
|
|
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). |
||
|
|
9e535e5ca1 |
fix(routing): strip prompt_cache_key for NVIDIA NIM (#7617) (#7709)
Codex CLI injects prompt_cache_key natively for its own prompt caching. injectPromptCacheKey() only guards against the router injecting a NEW key for nvidia/codex/xai — it never strips a key that arrived already present in the inbound body. NVIDIA NIM's OpenAI-compatible wrapper rejects the field with a 400, and NIM has no documented support for prompt caching (providerSupportsCaching already treats nvidia as non-cache-capable). Adds a provider-wide STRIP_RULES entry in paramSupport.ts (match-all, since prompt_cache_key rejection isn't model-specific) so stripUnsupportedParams() drops it for every nvidia target before the request reaches DefaultExecutor. |
||
|
|
5d755c3338 |
fix(providers): correct Chutes registry baseUrl (#7621) (#7708)
The built-in "Chutes" provider preset hardcoded the non-resolving domain api.chutesai.com (confirmed live: DNS NXDOMAIN). Every request using the built-in preset failed with getaddrinfo ENOTFOUND, independent of API key validity. The correct, resolving host is llm.chutes.ai, already used elsewhere in the codebase for model discovery (providerModelsConfig.ts:184-187). Regression test: tests/unit/chutes-registry-baseurl-7621.test.ts (RED before the fix, GREEN after). The provider.ts translate-path golden snapshot is updated to reflect the corrected URL only for the chutes entry. |
||
|
|
515dffd599 | docs(changelog): populate the [3.8.49] living section — all 306 cycle commits with per-PR author credits | ||
|
|
29dc630128 | docs(changelog): maintenance fragment for the base-red full-suite realignment | ||
|
|
00b853969f | chore(quality): annotated test-file-size rebaseline for base-red realignment (combo-routing +34, db-migration-runner +8, executor-default-base +4) | ||
|
|
764a4aee02 |
fix(base-red): fix execArgv test-env leak masking mass-migration abort + heal legacy refresh_token before index
Two independent bugs, not migration 126: 1. tests/unit/db-migration-runner.test.ts and tests/unit/migration-safety-abort-6260.test.ts: withNonTestEnvironment() only sanitized process.argv, not process.execArgv. #7359 made isAutomatedTestProcess() also scan execArgv (to catch `node --test`), so under the node:test runner execArgv always retains `--test` and the "simulate a non-test environment" helper became a no-op. The mass-migration safety-abort check (gated on !isTestEnvironment) never fired, migrations ran for real, and hit the hardcoded version-032 apikey-lifecycle special case against fixtures that never created api_keys — surfacing as "no such table: api_keys" instead of the expected MigrationSafetyAbortError. Fix: also strip test-token args from process.execArgv in the test helper. 2. tests/unit/db-core-init.test.ts: SCHEMA_SQL created idx_pc_auth_active_refresh on provider_connections(refresh_token) unconditionally, before ensureProviderConnectionsColumns() ran its column-healing pass — and that function never healed refresh_token in the first place. A legacy provider_connections table predating that column (simulated by the "max_concurrent column is healed" fixture) fails startup with "no such column: refresh_token" instead of healing. Fix: move the index into ensureProviderConnectionsColumns(), after adding a defensive refresh_token backfill. |
||
|
|
aa28676d87 |
fix(base-red): correct swapped isAutomatedTestProcess(argv, env) call
shouldSkipCloudSyncInitialization(env, argv) forwarded its own parameters in the wrong order to isAutomatedTestProcess(argv, env) — passing env where argv is expected and vice versa. Any real argv array landed in the `env` position (harmless there) but the env object landed in the `argv` position, and argv.some() then threw `TypeError: argv.some is not a function` as soon as a caller passed an explicit, correctly-ordered argv/env pair (tests/unit/model-sync- scheduler.test.ts "initCloudSync skips auto initialization..."). Fixed the call-site argument order. Also hardened isAutomatedTestProcess() to tolerate a non-array argv defensively (return false instead of throwing) since this check gates production background-task startup (auto-backup, migrations, cloud sync) and must never crash the process it's protecting. |
||
|
|
dbc9f60818 |
fix(base-red): align least-used combo tests with executionKey usage keying (#7015)
sortTargetsByUsage (open-sse/services/combo/targetSorters.ts, since #7015/#7059) keys usage lookups by the per-target executionKey (combo-name + step-id), not by the bare model string, so accounts sharing a modelStr don't collapse into a single usage bucket. Three tests called recordComboRequest() directly without a `target`, so the recorded usage landed under a modelStr fallback key that never matches the real executionKey computed at combo-resolution time — every target read back as 0 usage and the original combo order won, failing the "prefers the least-used model" assertions. Production is unaffected: every real combo.ts call site already passes `target: toRecordedTarget(target)`. Fixed by priming usage through real handleComboChat calls (which route recordComboRequest through the actual resolved target) instead of calling recordComboRequest() directly with an unlinked target. |
||
|
|
f048d98b46 | docs(base-red): document missing env vars (healthcheck jitter, issue-agent timeout, DNS opt-outs, SSE comments) | ||
|
|
fefe89c9a4 |
fix(base-red): align 1M-beta test with claude-sonnet-4-6 GA (#7129)
#7129 added claude-sonnet-4-6 to CONTEXT_1M_SUPPORTED_MODELS (1M context GA'd 2026-02-17) but missed this test in its sweep. A non-CC anthropic-compatible target with extendedContext:true now legitimately receives the context-1m beta header for this model — updating the stale undefined expectation. |
||
|
|
978675bc86 | docs(base-red): sync provider count to 265 across README/AGENTS/CLAUDE | ||
|
|
b96431fc98 | test(base-red): regenerate provider translate-path golden (agnes/dahl/xai-oauth additions) | ||
|
|
d51d17854b |
fix(base-red): align qwen oauth test with #7517 chat.qwen.ai fix
The test asserted the pre-#7517 bare qwen.ai host (from upstream PR #683 / decolua issue #572). #7517 (danscMax, live-verified) found that host 404s and restored chat.qwen.ai as the working device-code endpoint. Aligning the test with the intentional, live-validated production behavior instead of reverting it. |
||
|
|
45602a31fa | test(base-red): realign APIKEY_PROVIDERS count to 179 (release tip drift) | ||
|
|
f1a77fefc5 |
fix(combo): auto-clear stale session pins and emit recovery hints on combo exhaustion (#7625)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179) * 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) * 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 ( |
||
|
|
74c006e245 |
Add reasoning-based model and effort routing (#7607)
* feat(routing): add reasoning-based model and effort routing * refactor(routing): modularize reasoning and auto-routing pipeline * fix(routing): remove redundant DB re-export and prevent SQL scan false positives * fix(routing): resolve reasoning routing review blockers * fix(i18n): keep release ranking fallbacks outside reasoning * fix(db): renumber reasoning-routing migration past release tip (124→125) 124_generic_session_affinity_ttl.sql (#7274) has since landed on release/v3.8.49 at version 124, colliding with this PR's own 124_reasoning_routing_rules.sql. Renumbers to 125 (the next free slot past the current release tip) and updates the one filename reference in docs/routing/REASONING_ROUTING.md. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(db): renumber reasoning-routing migration 125→126 (slot taken by #7360) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(api): compact temp-path decls in exportAll GET (complexity-ratchet lines budget) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(api): single-statement auth guard in exportAll GET (function under 80-line cap) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
9fce7d0fbf |
fix(antigravity): allow cloudcode envelope through messages guard (#7582)
* fix(antigravity): allow cloudcode envelope through guard * fix(sse): dedupe antigravity source-format detection, shrink chat.ts under cap resolveChatSourceFormatForPath() in chat.ts duplicated the exact antigravity-path regex already in detectFormatFromEndpoint() (open-sse/services/provider.ts) — the added function pushed chat.ts to 1808 lines, over the frozen file-size cap of 1797, with no baseline bump. Remove the duplicate: add a thin detectFormatFromUrl(body, requestUrl) wrapper next to detectFormatFromEndpoint (single source of truth for the path/body-based format detection), and have chat.ts call it directly. Also drop the now-single-use FORMATS import (compare against the literal "antigravity", matching the existing convention in chatHelpers.ts) and remove an unneeded block-scope around the pre-existing #6402 messages guard (renamed its local to msgBody — a second, separate `const b` block further down for temperature/top_p/max_tokens/n validation is untouched and does not collide). Net effect: chat.ts 1808 -> 1797 lines (exactly at the frozen cap, no baseline change). Behavior is unchanged — same tests, same guard logic, same antigravity bypass. Re-verified full green: typecheck:core, eslint, file-size/complexity/cognitive-complexity/complexity-ratchets/changelog- integrity/test-discovery gates, and the PR's own regression suites (chat-messages-validation-6402.test.ts 26/26, mitm-server-antigravity- route-alias.test.ts 4/4), plus the adjacent format-detection and chat-pipeline test suites. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
6ca35315bb |
fix(combo): failover when upstream SSE is truncated mid-lifecycle (#7545)
* fix(combo): failover when upstream SSE is truncated mid-lifecycle User log 1784230812441-bf3789: a combo target returned an SSE stream that carried bytes but never sent a recognised terminator (`data: [DONE]`, `message_stop`, `message_delta` with `stop_reason`, or a `finish_reason`) and never produced a single parseable SSE frame. The streaming quality validator's generic done-branch gate only checked `!sawAnyBytes`, so any byte at all — even unparseable garbage — passed the stream through. The combo did not fail over to the next target and the downstream SSE client hung waiting for events that never arrived. Rebuilt against the current release/v3.8.49 tip instead of the original branch diff: the original diff predates and deletes two fixes already merged to release — issue #7285 (`OpenAiLifecycleFlags` / `applyOpenAiLifecycleEvent`, the OpenAI-shape "truncated without finish_reason" failover branch) and issue #1382 (`SseLifecycleFlags .hasRealContent`, the Claude real-content vs. empty-content_block nuance). Both are preserved untouched here. Two new flags are tracked in parallel to that existing machinery instead of replacing it: * sawStructuredSSE — any parseable `event:` or `data:` frame was seen, even one carrying no recognised content (ping/metadata) — keeps the #3399/#3685 pass-through contract for those streams. * sawTerminator — a recognised terminator arrived: `data: [DONE]`, an OpenAI `finish_reason` (mirrors `openAi.hasTerminalMarker`), a Claude `message_stop`/`message_delta` with `stop_reason` (mirrors `sse.hasLifecycleEnd`), or a terminal `usage`-only chunk (new). The generic done-branch gate now requires neither flag to be true before marking the stream invalid, replacing the old `!sawAnyBytes` check (now dead and removed). The #7285 and #1382 branches are untouched. Tests added in tests/unit/validate-response-quality.test.ts (adapted from the original branch, same scenarios): 1. incomplete lifecycle (the bug) -> invalid 2. `[DONE]` only -> valid (regression guard for #3685) 3. `event: ping` only -> valid (regression guard for #3399) 4. OpenAI `finish_reason`-only chunk (no `[DONE]`) -> valid, isolates the new finish_reason check Full touched-area regression set verified green (51/51): the new tests plus combo-streaming-openai-no-finish-reason-7285, streaming-empty- content-block-1382, combo-quality-validator-reasoning, masked-200- exhaustion-fallback-6427, combo-streaming-empty-content-failover, combo-empty-content-failover-5085, combo-response-validation-failover, and combo-response-validation. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(combo): extract consumeSseLine + isTerminalUsageOnlyChunk helpers (complexity gate on parseAccumulatedSse) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(combo): move parseJsonRecord to module scope (finish complexity-gate compensation) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
8e011554fd | fix(stryker): add Microsoft Designer test to tap.testFiles (#7659) | ||
|
|
aaddfcd545 |
fix(providers): unify connection and routing flows (#7629)
* fix(providers): unify connection and routing flows * docs(changelog): add provider flow consistency entry * test(providers): move section-visibility cases to own file (test file-size cap) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(api): extract fetchLiveNoAuthModels + toLiveModel helpers (cognitive-complexity gate on buildNoAuthModelsResponse) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
2ad7da5151 |
fix(embeddings): add lmstudio to embedding provider registry (#7614)
* fix(embeddings): add lmstudio to embedding provider registry LM Studio is already registered as a local provider in the provider catalog (src/shared/constants/providers/local.ts) but was missing from EMBEDDING_PROVIDERS in open-sse/config/embeddingRegistry.ts. This caused /v1/embeddings requests targeting lmstudio models to fail with 'Unknown embedding provider: lmstudio'. Follows the same pattern as deepinfra (#2298) and openrouter (#960), but with authType: 'none' since LM Studio is a local server. Fixes #7601 * test(embeddings): add lmstudio regression test + changelog (#7601) Adds the regression test and changelog fragment required by the contribution guidelines (Hard Rule #18) for the new lmstudio entry in EMBEDDING_PROVIDERS, mirroring the precedent set by the mixedbread (#6660) and openrouter-embeddings (#6976) provider-registry additions: - tests/unit/lmstudio-embedding-provider-7601.test.ts: asserts getEmbeddingProvider('lmstudio').baseUrl/authType/authHeader and parseEmbeddingModel('lmstudio/<model>') passthrough resolution (including namespaced model ids). Verified red without the registry entry (assert.ok(provider) fails), green with it. - changelog.d/features/7601-lmstudio-embeddings.md: changelog fragment referencing issue #7601 and the new test. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Erick Kinnee <erickinnee@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
d6e86f413b |
fix(translator): synthesize tool call chunks from response.completed batched output (#7613)
* fix(translator): synthesize tool call chunks from response.completed output[]
When an upstream provider sends a batched response.completed event carrying
function_call items in its data.response.output[] array — without having
sent the individual response.output_item.added / .delta / .done events —
the state variables toolCallIndex and currentToolCallId were never set,
causing computeFinishReason to return 'stop' instead of 'tool_calls'.
This broke the agent loop for downstream Chat Completions clients
(OpenCode, Hermes, etc.) when routing through providers that batch their
output into the completed event.
Fix: parse data.response.output[] for function_call items in the
response.completed handler, synthesize the tool call header + arguments
delta chunks, advance state, and emit finish_reason: 'tool_calls'.
Also updates withAssistantRoleOnFirstDelta to handle array results.
Fixes #180, #3980
Refs: https://github.com/diegosouzapw/OmniRoute/issues/180
Refs: https://github.com/diegosouzapw/OmniRoute/issues/3980
* fix(translator): guard against double-emission for incrementally-streamed tool calls
Add a guard that skips response.completed synthesis for call_ids already
tracked via incremental output_item.added/.done events. Without this,
providers that stream incrementally AND echo function_call items in the
response.completed output[] snapshot get duplicate tool call chunks.
Also adds a regression test combining both incremental events and a
response.completed snapshot in the same turn.
Refs: diegosouzapw/OmniRoute#7613
* chore: add docker-compose.yml.bak to gitignore
* refactor(translator): extract response.completed synthesis, fix ratchets
Fixes the file-size and complexity/cognitive-complexity ratchet
regressions the dedup-guard commit (
|
||
|
|
9152e3d8f5 |
fix(stream-readiness): bump timeout for heavy Claude-format reasoning replicas (#7612)
* fix(stream-readiness): bump timeout for heavy Claude-format reasoning replicas Third-party Claude-format replicas (Minimax M2.7/M3, ZAI, bailian, agentrouter, wafer, …) inherit Anthropic's stream shape but their reasoning warm-ups routinely exceed the default 80s readiness window — the STREAM_READINESS_TIMEOUT fires before the upstream emits its first non-ping SSE event, surfacing the request as a stalled task to clients like OpenChamber / Claude Code and demanding manual continuation on every long-running task. Mirror the codex_gpt_5_5_high_reasoning +30s bump on every provider whose registry entry has format === 'claude' (excluding first-party claude/anthropic which have stable cold starts). The registry is the single source of truth, so newly-registered replicas inherit the bump without code changes. Stays within the existing maxTimeoutMs cap so a single env knob still bounds the readiness window overall. Tests: - covers Minimax M3, ZAI, official claude/anthropic (no bump), OpenAI (no bump), unknown providers (no false positives), and the maxTimeoutMs cap with the new bump stacked against large payloads. - all 17 stream-readiness-policy tests pass (10 existing + 7 new). - existing 16 stream-readiness + 11 combo-stream-readiness-fallback tests still pass (no regressions). * fix(quality): rebaseline coverage.functions 86.44->86.42 and zizmorFindings 175->176 Pre-existing drift on source branch, not introduced by #7612: - coverage.functions drifted -0.02 from PR #7625 adding failureTracker.ts (+2 function definitions). Coverage denominator grew; numerator unchanged because the 8 coverage shards do not exercise the new file. Legitimate drift from feature addition. - zizmorFindings +1 from upstream workflow drift on release/v3.8.49. PR #7612 touches zero workflow files. Same class as the _rebaseline_2026_07_17_v3849_release rebaseline that bumped 169->175. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |
||
|
|
60bcf6e75e |
fix(mitm): route Claude Code standalone MITM traffic (#7574)
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
987b6448f7 |
feat(resilience): guard OmniRoute peer routing loops (#7555)
* feat(resilience): guard OmniRoute peer routing loops * refactor(resilience): fold peer-loop log+response into rejectPeerRequest helper (file-size budget on chat.ts) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Isiah Wheeler <2122839+isiahw1@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
b5134f0b18 |
fix(sse): preserve custom tool output images (#7540)
* fix(sse): preserve custom tool output images * docs: add changelog for custom tool images --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
9544fb6353 |
feat(kimi): sync Code, Web, and Moonshot providers (#7531)
* feat(kimi): sync Code, Web, and Moonshot providers * chore(quality): trim frozen file-size overflow in Kimi sync The Kimi/Moonshot provider sync added a net +1 line to both src/sse/services/auth.ts and ProviderDetailPageClient.tsx, pushing each 1 line past its frozen cap in file-size-baseline.json. Drop one optional blank line in each (prettier-neutral, no behavior change) to land back at/under the frozen baseline. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
fac866f70b |
fix(mitm): strip trailing assistant prefill to prevent upstream Anthropic 400 errors (#7520)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168)
* fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179)
* 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)
* 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 (
|