From e674e44ccf92e12e86b53e9eff5fd148f5777d16 Mon Sep 17 00:00:00 2001 From: Jack Chen Date: Thu, 9 Jul 2026 16:25:51 +0800 Subject: [PATCH] fix byok-opencode: route non-OpenAI openai-protocol hosts to @ai-sdk/openai-compatible The BYOK OpenCode path mapped protocol "openai" straight to @ai-sdk/openai (Responses API /responses) for every host, so providers that only serve /chat/completions (DeepSeek, vLLM, etc.) 404 on /responses. Route non-api.openai.com openai-protocol hosts to @ai-sdk/openai-compatible; real OpenAI stays on @ai-sdk/openai. Adds a DeepSeek regression test and corrects the vLLM test to openai-compatible. --- apps/daemon/src/runtimes/byok-opencode.ts | 25 +++++++++++++++++-- .../tests/runtimes/byok-opencode.test.ts | 25 ++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/apps/daemon/src/runtimes/byok-opencode.ts b/apps/daemon/src/runtimes/byok-opencode.ts index 464f6c00ce7..b3384679327 100644 --- a/apps/daemon/src/runtimes/byok-opencode.ts +++ b/apps/daemon/src/runtimes/byok-opencode.ts @@ -158,6 +158,15 @@ function isExactOrigin(value: string, origin: string): boolean { } } +function isRealOpenAIHost(baseUrl: string): boolean { + if (!baseUrl) return true; + try { + return new URL(baseUrl).hostname === 'api.openai.com'; + } catch { + return true; + } +} + function buildProviderEntry( protocol: ByokChatProviderConfig['protocol'], baseUrl: string, @@ -207,11 +216,23 @@ function buildProviderEntry( }, }; case 'openai': + // Real OpenAI speaks the Responses API via @ai-sdk/openai. Every other + // host under the "openai" protocol (DeepSeek, vLLM, etc.) only serves + // /chat/completions, so route it through @ai-sdk/openai-compatible. + if (isRealOpenAIHost(baseUrl)) { + return { + npm: '@ai-sdk/openai', + options: { + ...apiKeyOption, + ...(baseUrl ? { baseURL: baseUrl } : {}), + }, + }; + } return { - npm: '@ai-sdk/openai', + npm: '@ai-sdk/openai-compatible', options: { + baseURL: baseUrl, ...apiKeyOption, - ...(baseUrl ? { baseURL: baseUrl } : {}), }, }; case 'senseaudio': diff --git a/apps/daemon/tests/runtimes/byok-opencode.test.ts b/apps/daemon/tests/runtimes/byok-opencode.test.ts index 363788375e3..f23297c1549 100644 --- a/apps/daemon/tests/runtimes/byok-opencode.test.ts +++ b/apps/daemon/tests/runtimes/byok-opencode.test.ts @@ -74,6 +74,29 @@ describe('byok-opencode runtime config', () => { }); }); + it('routes OpenAI-protocol BYOK with a non-OpenAI base URL to the OpenAI-compatible provider package', () => { + expect(buildOpenCodeByokProviderConfig( + { protocol: 'openai', apiKey: 'sk-deepseek', baseUrl: 'https://api.deepseek.com' }, + 'deepseek-v4-pro', + )?.config).toMatchObject({ + provider: { + [BYOK_OPENCODE_PROVIDER_ID]: { + npm: '@ai-sdk/openai-compatible', + options: { + baseURL: 'https://api.deepseek.com', + apiKey: `{env:${BYOK_OPENCODE_API_KEY_ENV}}`, + }, + }, + }, + }); + expect(buildOpenCodeByokProviderConfig( + { protocol: 'openai', apiKey: 'sk-openai', baseUrl: 'https://api.openai.com' }, + 'deepseek-v4-pro', + )?.config).toMatchObject({ + provider: { [BYOK_OPENCODE_PROVIDER_ID]: { npm: '@ai-sdk/openai' } }, + }); + }); + it('normalizes origin-only native provider base URLs for OpenCode provider packages', () => { expect(buildOpenCodeByokProviderConfig( { protocol: 'anthropic', apiKey: 'sk-ant', baseUrl: 'https://api.anthropic.com' }, @@ -272,7 +295,7 @@ describe('byok-opencode runtime config', () => { expect(out?.config).toMatchObject({ provider: { [BYOK_OPENCODE_PROVIDER_ID]: { - npm: '@ai-sdk/openai', + npm: '@ai-sdk/openai-compatible', options: { baseURL: 'http://127.0.0.1:8000/v1', },