From 817eefd7846719e698fbbf74734cb24a65b8546c Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sun, 16 Aug 2026 12:50:58 +0900 Subject: [PATCH 1/3] fix(chat): preserve OpenRouter routing in native passthrough --- src/adapters/openai-chat.ts | 3 +++ tests/openrouter-provider-routing.test.ts | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/adapters/openai-chat.ts b/src/adapters/openai-chat.ts index 8275a5f3de..6e9e1314e9 100644 --- a/src/adapters/openai-chat.ts +++ b/src/adapters/openai-chat.ts @@ -100,6 +100,9 @@ export function buildOpenAIChatPassthroughRequest( if (rawBody[field] !== undefined) body[field] = rawBody[field]; } + const openRouterRouting = resolveOpenRouterRouting(provider, modelId); + if (openRouterRouting) body.provider = openRouterProviderPayload(openRouterRouting); + if (modelInList(provider.noTemperatureModels, modelId)) delete body.temperature; if (modelInList(provider.noTopPModels, modelId)) delete body.top_p; if (modelInList(provider.noPenaltyModels, modelId)) { diff --git a/tests/openrouter-provider-routing.test.ts b/tests/openrouter-provider-routing.test.ts index 4d98275d0f..4829d47013 100644 --- a/tests/openrouter-provider-routing.test.ts +++ b/tests/openrouter-provider-routing.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createOpenAIChatAdapter } from "../src/adapters/openai-chat"; +import { buildOpenAIChatPassthroughRequest, createOpenAIChatAdapter } from "../src/adapters/openai-chat"; import { openRouterRoutingConfigError, openRouterProviderPayload, @@ -100,6 +100,21 @@ describe("OpenRouter configurable provider routing", () => { expect(requestBody.stream_options).toEqual({ include_usage: true }); }); + test("preserves exact model routing on native Chat passthrough requests", () => { + const request = buildOpenAIChatPassthroughRequest(provider("https://openrouter.ai/api/v1", { + openRouterRouting: { order: ["deepseek"], allowFallbacks: true }, + modelOpenRouterRouting: { + "anthropic/claude-sonnet-5": { only: ["anthropic"], allowFallbacks: false }, + }, + }), { + messages: [{ role: "user", content: "hello" }], + }, "anthropic/claude-sonnet-5", false); + + expect(JSON.parse(request.body as string).provider).toEqual({ + only: ["anthropic"], allow_fallbacks: false, + }); + }); + test.each([ ["https://openrouter.ai/api/v1", "anthropic/claude-sonnet-5", {}], ["https://api.deepseek.com/v1", "deepseek-chat", deepSeekLock], From 0ab5f1b4c6e715cf5d8ca766d797d6a8edfea684 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:48:57 +0900 Subject: [PATCH 2/3] test(chat): cover native OpenRouter routing --- tests/openrouter-provider-routing.test.ts | 87 +++++++++++++++++++++-- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/tests/openrouter-provider-routing.test.ts b/tests/openrouter-provider-routing.test.ts index 4829d47013..b806851256 100644 --- a/tests/openrouter-provider-routing.test.ts +++ b/tests/openrouter-provider-routing.test.ts @@ -4,6 +4,7 @@ import { openRouterRoutingConfigError, openRouterProviderPayload, } from "../src/providers/openrouter-routing"; +import { clearKeyCooldowns, rotateProviderTransportOn429 } from "../src/providers/key-failover"; import { routeModel } from "../src/router"; import { providerManagementConfigError, safeConfigDTO } from "../src/server/auth-cors"; import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../src/types"; @@ -26,6 +27,18 @@ function body(baseUrl: string, modelId: string, overrides: Partial; } +function passthroughBody( + providerConfig: OcxProviderConfig, + modelId: string, + rawBody: Record = {}, +): Record { + const request = buildOpenAIChatPassthroughRequest(providerConfig, { + messages: [{ role: "user", content: "hello" }], + ...rawBody, + }, modelId, false); + return JSON.parse(request.body as string) as Record; +} + describe("OpenRouter configurable provider routing", () => { const deepSeekLock = { openRouterRouting: { only: ["deepseek"], allowFallbacks: false } }; @@ -101,20 +114,84 @@ describe("OpenRouter configurable provider routing", () => { }); test("preserves exact model routing on native Chat passthrough requests", () => { - const request = buildOpenAIChatPassthroughRequest(provider("https://openrouter.ai/api/v1", { + const requestBody = passthroughBody(provider("https://openrouter.ai/api/v1", { openRouterRouting: { order: ["deepseek"], allowFallbacks: true }, modelOpenRouterRouting: { "anthropic/claude-sonnet-5": { only: ["anthropic"], allowFallbacks: false }, }, - }), { - messages: [{ role: "user", content: "hello" }], - }, "anthropic/claude-sonnet-5", false); + }), "anthropic/claude-sonnet-5", { + provider: { only: ["caller-controlled"] }, + }); - expect(JSON.parse(request.body as string).provider).toEqual({ + expect(requestBody.provider).toEqual({ only: ["anthropic"], allow_fallbacks: false, }); }); + test("preserves provider-wide routing on native Chat passthrough requests", () => { + expect(passthroughBody( + provider("https://openrouter.ai/api/v1", deepSeekLock), + "deepseek/deepseek-chat", + ).provider).toEqual({ only: ["deepseek"], allow_fallbacks: false }); + }); + + test("resolves routed aliases before applying native Chat model preferences", () => { + const nativeModelId = "anthropic/claude-sonnet-5"; + const config: OcxConfig = { + port: 10100, + defaultProvider: "openrouter", + providers: { + openrouter: provider("https://openrouter.ai/api/v1", { + models: [nativeModelId], + openRouterRouting: { only: ["deepseek"] }, + modelOpenRouterRouting: { + [nativeModelId]: { only: ["anthropic"], allowFallbacks: false }, + }, + }), + }, + }; + const route = routeModel(config, "openrouter/anthropic-claude-sonnet-5"); + expect(route.modelId).toBe(nativeModelId); + expect(passthroughBody(route.provider, route.modelId).provider).toEqual({ + only: ["anthropic"], allow_fallbacks: false, + }); + }); + + test("does not forward a caller provider object to non-OpenRouter passthroughs", () => { + expect(passthroughBody( + provider("https://api.deepseek.com/v1"), + "deepseek-chat", + { provider: { only: ["caller-controlled"] } }, + ).provider).toBeUndefined(); + }); + + test("preserves provider routing after native Chat key rotation", () => { + clearKeyCooldowns("openrouter"); + const openrouter = provider("https://openrouter.ai/api/v1", { + authMode: "key", + apiKey: "key-one", + apiKeyPool: [{ id: "one", key: "key-one" }, { id: "two", key: "key-two" }], + openRouterRouting: { only: ["anthropic"], allowFallbacks: false }, + }); + const config: OcxConfig = { + port: 10100, + defaultProvider: "openrouter", + providers: { openrouter }, + }; + try { + const rotated = rotateProviderTransportOn429(config, "openrouter", openrouter, { + attemptedKey: "key-one", + now: 1_000_000, + }); + expect(rotated?.apiKey).toBe("key-two"); + expect(passthroughBody(rotated!, "anthropic/claude-sonnet-5").provider).toEqual({ + only: ["anthropic"], allow_fallbacks: false, + }); + } finally { + clearKeyCooldowns("openrouter"); + } + }); + test.each([ ["https://openrouter.ai/api/v1", "anthropic/claude-sonnet-5", {}], ["https://api.deepseek.com/v1", "deepseek-chat", deepSeekLock], From 660c8ee74f8a0b70f6a4361fb3a936c7559789b2 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 18 Aug 2026 07:45:10 +0900 Subject: [PATCH 3/3] test(chat): isolate OpenRouter key-rotation persistence --- tests/openrouter-provider-routing.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/openrouter-provider-routing.test.ts b/tests/openrouter-provider-routing.test.ts index b806851256..b6ee368f3f 100644 --- a/tests/openrouter-provider-routing.test.ts +++ b/tests/openrouter-provider-routing.test.ts @@ -1,4 +1,7 @@ import { describe, expect, test } from "bun:test"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { buildOpenAIChatPassthroughRequest, createOpenAIChatAdapter } from "../src/adapters/openai-chat"; import { openRouterRoutingConfigError, @@ -8,6 +11,7 @@ import { clearKeyCooldowns, rotateProviderTransportOn429 } from "../src/provider import { routeModel } from "../src/router"; import { providerManagementConfigError, safeConfigDTO } from "../src/server/auth-cors"; import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../src/types"; +import { removeTreeWithRetry } from "./helpers/remove-tree"; function provider(baseUrl: string, overrides: Partial = {}): OcxProviderConfig { return { adapter: "openai-chat", baseUrl, apiKey: "test-key", ...overrides }; @@ -166,6 +170,9 @@ describe("OpenRouter configurable provider routing", () => { }); test("preserves provider routing after native Chat key rotation", () => { + const previousHome = process.env.OPENCODEX_HOME; + const home = mkdtempSync(join(tmpdir(), "ocx-openrouter-routing-")); + process.env.OPENCODEX_HOME = home; clearKeyCooldowns("openrouter"); const openrouter = provider("https://openrouter.ai/api/v1", { authMode: "key", @@ -189,6 +196,9 @@ describe("OpenRouter configurable provider routing", () => { }); } finally { clearKeyCooldowns("openrouter"); + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + removeTreeWithRetry(home); } });