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

> Learn how OmniRoute's plugin system translates LLM requests and responses between providers, handling format differences, tool calls, and streaming for seamless integration.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-14

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts), two `Map` objects store the available translators:

```ts
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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:

```ts
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts) module implements detailed field mapping from OpenAI's chat completion format to Gemini's generateContent API:

```ts
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-claude.ts) applies Anthropic-specific rules:

```ts
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/response/openai-to-gemini.ts) reconstructs OpenAI-compatible `choices` from Gemini's `contents`:

```ts
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts):

```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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts) | Translator registration and lookup |
| [`open-sse/translator/formats.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/formats.ts) | Provider format constants |
| [`open-sse/translator/request/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-gemini.ts) | OpenAI → Gemini request conversion |
| [`open-sse/translator/request/openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-claude.ts) | OpenAI → Claude request conversion |
| [`open-sse/translator/response/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts)) to map or validate the field. The modular design isolates changes to the specific provider pair affected.