How OmniRoute Translates Between Different LLM Provider Formats: A Deep Dive into the Translator Architecture
OmniRoute uses a hub-and-spoke translator layer that normalizes all inbound requests to an OpenAI-style canonical schema and converts outbound responses back to the target provider's native format.
OmniRoute is an open-source LLM routing framework that abstracts away provider-specific API differences through a unified translation system. This article examines how the project handles format conversion between providers like OpenAI, Gemini, and Claude—enabling developers to route requests across multiple backends without rewriting client code.
The Core Translator Hub: translateRequest and translateResponse
All format conversion in OmniRoute flows through two central functions defined in open-sse/translator/index.ts. The translator hub acts as a single integration point that delegates to provider-specific modules based on source-to-target mappings.
Request Translation (Line 306)
The translateRequest() function initiates every outbound translation:
export function translateRequest(
sourceFormat: SourceFormat,
targetFormat: TargetFormat,
payload: unknown,
state: RequestState,
context: RequestContext
): Promise<TranslationResult>
This function performs three operations:
- Registry lookup – Queries
open-sse/translator/registry.tsto find the appropriate request translator for the source → target pair - Payload transformation – Rewrites messages, function-call blocks, tool results, and token limits into the target format
- Credential mapping – Returns
translationCredentialsfor the downstream executor
Response Translation (Line 794)
The translateResponse() function handles inbound translation after provider execution:
export function translateResponse(
targetFormat: TargetFormat,
sourceFormat: SourceFormat,
payload: unknown,
state: TranslationState
): unknown
This normalizes streaming chunks or complete responses back to the OpenAI-compatible format expected by clients and downstream OmniRoute features like tool-call handling and SSE processing.
The Translator Registry: Mapping Format Pairs
The registry system in open-sse/translator/registry.ts maintains the mapping between supported format combinations. It enables dynamic discovery of available translation pathways without hardcoding provider logic in the hub.
To inspect supported translations programmatically:
import { registry } from "@/translator/registry";
const supportedPairs = registry.getSupportedPairs();
// Returns: [["openai","gemini"], ["openai","claude"], ...]
The registry pattern keeps the core hub agnostic to specific providers—new translators can be added without modifying index.ts.
Provider-Specific Translator Modules
Individual translators live in dedicated files following a naming convention: {source}-to-{target}.ts for requests, {source}-to-{target}.ts for responses.
OpenAI to Gemini Request Translation
File: open-sse/translator/request/openai-to-gemini.ts
This module handles critical conversions including:
- Message role mapping – OpenAI's
system/user/assistant/toolroles to Gemini'suser/modelstructure - Function calling – Rewriting OpenAI function definitions to Gemini's
toolsdeclaration format - Token parameters – Mapping
max_tokensto Gemini'smaxOutputTokens - Multimodal content – Converting image URLs to Gemini's inline data format
Gemini to OpenAI Response Translation
File: open-sse/translator/response/gemini-to-openai.ts
This module normalizes:
- Streaming chunks – Gemini's
deltastructure to OpenAI'schoices[0].deltaformat - Finish reasons – Mapping
STOP,MAX_TOKENS,SAFETYto OpenAI equivalents - Tool call outputs – Converting Gemini's
functionCallblocks to OpenAI'stool_callsarray - Candidate handling – Flattening Gemini's multi-candidate responses to single-choice format
Format Definitions and Type Safety
The open-sse/translator/formats.ts file enumerates all supported format identifiers:
export type SourceFormat = "openai" | "gemini" | "claude" | "antigravity";
export type TargetFormat = "openai" | "gemini" | "claude" | "antigravity";
These type definitions ensure compile-time safety across the translation pipeline and provide a single source of truth for available integrations.
Shared Helper Utilities
The open-sse/translator/helpers/ directory contains reusable logic that keeps individual translators small:
| Helper | Purpose |
|---|---|
maxTokensHelper.ts |
Normalizes token budget calculations across providers with different limit semantics |
toolCallShim.ts |
Creates compatibility layers for function calling schemas that don't map 1:1 |
imageSizeMapper.ts |
Handles dimension constraints and encoding requirements for multimodal inputs |
schemaCoercion.ts |
Validates and coerces parameter types where provider schemas diverge |
End-to-End Translation Flow
Here's how the complete translation pipeline executes for a cross-provider request:
import { translateRequest } from "@/translator";
import { getExecutor } from "@/executors";
// Client sends OpenAI-format request
const openAiPayload = {
model: "gpt-4o-mini",
messages: [{ role: "user", content: "What's the weather in Paris?" }],
temperature: 0.7,
};
// Step 1: Translate to target provider format
const { model, result, stream, translationCredentials } = await translateRequest(
"openai", // source format
"gemini", // target format
openAiPayload,
{} as any, // request state
{} as any, // request context
);
// Step 2: Execute against provider
const executor = getExecutor("gemini");
const rawResponse = await executor.execute({
model,
body: result,
stream,
credentials: translationCredentials,
});
For streaming responses, translation happens per-chunk:
import { translateResponse } from "@/translator";
function onProviderChunk(chunk: unknown, state: TranslationState) {
// Step 3: Normalize back to OpenAI format
const openAiChunk = translateResponse(
"openai", // target format (what client expects)
"gemini", // source format (what provider sent)
chunk,
state, // tracks multi-chunk state like tool call accumulation
);
sendToClient(openAiChunk);
}
Why This Architecture Matters
The hub-and-spoke translator design provides three critical benefits:
- Schema uniformity – Downstream OmniRoute features (circuit breakers, combo routing, memory, skills) operate on a single well-known OpenAI-compatible schema regardless of upstream provider
- Extensibility – Adding a new provider requires only implementing two translator modules (request + response) and registering the format pair
- Testability – Each translator is a pure function that can be unit tested in isolation without mocking external APIs
Because translation happens at the network boundary, the rest of the codebase remains provider-agnostic—enabling features like automatic failover between Gemini and Claude without format-aware logic in the routing layer.
Summary
- OmniRoute's translator hub in
open-sse/translator/index.tsexposestranslateRequest()(line 306) andtranslateResponse()(line 794) as the primary API for format conversion - The registry system in
open-sse/translator/registry.tsmaps source-to-target format pairs to concrete translator implementations - Provider-specific translators like
openai-to-gemini.tsandgemini-to-openai.tshandle the actual payload transformation - Format definitions in
formats.tsprovide type safety and a canonical enumeration of supported integrations - Helper utilities in
open-sse/translator/helpers/extract reusable logic for token budgets, tool calls, and multimodal content - The architecture implements a hub-and-spoke pattern that guarantees downstream components see only OpenAI-compatible schemas
Frequently Asked Questions
What LLM providers does OmniRoute support for translation?
OmniRoute currently supports translation between OpenAI, Gemini, Claude, and Antigravity formats as defined in open-sse/translator/formats.ts. The registry pattern allows new providers to be added by implementing request and response translator modules without modifying the core hub.
How does OmniRoute handle streaming responses from non-OpenAI providers?
Streaming translation uses translateResponse() called per-chunk with a TranslationState object that persists across chunks. This state tracks partial tool calls, accumulated content, and finish reasons—enabling accurate reconstruction of OpenAI-compatible streaming deltas even when the source provider uses different framing or multi-candidate structures.
Can I use OmniRoute's translator layer independently of the routing features?
Yes. The translator modules are designed as pure functions with no dependencies on OmniRoute's circuit breakers, combo routing, or memory systems. You can import translateRequest and translateResponse directly from @/translator to convert payloads without executing through OmniRoute's executor pipeline.
Where are the actual HTTP calls to providers made?
HTTP execution happens in provider-specific executors under open-sse/executors/, not in the translator layer. Translators only handle payload transformation—the executor receives the translated body and handles authentication, transport, and retry logic. This separation keeps translation logic stateless and testable.
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 →