mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-29 06:25:16 -07:00
Rationalize different Extension typings (#10435)
This commit is contained in:
@@ -20,16 +20,16 @@ import {
|
||||
GEMINI_CONFIG_DIR,
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
type GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
import type { Settings } from './settings.js';
|
||||
import type { Extension } from './extension.js';
|
||||
import { type AgentSettings, CoderAgentEvent } from '../types.js';
|
||||
|
||||
export async function loadConfig(
|
||||
settings: Settings,
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
taskId: string,
|
||||
): Promise<Config> {
|
||||
const mcpServers = mergeMcpServers(settings, extensions);
|
||||
@@ -118,20 +118,21 @@ export async function loadConfig(
|
||||
return config;
|
||||
}
|
||||
|
||||
export function mergeMcpServers(settings: Settings, extensions: Extension[]) {
|
||||
export function mergeMcpServers(
|
||||
settings: Settings,
|
||||
extensions: GeminiCLIExtension[],
|
||||
) {
|
||||
const mcpServers = { ...(settings.mcpServers || {}) };
|
||||
for (const extension of extensions) {
|
||||
Object.entries(extension.config.mcpServers || {}).forEach(
|
||||
([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
console.warn(
|
||||
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = server;
|
||||
},
|
||||
);
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
console.warn(
|
||||
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = server;
|
||||
});
|
||||
}
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
// Copied exactly from packages/cli/src/config/extension.ts, last PR #1026
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import type {
|
||||
MCPServerConfig,
|
||||
ExtensionInstallMetadata,
|
||||
GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
@@ -14,47 +18,51 @@ import { logger } from '../utils/logger.js';
|
||||
|
||||
export const EXTENSIONS_DIRECTORY_NAME = path.join('.gemini', 'extensions');
|
||||
export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json';
|
||||
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
||||
|
||||
export interface Extension {
|
||||
config: ExtensionConfig;
|
||||
contextFiles: string[];
|
||||
}
|
||||
|
||||
export interface ExtensionConfig {
|
||||
/**
|
||||
* Extension definition as written to disk in gemini-extension.json files.
|
||||
* This should *not* be referenced outside of the logic for reading files.
|
||||
* If information is required for manipulating extensions (load, unload, update)
|
||||
* outside of the loading process that data needs to be stored on the
|
||||
* GeminiCLIExtension class defined in Core.
|
||||
*/
|
||||
interface ExtensionConfig {
|
||||
name: string;
|
||||
version: string;
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
contextFileName?: string | string[];
|
||||
excludeTools?: string[];
|
||||
}
|
||||
|
||||
export function loadExtensions(workspaceDir: string): Extension[] {
|
||||
export function loadExtensions(workspaceDir: string): GeminiCLIExtension[] {
|
||||
const allExtensions = [
|
||||
...loadExtensionsFromDir(workspaceDir),
|
||||
...loadExtensionsFromDir(os.homedir()),
|
||||
];
|
||||
|
||||
const uniqueExtensions: Extension[] = [];
|
||||
const uniqueExtensions: GeminiCLIExtension[] = [];
|
||||
const seenNames = new Set<string>();
|
||||
for (const extension of allExtensions) {
|
||||
if (!seenNames.has(extension.config.name)) {
|
||||
if (!seenNames.has(extension.name)) {
|
||||
logger.info(
|
||||
`Loading extension: ${extension.config.name} (version: ${extension.config.version})`,
|
||||
`Loading extension: ${extension.name} (version: ${extension.version})`,
|
||||
);
|
||||
uniqueExtensions.push(extension);
|
||||
seenNames.add(extension.config.name);
|
||||
seenNames.add(extension.name);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueExtensions;
|
||||
}
|
||||
|
||||
function loadExtensionsFromDir(dir: string): Extension[] {
|
||||
function loadExtensionsFromDir(dir: string): GeminiCLIExtension[] {
|
||||
const extensionsDir = path.join(dir, EXTENSIONS_DIRECTORY_NAME);
|
||||
if (!fs.existsSync(extensionsDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
for (const subdir of fs.readdirSync(extensionsDir)) {
|
||||
const extensionDir = path.join(extensionsDir, subdir);
|
||||
|
||||
@@ -66,7 +74,7 @@ function loadExtensionsFromDir(dir: string): Extension[] {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
function loadExtension(extensionDir: string): Extension | null {
|
||||
function loadExtension(extensionDir: string): GeminiCLIExtension | null {
|
||||
if (!fs.statSync(extensionDir).isDirectory()) {
|
||||
logger.error(
|
||||
`Warning: unexpected file ${extensionDir} in extensions directory.`,
|
||||
@@ -92,14 +100,22 @@ function loadExtension(extensionDir: string): Extension | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const installMetadata = loadInstallMetadata(extensionDir);
|
||||
|
||||
const contextFiles = getContextFileNames(config)
|
||||
.map((contextFileName) => path.join(extensionDir, contextFileName))
|
||||
.filter((contextFilePath) => fs.existsSync(contextFilePath));
|
||||
|
||||
return {
|
||||
config,
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
path: extensionDir,
|
||||
contextFiles,
|
||||
};
|
||||
installMetadata,
|
||||
mcpServers: config.mcpServers,
|
||||
excludeTools: config.excludeTools,
|
||||
isActive: true, // Barring any other signals extensions should be considered Active.
|
||||
} as GeminiCLIExtension;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Warning: error parsing extension config in ${configFilePath}: ${e}`,
|
||||
@@ -116,3 +132,19 @@ function getContextFileNames(config: ExtensionConfig): string[] {
|
||||
}
|
||||
return config.contextFileName;
|
||||
}
|
||||
|
||||
export function loadInstallMetadata(
|
||||
extensionDir: string,
|
||||
): ExtensionInstallMetadata | undefined {
|
||||
const metadataFilePath = path.join(extensionDir, INSTALL_METADATA_FILENAME);
|
||||
try {
|
||||
const configContent = fs.readFileSync(metadataFilePath, 'utf-8');
|
||||
const metadata = JSON.parse(configContent) as ExtensionInstallMetadata;
|
||||
return metadata;
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
`Failed to load or parse extension install metadata at ${metadataFilePath}: ${e}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user