# How to Add a New AI Provider with a Custom Executor in OmniRoute

> Learn to add a new AI provider with a custom executor in OmniRoute. Register your provider, implement an executor, and export it via the factory for seamless integration.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-26

---

**Adding a new AI provider to OmniRoute requires registering the provider in the constants registry, implementing a custom executor class that extends the base abstraction, and exporting it through the executor factory—optionally adding a request translator if the provider deviates from the OpenAI request schema.**

OmniRoute is an open-source AI gateway that normalizes requests across multiple LLM providers through a modular executor architecture. To add a new AI provider with a custom executor in OmniRoute, you will wire the provider into four specific layers: the provider registry ([`src/shared/constants/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/providers.ts)), the execution engine (`open-sse/executors/`), the translation layer (`open-sse/translator/`), and optionally the OAuth configuration (`src/lib/oauth/`).

## Step 1 – Register the Provider in the Constants File

Every provider must be declared in **[`src/shared/constants/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/providers.ts)** using Zod validation. This central registry makes the provider discoverable by the combo routing engine, the provider catalog UI, and the type-safe executor factory.

Add your provider ID to the Zod enum and define its metadata in the `providerMeta` object:

```typescript
export const PROVIDERS = z.enum([
  // …existing providers…
  "myprovider",                       // <-- new entry
]);

export type ProviderId = z.infer<typeof PROVIDERS>;

export const providerMeta = {
  myprovider: {
    displayName: "MyProvider",
    authType: "apiKey",               // or "oauth"
    baseUrl: "https://api.myprovider.com",
    // optional: default model list, capabilities, etc.
  },
};

```

This registration step is mandatory—without it, the factory cannot resolve the executor and the UI will not display the provider.

## Step 2 – Implement the Custom Executor

If your provider follows the standard OpenAI-compatible request format, you can reuse the default executor. For providers requiring non-standard headers, URL structures, or payloads, create a new executor class in `open-sse/executors/` that extends **`BaseExecutor`** from [`open-sse/executors/base.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/executors/base.ts).

Override these three abstract methods to customize the HTTP lifecycle:

- **`buildUrl(request)`** – Returns the fully qualified endpoint (e.g., `https://api.myprovider.com/v1/chat/completions`).
- **`buildHeaders(request)`** – Returns the authentication and content-type headers (e.g., `Authorization: Bearer <TOKEN>`).
- **`transformRequest(body)`** – Converts the normalized OmniRoute request body into the provider’s proprietary wire format.

Example implementation for a hypothetical provider:

```typescript
import { BaseExecutor } from "./base";
import type { ExecutorRequest } from "./types";

export class MyProviderExecutor extends BaseExecutor {
  protected buildUrl(_: ExecutorRequest): string {
    return "https://api.myprovider.com/v1/chat/completions";
  }

  protected buildHeaders(request: ExecutorRequest): Record<string, string> {
    return {
      Authorization: `Bearer ${request.apiKey}`,
      "Content-Type": "application/json",
    };
  }

  protected transformRequest(body: any): any {
    // MyProvider expects a field called `prompt` instead of `messages`
    return {
      model: body.model,
      prompt: body.messages.map((m: any) => m.content).join("\n"),
      max_tokens: body.max_tokens,
    };
  }
}

```

## Step 3 – Register the Executor in the Factory

After implementing the class, export it from **[`open-sse/executors/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/executors/index.ts)** so the factory can instantiate it when the router requests your provider ID.

Update the `getExecutor` function to return your new class:

```typescript
import { MyProviderExecutor } from "./myprovider";

export function getExecutor(providerId: string): BaseExecutor {
  switch (providerId) {
    case "myprovider":
      return new MyProviderExecutor();
    // …existing cases…
    default:
      return new DefaultExecutor(); // fallback
  }
}

```

This factory pattern ensures that OmniRoute’s routing logic remains decoupled from provider-specific implementation details.

## Step 4 – Add a Request Translator (If Needed)

Most OmniRoute providers consume the OpenAI request schema, but some (e.g., Anthropic, Gemini) require field translation. If your provider uses a divergent JSON shape, add a translator module under **`open-sse/translator/`** implementing `translateRequest` and `translateResponse`.

Wire the translator into **[`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts)** so the dispatcher invokes it automatically:

```typescript
import { translateMyProviderRequest } from "./myprovider";

export function translateRequest(
  body: any,
  sourceFormat: string,
  targetFormat: string,
) {
  if (targetFormat === "myprovider") {
    return translateMyProviderRequest(body, targetFormat);
  }
  // existing translators …
}

```

This layer sits between the router and the executor, normalizing payloads without polluting the executor’s HTTP logic.

## Step 5 – Configure OAuth (Optional)

For providers that use OAuth rather than static API keys, add the client credentials to **[`src/lib/oauth/constants/oauth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/constants/oauth.ts)**. This file defines client IDs, secrets, and authorization URLs for OAuth-based flows.

Ensure the OAuth flow logic in `src/lib/oauth/` is updated to handle your provider’s token exchange endpoints. Skip this step if your provider uses `authType: "apiKey"` as defined in Step 1.

## Testing Your Implementation

Once the provider is registered, the executor is implemented, and the factory is wired, run the validation suite to verify integration:

```bash
npm run check

```

This command verifies that:
- The new provider ID passes Zod validation in the provider catalog.
- The executor factory correctly resolves the class for your provider ID.
- The combo routing engine can route requests to the new executor without type errors.

## Summary

- **Register** the provider in [`src/shared/constants/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/providers.ts) using Zod enums and metadata objects.
- **Extend** `BaseExecutor` in `open-sse/executors/` to override `buildUrl`, `buildHeaders`, and `transformRequest` for custom HTTP logic.
- **Export** the executor from [`open-sse/executors/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/executors/index.ts) via the factory function `getExecutor`.
- **Translate** non-standard request schemas by adding modules under `open-sse/translator/` and wiring them into the dispatcher.
- **Configure** OAuth settings in [`src/lib/oauth/constants/oauth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/constants/oauth.ts) only if the provider does not use API keys.

## Frequently Asked Questions

### Do I need to create a custom executor for every new provider?

No. If the provider follows the OpenAI-compatible request shape (same JSON schema, authentication header format, and endpoint structure), you can use the default executor located at [`open-sse/executors/default.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/executors/default.ts). Only implement a custom executor when the provider requires non-standard headers, URL patterns, or request body transformations.

### How do I handle authentication for OAuth-based providers?

Set `authType: "oauth"` in the provider metadata within [`src/shared/constants/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/providers.ts), then add the client ID and secret definitions to [`src/lib/oauth/constants/oauth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/constants/oauth.ts). The OmniRoute OAuth flow will handle token exchange and refresh automatically; your executor will receive the resolved bearer token via the `request` object passed to `buildHeaders`.

### What if my provider returns a non-standard response format?

Implement a response translator in `open-sse/translator/` that normalizes the provider’s output to the OpenAI-compatible response schema. Import this translator into [`open-sse/translator/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/index.ts) and call it within the `translateResponse` function when the `targetFormat` matches your provider ID. This keeps the executor focused on HTTP transport while the translator handles schema mapping.

### Can I test the executor without deploying the full application?

Yes. The executor class can be unit tested in isolation by instantiating it directly and mocking the `ExecutorRequest` object. Verify that `buildUrl` returns the correct endpoint, `buildHeaders` injects the API key, and `transformRequest` produces the expected wire format. Run `npm run check` to ensure the factory registration and type safety constraints are satisfied before deploying to the routing layer.