mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
Rationalize different Extension typings (#10435)
This commit is contained in:
@@ -153,10 +153,8 @@ describe('mcp list command', () => {
|
||||
|
||||
mockedLoadExtensions.mockReturnValue([
|
||||
{
|
||||
config: {
|
||||
name: 'test-extension',
|
||||
mcpServers: { 'extension-server': { command: '/ext/server' } },
|
||||
},
|
||||
name: 'test-extension',
|
||||
mcpServers: { 'extension-server': { command: '/ext/server' } },
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -27,17 +27,15 @@ async function getMcpServersFromConfig(): Promise<
|
||||
);
|
||||
const mcpServers = { ...(settings.merged.mcpServers || {}) };
|
||||
for (const extension of extensions) {
|
||||
Object.entries(extension.config.mcpServers || {}).forEach(
|
||||
([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
...server,
|
||||
extensionName: extension.config.name,
|
||||
};
|
||||
},
|
||||
);
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
...server,
|
||||
extensionName: extension.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
OutputFormat,
|
||||
type GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { loadCliConfig, parseArguments, type CliArgs } from './config.js';
|
||||
import type { Settings } from './settings.js';
|
||||
import { ExtensionStorage, type Extension } from './extension.js';
|
||||
import { ExtensionStorage } from './extension.js';
|
||||
import * as ServerConfig from '@google/gemini-cli-core';
|
||||
import { isWorkspaceTrusted } from './trustedFolders.js';
|
||||
import { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
|
||||
@@ -1098,33 +1099,30 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
||||
it('should pass extension context file paths to loadServerHierarchicalMemory', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
contextFiles: ['/path/to/ext1/GEMINI.md'],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: {
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
},
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext3',
|
||||
config: {
|
||||
name: 'ext3',
|
||||
version: '1.0.0',
|
||||
},
|
||||
name: 'ext3',
|
||||
version: '1.0.0',
|
||||
contextFiles: [
|
||||
'/path/to/ext3/context1.md',
|
||||
'/path/to/ext3/context2.md',
|
||||
],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
@@ -1195,19 +1193,18 @@ describe('mergeMcpServers', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
mcpServers: {
|
||||
'ext1-server': {
|
||||
url: 'http://localhost:8081',
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
mcpServers: {
|
||||
'ext1-server': {
|
||||
url: 'http://localhost:8081',
|
||||
},
|
||||
},
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
const originalSettings = JSON.parse(JSON.stringify(settings));
|
||||
@@ -1241,24 +1238,22 @@ describe('mergeExcludeTools', () => {
|
||||
|
||||
it('should merge excludeTools from settings and extensions', async () => {
|
||||
const settings: Settings = { tools: { exclude: ['tool1', 'tool2'] } };
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool3', 'tool4'],
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool3', 'tool4'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: {
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool5'],
|
||||
},
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool5'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
process.argv = ['node', 'script.js'];
|
||||
@@ -1281,15 +1276,14 @@ describe('mergeExcludeTools', () => {
|
||||
|
||||
it('should handle overlapping excludeTools between settings and extensions', async () => {
|
||||
const settings: Settings = { tools: { exclude: ['tool1', 'tool2'] } };
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2', 'tool3'],
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2', 'tool3'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
process.argv = ['node', 'script.js'];
|
||||
@@ -1312,24 +1306,22 @@ describe('mergeExcludeTools', () => {
|
||||
|
||||
it('should handle overlapping excludeTools between extensions', async () => {
|
||||
const settings: Settings = { tools: { exclude: ['tool1'] } };
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2', 'tool3'],
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2', 'tool3'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: {
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool3', 'tool4'],
|
||||
},
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool3', 'tool4'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
process.argv = ['node', 'script.js'];
|
||||
@@ -1353,7 +1345,7 @@ describe('mergeExcludeTools', () => {
|
||||
it('should return an empty array when no excludeTools are specified and it is interactive', async () => {
|
||||
process.stdin.isTTY = true;
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const config = await loadCliConfig(
|
||||
@@ -1372,7 +1364,7 @@ describe('mergeExcludeTools', () => {
|
||||
it('should return default excludes when no excludeTools are specified and it is not interactive', async () => {
|
||||
process.stdin.isTTY = false;
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
process.argv = ['node', 'script.js', '-p', 'test'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const config = await loadCliConfig(
|
||||
@@ -1392,7 +1384,7 @@ describe('mergeExcludeTools', () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = { tools: { exclude: ['tool1', 'tool2'] } };
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
extensions,
|
||||
@@ -1411,15 +1403,14 @@ describe('mergeExcludeTools', () => {
|
||||
|
||||
it('should handle extensions with excludeTools but no settings', async () => {
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool1', 'tool2'],
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool1', 'tool2'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
process.argv = ['node', 'script.js'];
|
||||
@@ -1442,15 +1433,14 @@ describe('mergeExcludeTools', () => {
|
||||
|
||||
it('should not modify the original settings object', async () => {
|
||||
const settings: Settings = { tools: { exclude: ['tool1'] } };
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext',
|
||||
config: {
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2'],
|
||||
},
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
excludeTools: ['tool2'],
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
const originalSettings = JSON.parse(JSON.stringify(settings));
|
||||
@@ -1486,7 +1476,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
process.argv = ['node', 'script.js', '-p', 'test'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1516,7 +1506,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1546,7 +1536,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1576,7 +1566,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1599,7 +1589,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
process.argv = ['node', 'script.js', '--yolo', '-p', 'test'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1633,7 +1623,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
process.argv = testCase.args;
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1664,7 +1654,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = { tools: { exclude: ['custom_tool'] } };
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
@@ -1694,7 +1684,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
};
|
||||
|
||||
const settings: Settings = {};
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
await expect(
|
||||
loadCliConfig(
|
||||
settings,
|
||||
@@ -1976,16 +1966,20 @@ describe('loadCliConfig with allowed-mcp-server-names', () => {
|
||||
});
|
||||
|
||||
describe('loadCliConfig extensions', () => {
|
||||
const mockExtensions: Extension[] = [
|
||||
const mockExtensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: { name: 'ext1', version: '1.0.0' },
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
contextFiles: ['/path/to/ext1.md'],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: { name: 'ext2', version: '1.0.0' },
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
contextFiles: ['/path/to/ext2.md'],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
FileFilteringOptions,
|
||||
MCPServerConfig,
|
||||
OutputFormat,
|
||||
GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { extensionsCommand } from '../commands/extensions.js';
|
||||
import {
|
||||
@@ -37,7 +38,6 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Settings } from './settings.js';
|
||||
|
||||
import type { Extension } from './extension.js';
|
||||
import { annotateActiveExtensions } from './extension.js';
|
||||
import { getCliVersion } from '../utils/version.js';
|
||||
import { loadSandboxConfig } from './sandboxConfig.js';
|
||||
@@ -472,7 +472,7 @@ export function isDebugMode(argv: CliArgs): boolean {
|
||||
|
||||
export async function loadCliConfig(
|
||||
settings: Settings,
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
extensionEnablementManager: ExtensionEnablementManager,
|
||||
sessionId: string,
|
||||
argv: CliArgs,
|
||||
@@ -787,30 +787,28 @@ function allowedMcpServers(
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
function mergeMcpServers(settings: Settings, extensions: Extension[]) {
|
||||
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]) {
|
||||
logger.warn(
|
||||
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
...server,
|
||||
extensionName: extension.config.name,
|
||||
};
|
||||
},
|
||||
);
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
logger.warn(
|
||||
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
...server,
|
||||
extensionName: extension.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
function mergeExcludeTools(
|
||||
settings: Settings,
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
extraExcludes?: string[] | undefined,
|
||||
): string[] {
|
||||
const allExcludeTools = new Set([
|
||||
@@ -818,7 +816,7 @@ function mergeExcludeTools(
|
||||
...(extraExcludes || []),
|
||||
]);
|
||||
for (const extension of extensions) {
|
||||
for (const tool of extension.config.excludeTools || []) {
|
||||
for (const tool of extension.excludeTools || []) {
|
||||
allExcludeTools.add(tool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
performWorkspaceExtensionMigration,
|
||||
requestConsentNonInteractive,
|
||||
uninstallExtension,
|
||||
type Extension,
|
||||
} from './extension.js';
|
||||
import {
|
||||
GEMINI_DIR,
|
||||
@@ -158,7 +157,7 @@ describe('extension tests', () => {
|
||||
);
|
||||
expect(extensions).toHaveLength(1);
|
||||
expect(extensions[0].path).toBe(extensionDir);
|
||||
expect(extensions[0].config.name).toBe('test-extension');
|
||||
expect(extensions[0].name).toBe('test-extension');
|
||||
});
|
||||
|
||||
it('should load context file path when GEMINI.md is present', () => {
|
||||
@@ -179,8 +178,8 @@ describe('extension tests', () => {
|
||||
);
|
||||
|
||||
expect(extensions).toHaveLength(2);
|
||||
const ext1 = extensions.find((e) => e.config.name === 'ext1');
|
||||
const ext2 = extensions.find((e) => e.config.name === 'ext2');
|
||||
const ext1 = extensions.find((e) => e.name === 'ext1');
|
||||
const ext2 = extensions.find((e) => e.name === 'ext2');
|
||||
expect(ext1?.contextFiles).toEqual([
|
||||
path.join(userExtensionsDir, 'ext1', 'GEMINI.md'),
|
||||
]);
|
||||
@@ -201,7 +200,7 @@ describe('extension tests', () => {
|
||||
);
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
const ext1 = extensions.find((e) => e.config.name === 'ext1');
|
||||
const ext1 = extensions.find((e) => e.name === 'ext1');
|
||||
expect(ext1?.contextFiles).toEqual([
|
||||
path.join(userExtensionsDir, 'ext1', 'my-context-file.md'),
|
||||
]);
|
||||
@@ -254,13 +253,12 @@ describe('extension tests', () => {
|
||||
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
|
||||
);
|
||||
expect(extensions).toHaveLength(1);
|
||||
const loadedConfig = extensions[0].config;
|
||||
const expectedCwd = path.join(
|
||||
userExtensionsDir,
|
||||
'test-extension',
|
||||
'server',
|
||||
);
|
||||
expect(loadedConfig.mcpServers?.['test-server'].cwd).toBe(expectedCwd);
|
||||
expect(extensions[0].mcpServers?.['test-server'].cwd).toBe(expectedCwd);
|
||||
});
|
||||
|
||||
it('should load a linked extension correctly', async () => {
|
||||
@@ -287,7 +285,7 @@ describe('extension tests', () => {
|
||||
expect(extensions).toHaveLength(1);
|
||||
|
||||
const linkedExt = extensions[0];
|
||||
expect(linkedExt.config.name).toBe('my-linked-extension');
|
||||
expect(linkedExt.name).toBe('my-linked-extension');
|
||||
|
||||
expect(linkedExt.path).toBe(sourceExtDir);
|
||||
expect(linkedExt.installMetadata).toEqual({
|
||||
@@ -340,10 +338,10 @@ describe('extension tests', () => {
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
const extension = extensions[0];
|
||||
expect(extension.config.name).toBe('test-extension');
|
||||
expect(extension.config.mcpServers).toBeDefined();
|
||||
expect(extension.name).toBe('test-extension');
|
||||
expect(extension.mcpServers).toBeDefined();
|
||||
|
||||
const serverConfig = extension.config.mcpServers?.['test-server'];
|
||||
const serverConfig = extension.mcpServers?.['test-server'];
|
||||
expect(serverConfig).toBeDefined();
|
||||
expect(serverConfig?.env).toBeDefined();
|
||||
expect(serverConfig?.env?.API_KEY).toBe('test-api-key-123');
|
||||
@@ -393,7 +391,7 @@ describe('extension tests', () => {
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
const extension = extensions[0];
|
||||
const serverConfig = extension.config.mcpServers!['test-server'];
|
||||
const serverConfig = extension.mcpServers!['test-server'];
|
||||
expect(serverConfig.env).toBeDefined();
|
||||
expect(serverConfig.env!.MISSING_VAR).toBe('$UNDEFINED_ENV_VAR');
|
||||
expect(serverConfig.env!.MISSING_VAR_BRACES).toBe('${ALSO_UNDEFINED}');
|
||||
@@ -422,7 +420,7 @@ describe('extension tests', () => {
|
||||
);
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
expect(extensions[0].config.name).toBe('good-ext');
|
||||
expect(extensions[0].name).toBe('good-ext');
|
||||
expect(consoleSpy).toHaveBeenCalledOnce();
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -456,7 +454,7 @@ describe('extension tests', () => {
|
||||
);
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
expect(extensions[0].config.name).toBe('good-ext');
|
||||
expect(extensions[0].name).toBe('good-ext');
|
||||
expect(consoleSpy).toHaveBeenCalledOnce();
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -485,8 +483,7 @@ describe('extension tests', () => {
|
||||
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
|
||||
);
|
||||
expect(extensions).toHaveLength(1);
|
||||
const loadedConfig = extensions[0].config;
|
||||
expect(loadedConfig.mcpServers?.['test-server'].trust).toBeUndefined();
|
||||
expect(extensions[0].mcpServers?.['test-server'].trust).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw an error for invalid extension names', () => {
|
||||
@@ -513,21 +510,27 @@ describe('extension tests', () => {
|
||||
});
|
||||
|
||||
describe('annotateActiveExtensions', () => {
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: { name: 'ext1', version: '1.0.0' },
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: { name: 'ext2', version: '1.0.0' },
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext3',
|
||||
config: { name: 'ext3', version: '1.0.0' },
|
||||
name: 'ext3',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -622,13 +625,15 @@ describe('extension tests', () => {
|
||||
});
|
||||
|
||||
it('should be true if autoUpdate is true in install metadata', () => {
|
||||
const extensionsWithAutoUpdate: Extension[] = extensions.map((e) => ({
|
||||
...e,
|
||||
installMetadata: {
|
||||
...e.installMetadata!,
|
||||
autoUpdate: true,
|
||||
},
|
||||
}));
|
||||
const extensionsWithAutoUpdate: GeminiCLIExtension[] = extensions.map(
|
||||
(e) => ({
|
||||
...e,
|
||||
installMetadata: {
|
||||
...e.installMetadata!,
|
||||
autoUpdate: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const activeExtensions = annotateActiveExtensions(
|
||||
extensionsWithAutoUpdate,
|
||||
tempHomeDir,
|
||||
@@ -642,31 +647,37 @@ describe('extension tests', () => {
|
||||
});
|
||||
|
||||
it('should respect the per-extension settings from install metadata', () => {
|
||||
const extensionsWithAutoUpdate: Extension[] = [
|
||||
const extensionsWithAutoUpdate: GeminiCLIExtension[] = [
|
||||
{
|
||||
path: '/path/to/ext1',
|
||||
config: { name: 'ext1', version: '1.0.0' },
|
||||
name: 'ext1',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
installMetadata: {
|
||||
source: 'test',
|
||||
type: 'local',
|
||||
autoUpdate: true,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext2',
|
||||
config: { name: 'ext2', version: '1.0.0' },
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
installMetadata: {
|
||||
source: 'test',
|
||||
type: 'local',
|
||||
autoUpdate: false,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
path: '/path/to/ext3',
|
||||
config: { name: 'ext3', version: '1.0.0' },
|
||||
name: 'ext3',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
const activeExtensions = annotateActiveExtensions(
|
||||
@@ -1229,7 +1240,7 @@ This extension will run the following MCP servers:
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
});
|
||||
const extensionsToMigrate: Extension[] = [
|
||||
const extensionsToMigrate: GeminiCLIExtension[] = [
|
||||
loadExtension({
|
||||
extensionDir: ext1Path,
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
@@ -1273,15 +1284,17 @@ This extension will run the following MCP servers:
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
const extensions: Extension[] = [
|
||||
const extensions: GeminiCLIExtension[] = [
|
||||
loadExtension({
|
||||
extensionDir: ext1Path,
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
})!,
|
||||
{
|
||||
path: '/ext/path/1',
|
||||
config: { name: 'ext2', version: '1.0.0' },
|
||||
name: 'ext2',
|
||||
version: '1.0.0',
|
||||
contextFiles: [],
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -45,14 +45,14 @@ export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions');
|
||||
export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json';
|
||||
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
||||
|
||||
export interface Extension {
|
||||
path: string;
|
||||
config: ExtensionConfig;
|
||||
contextFiles: string[];
|
||||
installMetadata?: ExtensionInstallMetadata | undefined;
|
||||
}
|
||||
|
||||
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>;
|
||||
@@ -96,7 +96,9 @@ export class ExtensionStorage {
|
||||
}
|
||||
}
|
||||
|
||||
export function getWorkspaceExtensions(workspaceDir: string): Extension[] {
|
||||
export function getWorkspaceExtensions(
|
||||
workspaceDir: string,
|
||||
): GeminiCLIExtension[] {
|
||||
// If the workspace dir is the user extensions dir, there are no workspace extensions.
|
||||
if (path.resolve(workspaceDir) === path.resolve(os.homedir())) {
|
||||
return [];
|
||||
@@ -112,7 +114,7 @@ export async function copyExtension(
|
||||
}
|
||||
|
||||
export async function performWorkspaceExtensionMigration(
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
requestConsent: (consent: string) => Promise<boolean>,
|
||||
): Promise<string[]> {
|
||||
const failedInstallNames: string[] = [];
|
||||
@@ -125,7 +127,7 @@ export async function performWorkspaceExtensionMigration(
|
||||
};
|
||||
await installExtension(installMetadata, requestConsent);
|
||||
} catch (_) {
|
||||
failedInstallNames.push(extension.config.name);
|
||||
failedInstallNames.push(extension.name);
|
||||
}
|
||||
}
|
||||
return failedInstallNames;
|
||||
@@ -148,7 +150,7 @@ function getTelemetryConfig(cwd: string) {
|
||||
export function loadExtensions(
|
||||
extensionEnablementManager: ExtensionEnablementManager,
|
||||
workspaceDir: string = process.cwd(),
|
||||
): Extension[] {
|
||||
): GeminiCLIExtension[] {
|
||||
const settings = loadSettings(workspaceDir).merged;
|
||||
const allExtensions = [...loadUserExtensions()];
|
||||
|
||||
@@ -160,41 +162,41 @@ export function loadExtensions(
|
||||
allExtensions.push(...getWorkspaceExtensions(workspaceDir));
|
||||
}
|
||||
|
||||
const uniqueExtensions = new Map<string, Extension>();
|
||||
const uniqueExtensions = new Map<string, GeminiCLIExtension>();
|
||||
|
||||
for (const extension of allExtensions) {
|
||||
if (
|
||||
!uniqueExtensions.has(extension.config.name) &&
|
||||
extensionEnablementManager.isEnabled(extension.config.name, workspaceDir)
|
||||
!uniqueExtensions.has(extension.name) &&
|
||||
extensionEnablementManager.isEnabled(extension.name, workspaceDir)
|
||||
) {
|
||||
uniqueExtensions.set(extension.config.name, extension);
|
||||
uniqueExtensions.set(extension.name, extension);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueExtensions.values());
|
||||
}
|
||||
|
||||
export function loadUserExtensions(): Extension[] {
|
||||
export function loadUserExtensions(): GeminiCLIExtension[] {
|
||||
const userExtensions = loadExtensionsFromDir(os.homedir());
|
||||
|
||||
const uniqueExtensions = new Map<string, Extension>();
|
||||
const uniqueExtensions = new Map<string, GeminiCLIExtension>();
|
||||
for (const extension of userExtensions) {
|
||||
if (!uniqueExtensions.has(extension.config.name)) {
|
||||
uniqueExtensions.set(extension.config.name, extension);
|
||||
if (!uniqueExtensions.has(extension.name)) {
|
||||
uniqueExtensions.set(extension.name, extension);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueExtensions.values());
|
||||
}
|
||||
|
||||
export function loadExtensionsFromDir(dir: string): Extension[] {
|
||||
export function loadExtensionsFromDir(dir: string): GeminiCLIExtension[] {
|
||||
const storage = new Storage(dir);
|
||||
const extensionsDir = storage.getExtensionsDir();
|
||||
if (!fs.existsSync(extensionsDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const extensions: Extension[] = [];
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
for (const subdir of fs.readdirSync(extensionsDir)) {
|
||||
const extensionDir = path.join(extensionsDir, subdir);
|
||||
|
||||
@@ -206,7 +208,9 @@ export function loadExtensionsFromDir(dir: string): Extension[] {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
export function loadExtension(context: LoadExtensionContext): Extension | null {
|
||||
export function loadExtension(
|
||||
context: LoadExtensionContext,
|
||||
): GeminiCLIExtension | null {
|
||||
const { extensionDir, workspaceDir } = context;
|
||||
if (!fs.statSync(extensionDir).isDirectory()) {
|
||||
return null;
|
||||
@@ -243,10 +247,14 @@ export function loadExtension(context: LoadExtensionContext): Extension | null {
|
||||
.filter((contextFilePath) => fs.existsSync(contextFilePath));
|
||||
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
path: effectiveExtensionPath,
|
||||
config,
|
||||
contextFiles,
|
||||
installMetadata,
|
||||
mcpServers: config.mcpServers,
|
||||
excludeTools: config.excludeTools,
|
||||
isActive: true, // Barring any other signals extensions should be considered Active.
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(
|
||||
@@ -261,7 +269,7 @@ export function loadExtension(context: LoadExtensionContext): Extension | null {
|
||||
export function loadExtensionByName(
|
||||
name: string,
|
||||
workspaceDir: string = process.cwd(),
|
||||
): Extension | null {
|
||||
): GeminiCLIExtension | null {
|
||||
const userExtensionsDir = ExtensionStorage.getUserExtensionsDir();
|
||||
if (!fs.existsSync(userExtensionsDir)) {
|
||||
return null;
|
||||
@@ -273,10 +281,7 @@ export function loadExtensionByName(
|
||||
continue;
|
||||
}
|
||||
const extension = loadExtension({ extensionDir, workspaceDir });
|
||||
if (
|
||||
extension &&
|
||||
extension.config.name.toLowerCase() === name.toLowerCase()
|
||||
) {
|
||||
if (extension && extension.name.toLowerCase() === name.toLowerCase()) {
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
@@ -320,17 +325,14 @@ function getContextFileNames(config: ExtensionConfig): string[] {
|
||||
* @param workspaceDir The current workspace directory.
|
||||
*/
|
||||
export function annotateActiveExtensions(
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
workspaceDir: string,
|
||||
manager: ExtensionEnablementManager,
|
||||
): GeminiCLIExtension[] {
|
||||
manager.validateExtensionOverrides(extensions);
|
||||
return extensions.map((extension) => ({
|
||||
name: extension.config.name,
|
||||
version: extension.config.version,
|
||||
isActive: manager.isEnabled(extension.config.name, workspaceDir),
|
||||
path: extension.path,
|
||||
installMetadata: extension.installMetadata,
|
||||
...extension,
|
||||
isActive: manager.isEnabled(extension.name, workspaceDir),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -489,7 +491,7 @@ export async function installExtension(
|
||||
const installedExtensions = loadUserExtensions();
|
||||
if (
|
||||
installedExtensions.some(
|
||||
(installed) => installed.config.name === newExtensionName,
|
||||
(installed) => installed.name === newExtensionName,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
@@ -672,11 +674,10 @@ export async function uninstallExtension(
|
||||
const installedExtensions = loadUserExtensions();
|
||||
const extensionName = installedExtensions.find(
|
||||
(installed) =>
|
||||
installed.config.name.toLowerCase() ===
|
||||
extensionIdentifier.toLowerCase() ||
|
||||
installed.name.toLowerCase() === extensionIdentifier.toLowerCase() ||
|
||||
installed.installMetadata?.source.toLowerCase() ===
|
||||
extensionIdentifier.toLowerCase(),
|
||||
)?.config.name;
|
||||
)?.name;
|
||||
if (!extensionName) {
|
||||
throw new Error(`Extension not found.`);
|
||||
}
|
||||
@@ -698,20 +699,17 @@ export async function uninstallExtension(
|
||||
}
|
||||
|
||||
export function toOutputString(
|
||||
extension: Extension,
|
||||
extension: GeminiCLIExtension,
|
||||
workspaceDir: string,
|
||||
): string {
|
||||
const manager = new ExtensionEnablementManager(
|
||||
ExtensionStorage.getUserExtensionsDir(),
|
||||
);
|
||||
const userEnabled = manager.isEnabled(extension.config.name, os.homedir());
|
||||
const workspaceEnabled = manager.isEnabled(
|
||||
extension.config.name,
|
||||
workspaceDir,
|
||||
);
|
||||
const userEnabled = manager.isEnabled(extension.name, os.homedir());
|
||||
const workspaceEnabled = manager.isEnabled(extension.name, workspaceDir);
|
||||
|
||||
const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
|
||||
let output = `${status} ${extension.config.name} (${extension.config.version})`;
|
||||
let output = `${status} ${extension.name} (${extension.version})`;
|
||||
output += `\n Path: ${extension.path}`;
|
||||
if (extension.installMetadata) {
|
||||
output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`;
|
||||
@@ -730,15 +728,15 @@ export function toOutputString(
|
||||
output += `\n ${contextFile}`;
|
||||
});
|
||||
}
|
||||
if (extension.config.mcpServers) {
|
||||
if (extension.mcpServers) {
|
||||
output += `\n MCP servers:`;
|
||||
Object.keys(extension.config.mcpServers).forEach((key) => {
|
||||
Object.keys(extension.mcpServers).forEach((key) => {
|
||||
output += `\n ${key}`;
|
||||
});
|
||||
}
|
||||
if (extension.config.excludeTools) {
|
||||
if (extension.excludeTools) {
|
||||
output += `\n Excluded tools:`;
|
||||
extension.config.excludeTools.forEach((tool) => {
|
||||
extension.excludeTools.forEach((tool) => {
|
||||
output += `\n ${tool}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { ExtensionEnablementManager, Override } from './extensionEnablement.js';
|
||||
import type { Extension } from '../extension.js';
|
||||
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
|
||||
// Helper to create a temporary directory for testing
|
||||
function createTestDir() {
|
||||
@@ -286,9 +286,9 @@ describe('ExtensionEnablementManager', () => {
|
||||
'ext-two',
|
||||
]);
|
||||
const extensions = [
|
||||
{ config: { name: 'ext-one' } },
|
||||
{ config: { name: 'ext-two' } },
|
||||
] as Extension[];
|
||||
{ name: 'ext-one' },
|
||||
{ name: 'ext-two' },
|
||||
] as GeminiCLIExtension[];
|
||||
manager.validateExtensionOverrides(extensions);
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -300,9 +300,9 @@ describe('ExtensionEnablementManager', () => {
|
||||
'ext-another-invalid',
|
||||
]);
|
||||
const extensions = [
|
||||
{ config: { name: 'ext-one' } },
|
||||
{ config: { name: 'ext-two' } },
|
||||
] as Extension[];
|
||||
{ name: 'ext-one' },
|
||||
{ name: 'ext-two' },
|
||||
] as GeminiCLIExtension[];
|
||||
manager.validateExtensionOverrides(extensions);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(2);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { type Extension } from '../extension.js';
|
||||
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
|
||||
export interface ExtensionEnablementConfig {
|
||||
overrides: string[];
|
||||
@@ -119,13 +119,11 @@ export class ExtensionEnablementManager {
|
||||
enabledExtensionNames?.map((name) => name.toLowerCase()) ?? [];
|
||||
}
|
||||
|
||||
validateExtensionOverrides(extensions: Extension[]) {
|
||||
validateExtensionOverrides(extensions: GeminiCLIExtension[]) {
|
||||
for (const name of this.enabledExtensionNamesOverride) {
|
||||
if (name === 'none') continue;
|
||||
if (
|
||||
!extensions.some(
|
||||
(ext) => ext.config.name.toLowerCase() === name.toLowerCase(),
|
||||
)
|
||||
!extensions.some((ext) => ext.name.toLowerCase() === name.toLowerCase())
|
||||
) {
|
||||
console.error(`Extension not found: ${name}`);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ describe('git extension helpers', () => {
|
||||
type: 'link',
|
||||
source: '',
|
||||
},
|
||||
contextFiles: [],
|
||||
};
|
||||
const result = await checkForExtensionUpdate(extension);
|
||||
expect(result).toBe(ExtensionUpdateState.NOT_UPDATABLE);
|
||||
@@ -152,6 +153,7 @@ describe('git extension helpers', () => {
|
||||
type: 'git',
|
||||
source: '',
|
||||
},
|
||||
contextFiles: [],
|
||||
};
|
||||
mockGit.getRemotes.mockResolvedValue([]);
|
||||
const result = await checkForExtensionUpdate(extension);
|
||||
@@ -168,6 +170,7 @@ describe('git extension helpers', () => {
|
||||
type: 'git',
|
||||
source: 'my/ext',
|
||||
},
|
||||
contextFiles: [],
|
||||
};
|
||||
mockGit.getRemotes.mockResolvedValue([
|
||||
{ name: 'origin', refs: { fetch: 'http://my-repo.com' } },
|
||||
@@ -189,6 +192,7 @@ describe('git extension helpers', () => {
|
||||
type: 'git',
|
||||
source: 'my/ext',
|
||||
},
|
||||
contextFiles: [],
|
||||
};
|
||||
mockGit.getRemotes.mockResolvedValue([
|
||||
{ name: 'origin', refs: { fetch: 'http://my-repo.com' } },
|
||||
@@ -210,6 +214,7 @@ describe('git extension helpers', () => {
|
||||
type: 'git',
|
||||
source: 'my/ext',
|
||||
},
|
||||
contextFiles: [],
|
||||
};
|
||||
mockGit.getRemotes.mockRejectedValue(new Error('git error'));
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ export async function checkForExtensionUpdate(
|
||||
);
|
||||
return ExtensionUpdateState.ERROR;
|
||||
}
|
||||
if (newExtension.config.version !== extension.version) {
|
||||
if (newExtension.version !== extension.version) {
|
||||
return ExtensionUpdateState.UPDATE_AVAILABLE;
|
||||
}
|
||||
return ExtensionUpdateState.UP_TO_DATE;
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function updateExtension(
|
||||
});
|
||||
throw new Error('Updated extension not found after installation.');
|
||||
}
|
||||
const updatedVersion = updatedExtension.config.version;
|
||||
const updatedVersion = updatedExtension.version;
|
||||
dispatchExtensionStateUpdate({
|
||||
type: 'SET_STATE',
|
||||
payload: {
|
||||
|
||||
@@ -357,7 +357,7 @@ export async function main() {
|
||||
if (config.getListExtensions()) {
|
||||
console.log('Installed extensions:');
|
||||
for (const extension of extensions) {
|
||||
console.log(`- ${extension.config.name}`);
|
||||
console.log(`- ${extension.name}`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,14 @@
|
||||
*/
|
||||
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import {
|
||||
type Extension,
|
||||
performWorkspaceExtensionMigration,
|
||||
} from '../../config/extension.js';
|
||||
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
import { performWorkspaceExtensionMigration } from '../../config/extension.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function WorkspaceMigrationDialog(props: {
|
||||
workspaceExtensions: Extension[];
|
||||
workspaceExtensions: GeminiCLIExtension[];
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
@@ -92,7 +90,7 @@ export function WorkspaceMigrationDialog(props: {
|
||||
|
||||
<Box flexDirection="column" marginTop={1} marginLeft={2}>
|
||||
{workspaceExtensions.map((extension) => (
|
||||
<Text key={extension.config.name}>- {extension.config.name}</Text>
|
||||
<Text key={extension.name}>- {extension.name}</Text>
|
||||
))}
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
|
||||
@@ -70,6 +70,7 @@ describe('useExtensionUpdates', () => {
|
||||
source: 'https://some/repo',
|
||||
autoUpdate: false,
|
||||
},
|
||||
contextFiles: [],
|
||||
},
|
||||
];
|
||||
const addItem = vi.fn();
|
||||
@@ -262,6 +263,7 @@ describe('useExtensionUpdates', () => {
|
||||
source: 'https://some/repo1',
|
||||
autoUpdate: false,
|
||||
},
|
||||
contextFiles: [],
|
||||
},
|
||||
{
|
||||
name: 'test-extension-2',
|
||||
@@ -274,6 +276,7 @@ describe('useExtensionUpdates', () => {
|
||||
source: 'https://some/repo2',
|
||||
autoUpdate: false,
|
||||
},
|
||||
contextFiles: [],
|
||||
},
|
||||
];
|
||||
const addItem = vi.fn();
|
||||
|
||||
@@ -5,19 +5,17 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
type Extension,
|
||||
getWorkspaceExtensions,
|
||||
} from '../../config/extension.js';
|
||||
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
import { getWorkspaceExtensions } from '../../config/extension.js';
|
||||
import { type LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||
import process from 'node:process';
|
||||
|
||||
export function useWorkspaceMigration(settings: LoadedSettings) {
|
||||
const [showWorkspaceMigrationDialog, setShowWorkspaceMigrationDialog] =
|
||||
useState(false);
|
||||
const [workspaceExtensions, setWorkspaceExtensions] = useState<Extension[]>(
|
||||
[],
|
||||
);
|
||||
const [workspaceExtensions, setWorkspaceExtensions] = useState<
|
||||
GeminiCLIExtension[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Default to true if not set.
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
GeminiChat,
|
||||
ToolResult,
|
||||
ToolCallConfirmationDetails,
|
||||
GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
AuthType,
|
||||
@@ -40,7 +41,7 @@ import * as path from 'node:path';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { ExtensionStorage, type Extension } from '../config/extension.js';
|
||||
import { ExtensionStorage } from '../config/extension.js';
|
||||
import type { CliArgs } from '../config/config.js';
|
||||
import { loadCliConfig } from '../config/config.js';
|
||||
import { ExtensionEnablementManager } from '../config/extensions/extensionEnablement.js';
|
||||
@@ -61,7 +62,7 @@ export function resolveModel(model: string, isInFallbackMode: boolean): string {
|
||||
export async function runZedIntegration(
|
||||
config: Config,
|
||||
settings: LoadedSettings,
|
||||
extensions: Extension[],
|
||||
extensions: GeminiCLIExtension[],
|
||||
argv: CliArgs,
|
||||
) {
|
||||
const stdout = Writable.toWeb(process.stdout) as WritableStream;
|
||||
@@ -88,7 +89,7 @@ class GeminiAgent {
|
||||
constructor(
|
||||
private config: Config,
|
||||
private settings: LoadedSettings,
|
||||
private extensions: Extension[],
|
||||
private extensions: GeminiCLIExtension[],
|
||||
private argv: CliArgs,
|
||||
private client: acp.Client,
|
||||
) {}
|
||||
|
||||
Reference in New Issue
Block a user