Repository navigation
[v3.8.50] refactor(codex): isolate virtual quota pools - #8367
diegosouzapw merged 292 commits into
Conversation
dee5b29 to
25e0297
Compare
|
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. |
0aa2c26 to
fbf4008
Compare
|
CI follow-up after rebasing onto current
All checks are now terminal. Remaining reds reproduce on / are byte-identical to the exact current upstream base and are unrelated to this PR:
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. |
|
@diegosouzapw Rebased onto the current I also fixed the only PR-specific post-rebase quality-gate regression in The branch is ready for another look. |
Heads-up: this PR is running against a stale base
I verified this on a clean checkout of the current tip ( Currently failing here:
Please update the branch (the Update branch button, or rebase onto Note this is not a promise that every red disappears — a failure specific to the changes in this PR will survive the rebase, and No action was taken on this PR beyond this comment. |
|
Re-homed to |
fbf4008 to
f6c491a
Compare
…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>
…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>
f060117
into
cheaper-inference:release/v3.8.50
…#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!
…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!
…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!
Summary
codexandsparkquota children.Why This Is A Higher-Level Fix
This work follows #4293, which was opened after we found that a Spark-specific Codex
429could be written as connection-wide state and thereby block normal Codex models such asgpt-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 writingrateLimitedUntilfor 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
lastErroranderrorCodefor a model-specific429, 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
codexandsparkbecome virtual quota children whose state is interpreted and updated consistently across every path.Related Issues
Validation
npm run lintnpm run test:unit— the Codex-focused set passes 170/170; the full command is blocked by the unchanged upstreamtests/unit/video-dashscope.test.tsbaseline (6/6 failures at base852bf4e0band 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.>= 60%for statements, lines, functions, and branches — no report is produced because the unchanged base test failures stop the coverage runner.Additional validation:
npm run typecheck:corenpm run check:migration-numberingnpm run i18n:check-ui-coverage(all 42 non-English locales pass the 80% gate)git diff --checkTests Added Or Updated
tests/integration/codex-account-pool-restart-http.test.tstests/integration/fixtures/codex-account-pool-restart-phase.tstests/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 fromchatcore-translation-paths.test.ts)tests/unit/codex-account-cooldown-write.test.tstests/unit/codex-account-pool.test.tstests/unit/codex-executor-split.test.tstests/unit/codex-quota-selection-hydration.test.tstests/unit/observability-payloads.test.tstests/unit/providers-route-codex-account-pool.test.tstests/unit/request-defaults-store-session.test.tstests/unit/resilience-explain-codex-account.test.tstests/unit/sse-auth-codex-account-pool.test.ts(focused suite extracted fromsse-auth.test.ts)tests/unit/ui/codex-account-details.test.tsxtests/unit/ui/connection-row-codex-account-pool.test.tsxCoverage 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
provider_specific_dataJSON on the one parent connection.{ parentConnectionId, scope }; they never create synthetic provider-connection IDs or rows..pi, orPLANfiles are included.