Skip to content

Request Transformation: Multi-Model Protocol Bridge

What You'll Learn

  • Understand how the plugin transforms request formats between OpenCode and Antigravity API
  • Master Claude and Gemini model protocol differences and transformation rules
  • Troubleshoot 400 errors caused by schema incompatibilities
  • Optimize Thinking configuration for best performance

Your Current Challenge

You may encounter these issues:

  • ❌ MCP server returns 400 Unknown name 'parameters' error
  • ❌ Using Gemini models triggers 400 Unknown name 'const'
  • ❌ Thinking model thinking blocks display incorrectly
  • ❌ Tool calls fail with signature verification errors
  • ❌ Unclear why the plugin supports both Claude and Gemini

When to Use This

When you need:

ScenarioWhy Understand Transformation Mechanism
Developing custom MCP serversEnsure tool schema is compatible with Antigravity API
Troubleshooting 400/500 errorsDetermine if it's a schema issue or transformation logic issue
Optimizing Thinking performanceUnderstand thinking block signature and caching mechanism
Debugging tool call failuresCheck tool ID allocation and parameter signatures

Prerequisites

Before starting this tutorial, ensure you have:

  • ✅ Installed the opencode-antigravity-auth plugin
  • ✅ Understood available models and their variants
  • ✅ Grasped basic concepts of Thinking models

Available Models Tutorial | Thinking Models Tutorial

Core Concept

Request transformation is the plugin's core mechanism. It does four things:

  1. Intercept OpenCode requests — Intercept fetch(generativeLanguage.googleapis.com) calls
  2. Apply model transformation — Transform format based on model type (Claude/Gemini)
  3. Wrap and send — Wrap as Antigravity format and call API
  4. Transform response — Convert response back to OpenCode-recognizable format

Transformation Flow:

Key Transformation Points:

Transformation TypePurposeSource Location
Schema cleaningRemove fields not supported by Antigravity APIsrc/plugin/request-helpers.ts
Thinking configurationInject correct thinking config based on model familysrc/plugin/transform/claude.ts, src/plugin/transform/gemini.ts
Thinking block processingStrip historical thinking blocks and inject signaturessrc/plugin/request.ts
Response stream transformationConvert SSE events to OpenCode formatsrc/plugin/core/streaming.ts

Let's Walk Through It

Step 1: Understand Claude Transformation Rules

Why Claude models use different protocol formats (snake_case, VALIDATED mode), requiring special handling.

Key Transformation Rules

Original FormatTransformed FormatDescription
toolConfig.functionCallingConfig.mode"VALIDATED"Force enable tool call verification
thinkingConfig.includeThoughtsinclude_thoughtssnake_case format
thinkingConfig.thinkingBudgetthinking_budgetsnake_case format
maxOutputTokensAuto-adjust to 64,000Thinking models need larger output space

Code location: src/plugin/transform/claude.ts:43-56

Example

typescript
// Before transformation (OpenCode format)
{
  "toolConfig": {
    "functionCallingConfig": {
      "mode": "AUTO"
    }
  },
  "thinkingConfig": {
    "includeThoughts": true,
    "thinkingBudget": 32000
  }
}

// After transformation (Antigravity format)
{
  "toolConfig": {
    "functionCallingConfig": {
      "mode": "VALIDATED"  // Force VALIDATED
    }
  },
  "thinkingConfig": {
    "include_thoughts": true,  // snake_case
    "thinking_budget": 32000   // snake_case
  },
  "generationConfig": {
    "maxOutputTokens": 64000   // Auto-adjust for Thinking models
  }
}

You should see:

  • All Claude model transformations follow snake_case naming
  • maxOutputTokens is automatically adjusted to a sufficiently large value (CLAUDE_THINKING_MAX_OUTPUT_TOKENS = 64,000)

Step 2: Understand Gemini Transformation Rules

Why Gemini models use camelCase format and have strict requirements for JSON Schema (uppercase types).

Key Transformation Rules

Original FormatTransformed FormatDescription
JSON Schema type: "object"type: "OBJECT"Types must be uppercase
additionalProperties: falseRemoveGemini API doesn't support
$ref: "#/$defs/Foo"Convert to description: "See: Foo"References converted to description
const: "foo"enum: ["foo"]const converted to enum
enum: ["a", "b"]Add description hint (Allowed: a, b)Enums with 2-10 items get automatic hints

Code location: src/plugin/transform/gemini.ts:52-124

Example

json
// Before transformation (OpenCode format)
{
  "parameters": {
    "type": "object",
    "properties": {
      "status": {
        "type": "string",
        "const": "active",
        "enum": ["active", "inactive"]
      }
    }
  }
}

// After transformation (Gemini format)
{
  "parameters": {
    "type": "OBJECT",  // Uppercase
    "properties": {
      "status": {
        "type": "STRING",  // Uppercase
        "enum": ["active", "inactive"],  // const removed
        "description": "(Allowed: active, inactive)"  // Auto-added hint
      }
    }
  }
}

You should see:

  • All Gemini Schema types are converted to uppercase (STRING, OBJECT, ARRAY)
  • const fields are removed and converted to enum
  • Unsupported fields ($ref, additionalProperties) are removed

Step 3: Understand Schema Cleaning Process

Why Antigravity API uses strict protobuf-backed validation and doesn't support all fields from standard JSON Schema.

Four-Phase Cleaning Process

  1. Phase 1a: Convert $ref to description

    • $ref: "#/$defs/Foo"{ description: "See: Foo" }
  2. Phase 1b: Convert const to enum

    • const: "foo"enum: ["foo"]
  3. Phase 1c: Add enum hints

    • enum: ["a", "b"] → Add (Allowed: a, b) to description
  4. Phase 1d: Remove unsupported fields

    • Delete: $schema, $defs, additionalProperties, pattern, minLength, maxLength, etc.

Code location: src/plugin/request-helpers.ts:20-280

Unsupported Fields List:

FieldWhy UnsupportedAlternative
$refReferences not allowedConvert to description hint
constConstants not allowedUse enum
additionalPropertiesDoesn't validate extra propertiesNote in description
$schema, $defsDoesn't use JSON DraftRemove
pattern, minLength, maxLengthString constraints handled by serverRemove
minItems, maxItemsArray constraints handled by serverRemove

You should see:

  • MCP server schemas are cleaned to Antigravity-compatible format
  • 400 errors are reduced, with clearer error messages

Step 4: Understand Thinking Block Processing Mechanism

Why Claude and Gemini 3 models require stable thinking block signatures, otherwise signature verification errors occur.

Three-Step Processing Flow

  1. Strip historical thinking blocks

    • Recursively remove all historical thinking blocks (avoid signature conflicts)
    • Use cache to verify signature validity
  2. Inject new thinking signatures

    • Generate stable signatures for new thinking blocks
    • Cache signatures for multi-turn conversations
  3. Ensure thinking block ordering

    • Claude: thinking must come before tool_use
    • Gemini: thinking can appear anywhere

Code location:

Thinking Block Signature Example:

typescript
// Claude Thinking block format
{
  "type": "thinking",
  "text": "Need to analyze user requirements...",
  "signature": "sig-abc123",  // Plugin-injected signature
  "cache_control": { "type": "ephemeral" }  // Cache control
}

// Historical thinking block (stripped)
{
  "type": "thinking",
  "text": "Old analysis...",  // Removed
  "signature": "sig-old456"  // Signature invalid
}

You should see:

  • In multi-turn conversations, historical thinking blocks don't repeat
  • New thinking blocks have correct signatures
  • Complete thinking process appears before tool calls

Step 5: Understand Response Streaming Transformation

Why Antigravity API returns SSE (Server-Sent Events) streams, which need to be converted to OpenCode-recognizable format.

Key Transformation Rules

Original FormatTransformed FormatDescription
thought: truetype: "reasoning"Thinking block format conversion
textKeep unchangedText content
tool_useKeep unchangedTool calls
tool_resultKeep unchangedTool results

Code location: src/plugin/core/streaming.ts

SSE Event Example:

// Returned by Antigravity API
data: {"type": "thinking", "text": "Analyzing...", "thought": true}

// After transformation
data: {"type": "reasoning", "text": "Analyzing..."}

// Text event
data: {"type": "text", "text": "Hello"}

// Tool call event
data: {"type": "tool_use", "id": "tool-123", "name": "my_function"}

You should see:

  • Thinking blocks display correctly as reasoning type in the interface
  • Streaming responses have no latency, converted line by line
  • Tool call events are in correct format

Checkpoint ✅

After completing the steps above, you should be able to answer:

  • [ ] What will Claude model's toolConfig.mode be set to?
  • [ ] What will Gemini Schema's type: "string" be converted to?
  • [ ] Why strip historical thinking blocks?
  • [ ] What format will const fields be converted to?
  • [ ] What is the purpose of thinking block signatures?

Common Pitfalls

Pitfall 1: MCP Schema contains $ref causing 400 errors

Error message: 400 Unknown name 'parameters'

Cause: MCP server uses JSON Schema's $ref references, which Antigravity API doesn't support.

Solution:

  • Check MCP server's schema definition
  • Remove $ref, directly expand object structure
  • Or modify MCP server code

Example:

json
// ❌ Wrong: Using $ref
{
  "properties": {
    "data": { "$ref": "#/$defs/DataModel" }
  },
  "$defs": {
    "DataModel": { "type": "string" }
  }
}

// ✅ Correct: Directly expanded
{
  "properties": {
    "data": { "type": "string" }
  }
}

Pitfall 2: const field causes Gemini model 400 errors

Error message: 400 Unknown name 'const'

Cause: Antigravity API's Gemini endpoint doesn't support const fields.

Solution:

  • Manually convert const to enum
  • Or rely on plugin's automatic conversion (already implemented)

Example:

json
// ❌ Wrong: Using const
{
  "properties": {
    "status": { "type": "string", "const": "active" }
  }
}

// ✅ Correct: Using enum
{
  "properties": {
    "status": { "type": "string", "enum": ["active"] }
  }
}

Pitfall 3: Thinking model displays garbled text

Error message: Thinking blocks display as [object Object] or incorrect format

Cause: Response transformation logic has a bug, or signature cache is invalid.

Solution:

  1. Check debug logs: opencode --debug
  2. Clear signature cache: Remove cache fields in ~/.config/opencode/antigravity-accounts.json
  3. Restart OpenCode

Pitfall 4: Tool call fails with signature error

Error message: tool_result_missing or signature verification failed

Cause:

  • Incorrect thinking block ordering (thinking must come before tool_use)
  • Signature cache inconsistency
  • Tool ID allocation error

Solution:

  • Plugin will automatically retry (session recovery mechanism)
  • Enable debug mode to view detailed errors
  • Check if tool definitions are correct

Lesson Summary

Key points of request transformation mechanism:

  1. Model family determines transformation rules — Claude (snake_case, VALIDATED) vs Gemini (camelCase, Schema uppercase)
  2. Schema cleaning is essential — Remove unsupported fields like $ref, const, additionalProperties
  3. Thinking block signatures are critical — Stable signatures ensure multi-turn conversation consistency
  4. Response streaming transformation — Convert SSE events to OpenCode format in real-time

Key source code locations:

Coming Up Next

In the next lesson, we'll learn Session Recovery Mechanism.

You'll learn:

  • How session recovery works
  • How to automatically handle tool call failures
  • How to fix corrupted thinking block ordering

Appendix: Source Code Reference

Click to view source code locations

Updated: 2026-01-23

FeatureFile PathLine Numbers
Main request transformation entrysrc/plugin/request.ts585-1443
Response transformation entrysrc/plugin/request.ts1445-1663
Claude model detectionsrc/plugin/transform/claude.ts27-29
Claude Thinking configurationsrc/plugin/transform/claude.ts62-72
Claude Tool configurationsrc/plugin/transform/claude.ts43-57
Gemini model detectionsrc/plugin/transform/gemini.ts129-132
Gemini 3 Thinking configurationsrc/plugin/transform/gemini.tsFind buildGemini3ThinkingConfig
Gemini Schema transformationsrc/plugin/transform/gemini.ts52-124
---------
---------
---------
Thinking block strippingsrc/plugin/request-helpers.tsFind deepFilterThinkingBlocks
Thinking block signature injectionsrc/plugin/request.ts715-720
Streaming response transformationsrc/plugin/core/streaming.tsFull file

Key Constants:

Key Functions: