@@ -73,6 +73,7 @@ export interface ApiServerConfig {
7373 maxPayloadSizeMb : number
7474 maxConnectionsPerIp : number
7575 mcpEnabled : boolean
76+ cloudflaredPath : string
7677}
7778
7879// Request history entry
@@ -951,6 +952,10 @@ export class CopilotApiGateway implements vscode.Disposable {
951952 await this . updateServerConfig ( { maxConnectionsPerIp : normalized } ) ;
952953 }
953954
955+ public async setCloudflaredPath ( path : string ) : Promise < void > {
956+ await this . updateServerConfig ( { cloudflaredPath : path } ) ;
957+ }
958+
954959 public async setMaxConcurrency ( limit : number ) : Promise < void > {
955960 const normalized = Number . isFinite ( limit ) ? Math . max ( 1 , Math . floor ( limit ) ) : 4 ;
956961 await this . updateServerConfig ( { maxConcurrentRequests : normalized } ) ;
@@ -1005,25 +1010,52 @@ export class CopilotApiGateway implements vscode.Disposable {
10051010 fs . mkdirSync ( globalStoragePath , { recursive : true } ) ;
10061011 }
10071012
1008- const cloudflaredBin = path . join (
1009- globalStoragePath ,
1010- process . platform === 'win32' ? 'cloudflared.exe' : 'cloudflared'
1011- ) ;
1013+ let cloudflaredBin = '' ;
1014+ const customPath = this . config . cloudflaredPath ;
10121015
1013- // Download the binary if it doesn't exist
1014- if ( ! fs . existsSync ( cloudflaredBin ) ) {
1015- this . logInfo ( 'Cloudflared binary not found, downloading...' ) ;
1016+ // 1. Check custom configured path
1017+ if ( customPath && fs . existsSync ( customPath ) ) {
1018+ this . logInfo ( `Using custom cloudflared path from configuration: ${ customPath } ` ) ;
1019+ cloudflaredBin = customPath ;
1020+ }
1021+ // 2. Check system PATH
1022+ else {
1023+ const { execSync } = await import ( 'child_process' ) ;
10161024 try {
1017- await install ( cloudflaredBin ) ;
1018- this . logInfo ( 'Cloudflared binary downloaded successfully' ) ;
1019- } catch ( downloadError ) {
1020- const errMsg = downloadError instanceof Error ? downloadError . message : String ( downloadError ) ;
1021- this . logError ( 'Failed to download cloudflared binary' , downloadError ) ;
1022- return { success : false , error : `Failed to download cloudflared: ${ errMsg } . Check your internet connection.` } ;
1025+ const sysPath = execSync ( process . platform === 'win32' ? 'where cloudflared' : 'which cloudflared' ) . toString ( ) . split ( '\n' ) [ 0 ] . trim ( ) ;
1026+ if ( sysPath && fs . existsSync ( sysPath ) ) {
1027+ this . logInfo ( `Found cloudflared in system PATH: ${ sysPath } ` ) ;
1028+ cloudflaredBin = sysPath ;
1029+ }
1030+ } catch ( e ) {
1031+ // cloudflared not in PATH
1032+ }
1033+ }
1034+
1035+ // 3. Fallback to downloading or using cached binary
1036+ if ( ! cloudflaredBin ) {
1037+ cloudflaredBin = path . join (
1038+ globalStoragePath ,
1039+ process . platform === 'win32' ? 'cloudflared.exe' : 'cloudflared'
1040+ ) ;
1041+
1042+ if ( ! fs . existsSync ( cloudflaredBin ) ) {
1043+ this . logInfo ( 'Cloudflared binary not found locally or in PATH, downloading...' ) ;
1044+ try {
1045+ await install ( cloudflaredBin ) ;
1046+ this . logInfo ( 'Cloudflared binary downloaded successfully' ) ;
1047+ } catch ( downloadError ) {
1048+ const errMsg = downloadError instanceof Error ? downloadError . message : String ( downloadError ) ;
1049+ this . logError ( 'Failed to download cloudflared binary' , downloadError ) ;
1050+ return { success : false , error : `Failed to download cloudflared: ${ errMsg } . Check your internet connection.` } ;
1051+ }
1052+ } else {
1053+ this . logInfo ( `Using cached cloudflared binary: ${ cloudflaredBin } ` ) ;
10231054 }
1055+ } else {
1056+ this . logInfo ( `Using chosen cloudflared binary: ${ cloudflaredBin } ` ) ;
10241057 }
10251058
1026- this . logInfo ( `Using cloudflared binary: ${ cloudflaredBin } ` ) ;
10271059 use ( cloudflaredBin ) ;
10281060
10291061 const localUrl = `http://${ this . config . host === '0.0.0.0' ? '127.0.0.1' : this . config . host } :${ this . config . port } ` ;
@@ -5404,6 +5436,9 @@ export class CopilotApiGateway implements vscode.Disposable {
54045436 if ( patch . maxConnectionsPerIp !== undefined ) {
54055437 updates . push ( Promise . resolve ( config . update ( 'server.maxConnectionsPerIp' , patch . maxConnectionsPerIp , vscode . ConfigurationTarget . Global ) ) ) ;
54065438 }
5439+ if ( patch . cloudflaredPath !== undefined ) {
5440+ updates . push ( Promise . resolve ( config . update ( 'tunnel.cloudflaredPath' , patch . cloudflaredPath , vscode . ConfigurationTarget . Global ) ) ) ;
5441+ }
54075442 if ( patch . redactionPatterns !== undefined ) {
54085443 updates . push ( Promise . resolve ( config . update ( 'server.redactionPatterns' , patch . redactionPatterns , vscode . ConfigurationTarget . Global ) ) ) ;
54095444 }
@@ -5633,12 +5668,13 @@ function getServerConfig(): ApiServerConfig {
56335668 const maxPayloadSizeMb = configuration . get < number > ( 'server.maxPayloadSizeMb' , 1 ) ;
56345669 const maxConnectionsPerIp = configuration . get < number > ( 'server.maxConnectionsPerIp' , 10 ) ;
56355670 const mcpEnabled = vscode . workspace . getConfiguration ( 'githubCopilotApi.mcp' ) . get < boolean > ( 'enabled' , true ) ;
5671+ const cloudflaredPath = vscode . workspace . getConfiguration ( 'githubCopilotApi.tunnel' ) . get < string > ( 'cloudflaredPath' , '' ) . trim ( ) ;
56365672
56375673 return {
56385674 enabled, enableHttp, enableWebSocket, enableHttps, tlsCertPath, tlsKeyPath, host, port, maxConcurrentRequests,
56395675 defaultModel, apiKey, enableLogging, rateLimitPerMinute, defaultSystemPrompt,
56405676 redactionPatterns, ipAllowlist, requestTimeoutSeconds, maxPayloadSizeMb, maxConnectionsPerIp,
5641- mcpEnabled
5677+ mcpEnabled, cloudflaredPath
56425678 } ;
56435679}
56445680
0 commit comments