From 61d3f947788b7c91c8a4b299e753046501eded8b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 12 Aug 2025 14:05:39 +0000 Subject: [PATCH] fix: replace axios with fetch to respect VSCode proxy settings - Replace axios.get with fetch in getOpenAiModels function - Replace axios.get with fetch in getLmStudioModels function - Update tests to mock fetch instead of axios - This ensures model fetching respects VSCode proxy settings like PAC files Fixes #6991 --- src/api/providers/__tests__/openai.spec.ts | 112 ++++++++++++--------- src/api/providers/lm-studio.ts | 17 +++- src/api/providers/openai.ts | 17 ++-- 3 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 0d42c082a9..6beee83ec5 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -6,7 +6,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" import { openAiModelInfoSaneDefaults } from "@roo-code/types" import { Package } from "../../../shared/package" -import axios from "axios" const mockCreate = vitest.fn() @@ -69,12 +68,9 @@ vitest.mock("openai", () => { } }) -// Mock axios for getOpenAiModels tests -vitest.mock("axios", () => ({ - default: { - get: vitest.fn(), - }, -})) +// Mock fetch for getOpenAiModels tests +const mockFetch = vitest.fn() +global.fetch = mockFetch describe("OpenAiHandler", () => { let handler: OpenAiHandler @@ -787,80 +783,84 @@ describe("OpenAiHandler", () => { describe("getOpenAiModels", () => { beforeEach(() => { - vi.mocked(axios.get).mockClear() + mockFetch.mockClear() }) it("should return empty array when baseUrl is not provided", async () => { const result = await getOpenAiModels(undefined, "test-key") expect(result).toEqual([]) - expect(axios.get).not.toHaveBeenCalled() + expect(mockFetch).not.toHaveBeenCalled() }) it("should return empty array when baseUrl is empty string", async () => { const result = await getOpenAiModels("", "test-key") expect(result).toEqual([]) - expect(axios.get).not.toHaveBeenCalled() + expect(mockFetch).not.toHaveBeenCalled() }) it("should trim whitespace from baseUrl", async () => { - const mockResponse = { - data: { - data: [{ id: "gpt-4" }, { id: "gpt-3.5-turbo" }], - }, + const mockResponseData = { + data: [{ id: "gpt-4" }, { id: "gpt-3.5-turbo" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) const result = await getOpenAiModels(" https://api.openai.com/v1 ", "test-key") - expect(axios.get).toHaveBeenCalledWith("https://api.openai.com/v1/models", expect.any(Object)) + expect(mockFetch).toHaveBeenCalledWith("https://api.openai.com/v1/models", expect.any(Object)) expect(result).toEqual(["gpt-4", "gpt-3.5-turbo"]) }) it("should handle baseUrl with trailing spaces", async () => { - const mockResponse = { - data: { - data: [{ id: "model-1" }, { id: "model-2" }], - }, + const mockResponseData = { + data: [{ id: "model-1" }, { id: "model-2" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) const result = await getOpenAiModels("https://api.example.com/v1 ", "test-key") - expect(axios.get).toHaveBeenCalledWith("https://api.example.com/v1/models", expect.any(Object)) + expect(mockFetch).toHaveBeenCalledWith("https://api.example.com/v1/models", expect.any(Object)) expect(result).toEqual(["model-1", "model-2"]) }) it("should handle baseUrl with leading spaces", async () => { - const mockResponse = { - data: { - data: [{ id: "model-1" }], - }, + const mockResponseData = { + data: [{ id: "model-1" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) const result = await getOpenAiModels(" https://api.example.com/v1", "test-key") - expect(axios.get).toHaveBeenCalledWith("https://api.example.com/v1/models", expect.any(Object)) + expect(mockFetch).toHaveBeenCalledWith("https://api.example.com/v1/models", expect.any(Object)) expect(result).toEqual(["model-1"]) }) it("should return empty array for invalid URL after trimming", async () => { const result = await getOpenAiModels(" not-a-valid-url ", "test-key") expect(result).toEqual([]) - expect(axios.get).not.toHaveBeenCalled() + expect(mockFetch).not.toHaveBeenCalled() }) it("should include authorization header when apiKey is provided", async () => { - const mockResponse = { - data: { - data: [{ id: "model-1" }], - }, + const mockResponseData = { + data: [{ id: "model-1" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) await getOpenAiModels("https://api.example.com/v1", "test-api-key") - expect(axios.get).toHaveBeenCalledWith( + expect(mockFetch).toHaveBeenCalledWith( "https://api.example.com/v1/models", expect.objectContaining({ headers: expect.objectContaining({ @@ -871,12 +871,13 @@ describe("getOpenAiModels", () => { }) it("should include custom headers when provided", async () => { - const mockResponse = { - data: { - data: [{ id: "model-1" }], - }, + const mockResponseData = { + data: [{ id: "model-1" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) const customHeaders = { "X-Custom-Header": "custom-value", @@ -884,7 +885,7 @@ describe("getOpenAiModels", () => { await getOpenAiModels("https://api.example.com/v1", "test-key", customHeaders) - expect(axios.get).toHaveBeenCalledWith( + expect(mockFetch).toHaveBeenCalledWith( "https://api.example.com/v1/models", expect.objectContaining({ headers: expect.objectContaining({ @@ -896,7 +897,18 @@ describe("getOpenAiModels", () => { }) it("should handle API errors gracefully", async () => { - vi.mocked(axios.get).mockRejectedValueOnce(new Error("Network error")) + mockFetch.mockRejectedValueOnce(new Error("Network error")) + + const result = await getOpenAiModels("https://api.example.com/v1", "test-key") + + expect(result).toEqual([]) + }) + + it("should handle non-ok response", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 401, + }) const result = await getOpenAiModels("https://api.example.com/v1", "test-key") @@ -904,7 +916,10 @@ describe("getOpenAiModels", () => { }) it("should handle malformed response data", async () => { - vi.mocked(axios.get).mockResolvedValueOnce({ data: null }) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => null, + }) const result = await getOpenAiModels("https://api.example.com/v1", "test-key") @@ -912,12 +927,13 @@ describe("getOpenAiModels", () => { }) it("should deduplicate model IDs", async () => { - const mockResponse = { - data: { - data: [{ id: "gpt-4" }, { id: "gpt-4" }, { id: "gpt-3.5-turbo" }, { id: "gpt-4" }], - }, + const mockResponseData = { + data: [{ id: "gpt-4" }, { id: "gpt-4" }, { id: "gpt-3.5-turbo" }, { id: "gpt-4" }], } - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse) + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponseData, + }) const result = await getOpenAiModels("https://api.example.com/v1", "test-key") diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index 6c49920bd1..0791fe3222 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -1,6 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import axios from "axios" import { type ModelInfo, openAiModelInfoSaneDefaults, LMSTUDIO_DEFAULT_TEMPERATURE } from "@roo-code/types" @@ -177,8 +176,20 @@ export async function getLmStudioModels(baseUrl = "http://localhost:1234") { return [] } - const response = await axios.get(`${baseUrl}/v1/models`) - const modelsArray = response.data?.data?.map((model: any) => model.id) || [] + // Use fetch instead of axios to respect VSCode's proxy settings + const response = await fetch(`${baseUrl}/v1/models`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + + if (!response.ok) { + return [] + } + + const data = await response.json() + const modelsArray = data?.data?.map((model: any) => model.id) || [] return [...new Set(modelsArray)] } catch (error) { return [] diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index eed719cf0f..08ae562d8b 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -1,6 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI, { AzureOpenAI } from "openai" -import axios from "axios" import { type ModelInfo, @@ -423,7 +422,6 @@ export async function getOpenAiModels(baseUrl?: string, apiKey?: string, openAiH return [] } - const config: Record = {} const headers: Record = { ...DEFAULT_HEADERS, ...(openAiHeaders || {}), @@ -433,12 +431,19 @@ export async function getOpenAiModels(baseUrl?: string, apiKey?: string, openAiH headers["Authorization"] = `Bearer ${apiKey}` } - if (Object.keys(headers).length > 0) { - config["headers"] = headers + // Use fetch instead of axios to respect VSCode's proxy settings + // This matches how the OpenAI SDK makes requests internally + const response = await fetch(`${trimmedBaseUrl}/models`, { + method: "GET", + headers: headers, + }) + + if (!response.ok) { + return [] } - const response = await axios.get(`${trimmedBaseUrl}/models`, config) - const modelsArray = response.data?.data?.map((model: any) => model.id) || [] + const data = await response.json() + const modelsArray = data?.data?.map((model: any) => model.id) || [] return [...new Set(modelsArray)] } catch (error) { return []