Skip to content

fix(antigravity): bootstrap project via loadCodeAssist + fetchAvailableModels fallback - #2219

Merged
diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.0from
NomenAK:fix/antigravity-discovery-loadcodeassist-2026-05-13
May 14, 2026
Merged

diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.0from
NomenAK:fix/antigravity-discovery-loadcodeassist-2026-05-13

Conversation

@NomenAK

@NomenAK NomenAK commented May 13, 2026

Copy link
Copy Markdown
Contributor

What

Bootstrap Antigravity project assignment via :loadCodeAssist before model discovery, and prefer /v1internal:fetchAvailableModels over /v1internal:models (which 404s for free-tier accounts).

Why

Two root causes were observed when antigravity discovery 404'd on every container boot:

  1. /v1internal:models requires a prior /v1internal:loadCodeAssist call to assign a project id to the OAuth token. Without that bootstrap, the server has no context and returns 404 for all three base URLs (prod, daily, sandbox).
  2. Even with the bootstrap, /v1internal:models is dead for free-tier accounts. The correct endpoint is /v1internal:fetchAvailableModels, which returns 200 with the full model list.

Both gaps confirmed by direct probing with a live token.

How

  • New open-sse/services/antigravityProjectBootstrap.ts (~129 lines): ensureAntigravityProjectAssigned(accessToken, opts?) — idempotent loadCodeAssist call with per-token memoization, 8s timeout per URL, tries all 3 base URLs, non-fatal on failure
  • Modify src/app/api/providers/[id]/models/route.ts:
    • Call ensureAntigravityProjectAssigned(accessToken) immediately after resolveAntigravityVersion()
    • Discovery loop iterates [...fetchAvailableModelsUrls, ...modelsDiscoveryUrls] — fetchAvailableModels first, :models kept as paid-tier fallback
  • 9 new unit tests in tests/unit/antigravity-discovery-bootstrap.test.ts: memoization, fallthrough, non-fatal errors, auth headers, ordering, etc.
  • Exports clearAntigravityProjectCache and getAntigravityProjectFromCache test helpers

Notes

The fix keeps :models in the fallback list because paid-tier accounts may still use it — happy to remove it entirely if you prefer the cleaner code path. Also happy to revise the cache eviction strategy or the timeout values if they don't fit upstream defaults.

…tchAvailableModels fallback (#12)

* fix(antigravity): bootstrap project assignment via loadCodeAssist before discovery

All three antigravity discovery endpoints (prod, daily, sandbox) were
404-ing because :models requires a prior :loadCodeAssist call to
assign a project id to the OAuth token. Without that bootstrap step,
the server has no context for the request and returns 404.

Adds open-sse/services/antigravityProjectBootstrap.ts with an idempotent
ensureAntigravityProjectAssigned() helper that calls :loadCodeAssist
before the first :models request for each access token, with per-token
memoization to avoid repeated bootstraps across concurrent or sequential
discovery calls. Falls through all three base URLs on failure (non-fatal).

Wired into fetchAntigravityDiscoveryModelsCached() in the models route
immediately after resolveAntigravityVersion() and before the discovery
URL loop.

Mirrors the loadCodeAssist flow already used in:
- src/lib/oauth/services/antigravity.ts (OAuth connect flow)
- open-sse/executors/gemini-cli.ts (Gemini CLI project refresh)

Fixes: 3x antigravity discovery 404s on container start, 2026-05-13.

* fix(antigravity): try fetchAvailableModels before models in discovery loop

The /v1internal:models endpoint returns 404 for free-tier accounts while
/v1internal:fetchAvailableModels returns 200 with the full model list.
The normalizer already handles the fetchAvailableModels response format
(object map keyed by model id). Adding fetchAvailableModels URLs first
in the discovery loop ensures successful discovery for free-tier accounts,
with models URLs retained as fallback for accounts that may support them.

---------

Co-authored-by: OmniRoute Ops <ops@nomenak.dev>
@NomenAK
NomenAK requested a review from diegosouzapw as a code owner May 13, 2026 12:25

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a bootstrap mechanism for the Google Cloud Code Assist API to ensure a project context is assigned to OAuth tokens before model discovery. It adds a new service, antigravityProjectBootstrap.ts, which handles memoized project assignment and integrates this step into the model discovery route. Comprehensive unit tests were also added to verify the bootstrap logic and execution order. Review feedback recommends using AbortSignal.timeout() for more concise timeout management and adopting the Headers constructor in tests to improve type safety when accessing request headers.

Comment on lines +47 to +89
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), BOOTSTRAP_TIMEOUT_MS);
try {
const response = await fetchImpl(url, {
method: "POST",
headers: getAntigravityHeaders("loadCodeAssist", accessToken),
body: JSON.stringify({ metadata: getAntigravityLoadCodeAssistMetadata() }),
signal: controller.signal,
});

if (!response.ok) {
console.warn(
`[models] antigravity loadCodeAssist failed at ${url} (${response.status}) — trying next`
);
continue;
}

const data = (await response.json()) as Record<string, unknown>;

// cloudaicompanionProject may be a plain string or an object with an id field.
const raw = data.cloudaicompanionProject;
let projectId =
typeof raw === "string"
? raw.trim()
: raw &&
typeof raw === "object" &&
typeof (raw as Record<string, unknown>).id === "string"
? ((raw as Record<string, unknown>).id as string).trim()
: "";

if (projectId) {
return projectId;
}

console.warn(
`[models] antigravity loadCodeAssist at ${url} returned no project id — trying next`
);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
console.warn(`[models] antigravity loadCodeAssist threw for ${url}: ${msg} — trying next`);
} finally {
clearTimeout(timeoutId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved readability and to leverage modern platform APIs, you can simplify the timeout logic by using AbortSignal.timeout(). This avoids the need for manual setTimeout and clearTimeout management, making the code more concise and less error-prone.

    try {
      const response = await fetchImpl(url, {
        method: "POST",
        headers: getAntigravityHeaders("loadCodeAssist", accessToken),
        body: JSON.stringify({ metadata: getAntigravityLoadCodeAssistMetadata() }),
        signal: AbortSignal.timeout(BOOTSTRAP_TIMEOUT_MS),
      });

      if (!response.ok) {
        console.warn(
          `[models] antigravity loadCodeAssist failed at ${url} (${response.status}) — trying next`
        );
        continue;
      }

      const data = (await response.json()) as Record<string, unknown>;

      // cloudaicompanionProject may be a plain string or an object with an id field.
      const raw = data.cloudaicompanionProject;
      let projectId =
        typeof raw === "string"
          ? raw.trim()
          : raw &&
              typeof raw === "object" &&
              typeof (raw as Record<string, unknown>).id === "string"
            ? ((raw as Record<string, unknown>).id as string).trim()
            : "";

      if (projectId) {
        return projectId;
      }

      console.warn(
        `[models] antigravity loadCodeAssist at ${url} returned no project id — trying next`
      );
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      console.warn(`[models] antigravity loadCodeAssist threw for ${url}: ${msg} — trying next`);
    }

@NomenAK NomenAK May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in 950aab2 (pushed after this review) — swapped the manual setTimeout / clearTimeout for AbortSignal.timeout(8_000). Thanks for the suggestion.

const calledFor: string[] = [];

const mockFetch = async (url: string, init?: RequestInit): Promise<Response> => {
const auth = (init?.headers as Record<string, string> | undefined)?.Authorization ?? "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type assertion as Record<string, string> is not entirely safe because init.headers can have other types (e.g., a Headers object or string[][]). Using the Headers constructor is a more robust and type-safe way to parse the headers.

Suggested change
const auth = (init?.headers as Record<string, string> | undefined)?.Authorization ?? "";
const auth = new Headers(init?.headers).get("Authorization") ?? "";

@NomenAK NomenAK May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in 950aab2 — both headers assertions in the test now use new Headers(init.headers) and .get(...) instead of the unsafe Record<string, string> cast. Thanks.

let capturedAuth: string | null = null;

const mockFetch = async (_url: string, init?: RequestInit): Promise<Response> => {
capturedAuth = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve type safety and robustness, it's better to use the Headers constructor to parse the headers rather than relying on a potentially unsafe type assertion. This ensures the code correctly handles all possible types for init.headers.

Suggested change
capturedAuth = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;
capturedAuth = new Headers(init?.headers).get("Authorization") ?? null;

@NomenAK NomenAK May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix in 950aab2 — second Headers ctor swap applied at the same time. Thanks for the pair of catches.

…r per review

Apply gemini-code-assist suggestions from PR diegosouzapw#2219:
- antigravityProjectBootstrap.ts: replace manual AbortController +
  setTimeout/clearTimeout pair with AbortSignal.timeout() — drops the
  finally block.
- antigravity-discovery-bootstrap.test.ts: parse Authorization via
  new Headers(init?.headers).get() instead of unsafe Record cast.

All 9 tests in tests/unit/antigravity-discovery-bootstrap.test.ts still pass.
@NomenAK

NomenAK commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

All 3 suggestions applied in 950aab2:

  • AbortSignal.timeout() replaces the manual AbortController + setTimeout/clearTimeout pair (drops the finally block).
  • Both test sites use new Headers(init?.headers).get("Authorization") instead of the Record cast.

Tests still pass (9/9 in antigravity-discovery-bootstrap.test.ts). Thanks for the review.

@diegosouzapw
diegosouzapw merged commit bbdcb97 into diegosouzapw:release/v3.8.0 May 14, 2026
2 checks passed
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @NomenAK! Your contribution has been integrated into release/v3.8.0 and will ship in the upcoming release.

The PR branch was synced with the latest release/v3.8.0 (no conflicts) and squash-merged. The loadCodeAssist bootstrap + fetchAvailableModels fallback fix the antigravity model discovery flow nicely.

Reviewed and merged via the /review-prs-cc workflow.

diegosouzapw added a commit that referenced this pull request May 14, 2026
After merging PRs #2221 (ModelSync shared loopback readiness gate + IPv4 force)
and #2219 (Antigravity loadCodeAssist bootstrap + fetchAvailableModels fallback)
into release/v3.8.0, two test suites needed updates to match the new routing:

- tests/unit/model-sync-route.test.ts:
  * resetStorage() now calls __resetLoopbackReadinessForTests() so the
    module-level __loopbackReadyPromise cache does not leak between tests.
  * Every fetch mock now answers the /__readiness_probe__/ URL with 404 so
    the gate opens immediately (any HTTP response satisfies the probe).
  * Self-fetch target URL assertions updated from http://localhost/...
    to http://127.0.0.1:20128/... per PR #2221's IPv4-force.
- tests/unit/provider-models-route.test.ts:
  * The Antigravity discovery-retry test now treats loadCodeAssist calls as
    non-fatal failures so the discovery path is still exercised.
  * The expected discovery URL sequence is updated to the new
    fetchAvailableModels-first order introduced by PR #2219.
diegosouzapw added a commit that referenced this pull request May 14, 2026
Deep audit of all 320 commits since v3.7.9 found:
- 18 merged PRs not documented in CHANGELOG (4 features, 10 bug fixes, 1 security, 2 chores, 1 debug improvement)
- 3 contributors entirely missing from credits table (@NomenAK with 12 PRs, @kang-heewon, @one-vs)
- 4 existing contributors with inaccurate PR counts (@oyi77 8→12, @ddarkr 2→3, @andrewmunsell 2→3, @nickwizard 2→3)

New entries added:
- feat: #2135 (1proxy settings), #2227 (antigravity project ID), #2238 (Z.AI Search), #2240 (CLI Suite)
- fix: #2217, #2218, #2219, #2221, #2222, #2223, #2224, #2231, #2233, #2236, #2242, #2243
- security: #2209 (stack trace exposure)
- chore: #2228, #2234

Total contributors updated from 50+ to 55+.
rafaumeu added a commit to rafaumeu/OmniRoute that referenced this pull request Jul 20, 2026
…uzapw#5193 regression of diegosouzapw#2541)

PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by
if (projectId), which never fires when projectId is empty — the exact case
that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22.

Add an else-if branch that attempts onboarding inline (bounded by
AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist
to discover the newly created project.

- Existing accounts with projectId: unchanged (fire-and-forget)
- New accounts without projectId: now onboarded within login flow
- Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist)
- Graceful degradation: if onboarding fails, lazy retry handles it

Tests:
- 3 new tests covering empty-projectId onboarding path (RED→GREEN)
- Existing 2 tests preserved and passing
- Adjusted timeout assertion for the stall test (now includes onboardUser stall)
- 5/5 passing on Node 24

Fixes diegosouzapw#7814
Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219
diegosouzapw pushed a commit to rafaumeu/OmniRoute that referenced this pull request Jul 20, 2026
…uzapw#5193 regression of diegosouzapw#2541)

PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by
if (projectId), which never fires when projectId is empty — the exact case
that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22.

Add an else-if branch that attempts onboarding inline (bounded by
AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist
to discover the newly created project.

- Existing accounts with projectId: unchanged (fire-and-forget)
- New accounts without projectId: now onboarded within login flow
- Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist)
- Graceful degradation: if onboarding fails, lazy retry handles it

Tests:
- 3 new tests covering empty-projectId onboarding path (RED→GREEN)
- Existing 2 tests preserved and passing
- Adjusted timeout assertion for the stall test (now includes onboardUser stall)
- 5/5 passing on Node 24

Fixes diegosouzapw#7814
Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219
diegosouzapw added a commit that referenced this pull request Jul 20, 2026
…egression of #2541) (#7815)

* fix(antigravity): attempt onboarding when projectId is empty (#5193 regression of #2541)

PR #5193 changed onboarding from inline await to fire-and-forget gated by
if (projectId), which never fires when projectId is empty — the exact case
that needs onboarding. This re-introduced the #2541 catch-22.

Add an else-if branch that attempts onboarding inline (bounded by
AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist
to discover the newly created project.

- Existing accounts with projectId: unchanged (fire-and-forget)
- New accounts without projectId: now onboarded within login flow
- Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist)
- Graceful degradation: if onboarding fails, lazy retry handles it

Tests:
- 3 new tests covering empty-projectId onboarding path (RED→GREEN)
- Existing 2 tests preserved and passing
- Adjusted timeout assertion for the stall test (now includes onboardUser stall)
- 5/5 passing on Node 24

Fixes #7814
Related: #5193, #2569, #2541, #2219

* docs(changelog): add fragment for antigravity onboarding empty-projectId fix (#7814)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: Rafael Dias Zendron <rafaumeu@users.noreply.github.com>
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
After merging PRs diegosouzapw#2221 (ModelSync shared loopback readiness gate + IPv4 force)
and diegosouzapw#2219 (Antigravity loadCodeAssist bootstrap + fetchAvailableModels fallback)
into release/v3.8.0, two test suites needed updates to match the new routing:

- tests/unit/model-sync-route.test.ts:
  * resetStorage() now calls __resetLoopbackReadinessForTests() so the
    module-level __loopbackReadyPromise cache does not leak between tests.
  * Every fetch mock now answers the /__readiness_probe__/ URL with 404 so
    the gate opens immediately (any HTTP response satisfies the probe).
  * Self-fetch target URL assertions updated from http://localhost/...
    to http://127.0.0.1:20128/... per PR diegosouzapw#2221's IPv4-force.
- tests/unit/provider-models-route.test.ts:
  * The Antigravity discovery-retry test now treats loadCodeAssist calls as
    non-fatal failures so the discovery path is still exercised.
  * The expected discovery URL sequence is updated to the new
    fetchAvailableModels-first order introduced by PR diegosouzapw#2219.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
Deep audit of all 320 commits since v3.7.9 found:
- 18 merged PRs not documented in CHANGELOG (4 features, 10 bug fixes, 1 security, 2 chores, 1 debug improvement)
- 3 contributors entirely missing from credits table (@NomenAK with 12 PRs, @kang-heewon, @one-vs)
- 4 existing contributors with inaccurate PR counts (@oyi77 8→12, @ddarkr 2→3, @andrewmunsell 2→3, @nickwizard 2→3)

New entries added:
- feat: diegosouzapw#2135 (1proxy settings), diegosouzapw#2227 (antigravity project ID), diegosouzapw#2238 (Z.AI Search), diegosouzapw#2240 (CLI Suite)
- fix: diegosouzapw#2217, diegosouzapw#2218, diegosouzapw#2219, diegosouzapw#2221, diegosouzapw#2222, diegosouzapw#2223, diegosouzapw#2224, diegosouzapw#2231, diegosouzapw#2233, diegosouzapw#2236, diegosouzapw#2242, diegosouzapw#2243
- security: diegosouzapw#2209 (stack trace exposure)
- chore: diegosouzapw#2228, diegosouzapw#2234

Total contributors updated from 50+ to 55+.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…uzapw#5193 regression of diegosouzapw#2541) (diegosouzapw#7815)

* fix(antigravity): attempt onboarding when projectId is empty (diegosouzapw#5193 regression of diegosouzapw#2541)

PR diegosouzapw#5193 changed onboarding from inline await to fire-and-forget gated by
if (projectId), which never fires when projectId is empty — the exact case
that needs onboarding. This re-introduced the diegosouzapw#2541 catch-22.

Add an else-if branch that attempts onboarding inline (bounded by
AbortSignal.timeout) when projectId is empty, then retries loadCodeAssist
to discover the newly created project.

- Existing accounts with projectId: unchanged (fire-and-forget)
- New accounts without projectId: now onboarded within login flow
- Timeout bounded: +8s worst case (onboardUser + retry loadCodeAssist)
- Graceful degradation: if onboarding fails, lazy retry handles it

Tests:
- 3 new tests covering empty-projectId onboarding path (RED→GREEN)
- Existing 2 tests preserved and passing
- Adjusted timeout assertion for the stall test (now includes onboardUser stall)
- 5/5 passing on Node 24

Fixes diegosouzapw#7814
Related: diegosouzapw#5193, diegosouzapw#2569, diegosouzapw#2541, diegosouzapw#2219

* docs(changelog): add fragment for antigravity onboarding empty-projectId fix (diegosouzapw#7814)

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: Rafael Dias Zendron <rafaumeu@users.noreply.github.com>
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
After merging PRs diegosouzapw#2221 (ModelSync shared loopback readiness gate + IPv4 force)
and diegosouzapw#2219 (Antigravity loadCodeAssist bootstrap + fetchAvailableModels fallback)
into release/v3.8.0, two test suites needed updates to match the new routing:

- tests/unit/model-sync-route.test.ts:
  * resetStorage() now calls __resetLoopbackReadinessForTests() so the
    module-level __loopbackReadyPromise cache does not leak between tests.
  * Every fetch mock now answers the /__readiness_probe__/ URL with 404 so
    the gate opens immediately (any HTTP response satisfies the probe).
  * Self-fetch target URL assertions updated from http://localhost/...
    to http://127.0.0.1:20128/... per PR diegosouzapw#2221's IPv4-force.
- tests/unit/provider-models-route.test.ts:
  * The Antigravity discovery-retry test now treats loadCodeAssist calls as
    non-fatal failures so the discovery path is still exercised.
  * The expected discovery URL sequence is updated to the new
    fetchAvailableModels-first order introduced by PR diegosouzapw#2219.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
Deep audit of all 320 commits since v3.7.9 found:
- 18 merged PRs not documented in CHANGELOG (4 features, 10 bug fixes, 1 security, 2 chores, 1 debug improvement)
- 3 contributors entirely missing from credits table (@NomenAK with 12 PRs, @kang-heewon, @one-vs)
- 4 existing contributors with inaccurate PR counts (@oyi77 8→12, @ddarkr 2→3, @andrewmunsell 2→3, @nickwizard 2→3)

New entries added:
- feat: diegosouzapw#2135 (1proxy settings), diegosouzapw#2227 (antigravity project ID), diegosouzapw#2238 (Z.AI Search), diegosouzapw#2240 (CLI Suite)
- fix: diegosouzapw#2217, diegosouzapw#2218, diegosouzapw#2219, diegosouzapw#2221, diegosouzapw#2222, diegosouzapw#2223, diegosouzapw#2224, diegosouzapw#2231, diegosouzapw#2233, diegosouzapw#2236, diegosouzapw#2242, diegosouzapw#2243
- security: diegosouzapw#2209 (stack trace exposure)
- chore: diegosouzapw#2228, diegosouzapw#2234

Total contributors updated from 50+ to 55+.
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.

2 participants