diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index fd472dce..3c2a0d1a 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -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; + }; } /** diff --git a/packages/coding-agent/docs/models.md b/packages/coding-agent/docs/models.md index 2fa48a69..8321d45d 100644 --- a/packages/coding-agent/docs/models.md +++ b/packages/coding-agent/docs/models.md @@ -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 + } } } } diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index cdad1215..f68da2ac 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -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