* 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 #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 #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>
* fix(sse): label HTTP 499 disconnects as client_disconnected
Preserve caller-supplied error type/code in buildErrorBody so stream
abort classification is not overwritten by the status-code table.
* docs(security): document buildErrorBody classification arg
---------
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
* chore(release): open v3.8.18 development cycle
* fix(catalog): stop Codex CLI model-catalog refresh from erroring (#3481)
Codex's model-catalog refresh (codex_models_manager) does
GET /v1/models?client_version=<v> and decodes a JSON object with a
TOP-LEVEL `models` array. OmniRoute answers in the OpenAI-standard
`{object,data}` shape, so codex fails with "missing field `models`"
and logs "failed to refresh available models" on every startup.
Detect codex clients via the `originator` / `user-agent` = `codex_*`
headers they send and add an EMPTY top-level `models: []` so the decode
succeeds. Non-codex OpenAI clients keep the byte-identical `{object,data}`
response.
The array is intentionally empty: codex replaces its built-in per-model
agent prompt (`base_instructions`, ~21k chars) with whatever a populated
entry carries for the selected model, so emitting our catalog would drop
the agent prompt to nothing and break codex's agent behaviour (verified
empirically against codex 0.137). An empty list keeps codex on its
built-in model info — same inference as before, minus the error.
Validated end-to-end with the real handler against codex 0.137:
"failed to refresh available models" → 0 occurrences, instructions
preserved (built-in Codex agent prompt, not empty).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: ignore quality reports and local prompt artifacts
Add generated quality gate reports, metrics files, and local setup prompt
artifacts to .gitignore to prevent committing environment-specific or
temporary files.
* fix(provider): detect Responses API format when body has `input` but … (#3490)
Integrated into release/v3.8.18
* fix(sse): normalize numeric provider ids to strings (#3451)
Integrated into release/v3.8.18
* feat(browserPool): resolve Playwright proxy from proxy_registry DB (#3492)
Integrated into release/v3.8.18
* fix(theoldllm): generate X-Request-Token server-side, drop Playwright (#3491)
Integrated into release/v3.8.18
* feat(plugins): add lifecycle hooks and theme-manager plugin (#3473)
Integrated into release/v3.8.18
* fix(combo): parallel pre-screen + circuit-breaker fast-exit for priority combos (#3169)
Integrated into release/v3.8.18
* feat(ui): unifi active and finished requests into single view #1422 (#3401)
Integrated into release/v3.8.18
* docs(changelog): record #3401, #3473, #3492, #3490, #3451, #3491, #3169 under v3.8.18
* feat(docs): add doc accuracy gate + refresh AGENTS.md counts (#3510)
Integrated into release/v3.8.18
* fix(sse): drop empty-choices chunks without usage instead of injecting retry text (#3513)
PR #3422 ('allow OpenAI usage-only empty choices chunks') reintroduced the
assistant-content injection '[OmniRoute] Upstream returned an empty response.
Please retry.' for empty `choices: []` chunks that carry no valid usage. Clients
(Goose/opencode) feed that text back as a turn and spin in a retry loop -- the
exact regression #3400 had fixed by dropping the chunk.
Restore the drop behavior for the no-usage case while preserving #3422's
standards-compliant forwarding of usage-only `include_usage` final chunks.
Realign the mislabeled stream-utils test (it asserted the injection) and add a
dedicated regression guard.
Reported-by: @mochizzan
Refs: #3502, #3388, #3400, #3422
* fix(authz): fall back to URL token when Authorization isn't a usable Bearer (#3504)
Integrated into release/v3.8.18
* fix(playground): authenticate via session, test key policy by id (#3503)
Integrated into release/v3.8.18
* docs(changelog): record #3510, #3504, #3503 under v3.8.18
* fix: llama base url normalization (#3519)
* docs(changelog): reconcile v3.8.18 — add #3519, #3513, #3435-repair, gitignore chore (full commit↔changelog coverage)
* fix(opencode-plugin): bound regex quantifiers in normaliseFreeLabel (polynomial-ReDoS)
CodeQL js/polynomial-redos: unbounded \s* before an anchored \s*$ allowed
O(n²) backtracking on attacker-influenced display names. Bounded to {0,8}/{1,8}
(ample for any real label spacing). Plugin builds + 254 tests green.
* fix(types): restore clean typecheck:core for v3.8.18 release gate
- getPendingRequests() typed to real shape (was widened to object) → fixes
unknown 'count' in the unified-requests view (#3401)
- streamChunks log payload cast to its declared type (callLogs.ts)
- preScreenTargets aligned to canonical IsModelAvailable signature (#3169),
Promise.resolve-normalized so .catch never hits a bare boolean
All 5 gates green: lint(0 err) + typecheck:core + cycles + docs-all + unit + vitest(146).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Andrey Borodulin <borodulin@gmail.com>
Co-authored-by: Dmitrii Safronov <zimniy@cyberbrain.cc>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com>
Integrated into release/v3.8.8. Applied review fixes: moved the SELECT 1 into a pingDb() db helper (no raw SQL in route, Hard Rule #5) + the 503 catch no longer leaks err.message (Hard Rule #12). Thanks @herjarsa!