Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,20 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `"ask"`
- **Values:** `"ask"`, `"always"`, `"never"`

- **`billing.vertexAi.requestType`** (enum):
- **Description:** Sets the X-Vertex-AI-LLM-Request-Type header for Vertex AI
requests.
- **Default:** `undefined`
- **Values:** `"dedicated"`, `"shared"`
- **Requires restart:** Yes

- **`billing.vertexAi.sharedRequestType`** (enum):
- **Description:** Sets the X-Vertex-AI-LLM-Shared-Request-Type header for
Vertex AI requests.
- **Default:** `undefined`
- **Values:** `"priority"`, `"flex"`
- **Requires restart:** Yes

#### `model`

- **`model.name`** (string):
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ export async function loadCliConfig(
recordResponses: argv.recordResponses,
retryFetchErrors: settings.general?.retryFetchErrors,
billing: settings.billing,
vertexAiRouting: settings.billing?.vertexAi,
maxAttempts: settings.general?.maxAttempts,
ptyInfo: ptyInfo?.name,
disableLLMCorrection: settings.tools?.disableLLMCorrection,
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/config/settingsSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,22 @@ describe('SettingsSchema', () => {
).toBe(false);
});

it('should have Vertex AI routing settings in schema', () => {
const vertexAi =
getSettingsSchema().billing.properties.vertexAi.properties;

expect(vertexAi.requestType).toBeDefined();
expect(vertexAi.requestType.type).toBe('enum');
expect(
vertexAi.requestType.options?.map((option) => option.value),
).toEqual(['dedicated', 'shared']);
expect(vertexAi.sharedRequestType).toBeDefined();
expect(vertexAi.sharedRequestType.type).toBe('enum');
expect(
vertexAi.sharedRequestType.options?.map((option) => option.value),
).toEqual(['priority', 'flex']);
});

it('should have folderTrustFeature setting in schema', () => {
expect(
getSettingsSchema().security.properties.folderTrust.properties.enabled,
Expand Down
40 changes: 40 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type AgentOverride,
type CustomTheme,
type SandboxConfig,
type VertexAiRoutingConfig,
} from '@google/gemini-cli-core';
import type { SessionRetentionSettings } from './settings.js';
import { DEFAULT_MIN_RETENTION } from '../utils/sessionCleanup.js';
Expand Down Expand Up @@ -990,6 +991,45 @@ const SETTINGS_SCHEMA = {
{ value: 'never', label: 'Never use credits' },
],
},
vertexAi: {
type: 'object',
label: 'Vertex AI',
category: 'Advanced',
requiresRestart: true,
default: undefined as VertexAiRoutingConfig | undefined,
description: 'Vertex AI request routing settings.',
showInDialog: false,
properties: {
requestType: {
type: 'enum',
label: 'Vertex AI Request Type',
category: 'Advanced',
requiresRestart: true,
default: undefined as VertexAiRoutingConfig['requestType'],
description:
'Sets the X-Vertex-AI-LLM-Request-Type header for Vertex AI requests.',
showInDialog: false,
options: [
{ value: 'dedicated', label: 'Dedicated' },
{ value: 'shared', label: 'Shared' },
],
},
sharedRequestType: {
type: 'enum',
label: 'Vertex AI Shared Request Type',
category: 'Advanced',
requiresRestart: true,
default: undefined as VertexAiRoutingConfig['sharedRequestType'],
description:
'Sets the X-Vertex-AI-LLM-Shared-Request-Type header for Vertex AI requests.',
showInDialog: false,
options: [
{ value: 'priority', label: 'Priority' },
{ value: 'flex', label: 'Flex' },
],
},
},
},
},
},

Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,12 +783,37 @@ describe('Server Config (config.ts)', () => {
undefined,
undefined,
undefined,
undefined,
);
// Verify that contentGeneratorConfig is updated
expect(config.getContentGeneratorConfig()).toEqual(mockContentConfig);
expect(GeminiClient).toHaveBeenCalledWith(config);
});

it('should pass Vertex AI routing settings when refreshing auth', async () => {
const vertexAiRouting = {
requestType: 'shared' as const,
sharedRequestType: 'priority' as const,
};
const config = new Config({
...baseParams,
vertexAiRouting,
});

vi.mocked(createContentGeneratorConfig).mockResolvedValue({});

await config.refreshAuth(AuthType.USE_VERTEX_AI);

expect(createContentGeneratorConfig).toHaveBeenCalledWith(
config,
AuthType.USE_VERTEX_AI,
undefined,
undefined,
undefined,
vertexAiRouting,
);
});

it('should reset model availability status', async () => {
const config = new Config(baseParams);
const service = config.getModelAvailabilityService();
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
createContentGeneratorConfig,
type ContentGenerator,
type ContentGeneratorConfig,
type VertexAiRoutingConfig,
} from '../core/contentGenerator.js';
import type { OverageStrategy } from '../billing/billing.js';
import { PromptRegistry } from '../prompts/prompt-registry.js';
Expand Down Expand Up @@ -729,6 +730,7 @@ export interface ConfigParameters {
billing?: {
overageStrategy?: OverageStrategy;
};
vertexAiRouting?: VertexAiRoutingConfig;
}

export class Config implements McpContext, AgentLoopContext {
Expand Down Expand Up @@ -934,6 +936,7 @@ export class Config implements McpContext, AgentLoopContext {
private readonly billing: {
overageStrategy: OverageStrategy;
};
private readonly vertexAiRouting: VertexAiRoutingConfig | undefined;

private readonly enableAgents: boolean;
private agents: AgentSettings;
Expand Down Expand Up @@ -1358,6 +1361,7 @@ export class Config implements McpContext, AgentLoopContext {
this.billing = {
overageStrategy: params.billing?.overageStrategy ?? 'ask',
};
this.vertexAiRouting = params.vertexAiRouting;

if (params.contextFileName) {
setGeminiMdFilename(params.contextFileName);
Expand Down Expand Up @@ -1545,6 +1549,7 @@ export class Config implements McpContext, AgentLoopContext {
apiKey,
baseUrl,
customHeaders,
this.vertexAiRouting,
);
this.contentGenerator = await createContentGenerator(
newContentGeneratorConfig,
Expand Down
57 changes: 57 additions & 0 deletions packages/core/src/core/contentGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,44 @@ describe('createContentGenerator', () => {
);
});

it('should include Vertex AI routing headers for Vertex AI requests', 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',
vertexai: true,
authType: AuthType.USE_VERTEX_AI,
vertexAiRouting: {
requestType: 'shared',
sharedRequestType: 'priority',
},
},
mockConfig,
);

expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
httpOptions: expect.objectContaining({
headers: expect.objectContaining({
'X-Vertex-AI-LLM-Request-Type': 'shared',
'X-Vertex-AI-LLM-Shared-Request-Type': 'priority',
}),
}),
}),
);
});

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'),
Expand Down Expand Up @@ -887,6 +925,25 @@ describe('createContentGeneratorConfig', () => {
expect(config.vertexai).toBe(true);
});

it('should include Vertex AI routing settings in content generator config', async () => {
vi.stubEnv('GOOGLE_API_KEY', 'env-google-key');
const vertexAiRouting = {
requestType: 'shared' as const,
sharedRequestType: 'priority' as const,
};

const config = await createContentGeneratorConfig(
mockConfig,
AuthType.USE_VERTEX_AI,
undefined,
undefined,
undefined,
vertexAiRouting,
);

expect(config.vertexAiRouting).toEqual(vertexAiRouting);
});

it('should configure for Vertex AI using GCP project and location when set', async () => {
vi.stubEnv('GOOGLE_API_KEY', undefined);
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'env-gcp-project');
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/core/contentGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,21 @@ export type ContentGeneratorConfig = {
proxy?: string;
baseUrl?: string;
customHeaders?: Record<string, string>;
vertexAiRouting?: VertexAiRoutingConfig;
};

export type VertexAiRequestType = 'dedicated' | 'shared';
export type VertexAiSharedRequestType = 'priority' | 'flex';

export interface VertexAiRoutingConfig {
requestType?: VertexAiRequestType;
sharedRequestType?: VertexAiSharedRequestType;
}

const LOCAL_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]'];
const VERTEX_AI_REQUEST_TYPE_HEADER = 'X-Vertex-AI-LLM-Request-Type';
const VERTEX_AI_SHARED_REQUEST_TYPE_HEADER =
'X-Vertex-AI-LLM-Shared-Request-Type';

function validateBaseUrl(baseUrl: string): void {
let url: URL;
Expand All @@ -122,6 +134,7 @@ export async function createContentGeneratorConfig(
apiKey?: string,
baseUrl?: string,
customHeaders?: Record<string, string>,
vertexAiRouting?: VertexAiRoutingConfig,
): Promise<ContentGeneratorConfig> {
const geminiApiKey =
apiKey ||
Expand All @@ -140,6 +153,7 @@ export async function createContentGeneratorConfig(
proxy: config?.getProxy(),
baseUrl,
customHeaders,
vertexAiRouting,
};

// If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now
Expand Down Expand Up @@ -280,6 +294,21 @@ export async function createContentGenerator(
if (config.customHeaders) {
headers = { ...headers, ...config.customHeaders };
}
if (
config.authType === AuthType.USE_VERTEX_AI &&
config.vertexAiRouting
) {
const { requestType, sharedRequestType } = config.vertexAiRouting;
headers = {
...headers,
...(requestType
? { [VERTEX_AI_REQUEST_TYPE_HEADER]: requestType }
: {}),
...(sharedRequestType
? { [VERTEX_AI_SHARED_REQUEST_TYPE_HEADER]: sharedRequestType }
: {}),
};
}
if (gcConfig?.getUsageStatisticsEnabled()) {
const installationManager = new InstallationManager();
const installationId = installationManager.getInstallationId();
Expand Down
23 changes: 23 additions & 0 deletions schemas/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,29 @@
"default": "ask",
"type": "string",
"enum": ["ask", "always", "never"]
},
"vertexAi": {
"title": "Vertex AI",
"description": "Vertex AI request routing settings.",
"markdownDescription": "Vertex AI request routing settings.\n\n- Category: `Advanced`\n- Requires restart: `yes`",
"type": "object",
"properties": {
"requestType": {
"title": "Vertex AI Request Type",
"description": "Sets the X-Vertex-AI-LLM-Request-Type header for Vertex AI requests.",
"markdownDescription": "Sets the X-Vertex-AI-LLM-Request-Type header for Vertex AI requests.\n\n- Category: `Advanced`\n- Requires restart: `yes`",
"type": "string",
"enum": ["dedicated", "shared"]
},
"sharedRequestType": {
"title": "Vertex AI Shared Request Type",
"description": "Sets the X-Vertex-AI-LLM-Shared-Request-Type header for Vertex AI requests.",
"markdownDescription": "Sets the X-Vertex-AI-LLM-Shared-Request-Type header for Vertex AI requests.\n\n- Category: `Advanced`\n- Requires restart: `yes`",
"type": "string",
"enum": ["priority", "flex"]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down
Loading