Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/vscode-node/promptVariablesService.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { ChatLanguageModelToolReference, ChatPromptReference } from 'vscode';
7
import * as vscode from 'vscode';
8
import { IChatDebugFileLoggerService } from '../../../platform/chat/common/chatDebugFileLoggerService';
9
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
10
import { IPromptPathRepresentationService } from '../../../platform/prompts/common/promptPathRepresentationService';
11
import { getToolName } from '../../tools/common/toolNames';
12
import { IPromptVariablesService } from '../node/promptVariablesService';
13
14
/**
15
* Known template variables that can be resolved at runtime.
16
* Each entry maps a placeholder name (without the `{{ }}` delimiters) to a
17
* resolver that produces the replacement string, or `undefined` if the
18
* variable cannot be resolved in the current context.
19
*/
20
type VariableResolver = (sessionId: string | undefined, debugTargetSessionIds: readonly string[] | undefined) => string | undefined;
21
22
export class PromptVariablesServiceImpl implements IPromptVariablesService {
23
24
declare readonly _serviceBrand: undefined;
25
26
private readonly _resolvers: ReadonlyMap<string, VariableResolver>;
27
28
constructor(
29
@IChatDebugFileLoggerService private readonly chatDebugFileLoggerService: IChatDebugFileLoggerService,
30
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
31
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
32
) {
33
this._resolvers = new Map<string, VariableResolver>([
34
['VSCODE_USER_PROMPTS_FOLDER', () => {
35
const globalStorageUri = this.extensionContext.globalStorageUri;
36
const userFolderUri = vscode.Uri.joinPath(globalStorageUri, '..', '..');
37
const userPromptsFolderUri = vscode.Uri.joinPath(userFolderUri, 'prompts');
38
return userPromptsFolderUri.fsPath;
39
}],
40
['VSCODE_TARGET_SESSION_LOG', (sessionId, debugTargetSessionIds) => {
41
if (debugTargetSessionIds && debugTargetSessionIds.length > 0) {
42
return debugTargetSessionIds.map(id => {
43
const sessionDir = this.chatDebugFileLoggerService.getSessionDir(id);
44
return sessionDir ? this.promptPathRepresentationService.getFilePath(sessionDir) : undefined;
45
}).filter((path): path is string => path !== undefined).join(', ');
46
}
47
if (!sessionId) {
48
return undefined;
49
}
50
const sessionDir = this.chatDebugFileLoggerService.getSessionDir(sessionId);
51
if (!sessionDir) {
52
return undefined;
53
}
54
return this.promptPathRepresentationService.getFilePath(sessionDir);
55
}],
56
]);
57
}
58
59
async resolveVariablesInPrompt(message: string, variables: ChatPromptReference[]): Promise<{ message: string }> {
60
for (const variable of this._reverseSortRefsWithRange(variables)) {
61
message = message.slice(0, variable.range[0]) + `[#${variable.name}](#${variable.name}-context)` + message.slice(variable.range[1]);
62
}
63
64
return { message };
65
}
66
67
async resolveToolReferencesInPrompt(message: string, toolReferences: ChatLanguageModelToolReference[]): Promise<string> {
68
// It's part of the extension API contract that these are in reverse order by range, but we sort it to be sure
69
70
let previousRange: [start: number, end: number] | undefined;
71
for (const toolReference of this._reverseSortRefsWithRange(toolReferences)) {
72
// 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.
73
// The tools are sorted by range, so we only need to look at the previous one.
74
const range = toolReference.range;
75
if (previousRange && range[0] === previousRange[0] && range[1] === previousRange[1]) {
76
continue;
77
}
78
const toolName = getToolName(toolReference.name);
79
message = message.slice(0, toolReference.range[0]) + `'${toolName}'` + message.slice(toolReference.range[1]);
80
previousRange = range;
81
}
82
return message;
83
}
84
85
buildTemplateVariablesContext(sessionId: string | undefined, debugTargetSessionIds?: readonly string[]): string {
86
const entries: [string, string][] = [];
87
for (const [name, resolve] of this._resolvers) {
88
const value = resolve(sessionId, debugTargetSessionIds);
89
if (value !== undefined) {
90
entries.push([name, value]);
91
}
92
}
93
if (entries.length === 0) {
94
return '';
95
}
96
const lines = entries.map(([name, value]) => `- ${name}: ${value}`);
97
return [
98
'The following template variables are available for this session:',
99
...lines,
100
'When a skill or instruction references {{VSCODE_VARIABLE_NAME}}, substitute the corresponding value above.',
101
].join('\n');
102
}
103
104
private _reverseSortRefsWithRange<T extends { range?: [number, number] }>(refs: T[]): (T & { range: [number, number] })[] {
105
const refsWithRange = refs.filter(ref => !!ref.range) as (T & { range: [number, number] })[];
106
return refsWithRange.sort((a, b) => b.range[0] - a.range[0]);
107
}
108
}
109
110