Path: blob/main/extensions/copilot/src/platform/prompts/common/promptPathRepresentationService.ts
13401 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 { Uri } from 'vscode';6import { createServiceIdentifier } from '../../../util/common/services';7import { getDriveLetter, hasDriveLetter } from '../../../util/vs/base/common/extpath';8import { Schemas } from '../../../util/vs/base/common/network';9import { isWindows } from '../../../util/vs/base/common/platform';10import { isDefined } from '../../../util/vs/base/common/types';11import { URI } from '../../../util/vs/base/common/uri';12import { IWorkspaceService } from '../../workspace/common/workspaceService';1314export const IPromptPathRepresentationService = createServiceIdentifier<IPromptPathRepresentationService>('IPromptPathRepresentationService');1516/**17* A service that is to be used to represent and restore document URI's in prompts.18* Using the service makes sure this happens in consistent and portable way across prompt elements.19*/20export interface IPromptPathRepresentationService {2122_serviceBrand: undefined;2324getFilePath(uri: Uri): string;2526resolveFilePath(filePath: string, predominantScheme?: string): Uri | undefined;2728getExampleFilePath(relativeFilePath: string): string;29}3031/**32* Used to represent file URIs in prompts. Typically, this happens in code blocks using the `filepath` comment.33* Using the service makes sure this happens in a consistent and portable way across prompt elements.34* When creating a prompt, use `getFilePath` to get the string to use as a `filepath`.35* When readong a LLM response, use `resolveFilePath` to get the URI from from a `filepath`36*37* Do not use this service for other usages than prompts.38* We currently use the fsPath for local and remote filesystems, and URI.toString() for other schemes.39*/40export class PromptPathRepresentationService implements IPromptPathRepresentationService {4142_serviceBrand: undefined;4344protected isWindows() {45return isWindows;46}4748constructor(@IWorkspaceService private readonly workspaceService: IWorkspaceService) { }4950getFilePath(uri: Uri): string {51if (uri.scheme === Schemas.file || uri.scheme === Schemas.vscodeRemote) {52return uri.fsPath;53}54return uri.toString();55}5657/**58* Resolves an `filepath` used in a prompt to a URI. The `filepath` should have been created by `getFilePath`.59*60* @param filepath The file path to resolve.61* @param predominantScheme The predominant scheme to use if the path is a file path. Defaults to 'file'.62*63* @returns The resolved URI or undefined if filepath does not look like a file path or URI.64*/65resolveFilePath(filepath: string, predominantScheme = Schemas.file): Uri | undefined {66// Always check for posix-like absolute paths, and also for platform-like67// (i.e. Windows) absolute paths in case the model generates them.68const isPosixPath = filepath.startsWith('/');69const isWindowsPath = this.isWindows() && (hasDriveLetter(filepath) || filepath.startsWith('\\'));70if (isPosixPath || isWindowsPath) {71// Some models double-escape backslashes, which causes problems down the line.72// Remove repeated backslashes from windows path (but preserve UNC paths)73if (isWindowsPath) {74const isUncPath = filepath.startsWith('\\\\');75filepath = filepath.replace(/\\+/g, '\\');76if (isUncPath) { filepath = '\\' + filepath; }77}7879// Some models see an example of a unix path in tool calls and try to80// represent unix paths on windows without a drive letter, which causes81// issues. Try to rectify this.82if (isPosixPath && this.isWindows() && predominantScheme === Schemas.file) {83const lowerCandidates = this.workspaceService.getWorkspaceFolders()84.filter(folder => folder.scheme === Schemas.file)85.map(folder => getDriveLetter(folder.fsPath, true))86.filter(isDefined);8788const matchingDriveLetter = lowerCandidates.find(c => this.workspaceService.getWorkspaceFolder(URI.file(`${c}:${filepath}`)));89if (matchingDriveLetter) {90filepath = `${matchingDriveLetter}:${filepath}`;91}92}9394const fileUri = URI.file(filepath);95return predominantScheme === Schemas.file ? fileUri : URI.from({ scheme: predominantScheme, path: fileUri.path });96}97if (/\w[\w\d+.-]*:\S/.test(filepath)) { // starts with a scheme98try {99return URI.parse(filepath);100} catch (e) {101return undefined;102}103}104return undefined;105}106107getExampleFilePath(absolutePosixFilePath: string): string {108if (this.isWindows()) {109return this.getFilePath(URI.parse(`file:///C:${absolutePosixFilePath}`));110} else {111return this.getFilePath(URI.parse(`file://${absolutePosixFilePath}`));112}113}114}115/**116* For testing we don't want OS dependent paths as they end up in the cache, so we use the posix path for all platforms.117*/118export class TestPromptPathRepresentationService extends PromptPathRepresentationService {119override getFilePath(uri: Uri): string {120if (uri.scheme === Schemas.file || uri.scheme === Schemas.vscodeRemote) {121return uri.path;122}123return uri.toString();124}125126override getExampleFilePath(absolutePosixFilePath: string): string {127return this.getFilePath(URI.parse(`file://${absolutePosixFilePath}`));128}129}130131132