Skip to content

Align Antigravity auth, transports, and current models#575

Open
Daltonganger wants to merge 5 commits into
NoeFabris:mainfrom
Daltonganger:antigravity-cli-migration-current-models
Open

Align Antigravity auth, transports, and current models#575
Daltonganger wants to merge 5 commits into
NoeFabris:mainfrom
Daltonganger:antigravity-cli-migration-current-models

Conversation

@Daltonganger

Copy link
Copy Markdown

Summary

  • Align OAuth with current Antigravity/CLI behavior: openid, redirect modes, safer opaque PKCE state storage, broader redaction, and auth recovery docs.
  • Reconcile CloudCode endpoints/headers and add a transport boundary with default GatewayTransport plus opt-in experimental agy CLI and Managed Agents transports.
  • Update model definitions to the current 7 Antigravity quota rows and remove Gemini CLI models from default config/model docs.
  • Add current-model E2E script coverage, schema/docs/support matrix, and troubleshooting/migration documentation.

Current Antigravity models

  • google/antigravity-gemini-3.1-pro-low
  • google/antigravity-gemini-3.1-pro-high
  • google/antigravity-claude-sonnet-4-6-thinking
  • google/antigravity-claude-opus-4-6-thinking
  • google/antigravity-gpt-oss-120b-medium
  • google/antigravity-gemini-3.5-flash-medium
  • google/antigravity-gemini-3.5-flash-high

Validation

  • npm run typecheck
  • npm run build
  • npm test — 1132 passed, 25 todo
  • Live smoke: google/antigravity-gemini-3.5-flash-medium passed

Notes

  • Gateway remains the default transport.
  • cli and managed-agent transports are opt-in/experimental and documented as separate agent backends.
  • Gemini CLI models are no longer included in the plugin defaults because Gemini CLI access sunsets on 2026-06-18.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a transport boundary (gateway/CLI/managed-agent) with implementations and tests; adds opaque in-memory OAuth state with redirectMode and sensitive-data redaction; updates endpoint selection (non-sandbox daily primary) and endpoint invariants tests; replaces the default model/quota set with Antigravity-only entries and updates resolver logic for Gemini 3.5 and GPT-OSS; changes request/response wiring so non-OAuth transports run via transport interfaces and gateway paths delegate to gatewayTransport; adds local agy version detection and related tests; updates config/schema to include a transport block with deep-merge behavior; and updates many docs, scripts, and package.json to 1.7.0-beta.0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • NoeFabris
  • irishavmishra

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review


P1 Badge Allow non-OAuth transports to initialize without OAuth auth

This early return {} makes the plugin skip installing its fetch interceptor whenever OpenCode auth is missing, which blocks the new cli and managed-agent transports even though both declare requiresOAuth: false. In a fresh setup that only configures transport.managed_agent.api_key (or transport.cli), requests will bypass the transport code entirely until the user performs OAuth login, contradicting the transport contract and effectively disabling the feature.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/plugin.ts
Comment on lines +1542 to +1545
if (cachedAgyAuthState === null) {
cachedAgyAuthState = await checkAgyAuthState(binaryPath)
}
if (cachedAgyAuthState === "unauthenticated") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recheck agy auth state after an unauthenticated probe

The auth probe result is cached once, and if it is initially "unauthenticated", every later request returns 401 without rechecking. That means following the returned instruction (logging into agy in a terminal) does not recover in the same plugin session; users must restart the process before requests can succeed. This creates a persistent false-negative auth state and breaks expected retry behavior.

Useful? React with 👍 / 👎.

Comment on lines +21 to +23
function readJsonBody(init: RequestInit | undefined): Record<string, unknown> {
if (typeof init?.body !== "string") return {}
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse request body from RequestInfo, not only init.body

CliTransport only parses JSON from ctx.init.body, so if callers pass a Request object (with its body on ctx.input and init omitted), prompt extraction fails and prepareRequest throws even for valid generative requests. Since the transport interface accepts RequestInfo, this is a real interoperability regression for request paths that use new Request(...).

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns the Antigravity plugin with current CLI behavior across three areas: OAuth (opaque PKCE state storage, broader token redaction, openid scope, redirect-mode parameterization), transports (a new Transport interface with GatewayTransport as the default plus opt-in CliTransport and ManagedAgentTransport), and model definitions (replaces stale model IDs with the current 7 Antigravity quota rows and removes Gemini CLI models from defaults).

  • OAuth hardening: authorization codes and tokens are now redacted before logging; PKCE verifiers are stored server-side in an in-memory TTL map rather than embedded in the opaque state string; exchangeAntigravity accepts a redirectMode parameter that must match the one used during authorization.
  • Transport abstraction: a new Transport interface gates request matching, preparation, and response transformation; the existing gateway logic is wrapped in GatewayTransport with no behavior change; opt-in CLI and Managed Agent transports are documented as experimental.
  • Model alignment: OPENCODE_MODEL_DEFINITIONS now lists the 6 current Antigravity quota-row IDs; Gemini CLI models are removed; model-resolver.ts adds Gemini 3.5 Flash routing (all tiers forced to -low while only that row is live) and a gpt-oss quota group.

Confidence Score: 4/5

Safe to merge for the Gateway transport path; the new CLI and Managed Agent transports have open issues discussed in prior review threads that should be resolved before those paths go to production.

The gateway refactoring is a thin wrapper with no behavior change, and the OAuth hardening (opaque state, broader redaction) is straightforward. The experimental transports carry open issues from prior review rounds: the CLI auth cache permanently locks out the user within a session, managed-agent streaming omits the required ?alt=sse parameter, and dangerously_skip_permissions defaults to opt-in rather than opt-out. These don't affect the default gateway path but would break users who enable the opt-in transports today.

src/plugin/transport/managed-agent-transport.ts and src/plugin/config/schema.ts — the streaming issue and permission default discussed in prior review threads are still present and affect anyone enabling those transports.

Important Files Changed

Filename Overview
src/antigravity/oauth.ts Adds opaque PKCE state storage (randomBytes, in-memory TTL map), broader token redaction, openid scope, and redirect-mode parameter threading; well-structured but module-level Map requires process restart to clear state.
src/plugin/transform/model-resolver.ts Adds Gemini 3.5 Flash routing (forces all tiers to -low) and gpt-oss family, but contains a dead-code ternary where both branches return "low".
src/plugin/transport/gateway-transport.ts Thin Transport wrapper over existing prepareAntigravityRequest/transformAntigravityResponse; no logic moved, clean seam.
src/plugin/transport/cli-transport.ts New CLI transport that extracts prompt text and shells out to agy --print; prompt extraction for complex content (multi-turn, multi-part) is reasonable but lossy for structured tool calls.
src/plugin/transport/managed-agent-transport.ts New Managed Agent transport; streaming path returns raw SSE without ?alt=sse on URL (flagged in prior review) and unknown-field fallback returns raw JSON to callers (also flagged).
src/plugin/transport/agy-cli.ts Binary detection and auth-probe helpers; timedOut detection via runtime killed property with type cast is functional but relies on undocumented Node.js error shape.
src/plugin/config/schema.ts Adds transport config block; dangerously_skip_permissions defaults to true (flagged in prior review), otherwise schema is well-documented with sensible defaults.
src/plugin/config/models.ts Replaces old model definitions with 6 current Antigravity quota-row IDs; PR describes 7 quota rows but only flash-low is registered (medium/high are silently aliased to low by the resolver).
src/plugin.ts Adds transport selection and non-OAuth request path; auth state cache for CLI transport never resets on unauthenticated (flagged in prior review); otherwise refactoring from direct calls to transport interface is clean.
src/constants.ts Adds openid scope, daily-nonsandbox endpoint, redirect mode helpers, and official-callback constants; official-callback documented as non-functional (flagged in prior review).
src/plugin/version.ts Adds local agy binary detection as step 1 of version resolution; outer timer and per-execFile timeout both set to 2000ms can race, but resolved guard prevents double resolution.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming fetch request] --> B{transport.matches?}
    B -- No --> C[Pass through to native fetch]
    B -- Yes --> D{transport.auth.requiresOAuth?}

    D -- No: CLI/ManagedAgent --> E{transport.id === cli?}
    E -- Yes --> F{cachedAgyBinaryPath?}
    F -- null --> G[findAgyBinary]
    G --> H{found?}
    H -- No --> ERR1[404 AGY_NOT_FOUND]
    H -- Yes --> I{cachedAgyAuthState?}
    F -- cached --> I
    I -- null --> J[checkAgyAuthState]
    J --> K{state?}
    K -- unauthenticated --> ERR2[401 UNAUTHENTICATED]
    K -- authenticated/unknown --> L[executeAgyCommand]
    I -- authenticated/unknown --> L
    L --> M[transformResponse: geminiTextResponse]

    E -- No: ManagedAgent --> N[prepareRequest: build Interactions API body]
    N --> O[fetch INTERACTIONS_ENDPOINT]
    O --> P{streaming?}
    P -- No --> Q[extractManagedAgentText: toGeminiTextResponse]
    P -- Yes --> R[return SSE as-is]

    D -- Yes: Gateway --> S[account selection + auth refresh]
    S --> T[endpoint fallback loop]
    T --> U[gatewayTransport.prepareRequest]
    U --> V[fetch backend endpoint]
    V --> W[gatewayTransport.transformResponse]
    W --> X[Return OpenCode-compatible Response]
    M --> X
    Q --> X
    R --> X
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/plugin/transform/model-resolver.ts:234-236
Dead-code ternary — both branches return `"low"`. If the intention was to keep a placeholder for when a different default tier is needed for non-3.5-flash Gemini 3 models, the else branch should differ; otherwise the condition can be removed entirely.

```suggestion
      const defaultThinkingLevel = "low";
```

### Issue 2 of 2
src/plugin/config/models.ts:66-70
**Smoke-tested model ID has no definition entry**

The PR validation runs against `google/antigravity-gemini-3.5-flash-medium`, and the description lists both `medium` and `high` flash as active quota rows. The resolver silently forces all `gemini-3.5-flash` requests to `-low` (by design), but `antigravity-gemini-3.5-flash-medium` and `antigravity-gemini-3.5-flash-high` are never registered here. Any UI component that enumerates the model registry (context-window display, quota-group picker) will only show `antigravity-gemini-3.5-flash-low`, while docs and the smoke test point users at the `medium` ID. Adding definitions for the other two quota rows would make the registry consistent with the documented quota surface, even if they all resolve to the same underlying API row.

Reviews (5): Last reviewed commit: "fix: align current models with live avai..." | Re-trigger Greptile

Comment on lines +472 to +473
/** Skip permission prompts in agy. */
dangerously_skip_permissions: z.boolean().default(true),

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.

P1 dangerously_skip_permissions defaults to true, so every user who enables the CLI transport immediately gets agy --dangerously-skip-permissions on all requests — bypassing all permission checks — without explicitly opting in. The name's own dangerously_ prefix signals this should be an explicit opt-in. With the current default, agy can read, write, or execute files without asking the user the moment they enable the transport.

Suggested change
/** Skip permission prompts in agy. */
dangerously_skip_permissions: z.boolean().default(true),
/** Skip permission prompts in agy. */
dangerously_skip_permissions: z.boolean().default(false),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin/config/schema.ts
Line: 472-473

Comment:
`dangerously_skip_permissions` defaults to `true`, so every user who enables the CLI transport immediately gets `agy --dangerously-skip-permissions` on all requests — bypassing all permission checks — without explicitly opting in. The name's own `dangerously_` prefix signals this should be an explicit opt-in. With the current default, agy can read, write, or execute files without asking the user the moment they enable the transport.

```suggestion
      /** Skip permission prompts in agy. */
      dangerously_skip_permissions: z.boolean().default(false),
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +125 to +128
return {
text: JSON.stringify(payload),
interactionId,
}

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.

P2 When none of the expected fields (output, text, candidates) are present, the entire raw JSON.stringify(payload) is returned as model text. Any field in the API response — including error details, internal IDs, or debugging artifacts — surfaces directly in the model's reply. A safer fallback would be a descriptive error string so users know parsing failed rather than getting a raw JSON dump.

Suggested change
return {
text: JSON.stringify(payload),
interactionId,
}
return {
text: "[ManagedAgent] Could not parse response text from API payload.",
interactionId,
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin/transport/managed-agent-transport.ts
Line: 125-128

Comment:
When none of the expected fields (`output`, `text`, `candidates`) are present, the entire raw `JSON.stringify(payload)` is returned as model text. Any field in the API response — including error details, internal IDs, or debugging artifacts — surfaces directly in the model's reply. A safer fallback would be a descriptive error string so users know parsing failed rather than getting a raw JSON dump.

```suggestion
  return {
    text: "[ManagedAgent] Could not parse response text from API payload.",
    interactionId,
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/constants.ts
Comment on lines +38 to +56
* The current plugin uses the v2 endpoint; the official CLI uses the v1 endpoint.
*/
export const ANTIGRAVITY_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/auth";

/**
* Redirect mode controls which OAuth callback URI and auth endpoint to use.
* - `local-callback`: existing behavior, local HTTP server on port 51121.
* - `official-callback`: experimental, uses the hosted Antigravity callback URI
* and v1 auth endpoint matching official `agy`. Not fully functional yet
* because the hosted callback consumes the authorization code server-side.
*/
export type AntigravityRedirectMode = "local-callback" | "official-callback";

/**
* Resolve the redirect URI for the given redirect mode.
*/
export function getRedirectUri(mode: AntigravityRedirectMode): string {
return mode === "official-callback"
? ANTIGRAVITY_OFFICIAL_REDIRECT_URI

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.

P2 official-callback mode exported while broken by design

AntigravityRedirectMode is a public type and getRedirectUri/getAuthEndpoint are exported, so callers can pass "official-callback" to authorizeAntigravity. The constants file itself documents this mode as "Not fully functional yet because the hosted callback consumes the authorization code server-side." If a caller uses this mode, the authorization URL is built correctly but token exchange will always fail. Consider marking the type @internal or gating the mode behind a non-exported constant until it is functional.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/constants.ts
Line: 38-56

Comment:
**`official-callback` mode exported while broken by design**

`AntigravityRedirectMode` is a public type and `getRedirectUri`/`getAuthEndpoint` are exported, so callers can pass `"official-callback"` to `authorizeAntigravity`. The constants file itself documents this mode as "Not fully functional yet because the hosted callback consumes the authorization code server-side." If a caller uses this mode, the authorization URL is built correctly but token exchange will always fail. Consider marking the type `@internal` or gating the mode behind a non-exported constant until it is functional.

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/constants.ts (1)

70-73: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Synchronize endpoint doc comment with the new fallback chain.

Line 71–73 still describes sandbox daily → autopush → prod, which no longer matches the exported fallback constants.

📝 Suggested comment update
- * CLIProxy and Vibeproxy use the daily sandbox endpoint first,
- * then fallback to autopush and prod if needed.
+ * Fallback order is non-sandbox daily, then sandbox daily, then prod.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/constants.ts` around lines 70 - 73, Update the block comment describing
root endpoints so it matches the current exported fallback constants: edit the
comment referencing CLIProxy and Vibeproxy to reflect the new fallback order
(replace the old "sandbox daily → autopush → prod" sequence with the actual
sequence used by the exported fallback constants), ensuring the text aligns with
the exported constants that define the fallback chain in this module.
docs/ARCHITECTURE.md (1)

138-141: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update fallback order text to match current runtime behavior.

Line 140 still documents daily → autopush → prod, but the current fallback chain is non-sandbox daily → sandbox daily → prod.

📝 Suggested doc correction
-- Endpoint fallback (daily → autopush → prod)
+- Endpoint fallback (non-sandbox daily → sandbox daily → prod)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ARCHITECTURE.md` around lines 138 - 141, Update the documentation line
that currently reads "Endpoint fallback (daily → autopush → prod)" to reflect
the actual runtime fallback order "non-sandbox daily → sandbox daily → prod";
locate the list item by its current text "Endpoint fallback (daily → autopush →
prod)" and replace it with the updated fallback chain so docs match runtime
behavior.
🧹 Nitpick comments (4)
src/plugin/transport/cli-transport.ts (2)

117-119: ⚡ Quick win

Clarify error message for empty prompts.

The error message states "could not extract a text prompt" but the actual issue is that the extracted prompt is empty or whitespace-only. Consider a more precise message like "Extracted prompt is empty or contains only whitespace." to help users distinguish between parsing failures and empty input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/transport/cli-transport.ts` around lines 117 - 119, The thrown
error for an empty/whitespace-only prompt is misleading; update the check in the
CliTransport code path that uses the prompt variable (the block that does if
(!prompt.trim()) { ... }) to throw a clearer message such as "Extracted prompt
is empty or contains only whitespace." so callers can distinguish empty input
from extraction/parsing failures—modify the Error thrown in that conditional
inside the CliTransport method that extracts `prompt`.

158-164: ⚡ Quick win

Add error handling for JSON parsing.

ctx.response.json() can throw if the response body is not valid JSON. Consider wrapping in try/catch to provide a clearer error message or fallback behavior.

🛡️ Suggested error handling
 async transformResponse(ctx: TransformTransportResponseContext): Promise<Response> {
   if (!ctx.response.ok) return ctx.response

-  const payload = await ctx.response.json() as { stdout?: unknown; stderr?: unknown }
-  const stdout = typeof payload.stdout === "string" ? payload.stdout : ""
+  try {
+    const payload = await ctx.response.json() as { stdout?: unknown; stderr?: unknown }
+    const stdout = typeof payload.stdout === "string" ? payload.stdout : ""
+    return geminiTextResponse(stdout.trim())
+  } catch (error) {
+    throw new Error(`Failed to parse CLI response: ${error instanceof Error ? error.message : String(error)}`)
+  }
-
-  return geminiTextResponse(stdout.trim())
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/transport/cli-transport.ts` around lines 158 - 164, The
transformResponse function should guard against invalid JSON from
ctx.response.json() by wrapping the parse in a try/catch: catch JSON parse
errors when calling ctx.response.json(), log or attach the error context
(including ctx.response.status) and fall back to returning the original
ctx.response or a safe geminiTextResponse with an empty string; update
transformResponse (reference function transformResponse, variable payload, call
to ctx.response.json(), and return geminiTextResponse(stdout.trim())) to
implement this try/catch and clear fallback behavior.
src/plugin/version.ts (2)

28-33: 💤 Low value

Filter out the ~/.local/bin/agy candidate when HOME is unset.

When process.env.HOME is undefined/empty, the first candidate becomes the literal /.local/bin/agy, which is harmless (ENOENT) but wastes a process spawn on every plugin start and pollutes any future debug logs. Either skip the candidate or only include it when HOME is set.

♻️ Proposed fix
-const AGY_CANDIDATES = [
-  `${process.env["HOME"] ?? ""}/.local/bin/agy`,
-  "agy",
-];
+const AGY_CANDIDATES = [
+  ...(process.env["HOME"] ? [`${process.env["HOME"]}/.local/bin/agy`] : []),
+  "agy",
+];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/version.ts` around lines 28 - 33, AGY_CANDIDATES currently
includes `${process.env["HOME"] ?? ""}/.local/bin/agy` which becomes
`/.local/bin/agy` when HOME is unset and causes unnecessary spawns; update the
AGY_CANDIDATES definition to only include the HOME-based path when
process.env.HOME is a non-empty string (e.g., build the array conditionally or
filter falsy entries) so the HOME candidate is omitted when HOME is not set,
leaving only "agy" as the fallback.

47-79: 💤 Low value

Cascade keeps spawning agy processes after the outer timeout resolves.

tryNext recurses through AGY_CANDIDATES purely from execFile callbacks and never checks whether the outer setTimeout has already resolved the promise with null. If a candidate hits its own timeout (or simply errors) after the outer 2s timer has already fired done(null), the next candidate is still spawned (a fresh 2s execFile), and so on. The promise result is correct, but the helper can keep launching agy subprocesses after it has logically completed.

Short-circuit the chain when resolved is set:

♻️ Proposed fix
     const tryNext = (candidates: string[]): void => {
+      if (resolved) return;
       if (candidates.length === 0) {
         clearTimeout(timer);
         done(null);
         return;
       }
       const [candidate, ...rest] = candidates;
       execFile(candidate!, ["--version"], { timeout: AGY_BINARY_TIMEOUT_MS }, (err, stdout, stderr) => {
+        if (resolved) return;
         if (err) {
           tryNext(rest);
           return;
         }
         const version = parseVersion((stdout || stderr).trim());
         clearTimeout(timer);
         done(version);
       });
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/version.ts` around lines 47 - 79, The tryLocalAgyVersion helper
can continue spawning execFile calls after the outer timeout sets resolved;
update tryNext/execFile usage in tryLocalAgyVersion so the chain short-circuits
when resolved is true: before calling execFile check if resolved and return
early, store the child returned by execFile and, in the outer timeout/done path,
ensure any in-flight child is killed, and inside the execFile callback
immediately return if resolved is set (and avoid calling tryNext). Make these
changes around the functions/variables tryLocalAgyVersion, tryNext, done,
resolved, timer, AGY_CANDIDATES and the execFile invocation so no new processes
are spawned after resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/antigravity.schema.json`:
- Around line 382-384: Change the JSON schema for the experimental transport so
the property "dangerously_skip_permissions" is opt-in: locate the
"dangerously_skip_permissions" property in assets/antigravity.schema.json and
change its "default" value from true to false; ensure the property's "type"
remains "boolean" and run schema validation/tests to confirm no other code
assumes the old default.

In `@docs/ANTIGRAVITY_CLI_MIGRATION_PLAN.md`:
- Around line 788-797: The document contradicts itself by saying “Start Phase 1
with tests first:” under the "## Recommended Immediate Next Step" header while
an earlier section already marks Phase 1 complete; update the wording to match
the actual state — either change "Start Phase 1 with tests first:" to "Begin
Phase 2 with tests first:" (keeping the existing 1–4 checklist: OAuth tests, add
openid/redirect-mode, verify local callback, experiment with hosted callback)
or, if Phase 1 is not actually complete, remove the earlier "Phase 1 complete"
marker so the current instruction remains correct; ensure the header and the
checklist consistently reference the same phase ("Phase 1" or "Phase 2")
throughout the doc.

In `@docs/ARCHITECTURE.md`:
- Around line 69-78: The fenced code block in the ARCHITECTURE.md snippet is
missing a language tag; update the block opener to include a language identifier
(e.g., "text" or "txt") so the markdown linter passes—leave the content
unchanged (the diagram referencing plugin.ts and gatewayTransport methods:
matches(), getRequestMetadata(), prepareRequest() / prepareAntigravityRequest(),
transformResponse() / transformAntigravityResponse()) and only add the language
tag to the initial ``` fence.

In `@README.md`:
- Around line 686-687: The README currently instructs users to "disable
`quota_fallback`" even though `quota_fallback` is a deprecated no-op; update the
Gemini CLI sunset note to remove that recommendation and replace it with
actionable guidance — mention `gemini-cli` sunset date and advise concrete next
steps such as choosing an alternate transport (e.g., switch from quota-based
transport to authenticated Google AI transport), migrating to supported models
or tiers (Google AI Pro/Ultra alternatives), and updating any config fields that
actually affect routing (call out the `quota_fallback` key as deprecated/ignored
and recommend the real config keys or workflows to change). Ensure the copy
explicitly references the `quota_fallback` key as deprecated and provides clear
migration/transport selection steps and links to relevant docs or config keys.

In `@script/test-cross-model-e2e.sh`:
- Line 6: Update the header comment that lists the Gemini models to correct the
second model ID by adding the missing google/antigravity- prefix so both entries
match the full model IDs (change "gemini-3.5-flash-medium" to
"google/antigravity-gemini-3.5-flash-medium" in the comment line that starts
with "1. Gemini").

In `@src/antigravity/oauth.ts`:
- Around line 31-56: The current regexes only match parameters when preceded by
? or &, so "code=..." or "state=..." at the start of the string bypasses
redaction; update each pattern that begins with ([?&]PARAM=) to allow
start-of-string by changing it to /(^|[?&])PARAM=[^&\s"']+/gi and change the
replacement to include the captured prefix and the param name (e.g.,
"$1code=[REDACTED]" for the patterns referencing code), applying this change for
code, state, refresh_token, access_token, id_token, client_secret,
code_verifier, and code_challenge in the same replace chain in
src/antigravity/oauth.ts so start-of-string params are redacted.

In `@src/plugin.ts`:
- Around line 1490-1546: The current logic in the cli transport block caches
negative results permanently: cachedAgyBinaryPath and cachedAgyAuthState are set
even when no binary is found or when checkAgyAuthState returns
"unauthenticated", which blocks recovery until plugin restart; change the
caching policy so only positive findings are sticky: set cachedAgyBinaryPath
only when a valid binary path is found (leave it null if not found) and set
cachedAgyAuthState to "authenticated" only when checkAgyAuthState returns that;
do not cache "unauthenticated" (keep it null or "unknown") so subsequent
requests will re-run findAgyBinary() and checkAgyAuthState() and allow recovery
after the user installs or signs in; update the cli branch where
findAgyBinary(), cachedAgyBinaryPath and checkAgyAuthState() are used to
implement this behavior.

In `@src/plugin/transport/agy-cli.ts`:
- Around line 37-53: The execFileText function calls execFile without a
maxBuffer which can cause stdout/stderr truncation for large outputs; update
execFileText to pass a larger maxBuffer in the options object passed to execFile
(e.g., { timeout: timeoutMs, maxBuffer: <suitable byte size> }) or switch
execFile usage to a streaming approach (spawn and stream stdout/stderr) if you
expect arbitrarily large output; locate the execFile call inside execFileText
and either add the maxBuffer option or refactor execFileText to use spawn and
stream handling to avoid Node's default buffer limit.

---

Outside diff comments:
In `@docs/ARCHITECTURE.md`:
- Around line 138-141: Update the documentation line that currently reads
"Endpoint fallback (daily → autopush → prod)" to reflect the actual runtime
fallback order "non-sandbox daily → sandbox daily → prod"; locate the list item
by its current text "Endpoint fallback (daily → autopush → prod)" and replace it
with the updated fallback chain so docs match runtime behavior.

In `@src/constants.ts`:
- Around line 70-73: Update the block comment describing root endpoints so it
matches the current exported fallback constants: edit the comment referencing
CLIProxy and Vibeproxy to reflect the new fallback order (replace the old
"sandbox daily → autopush → prod" sequence with the actual sequence used by the
exported fallback constants), ensuring the text aligns with the exported
constants that define the fallback chain in this module.

---

Nitpick comments:
In `@src/plugin/transport/cli-transport.ts`:
- Around line 117-119: The thrown error for an empty/whitespace-only prompt is
misleading; update the check in the CliTransport code path that uses the prompt
variable (the block that does if (!prompt.trim()) { ... }) to throw a clearer
message such as "Extracted prompt is empty or contains only whitespace." so
callers can distinguish empty input from extraction/parsing failures—modify the
Error thrown in that conditional inside the CliTransport method that extracts
`prompt`.
- Around line 158-164: The transformResponse function should guard against
invalid JSON from ctx.response.json() by wrapping the parse in a try/catch:
catch JSON parse errors when calling ctx.response.json(), log or attach the
error context (including ctx.response.status) and fall back to returning the
original ctx.response or a safe geminiTextResponse with an empty string; update
transformResponse (reference function transformResponse, variable payload, call
to ctx.response.json(), and return geminiTextResponse(stdout.trim())) to
implement this try/catch and clear fallback behavior.

In `@src/plugin/version.ts`:
- Around line 28-33: AGY_CANDIDATES currently includes `${process.env["HOME"] ??
""}/.local/bin/agy` which becomes `/.local/bin/agy` when HOME is unset and
causes unnecessary spawns; update the AGY_CANDIDATES definition to only include
the HOME-based path when process.env.HOME is a non-empty string (e.g., build the
array conditionally or filter falsy entries) so the HOME candidate is omitted
when HOME is not set, leaving only "agy" as the fallback.
- Around line 47-79: The tryLocalAgyVersion helper can continue spawning
execFile calls after the outer timeout sets resolved; update tryNext/execFile
usage in tryLocalAgyVersion so the chain short-circuits when resolved is true:
before calling execFile check if resolved and return early, store the child
returned by execFile and, in the outer timeout/done path, ensure any in-flight
child is killed, and inside the execFile callback immediately return if resolved
is set (and avoid calling tryNext). Make these changes around the
functions/variables tryLocalAgyVersion, tryNext, done, resolved, timer,
AGY_CANDIDATES and the execFile invocation so no new processes are spawned after
resolution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aafccb17-7d5f-4e4d-b424-ef9c5c2234f4

📥 Commits

Reviewing files that changed from the base of the PR and between 740e315 and 1acb7be.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (47)
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • CHANGELOG.md
  • README.md
  • assets/antigravity.schema.json
  • docs/ANTIGRAVITY_API_SPEC.md
  • docs/ANTIGRAVITY_AUTH_RECOVERY_PLAN.md
  • docs/ANTIGRAVITY_CLI_MIGRATION_PLAN.md
  • docs/ANTIGRAVITY_PHASE0_BASELINE.md
  • docs/ARCHITECTURE.md
  • docs/CONFIGURATION.md
  • docs/MODEL-VARIANTS.md
  • docs/MULTI-ACCOUNT.md
  • docs/SUPPORT_MATRIX.md
  • docs/TROUBLESHOOTING.md
  • index.ts
  • script/test-cross-model-e2e.sh
  • script/test-gemini-cli-e2e.sh
  • script/test-models.ts
  • script/test-regression.ts
  • scripts/check-quota.mjs
  • scripts/setup-opencode-pi.sh
  • src/antigravity/oauth.test.ts
  • src/antigravity/oauth.ts
  • src/constants.endpoint.test.ts
  • src/constants.ts
  • src/plugin.ts
  • src/plugin/accounts.test.ts
  • src/plugin/config/loader.ts
  • src/plugin/config/models.test.ts
  • src/plugin/config/models.ts
  • src/plugin/config/schema.ts
  • src/plugin/config/updater.test.ts
  • src/plugin/quota.ts
  • src/plugin/request.ts
  • src/plugin/transform/model-resolver.test.ts
  • src/plugin/transform/model-resolver.ts
  • src/plugin/transport/agy-cli.test.ts
  • src/plugin/transport/agy-cli.ts
  • src/plugin/transport/cli-transport.test.ts
  • src/plugin/transport/cli-transport.ts
  • src/plugin/transport/gateway-transport.test.ts
  • src/plugin/transport/gateway-transport.ts
  • src/plugin/transport/managed-agent-transport.test.ts
  • src/plugin/transport/managed-agent-transport.ts
  • src/plugin/transport/types.ts
  • src/plugin/version.test.ts
  • src/plugin/version.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
🪛 LanguageTool
docs/ANTIGRAVITY_PHASE0_BASELINE.md

[style] ~208-~208: The double modal “Requires authenticated” is nonstandard (only accepted in certain dialects). Consider “to be authenticated”.
Context: ...ix:** Not captured in Phase 0. Requires authenticated fetchAvailableModels call. --- ## F...

(NEEDS_FIXED)


[locale-violation] ~373-~373: The phrase ‘in future’ is British English. Did you mean: “in the future”?
Context: ...t should be moved to env var or keyring in future. --- ## OAuth Test Coverage **Source...

(IN_FUTURE)


[style] ~498-~498: The double modal “requires authenticated” is nonstandard (only accepted in certain dialects). Consider “to be authenticated”.
Context: ... Not captured in Phase 0 (requires authenticated OpenCode session + debug logging): - [...

(NEEDS_FIXED)

CHANGELOG.md

[style] ~11-~11: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...point()helpers inconstants.ts. - Added redactOAuthSensitiveData()` to scrub c...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~12-~12: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...PKCE verifiers from error messages. - Added 32 OAuth tests covering URL constructio...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~66-~66: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...tions. - docs/ANTIGRAVITY_API_SPEC.md updated with endpoint status table and openid...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

docs/SUPPORT_MATRIX.md

[style] ~160-~160: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...the antigravity header style only. **If you stored accounts before this release...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

docs/ANTIGRAVITY_CLI_MIGRATION_PLAN.md

[style] ~84-~84: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... PKCE verifiers, or keyring material. - Do not replace the default gateway shim wi...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~85-~85: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...without a stable structured protocol. - Do not rewrite auth, request transformatio...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~86-~86: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... transport architecture in one phase. - Do not remove existing account storage unt...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~87-~87: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...l migration is proven and reversible. - Do not assume preview Managed Agents APIs ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~359-~359: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...chAvailableModels` on all candidates. - Test one Gemini model and one Claude model p...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~409-~409: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... - Keep recovery semantics unchanged. - Keep quota/account rotation outside optional...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~517-~517: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hangelog` or another stable command. 3. Detect authentication state without triggering...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~617-~617: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...evious_interaction_id` if supported. 5. Prototype streaming if available. 6. Compare tool...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.22.1)
docs/ANTIGRAVITY_PHASE0_BASELINE.md

[warning] 538-538: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/ANTIGRAVITY_API_SPEC.md

[warning] 58-58: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/ARCHITECTURE.md

[warning] 69-69: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Shellcheck (0.11.0)
scripts/setup-opencode-pi.sh

[warning] 30-30: schema is referenced but not assigned.

(SC2154)

🔇 Additional comments (45)
.github/ISSUE_TEMPLATE/bug_report.yml (1)

44-50: LGTM!

index.ts (1)

6-7: LGTM!

src/plugin/accounts.test.ts (1)

1851-1851: LGTM!

script/test-regression.ts (1)

55-57: LGTM!

src/plugin/config/updater.test.ts (1)

69-70: LGTM!

Also applies to: 238-238

src/plugin/config/loader.ts (1)

108-127: LGTM!

script/test-models.ts (1)

6-6: LGTM!

Also applies to: 10-22, 93-93, 100-100

scripts/check-quota.mjs (1)

94-210: LGTM!

CHANGELOG.md (1)

3-72: LGTM!

src/plugin/quota.ts (1)

16-227: LGTM!

docs/ANTIGRAVITY_API_SPEC.md (1)

23-76: LGTM!

src/constants.endpoint.test.ts (1)

1-124: LGTM!

src/plugin/request.ts (1)

758-1567: LGTM!

scripts/setup-opencode-pi.sh (1)

28-160: LGTM!

src/plugin/transport/cli-transport.test.ts (1)

1-224: LGTM!

src/plugin/version.ts (1)

107-129: LGTM!

docs/TROUBLESHOOTING.md (1)

33-57: LGTM!

Also applies to: 454-476, 506-541

src/plugin/config/models.ts (2)

41-60: LGTM!

Also applies to: 66-75


61-65: ⚡ Quick win

Confirm Antigravity quota-based limit values for antigravity-gpt-oss-120b-medium

src/plugin/config/models.ts sets antigravity-gpt-oss-120b-medium.limit to context: 200000, output: 64000 (same as the Claude “thinking” entries). The plugin code does not appear to read model.limit when constructing the Antigravity generationConfig (token caps are set elsewhere, e.g. CLAUDE_THINKING_MAX_OUTPUT_TOKENS), so a mismatch would mainly impact any downstream UI/token-budgeting that consumes this config. If limit is meant to mirror the Antigravity quota view, confirm these numbers against the quota row before merge.

docs/SUPPORT_MATRIX.md (1)

1-172: LGTM!

src/plugin/transform/model-resolver.ts (1)

218-247: LGTM!

Also applies to: 278-290

src/plugin/transport/managed-agent-transport.test.ts (1)

1-270: LGTM!

docs/CONFIGURATION.md (1)

247-356: LGTM!

docs/MODEL-VARIANTS.md (1)

1-75: LGTM!

src/plugin/transport/gateway-transport.ts (1)

1-104: LGTM!

src/plugin/transport/types.ts (1)

1-109: LGTM!

docs/MULTI-ACCOUNT.md (1)

15-58: LGTM!

src/plugin/transport/managed-agent-transport.ts (1)

1-238: LGTM!

src/plugin/config/schema.ts (1)

274-287: LGTM!

Also applies to: 443-487, 550-552

src/antigravity/oauth.test.ts (1)

1-535: LGTM!

src/plugin/transform/model-resolver.test.ts (1)

120-131: LGTM!

Also applies to: 188-196

assets/antigravity.schema.json (1)

338-381: LGTM!

Also applies to: 385-434

README.md (1)

9-16: LGTM!

Also applies to: 77-77, 100-100, 111-131, 141-273, 460-462, 598-601, 631-631, 676-685, 688-689, 724-724

docs/ANTIGRAVITY_AUTH_RECOVERY_PLAN.md (1)

1-161: LGTM!

src/plugin/transport/agy-cli.ts (1)

1-36: LGTM!

Also applies to: 55-160

src/plugin/transport/gateway-transport.test.ts (1)

1-212: LGTM!

script/test-gemini-cli-e2e.sh (1)

6-7: LGTM!

Also applies to: 118-120, 133-134

src/plugin/transport/agy-cli.test.ts (1)

1-184: LGTM!

docs/ANTIGRAVITY_CLI_MIGRATION_PLAN.md (1)

13-787: LGTM!

docs/ARCHITECTURE.md (1)

50-60: LGTM!

Also applies to: 65-67, 80-127, 135-136

src/plugin/version.test.ts (1)

16-34: LGTM!

Also applies to: 69-110

src/plugin/config/models.test.ts (1)

13-38: LGTM!

src/antigravity/oauth.ts (1)

148-228: LGTM!

Also applies to: 324-394

src/plugin.ts (1)

252-267: LGTM!

Also applies to: 2090-2113, 2501-2507, 2583-2633, 3149-3161, 3353-3368

src/constants.ts (1)

20-20: LGTM!

Also applies to: 28-67, 79-114

Comment on lines +382 to +384
"dangerously_skip_permissions": {
"default": true,
"type": "boolean"

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set dangerously_skip_permissions default to false.

This is currently secure-by-default inverted for an experimental transport. Keep it explicit opt-in only.

Suggested fix
             "dangerously_skip_permissions": {
-              "default": true,
+              "default": false,
               "type": "boolean"
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"dangerously_skip_permissions": {
"default": true,
"type": "boolean"
"dangerously_skip_permissions": {
"default": false,
"type": "boolean"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/antigravity.schema.json` around lines 382 - 384, Change the JSON
schema for the experimental transport so the property
"dangerously_skip_permissions" is opt-in: locate the
"dangerously_skip_permissions" property in assets/antigravity.schema.json and
change its "default" value from true to false; ensure the property's "type"
remains "boolean" and run schema validation/tests to confirm no other code
assumes the old default.

Comment on lines +788 to +797
## Recommended Immediate Next Step

Start Phase 1 with tests first:

1. Add OAuth URL/body tests for current behavior.
2. Add `openid` and redirect-mode support.
3. Verify local callback still works.
4. Only then experiment with official hosted callback.

This keeps the highest-risk auth change small, testable, and reversible.

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve the phase-status contradiction in the final action section.

Line 790 says to “Start Phase 1,” but Line 153 already marks Phase 1 as complete. This will confuse readers about current execution state.

📝 Suggested wording update
-## Recommended Immediate Next Step
-
-Start Phase 1 with tests first:
+## Recommended Immediate Next Step (for new migration cycles)
+
+If this roadmap is reused for a future migration cycle, start with Phase 1 tests first:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ANTIGRAVITY_CLI_MIGRATION_PLAN.md` around lines 788 - 797, The document
contradicts itself by saying “Start Phase 1 with tests first:” under the "##
Recommended Immediate Next Step" header while an earlier section already marks
Phase 1 complete; update the wording to match the actual state — either change
"Start Phase 1 with tests first:" to "Begin Phase 2 with tests first:" (keeping
the existing 1–4 checklist: OAuth tests, add openid/redirect-mode, verify local
callback, experiment with hosted callback) or, if Phase 1 is not actually
complete, remove the earlier "Phase 1 complete" marker so the current
instruction remains correct; ensure the header and the checklist consistently
reference the same phase ("Phase 1" or "Phase 2") throughout the doc.

Comment thread docs/ARCHITECTURE.md
Comment on lines +69 to +78
```
OpenCode request
└─ plugin.ts (fetch interceptor)
├─ account selection, quota rotation, rate limiting, retries
└─ gatewayTransport
├─ matches() — request recognition
├─ getRequestMetadata() — model/family extraction
├─ prepareRequest() — delegates to prepareAntigravityRequest()
└─ transformResponse() — delegates to transformAntigravityResponse()
```

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block to satisfy markdown lint.

Line 69 opens a fenced block without a language identifier.

🧹 Minimal fix
-```
+```text
 OpenCode request
   └─ plugin.ts (fetch interceptor)
       ├─ account selection, quota rotation, rate limiting, retries
       └─ gatewayTransport
           ├─ matches()          — request recognition
           ├─ getRequestMetadata() — model/family extraction
           ├─ prepareRequest()   — delegates to prepareAntigravityRequest()
           └─ transformResponse() — delegates to transformAntigravityResponse()
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 69-69: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ARCHITECTURE.md` around lines 69 - 78, The fenced code block in the
ARCHITECTURE.md snippet is missing a language tag; update the block opener to
include a language identifier (e.g., "text" or "txt") so the markdown linter
passes—leave the content unchanged (the diagram referencing plugin.ts and
gatewayTransport methods: matches(), getRequestMetadata(), prepareRequest() /
prepareAntigravityRequest(), transformResponse() /
transformAntigravityResponse()) and only add the language tag to the initial ```
fence.

Comment thread README.md
Comment on lines +686 to +687
> **⚠️ Gemini CLI sunset: 2026-06-18.** The `gemini-cli` quota pool stops serving requests for Google AI Pro/Ultra and free Gemini Code Assist individuals after this date. If requests fail after June 18, disable `quota_fallback` in your config.

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Troubleshooting points to a deprecated no-op setting.

This guidance tells users to disable quota_fallback, but that key is documented as ignored. Replace with actionable advice (for example, transport selection/model migration guidance).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 686 - 687, The README currently instructs users to
"disable `quota_fallback`" even though `quota_fallback` is a deprecated no-op;
update the Gemini CLI sunset note to remove that recommendation and replace it
with actionable guidance — mention `gemini-cli` sunset date and advise concrete
next steps such as choosing an alternate transport (e.g., switch from
quota-based transport to authenticated Google AI transport), migrating to
supported models or tiers (Google AI Pro/Ultra alternatives), and updating any
config fields that actually affect routing (call out the `quota_fallback` key as
deprecated/ignored and recommend the real config keys or workflows to change).
Ensure the copy explicitly references the `quota_fallback` key as deprecated and
provides clear migration/transport selection steps and links to relevant docs or
config keys.

Comment thread script/test-cross-model-e2e.sh Outdated
#
# Models tested:
# 1. Gemini (google/antigravity-gemini-3-pro-low, gemini-3-flash)
# 1. Gemini (google/antigravity-gemini-3.1-pro-low, gemini-3.5-flash-medium)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the model ID typo in the header comment.

The second model in the comment is missing the google/antigravity- prefix, which can confuse manual reruns/copy-paste from this script.

Proposed fix
-# 1. Gemini (google/antigravity-gemini-3.1-pro-low, gemini-3.5-flash-medium)
+# 1. Gemini (google/antigravity-gemini-3.1-pro-low, google/antigravity-gemini-3.5-flash-medium)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 1. Gemini (google/antigravity-gemini-3.1-pro-low, gemini-3.5-flash-medium)
# 1. Gemini (google/antigravity-gemini-3.1-pro-low, google/antigravity-gemini-3.5-flash-medium)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/test-cross-model-e2e.sh` at line 6, Update the header comment that
lists the Gemini models to correct the second model ID by adding the missing
google/antigravity- prefix so both entries match the full model IDs (change
"gemini-3.5-flash-medium" to "google/antigravity-gemini-3.5-flash-medium" in the
comment line that starts with "1. Gemini").

Comment thread src/antigravity/oauth.ts
Comment on lines +31 to +56
.replace(/([?&]code=)[^&\s"']+/gi, "$1[REDACTED]")
// OAuth state as query/body params. State is now opaque, but still sensitive enough to redact.
.replace(/([?&]state=)[^&\s"']+/gi, "$1[REDACTED]")
// Authorization codes as JSON keys ("code=...":"...")
.replace(/"code=[^"]*"(\s*:\s*"[^"]*")?/gi, '"code=[REDACTED]"')
// Authorization codes as JSON fields
.replace(/"code"\s*:\s*"[^"]+"/gi, '"code":"[REDACTED]"')
// OAuth state as JSON fields
.replace(/"state"\s*:\s*"[^"]+"/gi, '"state":"[REDACTED]"')
// Refresh tokens (common patterns in JSON error bodies)
.replace(/"refresh_token"\s*:\s*"[^"]+"/gi, '"refresh_token":"[REDACTED]"')
.replace(/(refresh_token=)[^&\s"']+/gi, "$1[REDACTED]")
// Access tokens
.replace(/"access_token"\s*:\s*"[^"]+"/gi, '"access_token":"[REDACTED]"')
.replace(/(access_token=)[^&\s"']+/gi, "$1[REDACTED]")
// ID tokens and client secrets
.replace(/"id_token"\s*:\s*"[^"]+"/gi, '"id_token":"[REDACTED]"')
.replace(/(id_token=)[^&\s"']+/gi, "$1[REDACTED]")
.replace(/"client_secret"\s*:\s*"[^"]+"/gi, '"client_secret":"[REDACTED]"')
.replace(/(client_secret=)[^&\s"']+/gi, "$1[REDACTED]")
// Bearer tokens in Authorization headers
.replace(/(Bearer\s+)[A-Za-z0-9\-._~+/]+=*/gi, "$1[REDACTED]")
// PKCE verifiers (code_verifier= query/body param)
.replace(/(code_verifier=)[^&\s"']+/gi, "$1[REDACTED]")
// PKCE challenges (code_challenge= query param)
.replace(/(code_challenge=)[^&\s"']+/gi, "$1[REDACTED]");

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redaction misses sensitive params at start-of-string.

Line 31 and related patterns only match ?code=/&code= style params. Inputs like code=... or state=... at string start bypass redaction.

🔒 Suggested regex hardening
-    .replace(/([?&]code=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])code=[^&\s"']+/gi, "$1code=[REDACTED]")
-    .replace(/([?&]state=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])state=[^&\s"']+/gi, "$1state=[REDACTED]")
 ...
-    .replace(/(refresh_token=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])refresh_token=[^&\s"']+/gi, "$1refresh_token=[REDACTED]")
-    .replace(/(access_token=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])access_token=[^&\s"']+/gi, "$1access_token=[REDACTED]")
-    .replace(/(id_token=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])id_token=[^&\s"']+/gi, "$1id_token=[REDACTED]")
-    .replace(/(client_secret=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])client_secret=[^&\s"']+/gi, "$1client_secret=[REDACTED]")
-    .replace(/(code_verifier=)[^&\s"']+/gi, "$1[REDACTED]")
+    .replace(/(^|[?&])code_verifier=[^&\s"']+/gi, "$1code_verifier=[REDACTED]")
-    .replace(/(code_challenge=)[^&\s"']+/gi, "$1[REDACTED]");
+    .replace(/(^|[?&])code_challenge=[^&\s"']+/gi, "$1code_challenge=[REDACTED]");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/antigravity/oauth.ts` around lines 31 - 56, The current regexes only
match parameters when preceded by ? or &, so "code=..." or "state=..." at the
start of the string bypasses redaction; update each pattern that begins with
([?&]PARAM=) to allow start-of-string by changing it to
/(^|[?&])PARAM=[^&\s"']+/gi and change the replacement to include the captured
prefix and the param name (e.g., "$1code=[REDACTED]" for the patterns
referencing code), applying this change for code, state, refresh_token,
access_token, id_token, client_secret, code_verifier, and code_challenge in the
same replace chain in src/antigravity/oauth.ts so start-of-string params are
redacted.

Comment thread src/plugin.ts
Comment on lines +1490 to +1546
// Cache agy auth state per plugin session to avoid probing on every request.
// Null means not yet checked; "unknown" means probe was inconclusive.
let cachedAgyAuthState: "authenticated" | "unauthenticated" | "unknown" | null = null
let cachedAgyBinaryPath: string | null = null

return {
apiKey: "",
async fetch(input, init) {
if (!isGenerativeLanguageRequest(input)) {
if (!transport.matches(input)) {
return fetch(input, init);
}

// Non-OAuth transports skip account management entirely
if (!transport.auth.requiresOAuth) {
const debugLines: string[] = []
const pushDebug = (line: string) => {
if (!isDebugEnabled()) return
debugLines.push(line)
}
pushDebug(`transport=${transport.id}`)

try {
const prepared = transport.prepareRequest({
input,
init,
accessToken: "",
projectId: "",
headerStyle: "antigravity",
})

let backendResponse: Response

if (transport.id === "cli") {
// CliTransport: execute agy binary.
// Binary detection and auth state are cached per plugin session
// to avoid spawning agy on every request.
if (cachedAgyBinaryPath === null) {
const configuredBinary = config.transport?.cli?.binary
const detectedBinary = configuredBinary ? null : await findAgyBinary()
cachedAgyBinaryPath = configuredBinary ?? detectedBinary?.path ?? ""
}
const binaryPath = cachedAgyBinaryPath
if (!binaryPath) {
return Response.json({
error: {
code: 404,
status: "AGY_NOT_FOUND",
message: "Could not find agy binary. Install Antigravity CLI (https://antigravity.google/cli) or set transport.cli.binary in config.",
},
}, { status: 404 })
}

if (cachedAgyAuthState === null) {
cachedAgyAuthState = await checkAgyAuthState(binaryPath)
}
if (cachedAgyAuthState === "unauthenticated") {
return Response.json({

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid sticky agy auth/binary cache failures across the whole session.

Line 1542 only probes auth when cache is null. If first probe returns unauthenticated (or no binary), this path remains blocked until plugin restart even after the user fixes it.

♻️ Recovery-friendly cache behavior
-                if (cachedAgyBinaryPath === null) {
+                if (cachedAgyBinaryPath === null || cachedAgyBinaryPath === "") {
                   const configuredBinary = config.transport?.cli?.binary
                   const detectedBinary = configuredBinary ? null : await findAgyBinary()
                   cachedAgyBinaryPath = configuredBinary ?? detectedBinary?.path ?? ""
                 }
                 const binaryPath = cachedAgyBinaryPath
@@
-                if (cachedAgyAuthState === null) {
+                if (cachedAgyAuthState !== "authenticated") {
                   cachedAgyAuthState = await checkAgyAuthState(binaryPath)
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin.ts` around lines 1490 - 1546, The current logic in the cli
transport block caches negative results permanently: cachedAgyBinaryPath and
cachedAgyAuthState are set even when no binary is found or when
checkAgyAuthState returns "unauthenticated", which blocks recovery until plugin
restart; change the caching policy so only positive findings are sticky: set
cachedAgyBinaryPath only when a valid binary path is found (leave it null if not
found) and set cachedAgyAuthState to "authenticated" only when checkAgyAuthState
returns that; do not cache "unauthenticated" (keep it null or "unknown") so
subsequent requests will re-run findAgyBinary() and checkAgyAuthState() and
allow recovery after the user installs or signs in; update the cli branch where
findAgyBinary(), cachedAgyBinaryPath and checkAgyAuthState() are used to
implement this behavior.

Comment on lines +37 to +53
function execFileText(
file: string,
args: string[],
timeoutMs: number,
): Promise<AgyResult> {
return new Promise((resolve) => {
execFile(file, args, { timeout: timeoutMs }, (err, stdout, stderr) => {
const nodeErr = err as NodeJS.ErrnoException | null
resolve({
stdout: String(stdout ?? ""),
stderr: String(stderr ?? ""),
exitCode: typeof nodeErr?.code === "number" ? nodeErr.code : err ? 1 : 0,
timedOut: Boolean((nodeErr as unknown as Record<string, unknown>)?.killed) || nodeErr?.code === "ETIMEDOUT",
})
})
})
}

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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current execFile option usage and absence/presence of maxBuffer in this module.
rg -n 'execFile\(' src/plugin/transport/agy-cli.ts -C2
rg -n 'maxBuffer' src/plugin/transport/agy-cli.ts || true

Repository: NoeFabris/opencode-antigravity-auth

Length of output: 304


Set maxBuffer for execFile in execFileText.

src/plugin/transport/agy-cli.ts calls execFile(file, args, { timeout: timeoutMs }, ...) without any maxBuffer setting (no maxBuffer found in the file). For large agy stdout/stderr, Node’s default buffering limit can be exceeded, leading to flaky CLI transport failures—add an explicit maxBuffer (or switch to streaming).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/transport/agy-cli.ts` around lines 37 - 53, The execFileText
function calls execFile without a maxBuffer which can cause stdout/stderr
truncation for large outputs; update execFileText to pass a larger maxBuffer in
the options object passed to execFile (e.g., { timeout: timeoutMs, maxBuffer:
<suitable byte size> }) or switch execFile usage to a streaming approach (spawn
and stream stdout/stderr) if you expect arbitrarily large output; locate the
execFile call inside execFileText and either add the maxBuffer option or
refactor execFileText to use spawn and stream handling to avoid Node's default
buffer limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89f3ed8e65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +159 to +160
const body = readJsonBody(ctx.init)
const input = extractInput(body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Read Managed Agent input from RequestInfo body

prepareRequest only parses ctx.init.body, so calls that pass a Request object (with JSON body attached to ctx.input and init omitted) are treated as empty input and fail with "could not extract input text." Since the transport API accepts RequestInfo, this breaks valid managed-agent requests in the same interoperability scenario already handled elsewhere.

Useful? React with 👍 / 👎.

*/
function getModelFamilyFromUrl(urlString: string): string {
const model = extractModelFromUrl(urlString)
return model?.includes("claude") ? "claude" : "gemini"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify gpt-oss separately from Gemini fallback pool

This family inference buckets every non-Claude model as gemini, which causes the new antigravity-gpt-oss-120b-medium path to participate in Gemini quota fallback logic (allowQuotaFallback), including switching to gemini-cli headers/endpoints when rate-limited. That fallback is incompatible with the gpt-oss model route and can turn recoverable Antigravity throttling into hard request failures for that model.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e39f134e4a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/plugin.ts
Comment on lines +1502 to +1503
// Non-OAuth transports skip account management entirely
if (!transport.auth.requiresOAuth) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow non-OAuth transports to initialize without OAuth auth

The new cli/managed-agent transports are configured here as non-OAuth, but they still cannot run in an unauthenticated session because loader returns {} earlier when !isOAuthAuth(auth) (lines 1423–1429), so this fetch path is never installed in that scenario. In practice this makes the new non-OAuth transport feature unusable unless users already have OAuth credentials, which contradicts the transport auth contract and breaks the advertised flow for these backends.

Useful? React with 👍 / 👎.

Comment thread src/plugin/version.ts
Comment on lines +107 to +110
// 1. Try local agy binary (most accurate — reflects what's actually installed)
version = await tryLocalAgyVersion();
if (version) {
source = "api";
source = "local-agy";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prefer remote/latest version over local agy version for headers

This now prioritizes agy --version over the remote updater/changelog version. If a user has an older local agy binary, the plugin will send stale Antigravity version headers even when newer supported versions are available remotely, which can reintroduce the “version no longer supported” request failures this initializer is meant to prevent. The local binary version is not a safe source of truth for gateway compatibility.

Useful? React with 👍 / 👎.

Comment thread src/plugin.ts
Comment on lines +1542 to +1553
if (cachedAgyAuthState === null) {
cachedAgyAuthState = await checkAgyAuthState(binaryPath)
}
if (cachedAgyAuthState === "unauthenticated") {
return Response.json({
error: {
code: 401,
status: "UNAUTHENTICATED",
message: "agy is not logged into Antigravity. Run `agy` manually in a terminal and complete the browser OAuth flow.",
},
}, { status: 401 })
}

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.

P1 Permanent auth lockout within the plugin session

Once cachedAgyAuthState is set to "unauthenticated", every subsequent request in that OpenCode session returns HTTP 401 — the cache is never invalidated. The error message tells users to "Run agy manually in a terminal and complete the browser OAuth flow," implying that action will unblock them. It won't: after they log in via agy, the next request still hits the cached "unauthenticated" guard and returns 401 again. The only recovery is restarting OpenCode entirely, which nothing in the error output hints at.

For contrast, the "unknown" state falls through to executeAgyCommand, which re-probes agy on every call and can recover naturally. The "unauthenticated" branch should either be given the same retry behaviour, or the error message must explicitly instruct the user to restart OpenCode after logging in.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin.ts
Line: 1542-1553

Comment:
**Permanent auth lockout within the plugin session**

Once `cachedAgyAuthState` is set to `"unauthenticated"`, every subsequent request in that OpenCode session returns HTTP 401 — the cache is never invalidated. The error message tells users to "Run `agy` manually in a terminal and complete the browser OAuth flow," implying that action will unblock them. It won't: after they log in via `agy`, the next request still hits the cached `"unauthenticated"` guard and returns 401 again. The only recovery is restarting OpenCode entirely, which nothing in the error output hints at.

For contrast, the `"unknown"` state falls through to `executeAgyCommand`, which re-probes agy on every call and can recover naturally. The `"unauthenticated"` branch should either be given the same retry behaviour, or the error message must explicitly instruct the user to restart OpenCode after logging in.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +184 to +186
return {
request: INTERACTIONS_ENDPOINT,
init: {

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.

P1 When config.stream is true, prepareRequest sets streaming: true and transformResponse returns the backend response as-is (expecting SSE), but the outgoing request goes to INTERACTIONS_ENDPOINT without any streaming indicator. The Generative Language API requires ?alt=sse on the URL to switch to SSE format; without it the server returns a regular JSON body, which the streaming branch never parses. Every streaming request will silently deliver a raw JSON blob to the caller instead of the expected SSE event stream.

Suggested change
return {
request: INTERACTIONS_ENDPOINT,
init: {
const endpoint = config.stream
? `${INTERACTIONS_ENDPOINT}?alt=sse`
: INTERACTIONS_ENDPOINT
return {
request: endpoint,
init: {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin/transport/managed-agent-transport.ts
Line: 184-186

Comment:
When `config.stream` is `true`, `prepareRequest` sets `streaming: true` and `transformResponse` returns the backend response as-is (expecting SSE), but the outgoing request goes to `INTERACTIONS_ENDPOINT` without any streaming indicator. The Generative Language API requires `?alt=sse` on the URL to switch to SSE format; without it the server returns a regular JSON body, which the streaming branch never parses. Every streaming request will silently deliver a raw JSON blob to the caller instead of the expected SSE event stream.

```suggestion
      const endpoint = config.stream
        ? `${INTERACTIONS_ENDPOINT}?alt=sse`
        : INTERACTIONS_ENDPOINT

      return {
        request: endpoint,
        init: {
```

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41beb3dcbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/plugin.ts
Comment on lines +1526 to +1530
if (cachedAgyBinaryPath === null) {
const configuredBinary = config.transport?.cli?.binary
const detectedBinary = configuredBinary ? null : await findAgyBinary()
cachedAgyBinaryPath = configuredBinary ?? detectedBinary?.path ?? ""
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-probe agy binary after an initial miss

When findAgyBinary() fails once, this assignment caches "" in cachedAgyBinaryPath; subsequent requests skip detection because the guard only checks for null. In a common recovery path (user installs agy or fixes PATH after the first failure), the plugin keeps returning AGY_NOT_FOUND for the rest of the session, even though the binary is now available.

Useful? React with 👍 / 👎.

},
body: JSON.stringify(requestBody),
},
streaming: config.stream,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive managed-agent streaming from request semantics

This transport marks every request as streaming purely from static config, but prepareRequest does not include any stream directive in the outbound Interactions payload. In transformResponse, prepared.streaming=true bypasses normalization and returns the raw backend response, so enabling transport.managed_agent.stream can return an Interactions-native payload instead of Gemini-compatible candidates output, breaking callers that rely on the plugin’s normalized response shape.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/plugin/transform/model-resolver.ts (1)

233-236: 💤 Low value

Dead ternary — both branches return "low".

defaultThinkingLevel evaluates to "low" regardless of whether the model starts with gemini-3.5-flash, so the conditional and the dedicated comment about 3.5-flash add no behavior. Either collapse to a constant, or — if Pro and 3.5-Flash defaults are expected to diverge later — pick the intended distinct value now so the special case is real.

♻️ Suggested simplification
-    if (isEffectiveGemini3) {
-      const defaultThinkingLevel = resolvedModel.toLowerCase().startsWith("gemini-3.5-flash")
-        ? "low"
-        : "low";
-
-      return {
-        actualModel: resolvedModel,
-        thinkingLevel: defaultThinkingLevel,
+    if (isEffectiveGemini3) {
+      return {
+        actualModel: resolvedModel,
+        thinkingLevel: "low",
         isThinkingModel: true,
         quotaPreference,
         explicitQuota,
       };
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugin/transform/model-resolver.ts` around lines 233 - 236, The ternary
assigning defaultThinkingLevel under the isEffectiveGemini3 branch is dead (both
outcomes are "low"); replace the conditional with a plain constant assignment
(const defaultThinkingLevel = "low") and remove or update any 3.5-flash-specific
comment, or if you intend a different default for gemini-3.5-flash, change the
second branch to the correct value; update references to
isEffectiveGemini3/resolvedModel accordingly so the special-case no longer
implies differing behavior.
docs/MODEL-VARIANTS.md (1)

61-66: ⚡ Quick win

Clarify the Claude section wording to avoid “thinking” ambiguity.

Line 63 says “Current Claude Thinking rows,” but Line 65 lists google/antigravity-claude-sonnet-4-6 (non-thinking). Renaming this subsection/body text would avoid misinterpretation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/MODEL-VARIANTS.md` around lines 61 - 66, The wording "Claude thinking"
is ambiguous because the list includes both non-thinking and thinking model IDs
(e.g., google/antigravity-claude-sonnet-4-6 and
google/antigravity-claude-opus-4-6-thinking); rename the subsection and rephrase
the sentence to clarify that these are Claude model entries and that models with
a "-thinking" suffix indicate higher-budget variants, and explicitly state the
plugin behavior that it enables the high-budget "thinking" variant by default
when no tier/budget is provided (update the text around "Current Claude Thinking
rows are direct model IDs" to something like "Claude model entries and
thinking-budget variants" and add the note about "-thinking" suffix and plugin
default).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/ISSUE_TEMPLATE/bug_report.yml:
- Around line 46-47: The dropdown in .github/ISSUE_TEMPLATE/bug_report.yml
contains a duplicated option "antigravity-gemini-3.5-flash-low"; update that
second entry to the intended variant (e.g.,
"antigravity-gemini-3.5-flash-medium" per the PR and smoke-test) or remove the
duplicate so the list matches the actual keys exposed by
OPENCODE_MODEL_DEFINITIONS / resolver aliases; ensure the final set of options
aligns with the model names used elsewhere (flash-medium/flash-high) to avoid
duplicate/incorrect choices.

In `@docs/MODEL-VARIANTS.md`:
- Around line 11-12: Remove the duplicate Gemini 3.5 Flash (Low) quota row and
ensure the model ID google/antigravity-gemini-3.5-flash-low appears only once;
consolidate the repeated row name at line 28 into a single entry and resolve the
conflicting thinkingLevel mappings by choosing the correct thinkingLevel value
for google/antigravity-gemini-3.5-flash-low and updating the thinkingLevel key
so each model ID maps to exactly one thinkingLevel; verify no other duplicate
rows remain and keep the table entries and model→thinkingLevel mapping
consistent.

---

Nitpick comments:
In `@docs/MODEL-VARIANTS.md`:
- Around line 61-66: The wording "Claude thinking" is ambiguous because the list
includes both non-thinking and thinking model IDs (e.g.,
google/antigravity-claude-sonnet-4-6 and
google/antigravity-claude-opus-4-6-thinking); rename the subsection and rephrase
the sentence to clarify that these are Claude model entries and that models with
a "-thinking" suffix indicate higher-budget variants, and explicitly state the
plugin behavior that it enables the high-budget "thinking" variant by default
when no tier/budget is provided (update the text around "Current Claude Thinking
rows are direct model IDs" to something like "Claude model entries and
thinking-budget variants" and add the note about "-thinking" suffix and plugin
default).

In `@src/plugin/transform/model-resolver.ts`:
- Around line 233-236: The ternary assigning defaultThinkingLevel under the
isEffectiveGemini3 branch is dead (both outcomes are "low"); replace the
conditional with a plain constant assignment (const defaultThinkingLevel =
"low") and remove or update any 3.5-flash-specific comment, or if you intend a
different default for gemini-3.5-flash, change the second branch to the correct
value; update references to isEffectiveGemini3/resolvedModel accordingly so the
special-case no longer implies differing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2d011a99-d5b4-4477-9268-e1c175e55548

📥 Commits

Reviewing files that changed from the base of the PR and between 41beb3d and fd15ddd.

📒 Files selected for processing (18)
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • CHANGELOG.md
  • README.md
  • docs/ANTIGRAVITY_AUTH_RECOVERY_PLAN.md
  • docs/MODEL-VARIANTS.md
  • docs/MULTI-ACCOUNT.md
  • docs/SUPPORT_MATRIX.md
  • docs/TROUBLESHOOTING.md
  • script/test-cross-model-e2e.sh
  • script/test-gemini-cli-e2e.sh
  • script/test-models.ts
  • script/test-regression.ts
  • scripts/setup-opencode-pi.sh
  • src/plugin/config/models.test.ts
  • src/plugin/config/models.ts
  • src/plugin/config/updater.test.ts
  • src/plugin/transform/model-resolver.test.ts
  • src/plugin/transform/model-resolver.ts
✅ Files skipped from review due to trivial changes (5)
  • docs/MULTI-ACCOUNT.md
  • docs/SUPPORT_MATRIX.md
  • CHANGELOG.md
  • docs/TROUBLESHOOTING.md
  • docs/ANTIGRAVITY_AUTH_RECOVERY_PLAN.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🔇 Additional comments (12)
script/test-cross-model-e2e.sh (2)

6-6: 💤 Low value

Header comment still missing google/antigravity- prefix on the second model id.

After the rename to gemini-3.5-flash-low, the second entry in the header listing is still bare and inconsistent with the first. This was previously flagged and not addressed in this revision.

🛠️ Proposed fix
-# 1. Gemini (google/antigravity-gemini-3.1-pro-low, gemini-3.5-flash-low)
+# 1. Gemini (google/antigravity-gemini-3.1-pro-low, google/antigravity-gemini-3.5-flash-low)

206-206: LGTM!

Also applies to: 246-246

src/plugin/config/updater.test.ts (1)

70-70: LGTM!

Also applies to: 238-238

script/test-models.ts (1)

6-21: LGTM!

Also applies to: 101-101

script/test-gemini-cli-e2e.sh (1)

1-12: LGTM!

Also applies to: 118-119, 133-134

src/plugin/config/models.ts (1)

40-71: ⚡ Quick win

Quota-row count diverges from PR description (6 here vs. "7 quota rows" in objectives).

PR objectives list 7 Antigravity rows including antigravity-gemini-3.5-flash-medium, antigravity-gemini-3.5-flash-high, and antigravity-claude-sonnet-4-6-thinking, plus mention flash-medium as the validated smoke-test target. This file exposes 6 rows (only flash-low, only the non--thinking Sonnet). If the consolidation to single live rows + resolver aliases is intentional, please update the PR description and bug-report dropdown to match; otherwise, the missing rows need to be added back here.

src/plugin/transform/model-resolver.ts (1)

196-204: LGTM!

Also applies to: 261-274, 293-305

script/test-regression.ts (1)

55-57: Confirm GEMINI_FLASH_CLI_QUOTA removal has no remaining references

Search across the repository shows zero occurrences of GEMINI_FLASH_CLI_QUOTA, so there shouldn’t be any stale imports/usage remaining.

src/plugin/transform/model-resolver.test.ts (1)

9-12: LGTM!

Also applies to: 138-167, 205-211

scripts/setup-opencode-pi.sh (1)

71-73: LGTM!

Also applies to: 122-124

README.md (1)

9-9: LGTM!

Also applies to: 117-120, 126-127, 180-182, 231-233, 443-443, 581-583

src/plugin/config/models.test.ts (1)

18-22: LGTM!

Also applies to: 26-30, 33-33

Comment on lines +46 to +47
- antigravity-gemini-3.5-flash-low
- antigravity-gemini-3.5-flash-low

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duplicate dropdown option — second flash-low likely meant flash-medium or flash-high.

Both entries are identical, so the dropdown has two indistinguishable rows. Either drop the duplicate or replace one with the intended variant (the PR objectives reference both flash-medium and flash-high, and the validated smoke-test in the PR description used antigravity-gemini-3.5-flash-medium).

🛠️ Proposed fix
         - antigravity-gemini-3.1-pro-low
         - antigravity-gemini-3.1-pro-high
         - antigravity-gemini-3.5-flash-low
-        - antigravity-gemini-3.5-flash-low
+        - antigravity-gemini-3.5-flash-medium
+        - antigravity-gemini-3.5-flash-high
         - antigravity-claude-sonnet-4-6
+        - antigravity-claude-sonnet-4-6-thinking
         - antigravity-claude-opus-4-6-thinking
         - antigravity-gpt-oss-120b-medium

Adjust the added rows to match whatever is actually exposed by OPENCODE_MODEL_DEFINITIONS / resolver aliases.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- antigravity-gemini-3.5-flash-low
- antigravity-gemini-3.5-flash-low
- antigravity-gemini-3.5-flash-low
- antigravity-gemini-3.5-flash-medium
- antigravity-gemini-3.5-flash-high
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/ISSUE_TEMPLATE/bug_report.yml around lines 46 - 47, The dropdown in
.github/ISSUE_TEMPLATE/bug_report.yml contains a duplicated option
"antigravity-gemini-3.5-flash-low"; update that second entry to the intended
variant (e.g., "antigravity-gemini-3.5-flash-medium" per the PR and smoke-test)
or remove the duplicate so the list matches the actual keys exposed by
OPENCODE_MODEL_DEFINITIONS / resolver aliases; ensure the final set of options
aligns with the model names used elsewhere (flash-medium/flash-high) to avoid
duplicate/incorrect choices.

Comment thread docs/MODEL-VARIANTS.md
Comment on lines +11 to +12
| Gemini 3.5 Flash (Low) | `google/antigravity-gemini-3.5-flash-low` |
| Gemini 3.5 Flash (Low) | `google/antigravity-gemini-3.5-flash-low` |

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix duplicate and contradictory Gemini 3.5 Flash documentation entries.

Line 11 and Line 12 duplicate the same quota row/model ID, Line 28 repeats the same row name twice, and Line 34/Line 35 map the same model ID to two different thinkingLevel values. This is internally inconsistent and likely to confuse model selection/migration.

Suggested doc fix
-| Gemini 3.5 Flash (Low) | `google/antigravity-gemini-3.5-flash-low` |
-| Gemini 3.5 Flash (Low) | `google/antigravity-gemini-3.5-flash-low` |
+| Gemini 3.5 Flash (Low) | `google/antigravity-gemini-3.5-flash-low` |

-The current Antigravity quota UI exposes rows such as "Gemini 3.5 Flash (Low)" and "Gemini 3.5 Flash (Low)". OpenCode only treats configured model keys as selectable model IDs in all contexts, including scripts and agent configs. Direct IDs therefore avoid "Model not found" errors in non-interactive usage.
+The current Antigravity quota UI exposes rows such as "Gemini 3.5 Flash (Low)". OpenCode only treats configured model keys as selectable model IDs in all contexts, including scripts and agent configs. Direct IDs therefore avoid "Model not found" errors in non-interactive usage.

-- `antigravity-gemini-3.5-flash-low` → API model `gemini-3.5-flash-low` + `thinkingLevel: "medium"`
-- `antigravity-gemini-3.5-flash-low` → API model `gemini-3.5-flash-low` + `thinkingLevel: "high"`
+- `antigravity-gemini-3.5-flash-low` → API model `gemini-3.5-flash-low` + `thinkingLevel: "low"`
+- `antigravity-gemini-3.5-flash-medium` / `antigravity-gemini-3.5-flash-high` → alias to `gemini-3.5-flash-low` + `thinkingLevel: "low"`

Also applies to: 28-28, 34-35

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/MODEL-VARIANTS.md` around lines 11 - 12, Remove the duplicate Gemini 3.5
Flash (Low) quota row and ensure the model ID
google/antigravity-gemini-3.5-flash-low appears only once; consolidate the
repeated row name at line 28 into a single entry and resolve the conflicting
thinkingLevel mappings by choosing the correct thinkingLevel value for
google/antigravity-gemini-3.5-flash-low and updating the thinkingLevel key so
each model ID maps to exactly one thinkingLevel; verify no other duplicate rows
remain and keep the table entries and model→thinkingLevel mapping consistent.

@ErcinDedeoglu

Copy link
Copy Markdown

so?

@insign

insign commented Jun 8, 2026

Copy link
Copy Markdown

Not trying to spam, but I've merged similar PR (#576) to https://github.com/insign/opencode-antigravity-auth-updated and it is easy available using "plugin": ["opencode-antigravity-auth-updated@latest"], but be aware it comes with no guarantees but it is working perfecly for me.

@chrisgeo

chrisgeo commented Jun 8, 2026

Copy link
Copy Markdown

@insign if you want a cleaner deploy, reset your main to chrisgeo/opencode-antigravity-auth#main

It'll eliminate any extraneous changes that may be causing the spam.

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.

4 participants