From e3ec10bc2a7a5d108f7bbe42ad6e3d9cd5b963bf Mon Sep 17 00:00:00 2001 From: NOXX - Commiter Date: Tue, 16 Jun 2026 19:06:38 +0300 Subject: [PATCH] fix(default-executor): honor custom providerSpecificData.baseUrl for OpenAI-format providers (#4002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.27 — honor custom providerSpecificData.baseUrl in DefaultExecutor (openai-format), tested. --- open-sse/executors/default.ts | 15 +++++++++++ tests/unit/executor-default-base.test.ts | 34 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts index 03433011f3..cc2444602a 100644 --- a/open-sse/executors/default.ts +++ b/open-sse/executors/default.ts @@ -304,6 +304,21 @@ export class DefaultExecutor extends BaseExecutor { return `https://${resourceUrl || "portal.qwen.ai"}/v1/chat/completions`; } default: { + // Honor a user-supplied custom base URL (providerSpecificData.baseUrl) for + // OpenAI-format providers (e.g. the built-in "openai" provider pointed at a + // proxy/gateway). Without this, a configured custom base URL was silently + // ignored and requests always hit the hardcoded this.config.baseUrl + // (https://api.openai.com/v1/...). Scoped to openai-format providers so + // non-OpenAI default-branch providers keep their existing behavior. + const customBaseUrl = + typeof credentials?.providerSpecificData?.baseUrl === "string" && + credentials.providerSpecificData.baseUrl.trim() + ? (credentials.providerSpecificData.baseUrl as string) + : null; + const isOpenAIFormat = !this.config.format || this.config.format === "openai"; + if (customBaseUrl && isOpenAIFormat) { + return normalizeOpenAIChatUrl(customBaseUrl); + } const url = this.config.baseUrl; const entry = getRegistryEntry(this.provider); return entry?.urlSuffix ? `${url}${entry.urlSuffix}` : url; diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index 75d8c5fe84..6266bcb7fa 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -108,6 +108,40 @@ test("DefaultExecutor.buildUrl uses full chat endpoints for hosted OpenAI-compat assert.equal(crof.buildUrl("gpt-4.1", true), "https://crof.ai/v1/chat/completions"); }); +test("DefaultExecutor.buildUrl honors a custom providerSpecificData.baseUrl for the built-in openai provider", () => { + const openai = new DefaultExecutor("openai"); + + // No override → hardcoded OpenAI endpoint (unchanged behavior). + assert.equal( + openai.buildUrl("gpt-4o", true), + "https://api.openai.com/v1/chat/completions" + ); + + // Custom base URL (e.g. a proxy/gateway) must be used instead of api.openai.com. + assert.equal( + openai.buildUrl("gpt-4o", true, 0, { + providerSpecificData: { baseUrl: "https://api.contactboxtools.me/v1" }, + }), + "https://api.contactboxtools.me/v1/chat/completions" + ); + + // Trailing slash is normalized. + assert.equal( + openai.buildUrl("gpt-4o", true, 0, { + providerSpecificData: { baseUrl: "https://proxy.example/v1/" }, + }), + "https://proxy.example/v1/chat/completions" + ); + + // A base URL already pointing at the chat endpoint is kept as-is. + assert.equal( + openai.buildUrl("gpt-4o", true, 0, { + providerSpecificData: { baseUrl: "https://proxy.example/v1/chat/completions" }, + }), + "https://proxy.example/v1/chat/completions" + ); +}); + test("DefaultExecutor.buildUrl handles openai-compatible and anthropic-compatible providers", () => { const openAICompat = new DefaultExecutor("openai-compatible-test"); const openAIResponsesCompat = new DefaultExecutor("openai-compatible-responses-test");