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
@@ -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),
);