mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-23 08:10:57 -07:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f93b9f849c | |||
| 55d0bf5f8e | |||
| 81217cc906 | |||
| 4325e434f9 |
Generated
+2
@@ -2150,6 +2150,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
|
||||
"integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.9",
|
||||
"ajv": "^8.17.1",
|
||||
@@ -17142,6 +17143,7 @@
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/gemini-cli-devtools": "file:../devtools",
|
||||
"@google/genai": "1.41.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/gemini-cli-devtools": "file:../devtools",
|
||||
"@google/genai": "1.41.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
@@ -23,17 +23,15 @@ import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
|
||||
export const GITHUB_WORKFLOW_PATHS = [
|
||||
'gemini-assistant/gemini-invoke.yml',
|
||||
'gemini-assistant/gemini-plan-execute.yml',
|
||||
'gemini-dispatch/gemini-dispatch.yml',
|
||||
'issue-triage/gemini-scheduled-triage.yml',
|
||||
'gemini-assistant/gemini-invoke.yml',
|
||||
'issue-triage/gemini-triage.yml',
|
||||
'issue-triage/gemini-scheduled-triage.yml',
|
||||
'pr-review/gemini-review.yml',
|
||||
];
|
||||
|
||||
export const GITHUB_COMMANDS_PATHS = [
|
||||
'gemini-assistant/gemini-invoke.toml',
|
||||
'gemini-assistant/gemini-plan-execute.toml',
|
||||
'issue-triage/gemini-scheduled-triage.toml',
|
||||
'issue-triage/gemini-triage.toml',
|
||||
'pr-review/gemini-review.toml',
|
||||
|
||||
@@ -177,14 +177,6 @@ describe('GeminiAgent', () => {
|
||||
|
||||
expect(response.protocolVersion).toBe(acp.PROTOCOL_VERSION);
|
||||
expect(response.authMethods).toHaveLength(3);
|
||||
const geminiAuth = response.authMethods?.find(
|
||||
(m) => m.id === AuthType.USE_GEMINI,
|
||||
);
|
||||
expect(geminiAuth?._meta).toEqual({
|
||||
'api-key': {
|
||||
provider: 'google',
|
||||
},
|
||||
});
|
||||
expect(response.agentCapabilities?.loadSession).toBe(true);
|
||||
});
|
||||
|
||||
@@ -195,7 +187,6 @@ describe('GeminiAgent', () => {
|
||||
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
undefined,
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
@@ -204,25 +195,6 @@ describe('GeminiAgent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should authenticate correctly with api-key in _meta', async () => {
|
||||
await agent.authenticate({
|
||||
methodId: AuthType.USE_GEMINI,
|
||||
_meta: {
|
||||
'api-key': 'test-api-key',
|
||||
},
|
||||
} as unknown as acp.AuthenticateRequest);
|
||||
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.USE_GEMINI,
|
||||
'test-api-key',
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'security.auth.selectedType',
|
||||
AuthType.USE_GEMINI,
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a new session', async () => {
|
||||
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
|
||||
apiKey: 'test-key',
|
||||
|
||||
@@ -37,17 +37,12 @@ import {
|
||||
partListUnionToString,
|
||||
LlmRole,
|
||||
ApprovalMode,
|
||||
getVersion,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as acp from '@agentclientprotocol/sdk';
|
||||
import { AcpFileSystemService } from './fileSystemService.js';
|
||||
import { getAcpErrorMessage } from './acpErrors.js';
|
||||
import { Readable, Writable } from 'node:stream';
|
||||
|
||||
function hasMeta(obj: unknown): obj is { _meta?: Record<string, unknown> } {
|
||||
return typeof obj === 'object' && obj !== null && '_meta' in obj;
|
||||
}
|
||||
import type { Content, Part, FunctionCall } from '@google/genai';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import { SettingScope, loadSettings } from '../config/settings.js';
|
||||
@@ -86,7 +81,6 @@ export async function runZedIntegration(
|
||||
export class GeminiAgent {
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
private clientCapabilities: acp.ClientCapabilities | undefined;
|
||||
private apiKey: string | undefined;
|
||||
|
||||
constructor(
|
||||
private config: Config,
|
||||
@@ -103,35 +97,25 @@ export class GeminiAgent {
|
||||
{
|
||||
id: AuthType.LOGIN_WITH_GOOGLE,
|
||||
name: 'Log in with Google',
|
||||
description: 'Log in with your Google account',
|
||||
description: null,
|
||||
},
|
||||
{
|
||||
id: AuthType.USE_GEMINI,
|
||||
name: 'Gemini API key',
|
||||
description: 'Use an API key with Gemini Developer API',
|
||||
_meta: {
|
||||
'api-key': {
|
||||
provider: 'google',
|
||||
},
|
||||
},
|
||||
name: 'Use Gemini API key',
|
||||
description:
|
||||
'Requires setting the `GEMINI_API_KEY` environment variable',
|
||||
},
|
||||
{
|
||||
id: AuthType.USE_VERTEX_AI,
|
||||
name: 'Vertex AI',
|
||||
description: 'Use an API key with Vertex AI GenAI API',
|
||||
description: null,
|
||||
},
|
||||
];
|
||||
|
||||
await this.config.initialize();
|
||||
const version = await getVersion();
|
||||
return {
|
||||
protocolVersion: acp.PROTOCOL_VERSION,
|
||||
authMethods,
|
||||
agentInfo: {
|
||||
name: 'gemini-cli',
|
||||
title: 'Gemini CLI',
|
||||
version,
|
||||
},
|
||||
agentCapabilities: {
|
||||
loadSession: true,
|
||||
promptCapabilities: {
|
||||
@@ -147,8 +131,7 @@ export class GeminiAgent {
|
||||
};
|
||||
}
|
||||
|
||||
async authenticate(req: acp.AuthenticateRequest): Promise<void> {
|
||||
const { methodId } = req;
|
||||
async authenticate({ methodId }: acp.AuthenticateRequest): Promise<void> {
|
||||
const method = z.nativeEnum(AuthType).parse(methodId);
|
||||
const selectedAuthType = this.settings.merged.security.auth.selectedType;
|
||||
|
||||
@@ -156,21 +139,17 @@ export class GeminiAgent {
|
||||
if (selectedAuthType && selectedAuthType !== method) {
|
||||
await clearCachedCredentialFile();
|
||||
}
|
||||
// Check for api-key in _meta
|
||||
const meta = hasMeta(req) ? req._meta : undefined;
|
||||
const apiKey =
|
||||
typeof meta?.['api-key'] === 'string' ? meta['api-key'] : undefined;
|
||||
|
||||
// Refresh auth with the requested method
|
||||
// This will reuse existing credentials if they're valid,
|
||||
// or perform new authentication if needed
|
||||
try {
|
||||
if (apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
await this.config.refreshAuth(method, apiKey ?? this.apiKey);
|
||||
await this.config.refreshAuth(method);
|
||||
} catch (e) {
|
||||
throw new acp.RequestError(-32000, getAcpErrorMessage(e));
|
||||
throw new acp.RequestError(
|
||||
getErrorStatus(e) || 401,
|
||||
getAcpErrorMessage(e),
|
||||
);
|
||||
}
|
||||
this.settings.setValue(
|
||||
SettingScope.User,
|
||||
@@ -198,7 +177,7 @@ export class GeminiAgent {
|
||||
let isAuthenticated = false;
|
||||
let authErrorMessage = '';
|
||||
try {
|
||||
await config.refreshAuth(authType, this.apiKey);
|
||||
await config.refreshAuth(authType);
|
||||
isAuthenticated = true;
|
||||
|
||||
// Extra validation for Gemini API key
|
||||
@@ -220,7 +199,7 @@ export class GeminiAgent {
|
||||
|
||||
if (!isAuthenticated) {
|
||||
throw new acp.RequestError(
|
||||
-32000,
|
||||
401,
|
||||
authErrorMessage || 'Authentication required.',
|
||||
);
|
||||
}
|
||||
@@ -323,7 +302,7 @@ export class GeminiAgent {
|
||||
// This satisfies the security requirement to verify the user before executing
|
||||
// potentially unsafe server definitions.
|
||||
try {
|
||||
await config.refreshAuth(selectedAuthType, this.apiKey);
|
||||
await config.refreshAuth(selectedAuthType);
|
||||
} catch (e) {
|
||||
debugLogger.error(`Authentication failed: ${e}`);
|
||||
throw acp.RequestError.authRequired();
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"./package.json"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"references": [{ "path": "../core" }]
|
||||
"references": [{ "path": "../core" }, { "path": "../devtools" }]
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ const mockConfig = {
|
||||
getNoBrowser: () => false,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => false,
|
||||
getExperimentalZedIntegration: () => false,
|
||||
} as unknown as Config;
|
||||
|
||||
// Mock fetch globally
|
||||
|
||||
@@ -271,12 +271,9 @@ async function initOauthClient(
|
||||
|
||||
await triggerPostAuthCallbacks(client.credentials);
|
||||
} else {
|
||||
// In Zed integration, we skip the interactive consent and directly open the browser
|
||||
if (!config.getExperimentalZedIntegration()) {
|
||||
const userConsent = await getConsentForOauth('');
|
||||
if (!userConsent) {
|
||||
throw new FatalCancellationError('Authentication cancelled by user.');
|
||||
}
|
||||
const userConsent = await getConsentForOauth('');
|
||||
if (!userConsent) {
|
||||
throw new FatalCancellationError('Authentication cancelled by user.');
|
||||
}
|
||||
|
||||
const webLogin = await authWithWeb(client);
|
||||
|
||||
@@ -499,7 +499,6 @@ describe('Server Config (config.ts)', () => {
|
||||
expect(createContentGeneratorConfig).toHaveBeenCalledWith(
|
||||
config,
|
||||
authType,
|
||||
undefined,
|
||||
);
|
||||
// Verify that contentGeneratorConfig is updated
|
||||
expect(config.getContentGeneratorConfig()).toEqual(mockContentConfig);
|
||||
|
||||
@@ -1126,7 +1126,7 @@ export class Config {
|
||||
return this.contentGenerator;
|
||||
}
|
||||
|
||||
async refreshAuth(authMethod: AuthType, apiKey?: string) {
|
||||
async refreshAuth(authMethod: AuthType) {
|
||||
// Reset availability service when switching auth
|
||||
this.modelAvailabilityService.reset();
|
||||
|
||||
@@ -1152,7 +1152,6 @@ export class Config {
|
||||
const newContentGeneratorConfig = await createContentGeneratorConfig(
|
||||
this,
|
||||
authMethod,
|
||||
apiKey,
|
||||
);
|
||||
this.contentGenerator = await createContentGenerator(
|
||||
newContentGeneratorConfig,
|
||||
|
||||
@@ -90,13 +90,9 @@ export type ContentGeneratorConfig = {
|
||||
export async function createContentGeneratorConfig(
|
||||
config: Config,
|
||||
authType: AuthType | undefined,
|
||||
apiKey?: string,
|
||||
): Promise<ContentGeneratorConfig> {
|
||||
const geminiApiKey =
|
||||
apiKey ||
|
||||
process.env['GEMINI_API_KEY'] ||
|
||||
(await loadApiKey()) ||
|
||||
undefined;
|
||||
process.env['GEMINI_API_KEY'] || (await loadApiKey()) || undefined;
|
||||
const googleApiKey = process.env['GOOGLE_API_KEY'] || undefined;
|
||||
const googleCloudProject =
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] ||
|
||||
|
||||
+16
-2
@@ -1304,7 +1304,21 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
"type": "string",
|
||||
},
|
||||
"instruction": {
|
||||
"description": "A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.",
|
||||
"description": "A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.
|
||||
|
||||
A good instruction should concisely answer:
|
||||
1. WHY is the change needed? (e.g., "To fix a bug where users can be null...")
|
||||
2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...")
|
||||
3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...")
|
||||
4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.")
|
||||
|
||||
**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws."
|
||||
|
||||
**BAD Examples:**
|
||||
- "Change the text." (Too vague)
|
||||
- "Fix the bug." (Doesn't explain the bug or the fix)
|
||||
- "Replace the line with this new line." (Brittle, just repeats the other parameters)
|
||||
",
|
||||
"type": "string",
|
||||
},
|
||||
"new_string": {
|
||||
@@ -1312,7 +1326,7 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
"type": "string",
|
||||
},
|
||||
"old_string": {
|
||||
"description": "The exact literal text to replace, unescaped. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.",
|
||||
"description": "The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -300,12 +300,26 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
type: 'string',
|
||||
},
|
||||
instruction: {
|
||||
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.`,
|
||||
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.
|
||||
|
||||
A good instruction should concisely answer:
|
||||
1. WHY is the change needed? (e.g., "To fix a bug where users can be null...")
|
||||
2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...")
|
||||
3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...")
|
||||
4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.")
|
||||
|
||||
**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws."
|
||||
|
||||
**BAD Examples:**
|
||||
- "Change the text." (Too vague)
|
||||
- "Fix the bug." (Doesn't explain the bug or the fix)
|
||||
- "Replace the line with this new line." (Brittle, just repeats the other parameters)
|
||||
`,
|
||||
type: 'string',
|
||||
},
|
||||
old_string: {
|
||||
description:
|
||||
'The exact literal text to replace, unescaped. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
||||
'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
||||
type: 'string',
|
||||
},
|
||||
new_string: {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useDevToolsData, type ConsoleLog, type NetworkLog } from './hooks';
|
||||
import { useDevToolsData, type ConsoleLog, type NetworkLog } from './hooks.js';
|
||||
|
||||
type ThemeMode = 'light' | 'dark' | null; // null means follow system
|
||||
|
||||
@@ -115,7 +115,6 @@ export default function App() {
|
||||
if (!networkMap.has(id)) {
|
||||
networkMap.set(id, {
|
||||
...payload,
|
||||
type,
|
||||
timestamp,
|
||||
id,
|
||||
} as NetworkLog);
|
||||
@@ -125,8 +124,7 @@ export default function App() {
|
||||
networkMap.set(id, {
|
||||
...existing,
|
||||
...payload,
|
||||
// Ensure we don't overwrite the original timestamp or type
|
||||
type: existing.type,
|
||||
// Ensure we don't overwrite the original timestamp
|
||||
timestamp: existing.timestamp,
|
||||
} as NetworkLog);
|
||||
}
|
||||
@@ -158,7 +156,7 @@ export default function App() {
|
||||
const entries: Array<{ timestamp: number; data: object }> = [];
|
||||
|
||||
// Export console logs
|
||||
filteredConsoleLogs.forEach((log) => {
|
||||
filteredConsoleLogs.forEach((log: ConsoleLog) => {
|
||||
entries.push({
|
||||
timestamp: log.timestamp,
|
||||
data: {
|
||||
@@ -171,7 +169,7 @@ export default function App() {
|
||||
});
|
||||
|
||||
// Export network logs
|
||||
filteredNetworkLogs.forEach((log) => {
|
||||
filteredNetworkLogs.forEach((log: NetworkLog) => {
|
||||
entries.push({
|
||||
timestamp: log.timestamp,
|
||||
data: {
|
||||
@@ -230,7 +228,9 @@ export default function App() {
|
||||
if (selectedSessionId === importedSessionId && importedLogs) {
|
||||
return importedLogs.console;
|
||||
}
|
||||
return consoleLogs.filter((l) => l.sessionId === selectedSessionId);
|
||||
return consoleLogs.filter(
|
||||
(l: ConsoleLog) => l.sessionId === selectedSessionId,
|
||||
);
|
||||
}, [consoleLogs, selectedSessionId, importedSessionId, importedLogs]);
|
||||
|
||||
const filteredNetworkLogs = useMemo(() => {
|
||||
@@ -238,7 +238,9 @@ export default function App() {
|
||||
if (selectedSessionId === importedSessionId && importedLogs) {
|
||||
return importedLogs.network;
|
||||
}
|
||||
return networkLogs.filter((l) => l.sessionId === selectedSessionId);
|
||||
return networkLogs.filter(
|
||||
(l: NetworkLog) => l.sessionId === selectedSessionId,
|
||||
);
|
||||
}, [networkLogs, selectedSessionId, importedSessionId, importedLogs]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import App from './App.js';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2023"],
|
||||
"jsx": "react-jsx",
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "client/src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user