Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/vscode-node/claudeSlashCommandService.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 vscode from 'vscode';
7
import { ILogService } from '../../../../platform/log/common/logService';
8
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
9
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
10
import { createDecorator, IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
11
import { getClaudeSlashCommandRegistry, IClaudeSlashCommandHandler } from './slashCommands/claudeSlashCommandRegistry';
12
13
export interface IClaudeSlashCommandRequest {
14
readonly prompt: string;
15
readonly command: string | undefined;
16
}
17
18
// Import all slash command handlers to trigger self-registration
19
import './slashCommands/index';
20
21
export interface IClaudeSlashCommandResult {
22
handled: boolean;
23
result?: vscode.ChatResult;
24
}
25
26
export interface IClaudeSlashCommandService {
27
readonly _serviceBrand: undefined;
28
29
/**
30
* Try to handle a slash command from the user's request.
31
*
32
* Checks `request.command` first (VS Code slash command), then falls back to
33
* parsing a `/command` pattern from `request.prompt`.
34
*
35
* @param request - The user's request containing prompt and optional command
36
* @param stream - Response stream for sending messages to the chat
37
* @param token - Cancellation token
38
* @returns Object indicating whether the command was handled and the result
39
*/
40
tryHandleCommand(
41
request: IClaudeSlashCommandRequest,
42
stream: vscode.ChatResponseStream,
43
token: CancellationToken
44
): Promise<IClaudeSlashCommandResult>;
45
46
/**
47
* Get all registered command names.
48
*/
49
getRegisteredCommands(): readonly string[];
50
}
51
52
export const IClaudeSlashCommandService = createDecorator<IClaudeSlashCommandService>('claudeSlashCommandService');
53
54
export class ClaudeSlashCommandService extends Disposable implements IClaudeSlashCommandService {
55
readonly _serviceBrand: undefined;
56
57
private _handlerCache = new Map<string, IClaudeSlashCommandHandler>();
58
private _initialized = false;
59
60
constructor(
61
@IInstantiationService private readonly instantiationService: IInstantiationService,
62
@ILogService private readonly logService: ILogService,
63
) {
64
super();
65
// Initialize eagerly to register VS Code commands at startup
66
this._ensureInitialized();
67
}
68
69
async tryHandleCommand(
70
request: IClaudeSlashCommandRequest,
71
stream: vscode.ChatResponseStream,
72
token: CancellationToken
73
): Promise<IClaudeSlashCommandResult> {
74
// 1. Check request.command (VS Code slash command selected via UI)
75
if (request.command) {
76
const handler = this._getHandler(request.command.toLowerCase());
77
if (handler) {
78
const result = await handler.handle(request.prompt, stream, token);
79
return { handled: true, result: result ?? {} };
80
}
81
}
82
83
// 2. Fall back to parsing /command from the prompt text
84
const match = request.prompt.trim().match(/^\/(\w+)(?:\s+(.*))?$/);
85
if (!match) {
86
return { handled: false };
87
}
88
89
const [, commandName, args] = match;
90
const handler = this._getHandler(commandName.toLowerCase());
91
if (!handler) {
92
return { handled: false };
93
}
94
95
const result = await handler.handle(args ?? '', stream, token);
96
return { handled: true, result: result ?? {} };
97
}
98
99
getRegisteredCommands(): readonly string[] {
100
this._ensureInitialized();
101
return Array.from(this._handlerCache.keys());
102
}
103
104
private _getHandler(commandName: string): IClaudeSlashCommandHandler | undefined {
105
this._ensureInitialized();
106
return this._handlerCache.get(commandName);
107
}
108
109
private _ensureInitialized(): void {
110
if (this._initialized) {
111
return;
112
}
113
114
// Instantiate all registered handlers and cache them by command name
115
const ctors = getClaudeSlashCommandRegistry();
116
for (const ctor of ctors) {
117
const handler = this.instantiationService.createInstance(ctor);
118
const commandKey = handler.commandName.toLowerCase();
119
// This shouldn't happen unless we accidentally register duplicates
120
if (this._handlerCache.has(commandKey)) {
121
this.logService.warn(`Duplicate Claude slash command name "${handler.commandName}" detected. Ignoring handler ${ctor.name || 'unknown constructor'}.`);
122
continue;
123
}
124
this._handlerCache.set(commandKey, handler);
125
126
// Register VS Code command if commandId is provided
127
if (handler.commandId) {
128
this._register(vscode.commands.registerCommand(handler.commandId, () => {
129
// Invoke with no args and no stream (Command Palette mode)
130
return handler.handle('', undefined, CancellationToken.None);
131
}));
132
}
133
}
134
135
this._initialized = true;
136
}
137
}
138
139