# How the OpenClaude OpenAI Shim Layer Normalizes API Calls for Multi-Provider Support

> Discover how the OpenClaude OpenAI shim layer normalizes API calls via request planning, message conversion, and schema normalization for seamless multi-provider support. Ensure compatibility and simplify integrations.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: how-to-guide
- Published: 2026-09-08

---

**The OpenClaude OpenAI shim layer normalizes API calls through a three-stage pipeline—request planning, message conversion, and schema normalization—that transforms Anthropic-style SDK inputs into provider-compliant OpenAI payloads while handling transport selection, role mapping, and strict JSON schema enforcement.**

The OpenClaude project utilizes an **OpenAI shim layer** to bridge its internal Anthropic-style SDK architecture with diverse OpenAI-compatible backends including OpenAI, Azure OpenAI, Ollama, Groq, and DeepSeek. This normalization system ensures that regardless of the original Anthropic input structure, every provider receives a correctly formatted request payload that adheres strictly to the OpenAI API contract.

## The Three-Stage Normalization Pipeline

The shim implements normalization through three tightly-coupled stages that progressively transform the request from Anthropic format to provider-specific OpenAI compatibility.

### 1. Request Planning with `prepareOpenAIRequest`

The `prepareOpenAIRequest` function in [`src/services/api/openaiShim/requestPreparation.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/requestPreparation.ts) serves as the primary orchestrator for building the request body. This function determines the appropriate transport mechanism through the `effectiveTransport` calculation at lines 50-57, selecting between chat-completions, responses, or Gemini protocols based on the target endpoint path. It injects default parameters such as `stream` and `temperature` while stripping incompatible fields—such as removing the `store` parameter for Gemini—using the `shimConfig.removeBodyFields` loop around line 80.

### 2. Message and Tool Conversion

The conversion stage transforms Anthropic-specific data structures into OpenAI-compatible formats through two specialized functions. `convertMessages` in [`src/services/api/openaiShim/messageConversion.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/messageConversion.ts) maps Anthropic roles (`assistant`, `user`, `tool`) to OpenAI equivalents and handles image block processing. Simultaneously, `convertTools` in [`src/services/api/openaiShim.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim.ts) (lines 42-48) delegates to [`src/services/api/openaiShim/toolConversion.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/toolConversion.ts) to transform Anthropic tool descriptors into OpenAI-compatible `function` objects.

### 3. Schema Normalization and Strict Mode

The final stage ensures JSON schema compliance through `normalizeSchemaForOpenAI` in [`src/services/api/openaiShim/toolConversion.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/toolConversion.ts) (lines 12-20). This function recursively sanitizes schemas to guarantee that every property defined in `properties` is explicitly listed in the `required` array, preventing the "`required` must be a superset of `properties`" 400 errors common on OpenAI-compatible back-ends. When strict mode is enabled—the default unless the provider is Gemini or the user sets `OPENCLAUDE_DISABLE_STRICT_TOOLS`—the function also enforces `additionalProperties: false` to prevent unexpected property injection.

## Transport Selection and Provider-Specific Optimization

The shim dynamically adapts requests based on provider capabilities through intelligent transport selection and field filtering. The `effectiveTransport` logic identifies whether the target endpoint uses standard chat-completions, the newer `/responses` format, or Gemini-specific paths, determining which request body fields are preserved or removed.

For providers that implement implicit prefix caching—such as OpenAI and DeepSeek—the shim invokes `providerUsesImplicitPrefixCaching` (lines 92-107 in [`requestPreparation.ts`](https://github.com/Gitlawb/openclaude/blob/main/requestPreparation.ts)) to disable tool-history compression. This preserves cache efficiency by avoiding modifications to request prefixes that would otherwise invalidate cached context windows.

## Converting Anthropic Messages to OpenAI Format

The `convertMessages` function exposed via `__test.convertMessages` in [`src/services/api/openaiShim.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim.ts) (lines 38-44) constructs an array of `OpenAIMessage` objects defined at lines 90-102. This conversion handles complex content types including image blocks and reasoning content preservation, ensuring that multimodal Anthropic inputs translate correctly to OpenAI's message schema without data loss or role misalignment.

## Normalizing Tool Schemas for OpenAI Compatibility

Tool normalization involves filtering specific tools—such as ignoring `ToolSearchTool`—and passing remaining tool schemas through `sanitizeSchemaForOpenAICompat`. The `convertTools` implementation in [`src/services/api/openaiShim/toolConversion.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/toolConversion.ts) (lines 59-71) applies strict schema enforcement when enabled, ensuring that all function parameters are properly constrained and required fields are explicitly declared. This prevents runtime validation errors on strict OpenAI-compatible endpoints while maintaining flexibility for providers like Gemini that require relaxed schemas.

## Usage Example: Creating a Normalized Request

The following implementation demonstrates how OpenClaude uses the shim client to normalize Anthropic-style requests for OpenAI-compatible providers:

```typescript
// 1️⃣ Create a shim client (the entry point used throughout OpenClaude)
import { createOpenAIShimClient } from './services/api/openaiShim.js';
const client = createOpenAIShimClient({ defaultHeaders: { 'x-api-key': process.env.OPENAI_API_KEY } });

// 2️⃣ Build an Anthropic‑style request
const anthropicPayload = {
  model: 'claude-2',
  messages: [
    { role: 'user', content: 'Explain recursion.' },
    { role: 'assistant', content: 'Sure!', tool_calls: [] },
  ],
  tools: [
    { name: 'Calculator', description: 'Simple math', input_schema: { type: 'object', properties: { a: {type: 'number'}, b: {type: 'number'} } } },
  ],
  stream: true,
};

// 3️⃣ Let the shim normalize everything and send the request
client.messages.create(anthropicPayload).then(response => {
  console.log('Anthropic‑compatible streamed response:', response);
});

```

Under the hood, `client.messages.create` invokes `createShimRequest` → `_doRequest` → `prepareOpenAIRequest`, which coordinates `convertMessages` and `convertTools` to produce a normalized payload. The resulting body is dispatched via `executeOpenAIRequest`, ensuring provider-ready formatting regardless of the original Anthropic SDK structure.

## Summary

- The OpenClaude OpenAI shim layer implements **three-stage normalization**: request planning, message/tool conversion, and schema sanitization.
- `prepareOpenAIRequest` in [`src/services/api/openaiShim/requestPreparation.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/requestPreparation.ts) handles **transport selection** and provider-specific field filtering through `effectiveTransport` calculations and `providerUsesImplicitPrefixCaching` logic.
- **Message conversion** via `convertMessages` maps Anthropic roles and content types to OpenAI equivalents, while **tool conversion** via `convertTools` handles schema transformation.
- `normalizeSchemaForOpenAI` ensures JSON schema compliance by auto-populating the `required` array with all `properties` keys and enforcing `additionalProperties: false` in strict mode.
- The shim automatically removes incompatible fields—such as `store` for Gemini—while preserving cache-efficient request structures for providers like OpenAI and DeepSeek.

## Frequently Asked Questions

### How does the shim determine which transport protocol to use?

The shim calculates the `effectiveTransport` in [`src/services/api/openaiShim/requestPreparation.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/requestPreparation.ts) (lines 50-57) by analyzing the target endpoint path. Endpoints containing `/messages` trigger chat-completions transport, `/responses` triggers the responses API format, and paths containing `models/gemini` activate Gemini-specific handling. This determination controls which request body fields are preserved or stripped before transmission.

### Why does the shim modify JSON schemas to add required fields?

OpenAI-compatible backends reject schemas where the `required` array does not include all keys defined in `properties`. The `normalizeSchemaForOpenAI` function in [`src/services/api/openaiShim/toolConversion.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/openaiShim/toolConversion.ts) (lines 12-20) recursively traverses schemas to ensure every property is listed in `required`, preventing 400 validation errors while maintaining the intended structural constraints of the original Anthropic tool definitions.

### How does the shim handle provider-specific restrictions like Gemini's limitations?

After building the normalized request body, the shim executes a cleanup loop using `shimConfig.removeBodyFields` around line 80 of [`requestPreparation.ts`](https://github.com/Gitlawb/openclaude/blob/main/requestPreparation.ts) to strip incompatible parameters. For Gemini specifically, it removes the `store` field and disables strict schema mode (which would enforce `additionalProperties: false`) since Gemini does not support these OpenAI-specific extensions. This ensures maximum compatibility across diverse backend implementations.