fix(core): externalize https-proxy-agent to fix proxy support (#26361)

This commit is contained in:
sotokisehiro
2026-05-15 07:34:36 +09:00
committed by GitHub
parent f6494f3862
commit 928a311fb0
4 changed files with 188 additions and 0 deletions
+1
View File
@@ -18448,6 +18448,7 @@
"glob": "^12.0.0",
"google-auth-library": "^9.11.0",
"html-to-text": "^9.0.5",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"ignore": "^7.0.0",
"ipaddr.js": "^1.9.1",
+1
View File
@@ -67,6 +67,7 @@
"glob": "^12.0.0",
"google-auth-library": "^9.11.0",
"html-to-text": "^9.0.5",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"ignore": "^7.0.0",
"ipaddr.js": "^1.9.1",
@@ -14,6 +14,8 @@ import {
} from './contentGenerator.js';
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
import { GoogleGenAI } from '@google/genai';
import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import type { Config } from '../config/config.js';
import { LoggingContentGenerator } from './loggingContentGenerator.js';
import { loadApiKey } from './apiKeyCredentialStorage.js';
@@ -464,6 +466,174 @@ describe('createContentGenerator', () => {
);
});
it('should inject HttpsProxyAgent into googleAuthOptions when proxy URL uses https://', async () => {
const mockConfigWithProxy = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getProxy: vi.fn().mockReturnValue('https://proxy.example.com:8080'),
getUsageStatisticsEnabled: () => false,
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
await createContentGenerator(
{
apiKey: 'test-api-key',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
proxy: 'https://proxy.example.com:8080',
},
mockConfigWithProxy,
);
expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
googleAuthOptions: {
clientOptions: {
transporterOptions: {
agent: expect.any(HttpsProxyAgent),
},
},
},
}),
);
});
it('should still use HttpsProxyAgent for HTTPS destinations even when proxy URL uses http://', async () => {
const mockConfigWithProxy = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getProxy: vi.fn().mockReturnValue('http://proxy.example.com:8080'),
getUsageStatisticsEnabled: () => false,
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
await createContentGenerator(
{
apiKey: 'test-api-key',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
proxy: 'http://proxy.example.com:8080',
},
mockConfigWithProxy,
);
expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
googleAuthOptions: {
clientOptions: {
transporterOptions: {
agent: expect.any(HttpsProxyAgent),
},
},
},
}),
);
});
it('should inject HttpProxyAgent when destination baseUrl uses http://', async () => {
const mockConfigWithProxy = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getProxy: vi.fn().mockReturnValue('http://proxy.example.com:8080'),
getUsageStatisticsEnabled: () => false,
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
vi.stubEnv('GOOGLE_VERTEX_BASE_URL', 'http://localhost:9999');
await createContentGenerator(
{
apiKey: 'test-api-key',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
proxy: 'http://proxy.example.com:8080',
},
mockConfigWithProxy,
);
expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
googleAuthOptions: {
clientOptions: {
transporterOptions: {
agent: expect.any(HttpProxyAgent),
},
},
},
}),
);
});
it('should trim whitespace from proxy URL before instantiating agent', async () => {
const mockConfigWithProxy = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getProxy: vi.fn().mockReturnValue(' https://proxy.example.com:8080 '),
getUsageStatisticsEnabled: () => false,
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
await createContentGenerator(
{
apiKey: 'test-api-key',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
proxy: ' https://proxy.example.com:8080 ',
},
mockConfigWithProxy,
);
expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
googleAuthOptions: {
clientOptions: {
transporterOptions: {
agent: expect.any(HttpsProxyAgent),
},
},
},
}),
);
});
it('should not include googleAuthOptions when no proxy is configured', async () => {
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
await createContentGenerator(
{
apiKey: 'test-api-key',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
},
mockConfig,
);
const callArg = vi.mocked(GoogleGenAI).mock.calls[0][0] as Record<
string,
unknown
>;
expect(callArg).not.toHaveProperty('googleAuthOptions');
});
it('should pass api key as Authorization Header when GEMINI_API_KEY_AUTH_MECHANISM is set to bearer', async () => {
const mockConfig = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
@@ -13,6 +13,8 @@ import {
type EmbedContentResponse,
type EmbedContentParameters,
} from '@google/genai';
import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import * as os from 'node:os';
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
import { isCloudShell } from '../ide/detect-ide.js';
@@ -343,6 +345,13 @@ export async function createContentGenerator(
httpOptions.baseUrl = baseUrl;
}
const proxyUrl = config.proxy?.trim();
const proxyAgent = proxyUrl
? baseUrl?.startsWith('http://')
? new HttpProxyAgent(proxyUrl)
: new HttpsProxyAgent(proxyUrl)
: undefined;
const googleGenAI = new GoogleGenAI({
apiKey:
config.authType === AuthType.GATEWAY
@@ -353,6 +362,13 @@ export async function createContentGenerator(
vertexai: config.vertexai ?? config.authType === AuthType.USE_VERTEX_AI,
httpOptions,
...(apiVersionEnv && { apiVersion: apiVersionEnv }),
...(proxyAgent && {
googleAuthOptions: {
clientOptions: {
transporterOptions: { agent: proxyAgent },
},
},
}),
});
return new LoggingContentGenerator(googleGenAI.models, gcConfig);
}