# How OmniRoute's Format Translator Converts Between OpenAI, Claude, and Gemini APIs

> Learn how OmniRoute's format translator seamlessly converts between OpenAI, Claude, and Gemini APIs. Normalize requests, handle tool-calls, and stream responses efficiently. Explore the registry-based pipeline.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-07-25

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-claude.ts) and [`open-sse/translator/request/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-gemini.ts), registered in [`open-sse/translator/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/response/openai-to-claude.ts) and [`open-sse/translator/response/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts) (lines 12-20).

### Using the HTTP Handler

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/formats.ts)
2. Create [`request/openai-to-myai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/request/openai-to-myai.ts) and [`response/openai-to-myai.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/response/openai-to-myai.ts) converters
3. Register in [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts): `register(FORMATS.OPENAI, FORMATS.MYAI, openaiToMyAIRequest, openaiToMyAIResponse)`

## Key Source Files

| Purpose | File Path |
|---------|-----------|
| Translator entry point | [`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts) |
| Format constants | [`open-sse/translator/formats.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/formats.ts) |
| OpenAI → Claude request | [`open-sse/translator/request/openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-claude.ts) |
| Claude → OpenAI response | [`open-sse/translator/response/openai-to-claude.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/response/openai-to-claude.ts) |
| OpenAI → Gemini request | [`open-sse/translator/request/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/request/openai-to-gemini.ts) |
| Gemini → OpenAI response | [`open-sse/translator/response/openai-to-gemini.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/response/openai-to-gemini.ts) |
| Registry implementation | [`open-sse/translator/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/registry.ts) |
| Tool-call helpers | [`open-sse/translator/helpers/toolCallShim.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.