diff --git a/- b/- new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.gemini/config.yaml b/.gemini/config.yaml index cbfb0c8059d..cb70ce967cf 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -3,10 +3,13 @@ have_fun: false code_review: disable: false - comment_severity_threshold: 'HIGH' - max_review_comments: -1 + comment_severity_threshold: 'MEDIUM' # Changed from HIGH to MEDIUM + max_review_comments: 20 # Changed from -1 to 20 pull_request_opened: help: false summary: true code_review: true -ignore_patterns: [] +ignore_patterns: + - "**/*.md" + - "**/package-lock.json" + - "**/dist/" \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8377d34af0e..a9b305b086d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,11 @@ -# By default, require reviews from the maintainers for all files. -* @google-gemini/gemini-cli-maintainers +# By default, require reviews from the maintainers for all files + +- @google-gemini/gemini-cli-maintainers + +# Require reviews from the release approvers for critical files + +# These patterns override the rule above -# Require reviews from the release approvers for critical files. -# These patterns override the rule above. /package.json @google-gemini/gemini-cli-askmode-approvers /package-lock.json @google-gemini/gemini-cli-askmode-approvers /GEMINI.md @google-gemini/gemini-cli-askmode-approvers diff --git a/.vscode/settings.json b/.vscode/settings.json index 3661ecf9c2a..d490ce08ade 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,7 @@ "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "vitest.disableWorkspaceWarning": true + "vitest.disableWorkspaceWarning": true, + "githubPullRequests.ignoredPullRequestBranches": ["main"], + "github.copilot.nextEditSuggestions.enabled": true } diff --git a/Happy b/Happy new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/cli/src/config/geminiApiKey.test.ts b/packages/cli/src/config/geminiApiKey.test.ts new file mode 100644 index 00000000000..70d194488f6 --- /dev/null +++ b/packages/cli/src/config/geminiApiKey.test.ts @@ -0,0 +1,135 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthType , Config } from '@google/gemini-cli-core'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createContentGeneratorConfig } from '@google/gemini-cli-core/core/contentGenerator'; +import { GoogleGenAI } from '@google/genai'; + +vi.mock('@google/genai', () => ({ + GoogleGenAI: vi.fn(() => ({ + models: {}, + })), +})); + +vi.mock('@google/gemini-cli-core/core/apiKeyCredentialStorage', () => ({ + loadApiKey: vi.fn(() => Promise.resolve(undefined)), +})); + +describe('Gemini API Key Configuration', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubEnv('GEMINI_API_KEY', undefined); + }); + + it('should use geminiApiKey from Config when provided', async () => { + const mockApiKey = 'TEST_API_KEY_FROM_CONFIG'; + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + geminiApiKey: mockApiKey, + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(mockApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: mockApiKey, + }), + ); + }); + + it('should prioritize GEMINI_API_KEY environment variable over loadedApiKey if config.geminiApiKey is not set', async () => { + const envApiKey = 'TEST_API_KEY_FROM_ENV'; + vi.stubEnv('GEMINI_API_KEY', envApiKey); + + const loadApiKey = vi.mocked( + (await vi.importActual( + '@google/gemini-cli-core/core/apiKeyCredentialStorage', + )) as { loadApiKey: () => Promise }, + ).loadApiKey; + loadApiKey.mockResolvedValueOnce('LOADED_API_KEY'); + + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(envApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: envApiKey, + }), + ); + expect(loadApiKey).toHaveBeenCalled(); + }); + + it('should prioritize loadedApiKey if config.geminiApiKey and GEMINI_API_KEY env are not set', async () => { + const loadedApiKey = 'LOADED_API_KEY'; + const loadApiKey = vi.mocked( + (await vi.importActual( + '@google/gemini-cli-core/core/apiKeyCredentialStorage', + )) as { loadApiKey: () => Promise }, + ).loadApiKey; + loadApiKey.mockResolvedValueOnce(loadedApiKey); + + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(loadedApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: loadedApiKey, + }), + ); + expect(loadApiKey).toHaveBeenCalled(); + }); + + it('should not leak api key in proxy config', async () => { + const mockApiKey = 'TEST_API_KEY_FROM_CONFIG'; + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + geminiApiKey: mockApiKey, + proxy: `http://user:${mockApiKey}@someproxy.com`, + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.proxy).toBe('http://user:***@someproxy.com'); + }); +}); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 96ec8c9ff13..bd4f33d17b6 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1331,6 +1331,15 @@ const SETTINGS_SCHEMA = { description: 'Whether to use an external authentication flow.', showInDialog: false, }, + geminiApiKey: { + type: 'string', + label: 'Gemini API Key', + category: 'Security', + requiresRestart: true, + default: undefined as string | undefined, + description: 'Your Gemini API Key.', + showInDialog: false, + }, }, }, }, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 15a1bcb85f5..1495e2ba1f7 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -368,6 +368,7 @@ export interface ConfigParameters { policyEngineConfig?: PolicyEngineConfig; output?: OutputSettings; disableModelRouterForAuth?: AuthType[]; + geminiApiKey?: string; codebaseInvestigatorSettings?: CodebaseInvestigatorSettings; cliHelpAgentSettings?: CliHelpAgentSettings; continueOnFailedApiCall?: boolean; diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 740bede47c9..3f6421a0908 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -66,7 +66,7 @@ export async function createContentGeneratorConfig( authType: AuthType | undefined, ): Promise { const geminiApiKey = - process.env['GEMINI_API_KEY'] || (await loadApiKey()) || undefined; + const googleApiKey = process.env['GOOGLE_API_KEY'] || undefined; const googleCloudProject = process.env['GOOGLE_CLOUD_PROJECT'] || diff --git a/vitest.explorer-1.32.1.vsix b/vitest.explorer-1.32.1.vsix new file mode 100644 index 00000000000..86d1d00bedd Binary files /dev/null and b/vitest.explorer-1.32.1.vsix differ