mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-29 14:34:55 -07:00
Add extensions logging (#11261)
This commit is contained in:
@@ -603,6 +603,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext1',
|
path: '/path/to/ext1',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
contextFiles: ['/path/to/ext1/GEMINI.md'],
|
contextFiles: ['/path/to/ext1/GEMINI.md'],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -610,6 +611,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext2',
|
path: '/path/to/ext2',
|
||||||
name: 'ext2',
|
name: 'ext2',
|
||||||
|
id: 'ext2-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -617,6 +619,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext3',
|
path: '/path/to/ext3',
|
||||||
name: 'ext3',
|
name: 'ext3',
|
||||||
|
id: 'ext3-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
contextFiles: [
|
contextFiles: [
|
||||||
'/path/to/ext3/context1.md',
|
'/path/to/ext3/context1.md',
|
||||||
@@ -690,6 +693,8 @@ describe('mergeMcpServers', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext1',
|
path: '/path/to/ext1',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
|
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
'ext1-server': {
|
'ext1-server': {
|
||||||
@@ -730,6 +735,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext1',
|
path: '/path/to/ext1',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool3', 'tool4'],
|
excludeTools: ['tool3', 'tool4'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -738,6 +744,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext2',
|
path: '/path/to/ext2',
|
||||||
name: 'ext2',
|
name: 'ext2',
|
||||||
|
id: 'ext2-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool5'],
|
excludeTools: ['tool5'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -764,6 +771,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext1',
|
path: '/path/to/ext1',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool2', 'tool3'],
|
excludeTools: ['tool2', 'tool3'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -790,6 +798,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext1',
|
path: '/path/to/ext1',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool2', 'tool3'],
|
excludeTools: ['tool2', 'tool3'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -798,6 +807,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext2',
|
path: '/path/to/ext2',
|
||||||
name: 'ext2',
|
name: 'ext2',
|
||||||
|
id: 'ext2-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool3', 'tool4'],
|
excludeTools: ['tool3', 'tool4'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -871,6 +881,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext',
|
path: '/path/to/ext',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool1', 'tool2'],
|
excludeTools: ['tool1', 'tool2'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
@@ -897,6 +908,7 @@ describe('mergeExcludeTools', () => {
|
|||||||
{
|
{
|
||||||
path: '/path/to/ext',
|
path: '/path/to/ext',
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
id: 'ext1-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
excludeTools: ['tool2'],
|
excludeTools: ['tool2'],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
loadExtensionConfig,
|
loadExtensionConfig,
|
||||||
loadExtensions,
|
loadExtensions,
|
||||||
uninstallExtension,
|
uninstallExtension,
|
||||||
|
hashValue,
|
||||||
} from './extension.js';
|
} from './extension.js';
|
||||||
import {
|
import {
|
||||||
GEMINI_DIR,
|
GEMINI_DIR,
|
||||||
@@ -1259,6 +1260,10 @@ This extension will run the following MCP servers:
|
|||||||
extensionsDir: userExtensionsDir,
|
extensionsDir: userExtensionsDir,
|
||||||
name: 'my-local-extension',
|
name: 'my-local-extension',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
|
installMetadata: {
|
||||||
|
source: userExtensionsDir,
|
||||||
|
type: 'local',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await uninstallExtension('my-local-extension', isUpdate);
|
await uninstallExtension('my-local-extension', isUpdate);
|
||||||
@@ -1269,7 +1274,8 @@ This extension will run the following MCP servers:
|
|||||||
} else {
|
} else {
|
||||||
expect(mockLogExtensionUninstall).toHaveBeenCalled();
|
expect(mockLogExtensionUninstall).toHaveBeenCalled();
|
||||||
expect(ExtensionUninstallEvent).toHaveBeenCalledWith(
|
expect(ExtensionUninstallEvent).toHaveBeenCalledWith(
|
||||||
'my-local-extension',
|
hashValue('my-local-extension'),
|
||||||
|
hashValue(userExtensionsDir),
|
||||||
'success',
|
'success',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1313,7 +1319,8 @@ This extension will run the following MCP servers:
|
|||||||
expect(fs.existsSync(sourceExtDir)).toBe(false);
|
expect(fs.existsSync(sourceExtDir)).toBe(false);
|
||||||
expect(mockLogExtensionUninstall).toHaveBeenCalled();
|
expect(mockLogExtensionUninstall).toHaveBeenCalled();
|
||||||
expect(ExtensionUninstallEvent).toHaveBeenCalledWith(
|
expect(ExtensionUninstallEvent).toHaveBeenCalledWith(
|
||||||
'gemini-sql-extension',
|
hashValue('gemini-sql-extension'),
|
||||||
|
hashValue('https://github.com/google/gemini-sql-extension'),
|
||||||
'success',
|
'success',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1423,6 +1430,10 @@ This extension will run the following MCP servers:
|
|||||||
extensionsDir: userExtensionsDir,
|
extensionsDir: userExtensionsDir,
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
|
installMetadata: {
|
||||||
|
source: userExtensionsDir,
|
||||||
|
type: 'local',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
disableExtension(
|
disableExtension(
|
||||||
@@ -1433,7 +1444,8 @@ This extension will run the following MCP servers:
|
|||||||
|
|
||||||
expect(mockLogExtensionDisable).toHaveBeenCalled();
|
expect(mockLogExtensionDisable).toHaveBeenCalled();
|
||||||
expect(ExtensionDisableEvent).toHaveBeenCalledWith(
|
expect(ExtensionDisableEvent).toHaveBeenCalledWith(
|
||||||
'ext1',
|
hashValue('ext1'),
|
||||||
|
hashValue(userExtensionsDir),
|
||||||
SettingScope.Workspace,
|
SettingScope.Workspace,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1497,6 +1509,10 @@ This extension will run the following MCP servers:
|
|||||||
extensionsDir: userExtensionsDir,
|
extensionsDir: userExtensionsDir,
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
|
installMetadata: {
|
||||||
|
source: userExtensionsDir,
|
||||||
|
type: 'local',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const extensionEnablementManager = new ExtensionEnablementManager();
|
const extensionEnablementManager = new ExtensionEnablementManager();
|
||||||
disableExtension(
|
disableExtension(
|
||||||
@@ -1512,7 +1528,8 @@ This extension will run the following MCP servers:
|
|||||||
|
|
||||||
expect(mockLogExtensionEnable).toHaveBeenCalled();
|
expect(mockLogExtensionEnable).toHaveBeenCalled();
|
||||||
expect(ExtensionEnableEvent).toHaveBeenCalledWith(
|
expect(ExtensionEnableEvent).toHaveBeenCalledWith(
|
||||||
'ext1',
|
hashValue('ext1'),
|
||||||
|
hashValue(userExtensionsDir),
|
||||||
SettingScope.Workspace,
|
SettingScope.Workspace,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -199,28 +199,6 @@ export function loadExtension(
|
|||||||
)
|
)
|
||||||
.filter((contextFilePath) => fs.existsSync(contextFilePath));
|
.filter((contextFilePath) => fs.existsSync(contextFilePath));
|
||||||
|
|
||||||
// IDs are created by hashing details of the installation source in order to
|
|
||||||
// deduplicate extensions with conflicting names and also obfuscate any
|
|
||||||
// potentially sensitive information such as private git urls, system paths,
|
|
||||||
// or project names.
|
|
||||||
const hash = createHash('sha256');
|
|
||||||
const githubUrlParts =
|
|
||||||
installMetadata &&
|
|
||||||
(installMetadata.type === 'git' ||
|
|
||||||
installMetadata.type === 'github-release')
|
|
||||||
? tryParseGithubUrl(installMetadata.source)
|
|
||||||
: null;
|
|
||||||
if (githubUrlParts) {
|
|
||||||
// For github repos, we use the https URI to the repo as the ID.
|
|
||||||
hash.update(
|
|
||||||
`https://github.com/${githubUrlParts.owner}/${githubUrlParts.repo}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
hash.update(installMetadata?.source ?? config.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = hash.digest('hex');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
version: config.version,
|
version: config.version,
|
||||||
@@ -230,7 +208,7 @@ export function loadExtension(
|
|||||||
mcpServers: config.mcpServers,
|
mcpServers: config.mcpServers,
|
||||||
excludeTools: config.excludeTools,
|
excludeTools: config.excludeTools,
|
||||||
isActive: extensionEnablementManager.isEnabled(config.name, workspaceDir),
|
isActive: extensionEnablementManager.isEnabled(config.name, workspaceDir),
|
||||||
id,
|
id: getExtensionId(config, installMetadata),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugLogger.error(
|
debugLogger.error(
|
||||||
@@ -384,6 +362,10 @@ async function promptForConsentInteractive(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hashValue(value: string): string {
|
||||||
|
return createHash('sha256').update(value).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
export async function installOrUpdateExtension(
|
export async function installOrUpdateExtension(
|
||||||
installMetadata: ExtensionInstallMetadata,
|
installMetadata: ExtensionInstallMetadata,
|
||||||
requestConsent: (consent: string) => Promise<boolean>,
|
requestConsent: (consent: string) => Promise<boolean>,
|
||||||
@@ -520,15 +502,15 @@ export async function installOrUpdateExtension(
|
|||||||
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
logExtensionUpdateEvent(
|
logExtensionUpdateEvent(
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
new ExtensionUpdateEvent(
|
new ExtensionUpdateEvent(
|
||||||
newExtensionConfig.name,
|
hashValue(newExtensionConfig.name),
|
||||||
|
getExtensionId(newExtensionConfig, installMetadata),
|
||||||
newExtensionConfig.version,
|
newExtensionConfig.version,
|
||||||
previousExtensionConfig.version,
|
previousExtensionConfig.version,
|
||||||
installMetadata.source,
|
installMetadata.type,
|
||||||
'success',
|
'success',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -536,9 +518,10 @@ export async function installOrUpdateExtension(
|
|||||||
logExtensionInstallEvent(
|
logExtensionInstallEvent(
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
new ExtensionInstallEvent(
|
new ExtensionInstallEvent(
|
||||||
newExtensionConfig.name,
|
hashValue(newExtensionConfig.name),
|
||||||
|
getExtensionId(newExtensionConfig, installMetadata),
|
||||||
newExtensionConfig.version,
|
newExtensionConfig.version,
|
||||||
installMetadata.source,
|
installMetadata.type,
|
||||||
'success',
|
'success',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -564,14 +547,19 @@ export async function installOrUpdateExtension(
|
|||||||
// Ignore error, this is just for logging.
|
// Ignore error, this is just for logging.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const config = newExtensionConfig ?? previousExtensionConfig;
|
||||||
|
const extensionId = config
|
||||||
|
? getExtensionId(config, installMetadata)
|
||||||
|
: undefined;
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
logExtensionUpdateEvent(
|
logExtensionUpdateEvent(
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
new ExtensionUpdateEvent(
|
new ExtensionUpdateEvent(
|
||||||
newExtensionConfig?.name ?? previousExtensionConfig.name,
|
hashValue(config?.name ?? ''),
|
||||||
|
extensionId ?? '',
|
||||||
newExtensionConfig?.version ?? '',
|
newExtensionConfig?.version ?? '',
|
||||||
previousExtensionConfig.version,
|
previousExtensionConfig.version,
|
||||||
installMetadata.source,
|
installMetadata.type,
|
||||||
'error',
|
'error',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -579,9 +567,10 @@ export async function installOrUpdateExtension(
|
|||||||
logExtensionInstallEvent(
|
logExtensionInstallEvent(
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
new ExtensionInstallEvent(
|
new ExtensionInstallEvent(
|
||||||
newExtensionConfig?.name ?? '',
|
hashValue(newExtensionConfig?.name ?? ''),
|
||||||
|
extensionId ?? '',
|
||||||
newExtensionConfig?.version ?? '',
|
newExtensionConfig?.version ?? '',
|
||||||
installMetadata.source,
|
installMetadata.type,
|
||||||
'error',
|
'error',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -707,16 +696,16 @@ export async function uninstallExtension(
|
|||||||
new ExtensionEnablementManager(),
|
new ExtensionEnablementManager(),
|
||||||
cwd,
|
cwd,
|
||||||
);
|
);
|
||||||
const extensionName = installedExtensions.find(
|
const extension = installedExtensions.find(
|
||||||
(installed) =>
|
(installed) =>
|
||||||
installed.name.toLowerCase() === extensionIdentifier.toLowerCase() ||
|
installed.name.toLowerCase() === extensionIdentifier.toLowerCase() ||
|
||||||
installed.installMetadata?.source.toLowerCase() ===
|
installed.installMetadata?.source.toLowerCase() ===
|
||||||
extensionIdentifier.toLowerCase(),
|
extensionIdentifier.toLowerCase(),
|
||||||
)?.name;
|
);
|
||||||
if (!extensionName) {
|
if (!extension) {
|
||||||
throw new Error(`Extension not found.`);
|
throw new Error(`Extension not found.`);
|
||||||
}
|
}
|
||||||
const storage = new ExtensionStorage(extensionName);
|
const storage = new ExtensionStorage(extension.name);
|
||||||
|
|
||||||
await fs.promises.rm(storage.getExtensionDir(), {
|
await fs.promises.rm(storage.getExtensionDir(), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@@ -727,13 +716,17 @@ export async function uninstallExtension(
|
|||||||
// uninstalls related to updates.
|
// uninstalls related to updates.
|
||||||
if (isUpdate) return;
|
if (isUpdate) return;
|
||||||
|
|
||||||
const manager = new ExtensionEnablementManager([extensionName]);
|
const manager = new ExtensionEnablementManager([extension.name]);
|
||||||
manager.remove(extensionName);
|
manager.remove(extension.name);
|
||||||
|
|
||||||
const telemetryConfig = getTelemetryConfig(cwd);
|
const telemetryConfig = getTelemetryConfig(cwd);
|
||||||
logExtensionUninstall(
|
logExtensionUninstall(
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
new ExtensionUninstallEvent(extensionName, 'success'),
|
new ExtensionUninstallEvent(
|
||||||
|
hashValue(extension.name),
|
||||||
|
extension.id,
|
||||||
|
'success',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,6 +740,7 @@ export function toOutputString(
|
|||||||
|
|
||||||
const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
|
const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
|
||||||
let output = `${status} ${extension.name} (${extension.version})`;
|
let output = `${status} ${extension.name} (${extension.version})`;
|
||||||
|
output += `\n ID: ${extension.id}`;
|
||||||
output += `\n Path: ${extension.path}`;
|
output += `\n Path: ${extension.path}`;
|
||||||
if (extension.installMetadata) {
|
if (extension.installMetadata) {
|
||||||
output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`;
|
output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`;
|
||||||
@@ -797,7 +791,10 @@ export function disableExtension(
|
|||||||
|
|
||||||
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
||||||
extensionEnablementManager.disable(name, true, scopePath);
|
extensionEnablementManager.disable(name, true, scopePath);
|
||||||
logExtensionDisable(config, new ExtensionDisableEvent(name, scope));
|
logExtensionDisable(
|
||||||
|
config,
|
||||||
|
new ExtensionDisableEvent(hashValue(name), extension.id, scope),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enableExtension(
|
export function enableExtension(
|
||||||
@@ -816,5 +813,32 @@ export function enableExtension(
|
|||||||
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
||||||
extensionEnablementManager.enable(name, true, scopePath);
|
extensionEnablementManager.enable(name, true, scopePath);
|
||||||
const config = getTelemetryConfig(cwd);
|
const config = getTelemetryConfig(cwd);
|
||||||
logExtensionEnable(config, new ExtensionEnableEvent(name, scope));
|
logExtensionEnable(
|
||||||
|
config,
|
||||||
|
new ExtensionEnableEvent(hashValue(name), extension.id, scope),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExtensionId(
|
||||||
|
config: ExtensionConfig,
|
||||||
|
installMetadata?: ExtensionInstallMetadata,
|
||||||
|
): string {
|
||||||
|
// IDs are created by hashing details of the installation source in order to
|
||||||
|
// deduplicate extensions with conflicting names and also obfuscate any
|
||||||
|
// potentially sensitive information such as private git urls, system paths,
|
||||||
|
// or project names.
|
||||||
|
let idValue = config.name;
|
||||||
|
const githubUrlParts =
|
||||||
|
installMetadata &&
|
||||||
|
(installMetadata.type === 'git' ||
|
||||||
|
installMetadata.type === 'github-release')
|
||||||
|
? tryParseGithubUrl(installMetadata.source)
|
||||||
|
: null;
|
||||||
|
if (githubUrlParts) {
|
||||||
|
// For github repos, we use the https URI to the repo as the ID.
|
||||||
|
idValue = `https://github.com/${githubUrlParts.owner}/${githubUrlParts.repo}`;
|
||||||
|
} else {
|
||||||
|
idValue = installMetadata?.source ?? config.name;
|
||||||
|
}
|
||||||
|
return hashValue(idValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ describe('git extension helpers', () => {
|
|||||||
it('should return NOT_UPDATABLE for non-git extensions', async () => {
|
it('should return NOT_UPDATABLE for non-git extensions', async () => {
|
||||||
const extension: GeminiCLIExtension = {
|
const extension: GeminiCLIExtension = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
id: 'test-id',
|
||||||
path: '/ext',
|
path: '/ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -160,6 +161,7 @@ describe('git extension helpers', () => {
|
|||||||
it('should return ERROR if no remotes found', async () => {
|
it('should return ERROR if no remotes found', async () => {
|
||||||
const extension: GeminiCLIExtension = {
|
const extension: GeminiCLIExtension = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
id: 'test-id',
|
||||||
path: '/ext',
|
path: '/ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -180,6 +182,7 @@ describe('git extension helpers', () => {
|
|||||||
it('should return UPDATE_AVAILABLE when remote hash is different', async () => {
|
it('should return UPDATE_AVAILABLE when remote hash is different', async () => {
|
||||||
const extension: GeminiCLIExtension = {
|
const extension: GeminiCLIExtension = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
id: 'test-id',
|
||||||
path: '/ext',
|
path: '/ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -205,6 +208,7 @@ describe('git extension helpers', () => {
|
|||||||
it('should return UP_TO_DATE when remote and local hashes are the same', async () => {
|
it('should return UP_TO_DATE when remote and local hashes are the same', async () => {
|
||||||
const extension: GeminiCLIExtension = {
|
const extension: GeminiCLIExtension = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
id: 'test-id',
|
||||||
path: '/ext',
|
path: '/ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -230,6 +234,7 @@ describe('git extension helpers', () => {
|
|||||||
it('should return ERROR on git error', async () => {
|
it('should return ERROR on git error', async () => {
|
||||||
const extension: GeminiCLIExtension = {
|
const extension: GeminiCLIExtension = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
id: 'test-id',
|
||||||
path: '/ext',
|
path: '/ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ describe('extensionsCommand', () => {
|
|||||||
|
|
||||||
const extensionOne: GeminiCLIExtension = {
|
const extensionOne: GeminiCLIExtension = {
|
||||||
name: 'ext-one',
|
name: 'ext-one',
|
||||||
|
id: 'ext-one-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
path: '/test/dir/ext-one',
|
path: '/test/dir/ext-one',
|
||||||
@@ -235,6 +236,7 @@ describe('extensionsCommand', () => {
|
|||||||
};
|
};
|
||||||
const extensionTwo: GeminiCLIExtension = {
|
const extensionTwo: GeminiCLIExtension = {
|
||||||
name: 'another-ext',
|
name: 'another-ext',
|
||||||
|
id: 'another-ext-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
path: '/test/dir/another-ext',
|
path: '/test/dir/another-ext',
|
||||||
@@ -247,6 +249,7 @@ describe('extensionsCommand', () => {
|
|||||||
};
|
};
|
||||||
const allExt: GeminiCLIExtension = {
|
const allExt: GeminiCLIExtension = {
|
||||||
name: 'all-ext',
|
name: 'all-ext',
|
||||||
|
id: 'all-ext-id',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
path: '/test/dir/all-ext',
|
path: '/test/dir/all-ext',
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ export interface SlashCommand {
|
|||||||
|
|
||||||
// Optional metadata for extension commands
|
// Optional metadata for extension commands
|
||||||
extensionName?: string;
|
extensionName?: string;
|
||||||
|
extensionId?: string;
|
||||||
|
|
||||||
// The action to run. Optional for parent commands that only group sub-commands.
|
// The action to run. Optional for parent commands that only group sub-commands.
|
||||||
action?: (
|
action?: (
|
||||||
|
|||||||
@@ -518,6 +518,7 @@ export const useSlashCommandProcessor = (
|
|||||||
command: resolvedCommandPath[0],
|
command: resolvedCommandPath[0],
|
||||||
subcommand,
|
subcommand,
|
||||||
status: SlashCommandStatus.ERROR,
|
status: SlashCommandStatus.ERROR,
|
||||||
|
extension_id: commandToExecute?.extensionId,
|
||||||
});
|
});
|
||||||
logSlashCommand(config, event);
|
logSlashCommand(config, event);
|
||||||
}
|
}
|
||||||
@@ -535,6 +536,7 @@ export const useSlashCommandProcessor = (
|
|||||||
command: resolvedCommandPath[0],
|
command: resolvedCommandPath[0],
|
||||||
subcommand,
|
subcommand,
|
||||||
status: SlashCommandStatus.SUCCESS,
|
status: SlashCommandStatus.SUCCESS,
|
||||||
|
extension_id: commandToExecute?.extensionId,
|
||||||
});
|
});
|
||||||
logSlashCommand(config, event);
|
logSlashCommand(config, event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ describe('useExtensionUpdates', () => {
|
|||||||
const extensions = [
|
const extensions = [
|
||||||
{
|
{
|
||||||
name: 'test-extension',
|
name: 'test-extension',
|
||||||
|
id: 'test-extension-id',
|
||||||
type: 'git',
|
type: 'git',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
path: '/some/path',
|
path: '/some/path',
|
||||||
@@ -269,6 +270,7 @@ describe('useExtensionUpdates', () => {
|
|||||||
const extensions = [
|
const extensions = [
|
||||||
{
|
{
|
||||||
name: 'test-extension-1',
|
name: 'test-extension-1',
|
||||||
|
id: 'test-extension-1-id',
|
||||||
type: 'git',
|
type: 'git',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
path: '/some/path1',
|
path: '/some/path1',
|
||||||
@@ -282,6 +284,8 @@ describe('useExtensionUpdates', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'test-extension-2',
|
name: 'test-extension-2',
|
||||||
|
id: 'test-extension-2-id',
|
||||||
|
|
||||||
type: 'git',
|
type: 'git',
|
||||||
version: '2.0.0',
|
version: '2.0.0',
|
||||||
path: '/some/path2',
|
path: '/some/path2',
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export interface GeminiCLIExtension {
|
|||||||
mcpServers?: Record<string, MCPServerConfig>;
|
mcpServers?: Record<string, MCPServerConfig>;
|
||||||
contextFiles: string[];
|
contextFiles: string[];
|
||||||
excludeTools?: string[];
|
excludeTools?: string[];
|
||||||
id?: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionInstallMetadata {
|
export interface ExtensionInstallMetadata {
|
||||||
|
|||||||
@@ -446,6 +446,15 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_TOOLS,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_TOOLS,
|
||||||
value: event.mcp_tools ? event.mcp_tools : '',
|
value: event.mcp_tools ? event.mcp_tools : '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key:
|
||||||
|
EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSIONS_COUNT,
|
||||||
|
value: event.extensions_count.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSION_IDS,
|
||||||
|
value: event.extension_ids.toString(),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
this.sessionData = data;
|
this.sessionData = data;
|
||||||
|
|
||||||
@@ -893,6 +902,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
value: event.extension_name,
|
value: event.extension_name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||||
|
value: event.extension_id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
||||||
value: event.extension_version,
|
value: event.extension_version,
|
||||||
@@ -921,6 +934,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
value: event.extension_name,
|
value: event.extension_name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||||
|
value: event.extension_id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UNINSTALL_STATUS,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UNINSTALL_STATUS,
|
||||||
value: event.status,
|
value: event.status,
|
||||||
@@ -941,6 +958,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
value: event.extension_name,
|
value: event.extension_name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||||
|
value: event.extension_id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
||||||
value: event.extension_version,
|
value: event.extension_version,
|
||||||
@@ -1037,6 +1058,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
value: event.extension_name,
|
value: event.extension_name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||||
|
value: event.extension_id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
gemini_cli_key:
|
gemini_cli_key:
|
||||||
EventMetadataKey.GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE,
|
EventMetadataKey.GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE,
|
||||||
@@ -1072,6 +1097,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
value: event.extension_name,
|
value: event.extension_name,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||||
|
value: event.extension_id,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
gemini_cli_key:
|
gemini_cli_key:
|
||||||
EventMetadataKey.GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE,
|
EventMetadataKey.GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// Defines valid event metadata keys for Clearcut logging.
|
// Defines valid event metadata keys for Clearcut logging.
|
||||||
export enum EventMetadataKey {
|
export enum EventMetadataKey {
|
||||||
// Deleted enums: 24
|
// Deleted enums: 24
|
||||||
// Next ID: 117
|
// Next ID: 122
|
||||||
|
|
||||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||||
|
|
||||||
@@ -373,6 +373,9 @@ export enum EventMetadataKey {
|
|||||||
// Logs the name of the extension.
|
// Logs the name of the extension.
|
||||||
GEMINI_CLI_EXTENSION_NAME = 85,
|
GEMINI_CLI_EXTENSION_NAME = 85,
|
||||||
|
|
||||||
|
// Logs the name of the extension.
|
||||||
|
GEMINI_CLI_EXTENSION_ID = 121,
|
||||||
|
|
||||||
// Logs the version of the extension.
|
// Logs the version of the extension.
|
||||||
GEMINI_CLI_EXTENSION_VERSION = 86,
|
GEMINI_CLI_EXTENSION_VERSION = 86,
|
||||||
|
|
||||||
@@ -391,6 +394,12 @@ export enum EventMetadataKey {
|
|||||||
// Logs the status of the extension uninstall
|
// Logs the status of the extension uninstall
|
||||||
GEMINI_CLI_EXTENSION_UPDATE_STATUS = 118,
|
GEMINI_CLI_EXTENSION_UPDATE_STATUS = 118,
|
||||||
|
|
||||||
|
// Logs the count of extensions in Start Session Event
|
||||||
|
GEMINI_CLI_START_SESSION_EXTENSIONS_COUNT = 119,
|
||||||
|
|
||||||
|
// Logs the name of extensions as a comma-separated string
|
||||||
|
GEMINI_CLI_START_SESSION_EXTENSION_IDS = 120,
|
||||||
|
|
||||||
// Logs the setting scope for an extension enablement.
|
// Logs the setting scope for an extension enablement.
|
||||||
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ describe('loggers', () => {
|
|||||||
getTargetDir: () => 'target-dir',
|
getTargetDir: () => 'target-dir',
|
||||||
getProxy: () => 'http://test.proxy.com:8080',
|
getProxy: () => 'http://test.proxy.com:8080',
|
||||||
getOutputFormat: () => OutputFormat.JSON,
|
getOutputFormat: () => OutputFormat.JSON,
|
||||||
|
getExtensions: () => [],
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
const startSessionEvent = new StartSessionEvent(mockConfig);
|
const startSessionEvent = new StartSessionEvent(mockConfig);
|
||||||
@@ -229,6 +230,8 @@ describe('loggers', () => {
|
|||||||
mcp_tools: undefined,
|
mcp_tools: undefined,
|
||||||
mcp_tools_count: undefined,
|
mcp_tools_count: undefined,
|
||||||
output_format: 'json',
|
output_format: 'json',
|
||||||
|
extension_ids: '',
|
||||||
|
extensions_count: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1042,6 +1045,10 @@ describe('loggers', () => {
|
|||||||
},
|
},
|
||||||
required: ['arg1', 'arg2'],
|
required: ['arg1', 'arg2'],
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'test-extension',
|
||||||
);
|
);
|
||||||
|
|
||||||
const call: CompletedToolCall = {
|
const call: CompletedToolCall = {
|
||||||
@@ -1076,6 +1083,7 @@ describe('loggers', () => {
|
|||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_TOOL_CALL,
|
'event.name': EVENT_TOOL_CALL,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
|
extension_id: 'test-extension',
|
||||||
function_name: 'mock_mcp_tool',
|
function_name: 'mock_mcp_tool',
|
||||||
function_args: JSON.stringify(
|
function_args: JSON.stringify(
|
||||||
{
|
{
|
||||||
@@ -1094,6 +1102,7 @@ describe('loggers', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
error_type: undefined,
|
error_type: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
|
content_length: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1310,7 +1319,8 @@ describe('loggers', () => {
|
|||||||
|
|
||||||
it('should log extension install event', () => {
|
it('should log extension install event', () => {
|
||||||
const event = new ExtensionInstallEvent(
|
const event = new ExtensionInstallEvent(
|
||||||
'vscode',
|
'testing',
|
||||||
|
'testing-id',
|
||||||
'0.1.0',
|
'0.1.0',
|
||||||
'git',
|
'git',
|
||||||
'success',
|
'success',
|
||||||
@@ -1323,14 +1333,14 @@ describe('loggers', () => {
|
|||||||
).toHaveBeenCalledWith(event);
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'Installed extension vscode',
|
body: 'Installed extension testing',
|
||||||
attributes: {
|
attributes: {
|
||||||
'session.id': 'test-session-id',
|
'session.id': 'test-session-id',
|
||||||
'user.email': 'test-user@example.com',
|
'user.email': 'test-user@example.com',
|
||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_EXTENSION_INSTALL,
|
'event.name': EVENT_EXTENSION_INSTALL,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
extension_name: 'vscode',
|
extension_name: 'testing',
|
||||||
extension_version: '0.1.0',
|
extension_version: '0.1.0',
|
||||||
extension_source: 'git',
|
extension_source: 'git',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@@ -1358,7 +1368,8 @@ describe('loggers', () => {
|
|||||||
|
|
||||||
it('should log extension update event', () => {
|
it('should log extension update event', () => {
|
||||||
const event = new ExtensionUpdateEvent(
|
const event = new ExtensionUpdateEvent(
|
||||||
'vscode',
|
'testing',
|
||||||
|
'testing-id',
|
||||||
'0.1.0',
|
'0.1.0',
|
||||||
'0.1.1',
|
'0.1.1',
|
||||||
'git',
|
'git',
|
||||||
@@ -1372,14 +1383,14 @@ describe('loggers', () => {
|
|||||||
).toHaveBeenCalledWith(event);
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'Updated extension vscode',
|
body: 'Updated extension testing',
|
||||||
attributes: {
|
attributes: {
|
||||||
'session.id': 'test-session-id',
|
'session.id': 'test-session-id',
|
||||||
'user.email': 'test-user@example.com',
|
'user.email': 'test-user@example.com',
|
||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_EXTENSION_UPDATE,
|
'event.name': EVENT_EXTENSION_UPDATE,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
extension_name: 'vscode',
|
extension_name: 'testing',
|
||||||
extension_version: '0.1.0',
|
extension_version: '0.1.0',
|
||||||
extension_previous_version: '0.1.1',
|
extension_previous_version: '0.1.1',
|
||||||
extension_source: 'git',
|
extension_source: 'git',
|
||||||
@@ -1407,7 +1418,11 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log extension uninstall event', () => {
|
it('should log extension uninstall event', () => {
|
||||||
const event = new ExtensionUninstallEvent('vscode', 'success');
|
const event = new ExtensionUninstallEvent(
|
||||||
|
'testing',
|
||||||
|
'testing-id',
|
||||||
|
'success',
|
||||||
|
);
|
||||||
|
|
||||||
logExtensionUninstall(mockConfig, event);
|
logExtensionUninstall(mockConfig, event);
|
||||||
|
|
||||||
@@ -1416,14 +1431,14 @@ describe('loggers', () => {
|
|||||||
).toHaveBeenCalledWith(event);
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'Uninstalled extension vscode',
|
body: 'Uninstalled extension testing',
|
||||||
attributes: {
|
attributes: {
|
||||||
'session.id': 'test-session-id',
|
'session.id': 'test-session-id',
|
||||||
'user.email': 'test-user@example.com',
|
'user.email': 'test-user@example.com',
|
||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_EXTENSION_UNINSTALL,
|
'event.name': EVENT_EXTENSION_UNINSTALL,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
extension_name: 'vscode',
|
extension_name: 'testing',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1445,7 +1460,7 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log extension enable event', () => {
|
it('should log extension enable event', () => {
|
||||||
const event = new ExtensionEnableEvent('vscode', 'user');
|
const event = new ExtensionEnableEvent('testing', 'testing-id', 'user');
|
||||||
|
|
||||||
logExtensionEnable(mockConfig, event);
|
logExtensionEnable(mockConfig, event);
|
||||||
|
|
||||||
@@ -1454,14 +1469,14 @@ describe('loggers', () => {
|
|||||||
).toHaveBeenCalledWith(event);
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'Enabled extension vscode',
|
body: 'Enabled extension testing',
|
||||||
attributes: {
|
attributes: {
|
||||||
'session.id': 'test-session-id',
|
'session.id': 'test-session-id',
|
||||||
'user.email': 'test-user@example.com',
|
'user.email': 'test-user@example.com',
|
||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_EXTENSION_ENABLE,
|
'event.name': EVENT_EXTENSION_ENABLE,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
extension_name: 'vscode',
|
extension_name: 'testing',
|
||||||
setting_scope: 'user',
|
setting_scope: 'user',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1483,7 +1498,7 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log extension disable event', () => {
|
it('should log extension disable event', () => {
|
||||||
const event = new ExtensionDisableEvent('vscode', 'user');
|
const event = new ExtensionDisableEvent('testing', 'testing-id', 'user');
|
||||||
|
|
||||||
logExtensionDisable(mockConfig, event);
|
logExtensionDisable(mockConfig, event);
|
||||||
|
|
||||||
@@ -1492,14 +1507,14 @@ describe('loggers', () => {
|
|||||||
).toHaveBeenCalledWith(event);
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'Disabled extension vscode',
|
body: 'Disabled extension testing',
|
||||||
attributes: {
|
attributes: {
|
||||||
'session.id': 'test-session-id',
|
'session.id': 'test-session-id',
|
||||||
'user.email': 'test-user@example.com',
|
'user.email': 'test-user@example.com',
|
||||||
'installation.id': 'test-installation-id',
|
'installation.id': 'test-installation-id',
|
||||||
'event.name': EVENT_EXTENSION_DISABLE,
|
'event.name': EVENT_EXTENSION_DISABLE,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
extension_name: 'vscode',
|
extension_name: 'testing',
|
||||||
setting_scope: 'user',
|
setting_scope: 'user',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
|||||||
mcp_tools_count?: number;
|
mcp_tools_count?: number;
|
||||||
mcp_tools?: string;
|
mcp_tools?: string;
|
||||||
output_format: OutputFormat;
|
output_format: OutputFormat;
|
||||||
|
extensions_count: number;
|
||||||
|
extension_ids: string;
|
||||||
|
|
||||||
constructor(config: Config, toolRegistry?: ToolRegistry) {
|
constructor(config: Config, toolRegistry?: ToolRegistry) {
|
||||||
const generatorConfig = config.getContentGeneratorConfig();
|
const generatorConfig = config.getContentGeneratorConfig();
|
||||||
@@ -85,6 +87,9 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
|||||||
config.getFileFilteringRespectGitIgnore();
|
config.getFileFilteringRespectGitIgnore();
|
||||||
this.mcp_servers_count = mcpServers ? Object.keys(mcpServers).length : 0;
|
this.mcp_servers_count = mcpServers ? Object.keys(mcpServers).length : 0;
|
||||||
this.output_format = config.getOutputFormat();
|
this.output_format = config.getOutputFormat();
|
||||||
|
const extensions = config.getExtensions();
|
||||||
|
this.extensions_count = extensions.length;
|
||||||
|
this.extension_ids = extensions.map((e) => e.id).join(',');
|
||||||
if (toolRegistry) {
|
if (toolRegistry) {
|
||||||
const mcpTools = toolRegistry
|
const mcpTools = toolRegistry
|
||||||
.getAllTools()
|
.getAllTools()
|
||||||
@@ -116,6 +121,8 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
|||||||
mcp_tools: this.mcp_tools,
|
mcp_tools: this.mcp_tools,
|
||||||
mcp_tools_count: this.mcp_tools_count,
|
mcp_tools_count: this.mcp_tools_count,
|
||||||
output_format: this.output_format,
|
output_format: this.output_format,
|
||||||
|
extensions_count: this.extensions_count,
|
||||||
|
extension_ids: this.extension_ids,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +205,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
|||||||
tool_type: 'native' | 'mcp';
|
tool_type: 'native' | 'mcp';
|
||||||
content_length?: number;
|
content_length?: number;
|
||||||
mcp_server_name?: string;
|
mcp_server_name?: string;
|
||||||
|
extension_id?: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
metadata?: { [key: string]: any };
|
metadata?: { [key: string]: any };
|
||||||
|
|
||||||
@@ -243,6 +251,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
|||||||
) {
|
) {
|
||||||
this.tool_type = 'mcp';
|
this.tool_type = 'mcp';
|
||||||
this.mcp_server_name = call.tool.serverName;
|
this.mcp_server_name = call.tool.serverName;
|
||||||
|
this.extension_id = call.tool.extensionId;
|
||||||
} else {
|
} else {
|
||||||
this.tool_type = 'native';
|
this.tool_type = 'native';
|
||||||
}
|
}
|
||||||
@@ -292,6 +301,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
|||||||
tool_type: this.tool_type,
|
tool_type: this.tool_type,
|
||||||
content_length: this.content_length,
|
content_length: this.content_length,
|
||||||
mcp_server_name: this.mcp_server_name,
|
mcp_server_name: this.mcp_server_name,
|
||||||
|
extension_id: this.extension_id,
|
||||||
metadata: this.metadata,
|
metadata: this.metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -627,6 +637,7 @@ export interface SlashCommandEvent extends BaseTelemetryEvent {
|
|||||||
command: string;
|
command: string;
|
||||||
subcommand?: string;
|
subcommand?: string;
|
||||||
status?: SlashCommandStatus;
|
status?: SlashCommandStatus;
|
||||||
|
extension_id?: string;
|
||||||
toOpenTelemetryAttributes(config: Config): LogAttributes;
|
toOpenTelemetryAttributes(config: Config): LogAttributes;
|
||||||
toLogBody(): string;
|
toLogBody(): string;
|
||||||
}
|
}
|
||||||
@@ -635,6 +646,7 @@ export function makeSlashCommandEvent({
|
|||||||
command,
|
command,
|
||||||
subcommand,
|
subcommand,
|
||||||
status,
|
status,
|
||||||
|
extension_id,
|
||||||
}: Omit<
|
}: Omit<
|
||||||
SlashCommandEvent,
|
SlashCommandEvent,
|
||||||
CommonFields | 'toOpenTelemetryAttributes' | 'toLogBody'
|
CommonFields | 'toOpenTelemetryAttributes' | 'toLogBody'
|
||||||
@@ -645,6 +657,7 @@ export function makeSlashCommandEvent({
|
|||||||
command,
|
command,
|
||||||
subcommand,
|
subcommand,
|
||||||
status,
|
status,
|
||||||
|
extension_id,
|
||||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||||
return {
|
return {
|
||||||
...getCommonAttributes(config),
|
...getCommonAttributes(config),
|
||||||
@@ -653,6 +666,7 @@ export function makeSlashCommandEvent({
|
|||||||
command: this.command,
|
command: this.command,
|
||||||
subcommand: this.subcommand,
|
subcommand: this.subcommand,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
extension_id: this.extension_id,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
toLogBody(): string {
|
toLogBody(): string {
|
||||||
@@ -1041,12 +1055,14 @@ export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
|||||||
'event.name': 'extension_install';
|
'event.name': 'extension_install';
|
||||||
'event.timestamp': string;
|
'event.timestamp': string;
|
||||||
extension_name: string;
|
extension_name: string;
|
||||||
|
extension_id: string;
|
||||||
extension_version: string;
|
extension_version: string;
|
||||||
extension_source: string;
|
extension_source: string;
|
||||||
status: 'success' | 'error';
|
status: 'success' | 'error';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
extension_name: string,
|
extension_name: string,
|
||||||
|
extension_id: string,
|
||||||
extension_version: string,
|
extension_version: string,
|
||||||
extension_source: string,
|
extension_source: string,
|
||||||
status: 'success' | 'error',
|
status: 'success' | 'error',
|
||||||
@@ -1054,6 +1070,7 @@ export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
|||||||
this['event.name'] = 'extension_install';
|
this['event.name'] = 'extension_install';
|
||||||
this['event.timestamp'] = new Date().toISOString();
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
this.extension_name = extension_name;
|
this.extension_name = extension_name;
|
||||||
|
this.extension_id = extension_id;
|
||||||
this.extension_version = extension_version;
|
this.extension_version = extension_version;
|
||||||
this.extension_source = extension_source;
|
this.extension_source = extension_source;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
@@ -1132,12 +1149,18 @@ export class ExtensionUninstallEvent implements BaseTelemetryEvent {
|
|||||||
'event.name': 'extension_uninstall';
|
'event.name': 'extension_uninstall';
|
||||||
'event.timestamp': string;
|
'event.timestamp': string;
|
||||||
extension_name: string;
|
extension_name: string;
|
||||||
|
extension_id: string;
|
||||||
status: 'success' | 'error';
|
status: 'success' | 'error';
|
||||||
|
|
||||||
constructor(extension_name: string, status: 'success' | 'error') {
|
constructor(
|
||||||
|
extension_name: string,
|
||||||
|
extension_id: string,
|
||||||
|
status: 'success' | 'error',
|
||||||
|
) {
|
||||||
this['event.name'] = 'extension_uninstall';
|
this['event.name'] = 'extension_uninstall';
|
||||||
this['event.timestamp'] = new Date().toISOString();
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
this.extension_name = extension_name;
|
this.extension_name = extension_name;
|
||||||
|
this.extension_id = extension_id;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1161,6 +1184,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
|||||||
'event.name': 'extension_update';
|
'event.name': 'extension_update';
|
||||||
'event.timestamp': string;
|
'event.timestamp': string;
|
||||||
extension_name: string;
|
extension_name: string;
|
||||||
|
extension_id: string;
|
||||||
extension_previous_version: string;
|
extension_previous_version: string;
|
||||||
extension_version: string;
|
extension_version: string;
|
||||||
extension_source: string;
|
extension_source: string;
|
||||||
@@ -1168,6 +1192,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
extension_name: string,
|
extension_name: string,
|
||||||
|
extension_id: string,
|
||||||
extension_version: string,
|
extension_version: string,
|
||||||
extension_previous_version: string,
|
extension_previous_version: string,
|
||||||
extension_source: string,
|
extension_source: string,
|
||||||
@@ -1176,6 +1201,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
|||||||
this['event.name'] = 'extension_update';
|
this['event.name'] = 'extension_update';
|
||||||
this['event.timestamp'] = new Date().toISOString();
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
this.extension_name = extension_name;
|
this.extension_name = extension_name;
|
||||||
|
this.extension_id = extension_id;
|
||||||
this.extension_version = extension_version;
|
this.extension_version = extension_version;
|
||||||
this.extension_previous_version = extension_previous_version;
|
this.extension_previous_version = extension_previous_version;
|
||||||
this.extension_source = extension_source;
|
this.extension_source = extension_source;
|
||||||
@@ -1205,12 +1231,18 @@ export class ExtensionEnableEvent implements BaseTelemetryEvent {
|
|||||||
'event.name': 'extension_enable';
|
'event.name': 'extension_enable';
|
||||||
'event.timestamp': string;
|
'event.timestamp': string;
|
||||||
extension_name: string;
|
extension_name: string;
|
||||||
|
extension_id: string;
|
||||||
setting_scope: string;
|
setting_scope: string;
|
||||||
|
|
||||||
constructor(extension_name: string, settingScope: string) {
|
constructor(
|
||||||
|
extension_name: string,
|
||||||
|
extension_id: string,
|
||||||
|
settingScope: string,
|
||||||
|
) {
|
||||||
this['event.name'] = 'extension_enable';
|
this['event.name'] = 'extension_enable';
|
||||||
this['event.timestamp'] = new Date().toISOString();
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
this.extension_name = extension_name;
|
this.extension_name = extension_name;
|
||||||
|
this.extension_id = extension_id;
|
||||||
this.setting_scope = settingScope;
|
this.setting_scope = settingScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1291,12 +1323,18 @@ export class ExtensionDisableEvent implements BaseTelemetryEvent {
|
|||||||
'event.name': 'extension_disable';
|
'event.name': 'extension_disable';
|
||||||
'event.timestamp': string;
|
'event.timestamp': string;
|
||||||
extension_name: string;
|
extension_name: string;
|
||||||
|
extension_id: string;
|
||||||
setting_scope: string;
|
setting_scope: string;
|
||||||
|
|
||||||
constructor(extension_name: string, settingScope: string) {
|
constructor(
|
||||||
|
extension_name: string,
|
||||||
|
extension_id: string,
|
||||||
|
settingScope: string,
|
||||||
|
) {
|
||||||
this['event.name'] = 'extension_disable';
|
this['event.name'] = 'extension_disable';
|
||||||
this['event.timestamp'] = new Date().toISOString();
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
this.extension_name = extension_name;
|
this.extension_name = extension_name;
|
||||||
|
this.extension_id = extension_id;
|
||||||
this.setting_scope = settingScope;
|
this.setting_scope = settingScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -610,6 +610,7 @@ export async function discoverTools(
|
|||||||
mcpServerConfig.trust,
|
mcpServerConfig.trust,
|
||||||
undefined,
|
undefined,
|
||||||
cliConfig,
|
cliConfig,
|
||||||
|
mcpServerConfig.extension?.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
|||||||
readonly trust?: boolean,
|
readonly trust?: boolean,
|
||||||
nameOverride?: string,
|
nameOverride?: string,
|
||||||
private readonly cliConfig?: Config,
|
private readonly cliConfig?: Config,
|
||||||
|
override readonly extensionId?: string,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
nameOverride ?? generateValidName(serverToolName),
|
nameOverride ?? generateValidName(serverToolName),
|
||||||
@@ -222,6 +223,8 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
|||||||
parameterSchema,
|
parameterSchema,
|
||||||
true, // isOutputMarkdown
|
true, // isOutputMarkdown
|
||||||
false, // canUpdateOutput
|
false, // canUpdateOutput
|
||||||
|
undefined, // messageBus
|
||||||
|
extensionId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +238,7 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
|||||||
this.trust,
|
this.trust,
|
||||||
`${this.serverName}__${this.serverToolName}`,
|
`${this.serverName}__${this.serverToolName}`,
|
||||||
this.cliConfig,
|
this.cliConfig,
|
||||||
|
this.extensionId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ export abstract class DeclarativeTool<
|
|||||||
readonly isOutputMarkdown: boolean = true,
|
readonly isOutputMarkdown: boolean = true,
|
||||||
readonly canUpdateOutput: boolean = false,
|
readonly canUpdateOutput: boolean = false,
|
||||||
readonly messageBus?: MessageBus,
|
readonly messageBus?: MessageBus,
|
||||||
|
readonly extensionId?: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get schema(): FunctionDeclaration {
|
get schema(): FunctionDeclaration {
|
||||||
|
|||||||
Reference in New Issue
Block a user