How OmniRoute Translates Requests Between Different AI Provider Formats

OmniRoute uses a centralized translator registry in open-sse/translator/ to map request and response payloads between any pair of AI provider formats, enabling OpenAI-compatible clients to call Gemini, Claude, and dozens of other LLM APIs without format changes.

OmniRoute acts as a universal front-end for LLM providers. The core translation capability lives in a dedicated layer that rewrites payloads on-the-fly, handling everything from generation parameters to tool calling conventions. This article examines how the system converts requests between formats like OpenAI, Gemini, and Claude based on the source code in the diegosouzapw/OmniRoute repository.

Translator Registry Architecture

The translation system centers on a registry that maps source:format → target:format pairs to converter functions.

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

type RequestTranslator = (
  model: string,
  body: Record<string, unknown>,
  stream?: boolean,
  credentials?: Record<string, unknown> | null
) => unknown;

type ResponseTranslator = (
  chunk: Record<string, unknown>,
  state: Record<string, unknown>
) => unknown;

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

The register() function populates these maps:

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);
}

Each provider pair implementation calls register() during initialization. The registry then serves getRequestTranslator() and getResponseTranslator() lookups at runtime.

Request Translation: OpenAI to Gemini

The most complex translator in OmniRoute converts OpenAI-formatted requests to Gemini's API schema. Located in open-sse/translator/request/openai-to-gemini.ts, this module handles numerous structural differences between the two formats.

Generation Configuration Mapping

Lines 88-102 extract OpenAI parameters and map them to Gemini's generationConfig:

OpenAI Field Gemini Target
temperature temperature
top_p topP
max_tokens maxOutputTokens
stop stopSequences

Thinking and Reasoning Budget

Gemini supports a thinkingConfig budget for reasoning models. The translator derives this from either:

  • OpenAI's reasoning_effort parameter (lines 124-140)
  • Claude-style thinking objects with type and budget_tokens (lines 141-172)

This bridges capabilities across provider ecosystems.

Tool Call Transformation

Tool handling spans lines 183-334 and addresses several incompatibilities:

  • Function signature conversion: OpenAI's JSON Schema tools become Gemini functionCall parts
  • Name sanitization: Removes characters illegal in Gemini tool names
  • Built-in tool stripping: Optionally removes Gemini-native tool names that conflict with client-defined tools
  • Bidirectional mapping: Maintains Map<string,string> to restore original names in responses (lines 16-23, 112-119)

System Messages and Safety

Multiple system messages get merged into Gemini's single systemInstruction (lines 47-58). Safety settings propagate from the OpenAI payload or fall back to DEFAULT_SAFETY_SETTINGS.

Response Format Conversion

Lines 90-106 translate OpenAI's response_format (JSON mode, JSON Schema) into Gemini's responseMimeType and responseSchema fields.

Finally, deepCleanUndefined (line 117) strips undefined values before the payload reaches Gemini's API.

Response Translation

Response translation typically reuses shared implementations. For OpenAI ↔ Gemini, open-sse/translator/response/openai-to-gemini.ts registers the Antigravity response converter:

register(
  FORMATS.OPENAI,
  FORMATS.GEMINI,
  null, // no request translator in this file
  openaiToAntigravityResponse // shared response logic
);

The openaiToAntigravityResponse function in open-sse/translator/response/openai-to-antigravity.ts handles:

  • Extracting candidates from Gemini's candidates array
  • Converting functionResponse parts back to OpenAI-style tool_calls
  • Restoring original tool names using the bidirectional map
  • Streaming SSE chunks in the client's expected format

End-to-End Translation Flow

Understanding how OmniRoute translates requests between AI provider formats requires seeing the full request lifecycle:

  1. Request ingestion: The API route (e.g., /v1/chat/completions) validates the body with Zod schemas
  2. Format detection: The handler identifies the source format as FORMATS.OPENAI
  3. Target resolution: Routing logic selects the downstream provider (e.g., Gemini)
  4. Request translation: translateRequest()getRequestTranslator(FORMATS.OPENAI, FORMATS.GEMINI)openaiToGeminiRequest()
  5. Execution: The translated payload passes to the provider-specific executor for the HTTP call
  6. Response streaming: As chunks arrive, translateResponse()openaiToAntigravityResponse rewrites each chunk back to OpenAI format before SSE delivery

This pipeline remains identical regardless of which provider pair is active—the registry abstraction makes the specific translator pluggable.

Extending the Translator System

Adding support for a new AI provider requires only three steps:

  1. Implement request translator: Map source format fields to the target provider's schema, handling tools, generation config, and provider-specific features
  2. Implement response translator (optional): Convert the provider's response chunks back to the client's expected format
  3. Register the pair: Call register(from, to, requestFn, responseFn) in the appropriate module

Because the registry uses simple Map lookups, no changes to routing, SSE handling, or streaming logic are necessary.

Direct API Usage

OmniRoute's translators can be invoked directly for custom integrations:

import { getRequestTranslator, getResponseTranslator } from "@/open-sse/translator/registry";
import { FORMATS } from "@/open-sse/translator/formats";

// Translate OpenAI request to Gemini format
const requestBody = {
  model: "gpt-4",
  messages: [{ role: "user", content: "Hello" }],
  temperature: 0.7
};
const model = "gemini-1.5-flash";

const requestTranslator = getRequestTranslator(FORMATS.OPENAI, FORMATS.GEMINI);
const geminiPayload = requestTranslator?.(model, requestBody, false, null);

// Later: translate Gemini response chunk back to OpenAI format
const geminiChunk = { /* Gemini streaming response */ };
const responseTranslator = getResponseTranslator(FORMATS.GEMINI, FORMATS.OPENAI);
const openaiChunk = responseTranslator?.(geminiChunk, {});

Key Source Files

File Purpose
open-sse/translator/registry.ts Central translator registry with register(), getRequestTranslator(), getResponseTranslator()
open-sse/translator/formats.ts Format enum definitions (OPENAI, GEMINI, ANTIGRAVITY, CLAUDE, etc.)
open-sse/translator/request/openai-to-gemini.ts Full OpenAI → Gemini implementation with thinking budgets, tool mapping, safety settings
open-sse/translator/request/openai-to-claude.ts OpenAI → Claude conversion with Anthropic-specific tool handling
open-sse/translator/request/claude-to-openai.ts Reverse direction for Claude-native clients
open-sse/translator/request/gemini-to-openai.ts Gemini-native client to OpenAI backend
open-sse/translator/response/openai-to-antigravity.ts Shared response converter used by multiple provider pairs

Summary

  • OmniRoute's translator layer decouples the public OpenAI-compatible API from downstream provider schemas using a lightweight registry pattern in open-sse/translator/registry.ts
  • Pair-wise registration maps source:format → target:format to optional request and response translator functions
  • OpenAI to Gemini translation demonstrates complex field mapping: generation config, thinking budgets, tool calls with name sanitization, system message merging, and safety settings
  • Response reuse allows multiple provider pairs to share converters like openaiToAntigravityResponse
  • Zero core changes are needed to add new providers—just implement translators and register them

Frequently Asked Questions

How does OmniRoute handle streaming responses during translation?

The response translator receives each chunk as it arrives from the downstream provider, transforms it through the registered converter, and immediately streams the rewritten chunk to the client. For Gemini responses, openaiToAntigravityResponse processes the candidates array incrementally, ensuring low-latency SSE delivery without buffering the complete response.

What happens if a provider format lacks equivalent fields?

OmniRoute's translators apply sensible defaults or omissions. For example, when OpenAI's reasoning_effort maps to Gemini's thinkingConfig, the translator falls back to a medium budget if no explicit value is provided. Unknown fields are stripped via deepCleanUndefined to prevent API errors, and provider-specific extensions are handled case-by-case in each translator implementation.

Can I use OmniRoute's translators outside the main routing pipeline?

Yes. The registry exports getRequestTranslator() and getResponseTranslator() as public APIs. Import these from @/open-sse/translator/registry along with the FORMATS enum to translate payloads in custom scripts, testing utilities, or alternative server implementations without invoking the full OmniRoute request handler.

Why does the OpenAI ↔ Gemini response translator reuse Antigravity logic?

Both Gemini and Antigravity share similar response envelope structures—nested candidates with content parts—so openaiToAntigravityResponse correctly handles both without duplication. This design pattern reduces maintenance burden: improvements to the shared converter automatically benefit both provider pairs, while provider-specific response handling remains in dedicated files when structural differences demand it.

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 →