How to Create Custom Routing Strategies in OmniRoute Beyond the 19 Built-In Options
You can create custom routing strategies in OmniRoute by implementing the RouterStrategy interface in open-sse/services/autoCombo/routerStrategy.ts, registering your class with registerStrategy(), and referencing the custom name in any combo definition—no changes to core request-handling code are required.
OmniRoute’s auto-combo engine delegates every provider-model decision to a pluggable RouterStrategy implementation. While the platform ships with 19 built-in strategies enumerated in src/shared/constants/routingStrategies.ts, the TypeScript class hierarchy lets you create custom routing strategies tailored to domain-specific metrics like latency, cost, or custom load-balancing without modifying the core engine.
How the OmniRoute Strategy Engine Works
The routing pipeline is implemented entirely inside open-sse/services/autoCombo/combo.ts. When a request arrives, resolveComboTargets() resolves a combo’s provider-model targets into an ordered array of ResolvedComboTarget objects. Next, handleComboChat() calls getStrategy(combo.strategy) from open-sse/services/autoCombo/routerStrategy.ts to obtain a RouterStrategy instance. The strategy’s pickTargets(targets, comboContext) method returns the ordered list the engine iterates, calling handleSingleModel() for each target until one succeeds or all are exhausted.
Because the engine only interacts with the strategy through the RouterStrategy interface, the internals of how targets are ordered remain fully encapsulated. As long as the class implements pickTargets and is registered in strategyRegistry, the combo engine will delegate to it automatically.
Implementing a Custom Routing Strategy
1. Implement the RouterStrategy Interface
Create a TypeScript class that satisfies the RouterStrategy contract. The interface defines a single method: pickTargets(targets: ResolvedComboTarget[], ctx: ComboContext): ResolvedComboTarget[]. The ComboContext object contains per-target latency, cost, health, and quota maps populated upstream by the combo engine.
// src/custom/router/MyLatencyAwareStrategy.ts
import type {
RouterStrategy,
ResolvedComboTarget,
ComboContext,
} from '@/open-sse/services/autoCombo/routerStrategy';
export class MyLatencyAwareStrategy implements RouterStrategy {
pickTargets(
targets: ResolvedComboTarget[],
ctx: ComboContext,
): ResolvedComboTarget[] {
return [...targets].sort((a, b) => {
const latA = ctx.latencyByTarget.get(a.id) ?? Infinity;
const latB = ctx.latencyByTarget.get(b.id) ?? Infinity;
if (latA !== latB) return latA - latB;
const costA = ctx.costByTarget.get(a.id) ?? Infinity;
const costB = ctx.costByTarget.get(b.id) ?? Infinity;
return costA - costB;
});
}
}
This example sorts targets by recorded latency ascending, then by cost ascending, using the runtime data already gathered by the engine.
2. Register Your Class with registerStrategy()
Add a registration call early in the server boot sequence, such as in src/server/init.ts or a dedicated plugin module. The global strategyRegistry (a Map<string, RouterStrategy>) lives alongside the interface in routerStrategy.ts.
// src/server/init.ts
import { registerStrategy } from '@/open-sse/services/autoCombo/routerStrategy';
import { MyLatencyAwareStrategy } from '@/custom/router/MyLatencyAwareStrategy';
registerStrategy('my-latency-aware', new MyLatencyAwareStrategy());
The registerStrategy function overwrites or adds a mapping in strategyRegistry. If the same name is registered twice, a console warning is emitted, as seen around lines 354–362 of routerStrategy.ts.
3. Configure a Combo to Reference the Custom Name
Create or edit a combo via the API, UI, or database. The Zod schema in src/shared/validation/schemas/combo.ts validates the strategy field. Because non-built-in names are accepted as custom strategies, you can reference your registered name directly.
{
"name": "my-fast-combo",
"strategy": "my-latency-aware",
"targets": [
{ "providerId": "openai", "modelId": "gpt-4o-mini" },
{ "providerId": "anthropic", "modelId": "claude-3-5-sonnet" },
{ "providerId": "gemini", "modelId": "gemini-1.5-flash" }
]
}
At runtime, when handleComboChat() calls getStrategy('my-latency-aware'), the engine resolves the custom implementation from strategyRegistry rather than the built-in set.
4. Verify Runtime Behavior
After deployment, confirm your strategy is active using the following steps:
- Run
omniroute combo listfrom the CLI and verify the combo shows strategymy-latency-aware. - Call
POST /api/v1/combo/createwith the payload above and note the returned combo ID. - Dispatch a chat request that uses the combo, for example via
POST /api/v1/chat/completionswith the headerx-omniroute-combo: my-fast-combo. - Enable debug-level server logs to observe the ordered target list chosen by your strategy after the
pickTargetscall.
Full Code Examples for Custom Routing Strategies
Latency-Aware Failover Strategy
The following file implements a strategy that prefers the target with the lowest recent latency and falls back to cost when latencies are equal.
// src/custom/router/MyLatencyAwareStrategy.ts
import type {
RouterStrategy,
ResolvedComboTarget,
ComboContext,
} from '@/open-sse/services/autoCombo/routerStrategy';
export class MyLatencyAwareStrategy implements RouterStrategy {
pickTargets(
targets: ResolvedComboTarget[],
ctx: ComboContext,
): ResolvedComboTarget[] {
return [...targets].sort((a, b) => {
const latA = ctx.latencyByTarget.get(a.id) ?? Infinity;
const latB = ctx.latencyByTarget.get(b.id) ?? Infinity;
if (latA !== latB) return latA - latB;
const costA = ctx.costByTarget.get(a.id) ?? Infinity;
const costB = ctx.costByTarget.get(b.id) ?? Infinity;
return costA - costB;
});
}
}
Round-Robin Load-Balancing Strategy
This example rotates the target list on every request to distribute load evenly across providers.
// src/custom/router/MyRoundRobinStrategy.ts
import type {
RouterStrategy,
ResolvedComboTarget,
ComboContext,
} from '@/open-sse/services/autoCombo/routerStrategy';
export class MyRoundRobinStrategy implements RouterStrategy {
private cursor = 0;
pickTargets(targets: ResolvedComboTarget[], _ctx: ComboContext) {
if (targets.length === 0) return [];
const start = this.cursor % targets.length;
this.cursor = (this.cursor + 1) % targets.length;
return [
...targets.slice(start),
...targets.slice(0, start),
];
}
}
Register it exactly like the latency-aware variant:
// src/server/init.ts
import { registerStrategy } from '@/open-sse/services/autoCombo/routerStrategy';
import { MyRoundRobinStrategy } from '@/custom/router/MyRoundRobinStrategy';
registerStrategy('my-rr', new MyRoundRobinStrategy());
Then reference "strategy": "my-rr" in any combo definition to dispatch requests using your round-robin logic.
Key Source Files for Custom Routing Strategies
Study the following files to understand the exact contract and extension points:
src/shared/constants/routingStrategies.ts– Canonical list of built-in identifiers inROUTING_STRATEGY_VALUES.open-sse/services/autoCombo/routerStrategy.ts– Definition ofRouterStrategy, built-in implementations,strategyRegistry, and thegetStrategy/registerStrategyhelpers.open-sse/services/autoCombo/combo.ts– Core combo engine containingresolveComboTargets,handleComboChat, and the retry loop over ordered targets.src/shared/validation/schemas/combo.ts– Zod schema that permits custom strategy names in combo payloads.docs/routing/AUTO-COMBO.md– User-facing documentation with additional strategy skeletons.
Summary
- OmniRoute routing is pluggable through a single-method
RouterStrategyinterface. - You create custom routing strategies by implementing
pickTargets()in a new class and registering it viaregisterStrategy()before the server accepts traffic. - The combo engine in
open-sse/services/autoCombo/combo.tsdelegates ordering to your implementation at runtime without core code changes. - Custom strategy names are accepted by the combo validation schema and resolved through
strategyRegistry, giving you full control over latency, cost, health, or custom load-balancing logic beyond the 19 built-in options.
Frequently Asked Questions
Do I need to modify OmniRoute core files to add a custom routing strategy?
No. The router is a pure TypeScript class hierarchy, so you implement the RouterStrategy interface in your own file and register it with the global strategyRegistry using registerStrategy(). The combo engine automatically picks up the custom implementation at runtime.
What runtime data is available inside the pickTargets method?
The pickTargets method receives the full array of ResolvedComboTarget objects plus a ComboContext parameter that contains per-target latency, cost, health, and quota maps populated upstream by the combo engine. You can inspect fields like ctx.latencyByTarget and ctx.costByTarget to build your ordering logic.
Can a custom strategy override one of the 19 built-in OmniRoute strategies?
Yes. Calling registerStrategy(name, impl) overwrites any existing mapping in strategyRegistry if the same name is supplied. The framework emits a console warning when this happens, as implemented around lines 354–362 of open-sse/services/autoCombo/routerStrategy.ts.
Where should I call registerStrategy() so it loads before requests arrive?
Call it early in the server boot sequence, such as in src/server/init.ts or a dedicated plugin file that runs at startup. The registration must complete before the first combo request reaches handleComboChat() in open-sse/services/autoCombo/combo.ts.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →