How OmniRoute Translates Requests Between OpenAI, Claude, and Gemini Model Formats
OmniRoute acts as a universal routing layer that accepts requests written for one LLM provider and forwards them to another by converting the payload into the target provider's wire format using a registry-based translator system.
The OmniRoute repository (diegosouzapw/OmniRoute) implements a sophisticated format translation layer that enables seamless interoperability between diverse LLM APIs. By treating provider-specific request shapes as interchangeable formats, OmniRoute allows developers to send an OpenAI-style request to a Gemini backend—or vice versa—without manual payload restructuring. This translation occurs through pure functions registered in a central map, ensuring type-safe conversions for both requests and streaming responses.
The Two-Stage Translation Architecture
OmniRoute's translation system operates through two distinct but coordinated mechanisms: a lightweight registry for lookup and pure functions for payload transformation.
Translator Registry
At the heart of the system lies the Translator Registry, defined in open-sse/translator/registry.ts. This module maintains two global maps—requestRegistry and responseRegistry—that bind a source format identifier to a target format identifier. The registry exposes three critical functions:
register(source, target, requestTranslator, responseTranslator)– Associates conversion functions with format pairs at module load time.getRequestTranslator(source, target)– Retrieves the appropriate request conversion function.getResponseTranslator(source, target)– Retrieves the appropriate response conversion function.
This design decouples the routing logic from translation logic, allowing new providers to be added without modifying core request handlers.
Request and Response Translators
Request Translators are pure functions that receive the original model name, JSON body, a streaming flag, and optional credential data, then emit a new object matching the target provider's API contract. For example, openaiToGeminiRequest in open-sse/translator/request/openai-to-gemini.ts handles the OpenAI → Gemini conversion, while claudeToOpenaiRequest in open-sse/translator/request/claude-to-openai.ts handles the Claude → OpenAI path.
Response Translators perform the inverse operation for streaming replies. The file open-sse/translator/response/openai-to-gemini.ts registers openaiToAntigravityResponse(), which reshapes OpenAI-style streaming chunks into Gemini's candidates envelope format.
How Request Translation Works
The translation pipeline follows a deterministic six-step process orchestrated by the chat handler in open-sse/handlers/chatCore.ts:
-
Detect Source Format – The handler inspects incoming JSON. If the payload contains a
contentsarray, OmniRoute identifies it as Gemini format; otherwise, it falls back to OpenAI or Claude based on the route URL. -
Lookup Translator – The system calls
getRequestTranslator(FORMATS.OPENAI, FORMATS.GEMINI)to fetch the conversion function from the registry. -
Convert Payload – The translator executes provider-specific logic. In
openai-to-gemini.ts, theopenaiToGeminiBase()function:- Copies generation-config fields (
temperature,top_p,stop, etc.) into Gemini'sgenerationConfig. - Transfers user-provided
cachedContentif present. - Invokes
sanitizeGeminiToolName()to rewrite tool names for Vertex AI compatibility. - Handles tool-calling configuration via
convertOpenAIToolChoiceToGemini(). - Optionally wraps the request in a Cloud-Code envelope for Antigravity using
wrapInCloudCodeEnvelope.
- Copies generation-config fields (
-
Register Conversion – During module initialization,
bootstrap.tsinvokesregister(FORMATS.OPENAI, FORMATS.GEMINI, openaiToGeminiRequest, null), making the conversion available for all subsequent requests. -
Stream Response Translation – For streaming requests, the response translator reshapes chunks in real-time. The Gemini-compatible upstream streams through
openaiToAntigravityResponse(), ensuring the downstream client receives its expected format. -
Edge-Case Handling – The system manages provider-specific limitations:
- Tool name sanitization removes built-in Gemini tool names that Antigravity rejects (defined in
GEMINI_BUILTIN_TOOL_NAMES). - Thought-signature support adds signature fields for Vertex AI when
supportsSignatureBypassis enabled. - Safety settings default to
DEFAULT_SAFETY_SETTINGSunless the client overrides them.
- Tool name sanitization removes built-in Gemini tool names that Antigravity rejects (defined in
Code Examples: Converting Between Formats
Below are practical implementations showing how to trigger translations programmatically.
Automatic Translation in API Routes
When handling chat completions, the router automatically selects the appropriate translator based on source and target formats:
import { getRequestTranslator } from '@omniroute/open-sse/translator';
import { FORMATS } from '@omniroute/open-sse/translator/formats';
async function handleChat(req) {
const source = FORMATS.OPENAI; // Request arrived as OpenAI payload
const target = FORMATS.GEMINI; // Router decided to use Gemini
const translate = getRequestTranslator(source, target);
if (!translate) throw new Error('No translator for this pair');
const { model, body, stream, credentials } = req;
const geminiBody = translate(model, body, stream, credentials);
return await callGeminiProvider(geminiBody);
}
Direct Translator Invocation
For testing or utility scripts, you can import translators directly and bypass the registry:
import { openaiToGeminiRequest } from '@omniroute/open-sse/translator/request/openai-to-gemini';
const openaiBody = {
model: 'gpt-4o',
temperature: 0.7,
top_p: 0.9,
messages: [{ role: 'user', content: 'Explain quantum entanglement.' }],
};
const geminiPayload = openaiToGeminiRequest(
'gemini-1.5-pro', // target Gemini model
openaiBody,
false, // not a streaming request
{ _provider: 'vertex', _preserveCacheControl: true }
);
console.log(JSON.stringify(geminiPayload, null, 2));
Key Design Features
Decoupled Registration – Centralizing the mapping in registry.ts means adding a new provider requires only a single register() call in bootstrap.ts, without touching the core request handling logic in chatCore.ts.
Pure Functions – Translators receive only primitive data and return plain objects, containing no side effects. This architecture makes unit testing straightforward, as demonstrated by the test suite in tests/unit/translator-openai-to-gemini.test.ts.
Feature-Flag Awareness – Helper functions like capThinkingBudget and capMaxOutputTokens respect model-level capabilities, ensuring translated requests never exceed provider-specific limits.
Streaming Parity – The response path mirrors the request path, guaranteeing that both request and response are consistently transformed for any combination of source and target formats.
Summary
- OmniRoute translates requests between different model formats using a registry-based architecture defined in
open-sse/translator/registry.ts. - Request translators are pure functions that convert payloads between OpenAI, Claude, and Gemini formats, handling tool names, safety settings, and generation configs.
- Response translators ensure streaming chunks are reshaped to match the client's expected wire format.
- The system uses feature flags and provider-specific helpers to handle edge cases like Vertex AI requirements and Antigravity compatibility.
- Registration occurs at module load time via
bootstrap.ts, making new format pairs available globally without code changes to handlers.
Frequently Asked Questions
How does OmniRoute determine which translator to use for a request?
OmniRoute detects the source format by inspecting the incoming request structure in open-sse/handlers/chatCore.ts. For example, if the JSON contains a contents array, it identifies the request as Gemini format. It then uses getRequestTranslator(source, target) from registry.ts to retrieve the appropriate conversion function based on the detected source and the intended target provider.
Can I add support for a new LLM provider without modifying core files?
Yes. The decoupled registry design allows you to add support by creating a new translator file, implementing the pure function signature (model, body, stream, credentials) => transformedBody, and calling register(FORMATS.NEW_PROVIDER, FORMATS.TARGET, requestFn, responseFn) in bootstrap.ts. The core routing logic in chatCore.ts does not require modification.
How does OmniRoute handle tool calling when translating between formats?
The system sanitizes tool names and converts tool-choice configurations during translation. For OpenAI → Gemini conversions, sanitizeGeminiToolName() rewrites tool names to comply with Vertex AI limitations, while convertOpenAIToolChoiceToGemini() maps OpenAI's tool choice objects to Gemini's function-calling interface. These helpers are located in open-sse/translator/request/openai-to-gemini/helpers.ts.
What happens to streaming responses during format translation?
Streaming responses undergo inverse transformation through registered response translators. When Gemini streams back a response intended for an OpenAI client, openaiToAntigravityResponse() (registered in open-sse/translator/response/openai-to-gemini.ts) reshapes the Gemini candidates envelope into OpenAI-style streaming chunks, ensuring the client receives data in the format it expects throughout the streaming session.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →