How OmniRoute Handles Format Translation Between Different LLM Providers: A Complete Technical Guide

OmniRoute uses a registry-based plugin system in open-sse/translator/ to convert requests and responses between incompatible LLM provider formats, mapping fields bidirectionally while handling tool calls, streaming, and provider-specific quirks.

The diegosouzapw/OmniRoute project solves a fundamental problem in modern AI infrastructure: every major LLM provider uses a different API schema. OpenAI, Google Gemini, and Anthropic Claude each have distinct request shapes, parameter names, authentication patterns, and response formats. OmniRoute's translation layer abstracts these differences away through a centralized, extensible architecture that lets developers route requests to any supported provider without changing client code.

How the Translation Registry Works

At the heart of OmniRoute's format translation is a central registry that maps source-to-target format pairs to conversion functions. This design enables dynamic translator lookup at runtime.

The Registry Data Structure

In open-sse/translator/registry.ts, two Map objects store the available translators:

const requestRegistry = new Map<string, RequestTranslator>();
const responseRegistry = new Map<string, ResponseTranslator>();

export function register(
  from: string,
  to: string,
  requestFn?: RequestTranslator,
  responseFn?: ResponseTranslator,
) {
  const key = `${from}:${to}`;
  if (requestFn) requestRegistry.set(key, requestFn);
  if (responseFn) responseRegistry.set(key, responseFn);
}

Registry keys follow the pattern "openai:gemini" or "openai:claude", constructed via a makeKey helper. Translators self-register at module load time, making the system automatically discoverable.

Format Constants

The FORMATS enum in open-sse/translator/formats.ts defines canonical names for each provider:

  • OPENAI
  • GEMINI
  • CLAUDE
  • ANTIGRAVITY

These constants ensure type-safe references throughout the translation pipeline.

Request Translation: Converting Client Requests to Provider Format

When a client sends a request, OmniRoute performs three steps: lookup the appropriate translator, transform the payload, and dispatch to the executor.

Step 1: Translator Lookup

The request handler fetches translators using the registry:

import { getRequestTranslator, getResponseTranslator } from "./registry.ts";

const reqTranslator = getRequestTranslator(FORMATS.OPENAI, targetFormat);
const respTranslator = getResponseTranslator(targetFormat, FORMATS.OPENAI);

Step 2: OpenAI to Gemini Translation

The openai-to-gemini.ts module implements detailed field mapping from OpenAI's chat completion format to Gemini's generateContent API:

function openaiToGeminiBase(
  model: string,
  body: Record<string, unknown>,
  stream: boolean,
  toolNameOptions: GeminiToolNameOptions = {}
) {
  const result: GeminiRequest = {
    model,
    contents: [],
    generationConfig: {},
    safetySettings: body.safetySettings || DEFAULT_SAFETY_SETTINGS,
  };

  // temperature → generationConfig.temperature
  if (body.temperature !== undefined) {
    result.generationConfig.temperature = body.temperature;
  }

  // max_tokens → generationConfig.maxOutputTokens (capped to model limits)
  const maxOutputTokens = capMaxOutputTokens(
    model,
    (body.max_tokens ?? body.max_completion_tokens) as number | undefined
  );
  if (maxOutputTokens !== null) {
    result.generationConfig.maxOutputTokens = maxOutputTokens;
  }

  // messages array transformed to Gemini contents with role mapping
  // tool calls converted to function declarations with signature handling
  // consecutive same-role messages merged (Gemini requirement)

  return result;
}

This translator handles thinking budgets, tool call signatures, and safety settings that have no direct OpenAI equivalent.

Step 3: OpenAI to Claude Translation

The Claude translator in openai-to-claude.ts applies Anthropic-specific rules:

export function openaiToClaudeRequest(model, body, stream, credentials = null) {
  const result: any = { 
    model, 
    max_tokens: adjustMaxTokens(body), 
    stream, 
    messages: [] 
  };

  // Temperature dropped when thinking is enabled (Claude constraint)
  if (body.temperature !== undefined && !modelForcesThinking) {
    result.temperature = body.temperature;
  }

  // System/developer messages extracted to Claude's top-level system array
  // Tool names prefixed for OAuth-scoped calls
  // Empty text blocks stripped (Claude rejects these)
  // Minimal user turn injected when only system messages exist

  return result;
}

Key differences from Gemini translation include system message handling (Claude uses a separate system parameter), tool name prefixing, and reasoning budget configuration.

Response Translation: Normalizing Provider Responses

After the upstream LLM returns a response, OmniRoute translates it back to the client's expected format.

Gemini to OpenAI Response Conversion

The response translator in open-sse/translator/response/openai-to-gemini.ts reconstructs OpenAI-compatible choices from Gemini's contents:

// Converts Gemini contents → OpenAI choices
// Handles streaming deltas for SSE responses
// Maps tool_responses back to function call results
// Preserves thought signatures in assistant messages

This ensures clients receive consistent response shapes regardless of which provider actually served the request.

Provider-Specific Edge Case Handling

OmniRoute embeds defensive logic for each provider's constraints:

  • Tool name sanitization (sanitizeGeminiToolName, sanitizeToolId) prevents 400 errors from invalid identifiers
  • Token budget capping (capThinkingBudget, capMaxOutputTokens) enforces model-specific limits
  • Signature handling manages Gemini's thoughtSignature and Claude's thinking blocks without breaking requests
  • Consecutive role merging satisfies Gemini's requirement that adjacent messages have different roles

Adding New Providers to the Translation System

The pluggable architecture minimizes effort for new provider support. Implementers need only:

  1. Create request and response translator functions
  2. Register them with register(from, to, requestFn, responseFn)
  3. Add the new format to FORMATS enum

Example registration from openai-to-gemini.ts:

register(
  FORMATS.OPENAI,
  FORMATS.GEMINI,
  (model, body, stream = false, credentials = null) =>
    openaiToGeminiRequest(model, body, stream, credentials, {
      signaturelessToolCallMode: "context",
    }),
  null, // response translator registered separately
);

The rest of the pipeline automatically discovers and uses the new translator.

Core Translation Files Reference

File Purpose
open-sse/translator/registry.ts Translator registration and lookup
open-sse/translator/formats.ts Provider format constants
open-sse/translator/request/openai-to-gemini.ts OpenAI → Gemini request conversion
open-sse/translator/request/openai-to-claude.ts OpenAI → Claude request conversion
open-sse/translator/response/openai-to-gemini.ts Gemini → OpenAI response conversion
open-sse/translator/helpers/* Sanitization, capping, and utility functions

Summary

  • Registry-based architecture: OmniRoute uses Map structures in registry.ts to dynamically pair source and target format translators
  • Bidirectional conversion: Separate request and response translators handle each direction of format transformation
  • Field-level mapping: Translators rewrite parameters like temperature, max_tokens, and messages to provider-specific equivalents
  • Provider quirks handled: Tool names, thinking budgets, safety settings, and role constraints are normalized automatically
  • Extensible design: New providers require only translator function implementation and register() invocation

Frequently Asked Questions

How does OmniRoute know which translator to use for a given request?

The request handler inspects the target provider from the request metadata, then calls getRequestTranslator(FORMATS.OPENAI, targetFormat) to retrieve the appropriate conversion function. The registry key pattern "openai:gemini" enables O(1) lookup of registered translators.

Can OmniRoute translate between non-OpenAI formats directly?

The registry supports any source-to-target pair. While the current implementation primarily registers OpenAI-to-provider translators, the architecture supports arbitrary mappings. Direct Gemini-to-Claude translation would require registering a translator with register(FORMATS.GEMINI, FORMATS.CLAUDE, ...).

What happens if a provider adds new API parameters?

Translators are versioned with the codebase. New parameters require updates to the relevant translator file (e.g., openai-to-gemini.ts) to map or validate the field. The modular design isolates changes to the specific provider pair affected.

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 →