Path: blob/main/extensions/copilot/src/extension/prompt/node/gitBranch.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 type * as vscode from 'vscode';6import { sessionResourceToId } from '../../../platform/chat/common/chatDebugFileLoggerService';7import { ChatFetchResponseType, ChatLocation } from '../../../platform/chat/common/commonTypes';8import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';9import { ILogService } from '../../../platform/log/common/logService';10import { CapturingToken } from '../../../platform/requestLogger/common/capturingToken';11import { IRequestLogger } from '../../../platform/requestLogger/common/requestLogger';12import { URI } from '../../../util/vs/base/common/uri';13import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';14import { ChatRequestTurn } from '../../../vscodeTypes';15import { renderPromptElement } from '../../prompts/node/base/promptRenderer';16import { GitBranchPrompt } from '../../prompts/node/panel/gitBranch';1718export class GitBranchNameGenerator {1920constructor(21@ILogService private readonly logService: ILogService,22@IEndpointProvider private endpointProvider: IEndpointProvider,23@IInstantiationService private readonly instantiationService: IInstantiationService,24@IRequestLogger private readonly requestLogger: IRequestLogger,25) { }2627async generateBranchName(28context: vscode.ChatContext,29token: vscode.CancellationToken,30): Promise<string | undefined> {3132// Get the first user message directly from the context33// Use instanceof to properly check if the first item is a ChatRequestTurn34const firstRequest = context.history.find(item => item instanceof ChatRequestTurn);35if (!firstRequest) {36return '';37}3839// Extract the parent session ID from the context's sessionResource (provided by VS Code)40const sessionResource = context.sessionResource;41const parentChatSessionId = sessionResource ? sessionResourceToId(URI.from(sessionResource)) : undefined;4243const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');44const normalizedCommand = firstRequest.command?.trim().replace(/^\/+/, '') ?? '';45const command = normalizedCommand ? `/${normalizedCommand} ` : '';46const userRequest = `${command}${firstRequest.prompt}`;47const { messages } = await renderPromptElement(this.instantiationService, endpoint, GitBranchPrompt, { userRequest });4849const capturingToken = new CapturingToken(50'git-branch',51undefined,52undefined,53undefined,54undefined,55parentChatSessionId,56'git-branch',57);5859const doRequest = async () => {60const response = await endpoint.makeChatRequest2({61debugName: 'git-branch',62messages,63finishedCb: undefined,64location: ChatLocation.Panel,65userInitiatedRequest: false,66isConversationRequest: false,67}, token);68return response;69};7071const response = await this.requestLogger.captureInvocation(capturingToken, doRequest);72if (token.isCancellationRequested) {73return '';74}7576if (response.type === ChatFetchResponseType.Success) {77let branchName = response.value.trim();78if (branchName.match(/^".*"$/)) {79branchName = branchName.slice(1, -1);80}81if (branchName.includes('can\'t assist with that')) {82return undefined;83}8485branchName = normalizeBranchName(branchName);86if (branchName.length < 8) {87throw new Error('Branch name is too short. Please keep it at least 8 characters.');88}8990return branchName;91} else {92this.logService.error(`Failed to fetch git branch name because of response type (${response.type}) and reason (${response.reason})`);93return '';94}95}96}9798export function normalizeBranchName(branchName: string): string {99// Only support alphanumeric characters and dashes for simplicity.100let normalized = branchName.replace(/[^a-zA-Z0-9\-]/g, '').toLowerCase();101// Collapse consecutive dots (..) into a single dot102normalized = normalized.replace(/\.{2,}/g, '.');103// Strip leading '-' or '.'104normalized = normalized.replace(/^[-.]+/, '');105// Strip trailing '.' or '/'106normalized = normalized.replace(/[./]+$/, '');107// Strip trailing .lock108normalized = normalized.replace(/\.lock$/, '');109110return normalized;111}112113114