Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/common/claudeToolPermissionService.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 * as l10n from '@vscode/l10n';6import { CancellationToken } from '../../../../util/vs/base/common/cancellation';7import { IInstantiationService, createDecorator } from '../../../../util/vs/platform/instantiation/common/instantiation';8import { LanguageModelTextPart } from '../../../../vscodeTypes';9import { ToolName } from '../../../tools/common/toolNames';10import { IToolsService } from '../../../tools/common/toolsService';11import {12ClaudeToolPermissionContext,13ClaudeToolPermissionResult,14IClaudeToolConfirmationParams,15IClaudeToolPermissionHandler16} from './claudeToolPermission';17import { getToolPermissionHandlerRegistry } from './claudeToolPermissionRegistry';18import { ClaudeToolInputMap, ClaudeToolNames } from './claudeTools';1920export const IClaudeToolPermissionService = createDecorator<IClaudeToolPermissionService>('claudeToolPermissionService');2122export interface IClaudeToolPermissionService {23readonly _serviceBrand: undefined;2425/**26* Check if a tool can be used, showing confirmation if needed.27* @param toolName The name of the Claude tool28* @param input The tool input parameters29* @param context Context including the tool invocation token30* @returns Permission result (allow with updated input, or deny with message)31*/32canUseTool(33toolName: string,34input: Record<string, unknown>,35context: ClaudeToolPermissionContext36): Promise<ClaudeToolPermissionResult>;37}3839/**40* Default deny message when user declines a tool41*/42const DenyToolMessage = 'The user declined to run the tool';4344export class ClaudeToolPermissionService implements IClaudeToolPermissionService {45declare readonly _serviceBrand: undefined;4647private readonly _handlerCache = new Map<ClaudeToolNames, IClaudeToolPermissionHandler>();4849constructor(50@IInstantiationService private readonly instantiationService: IInstantiationService,51@IToolsService private readonly toolsService: IToolsService,52) { }5354public async canUseTool(55toolName: string,56input: Record<string, unknown>,57context: ClaudeToolPermissionContext58): Promise<ClaudeToolPermissionResult> {59if (context.permissionMode === 'bypassPermissions') {60// Bypass mode: allow all tools without confirmation61return { behavior: 'allow', updatedInput: input };62}6364const handler = this._getHandler(toolName as ClaudeToolNames);6566// If handler has full custom implementation, use it67if (handler?.handle) {68return handler.handle(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames], context);69}7071// Check auto-approve (handler-specific or permission mode based)72if (handler?.canAutoApprove) {73const canAutoApprove = await handler.canAutoApprove(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames], context);74if (canAutoApprove) {75return { behavior: 'allow', updatedInput: input };76}77}7879// Get confirmation params (custom or default)80const confirmationParams = handler?.getConfirmationParams81? handler.getConfirmationParams(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames])82: this._getDefaultConfirmationParams(toolName, input);8384// Show confirmation dialog85return this._showConfirmation(confirmationParams, input, context);86}8788private _getHandler(toolName: ClaudeToolNames): IClaudeToolPermissionHandler | undefined {89// Check cache first90if (this._handlerCache.has(toolName)) {91return this._handlerCache.get(toolName);92}9394// Find registration for this tool95const registration = getToolPermissionHandlerRegistry().find(r => r.toolNames.includes(toolName));96if (!registration) {97return undefined;98}99100// Create handler instance101const handler = this.instantiationService.createInstance(registration.ctor);102// Cache for all tool names this handler supports103for (const name of registration.toolNames) {104this._handlerCache.set(name, handler);105}106107return handler;108}109110private _getDefaultConfirmationParams(toolName: string, input: Record<string, unknown>): IClaudeToolConfirmationParams {111return {112title: l10n.t('Use {0}?', toolName),113message: `\`\`\`\n${JSON.stringify(input, null, 2)}\n\`\`\``114};115}116117private async _showConfirmation(118params: IClaudeToolConfirmationParams,119input: Record<string, unknown>,120context: ClaudeToolPermissionContext121): Promise<ClaudeToolPermissionResult> {122try {123const result = await this.toolsService.invokeTool(ToolName.CoreConfirmationTool, {124input: params,125toolInvocationToken: context.toolInvocationToken,126}, CancellationToken.None);127128const firstResultPart = result.content.at(0);129if (firstResultPart instanceof LanguageModelTextPart && firstResultPart.value === 'yes') {130return {131behavior: 'allow',132updatedInput: input133};134}135} catch { }136137return {138behavior: 'deny',139message: DenyToolMessage140};141}142}143144145