Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,12 +653,11 @@ configurationRegistry.registerConfiguration({
default: true,
tags: ['preview'],
},
[ChatConfiguration.PluginPaths]: {
[ChatConfiguration.PluginLocations]: {
type: 'object',
additionalProperties: { type: 'boolean' },
restricted: true,
markdownDescription: nls.localize('chat.plugins.paths', "Plugin directories to discover. Each key is a path that points directly to a plugin folder, and the value enables (`true`) or disables (`false`) it. Paths can be absolute or relative to the workspace root."),
default: {},
markdownDescription: nls.localize('chat.pluginLocations', "Plugin directories to discover. Each key is a path that points directly to a plugin folder, and the value enables (`true`) or disables (`false`) it. Paths can be absolute, relative to the workspace root, or start with `~/` for the user's home directory."),
scope: ConfigurationScope.MACHINE,
Comment thread
connor4312 marked this conversation as resolved.
tags: ['experimental'],
},
Expand Down Expand Up @@ -1323,6 +1322,13 @@ Registry.as<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration).
return [];
}
},
{
key: 'chat.plugins.paths',
migrateFn: (value: unknown, _accessor) => ([
['chat.plugins.paths', { value: undefined }],
[ChatConfiguration.PluginLocations, { value }]
])
},
]);

class ChatResolverContribution extends Disposable {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RawContextKey } from '../../../../platform/contextkey/common/contextkey
export enum ChatConfiguration {
AIDisabled = 'chat.disableAIFeatures',
PluginsEnabled = 'chat.plugins.enabled',
PluginPaths = 'chat.plugins.paths',
PluginLocations = 'chat.pluginLocations',
Comment thread
connor4312 marked this conversation as resolved.
PluginMarketplaces = 'chat.plugins.marketplaces',
AgentEnabled = 'chat.agent.enabled',
PlanAgentDefaultModel = 'chat.planAgent.defaultModel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { RunOnceScheduler } from '../../../../../base/common/async.js';
import { parse as parseJSONC } from '../../../../../base/common/json.js';
import { untildify } from '../../../../../base/common/labels.js';
import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';
import { ResourceSet } from '../../../../../base/common/map.js';
import { cloneAndChange } from '../../../../../base/common/objects.js';
Expand Down Expand Up @@ -699,7 +700,7 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements

export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery {

private readonly _pluginPathsConfig: IObservable<Record<string, boolean>>;
private readonly _pluginLocationsConfig: IObservable<Record<string, boolean>>;

constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
Expand All @@ -711,28 +712,29 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
@IInstantiationService instantiationService: IInstantiationService,
) {
super(fileService, pathService, logService, instantiationService);
this._pluginPathsConfig = observableConfigValue<Record<string, boolean>>(ChatConfiguration.PluginPaths, {}, _configurationService);
this._pluginLocationsConfig = observableConfigValue<Record<string, boolean>>(ChatConfiguration.PluginLocations, {}, _configurationService);
}

public override start(): void {
const scheduler = this._register(new RunOnceScheduler(() => this._refreshPlugins(), 0));
this._register(autorun(reader => {
this._pluginPathsConfig.read(reader);
this._pluginLocationsConfig.read(reader);
scheduler.schedule();
}));
scheduler.schedule();
}

protected override async _discoverPluginSources(): Promise<readonly IPluginSource[]> {
const sources: IPluginSource[] = [];
const config = this._pluginPathsConfig.get();
const config = this._pluginLocationsConfig.get();
const userHome = await this._getUserHome();

for (const [path, enabled] of Object.entries(config)) {
if (!path.trim()) {
continue;
}

const resources = this._resolvePluginPath(path.trim());
const resources = this._resolvePluginPath(path.trim(), userHome);
for (const resource of resources) {
let stat;
try {
Expand Down Expand Up @@ -762,11 +764,23 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
return sources;
}

private async _getUserHome(): Promise<string> {
const userHome = await this._pathService.userHome();
return userHome.scheme === 'file' ? userHome.fsPath : userHome.path;
}

/**
* Resolves a plugin path to one or more resource URIs. Absolute paths are
* used directly; relative paths are resolved against each workspace folder.
* Resolves a plugin path to one or more resource URIs. Supports:
* - Absolute paths (used directly)
* - Tilde paths (expanded to user home directory)
* - Relative paths (resolved against each workspace folder)
*/
private _resolvePluginPath(path: string): URI[] {
private _resolvePluginPath(path: string, userHome: string): URI[] {
if (path.startsWith('~')) {
path = untildify(path, userHome);
}
Comment thread
connor4312 marked this conversation as resolved.

// Handle absolute paths
if (win32.isAbsolute(path) || posix.isAbsolute(path)) {
return [URI.file(path)];
}
Expand All @@ -781,7 +795,7 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
* writing to the most specific config target where the key is defined.
*/
private _updatePluginPathEnabled(configKey: string, value: boolean): void {
const inspected = this._configurationService.inspect<Record<string, boolean>>(ChatConfiguration.PluginPaths);
const inspected = this._configurationService.inspect<Record<string, boolean>>(ChatConfiguration.PluginLocations);

// Walk from most specific to least specific to find where this key is defined
const targets = [
Expand All @@ -797,7 +811,7 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
const mapping = getConfigValueInTarget(inspected, target);
if (mapping && Object.prototype.hasOwnProperty.call(mapping, configKey)) {
this._configurationService.updateValue(
ChatConfiguration.PluginPaths,
ChatConfiguration.PluginLocations,
{ ...mapping, [configKey]: value },
target,
);
Expand All @@ -808,18 +822,18 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
// Key not found in any target; write to USER_LOCAL as default
const current = getConfigValueInTarget(inspected, ConfigurationTarget.USER_LOCAL) ?? {};
this._configurationService.updateValue(
ChatConfiguration.PluginPaths,
ChatConfiguration.PluginLocations,
{ ...current, [configKey]: value },
ConfigurationTarget.USER_LOCAL,
);
}

/**
* Removes a plugin path from `chat.plugins.paths` in the most specific
* Removes a plugin path from `chat.pluginLocations` in the most specific
* config target where the key is defined.
*/
private _removePluginPath(configKey: string): void {
const inspected = this._configurationService.inspect<Record<string, boolean>>(ChatConfiguration.PluginPaths);
const inspected = this._configurationService.inspect<Record<string, boolean>>(ChatConfiguration.PluginLocations);

const targets = [
ConfigurationTarget.WORKSPACE_FOLDER,
Expand All @@ -836,7 +850,7 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
const updated = { ...mapping };
delete updated[configKey];
this._configurationService.updateValue(
ChatConfiguration.PluginPaths,
ChatConfiguration.PluginLocations,
updated,
target,
);
Expand Down
Loading