Merge pull request #2745 from QwenLM/fix/proxy-url-compability
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run

fix: normalize proxy URLs to support addresses without protocol prefix
This commit is contained in:
DennisYu07 2026-04-01 14:30:29 +08:00 committed by GitHub
commit 4b05c74b02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 164 additions and 3 deletions

View file

@ -108,6 +108,7 @@ import { shouldDefaultToNodePty } from '../utils/shell-utils.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
import { type ToolName } from '../utils/tool-utils.js';
import { getErrorMessage } from '../utils/errors.js';
import { normalizeProxyUrl } from '../utils/proxyUtils.js';
// Local config modules
import type { FileFilteringOptions } from './constants.js';
@ -747,8 +748,9 @@ export class Config {
initializeTelemetry(this);
}
if (this.getProxy()) {
setGlobalDispatcher(new ProxyAgent(this.getProxy() as string));
const proxyUrl = this.getProxy();
if (proxyUrl) {
setGlobalDispatcher(new ProxyAgent(proxyUrl));
}
this.geminiClient = new GeminiClient(this);
this.chatRecordingService = this.chatRecordingEnabled
@ -1717,7 +1719,7 @@ export class Config {
}
getProxy(): string | undefined {
return this.proxy;
return normalizeProxyUrl(this.proxy);
}
getWorkingDir(): string {

View file

@ -217,6 +217,7 @@ export * from './utils/pathReader.js';
export * from './utils/paths.js';
export * from './utils/projectSummary.js';
export * from './utils/promptIdContext.js';
export * from './utils/proxyUtils.js';
export * from './utils/quotaErrorDetection.js';
export * from './utils/readManyFiles.js';
export * from './utils/request-tokenizer/supportedImageFormats.js';

View file

@ -0,0 +1,105 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { normalizeProxyUrl } from './proxyUtils.js';
describe('normalizeProxyUrl', () => {
it('should return undefined for undefined input', () => {
expect(normalizeProxyUrl(undefined)).toBeUndefined();
});
it('should return undefined for empty string', () => {
expect(normalizeProxyUrl('')).toBeUndefined();
});
it('should return undefined for whitespace-only string', () => {
expect(normalizeProxyUrl(' ')).toBeUndefined();
});
it('should add http:// prefix to proxy URL without protocol', () => {
expect(normalizeProxyUrl('127.0.0.1:7860')).toBe('http://127.0.0.1:7860');
});
it('should add http:// prefix to proxy URL with port only', () => {
expect(normalizeProxyUrl('localhost:8080')).toBe('http://localhost:8080');
});
it('should not modify URL that already has http:// prefix', () => {
expect(normalizeProxyUrl('http://127.0.0.1:7860')).toBe(
'http://127.0.0.1:7860',
);
});
it('should not modify URL that already has https:// prefix', () => {
expect(normalizeProxyUrl('https://proxy.example.com:443')).toBe(
'https://proxy.example.com:443',
);
});
it('should handle HTTP:// prefix (case insensitive)', () => {
expect(normalizeProxyUrl('HTTP://127.0.0.1:7860')).toBe(
'HTTP://127.0.0.1:7860',
);
});
it('should handle HTTPS:// prefix (case insensitive)', () => {
expect(normalizeProxyUrl('HTTPS://proxy.example.com:443')).toBe(
'HTTPS://proxy.example.com:443',
);
});
it('should handle proxy URL with authentication', () => {
expect(normalizeProxyUrl('user:pass@proxy.example.com:8080')).toBe(
'http://user:pass@proxy.example.com:8080',
);
});
it('should handle proxy URL with authentication and http:// prefix', () => {
expect(normalizeProxyUrl('http://user:pass@proxy.example.com:8080')).toBe(
'http://user:pass@proxy.example.com:8080',
);
});
it('should trim whitespace from proxy URL', () => {
expect(normalizeProxyUrl(' 127.0.0.1:7860 ')).toBe(
'http://127.0.0.1:7860',
);
});
it('should handle IPv6 addresses', () => {
expect(normalizeProxyUrl('[::1]:8080')).toBe('http://[::1]:8080');
});
it('should handle IPv6 addresses with http:// prefix', () => {
expect(normalizeProxyUrl('http://[::1]:8080')).toBe('http://[::1]:8080');
});
// SOCKS proxy tests - should throw error since undici doesn't support SOCKS
it('should throw error for socks:// proxy URL', () => {
expect(() => normalizeProxyUrl('socks://proxy.example.com:1080')).toThrow(
'SOCKS proxy is not supported',
);
});
it('should throw error for socks4:// proxy URL', () => {
expect(() => normalizeProxyUrl('socks4://proxy.example.com:1080')).toThrow(
'SOCKS proxy is not supported',
);
});
it('should throw error for socks5:// proxy URL', () => {
expect(() => normalizeProxyUrl('socks5://proxy.example.com:1080')).toThrow(
'SOCKS proxy is not supported',
);
});
it('should throw error for SOCKS5:// proxy URL (case insensitive)', () => {
expect(() => normalizeProxyUrl('SOCKS5://proxy.example.com:1080')).toThrow(
'SOCKS proxy is not supported',
);
});
});

View file

@ -0,0 +1,53 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Normalizes a proxy URL to ensure it has a valid protocol prefix.
*
* Many proxy tools and environment variables provide proxy addresses without
* a protocol prefix (e.g., "127.0.0.1:7860" instead of "http://127.0.0.1:7860").
* This function adds the "http://" prefix if missing, since HTTP proxies are
* the most common default.
*
* Note: Only HTTP and HTTPS proxies are supported. SOCKS proxies (socks://,
* socks4://, socks5://) are NOT supported because the underlying undici library
* does not support them. See: https://github.com/nodejs/undici/issues/2224
*
* @param proxyUrl - The proxy URL to normalize
* @returns The normalized proxy URL with protocol prefix, or undefined if input is undefined/empty
* @throws Error if a SOCKS proxy URL is provided
*/
export function normalizeProxyUrl(
proxyUrl: string | undefined,
): string | undefined {
if (!proxyUrl) {
return undefined;
}
const trimmed = proxyUrl.trim();
if (!trimmed) {
return undefined;
}
// Check if the URL already has a protocol prefix
// Only support http and https protocols (undici limitation)
if (/^https?:\/\//i.test(trimmed)) {
return trimmed;
}
// Reject SOCKS proxies - undici does not support them
if (/^socks[45]?:\/\//i.test(trimmed)) {
throw new Error(
`SOCKS proxy is not supported. The underlying HTTP client (undici) only supports HTTP and HTTPS proxies. ` +
`Please use an HTTP/HTTPS proxy instead, or set up a SOCKS-to-HTTP proxy converter. ` +
`See: https://github.com/nodejs/undici/issues/2224`,
);
}
// Add http:// prefix for proxy URLs without protocol
// HTTP is the default for most proxy configurations
return `http://${trimmed}`;
}