'use strict'; const http = require('http'); const https = require('https'); const http2 = require('http2'); const net = require('net'); const crypto = require('crypto'); // os 模块不引用 — 避免暴露真实主机信息 // ─── 配置 ─────────────────────────────────────────────── const UPSTREAM_HOST = process.env.UPSTREAM_HOST || 'api.anthropic.com'; const LISTEN_PORT = parseInt(process.env.PROXY_PORT || '3456', 10); const LISTEN_HOST = process.env.PROXY_HOST || '127.0.0.1'; const UPSTREAM_PROXY = process.env.UPSTREAM_PROXY || ''; const CONNECT_TIMEOUT = parseInt(process.env.CONNECT_TIMEOUT || '30000', 10); const IDLE_TIMEOUT = parseInt(process.env.IDLE_TIMEOUT || '600000', 10); const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED !== 'false'; // 默认开启 const DD_API_KEY = process.env.DD_API_KEY || 'pubbbf48e6d78dae54bceaa4acf463299bf'; const CLI_VERSION = process.env.CLI_VERSION || '2.1.88'; const BUILD_TIME = process.env.BUILD_TIME || '2026-03-31T01:39:46Z'; // 伪装的 Node 版本(CLI 2.1.88 打包的 Bun 报告的 Node 兼容版本) const FAKE_NODE_VERSION = process.env.FAKE_NODE_VERSION || 'v24.3.0'; const log = (level, msg, extra = {}) => { const entry = { time: new Date().toISOString(), level, msg, ...extra }; process.stderr.write(JSON.stringify(entry) + '\n'); }; const HEALTH_PATH = '/__health'; const h2Hosts = new Set(); // Strip userinfo (user:pass) from proxy URL for safe logging function redactProxyURL(raw) { if (!raw) return ''; try { const u = new URL(raw); u.username = ''; u.password = ''; return u.toString(); } catch { return ''; } } const h2Sessions = new Map(); // ─── 虚拟主机身份生成 ───────────────────────────────────── // 每个账号基于 seed 生成全局唯一的主机身份,看起来像一台真实的个人开发机 // 匹配 CLI 的 OTEL detectResources: hostDetector + processDetector + serviceInstanceIdDetector // // 设计原则: // 1. 同一账号(seed)永远产出同一台"机器"的特征 // 2. 不同账号的特征互不相同(无共享池、无碰撞) // 3. 每个字段都像人手动设置的,不是程序生成的 // ─── macOS 主机身份词表 ────────────────────────────────────────── // macOS 用户 hostname 习惯: "alex-MBP", "sam-MacBook-Pro" 等 const MBP_NAMES = ['alex','sam','chris','max','lee','kai','jamie','taylor','morgan','casey', 'drew','avery','riley','blake','jordan','ryan','parker','quinn','reese','cameron']; const MBP_SUFFIX = ['-MBP','-MacBook','-MacBook-Pro','-MacBook-Air',"s-MBP","s-MacBook","s-MacBook-Pro"]; function generateHostIdentity(seed) { const h = (s) => crypto.createHash('sha256').update(seed + ':' + s).digest(); // ── hostname: macOS 风格 ── const hb = h('hostname'); const name = MBP_NAMES[hb.readUInt8(0) % MBP_NAMES.length]; const sfx = MBP_SUFFIX[hb.readUInt8(1) % MBP_SUFFIX.length]; const hostname = `${name}${sfx}`; // ── username: 取自 hostname 名字(真实 Mac 行为) ── const username = name; // ── terminal: macOS 常见终端分布 ── const termRoll = h('terminal').readUInt8(0) % 100; const terminal = termRoll < 75 ? 'xterm-256color' : termRoll < 88 ? 'screen-256color' : termRoll < 96 ? 'alacritty' : 'kitty'; // ── shell: macOS 默认 zsh(Catalina+);部分用 bash/fish ── const shellRoll = h('shell').readUInt8(0) % 100; const shell = shellRoll < 65 ? '/bin/zsh' : shellRoll < 82 ? '/usr/local/bin/zsh' : shellRoll < 93 ? '/bin/bash' : '/opt/homebrew/bin/fish'; // ── host.id: macOS IOPlatformUUID 格式(大写 UUID) ── const mid = h('machine-id'); const machineId = [ mid.slice(0,4).toString('hex').toUpperCase(), mid.slice(4,6).toString('hex').toUpperCase(), mid.slice(6,8).toString('hex').toUpperCase(), mid.slice(8,10).toString('hex').toUpperCase(), mid.slice(10,16).toString('hex').toUpperCase(), ].join('-'); // ── PID: macOS GUI 应用 PID 通常较小 ── const pid = 500 + Math.floor(Math.random() * 8000); // ── macOS 版本: 13(Ventura)/14(Sonoma)/15(Sequoia) ── const kb = h('kernel'); const macosMajor = 13 + (kb.readUInt8(0) % 3); const macosMinor = kb.readUInt8(1) % 8; const macosPatch = kb.readUInt8(2) % 5; // Darwin 内核: macOS 13=22.x, 14=23.x, 15=24.x const darwinMajor = 22 + (macosMajor - 13); const darwinMinor = kb.readUInt8(3) % 7; const darwinPatch = kb.readUInt8(4) % 5; const osVersion = `${macosMajor}.${macosMinor}.${macosPatch}`; // ── arch: Apple Silicon arm64 占 70%,Intel x64 占 30% ── const arch = h('arch').readUInt8(0) % 100 < 70 ? 'arm64' : 'x64'; // ── 可执行文件路径: macOS 常见安装位置 ── const pathRoll = h('execpath').readUInt8(0) % 100; const executablePath = pathRoll < 50 ? `/Users/${username}/.claude/local/claude` : pathRoll < 80 ? '/usr/local/bin/claude' : pathRoll < 95 ? `/Users/${username}/.local/bin/claude` : '/opt/homebrew/bin/claude'; return { hostname, username, terminal, shell, machineId, pid, arch, osType: 'Darwin', osVersion, kernelRelease: `${darwinMajor}.${darwinMinor}.${darwinPatch}`, serviceInstanceId: crypto.randomUUID(), executablePath, executableName: 'claude', command: 'claude', commandArgs: [], runtimeName: 'nodejs', runtimeVersion: FAKE_NODE_VERSION.replace('v', ''), ripgrepVersion: (() => { const rv = h('ripgrep'); return ['14.1.1','14.1.0','14.0.2','13.0.0','13.0.1','14.0.1','14.0.0'][rv.readUInt8(0) % 7]; })(), ripgrepPath: (() => { const rp = h('rgpath'); return [ '/opt/homebrew/bin/rg', '/usr/local/bin/rg', `/Users/${username}/.cargo/bin/rg`, '/usr/local/opt/ripgrep/bin/rg', ][rp.readUInt8(0) % 4]; })(), mcpServerCount: 1 + (h('mcp').readUInt8(0) % 5), mcpFailCount: h('mcp').readUInt8(1) % 3, }; } // ─── 遥测模拟 ──────────────────────────────────────────── // 每个 device_id 的会话状态 const sessionStates = new Map(); function getOrCreateSession(deviceId) { if (sessionStates.has(deviceId)) return sessionStates.get(deviceId); const hostId = generateHostIdentity(deviceId); const state = { sessionId: crypto.randomUUID(), deviceId, hostId, startTime: Date.now(), requestCount: 0, // 追踪 ripgrep 是否已上报 ripgrepReported: false, }; sessionStates.set(deviceId, state); return state; } function generateDeviceId(accountSeed) { return crypto.createHash('sha256').update(`device:${accountSeed}`).digest('hex'); } // ─── OTEL Resource Attributes (匹配 CLI 的 detectResources) ─── function buildEnvBlock(hostId) { const platformStr = 'darwin'; return { platform: platformStr, node_version: FAKE_NODE_VERSION, terminal: hostId.terminal, package_managers: 'npm,pnpm', runtimes: 'deno,node', is_running_with_bun: true, is_ci: false, is_claubbit: false, is_github_action: false, is_claude_code_action: false, is_claude_ai_auth: false, version: CLI_VERSION, arch: hostId.arch, is_claude_code_remote: false, deployment_environment: `unknown-${platformStr}`, is_conductor: false, version_base: CLI_VERSION, build_time: BUILD_TIME, is_local_agent_mode: false, vcs: 'git', platform_raw: platformStr, }; } function buildProcessMetrics(uptime) { // 模拟真实 CLI 的内存曲线:RSS 随 uptime 缓慢增长 const baseRss = 180_000_000 + Math.min(uptime * 50_000, 200_000_000); const rss = Math.floor(baseRss + Math.random() * 80_000_000); const heapTotal = Math.floor(rss * 0.6 + Math.random() * 10_000_000); const heapUsed = Math.floor(heapTotal * 0.5 + Math.random() * heapTotal * 0.3); return Buffer.from(JSON.stringify({ uptime, rss, heapTotal, heapUsed, external: 14_000_000 + Math.floor(Math.random() * 2_000_000), arrayBuffers: Math.floor(Math.random() * 200_000), constrainedMemory: 51539607552, cpuUsage: { user: Math.floor(uptime * 10_000 + Math.random() * 300_000), system: Math.floor(uptime * 2_000 + Math.random() * 80_000), }, cpuPercent: Math.random() * 200, })).toString('base64'); } function buildEvent(eventName, session, model, betas, extraData, timestampOverride) { const uptime = (Date.now() - session.startTime) / 1000; const processMetrics = buildProcessMetrics(uptime); // 缓存最近一次的 process metrics,供 DataDog 日志复用(保持两边一致) session._lastProcessMetrics = { uptime, raw: processMetrics }; const eventData = { event_name: eventName, client_timestamp: timestampOverride || new Date().toISOString(), model: model || 'claude-sonnet-4-6', session_id: session.sessionId, user_type: 'external', betas: betas || 'claude-code-20250219,interleaved-thinking-2025-05-14', env: buildEnvBlock(session.hostId), entrypoint: 'cli', is_interactive: true, client_type: 'cli', process: processMetrics, event_id: crypto.randomUUID(), device_id: session.deviceId, // 注意:不加 resource 字段 — event_logging/batch 是自定义端点, // OTEL resource attributes 由 CLI 通过单独的 OTLP exporter 发送,不在这里 }; // 合并额外字段(用于特定事件的附加数据) if (extraData) Object.assign(eventData, extraData); return { event_type: 'ClaudeCodeInternalEvent', event_data: eventData, }; } // 发送遥测到 api.anthropic.com/api/event_logging/batch function sendTelemetryEvents(events, session) { if (!TELEMETRY_ENABLED || events.length === 0) return; const body = JSON.stringify({ events }); const headers = { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'User-Agent': `claude-code/${CLI_VERSION}`, 'x-service-name': 'claude-code', 'Content-Length': Buffer.byteLength(body), }; // 注意:真实 CLI 2.1.84 的 event_logging/batch 不发 traceparent // traceparent 仅在 OTLP exporter(单独通道)中使用,不在这个端点 const opts = { hostname: 'api.anthropic.com', port: 443, path: '/api/event_logging/batch', method: 'POST', headers, timeout: 10000, }; const req = https.request(opts, (res) => { res.resume(); // drain log('debug', 'telemetry_sent', { status: res.statusCode, events: events.length }); }); req.on('error', (err) => { log('debug', 'telemetry_error', { error: err.message }); }); req.on('timeout', () => req.destroy()); req.end(body); } // 发送 DataDog 日志 function sendDatadogLog(eventName, session, model) { if (!TELEMETRY_ENABLED) return; const hostId = session.hostId; const uptime = (Date.now() - session.startTime) / 1000; // 复用 Anthropic 事件侧缓存的 process metrics(保持两边数值一致) // 如果没有缓存(首次调用),现场生成 let pm; if (session._lastProcessMetrics && Math.abs(session._lastProcessMetrics.uptime - uptime) < 2) { pm = JSON.parse(Buffer.from(session._lastProcessMetrics.raw, 'base64').toString()); } else { const baseRss = 180_000_000 + Math.min(uptime * 50_000, 200_000_000); const rss = Math.floor(baseRss + Math.random() * 80_000_000); const heapTotal = Math.floor(rss * 0.6 + Math.random() * 10_000_000); const heapUsed = Math.floor(heapTotal * 0.5 + Math.random() * heapTotal * 0.3); pm = { uptime, rss, heapTotal, heapUsed, external: 14_000_000 + Math.floor(Math.random() * 2_000_000), arrayBuffers: Math.floor(Math.random() * 10_000), constrainedMemory: 0, cpuUsage: { user: Math.floor(uptime * 10_000 + Math.random() * 300_000), system: Math.floor(uptime * 2_000 + Math.random() * 80_000), }, }; } const entry = { ddsource: 'nodejs', ddtags: `event:${eventName},arch:${hostId.arch},client_type:cli,model:${model || 'claude-sonnet-4-6'},platform:darwin,user_type:external,version:${CLI_VERSION},version_base:${CLI_VERSION}`, message: eventName, service: 'claude-code', hostname: hostId.hostname, env: 'external', model: model || 'claude-sonnet-4-6', session_id: session.sessionId, user_type: 'external', entrypoint: 'cli', is_interactive: 'true', client_type: 'cli', process_metrics: pm, platform: 'darwin', platform_raw: 'darwin', arch: hostId.arch, node_version: FAKE_NODE_VERSION, version: CLI_VERSION, version_base: CLI_VERSION, build_time: BUILD_TIME, deployment_environment: 'unknown-darwin', vcs: 'git', }; const body = JSON.stringify([entry]); const opts = { hostname: 'http-intake.logs.us5.datadoghq.com', port: 443, path: '/api/v2/logs', method: 'POST', headers: { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'User-Agent': 'axios/1.13.6', 'dd-api-key': DD_API_KEY, 'Content-Length': Buffer.byteLength(body), }, timeout: 10000, }; const req = https.request(opts, (res) => { res.resume(); }); req.on('error', () => {}); req.on('timeout', () => req.destroy()); req.end(body); } // 请求前发遥测(模拟 CLI 启动 + 初始化事件) function emitPreRequestTelemetry(reqHeaders, body) { const accountSeed = reqHeaders['x-forwarded-host'] || 'default'; const deviceId = generateDeviceId(accountSeed + ':' + (reqHeaders['authorization'] || '').slice(-16)); const session = getOrCreateSession(deviceId); session.requestCount++; // 从请求体解析真实 model let model = 'claude-sonnet-4-6'; try { const parsed = JSON.parse(body.toString()); if (parsed.model) model = parsed.model; } catch (_) {} const betas = reqHeaders['anthropic-beta'] || 'claude-code-20250219,context-1m-2025-08-07,interleaved-thinking-2025-05-14,redact-thinking-2026-02-12,context-management-2025-06-27,prompt-caching-scope-2026-01-05,effort-2025-11-24,token-efficient-tools-2026-03-28,advisor-tool-2026-03-01'; // 首次请求:发完整启动事件序列(匹配真实 CLI 的时序) if (session.requestCount === 1) { const hostId = session.hostId; // 生成递增的时间戳,模拟真实 CLI 启动流程的时间差 const baseTime = Date.now(); const ts = (offsetMs) => new Date(baseTime + offsetMs).toISOString(); // 第一批:启动 + 工具检测 + MCP 连接事件 const batch1 = [ buildEvent('tengu_started', session, model, betas, null, ts(0)), buildEvent('tengu_init', session, model, betas, null, ts(80 + Math.floor(Math.random() * 120))), // tengu_ripgrep_availability — CLI 必发的工具检测事件,版本/路径按账号不同 buildEvent('tengu_ripgrep_availability', session, model, betas, { ripgrep_available: true, ripgrep_version: hostId.ripgrepVersion, ripgrep_path: hostId.ripgrepPath, }, ts(200 + Math.floor(Math.random() * 150))), ]; // MCP 连接事件:数量按账号不同(真实用户配置的 MCP server 数量差异很大) let mcpOffset = 400; const mcpSuccessCount = hostId.mcpServerCount - hostId.mcpFailCount; for (let i = 0; i < hostId.mcpFailCount; i++) { mcpOffset += 100 + Math.floor(Math.random() * 300); batch1.push(buildEvent('tengu_mcp_server_connection_failed', session, model, betas, null, ts(mcpOffset))); } for (let i = 0; i < mcpSuccessCount; i++) { mcpOffset += 200 + Math.floor(Math.random() * 500); batch1.push(buildEvent('tengu_mcp_server_connection_succeeded', session, model, betas, null, ts(mcpOffset))); } session.ripgrepReported = true; sendTelemetryEvents(batch1, session); sendDatadogLog('tengu_started', session, model); sendDatadogLog('tengu_init', session, model); // 第二批延迟发送(真实 CLI 间隔约 30 秒) setTimeout(() => { const batch2 = [ buildEvent('tengu_session_init', session, model, betas), buildEvent('tengu_context_loaded', session, model, betas), ]; sendTelemetryEvents(batch2, session); }, 25000 + Math.floor(Math.random() * 10000)); } // 每次请求:发 request_started const events = [ buildEvent('tengu_api_request_started', session, model, betas), ]; sendTelemetryEvents(events, session); } // 请求后发遥测 function emitPostRequestTelemetry(reqHeaders, statusCode, body) { const accountSeed = reqHeaders['x-forwarded-host'] || 'default'; const deviceId = generateDeviceId(accountSeed + ':' + (reqHeaders['authorization'] || '').slice(-16)); const session = getOrCreateSession(deviceId); let model = 'claude-sonnet-4-6'; try { const parsed = JSON.parse(body.toString()); if (parsed.model) model = parsed.model; } catch (_) {} const betas = reqHeaders['anthropic-beta'] || 'claude-code-20250219,context-1m-2025-08-07,interleaved-thinking-2025-05-14,redact-thinking-2026-02-12,context-management-2025-06-27,prompt-caching-scope-2026-01-05,effort-2025-11-24,token-efficient-tools-2026-03-28,advisor-tool-2026-03-01'; // 请求完成事件 const events = [ buildEvent('tengu_api_request_completed', session, model, betas), buildEvent('tengu_conversation_turn_completed', session, model, betas), ]; sendTelemetryEvents(events, session); sendDatadogLog('tengu_api_request_completed', session, model); // 模拟错误遥测(低概率,匹配 TelemetrySafeError) if (statusCode >= 400 && Math.random() < 0.5) { const errorEvent = buildEvent('tengu_api_request_error', session, model, betas, { error_type: 'TelemetrySafeError', error_code: statusCode, error_message: statusCode === 429 ? 'rate_limit_exceeded' : statusCode === 529 ? 'overloaded' : statusCode >= 500 ? 'server_error' : 'client_error', }); sendTelemetryEvents([errorEvent], session); } // 随机发额外事件(仅使用已知的真实 CLI 事件名) if (Math.random() < 0.3) { setTimeout(() => { const extra = [ buildEvent('tengu_tool_use_completed', session, model, betas), ]; sendTelemetryEvents(extra, session); }, 2000 + Math.floor(Math.random() * 5000)); } } // ─── H2 session 管理 ──────────────────────────────────── function getOrCreateH2Session(host) { const existing = h2Sessions.get(host); if (existing && !existing.closed && !existing.destroyed) return existing; if (existing) { try { existing.close(); } catch (_) {} } const session = http2.connect(`https://${host}`); session.on('error', (err) => { log('warn', 'h2_session_error', { host, error: err.message }); h2Sessions.delete(host); try { session.close(); } catch (_) {} }); session.on('close', () => h2Sessions.delete(host)); session.on('goaway', () => { h2Sessions.delete(host); try { session.close(); } catch (_) {} }); session.setTimeout(IDLE_TIMEOUT, () => { session.close(); h2Sessions.delete(host); }); h2Sessions.set(host, session); return session; } function waitForConnect(session) { if (session.connected) return Promise.resolve(); return new Promise((resolve, reject) => { session.once('connect', resolve); session.once('error', reject); const t = setTimeout(() => reject(new Error('h2 connect timeout')), CONNECT_TIMEOUT); session.once('connect', () => clearTimeout(t)); }); } // ─── CONNECT 隧道 ──────────────────────────────────────── function connectViaProxy(proxyUrl, targetHost, targetPort) { return new Promise((resolve, reject) => { const proxy = new URL(proxyUrl); const conn = net.connect(parseInt(proxy.port || '80', 10), proxy.hostname, () => { const auth = proxy.username ? `Proxy-Authorization: Basic ${Buffer.from(`${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password || '')}`).toString('base64')}\r\n` : ''; conn.write(`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n${auth}\r\n`); }); conn.once('error', reject); conn.setTimeout(CONNECT_TIMEOUT, () => conn.destroy(new Error('CONNECT timeout'))); let buf = ''; conn.on('data', function onData(chunk) { buf += chunk.toString(); const idx = buf.indexOf('\r\n\r\n'); if (idx === -1) return; conn.removeListener('data', onData); const code = parseInt(buf.split(' ')[1], 10); if (code === 200) { conn.setTimeout(0); resolve(conn); } else { conn.destroy(); reject(new Error(`CONNECT ${code}`)); } }); }); } // ─── 收集请求体 ────────────────────────────────────────── function collectBody(req) { return new Promise((resolve) => { const chunks = []; req.on('data', (c) => chunks.push(c)); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', () => resolve(Buffer.concat(chunks))); }); } // ─── H1 代理 ───────────────────────────────────────────── function sendViaH1(targetHost, method, path, reqHeaders, body, res, savedHeaders) { return new Promise((resolve) => { const headers = { ...reqHeaders, host: targetHost }; ['x-forwarded-host', 'connection', 'keep-alive', 'proxy-connection', 'transfer-encoding'].forEach(h => delete headers[h]); if (body.length > 0) headers['content-length'] = String(body.length); const opts = { hostname: targetHost, port: 443, path, method, headers, servername: targetHost, timeout: CONNECT_TIMEOUT }; const startTime = Date.now(); const finish = (requestOpts) => { const proxyReq = https.request(requestOpts); proxyReq.on('response', (proxyRes) => { log('info', 'proxy_response', { host: targetHost, status: proxyRes.statusCode, path, proto: 'h1' }); const rh = { ...proxyRes.headers }; delete rh['connection']; delete rh['keep-alive']; res.writeHead(proxyRes.statusCode, rh); proxyRes.pipe(res, { end: true }); // 请求完成后发遥测 if (path.includes('/v1/messages') && savedHeaders) { emitPostRequestTelemetry(savedHeaders, proxyRes.statusCode, body); } resolve('ok'); }); proxyReq.on('error', (err) => { if (err.message === 'socket hang up' && (Date.now() - startTime) < 2000) { log('info', 'h1_rejected_switching_to_h2', { host: targetHost }); h2Hosts.add(targetHost); sendViaH2(targetHost, method, path, reqHeaders, body, res, savedHeaders).then(() => resolve('h2')); return; } log('error', 'h1_error', { error: err.message, host: targetHost, path }); if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: 'upstream_connection_error' })); } resolve('error'); }); proxyReq.on('timeout', () => proxyReq.destroy(new Error('timeout'))); proxyReq.end(body); }; // 动态上游代理:优先使用 per-request 的 X-Upstream-Proxy,回退到全局 UPSTREAM_PROXY const upstreamProxy = reqHeaders['x-upstream-proxy'] || UPSTREAM_PROXY; // 清除内部 header,不传给上游 delete headers['x-upstream-proxy']; if (upstreamProxy) { connectViaProxy(upstreamProxy, targetHost, 443) .then((socket) => { opts.socket = socket; opts.agent = false; finish(opts); }) .catch((err) => { log('error', 'tunnel_failed', { error: err.message, proxy: redactProxyURL(upstreamProxy) }); if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: 'upstream_connection_error' })); } resolve('error'); }); } else { finish(opts); } }); } // ─── H2 代理 ───────────────────────────────────────────── async function sendViaH2(targetHost, method, path, reqHeaders, body, res, savedHeaders) { try { const session = getOrCreateH2Session(targetHost); await waitForConnect(session); const headers = {}; const skip = new Set(['host','connection','keep-alive','proxy-connection','transfer-encoding','upgrade','x-forwarded-host','http2-settings']); for (const [k, v] of Object.entries(reqHeaders)) { if (!skip.has(k.toLowerCase())) headers[k] = v; } headers[':method'] = method; headers[':path'] = path; headers[':authority'] = targetHost; headers[':scheme'] = 'https'; if (body.length > 0) headers['content-length'] = String(body.length); const stream = session.request(headers); let responded = false; stream.on('response', (h2h) => { responded = true; const status = h2h[':status'] || 502; const rh = {}; for (const [k, v] of Object.entries(h2h)) { if (!k.startsWith(':')) rh[k] = v; } log('info', 'proxy_response', { host: targetHost, status, path, proto: 'h2' }); res.writeHead(status, rh); stream.on('data', (c) => res.write(c)); stream.on('end', () => res.end()); if (path.includes('/v1/messages') && savedHeaders) { emitPostRequestTelemetry(savedHeaders, status); } }); stream.on('error', (err) => { if (err.message && err.message.includes('NGHTTP2')) { h2Sessions.delete(targetHost); try { session.close(); } catch (_) {} } if (responded) { if (!res.writableEnded) res.end(); return; } log('error', 'h2_error', { error: err.message, host: targetHost, path }); if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: 'upstream_connection_error' })); } }); stream.on('close', () => { if (!responded && !res.headersSent) { log('warn', 'h2_no_response', { host: targetHost, path }); res.writeHead(502); res.end(JSON.stringify({ error: 'upstream_connection_error' })); } else if (!res.writableEnded) { res.end(); } }); stream.setTimeout(CONNECT_TIMEOUT, () => stream.close()); stream.end(body); } catch (err) { log('error', 'h2_exception', { error: err.message, host: targetHost }); h2Sessions.delete(targetHost); if (!res.headersSent) { res.writeHead(502); res.end(JSON.stringify({ error: 'upstream_connection_error' })); } } } // ─── 请求入口 ───────────────────────────────────────────── async function proxyRequest(req, res) { const targetHost = req.headers['x-forwarded-host'] || UPSTREAM_HOST; log('info', 'proxy_request', { host: targetHost, method: req.method, path: req.url }); // 保存原始 headers 用于遥测 const savedHeaders = { ...req.headers }; const body = await collectBody(req); // 请求前发遥测(仅 /v1/messages 请求) if (req.url.includes('/v1/messages') && TELEMETRY_ENABLED) { emitPreRequestTelemetry(savedHeaders, body); } // ── Jitter 注入 ────────────────────────────────────────────────── // 模拟人类编码间歇:80% 快速响应(80-300ms),20% 慢速思考(400-1200ms) // 使用 -log(rand) 指数衰减使延迟尾部更接近真实键盘输入节奏 const jitterMs = (() => { if (Math.random() < 0.80) { return Math.floor(80 + (-Math.log(Math.random()) * 90)); // 快:~80-300ms } return Math.floor(400 + Math.random() * 800); // 慢:400-1200ms })(); await new Promise(r => setTimeout(r, jitterMs)); // ── H2 优先策略 ────────────────────────────────────────────────── // Anthropic/Google API 均支持 HTTP/2。 // 直接走 H2 = Node.js 原生帧顺序,与真实 CLI 完全一致。 // 其他 host 维持原有 H1→H2 自动切换逻辑。 const H2_PREFER_HOSTS = new Set([ 'api.anthropic.com', 'cloudaicompanion.googleapis.com', 'generativelanguage.googleapis.com', 'cloudcode-pa.googleapis.com', 'daily-cloudcode-pa.googleapis.com', ]); if (H2_PREFER_HOSTS.has(targetHost) || h2Hosts.has(targetHost)) { await sendViaH2(targetHost, req.method, req.url, req.headers, body, res, savedHeaders); } else { await sendViaH1(targetHost, req.method, req.url, req.headers, body, res, savedHeaders); } } // ─── HTTP 服务器 ───────────────────────────────────────── const server = http.createServer((req, res) => { if (req.url === HEALTH_PATH) { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', node: process.version, openssl: process.versions.openssl, uptime: process.uptime(), h2Hosts: [...h2Hosts], telemetry: TELEMETRY_ENABLED, sessions: sessionStates.size, })); return; } proxyRequest(req, res).catch((err) => { log('error', 'unhandled', { error: err.message }); if (!res.headersSent) { res.writeHead(500); res.end('internal error'); } }); }); server.timeout = 0; server.keepAliveTimeout = IDLE_TIMEOUT; server.headersTimeout = 60000; server.listen(LISTEN_PORT, LISTEN_HOST, () => { log('info', 'node-tls-proxy started', { listen: `${LISTEN_HOST}:${LISTEN_PORT}`, node: process.version, openssl: process.versions.openssl, telemetry: TELEMETRY_ENABLED, }); }); // 定期清理过期 session(1 小时无活动) setInterval(() => { const now = Date.now(); for (const [id, state] of sessionStates) { if (now - state.startTime > 3600_000) sessionStates.delete(id); } }, 300_000); let stopping = false; function shutdown(sig) { if (stopping) return; stopping = true; for (const s of h2Sessions.values()) try { s.close(); } catch (_) {} h2Sessions.clear(); server.close(() => process.exit(0)); setTimeout(() => process.exit(1), 5000); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); process.on('uncaughtException', (e) => log('error', 'uncaught', { error: e.message })); process.on('unhandledRejection', (r) => log('error', 'rejection', { error: String(r) }));