Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/claudeSessionOptionBuilder.ts
13399 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 { PermissionMode } from '@anthropic-ai/claude-agent-sdk';6import * as l10n from '@vscode/l10n';7import * as vscode from 'vscode';8import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';9import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';10import { CancellationToken } from '../../../util/vs/base/common/cancellation';11import { basename } from '../../../util/vs/base/common/resources';12import { URI } from '../../../util/vs/base/common/uri';13import { IChatFolderMruService } from '../common/folderRepositoryManager';14import { folderMRUToChatProviderOptions, getSelectedOption, toWorkspaceFolderOptionItem } from './sessionOptionGroupBuilder';1516const permissionModes: ReadonlySet<PermissionMode> = new Set<PermissionMode>(['default', 'acceptEdits', 'bypassPermissions', 'plan', 'dontAsk']);1718export function isPermissionMode(value: string): value is PermissionMode {19return permissionModes.has(value as PermissionMode);20}2122export const PERMISSION_MODE_OPTION_ID = 'permissionMode';23export const FOLDER_OPTION_ID = 'folder';24const MAX_MRU_ENTRIES = 10;2526/**27* Builds and reads chat session option groups (permission mode, folder picker).28* Pure construction logic with no metadata or session-state dependencies — the29* controller resolves session-specific values and delegates here.30*/31export class ClaudeSessionOptionBuilder {32private _lastUsedPermissionMode: PermissionMode = 'acceptEdits';3334get lastUsedPermissionMode(): PermissionMode {35return this._lastUsedPermissionMode;36}3738constructor(39private readonly _configurationService: IConfigurationService,40private readonly _folderMruService: IChatFolderMruService,41private readonly _workspaceService: IWorkspaceService,42) { }4344async buildNewSessionGroups(): Promise<vscode.ChatSessionProviderOptionGroup[]> {45const groups: vscode.ChatSessionProviderOptionGroup[] = [];4647const folderGroup = await this.buildNewFolderGroup();48if (folderGroup) {49groups.push(folderGroup);50}5152const permissionGroup = this.buildPermissionModeGroup();53const selectedPermission = permissionGroup.items.find(i => i.id === this._lastUsedPermissionMode);54groups.push({55...permissionGroup,56selected: selectedPermission ?? permissionGroup.items[0],57});5859return groups;60}6162async buildExistingSessionGroups(permissionMode: PermissionMode, folderUri: URI | undefined): Promise<vscode.ChatSessionProviderOptionGroup[]> {63const groups: vscode.ChatSessionProviderOptionGroup[] = [];6465if (folderUri) {66groups.push(this.buildExistingFolderGroup(folderUri));67}6869const permissionGroup = this.buildPermissionModeGroup();70const selectedItem = permissionGroup.items.find(i => i.id === permissionMode) ?? permissionGroup.items[0];71groups.push({72...permissionGroup,73selected: selectedItem,74});7576return groups;77}7879buildPermissionModeGroup(): vscode.ChatSessionProviderOptionGroup {80const bypassEnabled = this._configurationService.getConfig(ConfigKey.ClaudeAgentAllowDangerouslySkipPermissions);81return buildPermissionModeItems(bypassEnabled);82}8384async buildNewFolderGroup(): Promise<vscode.ChatSessionProviderOptionGroup | undefined> {85const workspaceFolders = this._workspaceService.getWorkspaceFolders();86if (workspaceFolders.length === 1) {87return undefined;88}8990const folderItems = await this.getFolderOptionItems();91return {92id: FOLDER_OPTION_ID,93name: l10n.t('Folder'),94description: l10n.t('Pick Folder'),95items: folderItems,96selected: folderItems[0],97};98}99100buildExistingFolderGroup(folderUri: URI): vscode.ChatSessionProviderOptionGroup {101const folderItem: vscode.ChatSessionProviderOptionItem = {102...toWorkspaceFolderOptionItem(folderUri, this._workspaceService.getWorkspaceFolderName(folderUri) || basename(folderUri)),103locked: true,104};105return {106id: FOLDER_OPTION_ID,107name: l10n.t('Folder'),108description: l10n.t('Pick Folder'),109items: [folderItem],110selected: folderItem,111};112}113114async getFolderOptionItems(): Promise<vscode.ChatSessionProviderOptionItem[]> {115const workspaceFolders = this._workspaceService.getWorkspaceFolders();116117if (workspaceFolders.length === 0) {118const mruEntries = await this._folderMruService.getRecentlyUsedFolders(CancellationToken.None);119return folderMRUToChatProviderOptions(mruEntries).slice(0, MAX_MRU_ENTRIES);120}121122return workspaceFolders.map(folder =>123toWorkspaceFolderOptionItem(folder, this._workspaceService.getWorkspaceFolderName(folder))124);125}126127async getDefaultFolder(): Promise<URI | undefined> {128const workspaceFolders = this._workspaceService.getWorkspaceFolders();129if (workspaceFolders.length > 0) {130return workspaceFolders[0];131}132133const mru = await this._folderMruService.getRecentlyUsedFolders(CancellationToken.None);134if (mru.length > 0) {135return mru[0].folder;136}137138return undefined;139}140141/**142* Reads the current selections from option groups and updates143* {@link lastUsedPermissionMode} as a side-effect.144*/145getSelections(groups: readonly vscode.ChatSessionProviderOptionGroup[]): { permissionMode?: PermissionMode; folderUri?: URI } {146const selectedPermission = getSelectedOption(groups, PERMISSION_MODE_OPTION_ID);147let permissionMode: PermissionMode | undefined;148if (selectedPermission && isPermissionMode(selectedPermission.id)) {149this._lastUsedPermissionMode = selectedPermission.id;150permissionMode = selectedPermission.id;151}152153const selectedFolder = getSelectedOption(groups, FOLDER_OPTION_ID);154const folderUri = selectedFolder ? URI.file(selectedFolder.id) : undefined;155156return { permissionMode, folderUri };157}158}159160// #region Pure group-building functions (observable-friendly)161162/**163* Build the permission mode option group from explicit inputs.164* Pure and synchronous — suitable for use in `derived` computations.165*/166export function buildPermissionModeItems(bypassEnabled: boolean): vscode.ChatSessionProviderOptionGroup {167const items: vscode.ChatSessionProviderOptionItem[] = [168{ id: 'default', name: l10n.t('Ask before edits'), slashCommand: 'ask' },169{ id: 'acceptEdits', name: l10n.t('Edit automatically'), slashCommand: 'edit' },170{ id: 'plan', name: l10n.t('Plan mode'), slashCommand: 'plan' },171];172173if (bypassEnabled) {174items.push({ id: 'bypassPermissions', name: l10n.t('Bypass all permissions'), slashCommand: 'yolo' });175}176177return {178id: PERMISSION_MODE_OPTION_ID,179name: l10n.t('Permission Mode'),180description: l10n.t('Pick Permission Mode'),181items,182kind: 'permissions',183};184}185186// #endregion187188189