Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/vscode-node/claudeSlashCommandService.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 vscode from 'vscode';6import { ILogService } from '../../../../platform/log/common/logService';7import { CancellationToken } from '../../../../util/vs/base/common/cancellation';8import { Disposable } from '../../../../util/vs/base/common/lifecycle';9import { createDecorator, IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';10import { getClaudeSlashCommandRegistry, IClaudeSlashCommandHandler } from './slashCommands/claudeSlashCommandRegistry';1112export interface IClaudeSlashCommandRequest {13readonly prompt: string;14readonly command: string | undefined;15}1617// Import all slash command handlers to trigger self-registration18import './slashCommands/index';1920export interface IClaudeSlashCommandResult {21handled: boolean;22result?: vscode.ChatResult;23}2425export interface IClaudeSlashCommandService {26readonly _serviceBrand: undefined;2728/**29* Try to handle a slash command from the user's request.30*31* Checks `request.command` first (VS Code slash command), then falls back to32* parsing a `/command` pattern from `request.prompt`.33*34* @param request - The user's request containing prompt and optional command35* @param stream - Response stream for sending messages to the chat36* @param token - Cancellation token37* @returns Object indicating whether the command was handled and the result38*/39tryHandleCommand(40request: IClaudeSlashCommandRequest,41stream: vscode.ChatResponseStream,42token: CancellationToken43): Promise<IClaudeSlashCommandResult>;4445/**46* Get all registered command names.47*/48getRegisteredCommands(): readonly string[];49}5051export const IClaudeSlashCommandService = createDecorator<IClaudeSlashCommandService>('claudeSlashCommandService');5253export class ClaudeSlashCommandService extends Disposable implements IClaudeSlashCommandService {54readonly _serviceBrand: undefined;5556private _handlerCache = new Map<string, IClaudeSlashCommandHandler>();57private _initialized = false;5859constructor(60@IInstantiationService private readonly instantiationService: IInstantiationService,61@ILogService private readonly logService: ILogService,62) {63super();64// Initialize eagerly to register VS Code commands at startup65this._ensureInitialized();66}6768async tryHandleCommand(69request: IClaudeSlashCommandRequest,70stream: vscode.ChatResponseStream,71token: CancellationToken72): Promise<IClaudeSlashCommandResult> {73// 1. Check request.command (VS Code slash command selected via UI)74if (request.command) {75const handler = this._getHandler(request.command.toLowerCase());76if (handler) {77const result = await handler.handle(request.prompt, stream, token);78return { handled: true, result: result ?? {} };79}80}8182// 2. Fall back to parsing /command from the prompt text83const match = request.prompt.trim().match(/^\/(\w+)(?:\s+(.*))?$/);84if (!match) {85return { handled: false };86}8788const [, commandName, args] = match;89const handler = this._getHandler(commandName.toLowerCase());90if (!handler) {91return { handled: false };92}9394const result = await handler.handle(args ?? '', stream, token);95return { handled: true, result: result ?? {} };96}9798getRegisteredCommands(): readonly string[] {99this._ensureInitialized();100return Array.from(this._handlerCache.keys());101}102103private _getHandler(commandName: string): IClaudeSlashCommandHandler | undefined {104this._ensureInitialized();105return this._handlerCache.get(commandName);106}107108private _ensureInitialized(): void {109if (this._initialized) {110return;111}112113// Instantiate all registered handlers and cache them by command name114const ctors = getClaudeSlashCommandRegistry();115for (const ctor of ctors) {116const handler = this.instantiationService.createInstance(ctor);117const commandKey = handler.commandName.toLowerCase();118// This shouldn't happen unless we accidentally register duplicates119if (this._handlerCache.has(commandKey)) {120this.logService.warn(`Duplicate Claude slash command name "${handler.commandName}" detected. Ignoring handler ${ctor.name || 'unknown constructor'}.`);121continue;122}123this._handlerCache.set(commandKey, handler);124125// Register VS Code command if commandId is provided126if (handler.commandId) {127this._register(vscode.commands.registerCommand(handler.commandId, () => {128// Invoke with no args and no stream (Command Palette mode)129return handler.handle('', undefined, CancellationToken.None);130}));131}132}133134this._initialized = true;135}136}137138139