How OmniRoute Translates Requests Between OpenAI, Claude, and Gemini

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, 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 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.
  • Safety Settings Injection: Applies DEFAULT_SAFETY_SETTINGS from 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. 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 orchestrates the reverse conversion.

Gemini-to-OpenAI conversion (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, 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) 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:

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, and returns the response in OpenAI format.

Streaming Gemini Responses

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

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, 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:

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 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, mapping (sourceFormat, targetFormat) pairs to specific converter functions.
  • Gemini translation requires consecutive role merging, tool name sanitization via 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 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 maps Claude's thinking content blocks into the OpenAI message structure. For Gemini, thought signatures are stored via geminiThoughtSignatureStore.ts during request translation and re-injected during response streaming through 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 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 converts Gemini's candidate chunks into OpenAI SSE deltas, while 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.

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 →