| /** | |
| * Preconnect to the Anthropic API to overlap TCP+TLS handshake with startup. | |
| * | |
| * The TCP+TLS handshake is ~100-200ms that normally blocks inside the first | |
| * API call. Kicking a fire-and-forget fetch during init lets the handshake | |
| * happen in parallel with action-handler work (~100ms of setup/commands/mcp | |
| * before the API request in -p mode; unbounded "user is typing" window in | |
| * interactive mode). | |
| * | |
| * Bun's fetch shares a keep-alive connection pool globally, so the real API | |
| * request reuses the warmed connection. | |
| * | |
| * Called from init.ts AFTER applyExtraCACertsFromConfig() + configureGlobalAgents() | |
| * so settings.json env vars are applied and the TLS cert store is finalized. | |
| * The early cli.tsx call site was removed β it ran before settings.json loaded, | |
| * so ANTHROPIC_BASE_URL/proxy/mTLS in settings would be invisible and preconnect | |
| * would warm the wrong pool (or worse, lock BoringSSL's cert store before | |
| * NODE_EXTRA_CA_CERTS was applied). | |
| * | |
| * Skipped when: | |
| * - proxy/mTLS/unix socket configured (preconnect would use wrong transport β | |
| * the SDK passes a custom dispatcher/agent that doesn't share the global pool) | |
| * - Bedrock/Vertex/Foundry (different endpoints, different auth) | |
| */ | |
| import { getOauthConfig } from '../constants/oauth.js' | |
| import { isEnvTruthy } from './envUtils.js' | |
| let fired = false | |
| export function preconnectAnthropicApi(): void { | |
| if (fired) return | |
| fired = true | |
| // Skip if using a cloud provider β different endpoint + auth | |
| if ( | |
| isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) || | |
| isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) || | |
| isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) | |
| ) { | |
| return | |
| } | |
| // Skip if proxy/mTLS/unix β SDK's custom dispatcher won't reuse this pool | |
| if ( | |
| process.env.HTTPS_PROXY || | |
| process.env.https_proxy || | |
| process.env.HTTP_PROXY || | |
| process.env.http_proxy || | |
| process.env.ANTHROPIC_UNIX_SOCKET || | |
| process.env.CLAUDE_CODE_CLIENT_CERT || | |
| process.env.CLAUDE_CODE_CLIENT_KEY | |
| ) { | |
| return | |
| } | |
| // Use configured base URL (staging, local, or custom gateway). Covers | |
| // ANTHROPIC_BASE_URL env + USE_STAGING_OAUTH + USE_LOCAL_OAUTH in one lookup. | |
| // NODE_EXTRA_CA_CERTS no longer a skip β init.ts applied it before this fires. | |
| const baseUrl = | |
| process.env.ANTHROPIC_BASE_URL || getOauthConfig().BASE_API_URL | |
| // Fire and forget. HEAD means no response body β the connection is eligible | |
| // for keep-alive pool reuse immediately after headers arrive. 10s timeout | |
| // so a slow network doesn't hang the process; abort is fine since the real | |
| // request will handshake fresh if needed. | |
| // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins | |
| void fetch(baseUrl, { | |
| method: 'HEAD', | |
| signal: AbortSignal.timeout(10_000), | |
| }).catch(() => {}) | |
| } | |