How to Add a Custom AI Provider to OmniRoute: A Step-by-Step Guide for the Registry-Based Architecture

You add a custom AI provider to OmniRoute by creating a provider module with a RegistryEntry, registering it in the providers index, and optionally implementing custom executors or translators for non-standard APIs.

OmniRoute routes requests to AI back-ends through a centralized Provider Registry that serves as the single source of truth for all provider configuration. This article walks through the exact implementation steps based on the OmniRoute source code in diegosouzapw/OmniRoute, covering the registry system in open-sse/config/providerRegistry.ts and the module structure used throughout the codebase.

Understanding the Provider Registry Architecture

The registry system in OmniRoute is built around a lazy-loaded REGISTRY object that maps provider IDs to their full configuration. According to the source code, this registry is imported and re-exported from open-sse/config/providers/index.ts:

export { REGISTRY } from "./providers/index.ts";
import { REGISTRY } from "./providers/index.ts";

(source: providerRegistry.ts line 11-13)

The registry powers helper functions such as getRegistryEntry(), getProviderCategory(), and generateModels() that the routing, combo-logic, and resilience layers consume. When you add a custom AI provider, you integrate directly into this system so your back-end participates fully in OmniRoute's request pipeline.

Step 1: Create the Provider Module with RegistryEntry

Every provider in OmniRoute is defined by a RegistryEntry object that specifies its identity, authentication, endpoint configuration, and available models. Create a new directory under open-sse/config/providers/ with your provider's identifier.

Minimal provider definition (open-sse/config/providers/mycustom/provider.ts):

import { RegistryEntry } from "../../../config/providerRegistry.ts";

export const myCustomProvider: RegistryEntry = {
  id: "mycustom",
  format: "openai",
  baseUrl: "https://api.mycustom.ai/v1",
  authType: "apikey",                     // or "oauth"
  models: [
    { id: "mycustom/gpt-4", name: "GPT-4", maxTokens: 8192 },
    { id: "mycustom/claude", name: "Claude", maxTokens: 10000 },
  ],
  // Optional OAuth block (remove if authType is "apikey")
  // oauth: {
  //   clientIdEnv: "MYCUSTOM_CLIENT_ID",
  //   clientSecretEnv: "MYCUSTOM_CLIENT_SECRET",
  //   tokenUrl: "https://auth.mycustom.ai/token",
  // },
};

The RegistryEntry shape includes:

  • id: Unique provider identifier used in routing
  • format: Expected request/response format ("openai" or custom)
  • baseUrl: Root endpoint for the provider's API
  • authType: Either "apikey" or "oauth"
  • models: Array of model definitions with IDs, display names, and token limits
  • oauth: Optional credential configuration for OAuth flows

Step 2: Register the Module in the Providers Index

Add your new module to open-sse/config/providers/index.ts to include it in the aggregated REGISTRY object:

export * from "./mycustom/provider.ts";

This single export statement makes your provider discoverable throughout OmniRoute. The index file builds the runtime maps that power getRegistryEntry() and related utilities.

Step 3: Optionally Implement a Custom Executor

Most providers work with OmniRoute's generic HttpExecutor. Only implement a custom executor if your provider requires non-standard request handling such as multipart uploads, custom streaming protocols, or special header injection.

Custom executor skeleton (open-sse/executors/mycustomExecutor.ts):

import { BaseExecutor } from "./BaseExecutor.ts";

export class MyCustomExecutor extends BaseExecutor {
  async execute(request: any): Promise<any> {
    // custom fetch logic, headers, streaming, etc.
    return super.execute(request);
  }
}

Extend BaseExecutor and override execute() to inject provider-specific behavior while maintaining compatibility with OmniRoute's request lifecycle.

Step 4: Optionally Implement a Translator

If your provider's request or response schema differs from the OpenAI-compatible format, add a translator under open-sse/translator/. Translators convert between the provider's native format and OmniRoute's internal ChatRequest / ChatResponse shapes.

Translator skeleton (open-sse/translator/mycustomTranslator.ts):

import { Translator } from "./BaseTranslator.ts";

export class MyCustomTranslator extends Translator {
  toProvider(request) { /* map OmniRoute request → provider format */ }
  fromProvider(response) { /* map provider response → OmniRoute format */ }
}

Step 5: Configure OAuth Credentials via Environment Variables

For OAuth-based providers, the registry automatically resolves credentials from environment variables. In providerRegistry.ts lines 63-71, the OAuth resolution logic reads from the oauth section of your RegistryEntry:

if (entry.oauth) {
  if (entry.oauth.clientIdEnv) {
    p.clientId = process.env[entry.oauth.clientIdEnv] || entry.oauth.clientIdDefault;
  }
  if (entry.oauth.clientSecretEnv) {
    p.clientSecret =
      process.env[entry.oauth.clientSecretEnv] || entry.oauth.clientSecretDefault;
  }
  // …
}

(source: providerRegistry.ts line 63-71)

Define clientIdEnv and clientSecretEnv with the environment variable names you want operators to use, and optionally provide clientIdDefault / clientSecretDefault fallback values.

Step 6: Verify Registry Helper Functions

Once registered, your provider automatically populates the helper functions in providerRegistry.ts. The getRegistryEntry() function resolves providers by ID or alias:

export function getRegistryEntry(provider: string): RegistryEntry | null {
  ensureByAliasPopulated();
  return REGISTRY[provider] || _byAlias.get(provider) || null;
}

(source: providerRegistry.ts line 77-81)

Similarly, getProviderCategory() classifies your provider for routing decisions:

export function getProviderCategory(provider: string): "oauth" | "apikey" {
  const entry = getRegistryEntry(provider);
  if (!entry) return "apikey";
  return entry.authType === "apikey" ? "apikey" : "oauth";
}

(source: providerRegistry.ts line 71-76)

These functions ensure your custom AI provider integrates with OmniRoute's combo routing, circuit-breaker resilience, and cooldown logic without additional configuration.

Step 7: Add Unit Tests

OmniRoute requires test coverage for all production code changes. Create tests in tests/unit/ that verify registration, model discovery, and any custom logic:

import { getRegistryEntry } from "@/open-sse/config/providerRegistry";

test("mycustom provider is registered", () => {
  const entry = getRegistryEntry("mycustom");
  expect(entry).not.toBeNull();
  expect(entry?.models?.length).toBeGreaterThan(0);
});

Cover both success paths and edge cases such as missing OAuth credentials or invalid model IDs.

Step 8: Update Documentation

Add your provider to the generated provider reference at docs/reference/PROVIDER_REFERENCE.md using the standard link format. This makes your back-end discoverable to operators and automated tooling.

Key Files Reference

File Purpose
open-sse/config/providerRegistry.ts Core registry logic, lazy-loaded helper maps, OAuth env resolution
open-sse/config/providers/index.ts Aggregates all provider modules into the REGISTRY object
open-sse/config/providers/<your-provider>/provider.ts Your custom RegistryEntry definition
open-sse/executors/<your-provider>Executor.ts Optional: custom executor for non-standard request handling
open-sse/translator/<your-provider>Translator.ts Optional: translator for non-OpenAI schema
tests/unit/<your-provider>-provider.test.ts Required: unit tests for registration and functionality

Summary

  • The Provider Registry in open-sse/config/providerRegistry.ts is the single source of truth for all AI back-ends in OmniRoute
  • Create a RegistryEntry in open-sse/config/providers/<id>/provider.ts with your provider's ID, URL, auth type, and models
  • Register the module by exporting it from open-sse/config/providers/index.ts
  • Implement custom executors only for non-standard HTTP handling, and custom translators only for non-OpenAI request/response formats
  • OAuth credentials resolve automatically from environment variables defined in the oauth block
  • Helper functions like getRegistryEntry() and getProviderCategory() expose your provider to routing and resilience layers
  • Unit tests and documentation updates are required for production acceptance

Frequently Asked Questions

What is the minimum code required to add a custom AI provider to OmniRoute?

The minimum implementation requires two files: a provider module with a RegistryEntry export, and an index export. Create open-sse/config/providers/mycustom/provider.ts defining your RegistryEntry, then add export * from "./mycustom/provider.ts" to open-sse/config/providers/index.ts. This registers your provider for routing through the standard HTTP executor with OpenAI-compatible format.

When do I need a custom executor versus using the default HttpExecutor?

You need a custom executor only when your provider requires non-standard request handling that the generic HttpExecutor cannot accommodate. This includes multipart upload protocols, custom streaming implementations, special header injection, or request signing requirements. Most providers work with the default executor and require only a RegistryEntry configuration.

How does OmniRoute handle authentication for custom providers?

OmniRoute supports two authentication types defined in the authType field: "apikey" for API key authentication passed in headers, and "oauth" for OAuth 2.0 flows. For OAuth providers, the oauth block in your RegistryEntry specifies environment variable names (clientIdEnv, clientSecretEnv) that the registry resolves at runtime according to the logic in providerRegistry.ts lines 63-71.

Can I add a provider that uses a completely non-OpenAI API format?

Yes. Implement a custom translator in open-sse/translator/ that extends the base Translator class. Your translator must implement toProvider() to map OmniRoute's internal ChatRequest to your provider's native format, and fromProvider() to map the response back to OmniRoute's ChatResponse shape. Register the translator in your provider configuration so the routing layer applies it automatically.

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 →