Skip to content

[v3.8.50] refactor(codex): isolate virtual quota pools - #8367

Merged
diegosouzapw merged 292 commits into
cheaper-inference:release/v3.8.50from
xz-dev:refactor/codex-virtual-account-pools
Aug 20, 2026
Merged

diegosouzapw merged 292 commits into
cheaper-inference:release/v3.8.50from
xz-dev:refactor/codex-virtual-account-pools

Conversation

@xz-dev

@xz-dev xz-dev commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Model one persisted Codex OAuth connection as a credential-owning parent with virtual codex and spark quota children.
  • Keep quota exhaustion and cooldown evidence scoped to the requested model family while preserving parent-level OAuth identity, credentials, health, concurrency, session affinity, usage, and operations.
  • Hydrate the scoped state across restarts and expose a credential-clean parent/child read model in the providers API and dashboard.
  • Expire persisted exhaustion at its reset time and clear only the observed child after a later non-exhausted quota response.

Why This Is A Higher-Level Fix

This work follows #4293, which was opened after we found that a Spark-specific Codex 429 could be written as connection-wide state and thereby block normal Codex models such as gpt-5.5. At that point we deliberately chose the more basic, lower-risk fix: classify normal Codex and Spark separately, keep the failover cooldown in a model-scoped map, and avoid writing rateLimitedUntil for the whole connection.

That fix addressed the immediate regression, but it did not give the system a first-class representation of the two quota pools. A later failover persistence path could still call the generic connection updater with lastError and errorCode for a model-specific 429, reintroducing the same state confusion even though the cooldown timestamp itself was scoped.

This PR is the follow-up at the account-model boundary. A narrow patch in the reactive failover path would stop only that one incorrect write; quota fetching, cooldown persistence, preflight selection, restart hydration, API projection, and recovery would still share the assumption that one credential row has one quota state. The persisted connection therefore remains the single credential-owning parent, while codex and spark become virtual quota children whose state is interpreted and updated consistently across every path.

Related Issues

Validation

  • npm run lint
  • npm run test:unit — the Codex-focused set passes 170/170; the full command is blocked by the unchanged upstream tests/unit/video-dashscope.test.ts baseline (6/6 failures at base 852bf4e0b and at this head).
  • npm run test:coverage — blocked by the same and additional unchanged upstream-base failures before coverage reporting; the exact base also exits 1 under the same dependency tree.
  • Coverage is still >= 60% for statements, lines, functions, and branches — no report is produced because the unchanged base test failures stop the coverage runner.
  • SonarQube PR analysis is green or any remaining issues are explicitly documented below — pending CI.

Additional validation:

  • npm run typecheck:core
  • npm run check:migration-numbering
  • Migration collision tests: 4/4 passed
  • Focused native Codex/account/restart/API tests: 170/170 passed
  • Focused React tests: 3/3 passed
  • npm run i18n:check-ui-coverage (all 42 non-English locales pass the 80% gate)
  • Full changed-file ESLint and git diff --check
  • Independent review: approved after stale-exhaustion remediation

Tests Added Or Updated

  • tests/integration/codex-account-pool-restart-http.test.ts
  • tests/integration/fixtures/codex-account-pool-restart-phase.ts
  • tests/unit/chatcore-codex-quota.test.ts (coverage moved into the scoped account-pool tests and this obsolete singleton test was removed)
  • tests/unit/chatcore-codex-account-pool.test.ts (focused suite extracted from chatcore-translation-paths.test.ts)
  • tests/unit/codex-account-cooldown-write.test.ts
  • tests/unit/codex-account-pool.test.ts
  • tests/unit/codex-executor-split.test.ts
  • tests/unit/codex-quota-selection-hydration.test.ts
  • tests/unit/observability-payloads.test.ts
  • tests/unit/providers-route-codex-account-pool.test.ts
  • tests/unit/request-defaults-store-session.test.ts
  • tests/unit/resilience-explain-codex-account.test.ts
  • tests/unit/sse-auth-codex-account-pool.test.ts (focused suite extracted from sse-auth.test.ts)
  • tests/unit/ui/codex-account-details.test.tsx
  • tests/unit/ui/connection-row-codex-account-pool.test.tsx

Coverage Notes

The behavioral suite covers model-to-child resolution, atomic sibling-preserving writes, header-derived exhaustion, fallback precedence, restart hydration in separate processes, scoped preflight/failover, parent-auth reuse, observability, sanitized API projection, dashboard rendering, and recovery after an exhausted window expires or a later successful quota observation arrives.

No implementation-shape assertions were added for the stale-exhaustion correction; those tests exercise persisted DB behavior and the public projection.

Reviewer Notes

  • No database migration or schema change. Scoped state remains in the existing provider_specific_data JSON on the one parent connection.
  • Virtual children use { parentConnectionId, scope }; they never create synthetic provider-connection IDs or rows.
  • Child rows are read-only details. Delete, relogin, proxy, enable/disable, and other operational controls remain on the parent.
  • The API projection is allowlisted after the normal provider response sanitization; nested credentials and token fields are not included.
  • All 43 locale catalogs retain their current wording and add the nine pool labels.
  • No workflow, dependency, generated build output, deployment-only, .pi, or PLAN files are included.

@xz-dev
xz-dev requested a review from diegosouzapw as a code owner July 24, 2026 05:06
@xz-dev
xz-dev force-pushed the refactor/codex-virtual-account-pools branch from dee5b29 to 25e0297 Compare July 25, 2026 03:21
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for this — the parent/virtual-child model for Codex quota pools is a clean way to close out the follow-up from #4293 without scattering scope-aware logic across chatCore, auth.ts, restart hydration, and the API layer separately. I ran the codex-focused suite plus the new UI and integration tests myself and they all pass; the design (allowlisted API projection, no DB migration, read-only virtual children) looks right.

One thing we need before merging: GitHub currently reports this PR as having merge conflicts against release/v3.8.49. I checked and it's entirely mechanical — 39 src/i18n/messages/*.json files where your branch and a few other in-flight PRs both added new keys right after the same line. Could you rebase onto the latest release/v3.8.49 and resolve those (keep both sets of keys)? That should also clear up a few of the CI reds you flagged as pre-existing — I traced them and they're actually caused by your branch's merge ref being stale relative to two other commits that landed on the base afterward (#8013/#8390), not anything in your diff; once rebased they should go green.

Everything else looks ready to me. Appreciate the thorough validation notes and test list in the PR description — made this much faster to verify.

@xz-dev
xz-dev force-pushed the refactor/codex-virtual-account-pools branch 3 times, most recently from 0aa2c26 to fbf4008 Compare July 25, 2026 05:20
@xz-dev

xz-dev commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up after rebasing onto current release/v3.8.49 and pushing head fbf40080a:

  • Fixed the only PR-specific quality-gate regression: src/sse/services/auth.ts exceeded its frozen size cap. Commit fbf40080a removes a redundant duplicate Codex cooldown candidate and keeps the formatted file at 2472 lines (cap 2475). No quality baseline was changed.
  • Revalidated: Codex-focused native tests 47/47, related UI tests 3/3, typecheck:core, changed-file ESLint, dead-code check, and diff check. Independent review found no semantic blocker.
  • The fresh GitHub run confirms auth.ts is no longer listed by check:file-size.

All checks are now terminal. Remaining reds reproduce on / are byte-identical to the exact current upstream base and are unrelated to this PR:

  1. File-size gate: four unchanged base files are already over their frozen caps: src/lib/tokenHealthCheck.ts, src/sse/handlers/chat.ts, open-sse/services/accountFallback.ts, and open-sse/services/combo.ts. The PR does not modify them.
  2. ESLint gate: lint:json exits 2 because the unchanged upstream suppression file contains stale suppressions (There are suppressions left that do not occur anymore). The suppression file is byte-identical to base; this PR does not change lint policy or suppressions.
  3. Unit shard 1: five unchanged chatcore-client-usage-buffer expectations see 0 calls instead of 1, plus unchanged thundering-herd expects 163840000 while current base clamps at 120000.
  4. Unit shard 2: two unchanged error-classification backoff expectations fail on the same current-base 120000 clamp.
  5. Unit shard 4: unchanged DB export rule reports upstream compressionDetailNormalizers; this PR's new Codex DB leaf is not the reported module.

Green on the same head: change classification, merge integrity, docs gates, Vitest, unit shard 3, DAST, Semgrep, and Semgrep Cloud. PR remains mergeable. Per scope, I did not alter unrelated upstream tests/files or reset any quality baseline.

@xz-dev

xz-dev commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@diegosouzapw Rebased onto the current release/v3.8.49 and resolved the 39 locale conflicts, preserving both sets of keys. GitHub now reports the PR as mergeable.

I also fixed the only PR-specific post-rebase quality-gate regression in fbf40080a (auth.ts file size) without changing any quality baseline. The focused Codex tests and relevant static checks pass. The remaining red jobs are unchanged current-base failures; the exact classification and evidence are recorded in #8367 (comment).

The branch is ready for another look.

@diegosouzapw diegosouzapw added the deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) label Jul 27, 2026
@diegosouzapw diegosouzapw changed the title refactor(codex): isolate virtual quota pools [v3.8.50] refactor(codex): isolate virtual quota pools Jul 27, 2026
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Heads-up: this PR is running against a stale base

release/v3.8.49 has moved 215 commits ahead of the base this PR was last built against, and several gate failures that were red on the older base have since been repaired on the branch itself:

Gate class Repaired by
No new ESLint warnings (exit 2 — orphaned suppressions, not a lint violation) #8706, #8831
Fast Quality Gates / check:file-size / Quality Ratchet #8585, #8767, plus the post-merge-train re-pins
Docs Gates (fast-path) (missing Polish API_REFERENCE) #8831
backoff-clamp + compressionDetailNormalizers unit tests #8706

I verified this on a clean checkout of the current tip (3b515d90b3): lint:json --max-warnings 0, check:file-size, check:docs-sync, error-classification.test.ts (24/24) and check-db-rules.test.ts (22/22) all pass with no changes.

Currently failing here:

  • Fast Quality Gates
  • No new ESLint warnings
  • Unit Tests fast-path (1/4)
  • Unit Tests fast-path (2/4)
  • Unit Tests fast-path (4/4)

Please update the branch (the Update branch button, or rebase onto origin/release/v3.8.49) and let CI re-run. That should clear any failure caused by the stale base.

Note this is not a promise that every red disappears — a failure specific to the changes in this PR will survive the rebase, and Build (advisory) is advisory and does not block. Rebasing just removes the noise so what is left is actually yours.

No action was taken on this PR beyond this comment.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.49 to release/v3.8.50 July 28, 2026 18:43
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Re-homed to release/v3.8.50: v3.8.49 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

@xz-dev
xz-dev force-pushed the refactor/codex-virtual-account-pools branch from fbf4008 to f6c491a Compare August 3, 2026 10:26
backryun and others added 15 commits August 16, 2026 00:14
…t_index allocation (cheaper-inference#10330)

* fix(sse): close the synthetic keepalive reasoning item's output_item

RESPONSES_STARTUP_THINKING_FRAME (the /v1/responses early-keepalive
placeholder for slow-starting reasoning models) opened a synthetic
"rs_keepalive" reasoning item at output_index 0 and closed its nested
summary part (response.reasoning_summary_part.done), but never sent
response.output_item.done to close the item itself. The comment
claimed it was "closed within this one frame" — that was true for the
part, not the item.

Since this placeholder has no real upstream counterpart (the real
response starts an independent response.created lifecycle later and
never touches it), nothing else ever closes it. A client tracking open
items by output_index (as the Responses API spec requires — this is
exactly what OpenClaw's parser does) sees index 0 still open when the
real response's own output_item.added later reuses that same index,
and throws a collision.

Live incident (2026-08-13, reliably reproducing by 2026-08-14): traced
via a live tcpdump capture on the OmniRoute-dev container's network
namespace, correlated against the OpenClaw gateway journal and 10
separate real request/response pairs (all wire-clean on the response
side, ruling out provider corruption). The failing request's own
outbound payload confirmed a replayed reasoning item without
encrypted_content feeding a continuation call; the response wire bytes
for that exact exchange showed rs_keepalive's output_item.added at
index 0, then response.created/response.in_progress arriving *after*
it, then a second output_item.added reusing index 0 for the real
reasoning item — never preceded by an output_item.done for
rs_keepalive. Reported upstream as OpenClaw issue #123342 before the
OmniRoute-side root cause was found.

Fix: emit response.output_item.done for the synthetic item, matching
its already-buffered summary text, right after the summary part closes
and before the frame ends.

Test plan:
- tests/unit/early-stream-keepalive.test.ts: updated the frame-shape
  test to assert the full 5-event closed sequence (added the missing
  output_item.done and its field assertions); confirmed it fails
  against pre-fix code (only 4 events) and passes after
- node --test tests/unit/early-stream-keepalive.test.ts,
  tests/unit/earlyStreamKeepalive.test.ts,
  tests/unit/keepalive-cleanup-8140.test.ts,
  tests/unit/chat-body-admission.test.ts: 58 passed, 2 pre-existing
  skips unrelated to this change (Node test runner
  ReadableStream-error-simulation limitation)
- tsgo --noEmit: clean on both touched files

* fix(sse): allocate the keepalive output_index from a stack, not a literal

Follow-up to 03f8345. That commit patched the specific symptom (added
the missing response.output_item.done). This commit fixes the class:
RESPONSES_STARTUP_THINKING_FRAME hardcoded output_index: 0 as a literal
across five hand-written events, which is exactly how the missing-close
bug happened in the first place — nothing enforced that every open got
a matching close, so it silently didn't for months.

ResponsesOutputIndexStack (open-sse/utils/responsesOutputIndexStack.ts)
makes that structural: open() allocates the next sequential index,
close() must name the index being closed and throws if it doesn't match
the stack's top, and assertAllClosed() throws if anything is still open.
The keepalive frame now calls assertAllClosed() at module load, so a
future regression of this exact shape fails at import/boot time instead
of shipping a malformed stream to production and surfacing days later
as a live incident.

Also adds tests/helpers/assertResponsesOutputIndexLifecycle.ts: a
reusable version of the same invariant for replaying a full SSE event
sequence (not just checking one frame's own shape), mirroring what a
real client's output-index tracker enforces. Existing coverage for this
bug class (responses-reasoning-close-before-message-466.test.ts) only
asserted it by hand for one specific emitter (the real translator); nothing
generic existed for a hand-rolled synthetic frame like this keepalive to
be checked against, which is why its own test could pass while the actual
downstream contract still failed. Wired into
early-stream-keepalive.test.ts, including a test that concatenates the
keepalive frame with a plausible real subsequent response and asserts no
collision — the scenario that actually reproduced live, not just the
frame's own internal shape.

Test plan:
- tests/unit/responses-output-index-stack.test.ts (new): open/close/
  assertAllClosed behavior, including the exact mismatch and
  never-closed shapes this incident hit
- tests/unit/early-stream-keepalive.test.ts: existing frame-shape test
  plus new collision-simulation test, both passing
- node --test across responses-output-index-stack, early-stream-keepalive,
  earlyStreamKeepalive, keepalive-cleanup-8140, chat-body-admission:
  65 passed, 2 pre-existing skips unrelated to this change
- tsgo --noEmit: clean on all touched files

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
…per-inference#10370)

* fix(monitoring): canonicalize provider aliases in health matrix

* fix(monitoring): canonicalize aliases in health autopilot

---------

Co-authored-by: tkgo11 <7.1800574e+07+tkgo11@users.noreply.github.com>
…r-inference#10372)

* fix(db): default debugMode to false in getSettings() defaults

Fresh installs (or installs missing the persisted debugMode key) ran in
debug mode, contradicting the documented opt-in toggle and flooding new
production installs with debug-level logs. Flip the default to false;
installs that persisted debugMode=true keep it — only the missing-key
path changes, no migration needed.

Fixes cheaper-inference#10312

* changelog: fragment for cheaper-inference#10372
…ence#10394)

Co-authored-by: DarkAngel <48388675+DarkEsteves@users.noreply.github.com>
…eaper-inference#10397)

* fix(sse): dedupe header-budget drop warns by drop-set fingerprint

The 768-byte forwarded-header budget drop path emitted a full warn (with
up to 20 dropped entries) on every SSE response whose headers exceeded the
budget. The dropped set is usually identical across responses from the same
upstream, so the repeats carried no new information — under Desktop
multi-stream use this buried real errors and added event-loop serialization
work.

Warn once per unique drop fingerprint (sorted dropped-header names, capped
at 1000 fingerprints) per process, then log at debug level.

Fixes cheaper-inference#10315

* changelog: fragment for cheaper-inference#10397
… account has a dedicated proxy (cheaper-inference#10402)

OpencodeExecutor and MimocodeExecutor rotated to the next account only on
HTTP 429. A network exception (timeout, connection refused/reset) on one
account instead propagated out of execute() and failed the whole request,
even when other accounts remained available.

Both executors now rotate on a network exception only when the failed
account has its own dedicated proxy (account.proxy !== null) — a dead
proxy is genuinely account-scoped, so rotating away from it is safe.
Accounts sharing the default egress (no proxy configured) trigger the
same cooldown and are skipped for the rest of the request once the shared
egress is known down, but a later account with its own dedicated proxy is
still tried normally — a throw on a proxy-less account no longer strands
a proxied account further in the rotation. This behavior is gated behind
NETWORK_ROTATION_SHARED_EGRESS_GUARD (Feature Flag, default on); disabled,
it reproduces the immediate-propagation behavior this fix started from.

The shared rotation mechanics (pickAccount/markCooldown/markSuccess) are
extracted into executors/accountRotation.ts, used by both executors —
they had independently implemented the same round-robin+cooldown
skeleton. This also fixes an identical, pre-existing bug in
MimocodeExecutor that predates this PR: its catch block called
markCooldown unconditionally on any throw, with no proxy check and no
warn log (a silent exception swallow on a path that influences the
result).

The cooldown formula for both the proxy and shared-egress cases reuses
the repo's already-established "transient, not clearly attributable"
constants (errorConfig.ts TRANSIENT_COOLDOWN_MS/COOLDOWN_MS.transientMax,
already used by accountFallback.ts for network-error classification)
instead of introducing a separate value.

MimocodeExecutor's network-error 502 body also now goes through
buildErrorBody()/sanitizeErrorMessage() instead of embedding the raw
caught error message directly (Hard Rule cheaper-inference#12), matching the sanitization
already used on its cheaper-inference#2101 malformed-request path.

Validated by TDD (Hard Rule cheaper-inference#18): tests/unit/account-rotation.test.ts
covers the shared module directly; opencode-proxy-rotation-4954.test.ts
and mimocode-executor.test.ts cover the proxy-configured rotation path,
the mixed-fleet case, the shared-egress single-network-call case, and the
NETWORK_ROTATION_SHARED_EGRESS_GUARD-disabled legacy path, for each
executor. tsc, lint, and the provider golden-path gates
(check:provider-consistency, check:provider-assets,
provider-translate-path-golden.test.ts) are clean on all touched files.

Co-authored-by: Max <maxmad64@gmail.com>
…-inference#10405)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@v4.37.4...v4.37.6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…heaper-inference#10406)

Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@f205ea1...5595cca)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…eaper-inference#10407)

Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@f205ea1...5595cca)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…cheaper-inference#10415)

* fix(guardrails): reroute zero-vision combos through the vision bridge

Named combos whose model targets all lack vision support are never
reroute-eligible: the bridge only attempts the describe path, and when
describing cannot run or fails the raw images stay in the payload and the
request dies in the combo capability filter with capability_mismatch.

getComboVisionBridgeDecision now returns a "no-vision" verdict for combos
with zero vision-capable targets, and preCall treats it as reroute-eligible
with the same credential guards as single text-only models, falling back to
describe only when no usable reroute target exists.

* chore(changelog): fragment for cheaper-inference#10415 vision bridge combo reroute

* fix(guardrails): extend allNull stub fallback to no-vision combos

Reviewer follow-up (cheaper-inference#10415): the allNull stub-text fallback at the end of
preCall only fired for comboVisionBridgeDecision === 'process'. In the
compound-failure case for a zero-vision combo — reroute target without
usable credentials AND every describe call failing — raw images were
preserved and the original capability_mismatch recurred, because a
no-vision combo has no target that can consume images.

Include 'no-vision' in the guard: stub text is strictly better than raw
bytes no combo target can consume. Adds a double-failure unit test.

* ci: re-run dast-smoke (Build CLI bundle runner timeout flake)

* fix(build): bound and retry the opencode-plugin npm install in prepublish

The plugin's node_modules is gitignored, so every fresh CI checkout runs a
full npm install inside @omniroute/opencode-plugin during build:cli. npm's
unbounded fetch retries turn a stalled registry CDN connection (the recurring
onnxruntime-class ETIMEDOUT flake) into a 20-30 minute hang — the DAST
'Build CLI bundle' step has been cancelled at the 30m cap repeatedly.

- Bound npm fetch: --fetch-timeout 60s, 2 retries with capped backoff — a
  stalled connection now fails fast instead of hanging the job.
- Retry the install up to 3 times with a 10s pause between attempts, so
  transient CDN failures recover in-build.

Net effect: the step either completes (network OK) or fails quickly with a
clear error (network down) — it can no longer eat the whole job budget.

* ci(dast): use existing npm-ci-retry action instead of bare npm ci

dast-smoke died at 'Run npm ci' with connect ETIMEDOUT to the
onnxruntime-node binary CDN (Microsoft 150.171.x.x) — the same
transient CDN flake class that has hit Vitest/Quality Gates before.
quality.yml already wraps npm ci in ./.github/actions/npm-ci-retry
(3 attempts, exponential backoff); dast-smoke was the one workflow
still using a bare install. Use the existing action for consistency.

* ci(quality): use the npm-ci-retry action on every install step

Fast Quality Gates failed on the recurring onnxruntime-node postinstall
ETIMEDOUT (Microsoft CDN 150.171.x.x) - the same transient flake that has
hit Vitest and dast-smoke today. Only the Build job used the retry action;
the other five jobs (Docs, Fast Quality Gates, Vitest, Unit Tests,
changelog) still ran a bare install and die on any CDN hiccup. Use the
existing retry action (3 attempts, exponential backoff) on every install
step for consistency.

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
…connection probe (cheaper-inference#10420)

* fix(antigravity): classify geo-blocked egress, exclude account, real connection probe

Google refuses the Cloud Code model API from unsupported egress locations
with 400 FAILED_PRECONDITION "User location is not supported for the API
use." Previously this surfaced as a cryptic "Antigravity upstream error
(400)", never excluded the account, and the dashboard connection test stayed
green because it only probed the (non-geo-restricted) OAuth userinfo endpoint.

- errorClassifier: new GEO_BLOCKED type + isGeoBlockedError detection
  (400/403 + location-not-supported wording); non-terminal classification.
- chatCore fallback: GEO_BLOCKED marks the connection and caches a 24h
  rate-limit-until exclusion so routing moves to other accounts instead of
  re-selecting the same one; never bans/expires the account.
- auth: GEO_BLOCKED joins the non-terminal group (no banned/expired state).
- antigravityUpstreamError: geo refusals carry an actionable message (egress
  location vs account problem, proxy-in-supported-region guidance).
- connection test: antigravity/agy now probe the REAL streamGenerateContent
  surface (buildProbe), so a green tick means the model path actually works
  and a geo-blocked egress shows red with a clear diagnosis.

* chore(changelog): fragment for cheaper-inference#10420 antigravity geo-block resilience

* chore(pr): drop prettier-version drift noise, keep only real hunks

The earlier format pass (local prettier differs from the repo's pinned
version) rewrapped unrelated lines in chatCore.ts and the provider test
route. Restore the base formatting and re-apply only the GEO_BLOCKED
fallback branch and the buildProbe connection-test changes.

* fix(antigravity): strip competing-agent system prompts (429 RESOURCE_EXHAUSTED)

Port decolua/9router b566b20, generalized: Antigravity flags system prompts
advertising competing agents ('You are a Claude agent, built on Anthropic's
Claude Agent SDK.' — Zed, Claude Code, etc.) and answers with a 429 quota
error. sanitizeAntigravityGeminiRequest now strips known competitor identity
sentences from systemInstruction.parts before dispatch; surrounding
instruction text is untouched and non-matching prompts pass through without
allocation.

* chore(changelog): cover competitive prompt strip in cheaper-inference#10420 fragment

* fix(antigravity): scope GEO_BLOCKED classification to Google AI surfaces

Address reviewer feedback: classifyProviderError is shared across every
provider, so a lookalike 'not available in your region' body from an
unrelated upstream must not receive the egress-fixable 24h exclusion
treatment. Gate GEO_BLOCKED behind isGeoBlockEligibleProvider, which
matches the surfaces that actually emit Google's regional-availability
refusal: Cloud Code / Gemini Code Assist (antigravity, agy, cloudcode*),
the Gemini Developer API (gemini, gemini-cli, vertex), plus a
registry-driven fallback on executor/format. Non-Google providers fall
through to their existing 400/403 classification (typically null for an
unclassified 400), so a permanent block still follows its own path.

* ci: re-run quality gates

Trigger a fresh CI run for the PR: the previous run's 'Vitest (fast-path)'
job failed in 'npm ci' because the onnxruntime-node postinstall could not
download its binary from the Microsoft CDN (connect ETIMEDOUT
150.171.109.118:443). No tests ran; no code changed in this commit.

* fix(antigravity): guard provider before registry lookup in geo-block gate

isGeoBlockEligibleProvider passes the raw provider (string | null | undefined)
to getRegistryEntry(provider: string), failing typecheck:core and the
ts7-diagnostics ratchet (TS2345 at errorClassifier.ts:166). Add an explicit
null guard; runtime behavior is unchanged — a falsy provider already resolved
to !entry -> false.

* ci: re-run quality gates (vitest npm ci onnxruntime CDN flake)

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
… envelope (cheaper-inference#10430)

* fix(usage): read Gemini usageMetadata out of the antigravity response envelope

Port decolua/9router#59d858b: antigravity/gemini-cli wrap non-streaming
payloads in { response: {...} }, so extractUsageFromResponse only saw the
top-level usageMetadata and every non-streaming antigravity request logged
zero usage (IN 0 | OUT 0) and zeroed usage-dashboard rows. Top-level
metadata keeps priority; OpenAI/Claude branches untouched.

* chore(changelog): fragment for cheaper-inference#10430 antigravity usage envelope

* ci: re-run dast-smoke (Build CLI bundle runner timeout flake)

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
dpozimski and others added 25 commits August 20, 2026 06:29
…through (cheaper-inference#10729)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…cks (cheaper-inference#10681) (cheaper-inference#10730)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…ken refresh (cheaper-inference#10732)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…e#10733)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…tats (cheaper-inference#10769)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…log (cheaper-inference#10770)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…nference#10690) (cheaper-inference#10772)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…cription provider (cheaper-inference#10773) (cheaper-inference#10774)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…inference#10775)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…e#7992) (cheaper-inference#10779)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…cheaper-inference#10781)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…-inference#10782)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…eaper-inference#10783)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…ocks (cheaper-inference#9692) (cheaper-inference#10791)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…e account (cheaper-inference#9708) (cheaper-inference#10792)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…heaper-inference#10734) (cheaper-inference#10793)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…heaper-inference#10017 control-line rules (cheaper-inference#10796)

Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
…llision

cheaper-inference#10722 (Token Kiosk) and cheaper-inference#10729 (Cursor) each independently bumped the
gateways/specialty-media APIKEY_PROVIDERS count by one in the same merge-train
batch; neither PR could see the other's bump, so the merged tree landed at 230
while the surviving hardcoded assertion still said 229.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…tics (cheaper-inference#10797)

Merged — the 5 pre-existing tests that broke from this PR's intentional 404→401 remap (single-model no-credentials) are now realigned to the new contract. Thanks!
…fallback and combo hops

cheaper-inference#10792 (cheaper-inference#9708) added a same-account retry for retryable 502/503/504/507 transport
failures, applied uniformly inside handleSingleModelChat. Two other paths call
into the same function recursively/iteratively and each carries its own
documented single-call guarantee that the retry silently broke:

- Emergency fallback (cheaper-inference#1731): exactly one hop to the free fallback model, no
  extra calls against an already-exhausted provider. The retry was doubling
  that call whenever the fallback model itself returned a transient-looking
  status.
- Combo routing: target-level fallback is the combo's own policy (next target,
  not same-account retry). The retry delayed that policy and could surface the
  wrong terminal status when a later combo/global-fallback hop threw.

Both regressions were already covered by existing tests in
chat-route-coverage.test.ts (asserting exact call counts / preserved status) —
confirmed red on the release tip before this fix, green after.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…inference#10826)

Passo 10 of the Radar go-live: publish the OmniRoute catalog export the Radar
server consumes to a stable URL, so the 1 GB-RAM private server pulls it via
RADAR_EXPORT_URL instead of depending on the deploy-time snapshot.

- scripts/release/radar-export.mjs: emits {geradoEm, budgets, totais, registry,
  provenance} from the catalog config modules. Provenance (sourceCommit,
  sourceRef, runUrl, generatedBy) is never fabricated — unknown fields stay null.
- .github/workflows/radar-export.yml: on main catalog changes / manual dispatch /
  weekly, generates the export and clobbers the stable 'radar-export-latest'
  release asset (gh release, GH_TOKEN — checkout persist-credentials:false).
- tests/unit/radar-export.test.mjs: consumer contract (budgets[] non-empty) +
  provenance null-when-unknown + GitHub-env reflection.

Stable URL for RADAR_EXPORT_URL:
https://github.com/diegosouzapw/OmniRoute/releases/download/radar-export-latest/export-omniroute.json

Re-baselines zizmorFindings 190->192 (+2 unpinned-uses @vn, the repo-wide
deliberate convention; artipacked auto-fixed).

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
@diegosouzapw
diegosouzapw merged commit f060117 into cheaper-inference:release/v3.8.50 Aug 20, 2026
5 checks passed
diegosouzapw pushed a commit that referenced this pull request Aug 20, 2026
…#10694)

Merged — locally validated (23/23 focused probe-isolation tests, typecheck:core clean, file-size/changelog gates green). Reconciled with today's #8367 (codexAccount module extraction, merged earlier): the persistCodexQuotaState closure this PR touched had been extracted into persistCodexChildQuotaResponse — applied the same probe-origin isolation guard (!shouldIsolateProbeFailures()) at its new call site instead of reintroducing the old inline closure. Thanks for closing this real gap!
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ence#8367)

Merged — locally validated (30/30 focused tests: chatcore-codex-account-pool, codex-account-cooldown-write, codex-account-pool, providers-route-codex-account-pool, resilience-explain-codex-account, sse-auth-codex-account-pool; typecheck:core clean; file-size/complexity/cognitive-complexity/changelog gates all green). Merges clean against the current release tip with zero conflicts. Great refactor — extracting persistCodexQuotaState out of chatCore.ts into a proper codexAccount/ module with virtual quota pool isolation is a solid improvement. Thanks!
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…cheaper-inference#10694)

Merged — locally validated (23/23 focused probe-isolation tests, typecheck:core clean, file-size/changelog gates green). Reconciled with today's cheaper-inference#8367 (codexAccount module extraction, merged earlier): the persistCodexQuotaState closure this PR touched had been extracted into persistCodexChildQuotaResponse — applied the same probe-origin isolation guard (!shouldIsolateProbeFailures()) at its new call site instead of reintroducing the old inline closure. Thanks for closing this real gap!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.