Compare commits

..

4 Commits

Author SHA1 Message Date
Christian Gunderman f93b9f849c Revert "feat(core): refine Edit and WriteFile tool schemas for Gemini 3 (#19476)"
This reverts commit fb1b1b451d.
2026-02-26 22:20:01 +00:00
Christian Gunderman 55d0bf5f8e Fix build. 2026-02-25 06:27:48 +00:00
Christian Gunderman 81217cc906 fix: add devtools reference to cli tsconfig 2026-02-25 06:27:36 +00:00
Christian Gunderman 4325e434f9 Fix build. 2026-02-25 06:27:24 +00:00
16 changed files with 70 additions and 99 deletions
+2
View File
@@ -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",
+1
View File
@@ -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();
+1 -1
View File
@@ -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
+3 -6
View File
@@ -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);
-1
View File
@@ -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);
+1 -2
View File
@@ -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,
+1 -5
View File
@@ -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'] ||
@@ -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: {
+10 -8
View File
@@ -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 (
+1 -1
View File
@@ -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>
+2 -3
View File
@@ -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"]
}