expose previewFeatures flag in a2a (#14550)

This commit is contained in:
Sehoon Shon
2025-12-05 13:28:52 -05:00
committed by GitHub
parent 996cbcb680
commit 2c4ec31ed1
3 changed files with 204 additions and 1 deletions

View File

@@ -71,6 +71,7 @@ export async function loadConfig(
ideMode: false,
folderTrust: settings.folderTrust === true,
extensionLoader,
previewFeatures: settings.general?.previewFeatures,
};
const fileService = new FileDiscoveryService(workspaceDir);

View File

@@ -0,0 +1,197 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { loadSettings, USER_SETTINGS_PATH } from './settings.js';
const mocks = vi.hoisted(() => {
const suffix = Math.random().toString(36).slice(2);
return {
suffix,
};
});
vi.mock('node:os', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:os')>();
const path = await import('node:path');
return {
...actual,
homedir: () => path.join(actual.tmpdir(), `gemini-home-${mocks.suffix}`),
};
});
vi.mock('@google/gemini-cli-core', () => ({
GEMINI_DIR: '.gemini',
debugLogger: {
error: vi.fn(),
},
getErrorMessage: (error: unknown) => String(error),
}));
describe('loadSettings', () => {
const mockHomeDir = path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`);
const mockWorkspaceDir = path.join(
os.tmpdir(),
`gemini-workspace-${mocks.suffix}`,
);
const mockGeminiHomeDir = path.join(mockHomeDir, '.gemini');
const mockGeminiWorkspaceDir = path.join(mockWorkspaceDir, '.gemini');
beforeEach(() => {
vi.clearAllMocks();
// Create the directories using the real fs
if (!fs.existsSync(mockGeminiHomeDir)) {
fs.mkdirSync(mockGeminiHomeDir, { recursive: true });
}
if (!fs.existsSync(mockGeminiWorkspaceDir)) {
fs.mkdirSync(mockGeminiWorkspaceDir, { recursive: true });
}
// Clean up settings files before each test
if (fs.existsSync(USER_SETTINGS_PATH)) {
fs.rmSync(USER_SETTINGS_PATH);
}
const workspaceSettingsPath = path.join(
mockGeminiWorkspaceDir,
'settings.json',
);
if (fs.existsSync(workspaceSettingsPath)) {
fs.rmSync(workspaceSettingsPath);
}
});
afterEach(() => {
try {
if (fs.existsSync(mockHomeDir)) {
fs.rmSync(mockHomeDir, { recursive: true, force: true });
}
if (fs.existsSync(mockWorkspaceDir)) {
fs.rmSync(mockWorkspaceDir, { recursive: true, force: true });
}
} catch (e) {
console.error('Failed to cleanup temp dirs', e);
}
vi.restoreAllMocks();
});
it('should load nested previewFeatures from user settings', () => {
const settings = {
general: {
previewFeatures: true,
},
};
fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(settings));
const result = loadSettings(mockWorkspaceDir);
expect(result.general?.previewFeatures).toBe(true);
});
it('should load nested previewFeatures from workspace settings', () => {
const settings = {
general: {
previewFeatures: true,
},
};
const workspaceSettingsPath = path.join(
mockGeminiWorkspaceDir,
'settings.json',
);
fs.writeFileSync(workspaceSettingsPath, JSON.stringify(settings));
const result = loadSettings(mockWorkspaceDir);
expect(result.general?.previewFeatures).toBe(true);
});
it('should prioritize workspace settings over user settings', () => {
const userSettings = {
general: {
previewFeatures: false,
},
};
fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));
const workspaceSettings = {
general: {
previewFeatures: true,
},
};
const workspaceSettingsPath = path.join(
mockGeminiWorkspaceDir,
'settings.json',
);
fs.writeFileSync(workspaceSettingsPath, JSON.stringify(workspaceSettings));
const result = loadSettings(mockWorkspaceDir);
expect(result.general?.previewFeatures).toBe(true);
});
it('should handle missing previewFeatures', () => {
const settings = {
general: {},
};
fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(settings));
const result = loadSettings(mockWorkspaceDir);
expect(result.general?.previewFeatures).toBeUndefined();
});
it('should load other top-level settings correctly', () => {
const settings = {
showMemoryUsage: true,
coreTools: ['tool1', 'tool2'],
mcpServers: {
server1: {
command: 'cmd',
args: ['arg'],
},
},
fileFiltering: {
respectGitIgnore: true,
},
};
fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(settings));
const result = loadSettings(mockWorkspaceDir);
expect(result.showMemoryUsage).toBe(true);
expect(result.coreTools).toEqual(['tool1', 'tool2']);
expect(result.mcpServers).toHaveProperty('server1');
expect(result.fileFiltering?.respectGitIgnore).toBe(true);
});
it('should overwrite top-level settings from workspace (shallow merge)', () => {
const userSettings = {
showMemoryUsage: false,
fileFiltering: {
respectGitIgnore: true,
enableRecursiveFileSearch: true,
},
};
fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));
const workspaceSettings = {
showMemoryUsage: true,
fileFiltering: {
respectGitIgnore: false,
},
};
const workspaceSettingsPath = path.join(
mockGeminiWorkspaceDir,
'settings.json',
);
fs.writeFileSync(workspaceSettingsPath, JSON.stringify(workspaceSettings));
const result = loadSettings(mockWorkspaceDir);
// Primitive value overwritten
expect(result.showMemoryUsage).toBe(true);
// Object value completely replaced (shallow merge behavior)
expect(result.fileFiltering?.respectGitIgnore).toBe(false);
expect(result.fileFiltering?.enableRecursiveFileSearch).toBeUndefined();
});
});

View File

@@ -20,7 +20,9 @@ import stripJsonComments from 'strip-json-comments';
export const USER_SETTINGS_DIR = path.join(homedir(), GEMINI_DIR);
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');
// Reconcile with https://github.com/google-gemini/gemini-cli/blob/b09bc6656080d4d12e1d06734aae2ec33af5c1ed/packages/cli/src/config/settings.ts#L53
// TODO: Ensure full compatibility with V2 nested settings structure (settings.schema.json).
// This involves updating the interface and implementing migration logic to support legacy V1 (flat) settings,
// similar to how packages/cli/src/config/settings.ts handles it.
export interface Settings {
mcpServers?: Record<string, MCPServerConfig>;
coreTools?: string[];
@@ -29,6 +31,9 @@ export interface Settings {
showMemoryUsage?: boolean;
checkpointing?: CheckpointingSettings;
folderTrust?: boolean;
general?: {
previewFeatures?: boolean;
};
// Git-aware file filtering settings
fileFiltering?: {