|
| 1 | +import * as vscode from "vscode"; |
| 2 | +import { |
| 3 | + CHAT_CONFIG_SECTION, |
| 4 | + CHAT_PLUGINS_MARKETPLACE_ID, |
| 5 | + CHAT_PLUGINS_MARKETPLACES, |
| 6 | + CONFIG_AI_ENABLE_CHAT_PLUGINS, |
| 7 | + CONFIG_SECTION, |
| 8 | + CONTEXT_CHAT_PLUGINS_MARKETPLACE_REGISTERED, |
| 9 | +} from "./config"; |
| 10 | + |
| 11 | +// Forms of the marketplace reference we recognize when checking/removing an |
| 12 | +// existing entry: the `owner/repo` shorthand plus the common git-URL variants. |
| 13 | +const MARKETPLACE_ALIASES = [ |
| 14 | + CHAT_PLUGINS_MARKETPLACE_ID, |
| 15 | + `https://github.com/${CHAT_PLUGINS_MARKETPLACE_ID}`, |
| 16 | + `https://github.com/${CHAT_PLUGINS_MARKETPLACE_ID}.git`, |
| 17 | + `git@github.com:${CHAT_PLUGINS_MARKETPLACE_ID}`, |
| 18 | + `git@github.com:${CHAT_PLUGINS_MARKETPLACE_ID}.git`, |
| 19 | +].map((s) => s.toLowerCase()); |
| 20 | + |
| 21 | +export class ChatPluginsManager implements vscode.Disposable { |
| 22 | + private readonly _disposables: vscode.Disposable; |
| 23 | + |
| 24 | + constructor( |
| 25 | + public readonly extensionContext: vscode.ExtensionContext, |
| 26 | + public readonly outputChannel: vscode.OutputChannel, |
| 27 | + ) { |
| 28 | + this._disposables = vscode.Disposable.from( |
| 29 | + vscode.commands.registerCommand("robotcode.chatPlugins.addMarketplace", () => this.addMarketplace()), |
| 30 | + vscode.commands.registerCommand("robotcode.chatPlugins.removeMarketplace", () => this.removeMarketplace()), |
| 31 | + vscode.workspace.onDidChangeConfiguration((event) => { |
| 32 | + if (event.affectsConfiguration(`${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}`)) { |
| 33 | + ChatPluginsManager.updateContextKey(); |
| 34 | + } |
| 35 | + }), |
| 36 | + ); |
| 37 | + |
| 38 | + ChatPluginsManager.updateContextKey(); |
| 39 | + } |
| 40 | + |
| 41 | + private static readMarketplaces(): string[] { |
| 42 | + return vscode.workspace.getConfiguration(CHAT_CONFIG_SECTION).get<string[]>(CHAT_PLUGINS_MARKETPLACES, []); |
| 43 | + } |
| 44 | + |
| 45 | + private static matchesOurMarketplace(entry: string): boolean { |
| 46 | + return MARKETPLACE_ALIASES.includes(entry.trim().toLowerCase()); |
| 47 | + } |
| 48 | + |
| 49 | + private static isRegistered(list: string[]): boolean { |
| 50 | + return list.some((entry) => ChatPluginsManager.matchesOurMarketplace(entry)); |
| 51 | + } |
| 52 | + |
| 53 | + private static isBundledPluginEnabled(): boolean { |
| 54 | + return vscode.workspace.getConfiguration(CONFIG_SECTION).get<boolean>(CONFIG_AI_ENABLE_CHAT_PLUGINS, true); |
| 55 | + } |
| 56 | + |
| 57 | + private static updateContextKey(): void { |
| 58 | + void vscode.commands.executeCommand( |
| 59 | + "setContext", |
| 60 | + CONTEXT_CHAT_PLUGINS_MARKETPLACE_REGISTERED, |
| 61 | + ChatPluginsManager.isRegistered(ChatPluginsManager.readMarketplaces()), |
| 62 | + ); |
| 63 | + } |
| 64 | + |
| 65 | + private async writeMarketplaces(list: string[]): Promise<boolean> { |
| 66 | + try { |
| 67 | + await vscode.workspace |
| 68 | + .getConfiguration(CHAT_CONFIG_SECTION) |
| 69 | + .update(CHAT_PLUGINS_MARKETPLACES, list, vscode.ConfigurationTarget.Global); |
| 70 | + ChatPluginsManager.updateContextKey(); |
| 71 | + return true; |
| 72 | + } catch (error) { |
| 73 | + this.outputChannel.appendLine(`Failed to update ${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}: ${error}`); |
| 74 | + await vscode.window.showErrorMessage( |
| 75 | + `Could not update the chat plugin marketplaces setting. This requires GitHub Copilot Chat (or another agent that provides "${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}") to be installed.`, |
| 76 | + ); |
| 77 | + return false; |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + private async addMarketplace(): Promise<void> { |
| 82 | + const list = ChatPluginsManager.readMarketplaces(); |
| 83 | + if (ChatPluginsManager.isRegistered(list)) { |
| 84 | + await vscode.window.showInformationMessage("The RobotCode chat plugins marketplace is already added."); |
| 85 | + return; |
| 86 | + } |
| 87 | + |
| 88 | + if (!(await this.writeMarketplaces([...list, CHAT_PLUGINS_MARKETPLACE_ID]))) { |
| 89 | + return; |
| 90 | + } |
| 91 | + |
| 92 | + if (!ChatPluginsManager.isBundledPluginEnabled()) { |
| 93 | + await vscode.window.showInformationMessage( |
| 94 | + `Added the RobotCode chat plugins marketplace (${CHAT_PLUGINS_MARKETPLACE_ID}).`, |
| 95 | + ); |
| 96 | + return; |
| 97 | + } |
| 98 | + |
| 99 | + // The extension already bundles this plugin, so installing it from the |
| 100 | + // marketplace would run two copies. Offer to switch the bundled one off. |
| 101 | + const disable = "Disable Bundled Plugin"; |
| 102 | + const choice = await vscode.window.showInformationMessage( |
| 103 | + `Added the RobotCode chat plugins marketplace (${CHAT_PLUGINS_MARKETPLACE_ID}). The extension also bundles this plugin — if you install it from the marketplace, disable the bundled copy to avoid running it twice.`, |
| 104 | + disable, |
| 105 | + ); |
| 106 | + if (choice === disable) { |
| 107 | + await vscode.workspace |
| 108 | + .getConfiguration(CONFIG_SECTION) |
| 109 | + .update(CONFIG_AI_ENABLE_CHAT_PLUGINS, false, vscode.ConfigurationTarget.Global); |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + private async removeMarketplace(): Promise<void> { |
| 114 | + const list = ChatPluginsManager.readMarketplaces(); |
| 115 | + if (!ChatPluginsManager.isRegistered(list)) { |
| 116 | + await vscode.window.showInformationMessage("The RobotCode chat plugins marketplace is not added."); |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | + const next = list.filter((entry) => !ChatPluginsManager.matchesOurMarketplace(entry)); |
| 121 | + if (await this.writeMarketplaces(next)) { |
| 122 | + await vscode.window.showInformationMessage("Removed the RobotCode chat plugins marketplace."); |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + dispose(): void { |
| 127 | + this._disposables.dispose(); |
| 128 | + } |
| 129 | +} |
0 commit comments