72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
/**
|
|
* 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(() => {})
|
|
}
|