# How OmniRoute Translates Requests Between OpenAI, Claude, and Gemini

> Discover how OmniRoute translates requests between OpenAI, Claude, and Gemini. Learn about its registry-based translator layer and provider-specific adapters for seamless LLM integration.

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

---

**OmniRoute normalizes LLM provider differences through a registry-based translator layer that converts OpenAI-compatible payloads into Claude and Gemini native formats, then transforms responses back into OpenAI schema using provider-specific adapters.**

OmniRoute is an open-source API gateway that unifies access to multiple large language model providers. The project's translation architecture centers on the `open-sse/translator` package, which implements bidirectional format conversion between OpenAI's API schema and the native protocols used by Anthropic's Claude and Google's Gemini. This design allows downstream clients to remain agnostic to provider-specific requirements while OmniRoute handles protocol translation, tool name mapping, and streaming response normalization.

## The Translation Registry Architecture

The core routing mechanism resides in [`open-sse/translator/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts), which maintains a lookup map keyed by `(sourceFormat, targetFormat)` tuples. Each registered translator implements two functions: a **request translator** that converts incoming payloads to the target provider's schema, and a **response translator** that restructures provider responses back into OpenAI-compatible JSON or SSE streams.

When a request arrives at the API route (`/v1beta/models/[...path]/route.ts`), the system inspects the request body to detect the source format—checking for Gemini-style `contents` arrays or defaulting to OpenAI—then invokes `getTranslator()` to retrieve the appropriate converter pair. The registry handles all permutation combinations: OpenAI → Gemini, OpenAI → Claude, Gemini → OpenAI, and Claude → OpenAI.

## OpenAI-to-Gemini Request Translation

The [`open-sse/translator/request/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-gemini.ts) module handles the conversion from OpenAI's `messages` format to Gemini's `contents` envelope. This translator performs several critical normalizations:

- **Generation Config Mapping**: Translates `max_tokens` and `temperature` into Gemini's `generationConfig`, applying caps via `capMaxOutputTokens` and `capThinkingBudget` defined in [`modelCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/modelCapabilities.ts).
- **Safety Settings Injection**: Applies `DEFAULT_SAFETY_SETTINGS` from [`helpers/geminiHelper.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/helpers/geminiHelper.ts) while preserving caller-provided safety overrides.
- **Tool Name Sanitization**: Processes function names through `sanitizeGeminiToolName()` to resolve naming collisions, with optional prefix stripping controlled by `GeminiToolNameOptions`.
- **Consecutive Role Merging**: Gemini rejects adjacent messages with identical roles, so the translator invokes `mergeConsecutiveSameRoleContents()` (lines 68‑80) to collapse consecutive user or assistant messages into single content blocks.
- **System Instruction Handling**: Aggregates all system messages into Gemini's `systemInstruction` field, merging with any existing `body.system` parameters.

## OpenAI-to-Claude Request Translation

Conversion to Anthropic's format occurs in [`open-sse/translator/request/openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-claude.ts). This translator addresses Claude-specific schema requirements:

- **Thinking Budget Injection**: Automatically injects Claude's `thinking` block when the request requires extended reasoning capabilities, adjusting the `max_tokens` allocation to accommodate the thinking budget.
- **Tool Call Prefixing**: Prepends tool names with the configurable `CLAUDE_OAUTH_TOOL_PREFIX` constant to avoid namespace collisions.
- **Content Block Cleanup**: Removes empty text blocks that fail Anthropic's validation schema, ensuring only populated content arrays reach the upstream API.
- **Message Role Normalization**: Rewrites system messages into Claude's expected structure while preserving tool definitions and conversation history.

## Response Translation and Streaming

After the executor completes the upstream HTTP call, [`open-sse/handlers/responseTranslator.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/responseTranslator.ts) orchestrates the reverse conversion.

**Gemini-to-OpenAI conversion** ([`response/gemini-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/response/gemini-to-openai.ts)) parses streaming `candidates` objects from Gemini's SSE stream, reconstructing OpenAI-style `choices` with `delta` fields. The translator restores **thought signatures** using [`geminiThoughtSignatureStore.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/geminiThoughtSignatureStore.ts), allowing Gemini's reasoning traces to surface in the OpenAI-compatible response. Tool calls extract from Gemini's `functionCall` parts and remap to OpenAI's `tool_calls` array.

**Claude-to-OpenAI conversion** ([`response/claude-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/response/claude-to-openai.ts)) maps Claude's `content` blocks—whether text, tool_use, or tool_result—into OpenAI message objects. The translator normalizes tool result IDs and strips Claude-specific metadata that lacks equivalents in the OpenAI schema.

## Code Examples

### Calling Gemini Through OmniRoute

Submit an OpenAI-formatted request to receive Gemini responses normalized to OpenAI schema:

```javascript
import fetch from "node-fetch";

const resp = await fetch("http://localhost:20128/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "gemini-1.5-flash",
    messages: [
      { role: "system", content: "You are a helpful assistant." },
      { role: "user", content: "Explain quantum entanglement in plain language." }
    ],
    safetySettings: [
      { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_ONLY_HIGH" }
    ]
  })
});

const data = await resp.json();
console.log(data.choices[0].message.content);

```

OmniRoute detects the Gemini model identifier, routes through [`openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts), and returns the response in OpenAI format.

### Streaming Gemini Responses

Enable streaming to receive SSE chunks converted from Gemini's native format:

```javascript
const resp = await fetch("http://localhost:20128/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "gemini-1.5-pro",
    messages: [{ role: "user", content: "Write a poem about sunrise." }],
    stream: true
  })
});

for await (const chunk of resp.body) {
  const lines = chunk.toString().split('\n');
  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const data = JSON.parse(line.slice(6));
      process.stdout.write(data.choices[0].delta.content || '');
    }
  }
}

```

The pipeline routes Gemini `candidates` through [`openai-to-gemini-sse.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini-sse.ts), emitting OpenAI-compatible `delta` events while preserving thought signatures.

### Using Claude Tools via OmniRoute

Tool definitions remain in OpenAI format while OmniRoute handles Claude-specific transformations:

```javascript
const resp = await fetch("http://localhost:20128/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "claude-3-opus-1.2",
    messages: [{ role: "user", content: "What is the weather in Paris?" }],
    tools: [{
      type: "function",
      function: {
        name: "get_weather",
        description: "Fetch weather data",
        parameters: {
          type: "object",
          properties: { city: { type: "string" } },
          required: ["city"]
        }
      }
    }],
    tool_choice: "required"
  })
});

```

The [`openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-claude.ts) translator prefixes the tool name with `proxy_` (per `CLAUDE_OAUTH_TOOL_PREFIX`) and injects the `thinking` block if the model configuration requires it.

## Summary

- OmniRoute implements bidirectional translation through a registry pattern in [`open-sse/translator/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts), mapping `(sourceFormat, targetFormat)` pairs to specific converter functions.
- **Gemini translation** requires consecutive role merging, tool name sanitization via [`helpers/geminiToolsSanitizer.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/helpers/geminiToolsSanitizer.ts), and safety setting preservation.
- **Claude translation** handles thinking budget allocation, tool name prefixing, and removal of empty content blocks to satisfy Anthropic's validation.
- Response translators restore OpenAI schema compliance while preserving provider-specific features like Gemini thought signatures and Claude tool results.
- The API route at `/v1beta/models/[...path]/route.ts` automates format detection, enabling seamless provider switching without client code changes.

## Frequently Asked Questions

### How does OmniRoute handle Gemini's consecutive message restrictions?

Gemini's API rejects requests containing adjacent messages with identical roles. OmniRoute's [`openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini.ts) translator automatically merges consecutive messages using `mergeConsecutiveSameRoleContents()` (lines 68‑80), combining their content into single content parts while preserving the logical conversation flow.

### Can OmniRoute preserve Claude's thinking blocks when translating to OpenAI format?

Yes. When converting Claude responses to OpenAI format, [`claude-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/claude-to-openai.ts) maps Claude's `thinking` content blocks into the OpenAI message structure. For Gemini, thought signatures are stored via [`geminiThoughtSignatureStore.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/geminiThoughtSignatureStore.ts) during request translation and re-injected during response streaming through [`gemini-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/gemini-to-openai.ts), ensuring reasoning traces remain accessible in the unified response.

### What happens to tool names when routing between providers?

Tool names undergo provider-specific sanitization. For Gemini, `sanitizeGeminiToolName()` in [`helpers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/helpers.ts) cleans and deduplicates function names. For Claude, the translator prepends `CLAUDE_OAUTH_TOOL_PREFIX` to avoid namespace collisions. OmniRoute maintains internal mappings to ensure tool calls and results route correctly back to the client using original identifiers.

### Does OmniRoute support streaming responses from all providers?

Yes. The translation layer includes streaming-specific handlers: [`openai-to-gemini-sse.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/openai-to-gemini-sse.ts) converts Gemini's candidate chunks into OpenAI SSE deltas, while [`claude-to-openai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/claude-to-openai.ts) handles Claude's streaming content blocks. Both preserve real-time delivery while normalizing finish reasons and tool call fragments into the OpenAI streaming schema.