From 42a1879464114eafc6c2f0cd0d64dfc9de1a1b88 Mon Sep 17 00:00:00 2001 From: Yash Raj Pandey Date: Thu, 16 Jul 2026 13:18:47 -0400 Subject: [PATCH 1/2] fix(fetch): honor proxy and verifySsl on the node fetch path The remote config-sync/index-cache client (core/continueServer/stubs/client.ts) used the global fetch() and ignored config.yml requestOptions entirely, so proxy, verifySsl, and caBundlePath had no effect on those requests even though the LLM request path (fetchwithRequestOptions) already applied them correctly. Route the client through fetchwithRequestOptions and pass config.requestOptions through from CodebaseIndexer. --- core/continueServer/stubs/client.ts | 64 +++++++++++++++++----------- core/indexing/CodebaseIndexer.ts | 1 + packages/fetch/src/fetch.test.ts | 66 +++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 packages/fetch/src/fetch.test.ts diff --git a/core/continueServer/stubs/client.ts b/core/continueServer/stubs/client.ts index febed8bbd48..8a2ce093fca 100644 --- a/core/continueServer/stubs/client.ts +++ b/core/continueServer/stubs/client.ts @@ -1,3 +1,6 @@ +import { fetchwithRequestOptions } from "@continuedev/fetch"; + +import type { RequestOptions } from "../../index.js"; import type { ArtifactType, EmbeddingsCacheResponse, @@ -10,6 +13,7 @@ export class ContinueServerClient implements IContinueServerClient { constructor( serverUrl: string | undefined, private readonly userToken: string | undefined, + private readonly requestOptions?: RequestOptions, ) { try { this.url = @@ -32,12 +36,16 @@ export class ContinueServerClient implements IContinueServerClient { public async getConfig(): Promise<{ configJson: string }> { const userToken = await this.userToken; - const response = await fetch(new URL("sync", this.url).href, { - method: "GET", - headers: { - Authorization: `Bearer ${userToken}`, + const response = await fetchwithRequestOptions( + new URL("sync", this.url).href, + { + method: "GET", + headers: { + Authorization: `Bearer ${userToken}`, + }, }, - }); + this.requestOptions, + ); if (!response.ok) { throw new Error( `Failed to sync remote config (HTTP ${response.status}): ${response.statusText}`, @@ -66,17 +74,21 @@ export class ContinueServerClient implements IContinueServerClient { const url = new URL("indexing/cache", this.url); try { - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${await this.userToken}`, + const response = await fetchwithRequestOptions( + url, + { + method: "POST", + headers: { + Authorization: `Bearer ${await this.userToken}`, + }, + body: JSON.stringify({ + keys, + artifactId, + repo: repoName ?? "NONE", + }), }, - body: JSON.stringify({ - keys, - artifactId, - repo: repoName ?? "NONE", - }), - }); + this.requestOptions, + ); if (!response.ok) { const text = await response.text(); @@ -105,15 +117,19 @@ export class ContinueServerClient implements IContinueServerClient { const url = new URL("feedback", this.url); - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${await this.userToken}`, + const response = await fetchwithRequestOptions( + url, + { + method: "POST", + headers: { + Authorization: `Bearer ${await this.userToken}`, + }, + body: JSON.stringify({ + feedback, + data, + }), }, - body: JSON.stringify({ - feedback, - data, - }), - }); + this.requestOptions, + ); } } diff --git a/core/indexing/CodebaseIndexer.ts b/core/indexing/CodebaseIndexer.ts index c37992f98cd..8abab6c3d87 100644 --- a/core/indexing/CodebaseIndexer.ts +++ b/core/indexing/CodebaseIndexer.ts @@ -161,6 +161,7 @@ export class CodebaseIndexer { const continueServerClient = new ContinueServerClient( ideSettings.remoteConfigServerUrl, ideSettings.userToken, + config.requestOptions, ); if (!continueServerClient) { return []; diff --git a/packages/fetch/src/fetch.test.ts b/packages/fetch/src/fetch.test.ts new file mode 100644 index 00000000000..1bf5fee8769 --- /dev/null +++ b/packages/fetch/src/fetch.test.ts @@ -0,0 +1,66 @@ +import { HttpsProxyAgent } from "https-proxy-agent"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { fetchwithRequestOptions } from "./fetch.js"; +import patchedFetch from "./node-fetch-patch.js"; + +vi.mock("./node-fetch-patch.js", () => ({ + default: vi.fn(), +})); + +const originalEnv = process.env; + +beforeEach(() => { + process.env = { ...originalEnv }; + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.http_proxy; + delete process.env.https_proxy; + + vi.mocked(patchedFetch).mockReset(); + vi.mocked(patchedFetch).mockResolvedValue({ + ok: true, + headers: { get: () => undefined }, + } as any); +}); + +afterEach(() => { + process.env = originalEnv; +}); + +test("fetchwithRequestOptions uses an HttpsProxyAgent configured with requestOptions.proxy", async () => { + await fetchwithRequestOptions("https://example.com/api", undefined, { + proxy: "http://proxy.example.com:8080", + }); + + expect(patchedFetch).toHaveBeenCalledTimes(1); + const [, init] = vi.mocked(patchedFetch).mock.calls[0]; + const agent = (init as any).agent; + + expect(agent).toBeInstanceOf(HttpsProxyAgent); + expect(agent.proxy.href).toBe("http://proxy.example.com:8080/"); +}); + +test("fetchwithRequestOptions disables TLS verification when requestOptions.verifySsl is false", async () => { + await fetchwithRequestOptions("https://example.com/api", undefined, { + verifySsl: false, + }); + + const [, init] = vi.mocked(patchedFetch).mock.calls[0]; + const agent = (init as any).agent; + + expect(agent.options.rejectUnauthorized).toBe(false); +}); + +test("fetchwithRequestOptions honors proxy and disabled TLS verification together", async () => { + await fetchwithRequestOptions("https://example.com/api", undefined, { + proxy: "http://proxy.example.com:8080", + verifySsl: false, + }); + + const [, init] = vi.mocked(patchedFetch).mock.calls[0]; + const agent = (init as any).agent; + + expect(agent).toBeInstanceOf(HttpsProxyAgent); + expect(agent.proxy.href).toBe("http://proxy.example.com:8080/"); + expect(agent.connectOpts.rejectUnauthorized).toBe(false); +}); From b454a6c021e78550aa44a06d33be70982b0bddb1 Mon Sep 17 00:00:00 2001 From: Yash Raj Pandey Date: Thu, 16 Jul 2026 14:03:15 -0400 Subject: [PATCH 2/2] fix(fetch): cast remote client json() results to their declared return types fetchwithRequestOptions types json() as unknown (stricter than the global fetch's any), so getConfig/getFromIndexCache need an explicit cast to their declared return types to satisfy the type check. --- core/continueServer/stubs/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/continueServer/stubs/client.ts b/core/continueServer/stubs/client.ts index 8a2ce093fca..b1ef66ed515 100644 --- a/core/continueServer/stubs/client.ts +++ b/core/continueServer/stubs/client.ts @@ -51,7 +51,7 @@ export class ContinueServerClient implements IContinueServerClient { `Failed to sync remote config (HTTP ${response.status}): ${response.statusText}`, ); } - const data = await response.json(); + const data = (await response.json()) as { configJson: string }; return data; } @@ -100,7 +100,7 @@ export class ContinueServerClient implements IContinueServerClient { }; } - const data = await response.json(); + const data = (await response.json()) as EmbeddingsCacheResponse; return data; } catch (e) { console.warn("Failed to retrieve from remote cache", e);