diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 53a86ec5245..12567365126 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -60,6 +60,7 @@ import { IHostService } from '../../../services/host/browser/host.js'; import Severity from '../../../../base/common/severity.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { isWeb } from '../../../../base/common/platform.js'; +import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extensions/browser/extensionUrlHandler.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -109,7 +110,9 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr constructor( @IProductService private readonly productService: IProductService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ICommandService private readonly commandService: ICommandService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); @@ -122,6 +125,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr this.registerChatWelcome(); this.registerActions(); + this.registerUrlLinkHandler(); } private registerChatWelcome(): void { @@ -292,6 +296,18 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr registerAction2(ChatSetupHideAction); registerAction2(UpgradePlanAction); } + + private registerUrlLinkHandler(): void { + this._register(ExtensionUrlHandlerOverrideRegistry.registerHandler(URI.parse(`${this.productService.urlProtocol}://${defaultChat.chatExtensionId}`), { + handleURL: async () => { + this.telemetryService.publicLog2('workbenchActionExecuted', { id: TRIGGER_SETUP_COMMAND_ID, from: 'url' }); + + await this.commandService.executeCommand(TRIGGER_SETUP_COMMAND_ID); + + return true; + } + })); + } } //#endregion diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index ac3c7ed5006..04aa6fe51f8 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize, localize2 } from '../../../../nls.js'; -import { IDisposable, combinedDisposable } from '../../../../base/common/lifecycle.js'; +import { IDisposable, combinedDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -27,6 +27,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { isCancellationError } from '../../../../base/common/errors.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; +import { ResourceMap } from '../../../../base/common/map.js'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -99,6 +100,25 @@ type ExtensionUrlReloadHandlerClassification = { comment: 'This is used to understand the drop funnel of extension URI handling by the OS & VS Code.'; }; +export interface IExtensionUrlHandlerOverride { + handleURL(uri: URI): Promise; +} + +export class ExtensionUrlHandlerOverrideRegistry { + + private static readonly handlers = new ResourceMap(); + + static registerHandler(uri: URI, handler: IExtensionUrlHandlerOverride): IDisposable { + this.handlers.set(uri, handler); + + return toDisposable(() => this.handlers.delete(uri)); + } + + static getHandler(uri: URI): IExtensionUrlHandlerOverride | undefined { + return this.handlers.get(uri); + } +} + /** * This class handles URLs which are directed towards extensions. * If a URL is directed towards an inactive extension, it buffers it, @@ -153,6 +173,14 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return false; } + const overrideHandler = ExtensionUrlHandlerOverrideRegistry.getHandler(uri); + if (overrideHandler) { + const handled = await overrideHandler.handleURL(uri); + if (handled) { + return handled; + } + } + const extensionId = uri.authority; this.telemetryService.publicLog2('uri_invoked/start', { extensionId });