1- import axios from "axios"
1+ import axios , { AxiosRequestConfig } from "axios"
22import * as yaml from "yaml"
33import { z } from "zod"
44import { getRooCodeApiUrl } from "@roo-code/cloud"
@@ -23,6 +23,47 @@ export class RemoteConfigLoader {
2323 this . apiBaseUrl = getRooCodeApiUrl ( )
2424 }
2525
26+ private getProxyConfig ( ) : Partial < AxiosRequestConfig > {
27+ // Check for proxy environment variables
28+ const httpProxy = process . env . HTTP_PROXY || process . env . http_proxy
29+ const httpsProxy = process . env . HTTPS_PROXY || process . env . https_proxy
30+ const noProxy = process . env . NO_PROXY || process . env . no_proxy
31+
32+ // Check if the API URL should bypass proxy
33+ if ( noProxy ) {
34+ const noProxyList = noProxy . split ( "," ) . map ( ( host ) => host . trim ( ) )
35+ const apiUrl = new URL ( this . apiBaseUrl )
36+ const shouldBypassProxy = noProxyList . some ( ( host ) => {
37+ if ( host === "*" ) return true
38+ if ( host . startsWith ( "." ) ) return apiUrl . hostname . endsWith ( host )
39+ return apiUrl . hostname === host || apiUrl . hostname . endsWith ( "." + host )
40+ } )
41+ if ( shouldBypassProxy ) return { }
42+ }
43+
44+ // Use axios built-in proxy support
45+ const apiUrl = new URL ( this . apiBaseUrl )
46+ const proxyUrl = apiUrl . protocol === "https:" ? httpsProxy : httpProxy
47+
48+ if ( proxyUrl ) {
49+ try {
50+ const proxy = new URL ( proxyUrl )
51+ return {
52+ proxy : {
53+ protocol : proxy . protocol . slice ( 0 , - 1 ) , // Remove trailing ':'
54+ host : proxy . hostname ,
55+ port : parseInt ( proxy . port ) || ( proxy . protocol === "https:" ? 443 : 80 ) ,
56+ ...( proxy . username && { auth : { username : proxy . username , password : proxy . password || "" } } ) ,
57+ } ,
58+ }
59+ } catch ( error ) {
60+ console . warn ( "Invalid proxy URL format:" , proxyUrl )
61+ }
62+ }
63+
64+ return { }
65+ }
66+
2667 async loadAllItems ( hideMarketplaceMcps = false ) : Promise < MarketplaceItem [ ] > {
2768 const items : MarketplaceItem [ ] = [ ]
2869
@@ -80,25 +121,146 @@ export class RemoteConfigLoader {
80121
81122 for ( let i = 0 ; i < maxRetries ; i ++ ) {
82123 try {
83- const response = await axios . get ( url , {
84- timeout : 10000 , // 10 second timeout
124+ const proxyConfig = this . getProxyConfig ( )
125+ const config : AxiosRequestConfig = {
126+ timeout : 15000 , // Increased timeout for corporate networks
85127 headers : {
86128 Accept : "application/json" ,
87129 "Content-Type" : "application/json" ,
130+ "User-Agent" : "Roo-Code-Extension/1.0" ,
88131 } ,
89- } )
132+ // Add proxy configuration
133+ ...proxyConfig ,
134+ // Additional network resilience options
135+ maxRedirects : 5 ,
136+ validateStatus : ( status ) => status < 500 , // Accept 4xx errors but retry 5xx
137+ }
138+
139+ const response = await axios . get ( url , config )
140+
141+ // Handle non-2xx responses gracefully
142+ if ( response . status >= 400 ) {
143+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` )
144+ }
145+
90146 return response . data as T
91147 } catch ( error ) {
92148 lastError = error as Error
93- if ( i < maxRetries - 1 ) {
94- // Exponential backoff: 1s, 2s, 4s
95- const delay = Math . pow ( 2 , i ) * 1000
149+
150+ // Enhanced error categorization for better retry logic
151+ const isNetworkError = this . isNetworkError ( error )
152+ const isRetryableError = this . isRetryableError ( error )
153+
154+ // Don't retry on non-retryable errors (like 404, 401, etc.)
155+ if ( ! isRetryableError && i === 0 ) {
156+ // For non-retryable errors, throw immediately with enhanced message
157+ throw this . enhanceError ( error as Error )
158+ }
159+
160+ if ( i < maxRetries - 1 && ( isNetworkError || isRetryableError ) ) {
161+ // Progressive backoff: 2s, 4s, 8s for network issues
162+ const baseDelay = isNetworkError ? 2000 : 1000
163+ const delay = Math . pow ( 2 , i ) * baseDelay
96164 await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) )
97165 }
98166 }
99167 }
100168
101- throw lastError !
169+ throw this . enhanceError ( lastError ! )
170+ }
171+
172+ private isNetworkError ( error : any ) : boolean {
173+ if ( ! error ) return false
174+
175+ const networkErrorCodes = [
176+ "ECONNRESET" ,
177+ "ECONNREFUSED" ,
178+ "ENOTFOUND" ,
179+ "ENETUNREACH" ,
180+ "ETIMEDOUT" ,
181+ "ECONNABORTED" ,
182+ "EHOSTUNREACH" ,
183+ "EPIPE" ,
184+ ]
185+
186+ return (
187+ networkErrorCodes . includes ( error . code ) ||
188+ error . message ?. includes ( "socket hang up" ) ||
189+ error . message ?. includes ( "timeout" ) ||
190+ error . message ?. includes ( "network" )
191+ )
192+ }
193+
194+ private isRetryableError ( error : any ) : boolean {
195+ if ( ! error ) return false
196+
197+ // Retry on network errors
198+ if ( this . isNetworkError ( error ) ) return true
199+
200+ // Retry on 5xx server errors
201+ if ( error . response ?. status >= 500 ) return true
202+
203+ // Retry on specific axios errors
204+ if ( error . code === "ECONNABORTED" ) return true
205+
206+ return false
207+ }
208+
209+ private enhanceError ( error : Error ) : Error {
210+ const originalMessage = error . message || "Unknown error"
211+
212+ // Provide user-friendly error messages for common network issues
213+ if ( originalMessage . includes ( "socket hang up" ) ) {
214+ return new Error (
215+ "Network connection was interrupted while loading marketplace data. " +
216+ "This may be due to corporate proxy settings or network restrictions. " +
217+ "Please check your network configuration or try again later." ,
218+ )
219+ }
220+
221+ if ( originalMessage . includes ( "ENOTFOUND" ) || originalMessage . includes ( "getaddrinfo" ) ) {
222+ return new Error (
223+ "Unable to resolve marketplace server address. " +
224+ "Please check your internet connection and DNS settings." ,
225+ )
226+ }
227+
228+ if ( originalMessage . includes ( "ECONNREFUSED" ) ) {
229+ return new Error (
230+ "Connection to marketplace server was refused. " + "The service may be temporarily unavailable." ,
231+ )
232+ }
233+
234+ if ( originalMessage . includes ( "timeout" ) ) {
235+ return new Error (
236+ "Request to marketplace server timed out. " +
237+ "This may be due to slow network conditions or corporate firewall settings." ,
238+ )
239+ }
240+
241+ if ( originalMessage . includes ( "ECONNRESET" ) ) {
242+ return new Error (
243+ "Connection to marketplace server was reset. " +
244+ "This often occurs in corporate networks with strict proxy policies." ,
245+ )
246+ }
247+
248+ // For HTTP errors, provide more context
249+ if ( originalMessage . includes ( "HTTP 4" ) ) {
250+ return new Error (
251+ "Marketplace server returned a client error. " + "The requested resource may not be available." ,
252+ )
253+ }
254+
255+ if ( originalMessage . includes ( "HTTP 5" ) ) {
256+ return new Error ( "Marketplace server is experiencing issues. " + "Please try again later." )
257+ }
258+
259+ // Return enhanced error with original message for debugging
260+ return new Error (
261+ `Failed to load marketplace data: ${ originalMessage } . ` +
262+ "If you are behind a corporate firewall, please ensure proxy settings are configured correctly." ,
263+ )
102264 }
103265
104266 async getItem ( id : string , type : MarketplaceItemType ) : Promise < MarketplaceItem | null > {
0 commit comments