Add all OpenRouter provider routing fields to OpenRouterRouting (#2904)

The OpenRouterRouting interface only had only and order, but the runtime
passes the entire object as-is to the OpenRouter API's provider field.
This means all other fields worked at runtime but had no TypeScript
type-checking, no autocomplete, and no config validation.

This adds all fields from the OpenRouter provider selection API
(https://openrouter.ai/docs/guides/routing/provider-selection):
allow_fallbacks, require_parameters, data_collection, zdr,
enforce_distillable_text, ignore, quantizations, sort, max_price,
preferred_min_throughput, preferred_max_latency.

Also fixes the models.md documentation which had an invalid fallbacks
field in the example (not part of the OpenRouter API), replaces it with
a comprehensive example showing all available fields, and clarifies that
openRouterRouting is sent as-is in the provider field.
This commit is contained in:
Zeremonienmeister Berber Aab
2026-04-09 03:08:45 +02:00
committed by GitHub
parent ee2483cd34
commit f05f4e8a5f
3 changed files with 129 additions and 8 deletions

View File

@@ -291,13 +291,76 @@ export interface OpenAIResponsesCompat {
/**
* OpenRouter provider routing preferences.
* Controls which upstream providers OpenRouter routes requests to.
* @see https://openrouter.ai/docs/provider-routing
* Sent as the `provider` field in the OpenRouter API request body.
* @see https://openrouter.ai/docs/guides/routing/provider-selection
*/
export interface OpenRouterRouting {
/** List of provider slugs to exclusively use for this request (e.g., ["amazon-bedrock", "anthropic"]). */
only?: string[];
/** List of provider slugs to try in order (e.g., ["anthropic", "openai"]). */
/** Whether to allow backup providers to serve requests. Default: true. */
allow_fallbacks?: boolean;
/** Whether to filter providers to only those that support all parameters in the request. Default: false. */
require_parameters?: boolean;
/** Data collection setting. "allow" (default): allow providers that may store/train on data. "deny": only use providers that don't collect user data. */
data_collection?: "deny" | "allow";
/** Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. */
zdr?: boolean;
/** Whether to restrict routing to only models that allow text distillation. */
enforce_distillable_text?: boolean;
/** An ordered list of provider names/slugs to try in sequence, falling back to the next if unavailable. */
order?: string[];
/** List of provider names/slugs to exclusively allow for this request. */
only?: string[];
/** List of provider names/slugs to skip for this request. */
ignore?: string[];
/** A list of quantization levels to filter providers by (e.g., ["fp16", "bf16", "fp8", "fp6", "int8", "int4", "fp4", "fp32"]). */
quantizations?: string[];
/** Sorting strategy. Can be a string (e.g., "price", "throughput", "latency") or an object with `by` and `partition`. */
sort?:
| string
| {
/** The sorting metric: "price", "throughput", "latency". */
by?: string;
/** Partitioning strategy: "model" (default) or "none". */
partition?: string | null;
};
/** Maximum price per million tokens (USD). */
max_price?: {
/** Price per million prompt tokens. */
prompt?: number | string;
/** Price per million completion tokens. */
completion?: number | string;
/** Price per image. */
image?: number | string;
/** Price per audio unit. */
audio?: number | string;
/** Price per request. */
request?: number | string;
};
/** Preferred minimum throughput (tokens/second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. */
preferred_min_throughput?:
| number
| {
/** Minimum tokens/second at the 50th percentile. */
p50?: number;
/** Minimum tokens/second at the 75th percentile. */
p75?: number;
/** Minimum tokens/second at the 90th percentile. */
p90?: number;
/** Minimum tokens/second at the 99th percentile. */
p99?: number;
};
/** Preferred maximum latency (seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. */
preferred_max_latency?:
| number
| {
/** Maximum latency in seconds at the 50th percentile. */
p50?: number;
/** Maximum latency in seconds at the 75th percentile. */
p75?: number;
/** Maximum latency in seconds at the 90th percentile. */
p90?: number;
/** Maximum latency in seconds at the 99th percentile. */
p99?: number;
};
}
/**

View File

@@ -305,7 +305,7 @@ For providers with partial OpenAI compatibility, use the `compat` field.
| `requiresThinkingAsText` | Convert thinking blocks to plain text |
| `thinkingFormat` | Use `reasoning_effort`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters |
| `supportsStrictMode` | Include the `strict` field in tool definitions |
| `openRouterRouting` | OpenRouter routing config passed to OpenRouter for model/provider selection |
| `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
| `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
`qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.
@@ -325,8 +325,32 @@ Example:
"name": "OpenRouter Claude 3.5 Sonnet",
"compat": {
"openRouterRouting": {
"order": ["anthropic"],
"fallbacks": ["openai"]
"allow_fallbacks": true,
"require_parameters": false,
"data_collection": "deny",
"zdr": true,
"enforce_distillable_text": false,
"order": ["anthropic", "amazon-bedrock", "google-vertex"],
"only": ["anthropic", "amazon-bedrock"],
"ignore": ["gmicloud", "friendli"],
"quantizations": ["fp16", "bf16"],
"sort": {
"by": "price",
"partition": "model"
},
"max_price": {
"prompt": 10,
"completion": 20
},
"preferred_min_throughput": {
"p50": 100,
"p90": 50
},
"preferred_max_latency": {
"p50": 1,
"p90": 3,
"p99": 5
}
}
}
}

View File

@@ -35,9 +35,43 @@ const Ajv = (AjvModule as any).default || AjvModule;
const ajv = new Ajv();
// Schema for OpenRouter routing preferences
const PercentileCutoffsSchema = Type.Object({
p50: Type.Optional(Type.Number()),
p75: Type.Optional(Type.Number()),
p90: Type.Optional(Type.Number()),
p99: Type.Optional(Type.Number()),
});
const OpenRouterRoutingSchema = Type.Object({
only: Type.Optional(Type.Array(Type.String())),
allow_fallbacks: Type.Optional(Type.Boolean()),
require_parameters: Type.Optional(Type.Boolean()),
data_collection: Type.Optional(Type.Union([Type.Literal("deny"), Type.Literal("allow")])),
zdr: Type.Optional(Type.Boolean()),
enforce_distillable_text: Type.Optional(Type.Boolean()),
order: Type.Optional(Type.Array(Type.String())),
only: Type.Optional(Type.Array(Type.String())),
ignore: Type.Optional(Type.Array(Type.String())),
quantizations: Type.Optional(Type.Array(Type.String())),
sort: Type.Optional(
Type.Union([
Type.String(),
Type.Object({
by: Type.Optional(Type.String()),
partition: Type.Optional(Type.Union([Type.String(), Type.Null()])),
}),
]),
),
max_price: Type.Optional(
Type.Object({
prompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),
completion: Type.Optional(Type.Union([Type.Number(), Type.String()])),
image: Type.Optional(Type.Union([Type.Number(), Type.String()])),
audio: Type.Optional(Type.Union([Type.Number(), Type.String()])),
request: Type.Optional(Type.Union([Type.Number(), Type.String()])),
}),
),
preferred_min_throughput: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),
preferred_max_latency: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),
});
// Schema for Vercel AI Gateway routing preferences