Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C++ configuration as a language model tool (luca) #12685

Merged
merged 11 commits into from
Sep 6, 2024
16 changes: 15 additions & 1 deletion Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"Snippets"
],
"enabledApiProposals": [
"terminalDataWriteEvent"
"terminalDataWriteEvent",
"lmTools"
],
"capabilities": {
"untrustedWorkspaces": {
Expand Down Expand Up @@ -6440,6 +6441,19 @@
"description": "%c_cpp.codeActions.refactor.extract.function.description%"
}
}
],
"languageModelTools": [
{
"id": "cpptools-lmtool-configuration",
"name": "cpp",
"displayName": "%c_cpp.languageModelTools.configuration.displayName%",
"canBeInvokedManually": true,
"userDescription": "%c_cpp.languageModelTools.configuration.userDescription%",
"modelDescription": "For the active C or C++ file, this tool provides: the language (C, C++, or CUDA), the language standard version (such as C++11, C++14, C++17, or C++20), the compiler (such as GCC, Clang, or MSVC), the target platform (such as x86, x64, or ARM), and the target architecture (such as 32-bit or 64-bit).",
"icon": "$(file-code)",
"parametersSchema": {},
"when": "(config.C_Cpp.experimentalFeatures =~ /^[eE]nabled$/)"
}
]
},
"scripts": {
Expand Down
4 changes: 3 additions & 1 deletion Extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -1006,5 +1006,7 @@
"c_cpp.configuration.refactoring.includeHeader.markdownDescription": "Controls whether to include the header file of a refactored function/symbol to its corresponding source file when doing a refactoring action, such as create declaration/definition.",
"c_cpp.configuration.refactoring.includeHeader.always.description": "Always include the header file if it is not included explicitly in its source file.",
"c_cpp.configuration.refactoring.includeHeader.ifNeeded.description": "Only include the header file if it is not included explicitly in its source file or through implicit inclusion.",
"c_cpp.configuration.refactoring.includeHeader.never.description": "Never include the header file."
"c_cpp.configuration.refactoring.includeHeader.never.description": "Never include the header file.",
"c_cpp.languageModelTools.configuration.displayName": "C/C++ configuration",
"c_cpp.languageModelTools.configuration.userDescription": "Configuration of the active C or C++ file, like language standard version and target platform."
}
36 changes: 33 additions & 3 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import * as fs from 'fs';
import * as os from 'os';
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { IntelliSenseStatus, Status } from 'vscode-cpptools/out/testApi';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, ResponseError, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
import * as nls from 'vscode-nls';
import { DebugConfigurationProvider } from '../Debugger/configurationProvider';
Expand Down Expand Up @@ -58,12 +58,12 @@ import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorCon
import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
import { PersistentFolderState, PersistentWorkspaceState } from './persistentState';
import { createProtocolFilter } from './protocolFilter';
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
import * as refs from './references';
import { CppSettings, OtherSettings, SettingsParams, WorkspaceFolderSettingsParams } from './settings';
import { SettingsTracker } from './settingsTracker';
import { ConfigurationType, LanguageStatusUI, getUI } from './ui';
import { handleChangedFromCppToC, makeLspRange, makeVscodeLocation, makeVscodeRange } from './utils';
import { handleChangedFromCppToC, makeLspRange, makeVscodeLocation, makeVscodeRange, withCancellation } from './utils';
import minimatch = require("minimatch");

function deepCopy(obj: any) {
Expand Down Expand Up @@ -542,6 +542,14 @@ interface GetIncludesResult {
includedFiles: string[];
}

export interface ChatContextResult {
language: string;
standardVersion: string;
compiler: string;
targetPlatform: string;
targetArchitecture: string;
}

// Requests
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
Expand All @@ -562,6 +570,7 @@ const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Posit
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
const CppContextRequest: RequestType<void, ChatContextResult, void> = new RequestType<void, ChatContextResult, void>('cpptools/getChatContext');

// Notifications to the server
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
Expand Down Expand Up @@ -792,6 +801,7 @@ export interface Client {
setShowConfigureIntelliSenseButton(show: boolean): void;
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number): Promise<GetIncludesResult>;
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
}

export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
Expand Down Expand Up @@ -2234,6 +2244,25 @@ export class DefaultClient implements Client {
return this.languageClient.sendRequest(IncludesRequest, params);
}

public async getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> {
await withCancellation(this.ready, token);
let result: ChatContextResult;
try {
result = await this.languageClient.sendRequest(CppContextRequest, null, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
}

throw e;
}
if (token.isCancellationRequested) {
throw new vscode.CancellationError();
}

return result;
}

/**
* a Promise that can be awaited to know when it's ok to proceed.
*
Expand Down Expand Up @@ -4114,4 +4143,5 @@ class NullClient implements Client {
setShowConfigureIntelliSenseButton(show: boolean): void { }
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
getIncludes(): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
lukka marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 6 additions & 0 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, code
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
import { getCustomConfigProviders } from './customProviders';
import { getLanguageConfig } from './languageConfig';
import { CppConfigurationLanguageModelTool } from './lmTool';
import { PersistentState } from './persistentState';
import { NodeType, TreeNode } from './referencesModel';
import { CppSettings } from './settings';
Expand Down Expand Up @@ -248,6 +249,11 @@ export async function activate(): Promise<void> {
clients.timeTelemetryCollector.setFirstFile(activeEditor.document.uri);
activeDocument = activeEditor.document;
}

if (util.extensionContext && new CppSettings().experimentalFeatures) {
lukka marked this conversation as resolved.
Show resolved Hide resolved
const tool = vscode.lm.registerTool('cpptools-lmtool-configuration', new CppConfigurationLanguageModelTool());
disposables.push(tool);
}
}

export function updateLanguageConfigurations(): void {
Expand Down
109 changes: 109 additions & 0 deletions Extension/src/LanguageServer/lmTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';

import * as vscode from 'vscode';
import { localize } from 'vscode-nls';
import * as util from '../common';
import * as logger from '../logger';
import * as telemetry from '../telemetry';
import { ChatContextResult } from './client';
import { getClients } from './extension';

const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: string } } = {
language: {
'c': 'C',
'cpp': 'C++',
'cuda-cpp': 'CUDA C++'
},
compiler: {
'msvc': 'MSVC',
'clang': 'Clang',
'gcc': 'GCC'
},
standardVersion: {
'c++98': 'C++98',
'c++03': 'C++03',
'c++11': 'C++11',
'c++14': 'C++14',
'c++17': 'C++17',
'c++20': 'C++20',
'c++23': 'C++23',
'c90': "C90",
'c99': "C99",
'c11': "C11",
'c17': "C17",
'c23': "C23"
},
targetPlatform: {
'windows': 'Windows',
'Linux': 'Linux',
'macos': 'macOS'
}
};

class StringLanguageModelToolResult implements vscode.LanguageModelToolResult
sean-mcmanus marked this conversation as resolved.
Show resolved Hide resolved
{
public constructor(public readonly value: string) {}
public toString(): string { return this.value; }
}

export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTool
{
public async invoke(_parameters: any, token: vscode.CancellationToken): Promise<vscode.LanguageModelToolResult> {
return new StringLanguageModelToolResult(await this.getContext(token));
}

private async getContext(token: vscode.CancellationToken): Promise<string> {
try
{
const currentDoc = vscode.window.activeTextEditor?.document;
if (!currentDoc || (!util.isCpp(currentDoc) && !util.isHeaderFile(currentDoc.uri))) {
return 'The active document is not a C, C++, or CUDA file.';
}

const chatContext: ChatContextResult | undefined = await (getClients()?.ActiveClient?.getChatContext(token) ?? undefined);
if (!chatContext) {
return 'No configuration information is available for the active document.';
}

telemetry.logLanguageModelToolEvent(
'cpp',
{
"language": chatContext.language,
"compiler": chatContext.compiler,
"standardVersion": chatContext.standardVersion,
"targetPlatform": chatContext.targetPlatform,
"targetArchitecture": chatContext.targetArchitecture
});

for (const key in knownValues) {
const knownKey = key as keyof ChatContextResult;
if (knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey]] || chatContext[knownKey];
}
}

return `The user is working on a ${chatContext.language} project. The project uses language version ${chatContext.standardVersion}, compiles using the ${chatContext.compiler} compiler, targets the ${chatContext.targetPlatform} platform, and targets the ${chatContext.targetArchitecture} architecture.`;
}
catch (e: any)
{
await this.reportError();
return "";
}
}

private async reportError(): Promise<void>
{
try
{
logger.getOutputChannelLogger().appendLine(localize("copilot.cppcontext.error", "Error while retrieving the #cpp context."));
}
catch
{
// Intentionally swallow any exception.
}
}
}
Loading
Loading