diff --git a/src/utils/apiPreconnect.test.ts b/src/utils/apiPreconnect.test.ts new file mode 100644 index 00000000..1cce48c6 --- /dev/null +++ b/src/utils/apiPreconnect.test.ts @@ -0,0 +1,69 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' + +const originalEnv = { ...process.env } +const originalFetch = globalThis.fetch + +async function importFreshModule() { + return import(`./apiPreconnect.ts?ts=${Date.now()}-${Math.random()}`) +} + +beforeEach(() => { + process.env = { ...originalEnv } +}) + +afterEach(() => { + process.env = { ...originalEnv } + globalThis.fetch = originalFetch +}) + +describe('preconnectAnthropicApi', () => { + test('does not fetch when OpenAI mode is enabled', async () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 }))) + globalThis.fetch = fetchMock as typeof globalThis.fetch + + const { preconnectAnthropicApi } = await importFreshModule() + preconnectAnthropicApi() + + expect(fetchMock).not.toHaveBeenCalled() + }) + + test('does not fetch when Gemini mode is enabled', async () => { + process.env.CLAUDE_CODE_USE_GEMINI = '1' + const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 }))) + globalThis.fetch = fetchMock as typeof globalThis.fetch + + const { preconnectAnthropicApi } = await importFreshModule() + preconnectAnthropicApi() + + expect(fetchMock).not.toHaveBeenCalled() + }) + + test('does not fetch when GitHub mode is enabled', async () => { + process.env.CLAUDE_CODE_USE_GITHUB = '1' + const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 }))) + globalThis.fetch = fetchMock as typeof globalThis.fetch + + const { preconnectAnthropicApi } = await importFreshModule() + preconnectAnthropicApi() + + expect(fetchMock).not.toHaveBeenCalled() + }) + + test('fetches in first-party mode', async () => { + delete process.env.CLAUDE_CODE_USE_OPENAI + delete process.env.CLAUDE_CODE_USE_GEMINI + delete process.env.CLAUDE_CODE_USE_GITHUB + delete process.env.CLAUDE_CODE_USE_BEDROCK + delete process.env.CLAUDE_CODE_USE_VERTEX + delete process.env.CLAUDE_CODE_USE_FOUNDRY + + const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 }))) + globalThis.fetch = fetchMock as typeof globalThis.fetch + + const { preconnectAnthropicApi } = await importFreshModule() + preconnectAnthropicApi() + + expect(fetchMock).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/utils/apiPreconnect.ts b/src/utils/apiPreconnect.ts index 6a8de649..103198da 100644 --- a/src/utils/apiPreconnect.ts +++ b/src/utils/apiPreconnect.ts @@ -25,6 +25,7 @@ import { getOauthConfig } from '../constants/oauth.js' import { isEnvTruthy } from './envUtils.js' +import { getAPIProvider } from './model/providers.js' let fired = false @@ -32,6 +33,11 @@ export function preconnectAnthropicApi(): void { if (fired) return fired = true + // Third-party providers should not warm a connection to Anthropic. + if (getAPIProvider() !== 'firstParty') { + return + } + // Skip if using a cloud provider — different endpoint + auth if ( isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||