Path: blob/main/extensions/copilot/src/extension/prompt/vscode-node/promptVariablesService.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 { ChatLanguageModelToolReference, ChatPromptReference } from 'vscode';6import * as vscode from 'vscode';7import { IChatDebugFileLoggerService } from '../../../platform/chat/common/chatDebugFileLoggerService';8import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';9import { IPromptPathRepresentationService } from '../../../platform/prompts/common/promptPathRepresentationService';10import { getToolName } from '../../tools/common/toolNames';11import { IPromptVariablesService } from '../node/promptVariablesService';1213/**14* Known template variables that can be resolved at runtime.15* Each entry maps a placeholder name (without the `{{ }}` delimiters) to a16* resolver that produces the replacement string, or `undefined` if the17* variable cannot be resolved in the current context.18*/19type VariableResolver = (sessionId: string | undefined, debugTargetSessionIds: readonly string[] | undefined) => string | undefined;2021export class PromptVariablesServiceImpl implements IPromptVariablesService {2223declare readonly _serviceBrand: undefined;2425private readonly _resolvers: ReadonlyMap<string, VariableResolver>;2627constructor(28@IChatDebugFileLoggerService private readonly chatDebugFileLoggerService: IChatDebugFileLoggerService,29@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,30@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,31) {32this._resolvers = new Map<string, VariableResolver>([33['VSCODE_USER_PROMPTS_FOLDER', () => {34const globalStorageUri = this.extensionContext.globalStorageUri;35const userFolderUri = vscode.Uri.joinPath(globalStorageUri, '..', '..');36const userPromptsFolderUri = vscode.Uri.joinPath(userFolderUri, 'prompts');37return userPromptsFolderUri.fsPath;38}],39['VSCODE_TARGET_SESSION_LOG', (sessionId, debugTargetSessionIds) => {40if (debugTargetSessionIds && debugTargetSessionIds.length > 0) {41return debugTargetSessionIds.map(id => {42const sessionDir = this.chatDebugFileLoggerService.getSessionDir(id);43return sessionDir ? this.promptPathRepresentationService.getFilePath(sessionDir) : undefined;44}).filter((path): path is string => path !== undefined).join(', ');45}46if (!sessionId) {47return undefined;48}49const sessionDir = this.chatDebugFileLoggerService.getSessionDir(sessionId);50if (!sessionDir) {51return undefined;52}53return this.promptPathRepresentationService.getFilePath(sessionDir);54}],55]);56}5758async resolveVariablesInPrompt(message: string, variables: ChatPromptReference[]): Promise<{ message: string }> {59for (const variable of this._reverseSortRefsWithRange(variables)) {60message = message.slice(0, variable.range[0]) + `[#${variable.name}](#${variable.name}-context)` + message.slice(variable.range[1]);61}6263return { message };64}6566async resolveToolReferencesInPrompt(message: string, toolReferences: ChatLanguageModelToolReference[]): Promise<string> {67// It's part of the extension API contract that these are in reverse order by range, but we sort it to be sure6869let previousRange: [start: number, end: number] | undefined;70for (const toolReference of this._reverseSortRefsWithRange(toolReferences)) {71// Tool sets are passed as all the tools as references with the same ranges. For now, just ignore tool references that have the same range.72// The tools are sorted by range, so we only need to look at the previous one.73const range = toolReference.range;74if (previousRange && range[0] === previousRange[0] && range[1] === previousRange[1]) {75continue;76}77const toolName = getToolName(toolReference.name);78message = message.slice(0, toolReference.range[0]) + `'${toolName}'` + message.slice(toolReference.range[1]);79previousRange = range;80}81return message;82}8384buildTemplateVariablesContext(sessionId: string | undefined, debugTargetSessionIds?: readonly string[]): string {85const entries: [string, string][] = [];86for (const [name, resolve] of this._resolvers) {87const value = resolve(sessionId, debugTargetSessionIds);88if (value !== undefined) {89entries.push([name, value]);90}91}92if (entries.length === 0) {93return '';94}95const lines = entries.map(([name, value]) => `- ${name}: ${value}`);96return [97'The following template variables are available for this session:',98...lines,99'When a skill or instruction references {{VSCODE_VARIABLE_NAME}}, substitute the corresponding value above.',100].join('\n');101}102103private _reverseSortRefsWithRange<T extends { range?: [number, number] }>(refs: T[]): (T & { range: [number, number] })[] {104const refsWithRange = refs.filter(ref => !!ref.range) as (T & { range: [number, number] })[];105return refsWithRange.sort((a, b) => b.range[0] - a.range[0]);106}107}108109110