Guidance for AI agents working with this repository.
OpenCode plugin for Google Antigravity OAuth. Intercepts fetch() calls to generativelanguage.googleapis.com, transforms them to Antigravity format, and handles auth, quota, recovery, and multi-account rotation.
npm install # Install dependencies
npm run build # Compile (tsc -p tsconfig.build.json)
npm run typecheck # Type-check only (tsc --noEmit)
npm test # Run all tests (vitest run)
npx vitest run src/plugin/auth.test.ts # Single test file
npx vitest run -t "test name here" # Single test by name
npx vitest --watch src/plugin/auth.test.ts # Watch mode, single file
npm run test:coverage # Coverage report
npm run test:e2e:models # E2E: model availability check
npm run test:e2e:regression # E2E: regression suiteNo linter or formatter is configured. Style is enforced by convention (see below).
strict: truewith extra strictness:noUncheckedIndexedAccess,noImplicitOverride,noFallthroughCasesInSwitchverbatimModuleSyntax: true— useimport typefor type-only importstarget: ESNext,module: Preserve,moduleResolution: bundlerallowImportingTsExtensions: true— use.tsextensions in imports- No path aliases — all imports are relative
- Use
import type { ... }for type-only imports (enforced byverbatimModuleSyntax) - Named imports only — no default imports in src/
- Relative paths with
.tsextensions:import { foo } from "./bar.ts" - Order: node builtins > external packages > local modules
- Named exports only in src/ — no default exports
- Barrel files (index.ts) for module surfaces
camelCasefor functions, variables, parametersPascalCasefor types, interfaces, classes, enumsUPPER_SNAKE_CASEfor constantskebab-casefor file names (e.g.,request-helpers.ts,thinking-recovery.ts)- Test files:
*.test.tscolocated with source
- No
Iprefix on interfaces, noTypesuffix - Use
z.infer<typeof Schema>for Zod-derived types - Extract to
types.tswhen shared, inline when local - Discriminated unions preferred over boolean flags
- Never use
as any,@ts-ignore, or@ts-expect-error
export functionfor public APIs- Arrow functions for callbacks, factories, and inline closures
- Async functions with targeted try/catch (not blanket)
- Defensive try/catch with graceful degradation (fallback values, not crashes)
- Custom error classes with metadata when domain-specific
- Catch
unknown, log, and convert to domain errors — never empty catch blocks - Rate limit / quota errors trigger account rotation, not failure
- 2-space indentation
- Double quotes for strings
- Trailing commas in multiline constructs
- No semicolons (project convention)
createLogger("module-name")for structured loggingconsole.logonly for CLI/user-facing output
src/
├── plugin.ts # Main entry, fetch interceptor
├── constants.ts # Endpoints, headers, API config, system prompts
├── antigravity/oauth.ts # OAuth token exchange
└── plugin/
├── auth.ts # Token validation & refresh
├── request.ts # Request transformation (core logic)
├── request-helpers.ts # Schema cleaning, thinking filters
├── thinking-recovery.ts # Turn boundary detection
├── recovery.ts # Session recovery (tool_result_missing)
├── quota.ts # Quota checking (API usage stats)
├── cache.ts # Auth & signature caching
├── accounts.ts # Multi-account management & storage
├── storage.ts # Persistent storage schemas (Zod)
├── fingerprint.ts # Device fingerprint generation & headers
├── project.ts # Managed project context resolution
└── debug.ts # Debug logging utilities
Plugin intercepts fetch() for generativelanguage.googleapis.com, transforms to Antigravity format. Two header styles: antigravity (Electron-style UA + fingerprint) and gemini-cli (nodejs-client UA).
ALL thinking blocks are stripped from outgoing requests for Claude models. Claude generates fresh thinking each turn. This eliminates signature validation errors.
When tool execution is interrupted (ESC/timeout), the plugin injects synthetic tool_result blocks to recover the session without starting over.
Tool schemas are cleaned via allowlist. Unsupported fields (const, $ref, $defs) are removed or converted to Antigravity-compatible format.
Accounts rotate on rate limits. Gemini has dual quota pools (Antigravity headers + Gemini CLI headers). Fingerprints are per-account and regenerated on capacity exhaustion.
Per-account device fingerprints stored in antigravity-accounts.json. Each fingerprint includes deviceId, sessionToken, userAgent, and a reduced clientMetadata (ideType, platform, pluginType — no osVersion, arch, or sqmId). The only header composed is User-Agent, built by buildFingerprintHeaders() in fingerprint.ts and applied on the antigravity request path in request.ts. History tracked (max 5), restorable.
zod ^4— schema validation (NOT zod v3)@opencode-ai/plugin— OpenCode plugin interface@openauthjs/openauth— OAuth clientproper-lockfile— file locking for concurrent accessxdg-basedir— XDG directory resolution
- Framework: Vitest 3 with native ESM
- Config:
vitest.config.ts - Tests colocated:
src/plugin/foo.test.tsnext tosrc/plugin/foo.ts - Use
describe/it/expect— standard Vitest API - Mock with
vi.fn(),vi.spyOn(),vi.mock()
- README.md — Installation & usage
- docs/ARCHITECTURE.md — Detailed architecture guide
- docs/ANTIGRAVITY_API_SPEC.md — API reference
- CHANGELOG.md — Version history