How OmniRoute's Translation Layer Normalizes OpenAI, Claude, and Gemini API Formats
OmniRoute's translation layer converts inbound OpenAI-compatible requests into provider-specific formats for Claude and Gemini, then transforms responses back into the OpenAI shape clients expect.
The OmniRoute proxy (available at diegosouzapw/OmniRoute) presents a unified OpenAI-compatible façade while internally routing to heterogeneous LLM providers. Because Anthropic's Claude and Google's Gemini expose fundamentally different request/response schemas, the codebase implements a bidirectional translation system that bridges these gaps without requiring client-side changes.
Where the Translation Layer Lives
All translation logic resides under open-sse/translator/ and is logically partitioned by direction and provider:
| Partition | Path | Responsibility |
|---|---|---|
| Request translators | request/ |
Transform OpenAI payloads into Claude/Gemini native formats |
| Response translators | response/ |
Convert provider responses back to OpenAI-compatible JSON |
| Registry | registry.ts |
Maps provider names to their translator pairs |
| Bootstrap | bootstrap.ts |
Initializes translators at application startup |
| Shared helpers | helpers/ |
Utilities for token limits, schema coercion, JSON parsing |
Request Translation: OpenAI to Native Formats
Request translators handle the inbound transformation from the standard OpenAI chat completions schema to each provider's native structure.
OpenAI to Claude Conversion
The request/openai-to-claude.ts module performs the heaviest lifting. Key transformations include:
- Model selection – Maps OpenAI model strings to Claude model identifiers
- Temperature handling – Strips
temperaturewhen Claude's extended thinking is active (lines 63-66) - Tool prefixing – Prepends
CLAUDE_OAUTH_TOOL_PREFIXto tool names to prevent namespace collisions - Thinking effort mapping – Converts OpenAI's
reasoning_effortto Claude'sthinkingpayload usingADAPTIVE_EFFORT_LEVELSnormalization - Message restructuring – Flattens OpenAI's simple message format into Claude's content blocks with explicit type annotations
- Tool result adjacency enforcement – Calls
enforceToolResultAdjacency()to ensure tool use and results appear consecutively
// From request/openai-to-claude.ts
const claudeBody = {
model: "claude-3-opus-2024-06-01",
max_tokens: adjustMaxTokens(openAiPayload),
messages: [
{
role: "user",
content: [{ type: "text", text: "Write a haiku about clouds." }]
},
],
tools: [
{
name: "proxy_save_note", // prefixed to avoid clashes
description: "Save a short note",
input_schema: { type: "object", properties: { note: { type: "string" } } },
},
],
};
OpenAI to Gemini Conversion
The request/openai-to-gemini.ts module handles Google's Chat API format:
- Function declarations – Transforms OpenAI
toolsinto Gemini'sfunction_declarationsarray - Safety settings – Injects default safety filters compatible with OpenAI's unrestricted behavior
- Generation config – Maps
temperature,top_p,max_tokens, andstopsequences to Gemini'sgenerationConfigobject
Response Translation: Native Formats to OpenAI
Response translators perform the outbound transformation, ensuring clients receive the expected choices, usage, and finish_reason structure regardless of the upstream provider.
Claude to OpenAI Response
The response/claude-to-openai.ts (and related streaming handler in response/openai-to-claude.ts) handles:
- Content normalization – Uses
normalizeContentToString()to flatten Claude's content blocks into simple strings - Tool call reconstruction – Rebuilds OpenAI-style
tool_callsarrays from Claude'stool_useblocks - Streaming chunk sanitization – Processes
sanitizeStreamingChunk()for SSE stream compatibility - Usage extraction – Derives
prompt_tokensandcompletion_tokensfrom Claude's usage metadata
// Produced by response transformers
const openAiResponse = {
id: "chatcmpl-123",
object: "chat.completion",
created: Date.now() / 1000,
model: "claude-3-opus-2024-06-01",
choices: [
{
index: 0,
message: { role: "assistant", content: "Clouds drift…", tool_calls: [] },
finish_reason: "stop",
},
],
usage: { prompt_tokens: 12, completion_tokens: 15, total_tokens: 27 },
};
Gemini to OpenAI Response
The response/gemini-to-openai.ts module addresses Gemini's distinct response shape:
- Candidate extraction – Selects from Gemini's
candidatesarray to populatechoices - Finish reason mapping – Translates Gemini's
finishReasonenum values to OpenAI equivalents - Safety rating filtering – Optionally exposes Gemini's safety metadata as custom fields
Chain Translation for Fusion Strategies
A specialized path exists at response/gemini-to-claude.ts, which enables Gemini → Claude → OpenAI chaining. This supports OmniRoute's "Fusion" strategies where responses from multiple providers are combined or compared.
Registry and Bootstrap Architecture
Translators are registered centrally rather than hard-coded throughout the routing stack:
// From translator/registry.ts
register(FORMATS.claude, openaiToClaudeRequest, openaiToClaudeResponse);
register(FORMATS.gemini, openaiToGeminiRequest, geminiToOpenaiResponse);
The getTranslator("claude") method returns both requestTransformer and responseTransformer functions, allowing the rest of the pipeline to operate agnostically.
Registration occurs during bootstrap in translator/bootstrap.ts, ensuring all translators are available before the first request arrives.
Shared Translation Helpers
Common utilities in helpers/ prevent code duplication:
maxTokensHelper.ts– Normalizes max token limits across providers with different constraintsschemaCoercion.ts– Handles type coercion for parameters that differ in expected typesjsonUtil.ts– ProvidessafeParseJSON()for resilience against malformed provider responses- Content utilities –
stripEmptyTextBlocks()removes artifacts from Claude's content block format
Streaming Translation Support
Both request and response paths support Server-Sent Events (SSE) streaming:
- Request translators set appropriate streaming flags per provider
- Response translators apply
sanitizeStreamingChunk()to each SSE chunk - Claude's split tool-call chunks are reassembled correctly in
openai-to-claude.ts
Why This Architecture Matters
The translation layer enables provider-agnostic features upstream:
| Feature | Benefit from Translation Layer |
|---|---|
| Intelligent routing | Router sees uniform OpenAI-shaped requests |
| Circuit breakers | Failure detection uses consistent response formats |
| Caching | Cache keys derived from normalized request hashes |
| Fusion strategies | Multiple provider outputs combined in OpenAI format |
Without this boundary, every component would need provider-specific branches.
Summary
- OmniRoute's translation layer lives in
open-sse/translator/and consists of bidirectional request/response transformers for Claude and Gemini - Request translators (
request/openai-to-claude.ts,request/openai-to-gemini.ts) convert OpenAI schema to native provider formats - Response translators (
response/claude-to-openai.ts,response/gemini-to-openai.ts) reverse the transformation for client compatibility - Registry pattern in
registry.tsenables lookup by provider name, initialized viabootstrap.ts - Shared helpers handle cross-cutting concerns like token limits, JSON safety, and content normalization
- Streaming support maintains real-time compatibility through chunk-by-chunk transformation
Frequently Asked Questions
How does OmniRoute handle OpenAI's tool format with Claude's different tool structure?
OmniRoute's openai-to-claude.ts request transformer remaps OpenAI function tools to Claude's tool definitions with input_schema (JSON Schema) instead of parameters. It prefixes tool names with CLAUDE_OAUTH_TOOL_PREFIX to prevent collisions, sanitizes tool IDs via sanitizeToolResultId(), and enforces that tool results appear adjacent to their corresponding tool uses through enforceToolResultAdjacency().
What happens to temperature and reasoning settings when translating between APIs?
The translation layer maps OpenAI's temperature and reasoning_effort to provider-specific equivalents. For Claude, temperature is stripped entirely when extended thinking mode is active (detected via the thinking payload). The ADAPTIVE_EFFORT_LEVELS set normalizes effort levels across the reasoning_effort → thinking conversion, ensuring consistent behavior for low/medium/high reasoning configurations.
Can OmniRoute chain translations for multi-provider strategies?
Yes. The response/gemini-to-claude.ts module implements a Gemini → Claude → OpenAI translation chain specifically for Fusion strategies. This allows OmniRoute to normalize responses from multiple providers into a single comparable format before presenting the final OpenAI-compatible result to clients.
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 →