Add ExtensionLoader interface, use that on Config object (#12116)

This commit is contained in:
Jacob MacDonald
2025-10-28 09:04:30 -07:00
committed by GitHub
parent 25f27509c0
commit 1b302deeff
35 changed files with 619 additions and 505 deletions
+13 -4
View File
@@ -154,6 +154,10 @@ import {
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
} from './constants.js';
import { debugLogger } from '../utils/debugLogger.js';
import {
type ExtensionLoader,
SimpleExtensionLoader,
} from '../utils/extensionLoader.js';
export type { FileFilteringOptions };
export {
@@ -248,7 +252,7 @@ export interface ConfigParameters {
maxSessionTurns?: number;
experimentalZedIntegration?: boolean;
listExtensions?: boolean;
extensions?: GeminiCLIExtension[];
extensionLoader?: ExtensionLoader;
enabledExtensions?: string[];
blockedMcpServers?: Array<{ name: string; extensionName: string }>;
noBrowser?: boolean;
@@ -337,7 +341,7 @@ export class Config {
private inFallbackMode = false;
private readonly maxSessionTurns: number;
private readonly listExtensions: boolean;
private readonly _extensions: GeminiCLIExtension[];
private readonly _extensionLoader: ExtensionLoader;
private readonly _enabledExtensions: string[];
private readonly _blockedMcpServers: Array<{
name: string;
@@ -440,7 +444,8 @@ export class Config {
this.experimentalZedIntegration =
params.experimentalZedIntegration ?? false;
this.listExtensions = params.listExtensions ?? false;
this._extensions = params.extensions ?? [];
this._extensionLoader =
params.extensionLoader ?? new SimpleExtensionLoader([]);
this._enabledExtensions = params.enabledExtensions ?? [];
this._blockedMcpServers = params.blockedMcpServers ?? [];
this.noBrowser = params.noBrowser ?? false;
@@ -885,7 +890,11 @@ export class Config {
}
getExtensions(): GeminiCLIExtension[] {
return this._extensions;
return this._extensionLoader.getExtensions();
}
getExtensionLoader(): ExtensionLoader {
return this._extensionLoader;
}
// The list of explicitly enabled extensions, if any were given, may contain
+1
View File
@@ -66,6 +66,7 @@ export * from './utils/promptIdContext.js';
export * from './utils/thoughtUtils.js';
export * from './utils/debugLogger.js';
export * from './utils/events.js';
export * from './utils/extensionLoader.js';
// Export services
export * from './services/fileDiscoveryService.js';
@@ -0,0 +1,48 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { EventEmitter } from 'node:events';
import type { GeminiCLIExtension } from '../config/config.js';
export interface ExtensionLoader {
getExtensions(): GeminiCLIExtension[];
extensionEvents(): EventEmitter<ExtensionEvents>;
}
export interface ExtensionEvents {
extensionEnabled: ExtensionEnableEvent[];
extensionDisabled: ExtensionDisableEvent[];
extensionLoaded: ExtensionLoadEvent[];
extensionUnloaded: ExtensionUnloadEvent[];
extensionInstalled: ExtensionInstallEvent[];
extensionUninstalled: ExtensionUninstallEvent[];
extensionUpdated: ExtensionUpdateEvent[];
}
interface BaseExtensionEvent {
extension: GeminiCLIExtension;
}
export type ExtensionDisableEvent = BaseExtensionEvent;
export type ExtensionEnableEvent = BaseExtensionEvent;
export type ExtensionInstallEvent = BaseExtensionEvent;
export type ExtensionLoadEvent = BaseExtensionEvent;
export type ExtensionUnloadEvent = BaseExtensionEvent;
export type ExtensionUninstallEvent = BaseExtensionEvent;
export type ExtensionUpdateEvent = BaseExtensionEvent;
export class SimpleExtensionLoader implements ExtensionLoader {
private _eventEmitter = new EventEmitter<ExtensionEvents>();
constructor(private readonly extensions: GeminiCLIExtension[]) {}
extensionEvents(): EventEmitter<ExtensionEvents> {
return this._eventEmitter;
}
getExtensions(): GeminiCLIExtension[] {
return this.extensions;
}
}
+19 -18
View File
@@ -16,6 +16,7 @@ import {
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import { GEMINI_DIR } from './paths.js';
import type { GeminiCLIExtension } from '../config/config.js';
import { SimpleExtensionLoader } from './extensionLoader.js';
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal<typeof os>();
@@ -88,7 +89,7 @@ describe('loadServerHierarchicalMemory', () => {
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
false, // untrusted
);
@@ -117,7 +118,7 @@ describe('loadServerHierarchicalMemory', () => {
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
false, // untrusted
);
@@ -133,7 +134,7 @@ describe('loadServerHierarchicalMemory', () => {
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -155,7 +156,7 @@ describe('loadServerHierarchicalMemory', () => {
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -182,7 +183,7 @@ default context content
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -213,7 +214,7 @@ custom context content
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -248,7 +249,7 @@ cwd context content
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -280,7 +281,7 @@ Subdir custom memory
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -312,7 +313,7 @@ Src directory memory
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -356,7 +357,7 @@ Subdir memory
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -409,7 +410,7 @@ Subdir memory
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
'tree',
{
@@ -445,7 +446,7 @@ My code memory
[],
true,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
'tree', // importFormat
{
@@ -467,7 +468,7 @@ My code memory
[],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -489,12 +490,12 @@ My code memory
[],
false,
new FileDiscoveryService(projectRoot),
[
new SimpleExtensionLoader([
{
contextFiles: [extensionFilePath],
isActive: true,
} as GeminiCLIExtension,
], // extensions
]),
DEFAULT_FOLDER_TRUST,
);
@@ -521,7 +522,7 @@ Extension memory content
[includedDir],
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -556,7 +557,7 @@ included directory memory
createdFiles.map((f) => path.dirname(f)),
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
@@ -591,7 +592,7 @@ included directory memory
[childDir, parentDir], // Deliberately include duplicates
false,
new FileDiscoveryService(projectRoot),
[], // extensions
new SimpleExtensionLoader([]),
DEFAULT_FOLDER_TRUST,
);
+4 -3
View File
@@ -15,7 +15,7 @@ import { processImports } from './memoryImportProcessor.js';
import type { FileFilteringOptions } from '../config/constants.js';
import { DEFAULT_MEMORY_FILE_FILTERING_OPTIONS } from '../config/constants.js';
import { GEMINI_DIR } from './paths.js';
import type { GeminiCLIExtension } from '../config/config.js';
import type { ExtensionLoader } from './extensionLoader.js';
import { debugLogger } from './debugLogger.js';
// Simple console logger, similar to the one previously in CLI's config.ts
@@ -338,7 +338,7 @@ export async function loadServerHierarchicalMemory(
includeDirectoriesToReadGemini: readonly string[],
debugMode: boolean,
fileService: FileDiscoveryService,
extensions: GeminiCLIExtension[],
extensionLoader: ExtensionLoader,
folderTrust: boolean,
importFormat: 'flat' | 'tree' = 'tree',
fileFilteringOptions?: FileFilteringOptions,
@@ -365,7 +365,8 @@ export async function loadServerHierarchicalMemory(
// Add extension file paths separately since they may be conditionally enabled.
filePaths.push(
...extensions
...extensionLoader
.getExtensions()
.filter((ext) => ext.isActive)
.flatMap((ext) => ext.contextFiles),
);