Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
14 kB · 426 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427// @aws-sdk/credential-provider-node and @smithy/node-http-handler are imported// dynamically in getAWSClientProxyConfig() to defer ~929KB of AWS SDK.// undici is lazy-required inside getProxyAgent/configureGlobalAgents to defer// ~1.5MB when no HTTPS_PROXY/mTLS env vars are set (the common case).import axios, { type AxiosInstance } from 'axios'import type { LookupOptions } from 'dns'import type { Agent } from 'http'import { HttpsProxyAgent, type HttpsProxyAgentOptions } from 'https-proxy-agent'import memoize from 'lodash-es/memoize.js'import type * as undici from 'undici'import { getCACertificates } from './caCerts.js'import { logForDebugging } from './debug.js'import { isEnvTruthy } from './envUtils.js'import { getMTLSAgent, getMTLSConfig, getTLSFetchOptions, type TLSConfig,} from './mtls.js'
// Disable fetch keep-alive after a stale-pool ECONNRESET so retries open a// fresh TCP connection instead of reusing the dead pooled socket. Sticky for// the process lifetime — once the pool is known-bad, don't trust it again.// Works under Bun (native fetch respects keepalive:false for pooling).// Under Node/undici, keepalive is a no-op for pooling, but undici// naturally evicts dead sockets from the pool on ECONNRESET.let keepAliveDisabled = false
export function disableKeepAlive(): void { keepAliveDisabled = true}
export function _resetKeepAliveForTesting(): void { keepAliveDisabled = false}
/** * Convert dns.LookupOptions.family to a numeric address family value * Handles: 0 | 4 | 6 | 'IPv4' | 'IPv6' | undefined */export function getAddressFamily(options: LookupOptions): 0 | 4 | 6 { switch (options.family) { case 0: case 4: case 6: return options.family case 'IPv6': return 6 case 'IPv4': case undefined: return 4 default: throw new Error(`Unsupported address family: ${options.family}`) }}
type EnvLike = Record<string, string | undefined>
/** * Get the active proxy URL if one is configured * Prefers lowercase variants over uppercase (https_proxy > HTTPS_PROXY > http_proxy > HTTP_PROXY) * @param env Environment variables to check (defaults to process.env for production use) */export function getProxyUrl(env: EnvLike = process.env): string | undefined { return env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY}
/** * Get the NO_PROXY environment variable value * Prefers lowercase over uppercase (no_proxy > NO_PROXY) * @param env Environment variables to check (defaults to process.env for production use) */export function getNoProxy(env: EnvLike = process.env): string | undefined { return env.no_proxy || env.NO_PROXY}
/** * Check if a URL should bypass the proxy based on NO_PROXY environment variable * Supports: * - Exact hostname matches (e.g., "localhost") * - Domain suffix matches with leading dot (e.g., ".example.com") * - Wildcard "*" to bypass all * - Port-specific matches (e.g., "example.com:8080") * - IP addresses (e.g., "127.0.0.1") * @param urlString URL to check * @param noProxy NO_PROXY value (defaults to getNoProxy() for production use) */export function shouldBypassProxy( urlString: string, noProxy: string | undefined = getNoProxy(),): boolean { if (!noProxy) return false
// Handle wildcard if (noProxy === '*') return true
try { const url = new URL(urlString) const hostname = url.hostname.toLowerCase() const port = url.port || (url.protocol === 'https:' ? '443' : '80') const hostWithPort = `${hostname}:${port}`
// Split by comma or space and trim each entry const noProxyList = noProxy.split(/[,\s]+/).filter(Boolean)
return noProxyList.some(pattern => { pattern = pattern.toLowerCase().trim()
// Check for port-specific match if (pattern.includes(':')) { return hostWithPort === pattern }
// Check for domain suffix match (with or without leading dot) if (pattern.startsWith('.')) { // Pattern ".example.com" should match "sub.example.com" and "example.com" // but NOT "notexample.com" const suffix = pattern return hostname === pattern.substring(1) || hostname.endsWith(suffix) }
// Check for exact hostname match or IP address return hostname === pattern }) } catch { // If URL parsing fails, don't bypass proxy return false }}
/** * Create an HttpsProxyAgent with optional mTLS configuration * Skips local DNS resolution to let the proxy handle it */function createHttpsProxyAgent( proxyUrl: string, extra: HttpsProxyAgentOptions<string> = {},): HttpsProxyAgent<string> { const mtlsConfig = getMTLSConfig() const caCerts = getCACertificates()
const agentOptions: HttpsProxyAgentOptions<string> = { ...(mtlsConfig && { cert: mtlsConfig.cert, key: mtlsConfig.key, passphrase: mtlsConfig.passphrase, }), ...(caCerts && { ca: caCerts }), }
if (isEnvTruthy(process.env.CLAUDE_CODE_PROXY_RESOLVES_HOSTS)) { // Skip local DNS resolution - let the proxy resolve hostnames // This is needed for environments where DNS is not configured locally // and instead handled by the proxy (as in sandboxes) agentOptions.lookup = (hostname, options, callback) => { callback(null, hostname, getAddressFamily(options)) } }
return new HttpsProxyAgent(proxyUrl, { ...agentOptions, ...extra })}
/** * Axios instance with its own proxy agent. Same NO_PROXY/mTLS/CA * resolution as the global interceptor, but agent options stay * scoped to this instance. */export function createAxiosInstance( extra: HttpsProxyAgentOptions<string> = {},): AxiosInstance { const proxyUrl = getProxyUrl() const mtlsAgent = getMTLSAgent() const instance = axios.create({ proxy: false })
if (!proxyUrl) { if (mtlsAgent) instance.defaults.httpsAgent = mtlsAgent return instance }
const proxyAgent = createHttpsProxyAgent(proxyUrl, extra) instance.interceptors.request.use(config => { if (config.url && shouldBypassProxy(config.url)) { config.httpsAgent = mtlsAgent config.httpAgent = mtlsAgent } else { config.httpsAgent = proxyAgent config.httpAgent = proxyAgent } return config }) return instance}
/** * Get or create a memoized proxy agent for the given URI * Now respects NO_PROXY environment variable */export const getProxyAgent = memoize((uri: string): undici.Dispatcher => { // eslint-disable-next-line @typescript-eslint/no-require-imports const undiciMod = require('undici') as typeof undici const mtlsConfig = getMTLSConfig() const caCerts = getCACertificates()
// Use EnvHttpProxyAgent to respect NO_PROXY // This agent automatically checks NO_PROXY for each request const proxyOptions: undici.EnvHttpProxyAgent.Options & { requestTls?: { cert?: string | Buffer key?: string | Buffer passphrase?: string ca?: string | string[] | Buffer } } = { // Override both HTTP and HTTPS proxy with the provided URI httpProxy: uri, httpsProxy: uri, noProxy: process.env.NO_PROXY || process.env.no_proxy, }
// Set both connect and requestTls so TLS options apply to both paths: // - requestTls: used by ProxyAgent for the TLS connection through CONNECT tunnels // - connect: used by Agent for direct (no-proxy) connections if (mtlsConfig || caCerts) { const tlsOpts = { ...(mtlsConfig && { cert: mtlsConfig.cert, key: mtlsConfig.key, passphrase: mtlsConfig.passphrase, }), ...(caCerts && { ca: caCerts }), } proxyOptions.connect = tlsOpts proxyOptions.requestTls = tlsOpts }
return new undiciMod.EnvHttpProxyAgent(proxyOptions)})
/** * Get an HTTP agent configured for WebSocket proxy support * Returns undefined if no proxy is configured or URL should bypass proxy */export function getWebSocketProxyAgent(url: string): Agent | undefined { const proxyUrl = getProxyUrl()
if (!proxyUrl) { return undefined }
// Check if URL should bypass proxy if (shouldBypassProxy(url)) { return undefined }
return createHttpsProxyAgent(proxyUrl)}
/** * Get the proxy URL for WebSocket connections under Bun. * Bun's native WebSocket supports a `proxy` string option instead of Node's `agent`. * Returns undefined if no proxy is configured or URL should bypass proxy. */export function getWebSocketProxyUrl(url: string): string | undefined { const proxyUrl = getProxyUrl()
if (!proxyUrl) { return undefined }
if (shouldBypassProxy(url)) { return undefined }
return proxyUrl}
/** * Get fetch options for the Anthropic SDK with proxy and mTLS configuration * Returns fetch options with appropriate dispatcher for proxy and/or mTLS * * @param opts.forAnthropicAPI - Enables ANTHROPIC_UNIX_SOCKET tunneling. This * env var is set by `claude ssh` on the remote CLI to route API calls through * an ssh -R forwarded unix socket to a local auth proxy. It MUST NOT leak * into non-Anthropic-API fetch paths (MCP HTTP/SSE transports, etc.) or those * requests get misrouted to api.anthropic.com. Only the Anthropic SDK client * should pass `true` here. */export function getProxyFetchOptions(opts?: { forAnthropicAPI?: boolean }): { tls?: TLSConfig dispatcher?: undici.Dispatcher proxy?: string unix?: string keepalive?: false} { const base = keepAliveDisabled ? ({ keepalive: false } as const) : {}
// ANTHROPIC_UNIX_SOCKET tunnels through the `claude ssh` auth proxy, which // hardcodes the upstream to the Anthropic API. Scope to the Anthropic API // client so MCP/SSE/other callers don't get their requests misrouted. if (opts?.forAnthropicAPI) { const unixSocket = process.env.ANTHROPIC_UNIX_SOCKET if (unixSocket && typeof Bun !== 'undefined') { return { ...base, unix: unixSocket } } }
const proxyUrl = getProxyUrl()
// If we have a proxy, use the proxy agent (which includes mTLS config) if (proxyUrl) { if (typeof Bun !== 'undefined') { return { ...base, proxy: proxyUrl, ...getTLSFetchOptions() } } return { ...base, dispatcher: getProxyAgent(proxyUrl) } }
// Otherwise, use TLS options directly if available return { ...base, ...getTLSFetchOptions() }}
/** * Configure global HTTP agents for both axios and undici * This ensures all HTTP requests use the proxy and/or mTLS if configured */let proxyInterceptorId: number | undefined
export function configureGlobalAgents(): void { const proxyUrl = getProxyUrl() const mtlsAgent = getMTLSAgent()
// Eject previous interceptor to avoid stacking on repeated calls if (proxyInterceptorId !== undefined) { axios.interceptors.request.eject(proxyInterceptorId) proxyInterceptorId = undefined }
// Reset proxy-related defaults so reconfiguration is clean axios.defaults.proxy = undefined axios.defaults.httpAgent = undefined axios.defaults.httpsAgent = undefined
if (proxyUrl) { // workaround for https://github.com/axios/axios/issues/4531 axios.defaults.proxy = false
// Create proxy agent with mTLS options if available const proxyAgent = createHttpsProxyAgent(proxyUrl)
// Add axios request interceptor to handle NO_PROXY proxyInterceptorId = axios.interceptors.request.use(config => { // Check if URL should bypass proxy based on NO_PROXY if (config.url && shouldBypassProxy(config.url)) { // Bypass proxy - use mTLS agent if configured, otherwise undefined if (mtlsAgent) { config.httpsAgent = mtlsAgent config.httpAgent = mtlsAgent } else { // Remove any proxy agents to use direct connection delete config.httpsAgent delete config.httpAgent } } else { // Use proxy agent config.httpsAgent = proxyAgent config.httpAgent = proxyAgent } return config })
// Set global dispatcher that now respects NO_PROXY via EnvHttpProxyAgent // eslint-disable-next-line @typescript-eslint/no-require-imports ;(require('undici') as typeof undici).setGlobalDispatcher( getProxyAgent(proxyUrl), ) } else if (mtlsAgent) { // No proxy but mTLS is configured axios.defaults.httpsAgent = mtlsAgent
// Set undici global dispatcher with mTLS const mtlsOptions = getTLSFetchOptions() if (mtlsOptions.dispatcher) { // eslint-disable-next-line @typescript-eslint/no-require-imports ;(require('undici') as typeof undici).setGlobalDispatcher( mtlsOptions.dispatcher, ) } }}
/** * Get AWS SDK client configuration with proxy support * Returns configuration object that can be spread into AWS service client constructors */export async function getAWSClientProxyConfig(): Promise<object> { const proxyUrl = getProxyUrl()
if (!proxyUrl) { return {} }
const [{ NodeHttpHandler }, { defaultProvider }] = await Promise.all([ import('@smithy/node-http-handler'), import('@aws-sdk/credential-provider-node'), ])
const agent = createHttpsProxyAgent(proxyUrl) const requestHandler = new NodeHttpHandler({ httpAgent: agent, httpsAgent: agent, })
return { requestHandler, credentials: defaultProvider({ clientConfig: { requestHandler }, }), }}
/** * Clear proxy agent cache. */export function clearProxyCache(): void { getProxyAgent.cache.clear?.() logForDebugging('Cleared proxy agent cache')}