mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-20 00:11:18 +00:00
Merge 4f32c441ed into c3cae397a1
This commit is contained in:
commit
41c8a99f2b
7 changed files with 375 additions and 2 deletions
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
|
|
@ -878,6 +878,9 @@ importers:
|
||||||
gray-matter:
|
gray-matter:
|
||||||
specifier: ^4.0.3
|
specifier: ^4.0.3
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
|
https-proxy-agent:
|
||||||
|
specifier: ^7.0.6
|
||||||
|
version: 7.0.6
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^25.0.0
|
specifier: ^25.0.0
|
||||||
version: 25.2.1(typescript@5.8.3)
|
version: 25.2.1(typescript@5.8.3)
|
||||||
|
|
@ -17733,7 +17736,7 @@ snapshots:
|
||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.3
|
agent-base: 7.1.3
|
||||||
debug: 4.4.1(supports-color@8.1.1)
|
debug: 4.4.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
|
|
||||||
import { BaseProvider } from "./base-provider"
|
import { BaseProvider } from "./base-provider"
|
||||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||||
|
import { getProxyHttpAgent } from "../../utils/proxyFetch"
|
||||||
|
|
||||||
// https://docs.anthropic.com/en/api/claude-on-vertex-ai
|
// https://docs.anthropic.com/en/api/claude-on-vertex-ai
|
||||||
export class AnthropicVertexHandler extends BaseProvider implements SingleCompletionHandler {
|
export class AnthropicVertexHandler extends BaseProvider implements SingleCompletionHandler {
|
||||||
|
|
@ -40,10 +41,13 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
|
||||||
const projectId = this.options.vertexProjectId ?? "not-provided"
|
const projectId = this.options.vertexProjectId ?? "not-provided"
|
||||||
const region = this.options.vertexRegion ?? "us-east5"
|
const region = this.options.vertexRegion ?? "us-east5"
|
||||||
|
|
||||||
|
const httpAgent = getProxyHttpAgent()
|
||||||
|
|
||||||
if (this.options.vertexJsonCredentials) {
|
if (this.options.vertexJsonCredentials) {
|
||||||
this.client = new AnthropicVertex({
|
this.client = new AnthropicVertex({
|
||||||
projectId,
|
projectId,
|
||||||
region,
|
region,
|
||||||
|
httpAgent,
|
||||||
googleAuth: new GoogleAuth({
|
googleAuth: new GoogleAuth({
|
||||||
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
||||||
credentials: safeJsonParse<JWTInput>(this.options.vertexJsonCredentials, undefined),
|
credentials: safeJsonParse<JWTInput>(this.options.vertexJsonCredentials, undefined),
|
||||||
|
|
@ -53,13 +57,14 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
|
||||||
this.client = new AnthropicVertex({
|
this.client = new AnthropicVertex({
|
||||||
projectId,
|
projectId,
|
||||||
region,
|
region,
|
||||||
|
httpAgent,
|
||||||
googleAuth: new GoogleAuth({
|
googleAuth: new GoogleAuth({
|
||||||
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
||||||
keyFile: this.options.vertexKeyFile,
|
keyFile: this.options.vertexKeyFile,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.client = new AnthropicVertex({ projectId, region })
|
this.client = new AnthropicVertex({ projectId, region, httpAgent })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { ApiStream } from "../transform/stream"
|
||||||
import { getModelParams } from "../transform/model-params"
|
import { getModelParams } from "../transform/model-params"
|
||||||
import { filterNonAnthropicBlocks } from "../transform/anthropic-filter"
|
import { filterNonAnthropicBlocks } from "../transform/anthropic-filter"
|
||||||
import { handleProviderError } from "./utils/error-handler"
|
import { handleProviderError } from "./utils/error-handler"
|
||||||
|
import { getProxyHttpAgent } from "../../utils/proxyFetch"
|
||||||
|
|
||||||
import { BaseProvider } from "./base-provider"
|
import { BaseProvider } from "./base-provider"
|
||||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||||
|
|
@ -43,6 +44,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
||||||
this.client = new Anthropic({
|
this.client = new Anthropic({
|
||||||
baseURL: this.options.anthropicBaseUrl || undefined,
|
baseURL: this.options.anthropicBaseUrl || undefined,
|
||||||
[apiKeyFieldName]: this.options.apiKey,
|
[apiKeyFieldName]: this.options.apiKey,
|
||||||
|
httpAgent: getProxyHttpAgent(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { BaseProvider } from "./base-provider"
|
||||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||||
import { calculateApiCostAnthropic } from "../../shared/cost"
|
import { calculateApiCostAnthropic } from "../../shared/cost"
|
||||||
import { convertOpenAIToolsToAnthropic } from "../../core/prompts/tools/native-tools/converters"
|
import { convertOpenAIToolsToAnthropic } from "../../core/prompts/tools/native-tools/converters"
|
||||||
|
import { getProxyHttpAgent } from "../../utils/proxyFetch"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts OpenAI tool_choice to Anthropic ToolChoice format
|
* Converts OpenAI tool_choice to Anthropic ToolChoice format
|
||||||
|
|
@ -73,6 +74,7 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand
|
||||||
this.client = new Anthropic({
|
this.client = new Anthropic({
|
||||||
baseURL,
|
baseURL,
|
||||||
apiKey: options.minimaxApiKey,
|
apiKey: options.minimaxApiKey,
|
||||||
|
httpAgent: getProxyHttpAgent(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,7 @@
|
||||||
"global-agent": "^3.0.0",
|
"global-agent": "^3.0.0",
|
||||||
"google-auth-library": "^9.15.1",
|
"google-auth-library": "^9.15.1",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
"i18next": "^25.0.0",
|
"i18next": "^25.0.0",
|
||||||
"ignore": "^7.0.3",
|
"ignore": "^7.0.3",
|
||||||
"isbinaryfile": "^5.0.2",
|
"isbinaryfile": "^5.0.2",
|
||||||
|
|
|
||||||
255
src/utils/__tests__/proxyFetch.spec.ts
Normal file
255
src/utils/__tests__/proxyFetch.spec.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
import * as vscode from "vscode"
|
||||||
|
|
||||||
|
const mockHttpsProxyAgentConstructor = vi.fn()
|
||||||
|
|
||||||
|
vi.mock("https-proxy-agent", () => ({
|
||||||
|
HttpsProxyAgent: mockHttpsProxyAgentConstructor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock("vscode", () => ({
|
||||||
|
workspace: {
|
||||||
|
getConfiguration: vi.fn(),
|
||||||
|
onDidChangeConfiguration: vi.fn(() => ({ dispose: vi.fn() })),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
function createMockContext(): vscode.ExtensionContext {
|
||||||
|
return {
|
||||||
|
extensionMode: 1, // Production
|
||||||
|
subscriptions: [],
|
||||||
|
extensionPath: "/test/path",
|
||||||
|
globalState: {
|
||||||
|
get: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
keys: vi.fn().mockReturnValue([]),
|
||||||
|
setKeysForSync: vi.fn(),
|
||||||
|
},
|
||||||
|
workspaceState: {
|
||||||
|
get: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
keys: vi.fn().mockReturnValue([]),
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
get: vi.fn(),
|
||||||
|
store: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
onDidChange: vi.fn(),
|
||||||
|
},
|
||||||
|
extensionUri: { fsPath: "/test/path" } as vscode.Uri,
|
||||||
|
globalStorageUri: { fsPath: "/test/global" } as vscode.Uri,
|
||||||
|
logUri: { fsPath: "/test/logs" } as vscode.Uri,
|
||||||
|
storageUri: { fsPath: "/test/storage" } as vscode.Uri,
|
||||||
|
storagePath: "/test/storage",
|
||||||
|
globalStoragePath: "/test/global",
|
||||||
|
logPath: "/test/logs",
|
||||||
|
asAbsolutePath: vi.fn((p) => `/test/path/${p}`),
|
||||||
|
environmentVariableCollection: {} as vscode.GlobalEnvironmentVariableCollection,
|
||||||
|
extension: {} as vscode.Extension<unknown>,
|
||||||
|
languageModelAccessInformation: {} as vscode.LanguageModelAccessInformation,
|
||||||
|
} as unknown as vscode.ExtensionContext
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("proxyFetch", () => {
|
||||||
|
let mockHttpConfig: { get: ReturnType<typeof vi.fn> }
|
||||||
|
let savedFetch: typeof globalThis.fetch
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
vi.resetModules()
|
||||||
|
|
||||||
|
savedFetch = globalThis.fetch
|
||||||
|
|
||||||
|
mockHttpConfig = {
|
||||||
|
get: vi.fn().mockReturnValue(undefined),
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(
|
||||||
|
mockHttpConfig as unknown as vscode.WorkspaceConfiguration,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clear proxy env vars
|
||||||
|
delete process.env.HTTPS_PROXY
|
||||||
|
delete process.env.https_proxy
|
||||||
|
delete process.env.HTTP_PROXY
|
||||||
|
delete process.env.http_proxy
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
globalThis.fetch = savedFetch
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("resolveProxyUrl", () => {
|
||||||
|
it("should return undefined when no proxy is configured", async () => {
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return VSCode http.proxy setting when configured", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return "http://corporate-proxy:8080"
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://corporate-proxy:8080")
|
||||||
|
expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith("http")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trim whitespace from VSCode proxy setting", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return " http://corporate-proxy:8080 "
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://corporate-proxy:8080")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should ignore empty VSCode proxy setting and fall back to env vars", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return " "
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
process.env.HTTPS_PROXY = "http://env-proxy:3128"
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://env-proxy:3128")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should prefer HTTPS_PROXY over HTTP_PROXY", async () => {
|
||||||
|
process.env.HTTPS_PROXY = "http://https-proxy:3128"
|
||||||
|
process.env.HTTP_PROXY = "http://http-proxy:3128"
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://https-proxy:3128")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fall back to lowercase env vars", async () => {
|
||||||
|
process.env.https_proxy = "http://lowercase-proxy:3128"
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://lowercase-proxy:3128")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should prefer VSCode setting over env vars", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return "http://vscode-proxy:8080"
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
process.env.HTTPS_PROXY = "http://env-proxy:3128"
|
||||||
|
|
||||||
|
const { resolveProxyUrl } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const result = resolveProxyUrl()
|
||||||
|
|
||||||
|
expect(result).toBe("http://vscode-proxy:8080")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getProxyHttpAgent", () => {
|
||||||
|
it("should return undefined when no proxy is configured", async () => {
|
||||||
|
const { getProxyHttpAgent } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const agent = getProxyHttpAgent()
|
||||||
|
|
||||||
|
expect(agent).toBeUndefined()
|
||||||
|
expect(mockHttpsProxyAgentConstructor).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return an HttpsProxyAgent when proxy is configured", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return "http://corporate-proxy:8080"
|
||||||
|
if (key === "proxyStrictSSL") return true
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockAgent = { mock: true }
|
||||||
|
mockHttpsProxyAgentConstructor.mockReturnValue(mockAgent)
|
||||||
|
|
||||||
|
const { getProxyHttpAgent } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const agent = getProxyHttpAgent()
|
||||||
|
|
||||||
|
expect(agent).toBe(mockAgent)
|
||||||
|
expect(mockHttpsProxyAgentConstructor).toHaveBeenCalledWith("http://corporate-proxy:8080", {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should disable TLS verification when proxyStrictSSL is false", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return "http://corporate-proxy:8080"
|
||||||
|
if (key === "proxyStrictSSL") return false
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
mockHttpsProxyAgentConstructor.mockReturnValue({ mock: true })
|
||||||
|
|
||||||
|
const { getProxyHttpAgent } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
getProxyHttpAgent()
|
||||||
|
|
||||||
|
expect(mockHttpsProxyAgentConstructor).toHaveBeenCalledWith("http://corporate-proxy:8080", {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return undefined and log error when HttpsProxyAgent constructor throws", async () => {
|
||||||
|
mockHttpConfig.get.mockImplementation((key: string) => {
|
||||||
|
if (key === "proxy") return "http://bad-proxy:9999"
|
||||||
|
if (key === "proxyStrictSSL") return true
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
mockHttpsProxyAgentConstructor.mockImplementation(() => {
|
||||||
|
throw new Error("Invalid proxy URL")
|
||||||
|
})
|
||||||
|
|
||||||
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
|
|
||||||
|
const { getProxyHttpAgent } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const agent = getProxyHttpAgent()
|
||||||
|
|
||||||
|
expect(agent).toBeUndefined()
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("[ProxyFetch] Failed to create HttpsProxyAgent"),
|
||||||
|
)
|
||||||
|
|
||||||
|
consoleErrorSpy.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use env proxy when VSCode setting is not configured", async () => {
|
||||||
|
process.env.HTTPS_PROXY = "http://env-proxy:3128"
|
||||||
|
|
||||||
|
mockHttpsProxyAgentConstructor.mockReturnValue({ mock: true })
|
||||||
|
|
||||||
|
const { getProxyHttpAgent } = await import("../proxyFetch")
|
||||||
|
|
||||||
|
const agent = getProxyHttpAgent()
|
||||||
|
|
||||||
|
expect(agent).toBeDefined()
|
||||||
|
expect(mockHttpsProxyAgentConstructor).toHaveBeenCalledWith("http://env-proxy:3128", {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
105
src/utils/proxyFetch.ts
Normal file
105
src/utils/proxyFetch.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* Proxy-Aware HTTP Agent Module
|
||||||
|
*
|
||||||
|
* Provides proxy support for SDKs that use `node-fetch` with custom agents
|
||||||
|
* (e.g. Anthropic SDK v0.x), routing their traffic through the user's
|
||||||
|
* configured proxy (VSCode `http.proxy` setting or standard environment
|
||||||
|
* variables).
|
||||||
|
*
|
||||||
|
* Background:
|
||||||
|
* - VSCode patches Node.js `http`/`https` modules to honour `http.proxy`, so
|
||||||
|
* libraries that use those modules (e.g. axios) are already proxied.
|
||||||
|
* - The Anthropic SDK uses `node-fetch` with custom `agentkeepalive` agents,
|
||||||
|
* which bypass VSCode's proxy patching. For these SDKs, we provide
|
||||||
|
* `getProxyHttpAgent()` to create an `HttpsProxyAgent` that can be passed
|
||||||
|
* as the `httpAgent` option.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from "vscode"
|
||||||
|
import type { Agent } from "node:http"
|
||||||
|
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the effective proxy URL.
|
||||||
|
*
|
||||||
|
* Priority:
|
||||||
|
* 1. VSCode `http.proxy` setting (works in both local and remote mode)
|
||||||
|
* 2. Standard environment variables (`HTTPS_PROXY`, `HTTP_PROXY`, `https_proxy`, `http_proxy`)
|
||||||
|
*
|
||||||
|
* Returns `undefined` when no proxy is configured.
|
||||||
|
*/
|
||||||
|
export function resolveProxyUrl(): string | undefined {
|
||||||
|
// 1. VSCode setting
|
||||||
|
const httpConfig = vscode.workspace.getConfiguration("http")
|
||||||
|
const vsCodeProxy = httpConfig.get<string>("proxy")
|
||||||
|
if (typeof vsCodeProxy === "string" && vsCodeProxy.trim()) {
|
||||||
|
return vsCodeProxy.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Environment variables (standard precedence)
|
||||||
|
const envProxy =
|
||||||
|
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy
|
||||||
|
if (envProxy && envProxy.trim()) {
|
||||||
|
return envProxy.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether TLS certificate verification should be strict.
|
||||||
|
*
|
||||||
|
* Reads VSCode's `http.proxyStrictSSL` setting (defaults to `true`).
|
||||||
|
*/
|
||||||
|
function isStrictSSL(): boolean {
|
||||||
|
const httpConfig = vscode.workspace.getConfiguration("http")
|
||||||
|
return httpConfig.get<boolean>("proxyStrictSSL") ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redact credentials from a proxy URL for safe logging.
|
||||||
|
*/
|
||||||
|
function redactUrl(url: string): string {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url)
|
||||||
|
parsed.username = ""
|
||||||
|
parsed.password = ""
|
||||||
|
return parsed.toString()
|
||||||
|
} catch {
|
||||||
|
return url.replace(/\/\/[^@/]+@/g, "//REDACTED@")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an `HttpsProxyAgent` for SDKs that use `node-fetch` with custom
|
||||||
|
* agents (e.g. Anthropic SDK v0.x).
|
||||||
|
*
|
||||||
|
* Returns `undefined` when no proxy is configured, so callers can use:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* new Anthropic({ httpAgent: getProxyHttpAgent() })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The Anthropic SDK falls back to its default `agentkeepalive` agent when
|
||||||
|
* `httpAgent` is `undefined`, which is the correct behaviour when no proxy
|
||||||
|
* is needed.
|
||||||
|
*/
|
||||||
|
export function getProxyHttpAgent(): Agent | undefined {
|
||||||
|
const proxyUrl = resolveProxyUrl()
|
||||||
|
if (!proxyUrl) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const strictSSL = isStrictSSL()
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new HttpsProxyAgent(proxyUrl, {
|
||||||
|
rejectUnauthorized: strictSSL,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[ProxyFetch] Failed to create HttpsProxyAgent for "${redactUrl(proxyUrl)}": ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue