Commit Graph

2138 Commits

Author SHA1 Message Date
backryun
647afe327b refactor(sse): declare the multipart/gRPC frame bodies as ArrayBuffer-backed (#8533)
* refactor(sse): declare the multipart/gRPC frame bodies as ArrayBuffer-backed

Seven TS2769 across three files, one root cause: a bare `Uint8Array` widens to
`Uint8Array<ArrayBufferLike>`, which admits `SharedArrayBuffer` and so is not
assignable to `BodyInit`. Every one of the seven is a `fetch({ body })` call
rejecting an otherwise-valid payload.

They flow from exactly two producers:

  buildMultipartBody()  audioTranscription.ts  -> 5 sites here + 1 in audioTranslation.ts
  grpcWebFrame()        windsurf.ts            -> 1 site

Both allocate with `new Uint8Array(length)`, which is always ArrayBuffer-backed,
so declaring `Uint8Array<ArrayBuffer>` states what the code already guarantees.
This is a narrowing of the declaration to match reality, not a cast at the call
sites — no `as BodyInit` anywhere, and the seven call sites are untouched.

280 -> 273, zero new, on a line-number-agnostic diff of the full tsc error set.
The runtime diff is two return-type annotations; nothing executable changed.

Tests: buildMultipartBody already has four direct tests, but they assert the
payload's *contents* (boundary, filename sanitization, MIME fallback) — none
assert the backing, which is precisely what the new type guarantees and what a
plausible switch to a pooled `Buffer.concat` would break. Added
multipart-body-arraybuffer-backing.test.ts (3 tests): the buffer is a plain
ArrayBuffer, the view spans it entirely at offset 0 (no pool remainder), and
both hold for a 64 KiB payload past Node's Buffer-pool threshold. 258/258 across
the 17 affected audio/windsurf suites.

Not included: the 3 remaining TS2339 in audioTranscription.ts (`data?.data?.…`
on an untyped poll result). Different root cause — grouped by kind, not by file,
per #8484.

* chore(quality): trim the buildMultipartBody doc so audioTranscription.ts stays under the 800 cap

My doc comment on buildMultipartBody added 9 lines to a file with 8 to spare
(792 -> 801 as check:file-size counts, cap 800), so this PR was failing the gate
on growth I introduced. Condensed the comment to 4 lines; the file lands at 796.

Rebuilt on top of the /green-prs sync merge (9973bf3bf) rather than force-pushing
my own rebase over it — the release-tip sync there is @ikelvingo's work and had
already been validated against this branch.

Delta unchanged: 280 -> 273, 7 fixed, zero new. 68/68 on the audio/windsurf
suites; typecheck:core and eslint clean; check:file-size now OK.

---------

Co-authored-by: backryun <busan011@ormbiz.co.kr>
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-26 03:52:04 -03:00
backryun
d628105503 refactor(sse): retag the designer-web result unions with string discriminants (#8531)
All 10 diagnostics in this file are the `strictNullChecks: false` narrowing
limitation: a boolean-literal discriminant narrows the positive branch but
leaves the negative one as the full union, so `if (!resolved.ok)` and the code
after `if (outcome.success)` could not see `status`/`error`.

Three unions, all module-private and untouched by any test, so per the rule
recorded on #8499 these are retagged rather than fixed with predicates —
predicates are for exported unions whose shape callers depend on:

  resolveDesignerWebRequest  ok: true|false      -> state: "resolved"|"invalid"
  DesignerWebStepResult      done + success      -> state: "pending"|"ready"|"failed"

The step union collapsed two booleans into one discriminant; `done`/`success`
encoded three states across two flags, which is also why the pending arm had no
`success` property for `outcome.success` to read.

Also narrowed runDesignerWebPollLoop's declared return from
`DesignerWebStepResult | {…504…}` to a new DesignerWebOutcome (ready | failed).
The loop returns a step only after confirming it is terminal, and otherwise
synthesizes a 504 — it can never return a pending step, and the old signature
claiming it could is what made `.success` unreadable on the union at all.

280 -> 270, zero new, on a line-number-agnostic diff of the full tsc error set.

Tests: unlike the previous slices this rewrote real control flow (three
conditionals), so the existing suite is doing actual work here —
microsoft-designer-web-6672.test.ts drives the handler end-to-end through 400,
401, immediate-ready, poll-then-ready, non-OK upstream and 504-timeout, i.e.
every arm but one. The "empty" arm (unrecognized 200 body -> terminal 502) was
tested only at the parser level, never through the handler, so the 502 itself
was unasserted. Added designer-web-empty-response-502.test.ts (2 tests) pinning
that it is terminal (exactly one fetch, no polling) and distinct from the 504
deadline path. Both suites pass against the parent commit too — the tests are
black-box through the exported handler, so they are agnostic to the discriminant
rename and prove the retag is behavior-preserving. 61/61 across the 3 suites.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-26 03:52:00 -03:00
backryun
2cf462c1a1 refactor(sse): type the rerank response adapter's options parameter (#8528)
`transformResponseFromProvider(providerConfig, data, options = {})` annotated
nothing, so TS inferred `options` as `{}` from its default value and rejected
every read of it: `top_n` x6, `documents` x4, `return_documents` x2 across the
DeepInfra and Voyage adapters. All 12 of the file's diagnostics, one cause.

Declared `RerankResponseOptions` from the JSDoc that already documents these
fields on handleRerank, with `documents` as `Array<string | { text?: string }>`
— the two forms the Cohere-compatible API accepts and the adapters already
branch on. `providerConfig` and `data` stay unannotated; only `options` was
producing errors and only `options` is touched.

280 -> 268, zero new, on a line-number-agnostic diff of the full tsc error set.

Tests: `{ text }` documents were only ever exercised through the *request*
adapter (#5332, #7809) — every response-adapter test passed plain strings, so
the `typeof doc === "string" ? doc : doc?.text` branch on the response side was
uncovered, and that branch is exactly what gives the declared type its union.
Added rerank-object-documents-response-path.test.ts (5 tests): object-form
documents resolved through both adapters, a mixed string/object array, the
`{}`-without-text fallback, and Voyage's index remap across an empty `{ text: "" }`
document. They pass against the parent commit's rerank.ts too — no behavior
change. 43/43 across the 6 rerank/media-cost suites.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-26 03:51:56 -03:00
backryun
0312fe2a40 refactor(guardrails): narrow the documented void return at the dispatch site (#8525)
`BaseGuardrail.preCall`/`postCall` return `GuardrailResult<unknown> | void`,
and that `| void` is deliberate: docs/security/GUARDRAILS.md documents returning
nothing as one of the three "no change" signals, and CredentialMaskerGuardrail
still declares it. So the union stays.

The problem is the consumption site. `registry.ts` reads `result?.block`,
`result?.message`, `result?.meta`, `result?.modifiedPayload` — but optional
chaining does not make `void` inspectable, and neither does a truthiness test.
That is all 12 TS2339s in the file: one union, six property reads, two dispatch
loops.

Fixed by funnelling the return through `unknown` once, in a local
`asGuardrailResult()` helper, so the loops work against a plain
`GuardrailResult | undefined`. The public contract is untouched — no signature
narrowed, no guardrail changed, callers outside this file unaffected.

280 -> 268, zero new, on a line-number-agnostic diff of the full tsc error set.

Tests: the `void` arm of the documented contract had NO coverage —
guardrails-registry.test.ts exercises guardrails that return a result and one
that throws, never one that returns nothing. Added
guardrails-void-no-change-contract.test.ts (4 tests): silent preCall/postCall
pass through untouched and are recorded as ran-not-skipped-not-errored;
returning `{}` produces an identical execution record; a silent guardrail does
not stop a later one from modifying. They pass against the parent commit's
registry.ts too, which is the evidence for no behavior change.

75/75 across the 13 guardrail suites.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-26 03:51:53 -03:00
backryun
fd739ab008 refactor(sse): restore three executor types the runtime already relied on (#8520)
Three independent root causes in the TS 7 executor slice (#8484), each a
declaration that had drifted behind the code using it. All type-only —
no behavior change, verified by running the new tests against the parent
commit's sources (7/7 pass there too).

mimocode — AccountState was missing `proxy`, but syncAccountsFromCredentials()
writes it on every account and getProxyDispatcher() reads it. The #3837/#5521
contract ("always present, null when unconfigured") lived only in a comment.
Declared as AccountProxyConfig["proxy"] so the two stay in lockstep. (4)

opencode — the tools-truncation block narrowed `modifiedBody` with
`typeof === "object"` but, unlike the client_metadata block directly above it,
omitted `!Array.isArray()` and the Record cast, so `.tools` was unreachable on
`object`. Adopted the neighbouring block's idiom. Behavior is identical: the
old code reached `.tools` on arrays too and relied on Array.isArray(undefined)
short-circuiting. (4)

zed-hosted — enqueueSseObject/finish/processLine were annotated
ReadableStreamDefaultController but are driven from a TransformStream, whose
controller has no close(). All three only ever enqueue, so they are now typed
by that single capability (SseEnqueueTarget). (3)

310 -> 299 diagnostics, zero new, on a line-number-agnostic diff of the full
tsc error set.

Tests: new tests/unit/ts7-executor-shared-shapes.test.ts pins the tools
truncation (previously uncovered) and the proxy-always-present contract. The
zed-hosted transform is already covered end-to-end by
zed-hosted-think-close-marker.test.ts. 300/300 across the 26 affected files.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-26 03:51:49 -03:00
backryun
1bd1af2f48 refactor(sse): narrow three result unions via type predicates (#8499)
* refactor(sse): narrow three result unions via type predicates

Eight diagnostics across three executors, all the same root cause already recorded in
#8483: this workspace compiles with `strictNullChecks: false`, where a boolean-literal
discriminant narrows the positive branch but not the negative one. Reading a
failure-only field after `!result.ok` therefore leaves the full union.

  auggie.ts        2  .error       on AuggieModelResolution
  muse-spark-web   4  .error       on GraphqlResult
  notion-web       2  .retryable / .errorResult on the runOnce union

#8483 retagged its union with a string discriminant. That is the better shape when the
union is module-private and small, but it does not fit here: `resolveAuggieModel` is
exported and its tests deep-equal the literal `{ ok: true, model }` object, so retagging
would churn public API and assertions to fix a checker limitation. Each union instead
gets an explicit type predicate, which narrows correctly under these compiler settings
while leaving the shape, every call site, and the tests untouched. notion-web's inline
union is named `NotionAttempt` first so it has something to `Extract` from.

Validation: full tsc error-set diff against the base config — 335 -> 327, zero new
errors (line-number-agnostic). `typecheck:core` clean; the 6 existing test files
importing a touched executor pass.

Coverage: each predicate is a one-liner whose control flow inverts on a stray `!`, and
all three failure branches already have behavioral guards —
`auggie-executor.test.ts` (400 + /Unknown Auggie model/),
`muse-spark-web-continuation.test.ts` ("Warmup failed: …"), and
`executor-notion-web.test.ts` (nested temporarily-unavailable → retried). The two
assertions added here pin the arm that the predicate unlocks on the one union that is
exported and directly reachable.

* chore(quality): rebaseline muse-spark-web.ts for #8499 own growth

The new isGraphqlFailure() type-predicate helper (TS7 strictNullChecks:false
narrowing fix) grows the frozen file 1396->1405 (+9), irreducible per the
justification recorded in config/quality/file-size-baseline.json.

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

---------

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-26 03:51:45 -03:00
backryun
06326a3c80 refactor(sse): stop three executors shadowing BaseExecutor.buildHeaders (#8498)
`BaseExecutor.buildHeaders(credentials, stream?, clientHeaders?, model?, health?)` was
shadowed in three executors by same-named helpers with unrelated signatures:

  hailuo-web  private   buildHeaders(token: string, yy: string)
  lmarena     protected buildHeaders(_model: string, credentials: unknown, _body: unknown)
  qwen-web    private   buildHeaders(token: string, cookieHeader: string, chatId?: string)

Name collisions, not overrides — each reported TS2416. They are renamed to
`buildStreamHeaders` / `buildRequestHeaders` / `buildApiHeaders`; the two lmarena test
files that called the helper directly are updated with them.

Worth stating precisely, because the shadow sat on a live dispatch path without being a
live bug: `BaseExecutor.countTokens()` calls `this.buildHeaders(credentials, false)`, and
all three inherit `countTokens()`. It is unreachable today only because
`buildCountTokensUrl()` returns null unless `config.format === "claude"` and the URL
carries `/messages` — hailuo-web and qwen-web set no format, lmarena sets `"openai"` — so
`countTokens()` returns at the guard above. Latent, not live; one `format` change away
from passing a credentials object where a token string is expected.

Two more, surfaced by clearing the above:

* `lmarena` declared `buildUrl` and `transformRequest` `protected` while both are public
  on BaseExecutor (TS2415 — a subclass may widen visibility, never narrow it). Both were
  masked behind the buildHeaders TS2416 and appeared one at a time as it cleared. Runtime
  is unaffected; JavaScript has no member visibility.

* `GithubExecutor.refreshCredentials` had no declared return type, so TypeScript inferred
  the union of its four literal returns. `GheCopilotExecutor` legitimately overrides it
  with a wider `providerSpecificData` (it also records the enterprise proxy URL) and no
  `expiresIn`, which is not assignable to that inferred union. Declared as
  `RefreshedCopilotCredentials | null` — same shape of fix as #8489, on a different method.

Validation: full tsc error-set diff against the base config — 335 -> 331, zero new errors
(line-number-agnostic). `typecheck:core` clean; the 15 existing test files importing a
touched executor pass, including lmarena's 44 across the two updated files.
`plan3-p0.test.ts` fails identically with and without this change (it reads the
developer's real ~/.omniroute DB rather than a test-scoped DATA_DIR).

The new test pins that the inherited method is no longer shadowed — verified to fail on
the base, where all three prototypes still carry their own `buildHeaders` — and that the
`countTokens()` early return which kept it harmless still holds.
2026-07-26 03:51:42 -03:00
AmirHossein Rezaei
a51cd06322 fix(resilience): honor comboCooldownWait for every combo strategy (#8541) (#8559)
* fix(resilience): honor comboCooldownWait for every combo strategy

The cooldown-wait path claimed all strategies, but isComboCooldownWaitEligible still gated on quota-share/auto. Widen the gate, raise the per-target timeout floor with it, and consult the allow-list from earliestRetryAfter so a later 403 cannot skip the decision. Fixes #8541.

* test(combo): fold cooldown-wait strategy assertions into a loop

Keep combo-config.test.ts under the file-size ceiling while covering every
routing strategy (including internal quota-share) for the #8541 gate fix.

* chore(combo): trim cooldown-wait comment under file-size ceiling

After rebase onto tip the net +2 in combo.ts sat one line over the frozen
check:file-size count (split-based); fold the SECURITY note into the block.
2026-07-26 03:51:38 -03:00
MumuTW
c447be4329 fix(db): classify compressionDetailNormalizers as db-internal in check-db-rules (#8534)
* fix(db): classify compressionDetailNormalizers as db-internal in check-db-rules

check:db-rules fails on release/v3.8.49 at its own HEAD: the module added
by #8404 is neither re-exported from localDb.ts nor listed in
INTENTIONALLY_INTERNAL, so the gate blocks every PR->release run and
tests/unit/check-db-rules.test.ts fails its live-repo case.

Its only importer is its sibling src/lib/db/compression.ts, via a
relative import inside src/lib/db/ — the db-internal classification the
list already uses for apiKeyColumnFallbacks and caseMapping. Re-exporting
it from localDb.ts would instead advertise pure normalizer helpers as
part of the compat surface, which Hard Rule #2 discourages.

* test(db): mirror compressionDetailNormalizers in the INTENTIONALLY_INTERNAL audit

The classification guard asserts the exact audited set. Adding the module to
check-db-rules.mjs without the mirror left the exact-list/exact-size assertion
red; both assertions stay exact (37 entries).

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

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-25 11:10:48 -03:00
MumuTW
d06d3fb67d test(sse): update three backoff assertions stale since the #8396 cooldown cap (#8539)
These three cases assert that a transient-error cooldown keeps doubling
to baseCooldownMs * 2^maxLevel — roughly 45.5h at the default constants.
That is precisely the blackout #8396 removed: capScaledCooldownMs
(open-sse/services/accountFallback/cooldownCap.ts) now bounds every
scaled cooldown by profile.maxCooldownMs, falling back to
BACKOFF_CONFIG.max when the profile does not configure one. All three
call checkFallbackError with a null provider, so the fallback ceiling
applies and the observed value is BACKOFF_CONFIG.max.

The backoff-level clamping each case was written to guard is unchanged
and still asserted; only the expected duration moved. The expressions
keep the original formula wrapped in the cap so the relationship stays
readable, and the first case gains a precondition assertion so it cannot
silently become vacuous if the constants change.

Fixes the error-classification (x2) and thundering-herd (x1) failures
that are red on release/v3.8.49 at its own HEAD.
2026-07-25 11:10:44 -03:00
Diego Rodrigues de Sa e Souza
30709255c9 fix(sse): stop combo's aggregated failure response from mixing fields across targets (#8486) (#8508)
handleComboChat/handleRoundRobinCombo tracked lastStatus (first-write-wins),
lastError (last-write-wins), and earliestRetryAfter (global MIN across all
targets) independently, so the final unavailableResponse() could surface a
status/message pair from two different failing targets and decorate a
config-class error (e.g. Antigravity's 422 missing_project_id, which carries
no retryAfter of its own) with an unrelated target's long reset window.

- lastStatus now overwrites on every failure (last-write-wins), matching
  lastError, so status and message always come from the same target.
- the "(reset after ...)" decoration is only applied when the surfaced
  status is itself rate-limit-class (429/503) — see the new
  open-sse/services/combo/unavailableRetryGate.ts leaf module (both
  combo.ts and chat.ts are already over their file-size baseline, so the
  gate logic lives in a new module and combo.ts only wires it in).

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:57:29 -03:00
Diego Rodrigues de Sa e Souza
9ced2e99df fix(sse): stop stream readiness from treating a choices-less error frame as success (#7503) (#8504)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:57:22 -03:00
Diego Rodrigues de Sa e Souza
6cad24eec0 fix(api): probe provider alias in models.dev reverse capability lookup (#8429) (#8506)
reverseModelsDevProviders() only matched MODELS_DEV_PROVIDER_MAP entries
by the canonical OmniRoute provider id, but the map's RHS for the OAuth
CLI providers (codex/claude) only lists their alias (cx/cc), never the
canonical id itself. Since the models.dev sync job writes
model_capabilities rows under openai/cx and anthropic/cc (never
codex/claude), and the auto-combo gate canonicalizes a codex/... or
claude/... target's provider to "codex"/"claude" before the lookup,
the synced capability row was unreachable for those two providers.

Also probe the provider's alias (via the already-imported
PROVIDER_ID_TO_ALIAS) when scanning the map, so a canonical id like
"codex"/"claude" still matches entries keyed only by their alias.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:57:15 -03:00
Diego Rodrigues de Sa e Souza
61277813b2 fix(backend): compute AgentBridge diagnose DNS check per-agent instead of hard-coded Antigravity (#8466) (#8502)
getMitmStatus() hard-wired dnsConfigured to a single Antigravity hostname
regex regardless of which agent was being diagnosed, and the diagnose route
never accepted an agentId to check against. Add checkDNSEntryForAgent()
reusing resolveHostsForAgent()'s existing per-target host resolution, thread
an optional agentId through getMitmStatus(), and have the diagnose route
parse ?agentId= and pass it through. Callers that omit agentId keep the
legacy Antigravity-only behavior unchanged.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:57:07 -03:00
Diego Rodrigues de Sa e Souza
13cc128dae fix(cli): fall back to node:sqlite when better-sqlite3 is unavailable in omniroute doctor (#7586) (#8501)
bin/cli/sqlite.mjs::loadSqlite() had no fallback beyond better-sqlite3, unlike
the real server's driver cascade (src/lib/db/adapters/driverFactory.ts::tryOpenSync,
which tries bun:sqlite -> better-sqlite3 -> node:sqlite). On machines without a
working better-sqlite3 native binary, every `omniroute doctor` DB check reported
a false FAIL even when the actual server was healthy via its own driver cascade.

openSqliteDatabase() now falls back to tryOpenSync() when better-sqlite3 fails
to import, reusing the same already-tested cascade the real server uses instead
of re-deriving a second one.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:57:00 -03:00
Diego Rodrigues de Sa e Souza
55ff236023 fix(providers): repoint zai-web executor to chat.z.ai v2 chat-completions endpoint (#8014) (#8503)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:56:53 -03:00
Diego Rodrigues de Sa e Souza
24142a8e91 fix(providers): expose base-URL override for Kimi/Moonshot CN-region keys (#7447) (#8500)
CN-region Moonshot/Kimi API keys (issued on the domestic
platform.kimi.com/moonshot.cn account) belong to a completely separate
keyspace than the international platform.kimi.ai/api.moonshot.ai
account, so OmniRoute's hard-coded international base URL rejects them
with a generic "Invalid API key" 401.

Neither "kimi" (legacy id) nor "moonshot" (current user-facing id) was
in CONFIGURABLE_BASE_URL_PROVIDERS, so the Add-connection modal never
rendered a base-URL field for them and there was no supported way to
point a new connection at api.moonshot.cn. The underlying
resolveBaseUrl()/buildUrl() primitives already honor a
providerSpecificData.baseUrl override generically (same mechanism used
by siliconflow, xiaomi-mimo, etc.) -- this only exposes that existing
affordance for kimi/moonshot, defaulting to the unchanged
international host so existing users see no behavior change.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:56:46 -03:00
Diego Rodrigues de Sa e Souza
e0aef4deb9 fix(translator): set status:completed on Responses input items to satisfy strict upstream validators (#8083) (#8507)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-25 04:56:33 -03:00
backryun
7a8f9156da test(sse): repair two base-red gates on release/v3.8.49 (#8490)
* test(sse): repair two base-red gates on release/v3.8.49

`release/v3.8.49` is red at its own HEAD (36f8fd10) — `Quality Gates` and
`Release-Green (continuous)` both fail — so every PR opened against it inherits
four failing checks regardless of content. Two of those causes had no owner:
#8480/#8481/#8482 cover the compression ladder, the antigravity catalog, and the
resilience/translator regressions respectively, none of them these.

1. `chatcore-client-usage-buffer.test.ts` — 5/5 failing.

   #8331/#8356 (e8719783e) inserted an `options` parameter between
   `clientResponseFormat` and `deps` on `applyClientUsageBuffer()`. The five call
   sites here still passed `deps` in the fourth position, so the injected spies
   landed in the `options` slot and the real implementations ran — every
   `calls.*.length` assertion saw 0. The `Parameters<typeof
   applyClientUsageBuffer>[3]` cast in `makeDeps()` masked the type error, which is
   why it reached the branch. Call sites updated to `(resp, body, format, {}, deps)`
   and the cast repointed to `[4]`.

   Two cases added for the parameter that caused this, since nothing at this layer
   exercised it: `preserveContextBudgetInVisibleUsage` re-folds `context_budget_*`
   into the visible fields for the Claude-Code path, and the default path keeps the
   real unbuffered #8331 numbers.

2. `claude-to-openai-think-close-5123.test.ts` — 4 unsuppressed
   `@typescript-eslint/no-explicit-any` errors, failing `npm run lint`.

   Its suppression entry allows `count: 2` but the file had grown to four `(chunk:
   any)` callbacks. Rather than raise the frozen count, the cause is fixed:
   `collectChunks()` returns `StreamChunk[]` instead of `unknown[]`, narrowing once
   at the boundary so all four callbacks need no annotation. The suppression entry
   is then stale and removed, which ratchets the file to zero.

Test-only plus one allowlist deletion; no production code. Verified on a clean
worktree of the base commit: `npm run lint` clean (was 4 errors),
`chatcore-client-usage-buffer` 7/7 (was 0/5), `claude-to-openai-think-close-5123`
3/3.

* chore(ci): rebaseline five inherited file-size overages on release/v3.8.49

`check:file-size` fails on release/v3.8.49 at its own HEAD (36f8fd10), which is
what turns `Fast Quality Gates` red for every PR against this base:

  tokenHealthCheck.ts   832 -> 841
  chat.ts              1865 -> 1866
  auth.ts              2475 -> 2486
  accountFallback.ts   1941 -> 1960
  combo.ts             3630 -> 3642

None of these files is touched by this PR. The growth was inherited from already
merged PRs that did not bump their entries, so there is no offending branch left
to fix — the same situation the baseline already records under
`_rebaseline_2026_07_02_5798_release_green`, and the procedure it documents is to
raise the frozen values with a justification note.

Raised to the current base values only. The files stay frozen and cannot grow
further; an in-flight PR that adds lines to them bumps its own entry as usual
(#8482 touches accountFallback.ts and combo.ts and will need that).

* chore(ci): register 11 covering unit tests in stryker tap.testFiles

`check:mutation-test-coverage --strict` fails on release/v3.8.49 at its own HEAD:
11 unit tests that cover mutated modules are absent from `stryker.conf.json`
`tap.testFiles`, so their mutant kills do not count. The gate does not self-heal —
it names the exact files to add.

  open-sse/services/accountFallback.ts  + 8247-accountfallback-model-unhealthy,
                                          8248-accountfallback-nvidia-degraded,
                                          model-lockout-exact-cooldown-cap,
                                          repro-antigravity-404-family-cooldown-hijack
  src/sse/services/auth.ts              + 7993-noauth-proxy-routing,
                                          8200-perplexity-web-401-cooldown,
                                          sse-auth-antigravity-credits
  src/server/authz/routeGuard.ts        + authz/route-guard-vnc-session-local-only
  open-sse/utils/error.ts               + error-sensitive-redaction
  open-sse/utils/publicCreds.ts         + adobe-firefly
  src/shared/utils/circuitBreaker.ts    + 8332-combo-vision-fallback

None is a file this PR touches, and the gate reports the identical 11 on a worktree
carrying none of these base-red fixes. Additions only (11 insertions, 0 deletions);
the array stays sorted. This widens what the mutation run accounts for rather than
relaxing anything.

* chore(ci): re-measure accountFallback.ts file-size cap against the current base tip

The entry frozen in this PR (1960) was the value at 36f8fd10; the base has
since advanced to 1cafd328c and the file is 1966 there, so check:file-size
would still have been red on the merge commit. Re-measured to 1966.

Same inherited drift the note already documents: check:file-size does not run
on the PR->release fast path, so growth accrues unmeasured between release
rebaselines. The other four entries still match the current tip
(tokenHealthCheck 841, chat 1866, auth 2486, combo frozen 3642 >= 3640).

---------

Co-authored-by: backryun <busan011@ormbiz.co.kr>
2026-07-25 02:53:25 -03:00
backryun
5a20314782 refactor(sse): declare the executor execute() result contract (#8489)
`normalizeExecutorResult()` has always accepted `Response | { response, url, headers,
transformedBody }` — the bare arm is what the web/scraping executors return from their
error and passthrough paths, and `chatcore-upstream-timeouts.test.ts` already covers
that both shapes are handled. But `BaseExecutor.execute` has no explicit return type,
so TypeScript inferred it from the method's single `return` — the object shape alone.

Every override returning a bare `Response` was therefore reported as incompatible:

  * 14 × TS2739 in `duckduckgo-web.ts`, whose `execute()` additionally pinned its own
    signature to just the object shape while returning `errorResponse()` /
    `processResponse()` (both `Response`) from 14 valid paths
  * TS2416 in `felo-web.ts` and `gitlab.ts`, which declare `Promise<Response>`

Fix the declaration rather than the call sites: export `ExecutorExecuteResult` from
`base.ts` — the same union `normalizeExecutorResult()` accepts — and annotate
`BaseExecutor.execute` with it. `duckduckgo-web.ts` then drops its over-narrow
annotation, matching BaseExecutor and the ~38 other executors that let the return type
be inferred.

Two subclasses read `.response` straight off `super.execute()` and now narrow first:

  * `github.ts` — the existing `!result.response` guard already meant "bare Response,
    nothing to materialize"; it is now expressed as `result instanceof Response`, which
    is the same branch for every input (bare / object / nullish)
  * `pollinations.ts` — reads the status through both arms for its pool bookkeeping

Wrapping DuckDuckGo's 14 returns would have been the wrong fix: the values are already
correct, and `normalizeExecutorResult()` produces exactly `{ response, url: "",
headers: {}, transformedBody: null }` for them.

Validation: full tsc error-set diff against the base config — 335 -> 319, **zero new
errors** (line-number-agnostic diff is empty; the two `duckduckgo-web.ts` TS2345s that
appear to move are the same two pre-existing errors renumbered by added comments, and
are left for a later slice). `typecheck:core` clean, `check:type-coverage` 92.17% ->
94.17%, and 49 of the 50 existing test files importing a touched executor pass —
`plan3-p0.test.ts` fails identically with and without this change (it reads the
developer's real ~/.omniroute DB rather than a test-scoped DATA_DIR).

The new test pins the runtime behavior of the narrowing so a later simplification
cannot quietly drop the bare-Response arm.
2026-07-25 02:53:19 -03:00
backryun
3f2bf86c3c fix(sse): call the real abort-signal helper in the Gemini Business executor (#8485)
`gemini-business.ts` built its upstream fetch options with `combineAbortSignals(...)`,
which is defined nowhere in the repository. The module imports `mergeAbortSignals`
from `./base.ts` on line 31 and never used it — a rename that was only half applied.

Because the call sits inside the fetch options object literal, the ReferenceError was
thrown while *constructing* the arguments, before `fetch()` ran, and the surrounding
try/catch turned it into `makeErrorResult(502, "Gemini Business network error: ...")`.
So every Gemini Business request failed with what reads like an upstream outage. The
provider is registered and reachable (`open-sse/executors/index.ts`), so this affects
the whole provider, not an edge case.

`mergeAbortSignals(primary, secondary)` requires two real signals while
`ExecuteInput.signal` is `AbortSignal | null | undefined`, so the call is guarded and
falls back to the timeout alone — the same shape huggingchat, grok-web, claude-web,
and ninerouter already use.

Why it went unnoticed: this file is only type-checked by `open-sse/tsconfig.json`,
whose runs abort at `TS5101` (the deprecated `baseUrl`) before any file is checked,
and `typecheck:core` covers a curated 26-file allowlist that excludes every executor.
Removing that config error is #8473; this bug is what the first full run surfaced.

TDD: the two new tests fail on the parent commit — `execute()` never reaches the
stubbed `fetch` — and pass with the fix. They also cover the null-signal path, since
that is where an unguarded `mergeAbortSignals` would throw next.
2026-07-25 02:53:13 -03:00
backryun
8eebda13ca refactor(sse): resolve open-sse utils/translator type diagnostics for TS 7 (#8483)
First slice of the TypeScript 7 migration split requested on #7697: resolve the
type diagnostics under `open-sse/tsconfig.json` in the lowest-risk modules, with
no toolchain change. 12 diagnostics across 8 files, all outside the hot path —
`chatCore.ts` and `stream.ts` are deliberately left for a later, standalone slice.

Fixes, by cause:

* `Transformer.cancel` (progressTracker, sseHeartbeat, and stream.ts's existing
  handler) — the WHATWG Streams standard defines `transformer.cancel(reason)` and
  Node implements it (verified on v24: cancelling the readable side invokes it),
  but `lib.dom.d.ts` still omits it from `Transformer`, so every such handler was
  TS2353. These handlers clear the heartbeat/progress intervals when an SSE client
  disconnects, so deleting them to satisfy the checker would leak a timer per
  abandoned stream. The interface is patched in `open-sse/types.d.ts` instead.

* `earlyStreamKeepalive` — `SettledHandler` was discriminated by `ok: true | false`.
  This workspace compiles with `strictNullChecks: false`, where a boolean-literal
  discriminant narrows the positive branch but not the negative one, so reading
  `.error` off the rejected arm did not type-check (the two `.response` reads
  elsewhere in the file did, which is why only one site errored). Retagged with a
  string discriminant, which narrows both branches under the same settings.

* `toolCallShim` / `openai-responses` — assigning back to a property declared
  `unknown` resets the `typeof` narrowing, so the following comparison no longer
  saw a number/array. Both now read through a local. The `Read` limit clamp is
  behavior-identical: its two branches are mutually exclusive at READ_MAX_LIMIT 2000.

* `sanitizeToolResultId` — takes `unknown` but forwards to a `string` parameter; a
  non-string id previously reached `.replace()` and threw. Coerced instead.

* `openaiHelper` — `opts = {}` inferred `{}`; typed as `FilterToOpenAIFormatOptions`.

* `cursorAgentProtobuf` — `Buffer.alloc(0)` infers `Buffer<ArrayBuffer>` under
  @types/node 26 while the decoded field is `Buffer<ArrayBufferLike>`; the locals
  now use bare `Buffer`, matching `requestMetadata` a few lines above.

Validation: 335 -> 321 diagnostics with zero new errors (full tsc error-set diff
against the base config). typecheck:core clean, lint clean, check:type-coverage
92.17% -> 94.17%. All 114 existing test files that import a touched module pass;
`plan3-p0.test.ts` fails identically with and without this change (it reads the
developer's real ~/.omniroute DB instead of a test-scoped DATA_DIR).

The new test covers the three behavioral surfaces rather than the refactors the
existing keepalive/heartbeat suites already hold: that `transformer.cancel()`
really fires and can clear an interval, the id coercion, and the limit-clamp bounds.
2026-07-25 02:53:07 -03:00
backryun
1930b09c6a chore(sse): drop deprecated baseUrl from open-sse tsconfig for TS 7.0 (#8473)
TypeScript 6.x raises TS5101 on `open-sse/tsconfig.json`: `baseUrl` is
deprecated and stops functioning in TypeScript 7.0. It was paired with
`ignoreDeprecations: "5.0"`, which no longer silences it under TS 6 (the
compiler now demands "6.0").

Remove `baseUrl: ".."` and rewrite the `paths` mappings relative to the
tsconfig's own directory, which is how TypeScript resolves them with no
baseUrl set:

  "@/*"                     ./src/*       -> ../src/*
  "@omniroute/open-sse"     ./open-sse    -> ../open-sse
  "@omniroute/open-sse/*"   ./open-sse/*  -> ../open-sse/*

`ignoreDeprecations` goes with it — baseUrl was the only deprecated option
it was suppressing.

Verified by diffing the full tsc error set against the previous config (the
base run used `ignoreDeprecations: "6.0"` so compilation proceeds past the
config error, which otherwise aborts type-checking and masks everything):
zero new errors, 28 fewer. All 28 were in `electron/*.js`, which
`baseUrl: ".."` had been dragging into the open-sse program via
root-relative resolution. Scoping the program back to open-sse also moves
`check:type-coverage` from 92.17% to 94.01%; the ratchet direction is up so
the gate passes, and the baseline is deliberately left alone because the
gain is a measurement-scope change rather than new typing work.

The guard test asserts no tsconfig reintroduces `baseUrl` or
`ignoreDeprecations`, and that every `paths` target still resolves to a real
directory — the second half is the part that matters, since dropping baseUrl
silently changes what those mappings point at.
2026-07-25 02:53:01 -03:00
backryun
909642879f feat: add Claude Opus 5 support (#8464) 2026-07-25 02:52:55 -03:00
Markus Hartung
11f7ba2c72 fix(sse): record tool calls into shared state for openai->openai-responses call-log summary (#8462)
The openai-responses translator tracked tool calls in its own funcCallIds/
funcNames/funcArgsBuf bookkeeping without ever writing to the shared
state.toolCalls Map that stream.ts's completion-log summary builder reads.
Every openai->openai-responses translated stream with a tool call was
persisted with finish_reason "stop" and no tool_calls, even though the
actual SSE events sent to the client were correct.
2026-07-25 02:52:49 -03:00
Honey Tyagi
0d92be2117 test(e2e): contract test for the full provider journey (#8330) (#8444)
Add an end-to-end contract test that walks the whole provider journey as one gate: create provider (node) -> add connection -> sync models -> select in Combo -> Playground -> /v1/models exposure -> call via API key -> visible in Topology.

Every step asserts against the same derived contract identity (published model id / configured prefix / raw node id), so a divergence on any surface fails the suite. Directly guards the bugs tracked from Discussion #8273: compatible-provider model regex drift, /v1/models namespace/UUID incoherence (#8327), and Topology blind to custom providers (#8328/#3198).

The primary journey drives the real App Router route handlers + DB layer in-process against an isolated DATA_DIR, so it runs in CI under test:integration (collected by the top-level tests/integration/*.test.ts glob) as a blocking gate with no live server. A second, opt-in block runs the same journey over HTTP and self-skips unless RUN_CONTRACT_INT=1 (same convention as the RUN_SERVICES_INT suites).

Refs #8273. Reported-by: @nguyenha935
2026-07-25 02:52:03 -03:00
Prudhvi Vuda
88180d069a feat(providers): add missing opencode-go reasoning effort variants (#8441)
Register OpenCode Go registry effort aliases and EFFORT_TIERS rewrites so
clients can select declared reasoning levels through OmniRoute.

Closes #8353
2026-07-25 02:51:56 -03:00
MumuTW
7cc922ba97 fix(dashboard): preserve connection health visual on last routed topology node (#8428) 2026-07-25 02:51:49 -03:00
MumuTW
3432579eb0 fix(oauth): add devin-cli and agy entries to OAUTH_TEST_CONFIG (#8427) 2026-07-25 02:51:42 -03:00
MumuTW
4528fc455e fix(auth): exclude local CLI providers from tokenHealthCheck expiration (Fixes #8407) (#8426)
* fix(providers): prevent health sweep from expiring devin-cli local credentials

* fix(auth): drop devin-cli from supportsTokenRefresh explicit set (#8407)

Root cause: listing "devin-cli" as refresh-capable made tokenHealthCheck
force-expire local CLI connections that never have a refresh token.

Remove it from the explicit set (keep windsurf) and drop the health-check
provider hardcode — the existing supportsTokenRefresh=false guard is enough.
2026-07-25 02:51:35 -03:00
Diego Rodrigues de Sa e Souza
5d9ace6778 feat(providers): map upstream reasoning-level metadata in openai-compatible discovery (#8347) (#8363) 2026-07-25 02:51:15 -03:00
Jay Ongg
58ab8b1d2c Clicking a provider card hitting back loses scroll position (#8349)
* clicking a provider card hitting back loses scroll position

* clean up code

* Rename some funcitons

* minor UX updates

* add null-guard in highlight()

* Add providerCardHandle tests

* add highlight tests. refactored code into separate utility functions

* Minor change to skip a firstElementChild call - use a ref to access the Link inside ProviderCard.
2026-07-25 02:51:00 -03:00
Adrian Rogala
9a0e764459 feat(cli): replace ANTHROPIC_SMALL_FAST_MODEL with Fable default (#8343)
Claude Code retired ANTHROPIC_SMALL_FAST_MODEL; expose
ANTHROPIC_DEFAULT_FABLE_MODEL from the claude registry instead.
2026-07-25 02:50:53 -03:00
Diego Rodrigues de Sa e Souza
53a91b3df8 feat(api): quota-aware fallback routing for web-fetch providers (#8297) (#8335)
Mirror the search/route.ts pattern for /v1/web/fetch: skip rate-limited
stubs instead of letting them short-circuit auto-select, walk the
fixed-priority pool (fill-first) with a request-time fallback on
retryable/quota upstream statuses (429 always; 402/403 for
Firecrawl/Tavily/TinyFish quota-style tiers), and return a proper 429
(with Retry-After) when the whole pool is exhausted instead of a
generic 400. Explicit-provider requests never silently fall back.
2026-07-25 02:50:46 -03:00
Diego Rodrigues de Sa e Souza
b8901b6506 feat(db): persist caller session tag into call_logs for per-session cost attribution (#8249) (#8334) 2026-07-25 02:50:40 -03:00
Diego Rodrigues de Sa e Souza
1cafd328c7 fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388) (#8404) 2026-07-24 20:37:37 -03:00
Diego Rodrigues de Sa e Souza
09e9ecef97 fix(resilience): treat an unreachable-proxy ECONNREFUSED as a circuit-breaker event so combo fails over instead of hitting the 503 max-retry limit (#8376) (#8403) 2026-07-24 20:37:31 -03:00
Diego Rodrigues de Sa e Souza
1f58a29e9c fix(api): estimate inline base64 image tokens instead of counting the data URL as text so it does not falsely exceed the context window (#8368) (#8401) 2026-07-24 20:37:25 -03:00
Diego Rodrigues de Sa e Souza
73762b1b32 fix(backend): stop prompt-cache affinity from silently reordering an explicit priority combo across models (#8370) (#8400) 2026-07-24 20:37:18 -03:00
Diego Rodrigues de Sa e Souza
544ae2d3da fix(api): accept a missing status query-param on GET /api/plugins instead of rejecting null with Invalid status value (#8374) (#8399) 2026-07-24 20:37:12 -03:00
Diego Rodrigues de Sa e Souza
312e24e785 fix(plugins): fire registered+active plugin hooks (onRequest/onResponse/onError) during proxying instead of never invoking them (#8395) (#8449)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-24 20:36:59 -03:00
Diego Rodrigues de Sa e Souza
d7f9475864 fix(backend): make disabling the global per-key proxy toggle override existing per-key proxy assignments (#8385) (#8447)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-24 20:36:52 -03:00
Diego Rodrigues de Sa e Souza
b64361dd2c fix(resilience): cap the connection cooldown after a 429 burst so combo fallback is not blacked out past the real rate-limit window (#8396) (#8446)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-24 20:36:44 -03:00
Diego Rodrigues de Sa e Souza
9a78ea2225 fix(providers): stop marking a multi-quota-window provider exhausted when only some windows are depleted (LIMIT-200 snapshot eviction drops idle healthy windows) (#8431) (#8445)
Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
2026-07-24 20:36:37 -03:00
Diego Rodrigues de Sa e Souza
e8719783ef fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331) (#8356)
* fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331)

* fix(sse): scope #8331's usage-buffer fix around Claude-Code-compatible providers

The #8331 fix correctly stopped folding the 2000-token context-window safety
margin into client-visible prompt_tokens/input_tokens/total_tokens for normal
API metering clients. But it also silently changed the response shape for
Claude-Code-compatible providers, whose own context accounting reads the
buffered number straight out of usage — regressing
tests/unit/cc-compatible-provider.test.ts (expected 2007, got 7).

Fold the computed context_budget_* fields back into the visible usage fields
for that one path only (applyClientUsageBuffer's new
preserveContextBudgetInVisibleUsage option, gated on the existing
isClaudeCodeCompatible flag in chatCore.ts). Every other caller keeps the
real, unbuffered #8331 numbers.
2026-07-24 12:10:06 -03:00
Diego Rodrigues de Sa e Souza
9994e00763 refactor(sse): classify SSE critical-path empty catches + add CONTRIBUTING convention (#8142) (#8364) 2026-07-24 11:45:10 -03:00
Diego Rodrigues de Sa e Souza
9b7bd6e5d3 fix(api): stop leaking the internal provider UUID in /v1/models and honor the configured prefix (#8327) (#8361) 2026-07-24 11:45:02 -03:00
Diego Rodrigues de Sa e Souza
cbc7786533 fix(backend): keep combo routing from dispatching image requests to text-only targets (#8332) (#8360) 2026-07-24 11:44:54 -03:00
Diego Rodrigues de Sa e Souza
fa46d93941 fix(api): accept the current compatible-provider connection id scheme in the models test route (#8326) (#8359) 2026-07-24 11:44:46 -03:00
Diego Rodrigues de Sa e Souza
493708f575 fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350) (#8358) 2026-07-24 11:44:37 -03:00