Files
oh-my-opencode-free-fork/ai-todolist.md
YeonGyu-Kim 7bfca25958 feat(google-antigravity-auth): create auth plugin for Google models
- Implement createGoogleAntigravityAuthPlugin factory function
- Add OAuth method with PKCE for Google authentication
- Create custom fetch interceptor loader for Antigravity API
- Update auth.ts to export Google Antigravity plugin as default
- Update barrel export in antigravity/index.ts
- Add Google Antigravity auth location to AGENTS.md

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-13 00:35:34 +09:00

37 KiB

Antigravity Auth Plugin Implementation Plan

Date: 2025-12-12 Branch: feature/antigravity-auth Status: REVISION 1 - Addressing reviewer feedback


현재 진행 중인 작업

모든 작업 완료 - Phase 4 완료


Revision History

Version Date Changes
1.0 2025-12-12 Initial plan
1.1 2025-12-12 Added tool normalization tasks, clarified provider ID, fixed export structure, added model mapping

User's Original Request

~/tools/cliproxyapi ultrasearch, and make a plan for "antigravity" auth from there to be implemented as openai codex auth implemented on here; use https://github.com/numman-ali/opencode-openai-codex-auth https://github.com/NoeFabris/opencode-antigravity-auth as a reference and also write it on the plan file. see how those auth providing plugin can be done by exploring of ~/local-workspaces/opencode and write me a full plan workable --review


References (CRITICAL)

External Repositories

Repository Purpose Key Files
numman-ali/opencode-openai-codex-auth Reference for OpenCode auth plugin structure index.ts (root), lib/request/fetch-helpers.ts, lib/auth/oauth.ts
NoeFabris/opencode-antigravity-auth Reference for Antigravity-specific implementation src/plugin.ts, src/antigravity/oauth.ts, src/plugin/request.ts

Local Codebase

Location Purpose
~/tools/cliproxyapi/sdk/auth/antigravity.go Original Go implementation to port
~/tools/cliproxyapi/internal/cmd/antigravity_login.go CLI login flow reference
~/local-workspaces/opencode/packages/plugin/src/index.ts AuthHook interface definition
~/local-workspaces/opencode/packages/opencode/src/provider/auth.ts Auth provider registration

Context Gathered

1. cliproxyapi Antigravity Auth (Go Implementation)

AntigravityAuthenticator Key Methods:

func (AntigravityAuthenticator) Provider() string { return "antigravity" }
func (AntigravityAuthenticator) Login() // OAuth flow with PKCE

OAuth Configuration:

  • Client ID: 1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com
  • Client Secret: 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
  • Redirect URI: http://localhost:51121/oauth-callback
  • Scopes: cloud-platform, userinfo.email, userinfo.profile, cclog, experimentsandconfigs

Auth Flow:

  1. Generate PKCE verifier/challenge
  2. Build OAuth URL with state containing verifier
  3. Open browser for Google OAuth
  4. Receive callback with code
  5. Exchange code for tokens
  6. Fetch user info (email)
  7. Fetch project ID via loadCodeAssist API
  8. Store tokens with project context

Token Storage Format:

{
  "type": "antigravity",
  "access_token": "...",
  "refresh_token": "...",
  "expires_in": 3600,
  "timestamp": 1640995200000,
  "email": "user@example.com",
  "project_id": "my-gcp-project"
}

2. OpenCode Auth Plugin Interface

AuthHook Interface (from packages/plugin/src/index.ts):

export type AuthHook = {
  provider: string
  loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
  methods: Array<{
    type: "oauth" | "api"
    label: string
    prompts?: Array<{ type: "text" | "select", key: string, message: string }>
    authorize?(inputs?: Record<string, string>): Promise<AuthOuathResult>
  }>
}

export type AuthOuathResult = { url: string; instructions: string } & (
  | { method: "auto"; callback(): Promise<{ type: "success" | "failed", ... }> }
  | { method: "code"; callback(code: string): Promise<{ type: "success" | "failed", ... }> }
)

3. opencode-openai-codex-auth Pattern

Plugin Structure:

opencode-openai-codex-auth/
├── index.ts                    # Main export
├── src/plugin.ts               # createCodexAuthPlugin()
├── lib/
│   ├── auth/oauth.ts           # OAuth flow
│   ├── request/
│   │   ├── fetch-helpers.ts    # Custom fetch interceptor
│   │   ├── request-transformer.ts
│   │   └── response-handler.ts
│   └── prompts/codex.ts        # Model-specific prompts

Key Pattern:

  • createPlugin() returns { hooks: { auth: AuthHook } }
  • loader() returns { fetch: customFetch } for request interception
  • Token refresh handled in custom fetch

4. opencode-antigravity-auth Pattern

Unique Features:

  • Endpoint fallback: daily → autopush → prod
  • Project context via loadCodeAssist API
  • Tool normalization for Claude/Gemini
  • Thinking block support
  • Token storage: refreshToken|projectId|managedProjectId

Endpoint Fallbacks:

ANTIGRAVITY_ENDPOINT_FALLBACKS = [
  "https://daily-cloudcode-pa.sandbox.googleapis.com",      // dev
  "https://autopush-cloudcode-pa.sandbox.googleapis.com",   // staging
  "https://cloudcode-pa.googleapis.com"                     // prod
]

Assumptions Made (User Did Not Respond)

Question Assumed Answer Rationale
Package structure B) Internal implementation More control, easier to customize
Existing auth relation A) Add alongside (coexist) Non-breaking change
Feature scope A) Full features Match reference implementations
Endpoint fallback A) All environments Maximum compatibility
Client credentials A) Hardcoded Match cliproxyapi and references
Tool normalization A) Include Full feature parity
Definition of Done C) Full conversation working Complete verification

User can override these assumptions by responding to the clarifying questions.


Critical Design Decisions (CORRECTED)

Provider ID Decision

Provider ID MUST be "google".

Rationale:

  • Antigravity is just an auth mechanism for Google models
  • Provider in OpenCode = the model provider (Google)
  • User selects google provider, then chooses Antigravity as auth method

UX in OpenCode:

opencode auth login
> Select provider: google
> Select auth method:
  - API Key
  - OAuth with Google (Antigravity)  <-- NEW

Export Structure (SIMPLE - No Migration)

Keep it simple like openai-codex-auth:

src/
├── auth.ts  # export { GoogleAntigravityAuthPlugin as default } from "./auth/antigravity"

That's it. No complex migration. Just works.

Model Mapping

Supported Models (Google/Gemini ONLY):

OpenCode Model ID Antigravity Model Notes
google/gemini-3-pro-preview gemini-3-pro-preview Default
google/gemini-3-pro-high gemini-3-pro-high High thinking
google/gemini-2.5-pro gemini-2.5-pro Standard

NO Claude models - Antigravity is for Google models only.

Token Storage in OpenCode

Storage Mechanism:

  • Uses OpenCode's built-in Auth.set(providerID, data) and Auth.get(providerID)
  • providerID = "google" (matches AuthHook.provider)
  • loader callback receives auth() function to retrieve stored tokens

Token Format Stored:

{
  "type": "oauth",
  "access": "ya29.xxx...",
  "refresh": "1//xxx...|projectId|managedProjectId",
  "expires": 1702400000000
}

Client Credentials (Via OpenCode Provider Options)

Client credentials configured via opencode.json provider options.

NO prompts. Just works with defaults, optionally configurable:

// opencode.json (OPTIONAL - only if user wants custom credentials)
{
  "provider": {
    "google": {
      "options": {
        "clientId": "your-custom-client-id",
        "clientSecret": "your-custom-client-secret"
      }
    }
  }
}

Implementation:

// In loader function - reads from provider.options
loader: async (auth, provider) => {
  const clientId = provider.options?.clientId || DEFAULT_CLIENT_ID
  const clientSecret = provider.options?.clientSecret || DEFAULT_CLIENT_SECRET
  // Use these for fetch interceptor
  return { fetch: createAntigravityFetch(auth, clientId, clientSecret, ...) }
}

// In authorize function - also reads from provider context
methods: [{
  type: "oauth",
  label: "OAuth with Google (Antigravity)",
  // NO prompts - just authorize directly
  authorize: async () => {
    // clientId/clientSecret passed from loader context or use defaults
    // Start OAuth flow
  }
}]

User Experience:

opencode auth login
> Select provider: google
> Select auth method: OAuth with Google (Antigravity)
# Browser opens immediately - no prompts for credentials
# Uses defaults or whatever is in opencode.json options

Defaults (from cliproxyapi):

  • DEFAULT_CLIENT_ID: 1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com
  • DEFAULT_CLIENT_SECRET: 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

Relationship with openai-codex-auth

This plugin REPLACES openai-codex-auth in this repo.

  • src/auth.ts will export GoogleAntigravityAuthPlugin as default
  • If users need OpenAI Codex auth, they should use opencode-openai-codex-auth directly as a separate plugin
  • This is NOT a "coexistence" scenario - it's a "replacement" for this repo's auth

Rationale:

  • oh-my-opencode focuses on Google/Gemini via Antigravity
  • OpenAI users already have a well-maintained separate plugin
  • Keeps this codebase focused and simple

Token Format Responsibility

refresh token format: refreshToken|projectId|managedProjectId

Where it's created: token.tsformatTokenForStorage()

function formatTokenForStorage(
  refreshToken: string, 
  projectId: string, 
  managedProjectId?: string
): string {
  return `${refreshToken}|${projectId}|${managedProjectId || ""}`
}

Where it's parsed: token.tsparseStoredToken()

function parseStoredToken(stored: string): {
  refreshToken: string
  projectId: string
  managedProjectId?: string
} {
  const [refreshToken, projectId, managedProjectId] = stored.split("|")
  return { refreshToken, projectId, managedProjectId: managedProjectId || undefined }
}

Flow:

  1. OAuth callback receives refresh_token from Google
  2. project.ts fetches projectId via loadCodeAssist API
  3. token.ts combines them into refresh|projectId|managedProjectId
  4. Combined string stored via Auth.set(providerID, { refresh: combinedString, ... })
  5. On reload, token.ts parses back to individual components

Concrete Deliverables

Deliverable Location Description
Google Antigravity auth plugin src/auth/antigravity/plugin.ts Main createGoogleAntigravityAuthPlugin()
OAuth flow src/auth/antigravity/oauth.ts PKCE OAuth with configurable credentials
Token management src/auth/antigravity/token.ts Token refresh, storage format
Request transformer src/auth/antigravity/request.ts OpenAI → Gemini/Antigravity format
Response handler src/auth/antigravity/response.ts Gemini/Antigravity → OpenAI format
Tool normalization src/auth/antigravity/tools.ts OpenAI tools ↔ Gemini functionDeclarations
Thinking block handler src/auth/antigravity/thinking.ts Extract/format Gemini thinking blocks
Fetch interceptor src/auth/antigravity/fetch.ts Request interception with endpoint fallback
Project context src/auth/antigravity/project.ts loadCodeAssist API integration
Constants src/auth/antigravity/constants.ts Default OAuth config, endpoints, headers
Types src/auth/antigravity/types.ts TypeScript interfaces
Barrel export src/auth/antigravity/index.ts Module exports
Auth entry src/auth.ts Simple default export

Definition of Done

  • bun run typecheck passes with no errors
  • bun run build succeeds
  • opencode auth login shows "google" provider with "OAuth with Google (Antigravity)" method
  • NO prompts - OAuth starts immediately (credentials from options or defaults)
  • OAuth flow completes and stores tokens
  • Token auto-refresh works before expiration
  • loadCodeAssist API returns project ID
  • Endpoint fallback works (tries prod if daily fails)
  • API request transformation works (OpenAI → Gemini format)
  • API response transformation works (Gemini → OpenAI format)
  • Can have full conversation with Gemini model via Antigravity
  • Custom credentials work when configured in opencode.json provider options

Must Have

  • OAuth with PKCE: Google OAuth 2.0 with Proof Key for Code Exchange
  • Token Management: Access token refresh before expiration (60s buffer)
  • Project Context: loadCodeAssist API for automatic project discovery
  • Request Transformation: Convert OpenAI-format requests to Gemini/Antigravity format
  • Response Transformation: Convert Gemini/Antigravity responses to OpenAI format
  • Endpoint Fallback: Try multiple endpoints if primary fails
  • REPLACES openai-codex-auth: This repo becomes Google/Antigravity focused (OpenAI users use separate plugin)

Must NOT Have

  • No modification to existing auth.ts beyond adding export
  • No new npm dependencies (use existing @openauthjs/openauth for PKCE)
  • No test files (test framework not configured)
  • No over-abstraction (no "AuthProvider" base class)
  • No separate npm package (internal implementation only)
  • No changes to opencode.json schema
  • No breaking changes to existing functionality

Task Flow Diagram

Phase 1 (Foundation)
├── Task 1: Create types ──────────────────────┐
├── Task 2: Create constants ──────────────────┤ Parallel
└── Task 3: Create module structure ───────────┘

Phase 2 (OAuth Core)
├── Task 4: Implement OAuth flow ──────────────┐
├── Task 5: Implement token management ────────┤ Sequential (5 depends on 4)
└── Task 6: Implement project context ─────────┘ Sequential (6 depends on 5)

Phase 3 (Request/Response Transformation)
├── Task 7: Implement request transformer ─────┐
├── Task 8: Implement response handler ────────┤ Parallel
├── Task 9: Implement tool normalization ──────┤ Parallel (after 7, 8 design known)
├── Task 10: Implement thinking block handler ─┤ Parallel with 9
└── Task 11: Implement fetch interceptor ──────┘ Sequential (depends on 7-10)

Phase 4 (Plugin Assembly)
├── Task 12: Create main plugin ───────────────┐
├── Task 13: Migrate auth exports ─────────────┤ Sequential
└── Task 14: Final verification ───────────────┘ Sequential

Phase 1 (Foundation) ├── Task 1: Create types ──────────────────────┐ ├── Task 2: Create constants ──────────────────┤ Parallel └── Task 3: Create barrel export structure ────┘

Phase 2 (OAuth Core) ├── Task 4: Implement OAuth flow ──────────────┐ ├── Task 5: Implement token management ────────┤ Sequential (5 depends on 4) └── Task 6: Implement project context ─────────┘ Sequential (6 depends on 5)

Phase 3 (Request/Response) ├── Task 7: Implement request transformer ─────┐ ├── Task 8: Implement response handler ────────┤ Parallel └── Task 9: Implement fetch interceptor ───────┘ Sequential (9 depends on 7, 8)

Phase 4 (Plugin Assembly) ├── Task 10: Create main plugin ───────────────┐ ├── Task 11: Update auth exports ──────────────┤ Sequential └── Task 12: Final verification ───────────────┘ Sequential


---

## Tasks

### Phase 1: Foundation

- [x] **1. Create Antigravity auth types**

  **What to do**:
  - Create `src/auth/antigravity/types.ts`
  - Define `AntigravityTokens` interface (access_token, refresh_token, expires_in, timestamp, email, project_id)
  - Define `AntigravityProjectContext` interface
  - Define `AntigravityRequestBody` interface
  - Define `AntigravityResponse` interface

  **Must NOT do**:
  - Do NOT create abstract base classes
  - Do NOT add fields not in cliproxyapi implementation

  **Parallelizable**: YES (with Task 2, 3)

  **MUST READ first**:
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go` - token structure
  - Reference: NoeFabris/opencode-antigravity-auth `src/plugin/types.ts`

  **Acceptance Criteria**:
  - [x] All interfaces match cliproxyapi token format
  - [x] Types exported from file
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 3)

---

- [x] **2. Create constants**

  **What to do**:
  - Create `src/auth/antigravity/constants.ts`
  - Define `ANTIGRAVITY_CLIENT_ID`, `ANTIGRAVITY_CLIENT_SECRET`
  - Define `ANTIGRAVITY_REDIRECT_URI` = `http://localhost:51121/oauth-callback`
  - Define `ANTIGRAVITY_SCOPES` array
  - Define `ANTIGRAVITY_ENDPOINT_FALLBACKS` array
  - Define `ANTIGRAVITY_HEADERS` object
  - Define `ANTIGRAVITY_DEFAULT_PROJECT_ID`

  **Must NOT do**:
  - Do NOT put credentials in environment variables
  - Do NOT modify values from cliproxyapi

  **Parallelizable**: YES (with Task 1, 3)

  **MUST READ first**:
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go` - OAuth credentials
  - Reference: NoeFabris/opencode-antigravity-auth `src/constants.ts`

  **Acceptance Criteria**:
  - [ ] All OAuth constants match cliproxyapi
  - [ ] Endpoint fallbacks in correct order (daily, autopush, prod)
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 3)

---

- [x] **3. Create module structure** ✅ COMPLETED

  **What to do**:
  - Create `src/auth/antigravity/index.ts` barrel export
  - Create empty placeholder files for remaining modules
  - Ensure directory structure matches plan

  **Must NOT do**:
  - Do NOT implement actual logic yet

  **Parallelizable**: YES (with Task 1, 2)

  **Acceptance Criteria**:
  - [x] Directory `src/auth/antigravity/` exists
  - [x] `index.ts` exports types and constants
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: YES

  **Commit Specification**:
  - **Message**: `feat(antigravity-auth): add types and constants foundation`
  - **Files to stage**: `src/auth/antigravity/`
  - **Pre-commit verification**:
    - [ ] `bun run typecheck` → No errors
  - **Rollback trigger**: Type errors in imports

---

### Phase 2: OAuth Core

- [x] **4. Implement OAuth flow**

  **What to do**:
  - Create `src/auth/antigravity/oauth.ts`
  - Implement `generatePKCE()` using @openauthjs/openauth
  - Implement `buildAuthURL(projectId: string)` 
  - Implement `exchangeCode(code: string, verifier: string)`
  - Implement `fetchUserInfo(accessToken: string)`
  - Start local callback server on port 51121

  **Must NOT do**:
  - Do NOT use custom PKCE implementation (use @openauthjs/openauth)
  - Do NOT change OAuth scopes from cliproxyapi

  **Parallelizable**: NO (foundation for Phase 2)

  **MUST READ first**:
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go:buildAntigravityAuthURL`
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go:exchangeAntigravityCode`
  - Reference: NoeFabris/opencode-antigravity-auth `src/antigravity/oauth.ts`
  - Reference: numman-ali/opencode-openai-codex-auth `lib/auth/oauth.ts`

  **References**:
  - cliproxyapi line: `buildAntigravityAuthURL` function
  - cliproxyapi line: `exchangeAntigravityCode` function

  **Acceptance Criteria**:
  - [x] PKCE verifier/challenge generated correctly
  - [x] Auth URL includes all required parameters
  - [x] Token exchange returns access_token and refresh_token
  - [x] User info fetch returns email
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 6)

---

- [x] **5. Implement token management**

  **What to do**:
  - Create `src/auth/antigravity/token.ts`
  - Implement `isTokenExpired(tokens: AntigravityTokens)` with 60s buffer
  - Implement `refreshAccessToken(refreshToken: string)`
  - Implement `parseStoredToken(stored: string)` for `refreshToken|projectId|managedProjectId` format
  - Implement `formatTokenForStorage(tokens, projectId, managedProjectId)`

  **Must NOT do**:
  - Do NOT implement retry logic for refresh
  - Do NOT cache tokens in memory (use OpenCode's storage)

  **Parallelizable**: NO (depends on Task 4)

  **MUST READ first**:
  - Reference: NoeFabris/opencode-antigravity-auth `src/plugin/token.ts`
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go` - token refresh logic

  **Acceptance Criteria**:
  - [x] Token expiration check includes 60s buffer
  - [x] Refresh token exchange works with Google endpoint
  - [x] Token parsing handles `|` separated format
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 6)

---

- [x] **6. Implement project context**

  **What to do**:
  - Create `src/auth/antigravity/project.ts`
  - Implement `fetchProjectContext(accessToken: string)` calling loadCodeAssist API
  - Extract `cloudaicompanionProject` from response
  - Implement fallback to `ANTIGRAVITY_DEFAULT_PROJECT_ID` if API fails
  - Cache project context per refresh token

  **Must NOT do**:
  - Do NOT fail if loadCodeAssist returns empty (use default)

  **Parallelizable**: NO (depends on Task 5)

  **MUST READ first**:
  - `~/tools/cliproxyapi/sdk/auth/antigravity.go:fetchAntigravityProjectID`
  - Reference: NoeFabris/opencode-antigravity-auth `src/plugin/project.ts`

  **References**:
  - API endpoint: `https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist`
  - Response field: `cloudaicompanionProject`

  **Acceptance Criteria**:
  - [x] loadCodeAssist API called with correct headers
  - [x] Project ID extracted from response
  - [x] Fallback to default project ID works
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: YES

  **Commit Specification**:
  - **Message**: `feat(antigravity-auth): add OAuth flow and token management`
  - **Files to stage**: `src/auth/antigravity/oauth.ts`, `src/auth/antigravity/token.ts`, `src/auth/antigravity/project.ts`
  - **Pre-commit verification**:
    - [ ] `bun run typecheck` → No errors
  - **Rollback trigger**: OAuth flow failures

---

### Phase 3: Request/Response Transformation

- [x] **7. Implement request transformer** ✅ COMPLETED

  **What to do**:
  - Create `src/auth/antigravity/request.ts`
  - Implement `transformRequest(body: OpenAIRequest)` → AntigravityRequest
  - Handle model name extraction from path
  - Wrap request body in `{ project, model, request }` format
  - Add Antigravity-specific headers

  **Must NOT do**:
  - Do NOT modify the original request object (create new)
  - Do NOT implement tool normalization in this task

  **Parallelizable**: YES (with Task 8)

  **MUST READ first**:
  - Reference: NoeFabris/opencode-antigravity-auth `src/plugin/request.ts`
  - Reference: numman-ali/opencode-openai-codex-auth `lib/request/request-transformer.ts`

  **Acceptance Criteria**:
  - [x] Model name extracted from URL path
  - [x] Request wrapped correctly for Antigravity API
  - [x] Headers include Authorization, User-Agent, X-Goog-Api-Client
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 9)

---

- [x] **8. Implement response handler** ✅ COMPLETED

  **What to do**:
  - Create `src/auth/antigravity/response.ts`
  - Implement `transformResponse(response: Response)` for non-streaming
  - Implement `transformStreamingResponse(response: Response)` for SSE
  - Handle error responses with retry-after extraction
  - Extract usage metadata from x-antigravity-* headers

  **Must NOT do**:
  - Do NOT block on streaming responses
  - Do NOT lose error details in transformation

  **Parallelizable**: YES (with Task 7)

  **MUST READ first**:
  - Reference: NoeFabris/opencode-antigravity-auth `src/plugin/request.ts` response handling
  - Reference: numman-ali/opencode-openai-codex-auth `lib/request/response-handler.ts`

  **Acceptance Criteria**:
  - [x] Non-streaming responses transformed correctly
  - [x] SSE streaming preserved and transformed
  - [x] Error responses include useful details
  - [x] `bun run typecheck` passes

  **Commit Checkpoint**: NO (groups with Task 9)

---

- [x] **9. Implement tool normalization (Gemini only)** ✅ COMPLETED

  **What to do**:
  - Create `src/auth/antigravity/tools.ts`
  - Implement `normalizeToolsForGemini(tools: OpenAITool[])` 
    - Convert OpenAI function calling format to Gemini format
    - Handle `function` type tools with name, description, parameters
  - Implement `normalizeToolResultsFromGemini(results: GeminiToolResult[])`
    - Convert Gemini tool call results back to OpenAI format

  **Must NOT do**:
  - Do NOT handle Claude models (Antigravity is Google/Gemini only)
  - Do NOT drop unknown tool types silently (log warning)
  - Do NOT modify tool behavior, only format

  **Parallelizable**: YES (with Task 10, after 7-8 design understood)

  **MUST READ first**:
  - Reference: NoeFabris/opencode-antigravity-auth tool handling in `src/plugin/request.ts`
  - OpenAI function calling format docs
  - Gemini tool format docs

  **Tool Format Mapping:**

OpenAI format: { "type": "function", "function": { "name": "x", "parameters": {...} } }

Gemini format: { "functionDeclarations": [{ "name": "x", "parameters": {...} }] }


**Acceptance Criteria**:
- [x] OpenAI-style tools converted to Gemini functionDeclarations
- [x] Tool call results mapped back correctly
- [x] Warning logged for unsupported tool types
- [x] `bun run typecheck` passes

**Commit Checkpoint**: NO (groups with Task 11)

---

- [x] **10. Implement thinking block handler (Gemini only)** ✅ COMPLETED

**What to do**:
- Create `src/auth/antigravity/thinking.ts`
- Implement `extractThinkingBlocks(response: GeminiResponse)`
  - Identify `thinkingContent` or reasoning segments in Gemini response
  - Separate thinking content from main response
- Implement `shouldIncludeThinking(model: string)` 
  - Return true for `-high` model variants (e.g., `gemini-3-pro-high`)
- Implement `formatThinkingForOpenAI(thinking: ThinkingBlock[])`
  - Convert Gemini thinking to OpenAI-compatible format

**Must NOT do**:
- Do NOT handle Claude models (Antigravity is Google/Gemini only)
- Do NOT lose thinking content without explicit decision
- Do NOT block on thinking extraction

**Parallelizable**: YES (with Task 9)

**MUST READ first**:
- Reference: NoeFabris/opencode-antigravity-auth thinking block handling
- Gemini thinking mode response format

**Thinking Block Detection (Gemini only):**
- Look for `thinkingContent` field in Gemini response
- Model variants with `-high` suffix have thinking enabled

**Acceptance Criteria**:
- [x] Thinking blocks extracted from Gemini responses
- [x] Model variant detection works (`-high`)
- [x] `bun run typecheck` passes

**Commit Checkpoint**: NO (groups with Task 11)

---

- [x] **11. Implement fetch interceptor** ✅ COMPLETED

**What to do**:
- Create `src/auth/antigravity/fetch.ts`
- Implement `createAntigravityFetch(getAuth, client, providerId)` factory
- Check token expiration before each request → auto refresh
- Rewrite URL for Antigravity endpoints
- Apply request transformation (including tool normalization)
- Apply response transformation (including thinking extraction)
- Implement endpoint fallback (try next endpoint on failure)

**Must NOT do**:
- Do NOT retry indefinitely (max 3 endpoints)
- Do NOT modify global fetch

**Parallelizable**: NO (depends on Task 7-10)

**MUST READ first**:
- Reference: numman-ali/opencode-openai-codex-auth `lib/request/fetch-helpers.ts`
- Reference: NoeFabris/opencode-antigravity-auth fetch implementation

**Endpoint Fallback Verification Procedure:**
```bash
# To test fallback, temporarily modify ANTIGRAVITY_ENDPOINT_FALLBACKS order
# or set environment variable ANTIGRAVITY_DEBUG=1 to see endpoint attempts
# Expected: If daily fails (4xx/5xx/timeout), tries autopush, then prod

Acceptance Criteria:

  • Token refresh triggered when needed
  • URL rewritten to Antigravity endpoint
  • Request transformation applied (including tools)
  • Response transformation applied (including thinking)
  • Endpoint fallback works (daily → autopush → prod)
  • Debug logging shows endpoint attempts when ANTIGRAVITY_DEBUG=1
  • bun run typecheck passes

Commit Checkpoint: YES

Commit Specification:

  • Message: feat(antigravity-auth): add request/response transformation with tools and thinking
  • Files to stage: src/auth/antigravity/request.ts, src/auth/antigravity/response.ts, src/auth/antigravity/tools.ts, src/auth/antigravity/thinking.ts, src/auth/antigravity/fetch.ts
  • Pre-commit verification:
    • bun run typecheck → No errors
  • Rollback trigger: Transformation errors

Phase 4: Plugin Assembly

  • 12. Create main plugin

    What to do:

    • Create src/auth/antigravity/plugin.ts
    • Implement createGoogleAntigravityAuthPlugin() factory
    • Return { hooks: { auth: AuthHook } } structure
    • AuthHook includes:
      • provider: "google"
      • loader: Reads provider.options for credentials, returns { fetch }
      • methods: OAuth method (NO prompts)

    Must NOT do:

    • Do NOT use prompts for credentials
    • Do NOT hardcode credentials in authorize() - read from options or use defaults

    Parallelizable: NO (depends on Phase 3)

    MUST READ first:

    • Reference: NoeFabris/opencode-antigravity-auth src/plugin.ts
    • Reference: numman-ali/opencode-openai-codex-auth index.ts (root entry point)
    • ~/local-workspaces/opencode/packages/plugin/src/index.ts - AuthHook interface

    Plugin Structure:

    {
      provider: "google",
      loader: async (auth, provider) => {
        // Read credentials from provider.options (opencode.json)
        const clientId = provider.options?.clientId || DEFAULT_CLIENT_ID
        const clientSecret = provider.options?.clientSecret || DEFAULT_CLIENT_SECRET
        return { 
          fetch: createAntigravityFetch(auth, clientId, clientSecret, ...) 
        }
      },
      methods: [{
        type: "oauth",
        label: "OAuth with Google (Antigravity)",
        // NO prompts - authorize directly
        authorize: async () => {
          // OAuth flow starts immediately
          // Credentials come from loader context or defaults
        }
      }]
    }
    

    Acceptance Criteria:

    • Plugin returns correct structure for OpenCode
    • provider is "google"
    • NO prompts in methods
    • loader reads credentials from provider.options
    • Falls back to DEFAULT_CLIENT_ID/SECRET if not configured
    • bun run typecheck passes

    Commit Checkpoint: NO (groups with Task 13)


  • 13. Update auth.ts export (SIMPLE)

    What to do:

    • Update src/auth.ts to export the new Google Antigravity plugin:
      export { createGoogleAntigravityAuthPlugin as default } from "./auth/antigravity"
      
    • Create src/auth/antigravity/index.ts barrel export

    That's it. Keep it simple.

    Must NOT do:

    • Do NOT create complex migration structure
    • Do NOT keep OpenAI auth (this is a separate plugin now)
    • Do NOT over-engineer

    Parallelizable: NO (depends on Task 12)

    MUST READ first:

    • Current src/auth.ts for existing pattern

    Final Structure:

    src/
    ├── auth.ts                           # export default from "./auth/antigravity"
    └── auth/
        └── antigravity/
            ├── index.ts                  # barrel export
            ├── plugin.ts                 # createGoogleAntigravityAuthPlugin
            ├── oauth.ts
            ├── token.ts
            ├── project.ts
            ├── request.ts
            ├── response.ts
            ├── tools.ts
            ├── thinking.ts
            ├── fetch.ts
            ├── types.ts
            └── constants.ts
    

    Acceptance Criteria:

    • src/auth.ts exports GoogleAntigravityAuthPlugin as default
    • All antigravity modules exported from barrel
    • bun run typecheck passes

    Commit Checkpoint: YES

    Commit Specification:

    • Message: feat(google-antigravity-auth): create auth plugin for Google models
    • Files to stage: src/auth.ts, src/auth/antigravity/
    • Pre-commit verification:
      • bun run typecheck → No errors
      • bun run build → Success
    • Rollback trigger: Export errors

  • 14. Final verification and documentation

    What to do:

    • Run full typecheck and build
    • Verify no console.log or debug statements (except when ANTIGRAVITY_DEBUG=1)
    • Update AGENTS.md with new auth location
    • Test complete flow manually

    Must NOT do:

    • Do NOT add test files
    • Do NOT modify README (separate task)

    Parallelizable: NO (final task)

    Manual Verification Procedure:

    # 1. Build and verify
    bun run typecheck && bun run build
    
    # 2. Test auth login shows option
    opencode auth login
    # Expected: See "google" provider with "OAuth with Google (Antigravity)" method
    
    # 3. Complete OAuth flow (NO prompts - browser opens immediately)
    # Select google → OAuth with Google (Antigravity)
    # Browser opens → Complete Google login
    # Expected: "Successfully authenticated with google"
    
    # 4. Test conversation (after auth)
    opencode chat --provider google --model gemini-3-pro-preview
    # Send: "Hello, what is 2+2?"
    # Expected: Non-empty response without errors
    
    # 5. (Optional) Test custom credentials via opencode.json
    # Add provider.google.options.clientId/clientSecret
    # Re-run auth login - should use custom credentials
    

    Acceptance Criteria:

    • bun run typecheck → No errors
    • bun run build → Success
    • opencode auth login shows "google" provider with Antigravity method (requires manual testing with OAuth credentials)
    • NO prompts - browser opens immediately after selecting method (requires manual testing)
    • OAuth flow completes and stores tokens (requires manual testing)
    • opencode chat --provider google receives Gemini response (requires manual testing)
    • Custom credentials from opencode.json options work if configured (requires manual testing)
    • No debug console.log in production (only with ANTIGRAVITY_DEBUG=1)

    Commit Checkpoint: YES

    Commit Specification:

    • Message: feat(google-antigravity-auth): complete implementation and verification
    • Files to stage: Any remaining changes, AGENTS.md
    • Pre-commit verification:
      • bun run typecheck → No errors
      • bun run build → Success
    • Rollback trigger: N/A

Commit Checkpoints Summary

After Task Commit Message Pre-commit Commands Rollback Condition
Task 3 feat(google-antigravity-auth): add types and constants foundation bun run typecheck Type errors
Task 6 feat(google-antigravity-auth): add OAuth flow and token management bun run typecheck OAuth failures
Task 11 feat(google-antigravity-auth): add request/response transformation for Gemini bun run typecheck Transform errors
Task 13 feat(google-antigravity-auth): create auth plugin for Google models bun run typecheck, bun run build Export errors
Task 14 feat(google-antigravity-auth): complete implementation and verification bun run typecheck, bun run build N/A

Estimated Effort

  • Phase 1 (Foundation): 30 minutes
  • Phase 2 (OAuth Core): 2 hours
  • Phase 3 (Request/Response + Tools + Thinking): 3 hours
  • Phase 4 (Plugin Assembly): 1.5 hours
  • Total: ~7 hours

Risk Assessment

Risk Mitigation
OAuth flow differences from Go Follow cliproxyapi and opencode-antigravity-auth closely
Token format incompatibility Test with actual Google OAuth tokens
Endpoint fallback complexity Start with prod-only, add fallback incrementally
Response transformation edge cases Log and handle unknown response formats gracefully
Repo focus change Clear communication: this repo now provides Google/Antigravity auth only

Notes

  • @openauthjs/openauth already used by opencode-openai-codex-auth for PKCE
  • Token storage format refreshToken|projectId|managedProjectId enables multi-account support
  • Endpoint fallback ensures service availability during Google outages
  • loadCodeAssist API returns managed project for enterprise users