Files
opencode-antigravity-auth/docs/ARCHITECTURE.md
Giulio Vaccari 88e27badee docs: tighten architecture overview intro sentence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 00:22:38 +02:00

303 lines
9.5 KiB
Markdown

# Architecture Guide
**Last Updated:** April 2026
This document explains how the Antigravity plugin works: request/response flow, Claude-specific handling, and session recovery.
---
## Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ OpenCode ──▶ Plugin ──▶ Antigravity API ──▶ Claude/Gemini │
│ │ │ │ │ │
│ │ │ │ └─ Model │
│ │ │ └─ Google's gateway (Gemini fmt) │
│ │ └─ THIS PLUGIN (auth, transform, recovery) │
│ └─ AI coding assistant │
└─────────────────────────────────────────────────────────────────┘
```
The plugin intercepts requests to `generativelanguage.googleapis.com`, transforms them for the Antigravity API, and handles authentication, rate limits, and error recovery.
---
## Module Structure
```
src/
├── index.ts # Plugin exports
├── plugin.ts # Main entry, fetch interceptor
├── constants.ts # Endpoints, headers, config
├── antigravity/
│ └── oauth.ts # OAuth token exchange
└── plugin/
├── auth.ts # Token validation & refresh
├── request.ts # Request transformation (main logic)
├── request-helpers.ts # Schema cleaning, thinking filters
├── thinking-recovery.ts # Turn boundary detection, crash recovery
├── recovery.ts # Session recovery (tool_result_missing)
├── quota.ts # Quota checking (API usage stats)
├── cache.ts # Auth & signature caching
├── cache/
│ └── signature-cache.ts # Disk-based signature persistence
├── config/
│ ├── schema.ts # Zod config schema
│ └── loader.ts # Config file loading
├── accounts.ts # Multi-account management
├── server.ts # OAuth callback server
└── debug.ts # Debug logging
```
---
## Request Flow
### 1. Interception (`plugin.ts`)
```typescript
fetch() intercepted isGenerativeLanguageRequest() prepareAntigravityRequest()
```
- Account selection (round-robin, rate-limit aware)
- Token refresh if expired
- Endpoint fallback (daily → autopush → prod)
### 2. Request Transformation (`request.ts`)
| Step | What Happens |
|------|--------------|
| Model detection | Detect Claude/Gemini from URL |
| Thinking config | Add `thinkingConfig` for thinking models |
| Thinking strip | Remove ALL thinking blocks (Claude) |
| Tool normalization | Convert to `functionDeclarations[]` |
| Schema cleaning | Remove unsupported JSON Schema fields |
| ID assignment | Assign IDs to tool calls (FIFO matching) |
| Wrap request | `{ project, model, request: {...} }` |
### 3. Response Transformation (`request.ts`)
| Step | What Happens |
|------|--------------|
| SSE streaming | Real-time line-by-line TransformStream |
| Signature caching | Cache `thoughtSignature` for display |
| Format transform | `thought: true``type: "reasoning"` |
| Envelope unwrap | Extract inner `response` object |
---
## Claude-Specific Handling
### Why Special Handling?
Claude through Antigravity requires:
1. **Gemini format** - `contents[].parts[]` not `messages[].content[]`
2. **Thinking signatures** - Multi-turn needs signed blocks or errors
3. **Schema restrictions** - Rejects `const`, `$ref`, `$defs`, etc.
4. **Tool validation** - `VALIDATED` mode requires proper schemas
### Thinking Block Strategy (v2.0)
**Problem:** OpenCode stores thinking blocks, but may corrupt signatures.
**Solution:** Strip ALL thinking blocks from outgoing requests.
```
Turn 1 Response: { thought: true, text: "...", thoughtSignature: "abc" }
↓ (stored by OpenCode, possibly corrupted)
Turn 2 Request: Plugin STRIPS all thinking blocks
Claude API: Generates fresh thinking
```
**Why this works:**
- Zero signature errors (impossible to have invalid signatures)
- Same quality (Claude sees full conversation, re-thinks fresh)
- Simpler code (no complex validation/restoration)
### Thinking Injection for Tool Use
Claude API requires thinking before `tool_use` blocks. The plugin:
1. Caches signed thinking from responses (`lastSignedThinkingBySessionKey`)
2. On subsequent requests, injects cached thinking before tool_use
3. Only injects for the **first** assistant message of a turn (not every message)
**Turn boundary detection** (`thinking-recovery.ts`):
```typescript
// A "turn" starts after a real user message (not tool_result)
// Only inject thinking into first assistant message after that
```
---
## Session Recovery
### Tool Result Missing Error
When a tool execution is interrupted (ESC, timeout, crash):
```
Error: tool_use ids were found without tool_result blocks immediately after
```
**Recovery flow** (`recovery.ts`):
1. Detect error via `session.error` event
2. Fetch session messages via `client.session.messages()`
3. Extract `tool_use` IDs from failed message
4. Inject synthetic `tool_result` blocks:
```typescript
{ type: "tool_result", tool_use_id: id, content: "Operation cancelled" }
```
5. Send via `client.session.prompt()`
6. Optionally auto-resume with "continue"
### Thinking Block Order Error
```
Error: Expected thinking but found text
```
**Recovery** (`thinking-recovery.ts`):
1. Detect conversation is in tool loop without thinking at turn start
2. Close the corrupted turn with synthetic messages
3. Start fresh turn where Claude can generate new thinking
---
## Schema Cleaning
Claude rejects unsupported JSON Schema features. The plugin uses an **allowlist approach**:
**Kept:** `type`, `properties`, `required`, `description`, `enum`, `items`
**Removed:** `const`, `$ref`, `$defs`, `default`, `examples`, `additionalProperties`, `$schema`, `title`
**Transformations:**
- `const: "value"` → `enum: ["value"]`
- Empty object schema → Add placeholder `reason` property
---
## Multi-Account Load Balancing
### How It Works
1. **Sticky selection** - Same account until rate limited (preserves cache)
2. **Per-model-family** - Claude/Gemini rate limits tracked separately
3. **Dual quota (Gemini)** - Antigravity + Gemini CLI headers
4. **Automatic failover** - On 429, switch to next available account
### Account Storage
Location: `~/.config/opencode/antigravity-accounts.json`
Contains OAuth refresh tokens - treat as sensitive.
---
## Configuration
### Environment Variables
| Variable | Purpose |
|----------|---------|
| `OPENCODE_ANTIGRAVITY_DEBUG` | `1` or `2` for file debug logging |
| `OPENCODE_ANTIGRAVITY_DEBUG_TUI` | `1` or `true` for TUI log panel debug output |
| `OPENCODE_ANTIGRAVITY_QUIET` | Suppress toast notifications |
`debug` and `debug_tui` are independent sinks: `debug` controls file logs, while `debug_tui` controls TUI logs.
### Config File
Location: `~/.config/opencode/antigravity.json`
```json
{
"session_recovery": true,
"auto_resume": true,
"resume_text": "continue",
"keep_thinking": false
}
```
---
## Key Functions Reference
### `request.ts`
| Function | Purpose |
|----------|---------|
| `prepareAntigravityRequest()` | Main request transformation |
| `transformAntigravityResponse()` | SSE streaming, format conversion |
| `ensureThinkingBeforeToolUseInContents()` | Inject cached thinking |
| `createStreamingTransformer()` | Real-time SSE processing |
### `request-helpers.ts`
| Function | Purpose |
|----------|---------|
| `deepFilterThinkingBlocks()` | Recursive thinking block removal |
| `cleanJSONSchemaForAntigravity()` | Schema sanitization |
| `transformThinkingParts()` | `thought` → `reasoning` format |
### `thinking-recovery.ts`
| Function | Purpose |
|----------|---------|
| `analyzeConversationState()` | Detect turn boundaries, tool loops |
| `needsThinkingRecovery()` | Check if recovery needed |
| `closeToolLoopForThinking()` | Inject synthetic messages |
### `recovery.ts`
| Function | Purpose |
|----------|---------|
| `handleSessionRecovery()` | Main recovery orchestration |
| `createSessionRecoveryHook()` | Hook factory for plugin |
---
## Debugging
### Enable Logging
```bash
export OPENCODE_ANTIGRAVITY_DEBUG=2 # Verbose file logs
export OPENCODE_ANTIGRAVITY_DEBUG_TUI=1 # TUI log panel output
```
### Log Location
`~/.config/opencode/antigravity-logs/`
### What To Check
1. Is `isClaudeModel` true for Claude models?
2. Are thinking blocks being stripped?
3. Are tool schemas being cleaned?
4. Is session recovery triggering?
---
## Troubleshooting
| Error | Cause | Solution |
|-------|-------|----------|
| `invalid signature` | Corrupted thinking block | Update plugin (strips all thinking) |
| `Unknown field: const` | Schema uses `const` | Plugin auto-converts to `enum` |
| `tool_use without tool_result` | Interrupted execution | Session recovery injects results |
| `Expected thinking but found text` | Turn started without thinking | Thinking recovery closes turn |
| `429 Too Many Requests` | Rate limited | Plugin auto-rotates accounts |
---
## See Also
- [ANTIGRAVITY_API_SPEC.md](./ANTIGRAVITY_API_SPEC.md) - API reference
- [README.md](../README.md) - Installation & usage