Path: blob/main/src/vs/workbench/contrib/chat/browser/tools/languageModelToolsConfirmationService.ts
5240 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 { Lazy } from '../../../../../base/common/lazy.js';7import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';8import { LRUCache } from '../../../../../base/common/map.js';9import { ThemeIcon } from '../../../../../base/common/themables.js';10import { localize } from '../../../../../nls.js';11import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';12import { IQuickInputButtonWithToggle, IQuickInputService, IQuickTreeItem, QuickInputButtonLocation } from '../../../../../platform/quickinput/common/quickInput.js';13import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';14import { ConfirmedReason, ToolConfirmKind } from '../../common/chatService/chatService.js';15import { ILanguageModelToolConfirmationActions, ILanguageModelToolConfirmationContribution, ILanguageModelToolConfirmationContributionQuickTreeItem, ILanguageModelToolConfirmationRef, ILanguageModelToolsConfirmationService } from '../../common/tools/languageModelToolsConfirmationService.js';16import { IToolData, ToolDataSource } from '../../common/tools/languageModelToolsService.js';1718const RUN_WITHOUT_APPROVAL = localize('runWithoutApproval', "without approval");19const CONTINUE_WITHOUT_REVIEWING_RESULTS = localize('continueWithoutReviewingResults', "without reviewing result");202122class GenericConfirmStore extends Disposable {23private _workspaceStore: Lazy<ToolConfirmStore>;24private _profileStore: Lazy<ToolConfirmStore>;25private _memoryStore = new Set<string>();2627constructor(28private readonly _storageKey: string,29private readonly _instantiationService: IInstantiationService,30) {31super();32this._workspaceStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.WORKSPACE, this._storageKey)));33this._profileStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.PROFILE, this._storageKey)));34}3536public setAutoConfirmation(id: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void {37// Clear from all scopes first38this._workspaceStore.value.setAutoConfirm(id, false);39this._profileStore.value.setAutoConfirm(id, false);40this._memoryStore.delete(id);4142// Set in the appropriate scope43if (scope === 'workspace') {44this._workspaceStore.value.setAutoConfirm(id, true);45} else if (scope === 'profile') {46this._profileStore.value.setAutoConfirm(id, true);47} else if (scope === 'session') {48this._memoryStore.add(id);49}50}5152public getAutoConfirmation(id: string): 'workspace' | 'profile' | 'session' | 'never' {53if (this._workspaceStore.value.getAutoConfirm(id)) {54return 'workspace';55}56if (this._profileStore.value.getAutoConfirm(id)) {57return 'profile';58}59if (this._memoryStore.has(id)) {60return 'session';61}62return 'never';63}6465public getAutoConfirmationIn(id: string, scope: 'workspace' | 'profile' | 'session'): boolean {66if (scope === 'workspace') {67return this._workspaceStore.value.getAutoConfirm(id);68} else if (scope === 'profile') {69return this._profileStore.value.getAutoConfirm(id);70} else {71return this._memoryStore.has(id);72}73}7475public reset(): void {76this._workspaceStore.value.reset();77this._profileStore.value.reset();78this._memoryStore.clear();79}8081public checkAutoConfirmation(id: string): ConfirmedReason | undefined {82if (this._workspaceStore.value.getAutoConfirm(id)) {83return { type: ToolConfirmKind.LmServicePerTool, scope: 'workspace' };84}85if (this._profileStore.value.getAutoConfirm(id)) {86return { type: ToolConfirmKind.LmServicePerTool, scope: 'profile' };87}88if (this._memoryStore.has(id)) {89return { type: ToolConfirmKind.LmServicePerTool, scope: 'session' };90}91return undefined;92}9394public getAllConfirmed(): Set<string> {95const all = new Set<string>();96for (const key of this._workspaceStore.value.getAll()) {97all.add(key);98}99for (const key of this._profileStore.value.getAll()) {100all.add(key);101}102for (const key of this._memoryStore) {103all.add(key);104}105return all;106}107}108109class ToolConfirmStore extends Disposable {110private _autoConfirmTools: LRUCache<string, boolean> = new LRUCache<string, boolean>(100);111private _didChange = false;112113constructor(114private readonly _scope: StorageScope,115private readonly _storageKey: string,116@IStorageService private readonly storageService: IStorageService,117) {118super();119120const stored = storageService.getObject<string[]>(this._storageKey, this._scope);121if (stored) {122for (const key of stored) {123this._autoConfirmTools.set(key, true);124}125}126127this._register(storageService.onWillSaveState(() => {128if (this._didChange) {129this.storageService.store(this._storageKey, [...this._autoConfirmTools.keys()], this._scope, StorageTarget.MACHINE);130this._didChange = false;131}132}));133}134135public reset() {136this._autoConfirmTools.clear();137this._didChange = true;138}139140public getAutoConfirm(id: string): boolean {141if (this._autoConfirmTools.get(id)) {142this._didChange = true;143return true;144}145146return false;147}148149public setAutoConfirm(id: string, autoConfirm: boolean): void {150if (autoConfirm) {151this._autoConfirmTools.set(id, true);152} else {153this._autoConfirmTools.delete(id);154}155this._didChange = true;156}157158public getAll(): string[] {159return [...this._autoConfirmTools.keys()];160}161}162163export class LanguageModelToolsConfirmationService extends Disposable implements ILanguageModelToolsConfirmationService {164declare readonly _serviceBrand: undefined;165166private _preExecutionToolConfirmStore: GenericConfirmStore;167private _postExecutionToolConfirmStore: GenericConfirmStore;168private _preExecutionServerConfirmStore: GenericConfirmStore;169private _postExecutionServerConfirmStore: GenericConfirmStore;170171private _contributions = new Map<string, ILanguageModelToolConfirmationContribution>();172173constructor(174@IInstantiationService private readonly _instantiationService: IInstantiationService,175@IQuickInputService private readonly _quickInputService: IQuickInputService,176) {177super();178179this._preExecutionToolConfirmStore = this._register(new GenericConfirmStore('chat/autoconfirm', this._instantiationService));180this._postExecutionToolConfirmStore = this._register(new GenericConfirmStore('chat/autoconfirm-post', this._instantiationService));181this._preExecutionServerConfirmStore = this._register(new GenericConfirmStore('chat/servers/autoconfirm', this._instantiationService));182this._postExecutionServerConfirmStore = this._register(new GenericConfirmStore('chat/servers/autoconfirm-post', this._instantiationService));183}184185getPreConfirmAction(ref: ILanguageModelToolConfirmationRef): ConfirmedReason | undefined {186// Check contribution first187const contribution = this._contributions.get(ref.toolId);188if (contribution?.getPreConfirmAction) {189const result = contribution.getPreConfirmAction(ref);190if (result) {191return result;192}193}194195// If contribution disables default approvals, don't check default stores196if (contribution && contribution.canUseDefaultApprovals === false) {197return undefined;198}199200// Check tool-level confirmation201const toolResult = this._preExecutionToolConfirmStore.checkAutoConfirmation(ref.toolId);202if (toolResult) {203return toolResult;204}205206// Check server-level confirmation for MCP tools207if (ref.source.type === 'mcp') {208const serverResult = this._preExecutionServerConfirmStore.checkAutoConfirmation(ref.source.definitionId);209if (serverResult) {210return serverResult;211}212}213214return undefined;215}216217getPostConfirmAction(ref: ILanguageModelToolConfirmationRef): ConfirmedReason | undefined {218// Check contribution first219const contribution = this._contributions.get(ref.toolId);220if (contribution?.getPostConfirmAction) {221const result = contribution.getPostConfirmAction(ref);222if (result) {223return result;224}225}226227// If contribution disables default approvals, don't check default stores228if (contribution && contribution.canUseDefaultApprovals === false) {229return undefined;230}231232// Check tool-level confirmation233const toolResult = this._postExecutionToolConfirmStore.checkAutoConfirmation(ref.toolId);234if (toolResult) {235return toolResult;236}237238// Check server-level confirmation for MCP tools239if (ref.source.type === 'mcp') {240const serverResult = this._postExecutionServerConfirmStore.checkAutoConfirmation(ref.source.definitionId);241if (serverResult) {242return serverResult;243}244}245246return undefined;247}248249getPreConfirmActions(ref: ILanguageModelToolConfirmationRef): ILanguageModelToolConfirmationActions[] {250const actions: ILanguageModelToolConfirmationActions[] = [];251252// Add contribution actions first253const contribution = this._contributions.get(ref.toolId);254if (contribution?.getPreConfirmActions) {255actions.push(...contribution.getPreConfirmActions(ref));256}257258// If contribution disables default approvals, only return contribution actions259if (contribution && contribution.canUseDefaultApprovals === false) {260return actions;261}262263// Add default tool-level actions264actions.push(265{266label: localize('allowSession', 'Allow in this Session'),267detail: localize('allowSessionTooltip', 'Allow this tool to run in this session without confirmation.'),268divider: !!actions.length,269select: async () => {270this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'session');271return true;272}273},274{275label: localize('allowWorkspace', 'Allow in this Workspace'),276detail: localize('allowWorkspaceTooltip', 'Allow this tool to run in this workspace without confirmation.'),277select: async () => {278this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'workspace');279return true;280}281},282{283label: localize('allowGlobally', 'Always Allow'),284detail: localize('allowGloballyTooltip', 'Always allow this tool to run without confirmation.'),285select: async () => {286this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'profile');287return true;288}289}290);291292// Add server-level actions for MCP tools293if (ref.source.type === 'mcp') {294const { serverLabel, definitionId } = ref.source;295actions.push(296{297label: localize('allowServerSession', 'Allow Tools from {0} in this Session', serverLabel),298detail: localize('allowServerSessionTooltip', 'Allow all tools from this server to run in this session without confirmation.'),299divider: true,300select: async () => {301this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'session');302return true;303}304},305{306label: localize('allowServerWorkspace', 'Allow Tools from {0} in this Workspace', serverLabel),307detail: localize('allowServerWorkspaceTooltip', 'Allow all tools from this server to run in this workspace without confirmation.'),308select: async () => {309this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'workspace');310return true;311}312},313{314label: localize('allowServerGlobally', 'Always Allow Tools from {0}', serverLabel),315detail: localize('allowServerGloballyTooltip', 'Always allow all tools from this server to run without confirmation.'),316select: async () => {317this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'profile');318return true;319}320}321);322}323324return actions;325}326327getPostConfirmActions(ref: ILanguageModelToolConfirmationRef): ILanguageModelToolConfirmationActions[] {328const actions: ILanguageModelToolConfirmationActions[] = [];329330// Add contribution actions first331const contribution = this._contributions.get(ref.toolId);332if (contribution?.getPostConfirmActions) {333actions.push(...contribution.getPostConfirmActions(ref));334}335336// If contribution disables default approvals, only return contribution actions337if (contribution && contribution.canUseDefaultApprovals === false) {338return actions;339}340341// Add default tool-level actions342actions.push(343{344label: localize('allowSessionPost', 'Allow Without Review in this Session'),345detail: localize('allowSessionPostTooltip', 'Allow results from this tool to be sent without confirmation in this session.'),346divider: !!actions.length,347select: async () => {348this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'session');349return true;350}351},352{353label: localize('allowWorkspacePost', 'Allow Without Review in this Workspace'),354detail: localize('allowWorkspacePostTooltip', 'Allow results from this tool to be sent without confirmation in this workspace.'),355select: async () => {356this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'workspace');357return true;358}359},360{361label: localize('allowGloballyPost', 'Always Allow Without Review'),362detail: localize('allowGloballyPostTooltip', 'Always allow results from this tool to be sent without confirmation.'),363select: async () => {364this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'profile');365return true;366}367}368);369370// Add server-level actions for MCP tools371if (ref.source.type === 'mcp') {372const { serverLabel, definitionId } = ref.source;373actions.push(374{375label: localize('allowServerSessionPost', 'Allow Tools from {0} Without Review in this Session', serverLabel),376detail: localize('allowServerSessionPostTooltip', 'Allow results from all tools from this server to be sent without confirmation in this session.'),377divider: true,378select: async () => {379this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'session');380return true;381}382},383{384label: localize('allowServerWorkspacePost', 'Allow Tools from {0} Without Review in this Workspace', serverLabel),385detail: localize('allowServerWorkspacePostTooltip', 'Allow results from all tools from this server to be sent without confirmation in this workspace.'),386select: async () => {387this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'workspace');388return true;389}390},391{392label: localize('allowServerGloballyPost', 'Always Allow Tools from {0} Without Review', serverLabel),393detail: localize('allowServerGloballyPostTooltip', 'Always allow results from all tools from this server to be sent without confirmation.'),394select: async () => {395this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'profile');396return true;397}398}399);400}401402return actions;403}404405registerConfirmationContribution(toolName: string, contribution: ILanguageModelToolConfirmationContribution): IDisposable {406this._contributions.set(toolName, contribution);407return {408dispose: () => {409this._contributions.delete(toolName);410}411};412}413414manageConfirmationPreferences(tools: readonly IToolData[], options?: { defaultScope?: 'workspace' | 'profile' | 'session' }): void {415interface IToolTreeItem extends IQuickTreeItem {416type: 'tool' | 'server' | 'tool-pre' | 'tool-post' | 'server-pre' | 'server-post' | 'manage';417toolId?: string;418serverId?: string;419scope?: 'workspace' | 'profile';420}421422// Helper to track tools under servers423const trackServerTool = (serverId: string, label: string, toolId: string, serversWithTools: Map<string, { label: string; tools: Set<string> }>) => {424if (!serversWithTools.has(serverId)) {425serversWithTools.set(serverId, { label, tools: new Set() });426}427serversWithTools.get(serverId)!.tools.add(toolId);428};429430// Helper to add server tool from source431const addServerToolFromSource = (source: ToolDataSource, toolId: string, serversWithTools: Map<string, { label: string; tools: Set<string> }>) => {432if (source.type === 'mcp') {433trackServerTool(source.definitionId, source.serverLabel || source.label, toolId, serversWithTools);434} else if (source.type === 'extension') {435trackServerTool(source.extensionId.value, source.label, toolId, serversWithTools);436}437};438439// Determine which tools should be shown440const relevantTools = new Set<string>();441const serversWithTools = new Map<string, { label: string; tools: Set<string> }>();442443// Add tools that request approval444for (const tool of tools) {445if (tool.canRequestPreApproval || tool.canRequestPostApproval || this._contributions.has(tool.id)) {446relevantTools.add(tool.id);447addServerToolFromSource(tool.source, tool.id, serversWithTools);448}449}450451// Add tools that have stored approvals (but we can't display them without metadata)452for (const id of this._preExecutionToolConfirmStore.getAllConfirmed()) {453if (!relevantTools.has(id)) {454// Only add if we have the tool data455const tool = tools.find(t => t.id === id);456if (tool) {457relevantTools.add(id);458addServerToolFromSource(tool.source, id, serversWithTools);459}460}461}462for (const id of this._postExecutionToolConfirmStore.getAllConfirmed()) {463if (!relevantTools.has(id)) {464// Only add if we have the tool data465const tool = tools.find(t => t.id === id);466if (tool) {467relevantTools.add(id);468addServerToolFromSource(tool.source, id, serversWithTools);469}470}471}472473if (relevantTools.size === 0) {474return; // Nothing to show475}476477// Determine initial scope from options478let currentScope = options?.defaultScope ?? 'workspace';479480// Helper function to build tree items based on current scope481const buildTreeItems = (): IToolTreeItem[] => {482const treeItems: IToolTreeItem[] = [];483484// Add server nodes485for (const [serverId, serverInfo] of serversWithTools) {486const serverChildren: IToolTreeItem[] = [];487488// Add server-level controls as first children489const hasAnyPre = Array.from(serverInfo.tools).some(toolId => {490const tool = tools.find(t => t.id === toolId);491return tool?.canRequestPreApproval;492});493const hasAnyPost = Array.from(serverInfo.tools).some(toolId => {494const tool = tools.find(t => t.id === toolId);495return tool?.canRequestPostApproval;496});497498const serverPreConfirmed = this._preExecutionServerConfirmStore.getAutoConfirmationIn(serverId, currentScope);499const serverPostConfirmed = this._postExecutionServerConfirmStore.getAutoConfirmationIn(serverId, currentScope);500501// Add individual tools from this server as children502for (const toolId of serverInfo.tools) {503const tool = tools.find(t => t.id === toolId);504if (!tool) {505continue;506}507508const toolChildren: IToolTreeItem[] = [];509const hasPre = !serverPreConfirmed && (tool.canRequestPreApproval || this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope));510const hasPost = !serverPostConfirmed && (tool.canRequestPostApproval || this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope));511512// Add child items for granular control when both approval types exist513if (hasPre && hasPost) {514toolChildren.push({515type: 'tool-pre',516toolId: tool.id,517label: RUN_WITHOUT_APPROVAL,518checked: this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope)519});520toolChildren.push({521type: 'tool-post',522toolId: tool.id,523label: CONTINUE_WITHOUT_REVIEWING_RESULTS,524checked: this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope)525});526}527528// Tool item always has a checkbox529const preApproval = this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);530const postApproval = this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);531let checked: boolean | 'mixed';532let description: string | undefined;533534if (hasPre && hasPost) {535// Both: checkbox is mixed if only one is enabled536checked = preApproval && postApproval ? true : (!preApproval && !postApproval ? false : 'mixed');537} else if (hasPre) {538checked = preApproval;539description = RUN_WITHOUT_APPROVAL;540} else if (hasPost) {541checked = postApproval;542description = CONTINUE_WITHOUT_REVIEWING_RESULTS;543} else {544continue;545}546547serverChildren.push({548type: 'tool',549toolId: tool.id,550label: tool.displayName || tool.id,551description,552checked,553collapsed: true,554children: toolChildren.length > 0 ? toolChildren : undefined555});556}557558serverChildren.sort((a, b) => a.label.localeCompare(b.label));559560if (hasAnyPost) {561serverChildren.unshift({562type: 'server-post',563serverId,564iconClass: ThemeIcon.asClassName(Codicon.play),565label: localize('continueWithoutReviewing', "Continue without reviewing any tool results"),566checked: serverPostConfirmed567});568}569if (hasAnyPre) {570serverChildren.unshift({571type: 'server-pre',572serverId,573iconClass: ThemeIcon.asClassName(Codicon.play),574label: localize('runToolsWithoutApproval', "Run any tool without approval"),575checked: serverPreConfirmed576});577}578579// Server node has checkbox to control both pre and post580const serverHasPre = this._preExecutionServerConfirmStore.getAutoConfirmationIn(serverId, currentScope);581const serverHasPost = this._postExecutionServerConfirmStore.getAutoConfirmationIn(serverId, currentScope);582let serverChecked: boolean | 'mixed';583if (hasAnyPre && hasAnyPost) {584serverChecked = serverHasPre && serverHasPost ? true : (!serverHasPre && !serverHasPost ? false : 'mixed');585} else if (hasAnyPre) {586serverChecked = serverHasPre;587} else if (hasAnyPost) {588serverChecked = serverHasPost;589} else {590serverChecked = false;591}592593const existingItem = quickTree.itemTree.find(i => i.serverId === serverId);594treeItems.push({595type: 'server',596serverId,597label: serverInfo.label,598checked: serverChecked,599children: serverChildren,600collapsed: existingItem ? quickTree.isCollapsed(existingItem) : true,601pickable: false602});603}604605// Add individual tool nodes (only for non-MCP/extension tools)606const sortedTools = tools.slice().sort((a, b) => a.displayName.localeCompare(b.displayName));607for (const tool of sortedTools) {608if (!relevantTools.has(tool.id)) {609continue;610}611612// Skip tools that belong to MCP/extension servers (they're shown under server nodes)613if (tool.source.type === 'mcp' || tool.source.type === 'extension') {614continue;615}616617const contributed = this._contributions.get(tool.id);618const toolChildren: IToolTreeItem[] = [];619620const manageActions = contributed?.getManageActions?.();621if (manageActions) {622toolChildren.push(...manageActions.map(action => ({623type: 'manage' as const,624...action,625})));626}627628629let checked: boolean | 'mixed' = false;630let description: string | undefined;631let pickable = false;632633if (contributed?.canUseDefaultApprovals !== false) {634pickable = true;635const hasPre = tool.canRequestPreApproval || this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);636const hasPost = tool.canRequestPostApproval || this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);637638// Add child items for granular control when both approval types exist639if (hasPre && hasPost) {640toolChildren.push({641type: 'tool-pre',642toolId: tool.id,643label: RUN_WITHOUT_APPROVAL,644checked: this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope)645});646toolChildren.push({647type: 'tool-post',648toolId: tool.id,649label: CONTINUE_WITHOUT_REVIEWING_RESULTS,650checked: this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope)651});652}653654// Tool item always has a checkbox655const preApproval = this._preExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);656const postApproval = this._postExecutionToolConfirmStore.getAutoConfirmationIn(tool.id, currentScope);657658if (hasPre && hasPost) {659// Both: checkbox is mixed if only one is enabled660checked = preApproval && postApproval ? true : (!preApproval && !postApproval ? false : 'mixed');661} else if (hasPre) {662checked = preApproval;663description = RUN_WITHOUT_APPROVAL;664} else if (hasPost) {665checked = postApproval;666description = CONTINUE_WITHOUT_REVIEWING_RESULTS;667} else {668// No approval capabilities - shouldn't happen but handle it669checked = false;670}671}672673treeItems.push({674type: 'tool',675toolId: tool.id,676label: tool.displayName || tool.id,677description,678checked,679pickable,680collapsed: true,681children: toolChildren.length > 0 ? toolChildren : undefined682});683}684685return treeItems;686};687688const disposables = new DisposableStore();689const quickTree = disposables.add(this._quickInputService.createQuickTree<IToolTreeItem>());690quickTree.ignoreFocusOut = true;691quickTree.sortByLabel = false;692693// Only show toggle if not in session scope694if (currentScope !== 'session') {695const scopeButton: IQuickInputButtonWithToggle = {696iconClass: ThemeIcon.asClassName(Codicon.folder),697tooltip: localize('workspaceScope', "Configure for this workspace only"),698toggle: { checked: currentScope === 'workspace' },699location: QuickInputButtonLocation.Input700};701quickTree.buttons = [scopeButton];702disposables.add(quickTree.onDidTriggerButton(button => {703if (button === scopeButton) {704currentScope = currentScope === 'workspace' ? 'profile' : 'workspace';705updatePlaceholder();706quickTree.setItemTree(buildTreeItems());707}708}));709}710711const updatePlaceholder = () => {712if (currentScope === 'session') {713quickTree.placeholder = localize('configureSessionToolApprovals', "Configure session tool approvals");714} else {715quickTree.placeholder = currentScope === 'workspace'716? localize('configureWorkspaceToolApprovals', "Configure workspace tool approvals")717: localize('configureGlobalToolApprovals', "Configure global tool approvals");718}719};720updatePlaceholder();721722quickTree.setItemTree(buildTreeItems());723724disposables.add(quickTree.onDidChangeCheckboxState(item => {725const newState = item.checked ? currentScope : 'never';726727if (item.type === 'server' && item.serverId) {728// Server-level checkbox: update both pre and post based on server capabilities729const serverInfo = serversWithTools.get(item.serverId);730if (serverInfo) {731this._preExecutionServerConfirmStore.setAutoConfirmation(item.serverId, newState);732this._postExecutionServerConfirmStore.setAutoConfirmation(item.serverId, newState);733}734} else if (item.type === 'tool' && item.toolId) {735const tool = tools.find(t => t.id === item.toolId);736if (tool?.canRequestPostApproval || newState === 'never') {737this._postExecutionToolConfirmStore.setAutoConfirmation(item.toolId, newState);738}739if (tool?.canRequestPreApproval || newState === 'never') {740this._preExecutionToolConfirmStore.setAutoConfirmation(item.toolId, newState);741}742} else if (item.type === 'tool-pre' && item.toolId) {743this._preExecutionToolConfirmStore.setAutoConfirmation(item.toolId, newState);744} else if (item.type === 'tool-post' && item.toolId) {745this._postExecutionToolConfirmStore.setAutoConfirmation(item.toolId, newState);746} else if (item.type === 'server-pre' && item.serverId) {747this._preExecutionServerConfirmStore.setAutoConfirmation(item.serverId, newState);748quickTree.setItemTree(buildTreeItems());749} else if (item.type === 'server-post' && item.serverId) {750this._postExecutionServerConfirmStore.setAutoConfirmation(item.serverId, newState);751quickTree.setItemTree(buildTreeItems());752} else if (item.type === 'manage') {753(item as ILanguageModelToolConfirmationContributionQuickTreeItem).onDidChangeChecked?.(!!item.checked);754}755}));756757disposables.add(quickTree.onDidTriggerItemButton(i => {758if (i.item.type === 'manage') {759(i.item as ILanguageModelToolConfirmationContributionQuickTreeItem).onDidTriggerItemButton?.(i.button);760}761}));762763disposables.add(quickTree.onDidAccept(() => {764for (const item of quickTree.activeItems) {765if (item.type === 'manage') {766(item as ILanguageModelToolConfirmationContributionQuickTreeItem).onDidOpen?.();767quickTree.hide();768}769}770}));771772disposables.add(quickTree.onDidHide(() => {773disposables.dispose();774}));775776quickTree.show();777}778779public resetToolAutoConfirmation(): void {780this._preExecutionToolConfirmStore.reset();781this._postExecutionToolConfirmStore.reset();782this._preExecutionServerConfirmStore.reset();783this._postExecutionServerConfirmStore.reset();784785// Reset all contributions786for (const contribution of this._contributions.values()) {787contribution.reset?.();788}789}790}791792793