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");