mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
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
This commit is contained in:
parent
bbe3362359
commit
99a9f2bfcf
4 changed files with 147 additions and 0 deletions
|
|
@ -116,6 +116,7 @@ describe("OpenAiHandler", () => {
|
|||
"User-Agent": `RooCode/${Package.version}`,
|
||||
},
|
||||
timeout: expect.any(Number),
|
||||
fetch: expect.any(Function),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { DEFAULT_HEADERS } from "./constants"
|
|||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { getApiRequestTimeout } from "./utils/timeout-config"
|
||||
import { createAxiosFetchAdapter, shouldUseAxiosForProxy } from "./utils/axios-fetch-adapter"
|
||||
|
||||
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
|
||||
// `OpenAINativeHandler` can subclass from this, since it's obviously
|
||||
|
|
@ -49,6 +50,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
|
||||
const timeout = getApiRequestTimeout()
|
||||
|
||||
// Determine if we should use axios for proxy support
|
||||
const useAxiosForProxy = this.options.openAiUseAxiosForProxy ?? shouldUseAxiosForProxy()
|
||||
const customFetch = createAxiosFetchAdapter(useAxiosForProxy)
|
||||
|
||||
if (isAzureAiInference) {
|
||||
// Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure
|
||||
this.client = new OpenAI({
|
||||
|
|
@ -57,6 +62,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
defaultHeaders: headers,
|
||||
defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" },
|
||||
timeout,
|
||||
fetch: customFetch,
|
||||
})
|
||||
} else if (isAzureOpenAi) {
|
||||
// Azure API shape slightly differs from the core API shape:
|
||||
|
|
@ -67,6 +73,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion,
|
||||
defaultHeaders: headers,
|
||||
timeout,
|
||||
fetch: customFetch,
|
||||
})
|
||||
} else {
|
||||
this.client = new OpenAI({
|
||||
|
|
@ -74,6 +81,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
apiKey,
|
||||
defaultHeaders: headers,
|
||||
timeout,
|
||||
fetch: customFetch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
src/api/providers/utils/axios-fetch-adapter.ts
Normal file
130
src/api/providers/utils/axios-fetch-adapter.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
|
||||
import { Readable } from "stream"
|
||||
|
||||
/**
|
||||
* Creates a fetch-compatible wrapper around axios for use with OpenAI SDK.
|
||||
* This adapter allows axios to be used instead of the native fetch API,
|
||||
* which is important for proxy support in VSCode extensions where the
|
||||
* patched fetch may not work correctly with certain proxy configurations
|
||||
* (particularly SOCKS5 proxies).
|
||||
*
|
||||
* @param useAxiosForProxy - If true, uses axios instead of native fetch
|
||||
* @returns A fetch-compatible function
|
||||
*/
|
||||
export function createAxiosFetchAdapter(useAxiosForProxy: boolean = false): typeof fetch {
|
||||
// If not using axios for proxy, return native fetch
|
||||
if (!useAxiosForProxy) {
|
||||
return fetch
|
||||
}
|
||||
|
||||
// Return an axios-based fetch implementation
|
||||
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : (input as Request).url
|
||||
|
||||
// Convert RequestInit to AxiosRequestConfig
|
||||
const config: AxiosRequestConfig = {
|
||||
url,
|
||||
method: (init?.method || "GET") as any,
|
||||
headers: init?.headers as any,
|
||||
data: init?.body,
|
||||
// Important: Set responseType to 'stream' for streaming responses
|
||||
responseType: "stream",
|
||||
// Disable automatic decompression to let the consumer handle it
|
||||
decompress: false,
|
||||
// Don't throw on HTTP error status codes
|
||||
validateStatus: () => true,
|
||||
}
|
||||
|
||||
try {
|
||||
const axiosResponse: AxiosResponse<Readable> = await axios(config)
|
||||
|
||||
// Convert axios response to fetch Response
|
||||
return createResponseFromAxios(axiosResponse)
|
||||
} catch (error: any) {
|
||||
// Handle network errors
|
||||
throw new TypeError(`Failed to fetch: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Axios response to a fetch Response object
|
||||
*/
|
||||
function createResponseFromAxios(axiosResponse: AxiosResponse<Readable>): Response {
|
||||
const { status, statusText, headers, data } = axiosResponse
|
||||
|
||||
// Convert Node.js Readable stream to Web ReadableStream
|
||||
const readableStream = nodeStreamToWebStream(data)
|
||||
|
||||
// Create Response with proper headers
|
||||
const responseHeaders = new Headers()
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
responseHeaders.set(key, String(value))
|
||||
}
|
||||
})
|
||||
|
||||
return new Response(readableStream, {
|
||||
status,
|
||||
statusText,
|
||||
headers: responseHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Node.js Readable stream to a Web ReadableStream
|
||||
*/
|
||||
function nodeStreamToWebStream(nodeStream: Readable): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
nodeStream.on("data", (chunk) => {
|
||||
// Ensure chunk is a Uint8Array
|
||||
if (typeof chunk === "string") {
|
||||
controller.enqueue(new TextEncoder().encode(chunk))
|
||||
} else if (chunk instanceof Buffer) {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
} else {
|
||||
controller.enqueue(chunk)
|
||||
}
|
||||
})
|
||||
|
||||
nodeStream.on("end", () => {
|
||||
controller.close()
|
||||
})
|
||||
|
||||
nodeStream.on("error", (err) => {
|
||||
controller.error(err)
|
||||
})
|
||||
},
|
||||
cancel() {
|
||||
nodeStream.destroy()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment suggests that axios should be used
|
||||
* instead of fetch for proxy support. This can be based on:
|
||||
* - Presence of proxy environment variables
|
||||
* - VSCode proxy settings
|
||||
* - User configuration
|
||||
*/
|
||||
export function shouldUseAxiosForProxy(): boolean {
|
||||
// Check for common proxy environment variables
|
||||
const proxyVars = [
|
||||
"HTTP_PROXY",
|
||||
"http_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"https_proxy",
|
||||
"ALL_PROXY",
|
||||
"all_proxy",
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
]
|
||||
|
||||
const hasProxyEnvVars = proxyVars.some((varName) => process.env[varName])
|
||||
|
||||
// For now, we'll enable axios for proxy support if proxy env vars are detected
|
||||
// This can be extended to check VSCode settings or user preferences
|
||||
return hasProxyEnvVars
|
||||
}
|
||||
|
|
@ -14,6 +14,14 @@ export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider"> & {
|
|||
* Defaults to true; set to false to disable summaries.
|
||||
*/
|
||||
enableGpt5ReasoningSummary?: boolean
|
||||
|
||||
/**
|
||||
* When true, uses axios instead of native fetch for OpenAI API calls.
|
||||
* This can help with proxy configurations where VSCode's patched fetch
|
||||
* doesn't work correctly (e.g., SOCKS5 proxies).
|
||||
* Defaults to auto-detection based on proxy environment variables.
|
||||
*/
|
||||
openAiUseAxiosForProxy?: boolean
|
||||
}
|
||||
|
||||
// RouterName
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue