mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-19 17:50:37 -07:00
fix(core): honor GOOGLE_GEMINI_BASE_URL and GOOGLE_VERTEX_BASE_URL (#25357)
This commit is contained in:
committed by
GitHub
parent
c5ad0abb5d
commit
cb289e0724
@@ -148,7 +148,7 @@ describe('createContentGenerator', () => {
|
||||
);
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.stringMatching(
|
||||
@@ -365,7 +365,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -409,7 +409,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -443,7 +443,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -481,7 +481,7 @@ describe('createContentGenerator', () => {
|
||||
);
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: {
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -517,7 +517,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -550,7 +550,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -589,7 +589,7 @@ describe('createContentGenerator', () => {
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: undefined,
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'User-Agent': expect.any(String),
|
||||
@@ -638,6 +638,193 @@ describe('createContentGenerator', () => {
|
||||
apiVersion: 'v1alpha',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass baseUrl to GoogleGenAI when GOOGLE_GEMINI_BASE_URL is set', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://gemini.test.local');
|
||||
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
|
||||
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_GEMINI,
|
||||
);
|
||||
await createContentGenerator(config, mockConfig);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: 'test-api-key',
|
||||
vertexai: false,
|
||||
httpOptions: expect.objectContaining({
|
||||
baseUrl: 'https://gemini.test.local',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass baseUrl to GoogleGenAI when GOOGLE_VERTEX_BASE_URL is set', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
vi.stubEnv('GOOGLE_VERTEX_BASE_URL', 'https://vertex.test.local');
|
||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'my-project');
|
||||
vi.stubEnv('GOOGLE_CLOUD_LOCATION', 'us-central1');
|
||||
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_VERTEX_AI,
|
||||
);
|
||||
await createContentGenerator(config, mockConfig);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: undefined,
|
||||
vertexai: true,
|
||||
httpOptions: expect.objectContaining({
|
||||
baseUrl: 'https://vertex.test.local',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prefer GOOGLE_VERTEX_BASE_URL when authType is USE_VERTEX_AI without inferred vertex credentials', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://gemini.test.local');
|
||||
vi.stubEnv('GOOGLE_VERTEX_BASE_URL', 'https://vertex.test.local');
|
||||
|
||||
await createContentGenerator(
|
||||
{
|
||||
authType: AuthType.USE_VERTEX_AI,
|
||||
},
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: undefined,
|
||||
vertexai: true,
|
||||
httpOptions: expect.objectContaining({
|
||||
baseUrl: 'https://vertex.test.local',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prefer an explicit baseUrl over GOOGLE_GEMINI_BASE_URL', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://env.test.local');
|
||||
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
|
||||
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_GEMINI,
|
||||
undefined,
|
||||
'https://explicit.test.local',
|
||||
);
|
||||
await createContentGenerator(config, mockConfig);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
httpOptions: expect.objectContaining({
|
||||
baseUrl: 'https://explicit.test.local',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow localhost baseUrl overrides over http', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
|
||||
await createContentGenerator(
|
||||
{
|
||||
apiKey: 'test-api-key',
|
||||
authType: AuthType.USE_GEMINI,
|
||||
baseUrl: 'http://127.0.0.1:8080',
|
||||
},
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
httpOptions: expect.objectContaining({
|
||||
baseUrl: 'http://127.0.0.1:8080',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject invalid custom baseUrl values', async () => {
|
||||
await expect(
|
||||
createContentGenerator(
|
||||
{
|
||||
apiKey: 'test-api-key',
|
||||
authType: AuthType.USE_GEMINI,
|
||||
baseUrl: 'not-a-url',
|
||||
},
|
||||
mockConfig,
|
||||
),
|
||||
).rejects.toThrow('Invalid custom base URL: not-a-url');
|
||||
});
|
||||
|
||||
it('should reject non-https remote custom baseUrl values', async () => {
|
||||
await expect(
|
||||
createContentGenerator(
|
||||
{
|
||||
apiKey: 'test-api-key',
|
||||
authType: AuthType.USE_GEMINI,
|
||||
baseUrl: 'http://example.com',
|
||||
},
|
||||
mockConfig,
|
||||
),
|
||||
).rejects.toThrow('Custom base URL must use HTTPS unless it is localhost.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContentGeneratorConfig', () => {
|
||||
|
||||
@@ -101,6 +101,21 @@ export type ContentGeneratorConfig = {
|
||||
customHeaders?: Record<string, string>;
|
||||
};
|
||||
|
||||
const LOCAL_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]'];
|
||||
|
||||
function validateBaseUrl(baseUrl: string): void {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(baseUrl);
|
||||
} catch {
|
||||
throw new Error(`Invalid custom base URL: ${baseUrl}`);
|
||||
}
|
||||
|
||||
if (url.protocol !== 'https:' && !LOCAL_HOSTNAMES.includes(url.hostname)) {
|
||||
throw new Error('Custom base URL must use HTTPS unless it is localhost.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function createContentGeneratorConfig(
|
||||
config: Config,
|
||||
authType: AuthType | undefined,
|
||||
@@ -273,18 +288,32 @@ export async function createContentGenerator(
|
||||
'x-gemini-api-privileged-user-id': `${installationId}`,
|
||||
};
|
||||
}
|
||||
let baseUrl = config.baseUrl;
|
||||
if (!baseUrl) {
|
||||
const envBaseUrl =
|
||||
config.authType === AuthType.USE_VERTEX_AI
|
||||
? process.env['GOOGLE_VERTEX_BASE_URL']
|
||||
: process.env['GOOGLE_GEMINI_BASE_URL'];
|
||||
if (envBaseUrl) {
|
||||
validateBaseUrl(envBaseUrl);
|
||||
baseUrl = envBaseUrl;
|
||||
}
|
||||
} else {
|
||||
validateBaseUrl(baseUrl);
|
||||
}
|
||||
|
||||
const httpOptions: {
|
||||
baseUrl?: string;
|
||||
headers: Record<string, string>;
|
||||
} = { headers };
|
||||
|
||||
if (config.baseUrl) {
|
||||
httpOptions.baseUrl = config.baseUrl;
|
||||
if (baseUrl) {
|
||||
httpOptions.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
const googleGenAI = new GoogleGenAI({
|
||||
apiKey: config.apiKey === '' ? undefined : config.apiKey,
|
||||
vertexai: config.vertexai,
|
||||
vertexai: config.vertexai ?? config.authType === AuthType.USE_VERTEX_AI,
|
||||
httpOptions,
|
||||
...(apiVersionEnv && { apiVersion: apiVersionEnv }),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user