Skip to content

Commit a6089ea

Browse files
authored
Support A/B Compiler Arguments Traits (#12979)
- Depends on cpptools' update to provide ProjectContextResult. - Send "standardVersion" trait in completion prompt by default. - Added the following new traits - intelliSenseDisclaimer: compiler information disclaimer. - intelliSenseDisclaimerBeginning: to note the beginning of IntelliSense information. - compilerArguments: a list of compiler command arguments that could affect Copilot generating completions. - directAsks: direct asking Copilot to do something instead of providing an argument. - intelliSenseDisclaimerEnd: to note the end of IntelliSense information. - A/B Experimental flags - copilotcppTraits: deprecated, no longer used. - copilotcppExcludeTraits:: deprecated, no longer used. - copilotcppIncludeTraits: string array to include individual trait, i.e., compilerArguments. - copilotcppMsvcCompilerArgumentFilter: map of regex string to absence prompt for MSVC. - copilotcppClangCompilerArgumentFilter: map of regex string to absence prompt for Clang. - copilotcppGccCompilerArgumentFilter: map of regex string to absence prompt for GCC. - copilotcppCompilerArgumentDirectAskMap: map of argument to prompt.
1 parent 21a44d9 commit a6089ea

File tree

7 files changed

+916
-140
lines changed

7 files changed

+916
-140
lines changed

Diff for: Extension/src/LanguageServer/client.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,19 @@ export interface ChatContextResult {
541541
targetArchitecture: string;
542542
}
543543

544+
export interface FileContextResult {
545+
compilerArguments: string[];
546+
}
547+
548+
export interface ProjectContextResult {
549+
language: string;
550+
standardVersion: string;
551+
compiler: string;
552+
targetPlatform: string;
553+
targetArchitecture: string;
554+
fileContext: FileContextResult;
555+
}
556+
544557
// Requests
545558
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
546559
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
@@ -560,7 +573,8 @@ const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Posit
560573
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
561574
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
562575
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
563-
const CppContextRequest: RequestType<void, ChatContextResult, void> = new RequestType<void, ChatContextResult, void>('cpptools/getChatContext');
576+
const CppContextRequest: RequestType<TextDocumentIdentifier, ChatContextResult, void> = new RequestType<TextDocumentIdentifier, ChatContextResult, void>('cpptools/getChatContext');
577+
const ProjectContextRequest: RequestType<TextDocumentIdentifier, ProjectContextResult, void> = new RequestType<TextDocumentIdentifier, ProjectContextResult, void>('cpptools/getProjectContext');
564578

565579
// Notifications to the server
566580
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
@@ -791,7 +805,8 @@ export interface Client {
791805
setShowConfigureIntelliSenseButton(show: boolean): void;
792806
addTrustedCompiler(path: string): Promise<void>;
793807
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
794-
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
808+
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult>;
809+
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult>;
795810
}
796811

797812
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -2220,10 +2235,18 @@ export class DefaultClient implements Client {
22202235
() => this.languageClient.sendRequest(IncludesRequest, params, token), token);
22212236
}
22222237

2223-
public async getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> {
2238+
public async getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> {
2239+
const params: TextDocumentIdentifier = { uri: uri.toString() };
2240+
await withCancellation(this.ready, token);
2241+
return DefaultClient.withLspCancellationHandling(
2242+
() => this.languageClient.sendRequest(CppContextRequest, params, token), token);
2243+
}
2244+
2245+
public async getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> {
2246+
const params: TextDocumentIdentifier = { uri: uri.toString() };
22242247
await withCancellation(this.ready, token);
22252248
return DefaultClient.withLspCancellationHandling(
2226-
() => this.languageClient.sendRequest(CppContextRequest, null, token), token);
2249+
() => this.languageClient.sendRequest(ProjectContextRequest, params, token), token);
22272250
}
22282251

22292252
/**
@@ -4129,5 +4152,6 @@ class NullClient implements Client {
41294152
setShowConfigureIntelliSenseButton(show: boolean): void { }
41304153
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
41314154
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
4132-
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
4155+
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
4156+
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> { return Promise.resolve({} as ProjectContextResult); }
41334157
}

Diff for: Extension/src/LanguageServer/copilotProviders.ts

+111-29
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
'use strict';
66

77
import * as vscode from 'vscode';
8+
import { localize } from 'vscode-nls';
89
import * as util from '../common';
9-
import { ChatContextResult, GetIncludesResult } from './client';
10+
import * as logger from '../logger';
11+
import * as telemetry from '../telemetry';
12+
import { GetIncludesResult } from './client';
1013
import { getActiveClient } from './extension';
14+
import { getCompilerArgumentFilterMap, getProjectContext } from './lmTool';
1115

1216
export interface CopilotTrait {
1317
name: string;
@@ -34,35 +38,113 @@ export async function registerRelatedFilesProvider(): Promise<void> {
3438
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
3539
api.registerRelatedFilesProvider(
3640
{ extensionId: util.extensionContext.extension.id, languageId },
37-
async (_uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
38-
39-
const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
40-
const getTraitsHandler = async () => {
41-
const chatContext: ChatContextResult | undefined = await (getActiveClient().getChatContext(token) ?? undefined);
42-
43-
if (!chatContext) {
44-
return undefined;
41+
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
42+
const start = performance.now();
43+
const telemetryProperties: Record<string, string> = {};
44+
const telemetryMetrics: Record<string, number> = {};
45+
try {
46+
const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
47+
const getTraitsHandler = async () => {
48+
const projectContext = await getProjectContext(uri, context, token);
49+
50+
if (!projectContext) {
51+
return undefined;
52+
}
53+
54+
let traits: CopilotTrait[] = [
55+
{ name: "intelliSenseDisclaimer", value: '', includeInPrompt: true, promptTextOverride: `IntelliSense is currently configured with the following compiler information. It reflects the active configuration, and the project may have more configurations targeting different platforms.` },
56+
{ name: "intelliSenseDisclaimerBeginning", value: '', includeInPrompt: true, promptTextOverride: `Beginning of IntelliSense information.` }
57+
];
58+
if (projectContext.language) {
59+
traits.push({ name: "language", value: projectContext.language, includeInPrompt: true, promptTextOverride: `The language is ${projectContext.language}.` });
60+
}
61+
if (projectContext.compiler) {
62+
traits.push({ name: "compiler", value: projectContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${projectContext.compiler}.` });
63+
}
64+
if (projectContext.standardVersion) {
65+
traits.push({ name: "standardVersion", value: projectContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${projectContext.standardVersion} language standard.` });
66+
}
67+
if (projectContext.targetPlatform) {
68+
traits.push({ name: "targetPlatform", value: projectContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${projectContext.targetPlatform}.` });
69+
}
70+
if (projectContext.targetArchitecture) {
71+
traits.push({ name: "targetArchitecture", value: projectContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${projectContext.targetArchitecture}.` });
72+
}
73+
74+
if (projectContext.compiler) {
75+
// We will process compiler arguments based on copilotcppXXXCompilerArgumentFilters and copilotcppCompilerArgumentDirectAskMap feature flags.
76+
// The copilotcppXXXCompilerArgumentFilters are maps. The keys are regex strings for filtering and the values, if not empty,
77+
// are the prompt text to use when no arguments are found.
78+
// copilotcppCompilerArgumentDirectAskMap map individual matched argument to a prompt text.
79+
// For duplicate matches, the last one will be used.
80+
const filterMap = getCompilerArgumentFilterMap(projectContext.compiler, context);
81+
if (filterMap !== undefined) {
82+
const directAskMap: Record<string, string> = context.flags.copilotcppCompilerArgumentDirectAskMap ? JSON.parse(context.flags.copilotcppCompilerArgumentDirectAskMap as string) : {};
83+
let directAsks: string = '';
84+
const remainingArguments: string[] = [];
85+
86+
for (const key in filterMap) {
87+
if (!key) {
88+
continue;
89+
}
90+
91+
const matchedArgument = projectContext.compilerArguments[key] as string;
92+
if (matchedArgument?.length > 0) {
93+
if (directAskMap[matchedArgument]) {
94+
directAsks += `${directAskMap[matchedArgument]} `;
95+
} else {
96+
remainingArguments.push(matchedArgument);
97+
}
98+
} else if (filterMap[key]) {
99+
// Use the prompt text in the absence of argument.
100+
directAsks += `${filterMap[key]} `;
101+
}
102+
}
103+
104+
if (remainingArguments.length > 0) {
105+
const compilerArgumentsValue = remainingArguments.join(", ");
106+
traits.push({ name: "compilerArguments", value: compilerArgumentsValue, includeInPrompt: true, promptTextOverride: `The compiler arguments include: ${compilerArgumentsValue}.` });
107+
}
108+
109+
if (directAsks) {
110+
traits.push({ name: "directAsks", value: directAsks, includeInPrompt: true, promptTextOverride: directAsks });
111+
}
112+
}
113+
}
114+
115+
traits.push({ name: "intelliSenseDisclaimerEnd", value: '', includeInPrompt: true, promptTextOverride: `End of IntelliSense information.` });
116+
117+
const includeTraitsArray = context.flags.copilotcppIncludeTraits ? context.flags.copilotcppIncludeTraits as string[] : [];
118+
const includeTraits = new Set(includeTraitsArray);
119+
telemetryProperties["includeTraits"] = includeTraitsArray.join(',');
120+
121+
// standardVersion trait is enabled by default.
122+
traits = traits.filter(trait => includeTraits.has(trait.name) || trait.name === 'standardVersion');
123+
124+
telemetryProperties["traits"] = traits.map(trait => trait.name).join(',');
125+
return traits.length > 0 ? traits : undefined;
126+
};
127+
128+
// Call both handlers in parallel
129+
const traitsPromise = getTraitsHandler();
130+
const includesPromise = getIncludesHandler();
131+
132+
return { entries: await includesPromise, traits: await traitsPromise };
133+
}
134+
catch (exception) {
135+
try {
136+
const err: Error = exception as Error;
137+
logger.getOutputChannelLogger().appendLine(localize("copilot.relatedfilesprovider.error", "Error while retrieving result. Reason: {0}", err.message));
45138
}
46-
47-
let traits: CopilotTrait[] = [
48-
{ name: "language", value: chatContext.language, includeInPrompt: true, promptTextOverride: `The language is ${chatContext.language}.` },
49-
{ name: "compiler", value: chatContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${chatContext.compiler}.` },
50-
{ name: "standardVersion", value: chatContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${chatContext.standardVersion} language standard.` },
51-
{ name: "targetPlatform", value: chatContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetPlatform}.` },
52-
{ name: "targetArchitecture", value: chatContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetArchitecture}.` }
53-
];
54-
55-
const excludeTraits = context.flags.copilotcppExcludeTraits as string[] ?? [];
56-
traits = traits.filter(trait => !excludeTraits.includes(trait.name));
57-
58-
return traits.length > 0 ? traits : undefined;
59-
};
60-
61-
// Call both handlers in parallel
62-
const traitsPromise = ((context.flags.copilotcppTraits as boolean) ?? false) ? getTraitsHandler() : Promise.resolve(undefined);
63-
const includesPromise = getIncludesHandler();
64-
65-
return { entries: await includesPromise, traits: await traitsPromise };
139+
catch {
140+
// Intentionally swallow any exception.
141+
}
142+
telemetryProperties["error"] = "true";
143+
throw exception; // Throw the exception for auto-retry.
144+
} finally {
145+
telemetryMetrics['duration'] = performance.now() - start;
146+
telemetry.logCopilotEvent('RelatedFilesProvider', telemetryProperties, telemetryMetrics);
147+
}
66148
}
67149
);
68150
}

0 commit comments

Comments
 (0)