Refactors how Claude thinking blocks are handled to improve reliability and address signature validation issues. - Removes the deprecated `KEEP_THINKING_BLOCKS` constant and associated environment variable. - Introduces runtime configuration for `keep_thinking` to control whether thinking blocks are preserved. - Implements logic to inject a sentinel value (`SKIP_THOUGHT_SIGNATURE`) when a thinking block's signature is invalid or missing, allowing the API to bypass validation. This addresses scenarios like cache misses, session mismatches, and plugin restarts. This change enhances the robustness of Claude integrations by providing a mechanism to handle signature issues gracefully.
9.2 KiB
Architecture Guide
Last Updated: December 2025
This document explains how the Antigravity plugin works, including the 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)
├── 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)
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:
- Gemini format -
contents[].parts[]notmessages[].content[] - Thinking signatures - Multi-turn needs signed blocks or errors
- Schema restrictions - Rejects
const,$ref,$defs, etc. - Tool validation -
VALIDATEDmode 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:
- Caches signed thinking from responses (
lastSignedThinkingBySessionKey) - On subsequent requests, injects cached thinking before tool_use
- Only injects for the first assistant message of a turn (not every message)
Turn boundary detection (thinking-recovery.ts):
// 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):
- Detect error via
session.errorevent - Fetch session messages via
client.session.messages() - Extract
tool_useIDs from failed message - Inject synthetic
tool_resultblocks:{ type: "tool_result", tool_use_id: id, content: "Operation cancelled" } - Send via
client.session.prompt() - Optionally auto-resume with "continue"
Thinking Block Order Error
Error: Expected thinking but found text
Recovery (thinking-recovery.ts):
- Detect conversation is in tool loop without thinking at turn start
- Close the corrupted turn with synthetic messages
- 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
reasonproperty
Multi-Account Load Balancing
How It Works
- Sticky selection - Same account until rate limited (preserves cache)
- Per-model-family - Claude/Gemini rate limits tracked separately
- Dual quota (Gemini) - Antigravity + Gemini CLI headers
- 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 debug logging |
OPENCODE_ANTIGRAVITY_QUIET |
Suppress toast notifications |
Config File
Location: ~/.config/opencode/antigravity.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
export OPENCODE_ANTIGRAVITY_DEBUG=2 # Verbose
Log Location
~/.config/opencode/antigravity-logs/
What To Check
- Is
isClaudeModeltrue for Claude models? - Are thinking blocks being stripped?
- Are tool schemas being cleaned?
- 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 - API reference
- README.md - Installation & usage