diff --git a/package.json b/package.json index fca8547f..887b58c5 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "fastify-type-provider-zod": "4.0.2", "http-proxy": "^1.18.1", "http-proxy-middleware": "^3.0.5", + "https-proxy-agent": "^7.0.6", "ink": "^6.5.1", "open": "^10.2.0", "ps-list": "^8.1.1", diff --git a/src/api/api.ts b/src/api/api.ts index fc381180..ae37296a 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -9,6 +9,7 @@ import { configuration } from '@/configuration'; import chalk from 'chalk'; import { Credentials } from '@/persistence'; import { connectionState, isNetworkError } from '@/utils/serverConnectionErrors'; +import { createProxyAgent } from '@/utils/proxy'; export class ApiClient { @@ -70,7 +71,9 @@ export class ApiClient { 'Authorization': `Bearer ${this.credential.token}`, 'Content-Type': 'application/json' }, - timeout: 60000 // 1 minute timeout for very bad network connections + timeout: 60000, // 1 minute timeout for very bad network connections + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() } ) @@ -190,7 +193,9 @@ export class ApiClient { 'Authorization': `Bearer ${this.credential.token}`, 'Content-Type': 'application/json' }, - timeout: 60000 // 1 minute timeout for very bad network connections + timeout: 60000, // 1 minute timeout for very bad network connections + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() } ); @@ -301,7 +306,9 @@ export class ApiClient { 'Authorization': `Bearer ${this.credential.token}`, 'Content-Type': 'application/json' }, - timeout: 5000 + timeout: 5000, + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() } ); @@ -329,7 +336,9 @@ export class ApiClient { 'Authorization': `Bearer ${this.credential.token}`, 'Content-Type': 'application/json' }, - timeout: 5000 + timeout: 5000, + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() } ); diff --git a/src/api/apiMachine.ts b/src/api/apiMachine.ts index b8e2b570..235953d7 100644 --- a/src/api/apiMachine.ts +++ b/src/api/apiMachine.ts @@ -11,6 +11,7 @@ import { registerCommonHandlers, SpawnSessionOptions, SpawnSessionResult } from import { encodeBase64, decodeBase64, encrypt, decrypt } from './encryption'; import { backoff } from '@/utils/time'; import { RpcHandlerManager } from './rpc/RpcHandlerManager'; +import { createProxyAgent } from '@/utils/proxy'; interface ServerToDaemonEvents { update: (data: Update) => void; @@ -227,7 +228,8 @@ export class ApiMachineClient { path: '/v1/updates', reconnection: true, reconnectionDelay: 1000, - reconnectionDelayMax: 5000 + reconnectionDelayMax: 5000, + agent: createProxyAgent() as any }); this.socket.on('connect', () => { diff --git a/src/api/apiSession.ts b/src/api/apiSession.ts index 187ce5b8..95d94c41 100644 --- a/src/api/apiSession.ts +++ b/src/api/apiSession.ts @@ -10,6 +10,7 @@ import { randomUUID } from 'node:crypto'; import { AsyncLock } from '@/utils/lock'; import { RpcHandlerManager } from './rpc/RpcHandlerManager'; import { registerCommonHandlers } from '../modules/common/registerCommonHandlers'; +import { createProxyAgent } from '@/utils/proxy'; /** * ACP (Agent Communication Protocol) message data types. @@ -91,7 +92,8 @@ export class ApiSessionClient extends EventEmitter { reconnectionDelayMax: 5000, transports: ['websocket'], withCredentials: true, - autoConnect: false + autoConnect: false, + agent: createProxyAgent() as any }); // diff --git a/src/api/auth.ts b/src/api/auth.ts index 642cb8a2..5f54c8d7 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { encodeBase64, encodeBase64Url, authChallenge } from './encryption'; import { configuration } from '@/configuration'; +import { createProxyAgent } from '@/utils/proxy'; /** * Note: This function is deprecated. Use readPrivateKey/writePrivateKey from persistence module instead. @@ -18,11 +19,14 @@ export async function getOrCreateSecretKey(): Promise { */ export async function authGetToken(secret: Uint8Array): Promise { const { challenge, publicKey, signature } = authChallenge(secret); - + const response = await axios.post(`${configuration.serverUrl}/v1/auth`, { challenge: encodeBase64(challenge), publicKey: encodeBase64(publicKey), signature: encodeBase64(signature) + }, { + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() }); if (!response.data.success || !response.data.token) { diff --git a/src/api/pushNotifications.ts b/src/api/pushNotifications.ts index a194a861..c3464745 100644 --- a/src/api/pushNotifications.ts +++ b/src/api/pushNotifications.ts @@ -1,6 +1,7 @@ import axios from 'axios' import { logger } from '@/ui/logger' import { Expo, ExpoPushMessage } from 'expo-server-sdk' +import { createProxyAgent } from '@/utils/proxy' export interface PushToken { id: string @@ -32,7 +33,9 @@ export class PushNotificationClient { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' - } + }, + httpAgent: createProxyAgent(), + httpsAgent: createProxyAgent() } ) diff --git a/src/claude/runClaude.ts b/src/claude/runClaude.ts index bcdd74fd..0f44e22d 100644 --- a/src/claude/runClaude.ts +++ b/src/claude/runClaude.ts @@ -27,6 +27,7 @@ import { startOfflineReconnection, connectionState } from '@/utils/serverConnect import { claudeLocal } from '@/claude/claudeLocal'; import { createSessionScanner } from '@/claude/utils/sessionScanner'; import { Session } from './session'; +import { setAgentType } from '@/utils/proxy'; /** JavaScript runtime to use for spawning Claude Code */ export type JsRuntime = 'node' | 'bun' @@ -44,6 +45,9 @@ export interface StartOptions { } export async function runClaude(credentials: Credentials, options: StartOptions = {}): Promise { + // Set agent type for proxy configuration + setAgentType('claude'); + logger.debug(`[CLAUDE] ===== CLAUDE MODE STARTING =====`); logger.debug(`[CLAUDE] This is the Claude agent, NOT Gemini`); diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts new file mode 100644 index 00000000..2a8ab028 --- /dev/null +++ b/src/utils/proxy.ts @@ -0,0 +1,63 @@ +/** + * HTTP proxy configuration utilities + * + * Reads proxy settings from: + * 1. Environment variables (http_proxy, https_proxy, HTTP_PROXY, HTTPS_PROXY) + * 2. Agent-specific config files: + * - Claude: ~/.claude/settings.json (env field) + * - Gemini: TODO - add support + * - Codex: TODO - add support + */ + +import { HttpsProxyAgent } from 'https-proxy-agent'; + +type AgentType = 'claude' | 'gemini' | 'codex' | null; +let currentAgentType: AgentType = null; +let claudeProxyCache: { url: string | undefined; loaded: boolean } = { url: undefined, loaded: false }; + +/** + * Set the current agent type (call this at startup) + */ +export function setAgentType(type: AgentType): void { + currentAgentType = type; +} + +/** + * Read proxy URL from Claude Code settings + * Only used when agent type is 'claude' + */ +function getClaudeProxyUrl(): string | undefined { + if (currentAgentType !== 'claude') return undefined; + if (claudeProxyCache.loaded) return claudeProxyCache.url; + claudeProxyCache.loaded = true; + + try { + // Lazy import to avoid circular dependency + const { readClaudeSettings } = require('@/claude/utils/claudeSettings'); + const settings = readClaudeSettings(); + claudeProxyCache.url = settings?.env?.HTTPS_PROXY || settings?.env?.HTTP_PROXY || + settings?.env?.https_proxy || settings?.env?.http_proxy; + return claudeProxyCache.url; + } catch { + return undefined; + } +} + +/** + * Get proxy URL from environment variables or Claude Code settings + * Priority: env vars > Claude Code settings (only for claude agent) + */ +export function getProxyUrl(): string | undefined { + return process.env.https_proxy || process.env.HTTPS_PROXY || + process.env.http_proxy || process.env.HTTP_PROXY || + getClaudeProxyUrl(); +} + +/** + * Create an HttpsProxyAgent if proxy is configured + */ +export function createProxyAgent(): HttpsProxyAgent | undefined { + const proxyUrl = getProxyUrl(); + if (!proxyUrl) return undefined; + return new HttpsProxyAgent(proxyUrl); +}