Path: blob/main/src/vs/workbench/contrib/browserView/electron-browser/tools/browserTools.contribution.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Codicon } from '../../../../../base/common/codicons.js';6import { Disposable, DisposableMap, DisposableStore } from '../../../../../base/common/lifecycle.js';7import { localize } from '../../../../../nls.js';8import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';9import { IAgentNetworkFilterService } from '../../../../../platform/networkFilter/common/networkFilterService.js';10import { registerWorkbenchContribution2, WorkbenchPhase, type IWorkbenchContribution } from '../../../../common/contributions.js';11import { IEditorService } from '../../../../services/editor/common/editorService.js';12import { IChatContextService } from '../../../chat/browser/contextContrib/chatContextService.js';13import { ILanguageModelToolsService, ToolDataSource, ToolSet } from '../../../chat/common/tools/languageModelToolsService.js';14import { BrowserViewSharingState, IBrowserViewWorkbenchService } from '../../common/browserView.js';15import { formatBrowserEditorList } from './browserToolHelpers.js';16import { ClickBrowserTool, ClickBrowserToolData } from './clickBrowserTool.js';17import { DragElementTool, DragElementToolData } from './dragElementTool.js';18import { HandleDialogBrowserTool, HandleDialogBrowserToolData } from './handleDialogBrowserTool.js';19import { HoverElementTool, HoverElementToolData } from './hoverElementTool.js';20import { NavigateBrowserTool, NavigateBrowserToolData } from './navigateBrowserTool.js';21import { OpenBrowserTool, OpenBrowserToolData } from './openBrowserTool.js';22import { OpenBrowserToolNonAgentic, OpenBrowserToolNonAgenticData } from './openBrowserToolNonAgentic.js';23import { ReadBrowserTool, ReadBrowserToolData } from './readBrowserTool.js';24import { RunPlaywrightCodeTool, RunPlaywrightCodeToolData } from './runPlaywrightCodeTool.js';25import { ScreenshotBrowserTool, ScreenshotBrowserToolData } from './screenshotBrowserTool.js';26import { TypeBrowserTool, TypeBrowserToolData } from './typeBrowserTool.js';272829class BrowserChatAgentToolsContribution extends Disposable implements IWorkbenchContribution {3031static readonly ID = 'browserView.chatAgentTools';32private static readonly CONTEXT_ID = 'browserView.trackedPages';3334private readonly _toolsStore = this._register(new DisposableStore());35private readonly _modelListeners = this._register(new DisposableMap<string, DisposableStore>());36private readonly _browserToolSet: ToolSet;3738constructor(39@IInstantiationService private readonly instantiationService: IInstantiationService,40@ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService,41@IChatContextService private readonly chatContextService: IChatContextService,42@IEditorService private readonly editorService: IEditorService,43@IBrowserViewWorkbenchService private readonly browserViewService: IBrowserViewWorkbenchService,44@IAgentNetworkFilterService private readonly agentNetworkFilterService: IAgentNetworkFilterService,45) {46super();4748this._browserToolSet = this._register(this.toolsService.createToolSet(49ToolDataSource.Internal,50'browser',51'browser',52{53icon: Codicon.globe,54description: localize('browserToolSet.description', 'Open and interact with integrated browser pages'),55}56));5758this._updateToolRegistrations();5960this._register(this.browserViewService.onDidChangeSharingAvailable(() => {61this._updateToolRegistrations();62}));63}6465private _updateToolRegistrations(): void {66this._toolsStore.clear();67this._modelListeners.clearAndDisposeAll();6869if (!this.browserViewService.isSharingAvailable) {70// If chat tools are disabled, we only register the non-agentic open tool,71// which allows opening browser pages without granting access to their contents.72this._toolsStore.add(this.toolsService.registerTool(OpenBrowserToolNonAgenticData, this.instantiationService.createInstance(OpenBrowserToolNonAgentic)));73this._toolsStore.add(this._browserToolSet.addTool(OpenBrowserToolNonAgenticData));74this.chatContextService.updateWorkspaceContextItems(BrowserChatAgentToolsContribution.CONTEXT_ID, []);75return;76}7778this._toolsStore.add(this.toolsService.registerTool(OpenBrowserToolData, this.instantiationService.createInstance(OpenBrowserTool)));79this._toolsStore.add(this.toolsService.registerTool(ReadBrowserToolData, this.instantiationService.createInstance(ReadBrowserTool)));80this._toolsStore.add(this.toolsService.registerTool(ScreenshotBrowserToolData, this.instantiationService.createInstance(ScreenshotBrowserTool)));81this._toolsStore.add(this.toolsService.registerTool(NavigateBrowserToolData, this.instantiationService.createInstance(NavigateBrowserTool)));82this._toolsStore.add(this.toolsService.registerTool(ClickBrowserToolData, this.instantiationService.createInstance(ClickBrowserTool)));83this._toolsStore.add(this.toolsService.registerTool(DragElementToolData, this.instantiationService.createInstance(DragElementTool)));84this._toolsStore.add(this.toolsService.registerTool(HoverElementToolData, this.instantiationService.createInstance(HoverElementTool)));85this._toolsStore.add(this.toolsService.registerTool(TypeBrowserToolData, this.instantiationService.createInstance(TypeBrowserTool)));86this._toolsStore.add(this.toolsService.registerTool(RunPlaywrightCodeToolData, this.instantiationService.createInstance(RunPlaywrightCodeTool)));87this._toolsStore.add(this.toolsService.registerTool(HandleDialogBrowserToolData, this.instantiationService.createInstance(HandleDialogBrowserTool)));8889this._toolsStore.add(this._browserToolSet.addTool(OpenBrowserToolData));90this._toolsStore.add(this._browserToolSet.addTool(ReadBrowserToolData));91this._toolsStore.add(this._browserToolSet.addTool(ScreenshotBrowserToolData));92this._toolsStore.add(this._browserToolSet.addTool(NavigateBrowserToolData));93this._toolsStore.add(this._browserToolSet.addTool(ClickBrowserToolData));94this._toolsStore.add(this._browserToolSet.addTool(DragElementToolData));95this._toolsStore.add(this._browserToolSet.addTool(HoverElementToolData));96this._toolsStore.add(this._browserToolSet.addTool(TypeBrowserToolData));97this._toolsStore.add(this._browserToolSet.addTool(RunPlaywrightCodeToolData));98this._toolsStore.add(this._browserToolSet.addTool(HandleDialogBrowserToolData));99100// Subscribe to browser view changes and model sharing state changes101this._syncModelListeners();102this._toolsStore.add(this.browserViewService.onDidChangeBrowserViews(() => {103this._syncModelListeners();104this._updateBrowserContext();105}));106this._toolsStore.add(this.editorService.onDidActiveEditorChange(() => this._updateBrowserContext()));107this._toolsStore.add(this.editorService.onDidVisibleEditorsChange(() => this._updateBrowserContext()));108this._toolsStore.add(this.agentNetworkFilterService.onDidChange(() => this._updateBrowserContext()));109110this._updateBrowserContext();111}112113/**114* Subscribe to sharingState changes on each known model so the workspace115* context updates whenever a page is shared or unshared.116*/117private _syncModelListeners(): void {118const views = this.browserViewService.getKnownBrowserViews();119// Remove listeners for views that no longer exist120for (const id of this._modelListeners.keys()) {121if (!views.has(id)) {122this._modelListeners.deleteAndDispose(id);123}124}125// Add listeners for new views126for (const [id, input] of views) {127if (!this._modelListeners.has(id) && input.model) {128const store = new DisposableStore();129store.add(input.model.onDidChangeSharingState(() => this._updateBrowserContext()));130this._modelListeners.set(id, store);131}132}133}134135private _updateBrowserContext(): void {136const views = [...this.browserViewService.getKnownBrowserViews().values()];137const sharedViews = views.filter(v => v.model?.sharingState === BrowserViewSharingState.Shared);138const unsharedCount = views.length - sharedViews.length;139140if (sharedViews.length === 0 && unsharedCount === 0) {141this.chatContextService.updateWorkspaceContextItems(BrowserChatAgentToolsContribution.CONTEXT_ID, []);142return;143}144145let value = '';146if (sharedViews.length > 0) {147value = 'The following browser pages are currently shared with you and can be interacted with using the browser tools:';148value += '\n' + formatBrowserEditorList(this.editorService, sharedViews, { agentNetworkFilterService: this.agentNetworkFilterService });149} else {150value = 'No browser pages are currently shared with you.';151}152153if (unsharedCount > 0) {154if (value) {155value += '\n\n';156}157value += `${unsharedCount} ${unsharedCount === 1 ? 'page is' : 'pages are'} open but not shared.`;158value += `\nUse the 'open_browser_page' tool to open a new page or to help the user share an existing page.`;159}160161this.chatContextService.updateWorkspaceContextItems(BrowserChatAgentToolsContribution.CONTEXT_ID, [{162handle: 0,163label: localize('browserContext.label', "Browser Pages"),164value: value165}]);166}167}168registerWorkbenchContribution2(BrowserChatAgentToolsContribution.ID, BrowserChatAgentToolsContribution, WorkbenchPhase.AfterRestored);169170171