fix(antigravity): streaming passthrough for non-streaming clients - #7408
diegosouzapw merged 16 commits into
Conversation
…the default branch (diegosouzapw#7168)
… (queue_conditions alone are eligibility-only) (diegosouzapw#7179)
…uto_merge_conditions (rules-based path is EOL 2026-07-16) (diegosouzapw#7216)
…; free plan queue is serial) (diegosouzapw#7220)
…H-hosted build hang dequeued every attempt) (diegosouzapw#7225)
…copy hard-fails every PR (diegosouzapw#7341) main's copy of this test still does git I/O inside a unit test: const baseSrc = git(['show', 'origin/main:' + FILE]); Runners check out a shallow single ref, so origin/main does not resolve and the test dies with 'fatal: invalid object name origin/main'. Every PR into main fails Unit Tests (7/8) on it — today that is diegosouzapw#7313, diegosouzapw#7315, diegosouzapw#7316, diegosouzapw#7334, diegosouzapw#7336 and diegosouzapw#7337, six PRs red on a defect none of them introduced. diegosouzapw#7313 has no other red at all. release/v3.8.49 already carries a fix (2e42b8e, diegosouzapw#7174: try/catch, fetch origin/main on demand, t.skip() when unreachable), but it only reaches main at release time — so main stays broken for the whole cycle. Cherry-picking it would also import a new problem: PR Test Policy classifies t.skip() as a silenced assertion, which we watched it correctly catch on diegosouzapw#7300 today. This is the hermetic version instead (ported from diegosouzapw#7327, which does the same for the release branch): read the file straight off disk, compare against an empty base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point — and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the runner's checkout depth can break. The diegosouzapw#6634 regression stays covered: the guard's logic lives in SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left byte-identical. Co-authored-by: growab <nekron@icloud.com>
…bers (diegosouzapw#7347) main's ratchet had been failing --require-tighten on every PR: 11 metrics improved but the baseline was never tightened. Same class as the diegosouzapw#6634 selfref guard — an infra fix that lands only on the release branch leaves main red for the whole cycle, and every PR into main pays for it. Values are the merged-coverage numbers from a run on main itself (a local run measures ~68% vs CI's ~80%; the baseline's own note warns about that gap). Only the 11 coverage values change — gitleaks and semgrepFindings keep main's own state. No changelog fragment: diegosouzapw#7326 carries it on release/v3.8.49, and a second one here would double the entry at release time.
The SSE collection in collectStreamToResponse had a hardcoded 120 s timeout. Reasoning-heavy models like gemini-3.1-pro-high on large prompts (>30 KB) regularly exceed 120 s of generation time, causing the executor to return a synthetic 504 before the model finishes. Replace the hardcoded value with FETCH_TIMEOUT_MS (default 600 s, overridable via FETCH_TIMEOUT_MS env var), which is the standard upstream-request budget across all OmniRoute providers. Signed-off-by: Minxi Hou <houminxi@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive updates, including a new SKILLS_SANDBOX_RUNTIME configuration, improved CLI health monitoring, expanded i18n support with the addition of zh-TW, and various infrastructure enhancements such as improved container runtime detection and optimized CI/CD gates. The reviewer provided actionable feedback regarding a redundant configuration definition in .env.example and requested clarification on the naming consistency of the newly ignored directories in .gitignore.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # Container runtime override for skill sandboxing. | ||
| # Used by: src/lib/skills/sandbox.ts + src/lib/skills/containerProvider.ts | ||
| # Values: auto | docker | apple | wsl | orbstack | podman | ||
| # - auto: OS-aware auto-detect (apple/orbstack on macOS, wsl on Windows, podman on Linux) | ||
| # - apple: Apple Container (native OCI on macOS 26+) | ||
| # - wsl: WSL Container CLI (wslc.exe on Windows) | ||
| # - orbstack: OrbStack (high-perf Linux VM + docker shim on macOS) | ||
| # - podman: Podman (rootless, daemonless) | ||
| # - docker: Docker (default fallback) | ||
| SKILLS_SANDBOX_RUNTIME=auto |
There was a problem hiding this comment.
The SKILLS_SANDBOX_RUNTIME variable is defined here and again on lines 1851-1858. To avoid confusion and redundancy in the example configuration, it would be best to define it only once. I'd suggest keeping the definition under the more specific 24. SKILLS & SANDBOXING section and removing this one.
|
|
||
|
|
||
| # CI/local quality artifacts (eslint-results.json, etc.) | ||
| .artifacts/ |
There was a problem hiding this comment.
This change adds .artifacts/ to be ignored. However, line 235 already ignores _artifacts/. Is the new directory name with a leading dot (.) intentional, or is this a typo for _artifacts/? If both directories are used, it might be clearer to group them with a comment explaining the purpose of each. If it's a typo, this entry should be removed to avoid confusion.
46e0731 to
9b20c31
Compare
Address review feedback on diegosouzapw#7408: - Remove duplicate SKILLS_SANDBOX_RUNTIME from .env.example line 225 (kept under SKILLS & SANDBOXING section at line 1851) - Clarify .artifacts/ vs _artifacts/ in .gitignore Signed-off-by: Minxi Hou <houminxi@gmail.com>
|
Thanks for this — nice root-cause chase on the 120s ceiling and a genuinely good cleanup (the shared A few small housekeeping items I'll handle on our side before merge, no action needed from you:
This looks complementary to #7582 (different files, different antigravity bug) — both should land. |
When a client sends stream: false to the Antigravity executor (Gemini models), OmniRoute buffered the entire SSE stream before responding. Long-thinking models exceeded the 120s timeout. Remove hardcoded SSE_COLLECT_TIMEOUT_MS. Extract shared createCreditsExtractionTransform with 16KB buffer cap and abort handling for client disconnect. Add parseSSEToGeminiResponse for the non-streaming drain path. Fix hasGeminiTerminalFinishReason to check top-level candidates (no response wrapper). Add signal null guards for credits retry path. Return 499 on early abort instead of piping cancelled body. Also remove duplicate SKILLS_SANDBOX_RUNTIME from .env.example and clarify .artifacts/ vs _artifacts/ in .gitignore. Signed-off-by: Minxi Hou <houminxi@gmail.com>
f57f879 to
e33937e
Compare
…wned) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…size cap) diegosouzapw#7408 added the non-streaming SSE pass-through (createCreditsExtractionTransform plus its two call sites: the credits-retry path and the main non-streaming path) inline in antigravity.ts, growing it to 1806 lines. Combined with two other authorized PRs touching the same file (diegosouzapw#6979 +11, diegosouzapw#7290 +30), the projected total exceeds the frozen file-size gate (1813). Extract the new streaming-passthrough logic verbatim into open-sse/executors/antigravity/streamingPassthrough.ts (createCreditsExtractionTransform + a new buildSsePassthroughResult that deduplicates the two near-identical call sites), following the existing sseCollect.ts submodule pattern -- pure, no host state, no fetch/auth. antigravity.ts keeps a thin wrapper for createCreditsExtractionTransform (same public signature the existing unit tests import) that injects updateAntigravityRemainingCredits so the two modules don't import each other. No behavior change: same abort handling, same 499-on-early-disconnect, same 16KB credits sliding-window cap. antigravity.ts: 1806 -> 1693 lines (under the 1755 pre-PR baseline, with margin). New module: 176 lines (cap 800). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…wn file (file-size caps) Two remaining frozen file-size violations from diegosouzapw#7408, resolved by extraction/move with zero behavior or assert changes: - open-sse/handlers/sseParser.ts (979 > frozen 830): the PR appended parseSSEToGeminiResponse (+153, the Gemini buffered-SSE -> chat.completion parser). Moved verbatim to open-sse/handlers/sseParser/geminiResponse.ts, following the handlers submodule pattern (chatCore/, responseSanitizer/). sseParser.ts is now byte-identical to its pre-PR content (825 lines; PR delta 0). Importers (chatCore/nonStreamingSse.ts, tests) point at the new module. - tests/unit/executor-antigravity.test.ts (1058 > testFrozen 942): the PR's new streaming-passthrough tests moved verbatim (same tests, same asserts) to tests/unit/antigravity-streaming-passthrough.test.ts: the 3 createCreditsExtractionTransform tests plus the non-streaming passthrough drain test ("auto-retries short 429 ... collects SSE for non-stream clients"), which the PR rewired onto the new raw-SSE path. The frozen file drops to 888 lines (below its pre-PR 941). New files: geminiResponse.ts 156 lines, passthrough test 202 lines (caps 800). Also fixes the stale sseParser.ts path in collectStreamToResponse's deprecation note. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…xity gate executeOnce() (complexity 127, 436 lines) and parseSSEToGeminiResponse() (complexity 39, 117 lines) were both over the check-complexity.mjs gate (complexity>15, max-lines-per-function>80). Decomposed each into small named helpers, no behavior change: - geminiResponse.ts: split into pure per-concern functions (markdown shortcut, candidate-parts walk, finishReason, usageMetadata, final response assembly). - antigravity.ts: extracted the per-url-index attempt pipeline (runAntigravityAttempt, handleAntigravityRateLimit, tryResolveRetryFromErrorBody, shouldAutoRetryTransient) and moved the request/result-building helpers (send, credits-retry, embed-retry, non-streaming/streaming result builders) into a new antigravity/executeAttempt.ts submodule, mirroring the existing streamingPassthrough.ts/sseCollect.ts pattern. Also fixes the antigravity.ts file-size cap (was pushed to 2084 lines > 1813 frozen ceiling by the decomposition itself; now 1428). check-complexity.mjs: 2054 violations (baseline 2058) — net improvement. execute/executeOnce/parseSSEToGeminiResponse no longer appear with ruleId complexity or max-lines-per-function. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…ough Resolves conflict in open-sse/executors/antigravity.ts between this branch's streaming-passthrough decomposition and diegosouzapw#7290's fallback-chain decomposition (already merged into release/v3.8.49) — both sides added imports from the same new antigravity/ submodule files, kept both. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw#3786's Pro-family fallback-chain retry loop (execute()) calls executeOnce() per candidate and inspects result.response directly, expecting a synthesized chat.completion JSON body on success. The streaming-passthrough migration made ALL non-streaming (stream: false) responses a raw SSE pass-through instead, so a successful retry candidate's response.json() threw ("data: {...}" is not valid JSON) — breaking the fallback chain (tests/unit/agy-pro-fallback-chain-3786.test.ts, 3 of 13 red). Route non-streaming (stream: false) responses back through collectStreamToResponse (buffered collect-to-JSON), which already uses FETCH_TIMEOUT_MS with no hardcoded 120s ceiling, so long-thinking models are not penalized. Passthrough is reserved for actual streaming clients (stream: true), which was the PR's real target scenario. Extracted the branch into buildAntigravityAttemptResult() to keep runAntigravityAttempt under the 80-line ratchet cap. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…ough (restore merge parent lost in prior flatten) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
60580ff
into
diegosouzapw:release/v3.8.49
|
Merged into |
…guard Resolve conflict in src/mitm/server.cjs: release's standaloneRouting.cjs (multi-agent alias resolution, merged via diegosouzapw#7408's cycle) supersedes this PR's mitmRouteAlias-only routeAlias.cjs, so drop the latter and its now-orphaned unit test in favor of the release-side module. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…egosouzapw#7408) * chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (diegosouzapw#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (diegosouzapw#7179) * fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (diegosouzapw#7216) * fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (diegosouzapw#7220) * fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (diegosouzapw#7225) * test(ci): make the diegosouzapw#6634 selfref guard hermetic — main's copy hard-fails every PR (diegosouzapw#7341) main's copy of this test still does git I/O inside a unit test: const baseSrc = git(['show', 'origin/main:' + FILE]); Runners check out a shallow single ref, so origin/main does not resolve and the test dies with 'fatal: invalid object name origin/main'. Every PR into main fails Unit Tests (7/8) on it — today that is diegosouzapw#7313, diegosouzapw#7315, diegosouzapw#7316, diegosouzapw#7334, diegosouzapw#7336 and diegosouzapw#7337, six PRs red on a defect none of them introduced. diegosouzapw#7313 has no other red at all. release/v3.8.49 already carries a fix (8bbd411, diegosouzapw#7174: try/catch, fetch origin/main on demand, t.skip() when unreachable), but it only reaches main at release time — so main stays broken for the whole cycle. Cherry-picking it would also import a new problem: PR Test Policy classifies t.skip() as a silenced assertion, which we watched it correctly catch on diegosouzapw#7300 today. This is the hermetic version instead (ported from diegosouzapw#7327, which does the same for the release branch): read the file straight off disk, compare against an empty base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point — and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the runner's checkout depth can break. The diegosouzapw#6634 regression stays covered: the guard's logic lives in SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left byte-identical. Co-authored-by: growab <nekron@icloud.com> * chore(quality): tighten main's coverage baseline to the CI's real numbers (diegosouzapw#7347) main's ratchet had been failing --require-tighten on every PR: 11 metrics improved but the baseline was never tightened. Same class as the diegosouzapw#6634 selfref guard — an infra fix that lands only on the release branch leaves main red for the whole cycle, and every PR into main pays for it. Values are the merged-coverage numbers from a run on main itself (a local run measures ~68% vs CI's ~80%; the baseline's own note warns about that gap). Only the 11 coverage values change — gitleaks and semgrepFindings keep main's own state. No changelog fragment: diegosouzapw#7326 carries it on release/v3.8.49, and a second one here would double the entry at release time. * fix(antigravity): remove hardcoded 120s SSE collect timeout The SSE collection in collectStreamToResponse had a hardcoded 120 s timeout. Reasoning-heavy models like gemini-3.1-pro-high on large prompts (>30 KB) regularly exceed 120 s of generation time, causing the executor to return a synthetic 504 before the model finishes. Replace the hardcoded value with FETCH_TIMEOUT_MS (default 600 s, overridable via FETCH_TIMEOUT_MS env var), which is the standard upstream-request budget across all OmniRoute providers. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(antigravity): streaming passthrough for non-streaming clients When a client sends stream: false to the Antigravity executor (Gemini models), OmniRoute buffered the entire SSE stream before responding. Long-thinking models exceeded the 120s timeout. Remove hardcoded SSE_COLLECT_TIMEOUT_MS. Extract shared createCreditsExtractionTransform with 16KB buffer cap and abort handling for client disconnect. Add parseSSEToGeminiResponse for the non-streaming drain path. Fix hasGeminiTerminalFinishReason to check top-level candidates (no response wrapper). Add signal null guards for credits retry path. Return 499 on early abort instead of piping cancelled body. Also remove duplicate SKILLS_SANDBOX_RUNTIME from .env.example and clarify .artifacts/ vs _artifacts/ in .gitignore. Signed-off-by: Minxi Hou <houminxi@gmail.com> * refactor(antigravity): extract streaming passthrough to module (file-size cap) diegosouzapw#7408 added the non-streaming SSE pass-through (createCreditsExtractionTransform plus its two call sites: the credits-retry path and the main non-streaming path) inline in antigravity.ts, growing it to 1806 lines. Combined with two other authorized PRs touching the same file (diegosouzapw#6979 +11, diegosouzapw#7290 +30), the projected total exceeds the frozen file-size gate (1813). Extract the new streaming-passthrough logic verbatim into open-sse/executors/antigravity/streamingPassthrough.ts (createCreditsExtractionTransform + a new buildSsePassthroughResult that deduplicates the two near-identical call sites), following the existing sseCollect.ts submodule pattern -- pure, no host state, no fetch/auth. antigravity.ts keeps a thin wrapper for createCreditsExtractionTransform (same public signature the existing unit tests import) that injects updateAntigravityRemainingCredits so the two modules don't import each other. No behavior change: same abort handling, same 499-on-early-disconnect, same 16KB credits sliding-window cap. antigravity.ts: 1806 -> 1693 lines (under the 1755 pre-PR baseline, with margin). New module: 176 lines (cap 800). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): split incremental parser + move new tests to own file (file-size caps) Two remaining frozen file-size violations from diegosouzapw#7408, resolved by extraction/move with zero behavior or assert changes: - open-sse/handlers/sseParser.ts (979 > frozen 830): the PR appended parseSSEToGeminiResponse (+153, the Gemini buffered-SSE -> chat.completion parser). Moved verbatim to open-sse/handlers/sseParser/geminiResponse.ts, following the handlers submodule pattern (chatCore/, responseSanitizer/). sseParser.ts is now byte-identical to its pre-PR content (825 lines; PR delta 0). Importers (chatCore/nonStreamingSse.ts, tests) point at the new module. - tests/unit/executor-antigravity.test.ts (1058 > testFrozen 942): the PR's new streaming-passthrough tests moved verbatim (same tests, same asserts) to tests/unit/antigravity-streaming-passthrough.test.ts: the 3 createCreditsExtractionTransform tests plus the non-streaming passthrough drain test ("auto-retries short 429 ... collects SSE for non-stream clients"), which the PR rewired onto the new raw-SSE path. The frozen file drops to 888 lines (below its pre-PR 941). New files: geminiResponse.ts 156 lines, passthrough test 202 lines (caps 800). Also fixes the stale sseParser.ts path in collectStreamToResponse's deprecation note. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): decompose execute + gemini parser below complexity gate executeOnce() (complexity 127, 436 lines) and parseSSEToGeminiResponse() (complexity 39, 117 lines) were both over the check-complexity.mjs gate (complexity>15, max-lines-per-function>80). Decomposed each into small named helpers, no behavior change: - geminiResponse.ts: split into pure per-concern functions (markdown shortcut, candidate-parts walk, finishReason, usageMetadata, final response assembly). - antigravity.ts: extracted the per-url-index attempt pipeline (runAntigravityAttempt, handleAntigravityRateLimit, tryResolveRetryFromErrorBody, shouldAutoRetryTransient) and moved the request/result-building helpers (send, credits-retry, embed-retry, non-streaming/streaming result builders) into a new antigravity/executeAttempt.ts submodule, mirroring the existing streamingPassthrough.ts/sseCollect.ts pattern. Also fixes the antigravity.ts file-size cap (was pushed to 2084 lines > 1813 frozen ceiling by the decomposition itself; now 1428). check-complexity.mjs: 2054 violations (baseline 2058) — net improvement. execute/executeOnce/parseSSEToGeminiResponse no longer appear with ruleId complexity or max-lines-per-function. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * Merge branch 'release/v3.8.49' into fix/antigravity-streaming-passthrough Resolves conflict in open-sse/executors/antigravity.ts between this branch's streaming-passthrough decomposition and diegosouzapw#7290's fallback-chain decomposition (already merged into release/v3.8.49) — both sides added imports from the same new antigravity/ submodule files, kept both. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(antigravity): keep buffered JSON contract for non-streaming callers diegosouzapw#3786's Pro-family fallback-chain retry loop (execute()) calls executeOnce() per candidate and inspects result.response directly, expecting a synthesized chat.completion JSON body on success. The streaming-passthrough migration made ALL non-streaming (stream: false) responses a raw SSE pass-through instead, so a successful retry candidate's response.json() threw ("data: {...}" is not valid JSON) — breaking the fallback chain (tests/unit/agy-pro-fallback-chain-3786.test.ts, 3 of 13 red). Route non-streaming (stream: false) responses back through collectStreamToResponse (buffered collect-to-JSON), which already uses FETCH_TIMEOUT_MS with no hardcoded 120s ceiling, so long-thinking models are not penalized. Passthrough is reserved for actual streaming clients (stream: true), which was the PR's real target scenario. Extracted the branch into buildAntigravityAttemptResult() to keep runAntigravityAttempt under the 80-line ratchet cap. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: growab <nekron@icloud.com> Co-authored-by: HouMinXi <1000+HouMinXi@users.noreply.github.com> Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com>
…egosouzapw#7408) * chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (diegosouzapw#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (diegosouzapw#7179) * fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (diegosouzapw#7216) * fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (diegosouzapw#7220) * fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (diegosouzapw#7225) * test(ci): make the diegosouzapw#6634 selfref guard hermetic — main's copy hard-fails every PR (diegosouzapw#7341) main's copy of this test still does git I/O inside a unit test: const baseSrc = git(['show', 'origin/main:' + FILE]); Runners check out a shallow single ref, so origin/main does not resolve and the test dies with 'fatal: invalid object name origin/main'. Every PR into main fails Unit Tests (7/8) on it — today that is diegosouzapw#7313, diegosouzapw#7315, diegosouzapw#7316, diegosouzapw#7334, diegosouzapw#7336 and diegosouzapw#7337, six PRs red on a defect none of them introduced. diegosouzapw#7313 has no other red at all. release/v3.8.49 already carries a fix (83a7551, diegosouzapw#7174: try/catch, fetch origin/main on demand, t.skip() when unreachable), but it only reaches main at release time — so main stays broken for the whole cycle. Cherry-picking it would also import a new problem: PR Test Policy classifies t.skip() as a silenced assertion, which we watched it correctly catch on diegosouzapw#7300 today. This is the hermetic version instead (ported from diegosouzapw#7327, which does the same for the release branch): read the file straight off disk, compare against an empty base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point — and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the runner's checkout depth can break. The diegosouzapw#6634 regression stays covered: the guard's logic lives in SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left byte-identical. Co-authored-by: growab <nekron@icloud.com> * chore(quality): tighten main's coverage baseline to the CI's real numbers (diegosouzapw#7347) main's ratchet had been failing --require-tighten on every PR: 11 metrics improved but the baseline was never tightened. Same class as the diegosouzapw#6634 selfref guard — an infra fix that lands only on the release branch leaves main red for the whole cycle, and every PR into main pays for it. Values are the merged-coverage numbers from a run on main itself (a local run measures ~68% vs CI's ~80%; the baseline's own note warns about that gap). Only the 11 coverage values change — gitleaks and semgrepFindings keep main's own state. No changelog fragment: diegosouzapw#7326 carries it on release/v3.8.49, and a second one here would double the entry at release time. * fix(antigravity): remove hardcoded 120s SSE collect timeout The SSE collection in collectStreamToResponse had a hardcoded 120 s timeout. Reasoning-heavy models like gemini-3.1-pro-high on large prompts (>30 KB) regularly exceed 120 s of generation time, causing the executor to return a synthetic 504 before the model finishes. Replace the hardcoded value with FETCH_TIMEOUT_MS (default 600 s, overridable via FETCH_TIMEOUT_MS env var), which is the standard upstream-request budget across all OmniRoute providers. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(antigravity): streaming passthrough for non-streaming clients When a client sends stream: false to the Antigravity executor (Gemini models), OmniRoute buffered the entire SSE stream before responding. Long-thinking models exceeded the 120s timeout. Remove hardcoded SSE_COLLECT_TIMEOUT_MS. Extract shared createCreditsExtractionTransform with 16KB buffer cap and abort handling for client disconnect. Add parseSSEToGeminiResponse for the non-streaming drain path. Fix hasGeminiTerminalFinishReason to check top-level candidates (no response wrapper). Add signal null guards for credits retry path. Return 499 on early abort instead of piping cancelled body. Also remove duplicate SKILLS_SANDBOX_RUNTIME from .env.example and clarify .artifacts/ vs _artifacts/ in .gitignore. Signed-off-by: Minxi Hou <houminxi@gmail.com> * refactor(antigravity): extract streaming passthrough to module (file-size cap) diegosouzapw#7408 added the non-streaming SSE pass-through (createCreditsExtractionTransform plus its two call sites: the credits-retry path and the main non-streaming path) inline in antigravity.ts, growing it to 1806 lines. Combined with two other authorized PRs touching the same file (diegosouzapw#6979 +11, diegosouzapw#7290 +30), the projected total exceeds the frozen file-size gate (1813). Extract the new streaming-passthrough logic verbatim into open-sse/executors/antigravity/streamingPassthrough.ts (createCreditsExtractionTransform + a new buildSsePassthroughResult that deduplicates the two near-identical call sites), following the existing sseCollect.ts submodule pattern -- pure, no host state, no fetch/auth. antigravity.ts keeps a thin wrapper for createCreditsExtractionTransform (same public signature the existing unit tests import) that injects updateAntigravityRemainingCredits so the two modules don't import each other. No behavior change: same abort handling, same 499-on-early-disconnect, same 16KB credits sliding-window cap. antigravity.ts: 1806 -> 1693 lines (under the 1755 pre-PR baseline, with margin). New module: 176 lines (cap 800). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): split incremental parser + move new tests to own file (file-size caps) Two remaining frozen file-size violations from diegosouzapw#7408, resolved by extraction/move with zero behavior or assert changes: - open-sse/handlers/sseParser.ts (979 > frozen 830): the PR appended parseSSEToGeminiResponse (+153, the Gemini buffered-SSE -> chat.completion parser). Moved verbatim to open-sse/handlers/sseParser/geminiResponse.ts, following the handlers submodule pattern (chatCore/, responseSanitizer/). sseParser.ts is now byte-identical to its pre-PR content (825 lines; PR delta 0). Importers (chatCore/nonStreamingSse.ts, tests) point at the new module. - tests/unit/executor-antigravity.test.ts (1058 > testFrozen 942): the PR's new streaming-passthrough tests moved verbatim (same tests, same asserts) to tests/unit/antigravity-streaming-passthrough.test.ts: the 3 createCreditsExtractionTransform tests plus the non-streaming passthrough drain test ("auto-retries short 429 ... collects SSE for non-stream clients"), which the PR rewired onto the new raw-SSE path. The frozen file drops to 888 lines (below its pre-PR 941). New files: geminiResponse.ts 156 lines, passthrough test 202 lines (caps 800). Also fixes the stale sseParser.ts path in collectStreamToResponse's deprecation note. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): decompose execute + gemini parser below complexity gate executeOnce() (complexity 127, 436 lines) and parseSSEToGeminiResponse() (complexity 39, 117 lines) were both over the check-complexity.mjs gate (complexity>15, max-lines-per-function>80). Decomposed each into small named helpers, no behavior change: - geminiResponse.ts: split into pure per-concern functions (markdown shortcut, candidate-parts walk, finishReason, usageMetadata, final response assembly). - antigravity.ts: extracted the per-url-index attempt pipeline (runAntigravityAttempt, handleAntigravityRateLimit, tryResolveRetryFromErrorBody, shouldAutoRetryTransient) and moved the request/result-building helpers (send, credits-retry, embed-retry, non-streaming/streaming result builders) into a new antigravity/executeAttempt.ts submodule, mirroring the existing streamingPassthrough.ts/sseCollect.ts pattern. Also fixes the antigravity.ts file-size cap (was pushed to 2084 lines > 1813 frozen ceiling by the decomposition itself; now 1428). check-complexity.mjs: 2054 violations (baseline 2058) — net improvement. execute/executeOnce/parseSSEToGeminiResponse no longer appear with ruleId complexity or max-lines-per-function. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * Merge branch 'release/v3.8.49' into fix/antigravity-streaming-passthrough Resolves conflict in open-sse/executors/antigravity.ts between this branch's streaming-passthrough decomposition and diegosouzapw#7290's fallback-chain decomposition (already merged into release/v3.8.49) — both sides added imports from the same new antigravity/ submodule files, kept both. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(antigravity): keep buffered JSON contract for non-streaming callers diegosouzapw#3786's Pro-family fallback-chain retry loop (execute()) calls executeOnce() per candidate and inspects result.response directly, expecting a synthesized chat.completion JSON body on success. The streaming-passthrough migration made ALL non-streaming (stream: false) responses a raw SSE pass-through instead, so a successful retry candidate's response.json() threw ("data: {...}" is not valid JSON) — breaking the fallback chain (tests/unit/agy-pro-fallback-chain-3786.test.ts, 3 of 13 red). Route non-streaming (stream: false) responses back through collectStreamToResponse (buffered collect-to-JSON), which already uses FETCH_TIMEOUT_MS with no hardcoded 120s ceiling, so long-thinking models are not penalized. Passthrough is reserved for actual streaming clients (stream: true), which was the PR's real target scenario. Extracted the branch into buildAntigravityAttemptResult() to keep runAntigravityAttempt under the 80-line ratchet cap. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: growab <nekron@icloud.com> Co-authored-by: HouMinXi <1000+HouMinXi@users.noreply.github.com> Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com>
Problem
When a client sends
stream: falseto the Antigravity executor (Geminimodels), OmniRoute buffered the entire SSE stream before responding.
Gemini 3.1 Pro High and similar models can take 2+ minutes to finish
thinking, which hit the 120-second
SSE_COLLECT_TIMEOUT_MSand returned504 Gateway Timeout.
Root cause
antigravity.tshadSSE_COLLECT_TIMEOUT_MS = 120000(2 min). Forstreaming clients this was fine -- chunks arrive continuously. But
non-streaming clients waited for the full response, and long-thinking
models exceeded the cap.
Fix
Remove the hardcoded timeout (244ba3e): let chatCore's
non-streaming drain handle Gemini SSE directly instead of buffering
in antigravity.ts.
Streaming passthrough (c727928): extract shared
createCreditsExtractionTransform()from 3 near-identicalTransformStream copies. Apply 16KB buffer cap to the non-streaming
path.
parseSSEToGeminiResponse (46e0731): new SSE parser for Gemini
format (candidate parts, markdown, finishReason, usageMetadata).
chatCore's non-streaming path uses this to drain Gemini SSE without
buffering the entire stream.
Testing
57/57 unit tests pass. Verified with gemini-3.1-pro-high that
responses >2 minutes no longer timeout.