mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-14 08:01:02 -07:00
fix(cli): resolve environment loading and auth validation issues in ACP mode (#18025)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -26,7 +26,11 @@ import {
|
||||
type Config,
|
||||
type MessageBus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { SettingScope, type LoadedSettings } from '../config/settings.js';
|
||||
import {
|
||||
SettingScope,
|
||||
type LoadedSettings,
|
||||
loadSettings,
|
||||
} from '../config/settings.js';
|
||||
import { loadCliConfig, type CliArgs } from '../config/config.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
@@ -35,6 +39,14 @@ vi.mock('../config/config.js', () => ({
|
||||
loadCliConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../config/settings.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../config/settings.js')>();
|
||||
return {
|
||||
...actual,
|
||||
loadSettings: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('node:crypto', () => ({
|
||||
randomUUID: () => 'test-session-id',
|
||||
}));
|
||||
@@ -95,6 +107,10 @@ describe('GeminiAgent', () => {
|
||||
initialize: vi.fn(),
|
||||
getFileSystemService: vi.fn(),
|
||||
setFileSystemService: vi.fn(),
|
||||
getContentGeneratorConfig: vi.fn(),
|
||||
getActiveModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getPreviewFeatures: vi.fn().mockReturnValue({}),
|
||||
getGeminiClient: vi.fn().mockReturnValue({
|
||||
startChat: vi.fn().mockResolvedValue({}),
|
||||
}),
|
||||
@@ -117,6 +133,13 @@ describe('GeminiAgent', () => {
|
||||
} as unknown as Mocked<acp.AgentSideConnection>;
|
||||
|
||||
(loadCliConfig as unknown as Mock).mockResolvedValue(mockConfig);
|
||||
(loadSettings as unknown as Mock).mockImplementation(() => ({
|
||||
merged: {
|
||||
security: { auth: { selectedType: AuthType.LOGIN_WITH_GOOGLE } },
|
||||
mcpServers: {},
|
||||
},
|
||||
setValue: vi.fn(),
|
||||
}));
|
||||
|
||||
agent = new GeminiAgent(mockConfig, mockSettings, mockArgv, mockConnection);
|
||||
});
|
||||
@@ -148,6 +171,9 @@ describe('GeminiAgent', () => {
|
||||
});
|
||||
|
||||
it('should create a new session', async () => {
|
||||
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
|
||||
apiKey: 'test-key',
|
||||
});
|
||||
const response = await agent.newSession({
|
||||
cwd: '/tmp',
|
||||
mcpServers: [],
|
||||
@@ -159,6 +185,28 @@ describe('GeminiAgent', () => {
|
||||
expect(mockConfig.getGeminiClient).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fail session creation if Gemini API key is missing', async () => {
|
||||
(loadSettings as unknown as Mock).mockImplementation(() => ({
|
||||
merged: {
|
||||
security: { auth: { selectedType: AuthType.USE_GEMINI } },
|
||||
mcpServers: {},
|
||||
},
|
||||
setValue: vi.fn(),
|
||||
}));
|
||||
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
|
||||
apiKey: undefined,
|
||||
});
|
||||
|
||||
await expect(
|
||||
agent.newSession({
|
||||
cwd: '/tmp',
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
message: 'Gemini API key is missing or not configured.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new session with mcp servers', async () => {
|
||||
const mcpServers = [
|
||||
{
|
||||
@@ -194,14 +242,14 @@ describe('GeminiAgent', () => {
|
||||
mockConfig.refreshAuth.mockRejectedValue(new Error('Auth failed'));
|
||||
const debugSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
// Should throw RequestError.authRequired()
|
||||
// Should throw RequestError with custom message
|
||||
await expect(
|
||||
agent.newSession({
|
||||
cwd: '/tmp',
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
message: 'Authentication required',
|
||||
message: 'Auth failed',
|
||||
});
|
||||
|
||||
debugSpy.mockRestore();
|
||||
|
||||
@@ -40,7 +40,7 @@ import { AcpFileSystemService } from './fileSystemService.js';
|
||||
import { Readable, Writable } from 'node:stream';
|
||||
import type { Content, Part, FunctionCall } from '@google/genai';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import { SettingScope } from '../config/settings.js';
|
||||
import { SettingScope, loadSettings } from '../config/settings.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { z } from 'zod';
|
||||
@@ -139,7 +139,11 @@ export class GeminiAgent {
|
||||
// Refresh auth with the requested method
|
||||
// This will reuse existing credentials if they're valid,
|
||||
// or perform new authentication if needed
|
||||
await this.config.refreshAuth(method);
|
||||
try {
|
||||
await this.config.refreshAuth(method);
|
||||
} catch (e) {
|
||||
throw new acp.RequestError(getErrorStatus(e) || 401, getErrorMessage(e));
|
||||
}
|
||||
this.settings.setValue(
|
||||
SettingScope.User,
|
||||
'security.auth.selectedType',
|
||||
@@ -152,12 +156,47 @@ export class GeminiAgent {
|
||||
mcpServers,
|
||||
}: acp.NewSessionRequest): Promise<acp.NewSessionResponse> {
|
||||
const sessionId = randomUUID();
|
||||
const config = await this.initializeSessionConfig(
|
||||
const loadedSettings = loadSettings(cwd);
|
||||
const config = await this.newSessionConfig(
|
||||
sessionId,
|
||||
cwd,
|
||||
mcpServers,
|
||||
loadedSettings,
|
||||
);
|
||||
|
||||
const authType =
|
||||
loadedSettings.merged.security.auth.selectedType || AuthType.USE_GEMINI;
|
||||
|
||||
let isAuthenticated = false;
|
||||
let authErrorMessage = '';
|
||||
try {
|
||||
await config.refreshAuth(authType);
|
||||
isAuthenticated = true;
|
||||
|
||||
// Extra validation for Gemini API key
|
||||
const contentGeneratorConfig = config.getContentGeneratorConfig();
|
||||
if (
|
||||
authType === AuthType.USE_GEMINI &&
|
||||
(!contentGeneratorConfig || !contentGeneratorConfig.apiKey)
|
||||
) {
|
||||
isAuthenticated = false;
|
||||
authErrorMessage = 'Gemini API key is missing or not configured.';
|
||||
}
|
||||
} catch (e) {
|
||||
isAuthenticated = false;
|
||||
authErrorMessage = getErrorMessage(e);
|
||||
debugLogger.error(
|
||||
`Authentication failed: ${e instanceof Error ? e.stack : e}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
throw new acp.RequestError(
|
||||
401,
|
||||
authErrorMessage || 'Authentication required.',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.clientCapabilities?.fs) {
|
||||
const acpFileSystemService = new AcpFileSystemService(
|
||||
this.connection,
|
||||
@@ -168,6 +207,9 @@ export class GeminiAgent {
|
||||
config.setFileSystemService(acpFileSystemService);
|
||||
}
|
||||
|
||||
await config.initialize();
|
||||
startupProfiler.flush(config);
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
const chat = await geminiClient.startChat();
|
||||
const session = new Session(sessionId, chat, config, this.connection);
|
||||
@@ -264,8 +306,10 @@ export class GeminiAgent {
|
||||
sessionId: string,
|
||||
cwd: string,
|
||||
mcpServers: acp.McpServer[],
|
||||
loadedSettings?: LoadedSettings,
|
||||
): Promise<Config> {
|
||||
const mergedMcpServers = { ...this.settings.merged.mcpServers };
|
||||
const currentSettings = loadedSettings || this.settings;
|
||||
const mergedMcpServers = { ...currentSettings.merged.mcpServers };
|
||||
|
||||
for (const server of mcpServers) {
|
||||
if (
|
||||
@@ -300,7 +344,10 @@ export class GeminiAgent {
|
||||
}
|
||||
}
|
||||
|
||||
const settings = { ...this.settings.merged, mcpServers: mergedMcpServers };
|
||||
const settings = {
|
||||
...currentSettings.merged,
|
||||
mcpServers: mergedMcpServers,
|
||||
};
|
||||
|
||||
const config = await loadCliConfig(settings, sessionId, this.argv, { cwd });
|
||||
|
||||
@@ -497,7 +544,10 @@ export class Session {
|
||||
return { stopReason: 'cancelled' };
|
||||
}
|
||||
|
||||
throw error;
|
||||
throw new acp.RequestError(
|
||||
getErrorStatus(error) || 500,
|
||||
getErrorMessage(error),
|
||||
);
|
||||
}
|
||||
|
||||
if (functionCalls.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user