Commit Graph

738 Commits

Author SHA1 Message Date
diegosouzapw
e52842b926 Merge branch 'compression-core' of github.com:Egorich-print/OmniRoute into fix/combine-9115 2026-08-04 08:33:32 -03:00
Diego Rodrigues de Sa e Souza
7163081f5e fix(agentrouter): retry on 400 content-blocked + burst guard (#9323)
The agentrouter.org upstream WAF returns 400 content-blocked
intermittently when:
  1. messages[].content contains a blocked keyword (Lorem ipsum, the
     phrase 'language model' alone, 'virtual assistant', etc.); or
  2. Requests from the same IP/key arrive in a burst, after which the
     WAF's per-IP suspicion bucket starts blocking content that would
     normally pass. The bucket relaxes after ~5-10s of idle.

Apply three mitigations:

1. Burst guard (open-sse/services/wafRateLimit.ts)
   Per-bucket (provider+url) gate that enforces a 500ms minimum gap
   between outbound requests to agentrouter. Configurable via
   configureWafRateLimit(). Tested in tests/unit/wafRateLimit.test.ts.

2. Reactive retry (BaseExecutor.WAF_RETRY_CONFIG in base.ts)
   New WAF_RETRY_CONFIG with maxAttempts=2, delayMs=1500,
   backoffMultiplier=2. When the upstream returns 400 with a body that
   matches /content[_-]blocked/i, retry the same URL with exponential
   backoff (1.5s, 3.0s) before falling through to the 429/401/fallback
   chain. Tested in tests/unit/base-executor-waf-retry.test.ts.

3. Documentation (docs/security/AGENTROUTER_WAF.md)
   Blocklist of always-blocked and almost-always-blocked patterns,
   behavior under load, guidance for prompts/tool output, and pointers
   to the relevant code paths in OmniRoute.

These are belt-and-suspenders: the burst guard prevents the WAF from
activating on normal traffic, and the reactive retry recovers when it
does anyway. Together they should eliminate the intermittent
400 content-blocked that Claude Code sees when running through
agentrouter via OmniRoute.

Refs #9275 follow-up. Test: 'WAF retry config shape' and 'WAF retry
differs from generic' guard the WAF_RETRY_CONFIG contract so future
refactors don't accidentally collapse the two retry paths.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-03 18:22:14 -03:00
Diego Rodrigues de Sa e Souza
a72e1656eb fix(routing): bare model ids route to codex first; validate synced candidates (#9275)
* fix(routing): bare model ids route to codex first; validate synced candidates

Two bare-model-routing bugs surfaced in the field when an OmniRoute
deployment had a codex subscription whose cookie quota was exhausted
(retry-after 429047s / ~5 days) AND an active kiro connection whose
upstream sync briefly advertised 'claude-opus-5' before kiro vendored
it into the static registry.

  1. Bare 'gpt-5.6-sol' (and friends) routed to the codex provider even
     when the user had explicitly configured 'agentrouter' as their
     provider (via model_provider in codex CLI). With codex in cooldown,
     every bare request 429'd. Fix: extend CODEX_NATIVE_UNPREFIXED_MODELS
     to include the full gpt-5.6-sol tier set + gpt-5.5 + the related
     codex-native ids. The Codex CLI default is now actually honored;
     users can still prefix 'agentrouter/gpt-5.6-sol' to opt into a
     specific provider.

  2. Bare 'claude-opus-5' silently routed to 'kiro' when kiro's synced
     /v1/models catalog had that id (likely from a transient upstream
     quirk). kiro's static registry never cataloged claude-opus-5, so
     the upstream call 404'd. Fix: validate activeSyncedProviders against
     MODEL_TO_PROVIDERS before merging them into the candidate list.
     Auto-discovery still wins when the model id has no static entry
     (brand-new models from upstream keep working).

Bonus: when handleNoCredentials returns a 404 'No active credentials for
provider: X' error, surface the top-3 candidate aliases (e.g.
'anthropic/claude-opus-5, claude/claude-opus-5, agentrouter/claude-opus-5')
so the operator can pick a working prefix instead of staring at a wall.

Tests (all pass, 25 regression tests preserved):
  - tests/unit/fix-bare-model-precedence.test.ts (7 tests)
  - tests/unit/fix-synced-model-validation.test.ts (3 tests)
  - tests/unit/fix-error-message-candidates.test.ts (3 tests)
  - tests/unit/fix-bare-routing-fallback.test.ts (7 tests)

* fix(tests): replace lorem ipsum with neutral text to avoid agentrouter WAF

The agentrouter.org WAF blocks requests containing 'lorem ipsum' in
messages[].content. When Claude Code reads test files via the Read tool,
the content appears in tool_result blocks which can trigger the filter.

Replace 'lorem ipsum dolor sit amet' with 'example content for testing
purposes' in compression harness test to avoid false positives.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-03 18:09:31 -03:00
Diego Rodrigues de Sa e Souza
0ef50886ef feat(g1): rewrite combo-strategy check to runtime-import approach (#9131)
G1 (v3.8.51): section (2) of check-known-symbols no longer regex-scans
strategy === "..." literals from combo source. The handled set now comes
from a runtime-imported dispatch registry (open-sse/services/combo/
strategyDispatch.ts) that imports the real ordering functions and enumerates
which strategies they implement. This keeps the canonical-not-handled gate
correct under the upcoming R0.3 registry dispatch, which removes the
strategy === branches the regex relied on.

- Adds HANDLED_COMBO_STRATEGIES registry (all 20 canonical strategies) + binds
  the real dispatch leaves (applyStrategyOrdering, resolveAutoStrategyOrder,
  tryFusionDispatch, tryPipelineDispatch, resolveComboTargetPipeline).
- main() imports the registry instead of reading/sourcing combo files.
- extractHandledStrategies + diffComboStrategies stay exported (pure, tested).
- New TDD test proves the runtime enumeration covers canonical exactly.

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-01 11:08:49 -03:00
Egor
4826385f19 feat(resilience): enable retry for all providers + attempt header + memory by default
- chatCore: maxAttempts 1→2 for all providers (previously only model-scope and codex got retries)
- combo.ts: add x-omniroute-attempt response header showing how many attempts were made
- memory/settings: enable memory injection by default (maxTokens 1000) — auto-extraction already wired in chatCore
2026-07-31 09:13:29 +03:00
Egor
0515e69a3f Merge branch 'pr/8949' into feat/personal-build
# Conflicts:
#	tests/unit/providers-constants-split.test.ts
2026-07-31 08:40:42 +03:00
Prudhvivuda
b2ce078c92 fix(sse): preserve Claude Code tool-name casing via Gemini/Antigravity (#9008)
Stop blindly lowercasing PascalCase tool_use names on the Gemini→Claude path so Claude Code no longer rejects Read/WebSearch as missing tools.
2026-07-30 16:32:11 -04:00
Diego Rodrigues de Sa e Souza
2c243cf1fc feat(sse): deprecate the gemini-cli upstream provider with a real migration path (#8980)
* feat(sse): deprecate the gemini-cli upstream provider with a real migration path

Stored `gemini-cli` connections were being kept alive for nothing. Measured before
touching anything:

  routable?    absent from PROVIDERS, from REGISTRY, from OAUTH_PROVIDERS, and no
               executor references it → the connection can NEVER serve a request
  refreshing?  yes, and successfully — it redeemed against PROVIDERS.gemini's client
               (681255809395-oo8ft2o…), the same public Gemini CLI / Code Assist OAuth
               client

So the scheduler made periodic upstream calls to Google to keep a credential fresh
that had nowhere to go. That is the waste this removes.

This is a deprecation, not a deletion, and the difference is deliberate. The path was
not dead code: #8232 added it after a user report (the UI advertises automatic OAuth
rotation and these rows never rotated), and #8275 narrowed it to exactly the legacy
refresh. Simply dropping it from `supportsTokenRefresh` would have produced a SILENT
skip — `Skipping … (refresh unsupported)` — leaving the row at "active" forever, doing
nothing. Worse than before.

Instead:

  DEPRECATED_PROVIDERS + isDeprecatedProvider/getDeprecationNotice in tokenRefresh
      one place naming the provider and where to migrate. A test asserts the migration
      target is itself routable, so the notice can never point somewhere useless.

  _getAccessTokenInternal returns the ESTABLISHED unrecoverable envelope
      { error: "unrecoverable_refresh_error", code: "provider_deprecated", migrateTo }
      Reusing `error` means isUnrecoverableRefreshError and the manual-refresh route
      already stop retrying — no new contract for callers to learn. The distinct `code`
      is what makes it legible. A bare `null` would read as transient and retry forever.

  tokenHealthCheck marks the connection terminal with the reason
      Placed after the existing terminal-status guard, which makes it idempotent for
      free: once "expired", later sweeps skip the row, so it writes once instead of
      rewriting the same reason every cycle.

  the manual-refresh route stops lying
      It said "Refresh token expired. Please re-authenticate this account." — false
      here: the token is fine, the provider is gone. Re-authenticating would loop
      against something that no longer exists. It now reports the deprecation and the
      migration target.

`gemini` uses the same OAuth client, so re-adding the account there is a working path,
not advice to start over.

Deliberately NOT touched:

  Category A — the gemini-cli CLIENT identity (#7034): clientIdentityProfiles.ts,
      clientApi.ts, googApiKeyAuth.ts. Same string, opposite direction — requests
      ARRIVING from the Gemini CLI, where OmniRoute is the server. Deleting these is the
      failure this change must never cause, so a test now asserts the profile survives.
      Audited: `git diff --name-only` touches none of those files.

  errorClassifier.ts's isCloudCodeProvider list still names gemini-cli. It is a
      defensive 403→PROJECT_ROUTE_ERROR list shared with cloudcode/cloud-code; the entry
      is unreachable for a non-routable provider, and editing a shared classification
      path for a dead string is risk without upside.

Tests — 42 across the six files that mention the identifier, all green:

    gemini-cli-legacy-refresh.test.ts        5   (3 assertions REWRITTEN, see below)
    gemini-cli-deprecation.test.ts           5   (new)
    client-identity-profiles.test.ts         9   (category A, untouched)
    service-token-refresh.test.ts           14
    errorclassifier-antigravity-403.test.ts  4
    gemini-cli-ansi-sanitization.test.ts     5   (category C, untouched)

The three rewritten assertions in the legacy file are alignment, not weakening, and the
gate is right to ask: each is now STRONGER. "refresh succeeds against Google's token
endpoint" became "zero upstream calls happen at all"; "a 400 surfaces invalid_grant"
became "the envelope is unchanged but the code says provider_deprecated" plus a control
asserting `gemini` still reports invalid_grant, proving the real path was not blunted.
The file's header keeps the whole #8232#8275 → deprecation arc, because each step is
why the next made sense. Count unchanged; no test deleted, so no allowlist entry needed.

* docs(changelog): fragment for #8980

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-30 09:36:54 -03:00
Diego Rodrigues de Sa e Souza
888ce1a73b chore(sse): drop the iflow entry from the token-refresh TTL map (#8966)
* chore(sse): drop the iflow entry from the token-refresh TTL map

The `iflow` provider was removed from the product, but its 24-hour refresh-lead
entry outlived it in REFRESH_LEAD_MS. Surfaced during v3.8.49 homologation on the
production VPS, where startup logs carry:

    [CREDENTIALS] Warning: unknown provider "iflow" in credentials file, skipping.

Measured across src/, open-sse/, tests/ and docs/ — the identifier had exactly two
occurrences repo-wide: the map entry and one test assertion. Nothing dispatches on
it, so `getRefreshLeadMs("iflow")` now falls through to TOKEN_EXPIRY_BUFFER_MS like
any other unknown provider.

The test assertion was not deleted, it was MOVED: from "returns explicit lead time
for known providers" to "falls back to TOKEN_EXPIRY_BUFFER_MS for unknown
providers". That is alignment to the new behavior and strictly more coverage than
before — a silent reintroduction of the entry now turns the fallback case red
instead of passing unnoticed. Flagged explicitly because the test-masking gate
rightly treats a removed assertion as suspicious.

Also removes the now-redundant "Non-rotating providers" section header: every
remaining entry under it is Google-backed and the following comment already says
"permanent (non-rotating)".

    node --import tsx/esm --test tests/unit/service-token-refresh.test.ts
    # 14 pass, 0 fail

* docs(changelog): fragment for #8966

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-30 09:07:54 -03:00
Jan Leon
8941eafcb9 Add native ChatGPT Web provider pipeline 2026-07-30 06:55:41 +02:00
Jan Leon
b02c586cb4 Bypass proxy compaction for native Codex context 2026-07-30 05:03:35 +02:00
NOXX - Commiter
fff11cbb57 fix(adobe-firefly): default gpt-image detailLevel to maximal (5) (#8863)
* fix(adobe-firefly): default gpt-image detailLevel to maximal (5)

GPT Image 2 quality is dominated by generationSettings.detailLevel (1-5).
The SPA often defaults to 3 (medium); missing/auto quality previously mapped
to 3 as well. Default now to 5 (high/max) so API clients and Media without
an explicit quality still get maximal detail. Explicit low/medium still honored.

* chore(quality): rebaseline adobeFireflyClient + changelog fragment

adobeFireflyClient.ts 2317->2322 (+5) — this PR's own growth at the existing
payload-build site. Covered by tests/unit/adobe-firefly.test.ts.

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

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-28 14:37:22 -03:00
Diego Rodrigues de Sa e Souza
85b9c1754e feat: Xiaomi MiMo Token Plan provider + per-connection API protocol selector (#8861)
* feat(sse): add alternateFormats registry field and resolver

* feat(sse): honor per-connection targetFormat in getTargetFormat

Registry-driven format lookup now resolves an alternate protocol
declared for the provider when the connection's providerSpecificData
carries a matching targetFormat, falling back to the entry's default
format otherwise.

* feat(sse): resolve base URL from selected alternate format

resolveBaseUrl now falls back to the connection's selected alternate
protocol (providerSpecificData.targetFormat) before the provider's
default base URL, while a manual providerSpecificData.baseUrl override
still wins over both.

* feat(sse): apply alternate format auth header and extra headers

DefaultExecutor's registry authHeader lookup and BaseExecutor's shared
header preamble now both honor a connection's selected alternate
protocol: the alternate's authHeader wins over the registry default,
and its extra headers (e.g. Anthropic-Version) are merged in.

* refactor(sse): extract resolveAlternate helper into BaseExecutor

Centralizes the getRegistryEntry() + resolveAlternateFormat() pair
that resolveBaseUrl, buildHeadersPreamble, and DefaultExecutor's
authHeader lookup each duplicated, so a future call-site can't diverge
from the shared precedence. Also translates the PT-BR comments added
in the previous three commits to match the surrounding English. Pure
refactor — no behavior change.

* feat(sse): declare Anthropic-compatible variant for xiaomi-mimo

The provider publishes the same catalog over /anthropic/v1/messages on the same
host. Selecting it also required bypassing the per-provider URL normalizers in
DefaultExecutor.buildUrl(): normalizeXiaomiMimoChatUrl() appends /chat/completions
unconditionally, which mangled the alternate's already-complete endpoint into
.../anthropic/v1/messages/chat/completions.

* feat(sse): add xiaomi-mimo-token-plan provider with monthly quota

Token Plan is a separate product: tp- keys authenticate only on the regional
token-plan-sgp host and return 401 on api.xiaomimimo.com, where the existing
xiaomi-mimo provider points. Same pattern as qwen-cloud-token-plan.

Registers the monthly token allowance (no balance API upstream) and declares
the Anthropic-compatible variant on the token-plan host.

* feat(dashboard): add API protocol selector to connection modal

Providers that declare alternateFormats in the registry now expose an opt-in
protocol dropdown on the connection modal. The choice persists to
providerSpecificData.targetFormat as an explicit null when set back to the
default, since the PUT route merges { ...existing, ...incoming } and an omitted
key would keep the previous override.

* fix(i18n,quality): vi parity for the protocol selector + own-growth rebaselines

The three new provider keys landed only in en/pt-BR, so the vi parity test failed
(tests/unit/i18n-vi-completeness.test.ts asserts key parity AND no __MISSING__
markers — running i18n:sync-ui would have satisfied the first and broken the
second). Added translated values instead. Scoped to vi: it and pt-BR are the only
locales with a parity test.

Rebaselines are this PR's own growth: EditConnectionModal.tsx 1283->1316 (the
selector field) and open-sse/executors/base.ts 1540->1562 (alternate-format
resolution at the existing buildUrl/headers chokepoint).

Adds the changelog fragment.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-28 12:56:12 -03:00
Bob.Hou
ec0edef499 fix(token-refresh): discover projectId during token refresh (#8860)
* fix(token-refresh): discover projectId during token refresh

The token refresh path (tokenRefresh.ts) did not discover projectId
for antigravity/agy accounts. Dashboard and health check refresh use
this path, not the executor path.

Add ensureAntigravityProjectAssigned call after refreshGoogleToken
for antigravity/agy providers when projectId is empty.

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

* chore(quality): rebaseline the token-refresh test file + changelog fragment

tests/unit/token-refresh-service.test.ts 1311->1378 (+67) — the four cases
covering projectId discovery on the tokenRefresh.ts path. Growth is the tests
this PR adds, nothing else.

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

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-28 12:40:08 -03:00
backryun
6133939acd fix(types): align web fallback contracts (#8819) 2026-07-27 23:57:23 -03:00
Alan Vb
6a59c09460 feat(search): add Firecrawl search provider (#8814)
* feat(search): add Firecrawl search provider

Introduce the Firecrawl search provider to handle `POST /v1/search` requests via Firecrawl /v2/search API (sources: web|news)

* chore(changelog): add changelog on firecrawl search provider support

* fix(search): satisfy file-size freeze and APIKEY count

---------

Co-authored-by: allanvb <allanvb@users.noreply.github.com>
2026-07-27 22:38:21 -03:00
backryun
0bd7ceadf8 fix(types): declare virtual chaos combo config (#8815) 2026-07-27 22:38:15 -03:00
backryun
5f5f4a589d fix(types): preserve compression detail config shapes (#8812) 2026-07-27 22:38:11 -03:00
backryun
308d3f06e2 fix(types): export web executor types from their module (#8810) 2026-07-27 22:38:04 -03:00
AmirHossein Rezaei
4d66dd113f fix(resilience): honor Cloudflare 1010 retryable:false — skip COOLDOWN_RETRY (#8775) (#8800)
Api-key 403 bodies with Cloudflare error 1010 / browser_signature_banned /
retryable:false were treated as short AUTH_ERROR cooldowns, so the chat loop
waited ~21–33s before falling through. Return cooldownMs:0 so the tier fails
fast without permanently banning the account.
2026-07-27 22:37:51 -03:00
AmirHossein Rezaei
1c2143182c fix(combo): fail open when strict context filter empties unknown-only pools (#8786) (#8798)
Strict contextFilterMode excluded every target whose context limit was missing
from the capability catalog, so otherwise-executable combos returned 404
no_executable_targets. Restore unknown-context targets when no known-good
survivor remains, surface context_requirements_exhausted from targetResolution,
and keep the empty-pool payload in pinRecovery after the #8592 split.
2026-07-27 22:37:48 -03:00
Wilson
f8487648c8 fix(resilience): keep resource 404s from cooling models (#8756) 2026-07-27 21:12:42 -03:00
Prudhvi Vuda
f3a78d68e8 fix(sse): compact Responses multi-turn images before context hard-reject (#8560) (#8595)
* fix(sse): compact Responses multi-turn images before context hard-reject (#8560)

Codex Desktop sessions near the 372k input cap were rejected on the second
inline image because compressContext no-op'd on Responses input[] and never
pruned older vision turns. Adapt via bodyAdapter, prune older images while
keeping the latest, and run last-resort compaction before the budget check.

* docs(env): document CONTEXT_KEEP_LATEST_IMAGES for #8560

Keep check:env-doc-sync green after the context image-pruning override.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-27 21:12:27 -03:00
MumuTW
0eeb8f45c0 refactor(sse): extract combo target resolution into combo/targetResolution.ts (#8592)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts

Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).

handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:

  - context-cache pin routing (Fix #679), including the
    pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
  - fusion panel dispatch + the #6455 misconfiguration warn
  - pipeline chaining
  - nested combo-ref execute-mode runtime-unit dispatch

Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.

open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)

Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.

combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.

Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.

* test(sse): close the dispatch-prelude coverage holes found by mutation testing

An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.

Three holes, now closed (8 tests -> 20):

Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.

Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.

Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.

Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.

The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.

Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.

* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch

The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:

  [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
      ✗ fusion
      ✗ pipeline

Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.

Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):

- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts

Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.

* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles

check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:

  ✗ 2 covering unit test(s) across 2 module(s) are missing from
    stryker.conf.json tap.testFiles
      open-sse/services/combo/comboStructure.ts
      open-sse/services/combo/rrState.ts

The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.

Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.

* docs(changelog): add fragment for #8582 combo dispatch prelude

* refactor(sse): extract combo target resolution into combo/targetResolution.ts

Pure move, no behaviour change. Lifts the target-resolution stage of
handleComboChat — everything between the dispatch prelude and the attempt
loop — into a new leaf, open-sse/services/combo/targetResolution.ts.

Moved verbatim: provider-wildcard expansion, weighted step-group resolution
+ sticky-weighted eligibility, request-tag routing, the known-context-overflow
early return, the smart/pipeline-enabled auto dispatch, auto-strategy
ordering, per-strategy ordering, cache-strategy affinity, session stickiness,
eval scores, request-compatibility + context-requirement filters, task-aware
reordering, prompt-cache affinity, and the priority-strategy pre-screen.

The three early exits become an { earlyResponse } result so the host decides
to return them (same pattern as resolveAutoStrategyOrder). The values the
attempt loop still reads — orderedTargets, stickyWeightedLimit,
getWeightedStepKeyForTarget, the session-stickiness result and preScreenMap —
are returned instead of closed over. Loop config (maxRetries, retryDelayMs,
fallbackDelayMs, maxSetRetries, setRetryDelayMs) stays in combo.ts.

buildAutoCandidates is dependency-injected because it lives in combo.ts, so
the leaf keeps zero back-edges into its host.

combo.ts 3640 -> 3321 lines; new leaf 484 lines (under the 800 cap).
Part of the #3501 god-file decomposition campaign.

* refactor(sse): split targetResolution into stage helpers, ratchet combo.ts file-size baseline

Follow-up to the target-resolution extraction: the moved region landed as one
311-line function, which converted inline code inside the (already-violating)
handleComboChat into a NEW separately-counted violating function — check:complexity
2169 -> 2171 and check:cognitive-complexity 956 -> 957.

Split resolveComboTargetPipeline along its natural stage boundaries into 14
helpers (wildcard expansion, weighted eviction/eligibility/sticky-key/selection,
step-key mapper, context-overflow response, pool-size log, smart-pipeline dispatch
and its fall-through logger, strategy ordering, continuity filters, task-aware
ordering, prompt-cache enablement/first-target protection/affinity stage). Each
stage takes the previous stage's output and returns the next; still a pure move.

The leaf now contributes ZERO complexity, max-lines-per-function and
cognitive-complexity violations. Both ratchets are back at base 4053e2314 values:
check:complexity 2169, check:cognitive-complexity 956. (Both still print RED
against their frozen ceilings 2130/951 — pre-existing base-red per #8580.)

Also ratchets ONLY the open-sse/services/combo.ts entry in
config/quality/file-size-baseline.json from 3642 to 3322, with a justification
note in the file's existing style. No sweep of unrelated entries.

* chore: stack targetResolution on dispatchPrelude tip, rebank + skills

Rebased onto refactor/combo-dispatch-prelude. Keep both leaves in
check-known-symbols. Regenerate file-size baseline; sync agent skills.

* fix(sse): restore #8494 capability fail-closed after targetResolution extract

Stacking targetResolution onto the dispatchPrelude tip dropped the #8488/#8494
compatFilterFailOpen wiring: hard capability filters emptied the pool into a
generic 404 no_executable_targets, and fail-open never re-admitted the pool.

Restore describeCapabilityFilterExhaustion earlyResponse in
applyContinuityFilters and the matching round-robin path, then rebank the
file-size baseline for tip growth the incomplete prior rebank missed.

* fix(sse): realign model-lockout cooldown options with the post-#8254 type

This branch predates #8254, which renamed the recordModelLockoutFailure option
`exactCooldownVerified` -> `exactCooldownIsUpstreamReset` and changed combo.ts's
predicate from `lockoutHintVerified` (#8393's `lockoutHintMs > 0`) to
`lockoutHintMs > mlSettings.baseCooldownMs`. Rebasing onto the current tip brought
the renamed type without updating these two call sites, so typecheck:core failed
with TS2353 at both.

Restores the base expression verbatim rather than re-wiring `lockoutHintVerified`
under the new name. The base predicate is the correct one: selectLockoutCooldownMs
returns the parsed hint ONLY when `lockoutHintMs > baseCooldownMs`, and otherwise
returns 0 or a synthetic baseCooldownMs — so `lockoutHintMs > 0` would mark a
synthetic cooldown as an upstream reset and let it bypass the #7940 maxCooldownMs
cap, which is the bug #8254 fixed.

---------

Co-authored-by: MumuTW <johnsxn.us@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-27 19:13:31 -03:00
Austin Liu
44c03926bd fix(autoCombo): handle missing model_capabilities table in taskFitness (#8603) (#8650)
Check if model_capabilities table exists in SQLite before running SELECT query in loadModelCapabilities to prevent SQL error when the table has not been created yet.

Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local>
2026-07-27 19:08:22 -03:00
Austin Liu
5cf3d1fc9c fix(windows): request shell when spawning bare qoder binary name on Windows (fixes #8590) (#8633)
* fix(windows): request shell when spawning bare qoder binary name on Windows (fixes #8590)

Post Node CVE-2024-27980, spawn('qodercli', [], { shell: false }) fails with ENOENT on Windows when command is a bare binary name without extension. Enabling shell mode for bare command names allows cmd.exe to resolve .cmd / .bat wrappers from PATH.

* fix(windows): ensure windowsHide: true on open-sse qoder/devin child spawns

---------

Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local>
2026-07-27 19:08:12 -03:00
glaze
01c0f8a7dd fix(providers): recover Kimi after quota reset (#8632)
* fix(providers): recover Kimi after quota reset

* docs: add Kimi quota recovery changelog
2026-07-27 19:08:09 -03:00
Éder Costa
9108955323 fix(claude): classify native subscription quota 429 (#8628)
Co-authored-by: Escalada Online <aescaladaonline@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-27 19:08:05 -03:00
Jonathan Bailey
dd22d9c017 fix(resilience): recover idle wedged limiters (#8616)
* chore(resilience): log queue state on expiry

* docs(changelog): document rate limiter instrumentation

* fix(resilience): recover idle wedged limiters

* chore(resilience): remove diagnostic queue logging
2026-07-27 19:08:02 -03:00
MumuTW
20aa796d97 fix(sse): route task-aware defaults by intent, fix fitness pattern shadowing (#8602, #8603) (#8605)
* fix(sse): restore task-aware routing config on restart (#8601)

The T05 Task-Aware Smart Routing config was persisted to settings.taskRouting
by PUT /api/settings/task-routing but never read back, so it silently reverted
to enabled:false + the hardcoded default model map on every restart.

Two root causes, both fixed:

- No boot hydration existed. Adds hydrateTaskRoutingConfig(settings), wired into
  src/instrumentation-node.ts next to the Thinking-Budget restore (#5312). It
  accepts either the JSON string the route persists or an already-parsed object,
  and fails open on malformed values. applyRuntimeSettings does not cover this
  key, same as the Global System Prompt (#2470).

- The config lived in a plain module-level `let`, which is duplicated per module
  graph — a boot hydration would have landed on the instrumentation graph's copy
  and never reached the one src/sse/handlers/chat.ts reads. This is the exact
  break #5312 fix-A hit on the VPS. Moves the store to the globalThis pattern
  already used by thinkingBudget.ts and systemPrompt.ts.

Runtime stats are never restored from the persisted blob.

Note the hydration is wired into instrumentation-node.ts, not the unused
src/server-init.ts.

* docs(changelog): add fragment for #8604 task-routing boot restore

* fix(sse): route task-aware defaults by intent, guard fitness pattern order (#8602, #8603)

Two related defects in the hand-maintained model-quality tables.

#8602 — DEFAULT_TASK_MODEL_MAP hardcoded literal provider/model ids
(openai/gpt-4o, gemini/gemini-2.5-flash-lite, deepseek/deepseek-chat, ...).
Wrong twice over: the ids rotted by a generation or two, and applyTaskAwareRouting
overwrites body.model directly, so a literal target skipped auto-combo's 13-factor
scoring (quota, circuit-breaker health, cost, latency, stability), connection
cooldown and model lockout — hard-failing for any operator with no connection for
that provider. Refreshing the strings would only reset the rot clock, so the
defaults now name auto/* INTENTS that resolve against the operator's actually
connected backends:

  coding        -> auto/coding
  analysis      -> auto/reasoning
  vision        -> auto/vision
  summarization -> auto/chat:fast
  background    -> auto/chat:cheap

creative and chat stay pass-through. Operators can still pin a specific model via
PUT /api/settings/task-routing; only the shipped defaults change. No provider/model
literal remains in the module.

#8603 — the pattern-shadowing fix LANDED UPSTREAM while this PR was open
(9f5be229b, Train 1D). lookupStaticFitnessTable now ranks patterns longest-first,
so gpt-4o-mini no longer inherits gpt-4o's 0.9 and deepseek-v3.2 no longer inherits
deepseek-v3's 0.85. This PR therefore no longer changes that behaviour — the
upstream scan is kept verbatim.

What remains for #8603 is the regression guard. The resolution chain hits the DB
(user_override / arena_elo / models.dev tier) before reaching layer 4, so asserting
the ordering through getTaskFitness would depend on DB fixture state. The layer is
exposed as getStaticFitnessTableScore and pinned directly by
taskFitness-pattern-order-8603.test.ts (7 cases), so the guarantee survives future
edits to FITNESS_TABLE. Those 7 cases were written against this PR's original
implementation and pass unchanged against the upstream one — independent
confirmation that the two are behaviourally equivalent.

* docs(changelog): add fragment for #8605 task-routing intent + fitness order
2026-07-27 19:07:30 -03:00
mikeiagents
39e777248c fix(auto-combo): short-circuit expandAutoComboCandidatePool when models[] is non-empty (#8598)
When an auto-combo has models[] populated by the operator but
config.auto.candidatePool is empty (the default for combos created
via the dashboard), expandAutoComboCandidatePool silently expands
the candidate pool to every model of every active provider
connection. This overrides the explicit list in models[] and lets
unintended models (e.g. gemini-3.1-flash-lite) win the auto-strategy
scoring contest.

In omniroute@3.8.48 (npm) only one guard exists before the expansion
loop (GUARD A: if (config.auto.candidatePool populated) return
eligibleTargets). The upstream release branch release/v3.8.49 added
a second guard (combo-ref check, PR #7301) but it does not cover
the common pattern where models[] holds explicit kind:"model"
entries. Both gaps share the same root mechanism and the same fix.

The new guard short-circuits whenever models[] is a non-empty array,
covering both kind:"model" entries (the dashboard default) and
kind:"combo-ref" entries (which #7301 already handles). With this
guard in place, the existing combo-ref check becomes redundant; it
is left in place for the minimal-scope surgical fix, and can be
removed in a follow-up cleanup.

Validation (in isolated Docker, 3 providers + 4 controlled combos):
  - 3 explicit models, empty candidatePool: pool 60 -> 6
  - 1 combo-ref + 2 explicit, empty candidatePool: pool 64 -> 10
  - 3 explicit models, populated candidatePool (GUARD A path): 6 -> 6
  - empty models[] virtual auto: 57 -> 57 (expansion preserved)

Closes #8597

Co-authored-by: Michael de Souza Marcos <michael.smarcos@hotmail.com>
2026-07-27 19:07:26 -03:00
Prudhvi Vuda
094f5839a6 fix(routing): exclude locked-out models from auto-combo candidates (#7623) (#8586)
Filter auto-combo candidates through existing model lockout, connection
cooldown, and terminal testStatus so repeatedly failing no-auth models
are not re-advertised into auto/* pools.
2026-07-27 19:07:07 -03:00
AmirHossein Rezaei
eadb28c7f0 refactor(tls): update TLSClient instantiation to use buildNativeTlsClientOptions across multiple services (#8583)
This change modifies the instantiation of TLSClient in chatgptTlsClient, claudeTlsClient, grokTlsClient, lmarenaTlsClient, notionTlsClient, and perplexityTlsClient to utilize the new buildNativeTlsClientOptions function. This refactor enhances consistency and maintainability across the TLS client implementations.
2026-07-27 19:06:59 -03:00
MumuTW
1d44ee00f8 refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts (#8582)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts

Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).

handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:

  - context-cache pin routing (Fix #679), including the
    pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
  - fusion panel dispatch + the #6455 misconfiguration warn
  - pipeline chaining
  - nested combo-ref execute-mode runtime-unit dispatch

Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.

open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)

Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.

combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.

Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.

* test(sse): close the dispatch-prelude coverage holes found by mutation testing

An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.

Three holes, now closed (8 tests -> 20):

Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.

Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.

Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.

Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.

The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.

Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.

* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch

The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:

  [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
      ✗ fusion
      ✗ pipeline

Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.

Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):

- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts

Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.

* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles

check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:

  ✗ 2 covering unit test(s) across 2 module(s) are missing from
    stryker.conf.json tap.testFiles
      open-sse/services/combo/comboStructure.ts
      open-sse/services/combo/rrState.ts

The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.

Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.

* docs(changelog): add fragment for #8582 combo dispatch prelude
2026-07-27 19:06:56 -03:00
Alan Vb
6bdfd540ba feat(providers): live monthly credit quota for Firecrawl (#8759)
* feat(providers): live monthly credit quota for Firecrawl

Wire Firecrawl team credits into Provider Limits / preflight via
GET /v2/team/credit-usage (Bearer API key)

- firecrawlQuotaFetcher + usage/firecrawl leaf
- USAGE_FETCHER_PROVIDERS + USAGE_SUPPORTED_PROVIDERS + apikey allowlist
- register via quotaTrackersBatch
- unit tests for fetcher + usage dispatch

* chore(changelog) - add changelog on live monthly credit quota for Firecrawl

* fix(providers): satisfy provider limits file-size gate

---------

Co-authored-by: allanvb <allanvb@users.noreply.github.com>
2026-07-27 17:25:47 -03:00
0xheycat
2f4aff381c Stabilize Notion web sessions and JSON output (#8751) 2026-07-27 17:25:43 -03:00
AmirHossein Rezaei
f35579a9b6 fix(sse): fall back to target provider for combo compression limits (#8716) (#8720)
parseModel can return provider:null when a combo target modelStr lacks a
provider/ prefix; ResolvedComboTarget already carries provider, so use it
before getTokenLimit to avoid null.toUpperCase() during compression.
2026-07-27 17:24:57 -03:00
backryun
116d583972 refactor(sse): declare the ArrayBuffer backing on media byte producers (#8665)
Six declarations spell a byte buffer as bare `Buffer` / `Uint8Array`. Without
its type argument that widens to `ArrayBufferLike`, which also admits
`SharedArrayBuffer` — so the value is rejected at every Web API boundary it is
actually passed to: `BodyInit` for `new Response(...)` and `BlobPart` for
`new Blob([...])`.

Every one of them is already ArrayBuffer-backed at runtime. `hexToBytes()`
allocates with `new Uint8Array(len)`; `synthesizeGtts()` and `pcmToWav()` return
`Buffer.concat(...)`; `fetchRemoteImage()` returns
`Buffer.from(await response.arrayBuffer())`; `readPageResponseBody()` returns
`Buffer.from(body)`, which copies. The declarations were simply less specific
than the values, so this states what the code already guarantees.

Same fix #8533 applied to the multipart and gRPC-web bodies.

Fixes 5 of the 208 `tsc -p open-sse/tsconfig.json` diagnostics with no new ones:
3 in audioSpeech.ts (MiniMax hex, gTTS, Vertex Gemini TTS), 1 in
imageGeneration.ts (Topaz Blob upload) and 1 in browserBackedChat.ts.

Refs #8484
2026-07-27 17:24:23 -03:00
Austin Liu
809e6d1880 fix(autoCombo): use longest pattern match in static fitness table (#8603) (#8664)
Sort static fitness table patterns by descending length before matching. This prevents shorter substrings like 'gpt-4o' from incorrectly matching longer model IDs like 'gpt-4o-mini' when 'gpt-4o' happens to be listed earlier in JavaScript Object key iteration order.

Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-07-27 17:24:18 -03:00
Diego Rodrigues de Sa e Souza
f9899c57ca Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:44 -03:00
fuko2935
d78a836d3c Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:33 -03:00
Austin Liu
9f5be229b8 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:30:01 -03:00
backryun
dd34520ffe Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:23 -03:00
Austin Liu
96c3b94811 Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass.
2026-07-27 11:29:18 -03:00
Diego Rodrigues de Sa e Souza
7f8a59ac66 fix: repair five base-red failures on release/v3.8.49 (#8706)
* fix: repair five base-red failures on release/v3.8.49

Every PR cut from this branch fails CI on the branch's own breakage. Five
distinct causes, none introduced by the PRs that trip over them:

1. dast-smoke / Turbopack build — src/sse/handlers/chat.ts imported
   PROVIDER_BREAKER_FAILURE_STATUSES twice in one statement. A duplicate import
   specifier is an ECMAScript syntax error, so the production build never
   compiled. Introduced by #8258, whose export fix landed on top of an import
   that already existed.

2. Unit Tests — the #8393 verified-cooldown bypass was renamed
   exactCooldownVerified -> exactCooldownIsUpstreamReset during the #8254
   conflict resolution, which also dropped the flag at the markAccountUnavailable
   call site entirely. The rename left the test passing the old key (so the flag
   was silently ignored and a verified upstream reset got clamped back to
   maxCooldownMs), and the dropped call site meant no real caller set it at all.
   Align the test on the surviving name, restore the call site, and restore the
   doc comment explaining #6863 vs #7940.

3. Unit Tests — #8526 added four common.* keys to en.json only, breaking the
   strict key-parity tests for pt-BR and vi. Translated into all 42 locales.

4. Unit Tests — vi carried 17 __MISSING__ placeholders from #8354 and #8463, and
   vi is the one locale with a no-placeholder test. Translated.

5. No new ESLint warnings — four suppressed `any`s in
   tests/unit/combo-routing-engine.test.ts no longer exist, and ESLint exits 2 on
   stale suppressions. Pruned (271 -> 267); no other entry moved.

Also regenerates skills/cli-backup-sync/SKILL.md, which still documented the
`backup status` flags #8512 removed — the merge-integrity gate compares the
generated output against the tree.

Not fixed here: the env/docs contract (NEXT_PUBLIC_OMNIROUTE_BASE_PATH and
OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS missing from .env.example), which #8690
already covers, and the quality baselines, which #8686 covers.

* fix(quality): keep prettier off the generated SKILL.md files

check:agent-skills-sync diffs the generator's output against the tree byte for
byte, but lint-staged runs prettier over any staged *.md — and prettier inserts a
blank line after the frontmatter that the generator does not emit. Committing a
regenerated skill therefore made the gate fail again on the very file that was
just brought back in sync. The 44 untouched skills only escape this because they
never pass through lint-staged.

The generator is the formatter of record for these files, so ignore them.

* fix(i18n,quality): drop the stale zh-TW key; raise the auth.ts frozen cap

#8463 renamed `oauthModal.googleOAuthWarning` away but left the old key behind in
zh-TW, so the "the stale googleOAuthWarning key is GONE from every locale" guard
fails on the branch. Removed it.

The auth.ts frozen line cap goes 2486 -> 2492. Restoring the dropped
exactCooldownIsUpstreamReset call site costs 7 lines, and staging the file makes
lint-staged reformat four pre-existing over-100-column lines to prettier's rule —
unavoidable without bypassing the hook, which hard rule #10 forbids. The file
still sits 12 lines below where the cap was set relative to its actual size.
2026-07-26 16:16:42 -03:00
Diego Rodrigues de Sa e Souza
7faec9339d fix(resilience,translator): three release/v3.8.49 base-red regressions + eslint baseline — conflict resolved (#8254)
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
2026-07-26 12:11:35 -03:00
Prudhvi Vuda
f3fa27f384 fix(backend): fail closed when capability filters empty the combo pool (#8494)
* fix(backend): fail closed when capability filters empty the combo pool

Tools/vision/structured_output filters no longer re-admit the full pool when
every target is incompatible. Opt-in via combo config compatFilterFailOpen.

Closes #8488

* fix(backend): keep tool-emulation providers under fail-closed filters

Carve out providers with toolCalling:"emulated" (#5240) from tools
capability_mismatch so chatgpt-web combos still reach the prompt shim.
Align round-robin compatFilterFailOpen with settings fallback and drop
new any-typed params from the #8488 combo-routing tests.

* chore(lint): prune stale combo-routing-engine any suppressions

Test cleanup in #8488 dropped four no-explicit-any hits; sync the freeze file.

* chore(quality): rebaseline combo.ts + freeze new-above-cap comboStructure.ts

check:file-size was red for this PR's own growth: combo.ts grew 3640->3693
(+53, the capability-filter fail-closed guard + compatFilterFailOpen escape
hatch at both call sites) and combo/comboStructure.ts crossed the 800-line
new-file cap at 918 (describeCapabilityFilterExhaustion +
providerSupportsEmulatedToolCalling for the #5240 emulated-tool-calling
exemption). Both are irreducible orchestration wiring at the existing combo
filter chokepoint (same precedent as #7301's cooldown-retry generalization).
Companion test tests/unit/combo-routing-engine.test.ts frozen at its own
grown size (3409->3449). No logic change; 95/95 tests pass.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Co-authored-by: Prudhvivuda <Prudhvivuda@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-26 12:11:06 -03:00
Alan Vb
da20b96dc0 feat(providers): weekly quota for xAI OAuth (Grok) (#8471)
* feat(providers): weekly quota for xAI OAuth (Grok)

Live weekly credit pool for xai-oauth (alias xao) via the shared
cli-chat-proxy billing API (creditUsagePercent), using the connection OAuth access token.

- Export fetchGrokBillingWithToken from grokQuotaFetcher for reuse
- xaiOauthQuotaFetcher: 60s cache, fail-open, preflight + monitor
- Provider Limits allowlist and weekly window

* test(providers): cover xai-oauth usage dispatch + fix changelog

Address PR review:
- fix changelog file & rename to 8471-xai-oauth-weekly-quota.md
- export getXaiOauthUsage via __testing
- add xai-oauth-usage.test.ts

---------

Co-authored-by: allanvb <allanvb@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-26 12:11:02 -03:00
nguyenha935
d4b9ce6016 fix(kiro): harden auth flows, quota lookup, and model discovery (#8565)
* fix(kiro): fetch builder id quota without profile arn

* fix(kiro): harden auth imports polling and model discovery

* fix(kiro): preserve auth identity and OAuth polling semantics

* docs(changelog): add Kiro auth and model discovery fix

---------

Co-authored-by: Nguyễn Thanh Hà <nguyenha@Mac-mini-M4.local>
Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
2026-07-26 03:53:47 -03:00
Hernan Javier Ardila Sanchez
3be0a5d290 fix: combo input-bound, Responses->Chat image strip, qwen-web toolCalling, empty-response exhaustion (#8476)
* test(tail): retire stale i18n __MISSING__ repro + fix qianfan website URL

Base-red slice 6, rebased onto the advanced release/v3.8.49 (91fd5f9). The oauth
grok-cli #7610 guard was already fixed on the base by #8027 (it reads the warning
from grokCliAuthJson.ts) — dropped from this slice to avoid a conflicting duplicate.
Remaining two, still red on the current base:

- i18n #7258: the "focused repro" asserted zh-TW.json STILL carries raw __MISSING__:
  placeholders. That backlog was filled (the "no locale has a raw __MISSING__: leaf"
  invariant is the durable guard); retired the now-inverted repro.
- qianfan: Baidu renamed the product page (product/wenxinworkshop -> product-s/
  qianfan_home); updated the expected website URL.

Validated (clean env): i18n 4/0, qianfan 5/0; oauth-modal-grok 2/0 already green on base.

* fix(resilience): short-circuit combo on input-bound failures (context_length_exceeded) (#8375)

isInputBoundRequestFailure() predicate detects deterministic input-bound
errors (context_length_exceeded/context_window_exceeded). The combo loop
propagates the original 400 immediately instead of burning MAX_GLOBAL_ATTEMPTS
retrying identical oversized inputs against every account.

Test: combo-input-bound-failure-8375.test.ts (1 test, 2 assertions)

* fix(resilience): add early-exit in combo dispatcher for input-bound failures (#8375)

When isInputBoundRequestFailure detects context_length_exceeded,
the combo loop returns {ok:false, response} immediately instead of
re-dispatching the oversized request.

Test: node --import tsx/esm --test tests/unit/combo-input-bound-failure-8375.test.ts
- 1 test, 2 assertions, 0 fail

* fix(translator): strip input_image from tool outputs in Responses->Chat downgrade (#8459)

toolOutputContentToString() extracts input_text/output_text parts and
replaces input_image with a placeholder instead of JSON.stringify'ing
the content-part array (which embedded raw ~52KB base64 as inert text).

Applied to both function_call_output and custom_tool_call_output branches.

Existing translator tests: 88/88 pass.
New tests: 4/4 pass.

* fix(providers): set qwen-web toolCalling to false — web-cookie provider has no native function calling (#8437)

qwen-web is a web-cookie provider that emulates tools via synthetic system
prompt text and <tool> XML parsing, never sending a native tools[] field
upstream. The filterTargetsByRequestCompatibility gate filters out non-tool-
calling targets when the request carries tools, but qwen-web's registry entry
had toolCalling=true, so the filter let it through and a tool-using session
failing over to qwen-web would silently degrade to text-only chat with
'Tool X does not exists' errors.

Sibling web-cookie providers (chatgpt-web, yuanbao-web, claude-web, etc.)
all correctly set toolCalling: false — qwen-web was an outlier introduced
in PR #7874.

Verification:
- LSP diagnostics: clean
- Pattern matches chatgpt-web, yuanbao-web, and other web-cookie providers

* fix(backend): empty upstream response mislabeled as exhausted_connection (#8397)

isEmptyContentFailure guard only matched '/empty content/i' but the actual
error text from detectMalformedNonStream is 'returned an empty response
(no usable choices/output)' — which lacks the word 'content'. Expanded
regex to also match '/empty response/i' so these transient upstream glitches
don't get classified as connection-level exhaustion in combo diagnostics.

Test: 28 existing combo-target-exhaustion tests pass (no new test needed)

* test(#8397): add regression test for empty-response 502 not marking provider/connection exhausted

* test(qwen-web): align registry snapshot with toolCalling:false

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

* fix(resilience): scope #8375 input-bound short-circuit to homogeneous remainders

The isInputBoundFailure short-circuit (context_length_exceeded /
context_window_exceeded) fired unconditionally on the first target, aborting
the whole combo even when later targets are a different model with a larger
context window — regressing the intentional heterogeneous-combo fallback that
isContextOverflow400 (#6637) protects. Reproduced with a 2-target combo
(small-context model fails, larger-context model would have succeeded): the
combo never reached target 2.

Scope the short-circuit to remainders where every remaining target shares the
same modelStr as the one that just failed — the "retrying will fail
identically" premise for context_length_exceeded only holds within a
homogeneous same-model pool.

Rebaselines open-sse/services/combo.ts's frozen file-size cap (3642->3679)
for this PR's own combo.ts growth (config/quality/file-size-baseline.json).

Refs #8375

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Probe Test <probe@example.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-26 03:53:32 -03:00