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.

  1. 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.ts and open-sse/translator/request/openai-to-gemini.ts, registered in open-sse/translator/registry.ts.

  2. Execution: The transformed request passes to the appropriate executor (open-sse/executors/*) which communicates with the upstream model. No translation occurs during this phase.

  3. Response Translation: The upstream response streams back and is remapped to OpenAI format. This logic resides in open-sse/translator/response/openai-to-claude.ts and open-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 messages to Claude's expected format with appropriate role conversions
  • Converting OpenAI tools into Claude's content blocks using the CLAUDE_OAUTH_TOOL_PREFIX constant and REVERSE_MAP normalization (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 via extractXmlInvokeBlocks (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_reason to OpenAI's finish_reason via convertFinishReason (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 (messagescontents)
  • Image attachment handling via sizeMapper.ts
  • Normalizing OpenAI tools into Gemini's functionCalls array (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 finishReason from Gemini's candidate structure
  • Injects token usage statistics by mapping usageMetadata to OpenAI's usage object (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":

  1. Add MYAI to FORMATS in open-sse/translator/formats.ts
  2. Create request/openai-to-myai.ts and response/openai-to-myai.ts converters
  3. 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.ts decouples format pairs from routing logic using register(FORMATS.OPENAI, FORMATS.CLAUDE, ...)
  • Tool-call normalization handles XML extraction for Claude via extractXmlInvokeBlocks and 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:

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 →