# How OmniRoute Converts Between OpenAI, Anthropic, and Gemini API Formats

> Discover how OmniRoute's translator layer effortlessly converts between OpenAI, Anthropic, and Gemini API formats using a hub-and-spoke engine with OpenAI's JSON schema as the intermediary.

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

---

**OmniRoute’s translator layer normalizes payloads through a hub-and-spoke engine that uses OpenAI’s JSON schema as the intermediate lingua-franca, executing direct source-to-target translations when available and falling back to a two-hop conversion otherwise.**

OmniRoute is an open-source routing layer built on Next.js that lets users convert between OpenAI, Anthropic, and Gemini API formats without rewriting client code. Its translator layer sits between the Next.js API routes and the provider executors, reshaping request and response payloads so that a client can speak any supported LLM schema while the upstream provider receives its native format. This article breaks down exactly how the `open-sse/translator` module orchestrates these conversions, as implemented in `diegosouzapw/OmniRoute`.

## Translator Registry and Bootstrapping

The translation pipeline starts with a global **Translator Registry** defined in [`open-sse/translator/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts). This registry stores request and response translator functions for every supported source-to-target pair via three core functions: `register()`, `getRequestTranslator()`, and `getResponseTranslator()`.

When the translator module loads, `bootstrapTranslatorRegistry()` runs immediately at the top of [`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts#L30-L31). It imports every per-format translator file, and each file self-registers by calling `register(source, target, requestFn, responseFn)`. The registry therefore contains an entry for every conversion pair before any traffic hits the API.

Individual translator modules live in `open-sse/translator/request/` and `open-sse/translator/response/`. Notable examples include [`openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-claude.ts), [`openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts), [`claude-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/claude-to-openai.ts), and [`gemini-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/gemini-to-openai.ts).

## Hub-and-Spoke Request and Response Translation

The central orchestration lives in [`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts). The engine implements `translateRequest()` and `translateResponse()`, which decide whether to use a direct translator or a two-step hub-and-spoke path.

### The `translateRequest` Flow

The `translateRequest()` function (defined at lines L49-L73) accepts parameters for `sourceFormat`, `targetFormat`, `model`, `body`, `stream`, `credentials`, `provider`, `reqLogger`, and optional `options`. The execution follows a strict three-step sequence:

1. **Normalization.** The function applies thinking-budget adjustments, role normalization, tool-call ID handling, and other pre-processing (lines L70-L91).

2. **Direct or Hub-and-Spoke Routing.** If a direct translator exists and neither format is OpenAI, the engine invokes it via `getRequestTranslator(source, target)`. For all other cases, it performs a two-hop conversion: first from **source → OpenAI** (`getRequestTranslator(source, FORMATS.OPENAI)`), then from **OpenAI → target** (`getRequestTranslator(FORMATS.OPENAI, target)`). The intermediate OpenAI payload is logged via `reqLogger?.logOpenAIRequest` for observability.

3. **Post-Processing.** The pipeline strips OpenAI-only echo fields, injects reasoning cache placeholders, sanitizes tool schemas, and ensures final IDs are unique.

This design means a client can submit a Gemini-shaped payload and have it delivered to Anthropic’s Claude endpoint without either side knowing the difference.

### The `translateResponse` Flow

The reverse path is implemented symmetrically in `translateResponse()` (see lines L87-L112). The function evaluates three conditions in order:

- If **source equals target**, the chunk returns unchanged.
- If a **direct response translator** exists for target → source, it is invoked via `getResponseTranslator()`.
- Otherwise, the engine routes the response through **target → OpenAI → source** using the same hub-and-spoke pattern (referenced at lines L11-L25).

The function also propagates an `_openaiIntermediate` field so that logging layers can capture the normalized representation.

### Why OpenAI Is the Lingua-Franca

OpenAI’s JSON schema is the most widely supported interchange format across the LLM ecosystem, with many vendors exposing “OpenAI-compatible” endpoints. By normalizing everything through this pivot, OmniRoute only needs **one** translator per non-OpenAI format, which dramatically reduces code duplication. Direct converters—such as Claude → Gemini in [`open-sse/translator/request/claude-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/claude-to-gemini.ts)—are still registered and preferred for performance-critical pairs when both endpoints are non-OpenAI.

## Normalizing Edge Cases Across Providers

Beyond simple field mapping, the translator layer accounts for provider quirks through helpers in `open-sse/translator/helpers/`.

### Tool Calls and Schema Coercion

Tool-related payloads are normalized by `ensureToolCallIds()` and `fixMissingToolResponses()` inside modules such as [`toolCallHelper.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/toolCallHelper.ts). Placeholder tool results are injected when a provider expects them but the source format omits them, while schemas are coerced en route to match the target’s expected structure.

### Role Mapping and Reasoning Cache

The engine converts developer-role messages to system-role unless `preserveDeveloperRole` is set. For providers like DeepSeek and Kimi that require reasoning payloads, the translator injects placeholders via reasoning helpers and can replay cached reasoning using `lookupReasoning()` and `recordReplay()`. Cache-control flags are propagated only when `providerHonorsOpenAIFormatCacheControl()` returns true (lines L122-L130).

## Converting Requests and Responses: Code Examples

### Claude to Gemini Direct Translation

When a client sends a Claude-compatible payload that must reach a Gemini model, OmniRoute can use a direct translator if both formats are non-OpenAI:

```typescript
import { translateRequest, FORMATS } from '@/open-sse/translator';

const claudePayload = {
  messages: [{ role: 'user', content: 'Hello' }],
  // ...Claude-specific fields...
};

const geminiPayload = translateRequest(
  FORMATS.CLAUDE,
  FORMATS.GEMINI,
  'gemini-1.5-flash',
  claudePayload,
  true,
  null,
  'google'
);

```

In this case, the registry locates the direct translator registered in [`open-sse/translator/request/claude-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/claude-to-gemini.ts) and executes it without an intermediate OpenAI hop.

### OpenAI to Anthropic Request Conversion

Because OpenAI is the hub, translating an OpenAI-compatible request to Claude follows a truncated path:

```typescript
import { translateRequest, FORMATS } from '@/open-sse/translator';

const openaiPayload = {
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Summarize this.' }],
};

const claudePayload = translateRequest(
  FORMATS.OPENAI,
  FORMATS.CLAUDE,
  'claude-3-5-sonnet-20240620',
  openaiPayload,
  true,
  null,
  'anthropic'
);

```

The hub-and-spoke path effectively runs OpenAI → OpenAI (no change), then OpenAI → Claude via [`open-sse/translator/request/openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-claude.ts).

### Gemini Response to OpenAI Format

```typescript
import { translateResponse, FORMATS } from '@/open-sse/translator';

const geminiChunk = {
  candidates: [{ content: { parts: [{ text: 'Answer' }] } }],
  // ...Gemini-specific fields...
};

const openaiChunk = translateResponse(
  FORMATS.GEMINI,
  FORMATS.OPENAI,
  geminiChunk,
  {}
);

```

The engine calls [`gemini-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/gemini-to-openai.ts) to extract Gemini `candidates` and reshape them into OpenAI `choices`. If a direct Gemini → OpenAI response translator is registered, it is used immediately; otherwise the hub-and-spoke routing applies.

## Summary

- OmniRoute’s translator layer lives under `open-sse/translator/` and bridges Next.js API routes with provider executors.
- [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts) maintains a global map of request and response translators bootstrapped at startup via `bootstrapTranslatorRegistry()`.
- `translateRequest()` and `translateResponse()` in [`index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/index.ts) prefer direct source-to-target translators, then fall back to a **source → OpenAI → target** hub-and-spoke pattern.
- OpenAI’s JSON schema acts as the lingua-franca because it is the de-facto standard across the LLM industry.
- Edge cases—including tool-call IDs, role normalization, reasoning cache, and cache-control flags—are handled by helpers in `open-sse/translator/helpers/`.

## Frequently Asked Questions

### What makes OpenAI the default hub format in OmniRoute?

OpenAI’s JSON schema is the most widely adopted request and response format among LLM providers, many of whom expose OpenAI-compatible endpoints. Using it as the pivot means OmniRoute only needs one translator per non-OpenAI format instead of an n² matrix of converters.

### Does OmniRoute support direct translation between Claude and Gemini?

Yes. When a direct translator is registered for a source-target pair and neither side is OpenAI, OmniRoute invokes it immediately. For example, [`open-sse/translator/request/claude-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/claude-to-gemini.ts) handles Claude → Gemini directly without converting through OpenAI.

### How does OmniRoute handle tool calls across different API formats?

The translator normalizes tool-call IDs via `ensureToolCallIds()`, injects missing tool responses through `fixMissingToolResponses()`, and coerces schemas before sending the payload to the target provider. These helpers reside in [`open-sse/translator/helpers/toolCallHelper.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/helpers/toolCallHelper.ts) and related files.

### Where does the translator registry get initialized?

The registry is initialized at module load time by `bootstrapTranslatorRegistry()`, which is called at lines L30-L31 in [`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts). Each translator file self-registers by importing the registry and calling `register()` with its source format, target format, and translation functions.