Calling Gemini Models - 1M Context Window Integration

claude-code gemini ai-integration large-context typescript workshop

Overview

This guide demonstrates how to integrate Claude Code with Google’s Gemini models to leverage their industry-leading 1M+ context window capabilities. By combining Claude’s advanced reasoning with Gemini’s massive context processing, developers can build applications that analyze entire codebases, process lengthy documents, and maintain context across extended conversations.

Table of Contents

Why Combine Claude and Gemini

Claude Code Strengths

  • Superior reasoning and analysis capabilities
  • Excellent code generation and understanding
  • Strong safety and alignment features
  • Native TypeScript SDK with streaming support

Gemini’s Context Window Advantages

  • Gemini 2.5 Pro: 2 million token context window (largest available)
  • Gemini 1.5 Flash: 1 million tokens with faster processing
  • Process entire codebases (30,000+ lines)
  • Analyze hours of video/audio content
  • Maintain context across extensive documentation

Use Cases for Integration

  1. Codebase Analysis: Use Gemini to load entire repositories, Claude to perform complex reasoning
  2. Document Processing: Gemini handles large documents, Claude extracts insights
  3. Multi-file Refactoring: Gemini maintains context, Claude suggests improvements
  4. Long-form Content: Gemini processes books/transcripts, Claude summarizes

Architecture Overview

graph TD
    A[User Request] --> B[Claude Code]
    B --> C{Task Router}
    C -->|Large Context Task| D[MCP Server]
    D --> E[Gemini API]
    E --> F[Process with 1M Context]
    F --> G[Return to Claude]
    G --> H[Claude Analysis]
    H --> I[User Response]
    C -->|Standard Task| J[Claude Direct Processing]
    J --> I

Key Components

  1. Claude Code: Primary interface and orchestrator
  2. MCP Server: Bridge between Claude and external APIs
  3. Task Router: Determines optimal model for each task
  4. Gemini Integration: Handles large context processing
  5. Response Pipeline: Combines results from multiple models

Setting Up the Integration

Prerequisites

# Install required packages
npm install @anthropic-ai/claude-code @google/genai @modelcontextprotocol/sdk zod dotenv

Environment Configuration

# .env file
CLAUDE_API_KEY=your_claude_api_key
GEMINI_API_KEY=your_gemini_api_key
GOOGLE_CLOUD_PROJECT=your_project_id
MCP_SERVER_PORT=3000

Basic MCP Server Setup

// mcp-gemini-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { GoogleGenerativeAI } from "@google/genai";
import { z } from "zod";
 
const gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
 
const server = new McpServer({
  name: "gemini-integration",
  version: "1.0.0",
  description: "Gemini integration for large context processing"
});
 
// Tool for processing large contexts with Gemini
server.tool(
  "process-large-context",
  {
    description: "Process large documents or codebases with Gemini's 1M+ context window",
    inputSchema: z.object({
      content: z.string().describe("The large content to process"),
      prompt: z.string().describe("What to do with the content"),
      model: z.enum(["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.5-pro"])
        .default("gemini-1.5-flash")
    })
  },
  async ({ content, prompt, model }) => {
    const geminiModel = gemini.getGenerativeModel({ model });
    
    const result = await geminiModel.generateContent([
      { text: content },
      { text: prompt }
    ]);
    
    return {
      content: [{
        type: "text",
        text: result.response.text()
      }]
    };
  }
);
 
// Tool for streaming large context responses
server.tool(
  "stream-large-context",
  {
    description: "Stream responses for large context processing",
    inputSchema: z.object({
      content: z.string(),
      prompt: z.string(),
      model: z.string().default("gemini-1.5-flash")
    })
  },
  async ({ content, prompt, model }) => {
    const geminiModel = gemini.getGenerativeModel({ model });
    
    const result = await geminiModel.generateContentStream([
      { text: content },
      { text: prompt }
    ]);
    
    let fullResponse = "";
    for await (const chunk of result.stream) {
      const chunkText = chunk.text();
      fullResponse += chunkText;
      
      // Send progress updates
      server.notification({
        method: "progress",
        params: { text: chunkText }
      });
    }
    
    return {
      content: [{
        type: "text",
        text: fullResponse
      }]
    };
  }
);
 
// Start the server
server.connect(process.stdin, process.stdout);

Registering with Claude Code

# Add the MCP server to Claude Code
claude mcp add gemini-server -- node /path/to/mcp-gemini-server.js
 
# Or with TypeScript
claude mcp add gemini-server -- npx tsx /path/to/mcp-gemini-server.ts

Workshop Examples

Example 1: Analyzing an Entire Codebase

// workshop-example-1.ts
import { query, type SDKMessage } from "@anthropic-ai/claude-code";
 
async function analyzeCodebase(codebasePath: string) {
  const messages: SDKMessage[] = [];
  
  for await (const message of query({
    prompt: `
      I need to analyze a large codebase for architectural patterns and potential improvements.
      
      First, use the process-large-context tool to load and understand the entire codebase at ${codebasePath}.
      Then provide a detailed analysis of:
      1. Overall architecture and design patterns
      2. Code quality issues
      3. Performance bottlenecks
      4. Security concerns
      5. Refactoring suggestions
    `,
    options: {
      model: "claude-opus-4-20250514",
      maxTurns: 5
    }
  })) {
    messages.push(message);
    
    if (message.type === "assistant" && message.content) {
      console.log("Claude:", message.content);
    }
  }
  
  return messages;
}
 
// Usage
analyzeCodebase("/path/to/large/repository");

Example 2: Document Comparison with Context Preservation

// workshop-example-2.ts
interface DocumentComparison {
  similarities: string[];
  differences: string[];
  recommendations: string[];
}
 
async function compareLargeDocuments(
  doc1Path: string,
  doc2Path: string
): Promise<DocumentComparison> {
  const comparisonPrompt = `
    You have access to two large documents that need to be compared.
    
    1. First, use process-large-context to analyze the first document at ${doc1Path}
    2. Then, use process-large-context to analyze the second document at ${doc2Path}
    3. Compare them for:
       - Key similarities in content and structure
       - Major differences and contradictions
       - Recommendations for reconciliation
    
    Use Gemini's large context window to maintain full document context.
  `;
  
  let comparison: DocumentComparison = {
    similarities: [],
    differences: [],
    recommendations: []
  };
  
  for await (const message of query({
    prompt: comparisonPrompt,
    options: {
      systemPrompt: "You are an expert document analyst specializing in detailed comparisons."
    }
  })) {
    if (message.type === "result" && message.result?.includes("similarities")) {
      // Parse the structured response
      comparison = JSON.parse(message.result);
    }
  }
  
  return comparison;
}

Example 3: Intelligent Model Routing

// workshop-example-3.ts
class IntelligentRouter {
  private tokenCounter = {
    estimateTokens: (text: string) => Math.ceil(text.length / 4)
  };
  
  async routeTask(content: string, task: string) {
    const estimatedTokens = this.tokenCounter.estimateTokens(content);
    
    // Decision logic for model routing
    const routing = {
      useGemini: estimatedTokens > 100_000, // Use Gemini for large contexts
      model: estimatedTokens > 1_000_000 ? "gemini-2.5-pro" : "gemini-1.5-flash",
      strategy: this.determineStrategy(task, estimatedTokens)
    };
    
    console.log(`Routing decision:`, routing);
    
    const prompt = routing.useGemini
      ? `Use the process-large-context tool with model ${routing.model} to ${task}`
      : `Directly process this task: ${task}`;
    
    for await (const message of query({ prompt })) {
      yield message;
    }
  }
  
  private determineStrategy(task: string, tokens: number) {
    if (task.includes("summarize") && tokens > 500_000) {
      return "hierarchical-summarization";
    } else if (task.includes("analyze") && tokens > 200_000) {
      return "chunked-analysis";
    } else if (task.includes("compare")) {
      return "parallel-processing";
    }
    return "direct-processing";
  }
}

Example 4: Codebase Migration Assistant

// workshop-example-4.ts
interface MigrationPlan {
  phases: Array<{
    name: string;
    files: string[];
    changes: string[];
    risks: string[];
  }>;
  timeline: string;
  dependencies: Record<string, string[]>;
}
 
class CodebaseMigrationAssistant {
  async planMigration(
    sourcePath: string,
    targetFramework: string
  ): Promise<MigrationPlan> {
    const analysisPrompt = `
      I need to plan a migration of a large codebase to ${targetFramework}.
      
      Steps:
      1. Use process-large-context to analyze the entire codebase at ${sourcePath}
      2. Identify all files, dependencies, and architectural patterns
      3. Create a detailed migration plan with:
         - Phased approach (what to migrate first)
         - File-by-file changes needed
         - Risk assessment for each phase
         - Dependency management strategy
         - Estimated timeline
      
      Output the plan as a structured JSON object.
    `;
    
    let plan: MigrationPlan | null = null;
    
    for await (const message of query({
      prompt: analysisPrompt,
      options: {
        model: "claude-opus-4-20250514",
        systemPrompt: "You are an expert in codebase migrations and refactoring."
      }
    })) {
      if (message.type === "assistant" && message.content?.includes("phases")) {
        try {
          plan = JSON.parse(message.content);
        } catch (e) {
          console.error("Failed to parse migration plan:", e);
        }
      }
    }
    
    return plan || this.getDefaultPlan();
  }
  
  private getDefaultPlan(): MigrationPlan {
    return {
      phases: [{
        name: "Preparation",
        files: [],
        changes: ["Set up new project structure"],
        risks: ["Incomplete analysis"]
      }],
      timeline: "Unknown",
      dependencies: {}
    };
  }
}

Example 5: Real-time Collaborative Analysis

// workshop-example-5.ts
import { EventEmitter } from 'events';
 
class CollaborativeAnalyzer extends EventEmitter {
  private activeAnalyses = new Map<string, any>();
  
  async analyzeWithProgress(
    content: string,
    analysisType: string,
    onProgress?: (update: string) => void
  ) {
    const analysisId = crypto.randomUUID();
    
    this.activeAnalyses.set(analysisId, {
      status: 'processing',
      startTime: Date.now()
    });
    
    const streamingPrompt = `
      Use the stream-large-context tool to analyze this content.
      Provide regular progress updates as you work through the analysis.
      
      Analysis type: ${analysisType}
      Content size: ${content.length} characters
    `;
    
    try {
      for await (const message of query({
        prompt: streamingPrompt,
        options: {
          streamOptions: {
            includeUsage: true
          }
        }
      })) {
        if (message.type === "assistant") {
          onProgress?.(message.content || "Processing...");
          this.emit('progress', {
            analysisId,
            content: message.content,
            timestamp: Date.now()
          });
        }
        
        if (message.type === "result") {
          this.activeAnalyses.set(analysisId, {
            status: 'completed',
            result: message.result,
            duration: Date.now() - this.activeAnalyses.get(analysisId).startTime
          });
        }
      }
    } catch (error) {
      this.activeAnalyses.set(analysisId, {
        status: 'error',
        error: error.message
      });
      throw error;
    }
    
    return this.activeAnalyses.get(analysisId);
  }
}
 
// Usage example
const analyzer = new CollaborativeAnalyzer();
 
analyzer.on('progress', (update) => {
  console.log(`Analysis progress:`, update);
});
 
analyzer.analyzeWithProgress(
  largeCodebaseContent,
  "security-audit",
  (progress) => {
    console.log(`Update: ${progress}`);
  }
);

Production Patterns

Context Caching Strategy

// context-caching.ts
class GeminiContextCache {
  private cache = new Map<string, {
    content: string;
    hash: string;
    timestamp: number;
    expiresAt: number;
  }>();
  
  private readonly CACHE_DURATION = 60 * 60 * 1000; // 1 hour
  
  async processWithCache(
    content: string,
    prompt: string,
    forceRefresh = false
  ) {
    const contentHash = this.hashContent(content);
    const cacheKey = `${contentHash}-${prompt}`;
    
    if (!forceRefresh && this.cache.has(cacheKey)) {
      const cached = this.cache.get(cacheKey)!;
      if (cached.expiresAt > Date.now()) {
        console.log("Using cached context");
        return cached.content;
      }
    }
    
    // Process with Gemini
    const result = await this.processWithGemini(content, prompt);
    
    // Cache the result
    this.cache.set(cacheKey, {
      content: result,
      hash: contentHash,
      timestamp: Date.now(),
      expiresAt: Date.now() + this.CACHE_DURATION
    });
    
    return result;
  }
  
  private hashContent(content: string): string {
    // Simple hash for demo - use crypto.subtle.digest in production
    return content.length + "-" + content.slice(0, 100);
  }
  
  private async processWithGemini(content: string, prompt: string) {
    // Call to Gemini API
    return "processed result";
  }
}

Error Handling and Fallbacks

// error-handling.ts
class ResilientGeminiClient {
  private readonly fallbackStrategies = {
    tokenLimit: async (content: string, prompt: string) => {
      // Fallback to chunking if content exceeds limits
      const chunks = this.chunkContent(content, 500_000);
      const results = await Promise.all(
        chunks.map(chunk => this.processChunk(chunk, prompt))
      );
      return this.mergeResults(results);
    },
    
    rateLimit: async (content: string, prompt: string) => {
      // Switch to Gemini Flash for rate limit issues
      return this.processWithModel(content, prompt, "gemini-1.5-flash");
    },
    
    timeout: async (content: string, prompt: string) => {
      // Simplify the prompt for faster processing
      const simplifiedPrompt = `Briefly: ${prompt}`;
      return this.processWithTimeout(content, simplifiedPrompt, 60000);
    }
  };
  
  async process(content: string, prompt: string) {
    try {
      return await this.processWithGemini(content, prompt);
    } catch (error) {
      return this.handleError(error, content, prompt);
    }
  }
  
  private async handleError(
    error: any,
    content: string,
    prompt: string
  ) {
    if (error.code === 'RESOURCE_EXHAUSTED') {
      return this.fallbackStrategies.rateLimit(content, prompt);
    } else if (error.message?.includes('token limit')) {
      return this.fallbackStrategies.tokenLimit(content, prompt);
    } else if (error.code === 'DEADLINE_EXCEEDED') {
      return this.fallbackStrategies.timeout(content, prompt);
    }
    
    throw error; // Re-throw if no fallback applies
  }
  
  private chunkContent(content: string, maxSize: number): string[] {
    const chunks: string[] = [];
    for (let i = 0; i < content.length; i += maxSize) {
      chunks.push(content.slice(i, i + maxSize));
    }
    return chunks;
  }
  
  private mergeResults(results: string[]): string {
    return results.join("\n\n---\n\n");
  }
  
  private async processWithGemini(content: string, prompt: string) {
    // Actual Gemini API call
    throw new Error("Not implemented");
  }
  
  private async processChunk(chunk: string, prompt: string) {
    return `Processed: ${chunk.slice(0, 50)}...`;
  }
  
  private async processWithModel(
    content: string,
    prompt: string,
    model: string
  ) {
    return `Processed with ${model}`;
  }
  
  private async processWithTimeout(
    content: string,
    prompt: string,
    timeout: number
  ) {
    return `Processed with timeout: ${timeout}ms`;
  }
}

Performance Monitoring

// performance-monitoring.ts
interface PerformanceMetrics {
  modelUsed: string;
  inputTokens: number;
  outputTokens: number;
  latency: number;
  cacheHit: boolean;
  cost: number;
}
 
class PerformanceMonitor {
  private metrics: PerformanceMetrics[] = [];
  
  async trackOperation<T>(
    operation: () => Promise<T>,
    metadata: Partial<PerformanceMetrics>
  ): Promise<T> {
    const startTime = Date.now();
    
    try {
      const result = await operation();
      
      const metrics: PerformanceMetrics = {
        modelUsed: metadata.modelUsed || "unknown",
        inputTokens: metadata.inputTokens || 0,
        outputTokens: metadata.outputTokens || 0,
        latency: Date.now() - startTime,
        cacheHit: metadata.cacheHit || false,
        cost: this.calculateCost(metadata)
      };
      
      this.metrics.push(metrics);
      this.logMetrics(metrics);
      
      return result;
    } catch (error) {
      this.logError(error, Date.now() - startTime);
      throw error;
    }
  }
  
  private calculateCost(metadata: Partial<PerformanceMetrics>): number {
    const rates = {
      "gemini-1.5-flash": { input: 0.000001, output: 0.000002 },
      "gemini-1.5-pro": { input: 0.00001, output: 0.00002 },
      "gemini-2.5-pro": { input: 0.000011, output: 0.000022 }
    };
    
    const model = metadata.modelUsed || "gemini-1.5-flash";
    const rate = rates[model] || rates["gemini-1.5-flash"];
    
    return (metadata.inputTokens || 0) * rate.input +
           (metadata.outputTokens || 0) * rate.output;
  }
  
  private logMetrics(metrics: PerformanceMetrics) {
    console.log("Performance metrics:", {
      ...metrics,
      cost: `$${metrics.cost.toFixed(6)}`
    });
  }
  
  private logError(error: any, duration: number) {
    console.error("Operation failed:", {
      error: error.message,
      duration,
      timestamp: new Date().toISOString()
    });
  }
  
  getAggregateMetrics() {
    return {
      totalOperations: this.metrics.length,
      averageLatency: this.metrics.reduce((sum, m) => sum + m.latency, 0) / this.metrics.length,
      totalCost: this.metrics.reduce((sum, m) => sum + m.cost, 0),
      cacheHitRate: this.metrics.filter(m => m.cacheHit).length / this.metrics.length
    };
  }
}

Performance Optimization

Token Usage Optimization

// token-optimization.ts
class TokenOptimizer {
  private readonly strategies = {
    compression: {
      whitespace: (text: string) => text.replace(/\s+/g, ' ').trim(),
      comments: (text: string) => text.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ''),
      emptyLines: (text: string) => text.replace(/\n\s*\n/g, '\n')
    },
    
    filtering: {
      byExtension: (files: any[], extensions: string[]) => 
        files.filter(f => extensions.includes(f.extension)),
      bySize: (files: any[], maxSize: number) =>
        files.filter(f => f.size <= maxSize),
      byRelevance: (files: any[], query: string) =>
        files.filter(f => f.content.includes(query))
    }
  };
  
  optimizeForContext(content: string, targetTokens: number): string {
    let optimized = content;
    
    // Apply compression strategies
    for (const [name, strategy] of Object.entries(this.strategies.compression)) {
      optimized = strategy(optimized);
      
      if (this.estimateTokens(optimized) <= targetTokens) {
        return optimized;
      }
    }
    
    // If still too large, truncate intelligently
    return this.intelligentTruncate(optimized, targetTokens);
  }
  
  private estimateTokens(text: string): number {
    // Rough estimation: 1 token ≈ 4 characters
    return Math.ceil(text.length / 4);
  }
  
  private intelligentTruncate(text: string, targetTokens: number): string {
    const targetChars = targetTokens * 4;
    
    if (text.length <= targetChars) {
      return text;
    }
    
    // Try to truncate at a natural boundary
    const truncated = text.slice(0, targetChars);
    const lastNewline = truncated.lastIndexOf('\n');
    
    if (lastNewline > targetChars * 0.8) {
      return truncated.slice(0, lastNewline);
    }
    
    return truncated + "\n... [truncated]";
  }
}

Parallel Processing

// parallel-processing.ts
class ParallelProcessor {
  private readonly MAX_CONCURRENT = 5;
  
  async processLargeDataset<T>(
    items: T[],
    processor: (item: T) => Promise<any>
  ): Promise<any[]> {
    const results: any[] = [];
    const queue = [...items];
    const inProgress = new Set<Promise<any>>();
    
    while (queue.length > 0 || inProgress.size > 0) {
      // Start new tasks up to the limit
      while (inProgress.size < this.MAX_CONCURRENT && queue.length > 0) {
        const item = queue.shift()!;
        const task = this.processWithRetry(item, processor)
          .then(result => {
            results.push(result);
            inProgress.delete(task);
          })
          .catch(error => {
            console.error("Task failed:", error);
            inProgress.delete(task);
          });
        
        inProgress.add(task);
      }
      
      // Wait for at least one task to complete
      if (inProgress.size > 0) {
        await Promise.race(inProgress);
      }
    }
    
    return results;
  }
  
  private async processWithRetry<T>(
    item: T,
    processor: (item: T) => Promise<any>,
    maxRetries = 3
  ): Promise<any> {
    let lastError;
    
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        return await processor(item);
      } catch (error) {
        lastError = error;
        await this.delay(Math.pow(2, attempt) * 1000);
      }
    }
    
    throw lastError;
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Cost Management

Cost Tracking and Budgeting

// cost-management.ts
class CostManager {
  private budget: number;
  private spent: number = 0;
  private alerts: Array<{ threshold: number; callback: () => void }> = [];
  
  constructor(budget: number) {
    this.budget = budget;
  }
  
  async trackOperation<T>(
    operation: () => Promise<T>,
    estimatedCost: number
  ): Promise<T> {
    if (this.spent + estimatedCost > this.budget) {
      throw new Error(`Operation would exceed budget. Current: $${this.spent}, Estimated: $${estimatedCost}, Budget: $${this.budget}`);
    }
    
    const result = await operation();
    
    this.spent += estimatedCost;
    this.checkAlerts();
    
    return result;
  }
  
  addAlert(threshold: number, callback: () => void) {
    this.alerts.push({ threshold, callback });
  }
  
  private checkAlerts() {
    const percentSpent = (this.spent / this.budget) * 100;
    
    for (const alert of this.alerts) {
      if (percentSpent >= alert.threshold) {
        alert.callback();
      }
    }
  }
  
  getStatus() {
    return {
      budget: this.budget,
      spent: this.spent,
      remaining: this.budget - this.spent,
      percentUsed: (this.spent / this.budget) * 100
    };
  }
}
 
// Usage
const costManager = new CostManager(100); // $100 budget
 
costManager.addAlert(80, () => {
  console.warn("80% of budget consumed!");
});
 
await costManager.trackOperation(
  async () => processWithGemini(largeContent),
  0.50 // Estimated $0.50 cost
);

Troubleshooting

Common Issues and Solutions

  1. Token Limit Exceeded

    // Solution: Implement chunking strategy
    if (error.message.includes("token limit")) {
      const chunks = chunkContent(content, 500000);
      const results = await Promise.all(
        chunks.map(chunk => processChunk(chunk))
      );
      return mergeResults(results);
    }
  2. Rate Limiting

    // Solution: Implement exponential backoff
    async function withRateLimitRetry(fn: () => Promise<any>) {
      for (let i = 0; i < 5; i++) {
        try {
          return await fn();
        } catch (error) {
          if (error.status === 429) {
            await delay(Math.pow(2, i) * 1000);
            continue;
          }
          throw error;
        }
      }
    }
  3. Context Cache Misses

    // Solution: Implement cache warming
    async function warmCache(documents: string[]) {
      const warmupPromises = documents.map(doc =>
        processWithCache(doc, "analyze", { warmup: true })
      );
      await Promise.all(warmupPromises);
    }

Debugging Techniques

// debug-helper.ts
class DebugHelper {
  static logContextSize(content: string) {
    console.log({
      characters: content.length,
      estimatedTokens: Math.ceil(content.length / 4),
      lines: content.split('\n').length,
      recommendedModel: content.length > 4_000_000 ? 'gemini-2.5-pro' : 'gemini-1.5-flash'
    });
  }
  
  static async measurePerformance(
    operation: () => Promise<any>,
    label: string
  ) {
    console.time(label);
    const memBefore = process.memoryUsage();
    
    try {
      const result = await operation();
      console.timeEnd(label);
      
      const memAfter = process.memoryUsage();
      console.log(`Memory delta: ${(memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024}MB`);
      
      return result;
    } catch (error) {
      console.timeEnd(label);
      throw error;
    }
  }
}

Additional Resources

Workshop Materials

  • Workshop materials available upon request
  • Example implementations in the patterns library
  • Interactive exercises coming soon

Community Resources


Tags: claude-code gemini ai-integration large-context typescript workshop mcp model-orchestration production-patterns

Version: 1.0.0