How OmniRoute Registers 237 AI Providers and Implements the Executor Pattern for Request Handling

OmniRoute registers 237 providers through a centralized proxy-based registry in src/shared/constants/providers.ts and handles requests using a Factory plus Strategy executor pattern where getExecutor() instantiates provider-specific classes extending BaseExecutor.

OmniRoute is an open-source AI gateway that unifies access to hundreds of model providers through a single interface. Understanding how the system registers its 237 providers and delegates requests through the executor pattern reveals the architecture that enables seamless multi-provider support. This deep dive examines the source code implementation in the diegosouzapw/OmniRoute repository.

Centralized Provider Registration

OmniRoute maintains a single source of truth for every AI provider through a unique lazy-loading registry architecture. Rather than loading all provider definitions at startup, the system uses a JavaScript Proxy to defer catalog access until first use.

The Proxy-Based Registry

The master file src/shared/constants/providers.ts exports AI_PROVIDERS as a Proxy object that wraps ten distinct catalog modules. When code accesses a provider identifier, the proxy's get handler iterates through catalogs to resolve the definition:

export const AI_PROVIDERS = new Proxy(
  {} as Record<string, AiProviderDefinition>,
  {
    get(_, id) {
      const catalogs = [
        NOAUTH_PROVIDERS,
        OAUTH_PROVIDERS,
        WEB_COOKIE_PROVIDERS,
        APIKEY_PROVIDERS,
        LOCAL_PROVIDERS,
        SEARCH_PROVIDERS,
        AUDIO_ONLY_PROVIDERS,
        UPSTREAM_PROXY_PROVIDERS,
        CLOUD_AGENT_PROVIDERS,
        SYSTEM_PROVIDERS,
      ];
      for (const cat of catalogs) if (id in cat) return cat[id as string];
      return undefined;
    },
  }
);

This design ensures that provider metadata exists in exactly one place while avoiding the memory overhead of loading 237 definitions simultaneously.

Catalog Organization by Authentication Type

The registry splits providers into semantic categories based on their authentication requirements and capabilities:

Each catalog exports a record mapping provider identifiers to rich AiProviderDefinition objects containing name, authentication method, icon, and capabilities.

The Executor Pattern Architecture

When requests reach the Next.js API layer at src/app/api/v1/chat/completions/route.ts, the system delegates execution through a Factory plus Strategy pattern that abstracts provider-specific HTTP logic behind a common interface.

Factory-Based Executor Selection

The factory function getExecutor in open-sse/executors/index.ts maps provider identifiers to concrete executor classes:

export function getExecutor(providerId: string): BaseExecutor {
  switch (providerId) {
    case "cursor":          return new CursorExecutor();
    case "codex":           return new CodexExecutor();
    case "antigravity":     return new AntigravityExecutor();
    // …specialized cases…
    default:                return new DefaultExecutor();
  }
}

The factory enables provider-specific optimizations while maintaining a consistent execution interface. Most of the 237 providers use the DefaultExecutor fallback, which implements generic OpenAI-compatible behavior.

The BaseExecutor Strategy

The abstract BaseExecutor class in open-sse/executors/base.ts defines the contract for all provider interactions. It encapsulates:

  • URL construction via buildUrl()
  • Header generation via buildHeaders()
  • Payload transformation via transformRequest()
  • Retry logic with exponential back-off
  • Abort signal handling and cleanup
  • HTTP execution via fetch() with typed responses

Provider-specific executors extend this base and override only the methods that differ from the standard OpenAI format. For example, CursorExecutor and VertexExecutor customize URL shapes and authentication headers while inheriting common retry and streaming logic.

Provider-Specific Implementations

Specialized executors live alongside default.ts in the open-sse/executors/ directory. These subclasses handle edge cases:

  • CursorExecutor – Custom authentication flows for Cursor IDE integration
  • CodexExecutor – Specific payload formats for OpenAI Codex
  • VertexExecutor – Google Cloud Platform URL construction and header signing

This Strategy pattern ensures that adding a new provider requires minimal code—often just a registry entry and occasionally a thin executor subclass.

Implementation Examples

Looking Up Provider Definitions

To access provider metadata anywhere in the application:

import { AI_PROVIDERS } from "@/shared/constants/providers";

const providerId = "openai";
const def = AI_PROVIDERS[providerId];

console.log(def?.name);        // → "OpenAI"
console.log(def?.authMethod); // → "apiKey"

The proxy lazily resolves the identifier against the appropriate catalog module.

Executing Provider Requests

The request handling flow demonstrates the factory pattern in action:

import { getExecutor } from "@/open-sse/executors";

async function callProvider(req) {
  const providerId = req.provider;           // e.g., "anthropic"
  const exec = getExecutor(providerId);      // Factory instantiation
  const resp = await exec.execute(req.body); // BaseExecutor logic
  return resp;
}

This abstraction allows the chat core handler to remain agnostic to provider-specific HTTP details.

Creating Custom Executors

For providers requiring non-standard endpoints, extend DefaultExecutor:

import { DefaultExecutor } from "./default";

export class VertexExecutor extends DefaultExecutor {
  protected buildUrl() {
    return `https://us-central1-aiplatform.googleapis.com/v1/${this.model}:predict`;
  }

  protected buildHeaders() {
    return {
      Authorization: `Bearer ${this.apiKey}`,
      "Content-Type": "application/json",
    };
  }
}

Register the executor in open-sse/executors/index.ts to integrate it into the routing logic.

Summary

  • Centralized Registry: OmniRoute consolidates 237 provider definitions in src/shared/constants/providers.ts using a lazy-loading Proxy that defers catalog initialization.
  • Modular Catalogs: Providers organize into authentication-based modules (OAuth, API key, local, etc.), ensuring single-responsibility for each catalog file.
  • Factory Pattern: The getExecutor() function in open-sse/executors/index.ts instantiates the correct executor class based on provider ID.
  • Strategy Implementation: The BaseExecutor abstract class defines common retry, streaming, and error-handling logic, while concrete subclasses implement provider-specific URL and header construction.
  • Extensibility: New providers require only registry entry in the appropriate catalog and optionally a custom executor class if not OpenAI-compatible.

Frequently Asked Questions

How does OmniRoute handle authentication differences between providers?

The registry categorizes providers by authentication method in separate catalog files (oauth.ts, apikey, web-cookie.ts, etc.). Each executor implementation then handles the specific authentication flow—whether Bearer tokens, cookie headers, or OAuth signatures—within its buildHeaders() method, keeping authentication logic encapsulated per provider.

What happens if a provider ID is not found in the registry?

When accessing AI_PROVIDERS[id] for an unknown identifier, the Proxy's get handler returns undefined after checking all ten catalogs. Downstream code should validate the existence of the definition before attempting to instantiate an executor, typically throwing a 404 or validation error if the provider is not supported.

How do I add a new provider to OmniRoute?

Adding a provider requires three steps: First, add the provider definition to the appropriate catalog module in src/shared/constants/providers/ (e.g., providers/apikey/frontier-labs.ts for API-key providers). Second, if the provider is not OpenAI-compatible, create a new executor class extending BaseExecutor or DefaultExecutor in open-sse/executors/. Third, register the provider ID in the getExecutor factory switch statement in open-sse/executors/index.ts.

Why use a Proxy instead of a static object for the provider registry?

The Proxy pattern enables lazy loading of provider catalogs. Rather than importing and initializing all 237 provider definitions at module load time—which would increase startup memory and parse time—the Proxy defers catalog access until the first time a specific provider is referenced. This architectural choice optimizes for applications that only utilize a subset of available providers while maintaining the developer experience of a simple object lookup (AI_PROVIDERS[id]).

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 →