How OmniRoute's Format Translator Converts Between OpenAI, Claude, and Gemini APIs
OmniRoute's format translator normalizes requests and responses between OpenAI, Claude, and Gemini APIs using a registry-based pipeline that maps schemas, handles tool-call conversions, and streams normalized chunks back to clients.
OmniRoute's format translator lives in the open-sse/translator package and serves as the central interoperability layer for the router. It transforms incoming OpenAI-compatible requests into provider-specific payloads for Anthropic Claude and Google Gemini, then converts the upstream responses back into a unified OpenAI format. This architecture enables the router to support over 290 providers while exposing a single, stable interface.
Three-Stage Translation Architecture
The translator operates through three distinct stages to maintain a consistent client interface while supporting provider-specific features.
-
Request Translation: The incoming OpenAI-style payload is converted to the target provider's schema before any HTTP call. This happens in
open-sse/translator/request/openai-to-claude.tsandopen-sse/translator/request/openai-to-gemini.ts, registered inopen-sse/translator/registry.ts. -
Execution: The transformed request passes to the appropriate executor (
open-sse/executors/*) which communicates with the upstream model. No translation occurs during this phase. -
Response Translation: The upstream response streams back and is remapped to OpenAI format. This logic resides in
open-sse/translator/response/openai-to-claude.tsandopen-sse/translator/response/openai-to-gemini.ts.
Request Translation: OpenAI to Claude
The conversion from OpenAI to Claude request format handles message restructuring and tool-call normalization.
In open-sse/translator/request/openai-to-claude.ts, the openaiToClaudeRequest(body, state) function (lines 1-30) parses the OpenAI JSON payload and rewrites fields to match Claude's messages schema. Claude uses content blocks for tool interactions rather than OpenAI's function objects.
Key transformations include:
- Mapping
messagesto Claude's expected format with appropriate role conversions - Converting OpenAI
toolsinto Claude's content blocks using theCLAUDE_OAUTH_TOOL_PREFIXconstant andREVERSE_MAPnormalization (lines 13-15) - Handling image attachments and streaming-compatible logic to preserve features like "thinking" blocks
Response Translation: Claude to OpenAI
The reverse conversion streams Claude's content blocks back into OpenAI-compatible SSE chunks.
The openaiToClaudeResponse function in open-sse/translator/response/openai-to-claude.ts processes Claude's content blocks (text, thinking, tool_use) and emits corresponding OpenAI events: message_start, content_block_start, content_block_delta, and message_stop.
Specific handling includes:
- XML-based tool calls: Extracts
<invoke>blocks viaextractXmlInvokeBlocks(lines 31-86) and emits them as proper tool calls at stream end - Partial tool-call arguments: Buffers JSON fragments using
appendToolCallArgumentDelta(lines 35-44) until the complete payload is received - Finish reason mapping: Converts Claude's
stop_reasonto OpenAI'sfinish_reasonviaconvertFinishReason(lines 58-78)
The registration in open-sse/translator/registry.ts wires this via register(FORMATS.OPENAI, FORMATS.CLAUDE, null, openaiToClaudeResponse) (lines 81-82).
Request Translation: OpenAI to Gemini
Google Gemini uses a different field structure (contents instead of messages, functionCalls instead of tools).
In open-sse/translator/request/openai-to-gemini.ts, the openaiToGeminiRequest(body, state) function (lines 1-35) maps the OpenAI payload to Gemini's generateContent schema. Key adaptations include:
- Field name remapping (
messages→contents) - Image attachment handling via
sizeMapper.ts - Normalizing OpenAI
toolsinto Gemini'sfunctionCallsarray (lines 45-70)
Response Translation: Gemini to OpenAI
Gemini returns candidates with content and usageMetadata that must map to OpenAI's choices format.
The openaiToGeminiResponse function in open-sse/translator/response/openai-to-gemini.ts (lines 10-70) transforms each Gemini chunk into OpenAI-compatible SSE events. It specifically:
- Preserves
finishReasonfrom Gemini's candidate structure - Injects token usage statistics by mapping
usageMetadatato OpenAI'susageobject (prompt/completion tokens) (lines 12-25)
How the Registry Connects Transformers
The open-sse/translator/registry.ts file maintains a mapping of source/target format pairs to their respective transformation functions.
Defined as const registry = new Map(), the registry uses register(source, target, requestFn, responseFn) (lines 5-15) to associate format pairs with their handlers. When the router in open-sse/handlers/chatCore.ts processes a request, it calls translateRequest(body, sourceFormat, targetFormat) to look up and execute the request transformer, then translateResponse(upstreamResponse, sourceFormat, targetFormat) after receiving the upstream response.
Practical Implementation Examples
Manually Invoking the Translator
import { translateRequest, translateResponse } from '@/open-sse/translator';
import { FORMATS } from '@/open-sse/translator/formats';
// OpenAI-style request
const openaiPayload = {
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Explain quantum entanglement.' }],
};
// Convert to Claude format
const claudeBody = translateRequest(
openaiPayload,
FORMATS.OPENAI,
FORMATS.CLAUDE,
);
// After receiving Claude response chunks:
const openaiChunk = translateResponse(
claudeChunk,
FORMATS.CLAUDE,
FORMATS.OPENAI,
);
The translateRequest and translateResponse utilities are exported from open-sse/translator/index.ts (lines 12-20).
Using the HTTP Handler
import { handleChat } from '@/open-sse/handlers/chatCore';
import { FORMATS } from '@/open-sse/translator/formats';
export async function POST(req) {
const body = await req.json();
// Router automatically invokes translator behind the scenes
return handleChat(body, {
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.CLAUDE,
});
}
Adding a New Provider
To extend OmniRoute with a new provider like "MyAI":
- Add
MYAItoFORMATSinopen-sse/translator/formats.ts - Create
request/openai-to-myai.tsandresponse/openai-to-myai.tsconverters - Register in
registry.ts:register(FORMATS.OPENAI, FORMATS.MYAI, openaiToMyAIRequest, openaiToMyAIResponse)
Key Source Files
| Purpose | File Path |
|---|---|
| Translator entry point | open-sse/translator/index.ts |
| Format constants | open-sse/translator/formats.ts |
| OpenAI → Claude request | open-sse/translator/request/openai-to-claude.ts |
| Claude → OpenAI response | open-sse/translator/response/openai-to-claude.ts |
| OpenAI → Gemini request | open-sse/translator/request/openai-to-gemini.ts |
| Gemini → OpenAI response | open-sse/translator/response/openai-to-gemini.ts |
| Registry implementation | open-sse/translator/registry.ts |
| Tool-call helpers | open-sse/translator/helpers/toolCallShim.ts |
Summary
- OmniRoute's format translator operates through three stages: request normalization, execution, and response remapping
- Request transformers in
open-sse/translator/request/convert OpenAI schemas to Claude and Gemini formats, handling tool calls and field renaming - Response transformers in
open-sse/translator/response/stream provider-specific chunks back into OpenAI-compatible SSE events - The registry pattern in
registry.tsdecouples format pairs from routing logic usingregister(FORMATS.OPENAI, FORMATS.CLAUDE, ...) - Tool-call normalization handles XML extraction for Claude via
extractXmlInvokeBlocksand functionCall mapping for Gemini through dedicated helper functions
Frequently Asked Questions
How does OmniRoute handle streaming tool calls from Claude?
OmniRoute buffers partial Claude tool-call arguments using appendToolCallArgumentDelta in open-sse/translator/response/openai-to-claude.ts (lines 35-44) until the full JSON payload is received. For XML-based tool invocations wrapped in <invoke> tags, the extractXmlInvokeBlocks function (lines 31-86) extracts these at stream end and emits them as standard OpenAI tool calls.
What field mappings occur when converting OpenAI requests to Gemini format?
The openaiToGeminiRequest function in open-sse/translator/request/openai-to-gemini.ts maps messages to contents, normalizes OpenAI tools into Gemini's functionCalls array (lines 45-70), and handles image sizing through sizeMapper.ts. It preserves the model identifier while restructuring the payload to match Gemini's generateContent schema.
Where is the format translator registry initialized?
The registry is defined in open-sse/translator/registry.ts as a Map with register(source, target, requestFn, responseFn) (lines 5-15). Transformers are registered via calls like register(FORMATS.OPENAI, FORMATS.CLAUDE, null, openaiToClaudeResponse) (lines 81-82), allowing the router in open-sse/handlers/chatCore.ts to dynamically look up converters using translateRequest() and translateResponse().
How does the translator handle token usage reporting for Gemini responses?
The openaiToGeminiResponse function in open-sse/translator/response/openai-to-gemini.ts extracts usageMetadata from Gemini's response chunks and maps it to OpenAI's usage object (lines 12-25), populating prompt_tokens and completion_tokens fields to maintain API consistency with OpenAI's billing and monitoring interfaces.
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 →