How NextChat Supports Multiple AI Models: Claude, Gemini, and GPT-4 Integration

NextChat uses a provider-agnostic architecture where the ClientApi class dynamically instantiates provider-specific adapters—such as ClaudeApi, GeminiProApi, or ChatGPTApi—based on the ModelProvider enum, enabling seamless integration of diverse LLMs through a unified LLMApi interface.

The ChatGPTNextWeb/NextChat repository demonstrates a robust pattern for supporting multiple AI providers within a single chat interface. By abstracting vendor-specific protocols behind a common interface, NextChat treats Claude, Gemini, and GPT-4 as first-class citizens while maintaining type safety and extensibility. This architecture allows users to switch between models without changing the underlying application logic.

Provider-Agnostic Architecture

NextChat's multi-model capability relies on a strict separation between the UI layer and backend-specific implementations. The system uses TypeScript enums and abstract classes to standardize interactions across heterogeneous AI services.

ModelProvider Enum and Type Safety

The foundation of multi-model support is the ModelProvider enum defined in app/constant.ts. This enumeration serves as the single source of truth for every supported backend, mapping human-readable identifiers to internal constants used throughout the routing layer.

// app/constant.ts
export enum ModelProvider {
  Stability = "Stability",
  GPT = "GPT",
  GeminiPro = "GeminiPro",
  Claude = "Claude",
  // ...
}

Each chat configuration stores a providerName property corresponding to these enum values, enabling the system to route requests correctly without hardcoding provider logic in React components.

ClientApi as the Central Router

The ClientApi class in app/client/api.ts functions as a factory that instantiates the appropriate provider implementation at runtime. Its constructor accepts a ModelProvider value and uses a switch statement to bind the correct adapter to the this.llm property.

// app/client/api.ts
constructor(provider: ModelProvider = ModelProvider.GPT) {
  switch (provider) {
    case ModelProvider.GeminiPro: 
      this.llm = new GeminiProApi(); 
      break;
    case ModelProvider.Claude:   
      this.llm = new ClaudeApi();   
      break;
    // ...
    default:                     
      this.llm = new ChatGPTApi(); 
      break;
  }
}

This pattern ensures that the rest of the application interacts with a uniform llm object implementing the LLMApi interface, regardless of whether the underlying model is Claude, Gemini, or GPT-4.

Provider-Specific Adapters

Each supported AI model implements the abstract LLMApi interface, which standardizes methods for chat, speech, usage, and models queries. These adapters handle protocol translation, authentication headers, streaming logic, and response parsing.

Claude (Anthropic) Integration

The ClaudeApi class in app/client/platforms/anthropic.ts builds Anthropic-specific request payloads, mapping NextChat's internal message roles to Anthropic's format. It handles tool calling through the stream and streamWithThink methods, managing unique requirements such as system prompt placement and stop sequence handling specific to the Anthropic API.

Gemini (Google) Integration

Located in app/client/platforms/google.ts, the GeminiProApi adapter converts NextChat message arrays into Gemini's contents schema. It injects Google-specific safety settings and implements streaming via Server-Sent Events (SSE), translating Gemini's chunked response format into the standard callback interface (onUpdate, onFinish, onError) consumed by the UI.

GPT-4 (OpenAI) Integration

The ChatGPTApi class in app/client/platforms/openai.ts supports OpenAI and Azure OpenAI endpoints. This adapter handles GPT-4, GPT-4o, O1, and vision capabilities, formatting requests to accommodate DALL·E image generation and function calling while maintaining compatibility with standard chat completion streams.

Runtime Provider Selection

Developers interact with multiple models through the getClientApi helper function, which abstracts the instantiation logic and returns a pre-configured ClientApi instance.

Selecting a Provider at Runtime

import { getClientApi } from "@/app/client/api";
import { ServiceProvider } from "@/app/constant";

// Initialize Google (Gemini) client
const geminiClient = getClientApi(ServiceProvider.Google);

// Initialize Anthropic (Claude) client  
const claudeClient = getClientApi(ServiceProvider.Anthropic);

// Initialize OpenAI (GPT-4) client
const gptClient = getClientApi(ServiceProvider.OpenAI);

The getClientApi function implements the same switching logic as the ClientApi constructor, ensuring consistent provider resolution across the application.

Unified Chat Interface

Regardless of the underlying provider, chat requests follow an identical structure defined by the ChatOptions interface:

const chatOpts = {
  messages: [
    { role: "user", content: "Explain quantum entanglement in plain language." },
  ],
  config: {
    model: "claude-3.5-sonnet",  // or "gemini-1.5-pro", "gpt-4o"
    providerName: "Anthropic",   // determines routing
    stream: true,
    temperature: 0.7,
    max_tokens: 2048,
  },
  onUpdate: (msg, chunk) => console.log("Δ", chunk),
  onFinish: (msg, _) => console.log("✅", msg),
  onError: err => console.error(err),
};

await claudeClient.llm.chat(chatOpts);

The same code pattern works for any supported model; only the model string and providerName values change, allowing dynamic model switching without refactoring business logic.

Extending Support for New Providers

Adding a new AI provider requires implementing three specific integration points without modifying the UI layer.

  1. Extend the enum in app/constant.ts:

    export enum ModelProvider { 
      // ... existing providers
      MyNewAI = "MyNewAI" 
    }
  2. Create the adapter in app/client/platforms/mynewai.ts:

    export class MyNewAIApi implements LLMApi {
      async chat(options: ChatOptions) {
        // Provider-specific implementation
      }
      async speech() { /* ... */ }
      async usage() { /* ... */ }
      async models() { /* ... */ }
    }
  3. Register the switch case in app/client/api.ts:

    case ModelProvider.MyNewAI:
      this.llm = new MyNewAIApi();
      break;

The settings UI automatically populates the new provider in dropdown menus because it reads directly from the ModelProvider enum.

Summary

  • Provider abstraction: NextChat uses the ModelProvider enum and ClientApi class to route requests to the correct backend implementation based on the providerName configuration.
  • Unified interface: All providers implement the LLMApi interface, exposing standardized methods for chat, speech, and usage queries across Claude, Gemini, and GPT-4.
  • Adapter pattern: Platform-specific logic lives in dedicated files like app/client/platforms/anthropic.ts, handling protocol translation, authentication, and streaming quirks.
  • Dynamic routing: The getClientApi function instantiates providers at runtime, allowing the same chat logic to power conversations with any supported model.
  • Extensible design: New models require only enum extension and adapter implementation, with zero changes to React components or chat state management.

Frequently Asked Questions

How does NextChat determine which AI provider to use for a chat request?

NextChat extracts the providerName property from the chat configuration object, which corresponds to a value in the ModelProvider enum defined in app/constant.ts. The ClientApi constructor or getClientApi factory function uses this value in a switch statement to instantiate the appropriate adapter class, such as ClaudeApi for Anthropic or GeminiProApi for Google.

Can I switch between GPT-4 and Claude in the same conversation thread?

While NextChat supports configuring different models per chat session, switching providers mid-conversation requires re-instantiating the client with a new configuration. The ClientApi initializes the provider adapter once per instance, so dynamic switching within a single request stream necessitates creating a new chat session or changing the model settings before the next message.

What interface must new provider adapters implement?

All provider adapters must implement the LLMApi interface exported from the client API layer. This interface requires four main methods: chat() for text generation with streaming support, speech() for text-to-speech conversion, usage() for quota checking, and models() for fetching available model lists. The chat() method must specifically handle the onUpdate, onFinish, and onError callbacks to integrate with NextChat's streaming UI.

Where does NextChat handle provider-specific authentication?

Each platform adapter manages its own authentication logic within its implementation file in app/client/platforms/. For example, app/client/platforms/anthropic.ts injects x-api-key headers for Claude, while app/client/platforms/google.ts handles Google API key parameters and safety settings. This encapsulation ensures that credential formatting and provider-specific headers remain isolated from the core chat logic in app/client/api.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →