Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/common/claudeToolPermissionService.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as l10n from '@vscode/l10n';
7
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
8
import { IInstantiationService, createDecorator } from '../../../../util/vs/platform/instantiation/common/instantiation';
9
import { LanguageModelTextPart } from '../../../../vscodeTypes';
10
import { ToolName } from '../../../tools/common/toolNames';
11
import { IToolsService } from '../../../tools/common/toolsService';
12
import {
13
ClaudeToolPermissionContext,
14
ClaudeToolPermissionResult,
15
IClaudeToolConfirmationParams,
16
IClaudeToolPermissionHandler
17
} from './claudeToolPermission';
18
import { getToolPermissionHandlerRegistry } from './claudeToolPermissionRegistry';
19
import { ClaudeToolInputMap, ClaudeToolNames } from './claudeTools';
20
21
export const IClaudeToolPermissionService = createDecorator<IClaudeToolPermissionService>('claudeToolPermissionService');
22
23
export interface IClaudeToolPermissionService {
24
readonly _serviceBrand: undefined;
25
26
/**
27
* Check if a tool can be used, showing confirmation if needed.
28
* @param toolName The name of the Claude tool
29
* @param input The tool input parameters
30
* @param context Context including the tool invocation token
31
* @returns Permission result (allow with updated input, or deny with message)
32
*/
33
canUseTool(
34
toolName: string,
35
input: Record<string, unknown>,
36
context: ClaudeToolPermissionContext
37
): Promise<ClaudeToolPermissionResult>;
38
}
39
40
/**
41
* Default deny message when user declines a tool
42
*/
43
const DenyToolMessage = 'The user declined to run the tool';
44
45
export class ClaudeToolPermissionService implements IClaudeToolPermissionService {
46
declare readonly _serviceBrand: undefined;
47
48
private readonly _handlerCache = new Map<ClaudeToolNames, IClaudeToolPermissionHandler>();
49
50
constructor(
51
@IInstantiationService private readonly instantiationService: IInstantiationService,
52
@IToolsService private readonly toolsService: IToolsService,
53
) { }
54
55
public async canUseTool(
56
toolName: string,
57
input: Record<string, unknown>,
58
context: ClaudeToolPermissionContext
59
): Promise<ClaudeToolPermissionResult> {
60
if (context.permissionMode === 'bypassPermissions') {
61
// Bypass mode: allow all tools without confirmation
62
return { behavior: 'allow', updatedInput: input };
63
}
64
65
const handler = this._getHandler(toolName as ClaudeToolNames);
66
67
// If handler has full custom implementation, use it
68
if (handler?.handle) {
69
return handler.handle(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames], context);
70
}
71
72
// Check auto-approve (handler-specific or permission mode based)
73
if (handler?.canAutoApprove) {
74
const canAutoApprove = await handler.canAutoApprove(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames], context);
75
if (canAutoApprove) {
76
return { behavior: 'allow', updatedInput: input };
77
}
78
}
79
80
// Get confirmation params (custom or default)
81
const confirmationParams = handler?.getConfirmationParams
82
? handler.getConfirmationParams(toolName as ClaudeToolNames, input as ClaudeToolInputMap[ClaudeToolNames])
83
: this._getDefaultConfirmationParams(toolName, input);
84
85
// Show confirmation dialog
86
return this._showConfirmation(confirmationParams, input, context);
87
}
88
89
private _getHandler(toolName: ClaudeToolNames): IClaudeToolPermissionHandler | undefined {
90
// Check cache first
91
if (this._handlerCache.has(toolName)) {
92
return this._handlerCache.get(toolName);
93
}
94
95
// Find registration for this tool
96
const registration = getToolPermissionHandlerRegistry().find(r => r.toolNames.includes(toolName));
97
if (!registration) {
98
return undefined;
99
}
100
101
// Create handler instance
102
const handler = this.instantiationService.createInstance(registration.ctor);
103
// Cache for all tool names this handler supports
104
for (const name of registration.toolNames) {
105
this._handlerCache.set(name, handler);
106
}
107
108
return handler;
109
}
110
111
private _getDefaultConfirmationParams(toolName: string, input: Record<string, unknown>): IClaudeToolConfirmationParams {
112
return {
113
title: l10n.t('Use {0}?', toolName),
114
message: `\`\`\`\n${JSON.stringify(input, null, 2)}\n\`\`\``
115
};
116
}
117
118
private async _showConfirmation(
119
params: IClaudeToolConfirmationParams,
120
input: Record<string, unknown>,
121
context: ClaudeToolPermissionContext
122
): Promise<ClaudeToolPermissionResult> {
123
try {
124
const result = await this.toolsService.invokeTool(ToolName.CoreConfirmationTool, {
125
input: params,
126
toolInvocationToken: context.toolInvocationToken,
127
}, CancellationToken.None);
128
129
const firstResultPart = result.content.at(0);
130
if (firstResultPart instanceof LanguageModelTextPart && firstResultPart.value === 'yes') {
131
return {
132
behavior: 'allow',
133
updatedInput: input
134
};
135
}
136
} catch { }
137
138
return {
139
behavior: 'deny',
140
message: DenyToolMessage
141
};
142
}
143
}
144
145