From dac7474da2379bf1027f5fbddc06108399bed4e5 Mon Sep 17 00:00:00 2001 From: jake Date: Mon, 19 Jan 2026 20:39:04 +0200 Subject: [PATCH] feat(ai): add OpenRouter provider routing support Allows custom models to specify which upstream providers OpenRouter should route requests to via the `openRouterRouting` field in model definitions. Supported fields: - `only`: list of provider slugs to exclusively use - `order`: list of provider slugs to try in order --- packages/ai/CHANGELOG.md | 1 + packages/ai/src/providers/openai-completions.ts | 7 +++++++ packages/ai/src/types.ts | 14 ++++++++++++++ .../openai-completions-tool-result-images.test.ts | 1 + packages/coding-agent/src/core/model-registry.ts | 7 +++++++ 5 files changed, 30 insertions(+) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 17c1768c..392b4b18 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Added OpenRouter provider routing support for custom models via `openRouterRouting` compat field ([#859](https://github.com/badlogic/pi-mono/pull/859) by [@v01dpr1mr0s3](https://github.com/v01dpr1mr0s3)) - Added `azure-openai-responses` provider support for Azure OpenAI Responses API. ([#890](https://github.com/badlogic/pi-mono/pull/890) by [@markusylisiurunen](https://github.com/markusylisiurunen)) - Added `createAssistantMessageEventStream()` factory function for use in extensions. - Added `resetApiProviders()` to clear and re-register built-in API providers. diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 46c62e9c..8c64cd28 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -445,6 +445,11 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio params.reasoning_effort = options.reasoningEffort; } + // OpenRouter provider routing preferences + if (model.baseUrl.includes("openrouter.ai") && model.compat?.openRouterRouting) { + (params as any).provider = model.compat.openRouterRouting; + } + return params; } @@ -777,6 +782,7 @@ function detectCompat(model: Model<"openai-completions">): Required): Required { id: string; diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts index 3a909022..36398f26 100644 --- a/packages/ai/test/openai-completions-tool-result-images.test.ts +++ b/packages/ai/test/openai-completions-tool-result-images.test.ts @@ -30,6 +30,7 @@ const compat: Required = { requiresThinkingAsText: false, requiresMistralToolIds: false, thinkingFormat: "openai", + openRouterRouting: {}, }; function buildToolResult(toolCallId: string, timestamp: number): ToolResultMessage { diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index 61c67c6e..2cd2171a 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -25,6 +25,12 @@ import type { AuthStorage } from "./auth-storage.js"; const Ajv = (AjvModule as any).default || AjvModule; +// Schema for OpenRouter routing preferences +const OpenRouterRoutingSchema = Type.Object({ + only: Type.Optional(Type.Array(Type.String())), + order: Type.Optional(Type.Array(Type.String())), +}); + // Schema for OpenAI compatibility settings const OpenAICompletionsCompatSchema = Type.Object({ supportsStore: Type.Optional(Type.Boolean()), @@ -37,6 +43,7 @@ const OpenAICompletionsCompatSchema = Type.Object({ requiresThinkingAsText: Type.Optional(Type.Boolean()), requiresMistralToolIds: Type.Optional(Type.Boolean()), thinkingFormat: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("zai")])), + openRouterRouting: Type.Optional(OpenRouterRoutingSchema), }); const OpenAIResponsesCompatSchema = Type.Object({