Skip to content

Commit 99a9f2b

Browse files
committed
fix: add axios-based fetch adapter for OpenAI SDK to fix proxy issues
- Created axios-fetch-adapter utility that wraps axios to provide fetch-compatible interface - Added openAiUseAxiosForProxy option to ApiHandlerOptions - Modified OpenAI provider to use axios adapter when proxy is detected or configured - Updated tests to expect the fetch property in OpenAI constructor This fixes issues where VSCode patched fetch does not work correctly with SOCKS5 proxies, while axios handles them properly. The adapter automatically detects proxy environment variables and switches to axios when needed. Fixes #6991
1 parent bbe3362 commit 99a9f2b

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed

src/api/providers/__tests__/openai.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ describe("OpenAiHandler", () => {
116116
"User-Agent": `RooCode/${Package.version}`,
117117
},
118118
timeout: expect.any(Number),
119+
fetch: expect.any(Function),
119120
})
120121
})
121122
})

src/api/providers/openai.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { DEFAULT_HEADERS } from "./constants"
2424
import { BaseProvider } from "./base-provider"
2525
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
2626
import { getApiRequestTimeout } from "./utils/timeout-config"
27+
import { createAxiosFetchAdapter, shouldUseAxiosForProxy } from "./utils/axios-fetch-adapter"
2728

2829
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
2930
// `OpenAINativeHandler` can subclass from this, since it's obviously
@@ -49,6 +50,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
4950

5051
const timeout = getApiRequestTimeout()
5152

53+
// Determine if we should use axios for proxy support
54+
const useAxiosForProxy = this.options.openAiUseAxiosForProxy ?? shouldUseAxiosForProxy()
55+
const customFetch = createAxiosFetchAdapter(useAxiosForProxy)
56+
5257
if (isAzureAiInference) {
5358
// Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure
5459
this.client = new OpenAI({
@@ -57,6 +62,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
5762
defaultHeaders: headers,
5863
defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" },
5964
timeout,
65+
fetch: customFetch,
6066
})
6167
} else if (isAzureOpenAi) {
6268
// Azure API shape slightly differs from the core API shape:
@@ -67,13 +73,15 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
6773
apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion,
6874
defaultHeaders: headers,
6975
timeout,
76+
fetch: customFetch,
7077
})
7178
} else {
7279
this.client = new OpenAI({
7380
baseURL,
7481
apiKey,
7582
defaultHeaders: headers,
7683
timeout,
84+
fetch: customFetch,
7785
})
7886
}
7987
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
2+
import { Readable } from "stream"
3+
4+
/**
5+
* Creates a fetch-compatible wrapper around axios for use with OpenAI SDK.
6+
* This adapter allows axios to be used instead of the native fetch API,
7+
* which is important for proxy support in VSCode extensions where the
8+
* patched fetch may not work correctly with certain proxy configurations
9+
* (particularly SOCKS5 proxies).
10+
*
11+
* @param useAxiosForProxy - If true, uses axios instead of native fetch
12+
* @returns A fetch-compatible function
13+
*/
14+
export function createAxiosFetchAdapter(useAxiosForProxy: boolean = false): typeof fetch {
15+
// If not using axios for proxy, return native fetch
16+
if (!useAxiosForProxy) {
17+
return fetch
18+
}
19+
20+
// Return an axios-based fetch implementation
21+
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
22+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : (input as Request).url
23+
24+
// Convert RequestInit to AxiosRequestConfig
25+
const config: AxiosRequestConfig = {
26+
url,
27+
method: (init?.method || "GET") as any,
28+
headers: init?.headers as any,
29+
data: init?.body,
30+
// Important: Set responseType to 'stream' for streaming responses
31+
responseType: "stream",
32+
// Disable automatic decompression to let the consumer handle it
33+
decompress: false,
34+
// Don't throw on HTTP error status codes
35+
validateStatus: () => true,
36+
}
37+
38+
try {
39+
const axiosResponse: AxiosResponse<Readable> = await axios(config)
40+
41+
// Convert axios response to fetch Response
42+
return createResponseFromAxios(axiosResponse)
43+
} catch (error: any) {
44+
// Handle network errors
45+
throw new TypeError(`Failed to fetch: ${error.message}`)
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Converts an Axios response to a fetch Response object
52+
*/
53+
function createResponseFromAxios(axiosResponse: AxiosResponse<Readable>): Response {
54+
const { status, statusText, headers, data } = axiosResponse
55+
56+
// Convert Node.js Readable stream to Web ReadableStream
57+
const readableStream = nodeStreamToWebStream(data)
58+
59+
// Create Response with proper headers
60+
const responseHeaders = new Headers()
61+
Object.entries(headers).forEach(([key, value]) => {
62+
if (value !== undefined) {
63+
responseHeaders.set(key, String(value))
64+
}
65+
})
66+
67+
return new Response(readableStream, {
68+
status,
69+
statusText,
70+
headers: responseHeaders,
71+
})
72+
}
73+
74+
/**
75+
* Converts a Node.js Readable stream to a Web ReadableStream
76+
*/
77+
function nodeStreamToWebStream(nodeStream: Readable): ReadableStream<Uint8Array> {
78+
return new ReadableStream({
79+
start(controller) {
80+
nodeStream.on("data", (chunk) => {
81+
// Ensure chunk is a Uint8Array
82+
if (typeof chunk === "string") {
83+
controller.enqueue(new TextEncoder().encode(chunk))
84+
} else if (chunk instanceof Buffer) {
85+
controller.enqueue(new Uint8Array(chunk))
86+
} else {
87+
controller.enqueue(chunk)
88+
}
89+
})
90+
91+
nodeStream.on("end", () => {
92+
controller.close()
93+
})
94+
95+
nodeStream.on("error", (err) => {
96+
controller.error(err)
97+
})
98+
},
99+
cancel() {
100+
nodeStream.destroy()
101+
},
102+
})
103+
}
104+
105+
/**
106+
* Checks if the current environment suggests that axios should be used
107+
* instead of fetch for proxy support. This can be based on:
108+
* - Presence of proxy environment variables
109+
* - VSCode proxy settings
110+
* - User configuration
111+
*/
112+
export function shouldUseAxiosForProxy(): boolean {
113+
// Check for common proxy environment variables
114+
const proxyVars = [
115+
"HTTP_PROXY",
116+
"http_proxy",
117+
"HTTPS_PROXY",
118+
"https_proxy",
119+
"ALL_PROXY",
120+
"all_proxy",
121+
"NO_PROXY",
122+
"no_proxy",
123+
]
124+
125+
const hasProxyEnvVars = proxyVars.some((varName) => process.env[varName])
126+
127+
// For now, we'll enable axios for proxy support if proxy env vars are detected
128+
// This can be extended to check VSCode settings or user preferences
129+
return hasProxyEnvVars
130+
}

src/shared/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider"> & {
1414
* Defaults to true; set to false to disable summaries.
1515
*/
1616
enableGpt5ReasoningSummary?: boolean
17+
18+
/**
19+
* When true, uses axios instead of native fetch for OpenAI API calls.
20+
* This can help with proxy configurations where VSCode's patched fetch
21+
* doesn't work correctly (e.g., SOCKS5 proxies).
22+
* Defaults to auto-detection based on proxy environment variables.
23+
*/
24+
openAiUseAxiosForProxy?: boolean
1725
}
1826

1927
// RouterName

0 commit comments

Comments
 (0)