Skip to content

Commit 5bd3ed7

Browse files
implement Copilot based on the proposed languageModels API (#1310)
* upgrade vscode engine to use log OutputChannel * use `languageModels` API proposal. * implement `Copilot` based on the proposed languageModels api and handles incomplement responses. * fix adding property to telemetry event and rename output channel.
1 parent a6a5bc5 commit 5bd3ed7

File tree

5 files changed

+373
-10
lines changed

5 files changed

+373
-10
lines changed

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"publisher": "vscjava",
88
"preview": false,
99
"engines": {
10-
"vscode": "^1.64.0"
10+
"vscode": "^1.74.0"
1111
},
1212
"aiKey": "b4a8a622-6ac7-4cf8-83aa-f325e1890795",
1313
"icon": "logo.png",
@@ -38,6 +38,9 @@
3838
"capabilities": {
3939
"virtualWorkspaces": false
4040
},
41+
"enabledApiProposals": [
42+
"languageModels"
43+
],
4144
"activationEvents": [
4245
"onLanguage:java",
4346
"workspaceContains:pom.xml",
@@ -333,7 +336,7 @@
333336
"@types/react-dom": "^16.9.16",
334337
"@types/react-redux": "^7.1.24",
335338
"@types/semver": "^7.3.12",
336-
"@types/vscode": "1.64.0",
339+
"@types/vscode": "^1.74.0",
337340
"@types/winreg": "^1.2.31",
338341
"@types/xmldom": "^0.1.31",
339342
"autoprefixer": "^10.4.12",

src/copilot/Copilot.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { LanguageModelChatMessage, LanguageModelChatUserMessage, LanguageModelChatAssistantMessage, lm, Disposable, CancellationToken, LanguageModelChatRequestOptions } from "vscode";
2+
import { instrumentSimpleOperation, sendInfo } from "vscode-extension-telemetry-wrapper";
3+
import { logger } from "./utils";
4+
5+
export default class Copilot {
6+
public static readonly DEFAULT_END_MARK = '<|endofresponse|>';
7+
public static readonly DEFAULT_MAX_ROUNDS = 10;
8+
public static readonly DEFAULT_MODEL = 'copilot-gpt-4';
9+
public static readonly DEFAULT_MODEL_OPTIONS: LanguageModelChatRequestOptions = { modelOptions: {} };
10+
public static readonly NOT_CANCELLABEL: CancellationToken = { isCancellationRequested: false, onCancellationRequested: () => Disposable.from() };
11+
12+
public constructor(
13+
private readonly systemMessagesOrSamples: LanguageModelChatMessage[],
14+
private readonly model: string = Copilot.DEFAULT_MODEL,
15+
private readonly modelOptions: LanguageModelChatRequestOptions = Copilot.DEFAULT_MODEL_OPTIONS,
16+
private readonly maxRounds: number = Copilot.DEFAULT_MAX_ROUNDS,
17+
private readonly endMark: string = Copilot.DEFAULT_END_MARK
18+
) {
19+
}
20+
21+
private async doSend(
22+
userMessage: string,
23+
modelOptions: LanguageModelChatRequestOptions = Copilot.DEFAULT_MODEL_OPTIONS,
24+
cancellationToken: CancellationToken = Copilot.NOT_CANCELLABEL
25+
): Promise<string> {
26+
let answer: string = '';
27+
let rounds: number = 0;
28+
const messages = [...this.systemMessagesOrSamples];
29+
const _send = async (message: string): Promise<boolean> => {
30+
rounds++;
31+
logger.info(`User: \n`, message);
32+
messages.push(new LanguageModelChatUserMessage(message));
33+
logger.info('Copilot: thinking...');
34+
35+
let rawAnswer: string = '';
36+
try {
37+
const response = await lm.sendChatRequest(this.model, messages, modelOptions ?? this.modelOptions, cancellationToken);
38+
for await (const item of response.stream) {
39+
rawAnswer += item;
40+
}
41+
} catch (e) {
42+
logger.error(`Failed to send request to copilot`, e);
43+
throw new Error(`Failed to send request to copilot: ${e}`);
44+
}
45+
messages.push(new LanguageModelChatAssistantMessage(rawAnswer));
46+
logger.info(`Copilot: \n`, rawAnswer);
47+
answer += rawAnswer;
48+
return answer.trim().endsWith(this.endMark);
49+
};
50+
let complete: boolean = await _send(userMessage);
51+
while (!complete && rounds < this.maxRounds) {
52+
complete = await _send('continue where you left off.');
53+
}
54+
logger.debug('rounds', rounds);
55+
sendInfo('java.copilot.sendRequest.rounds', { rounds: rounds });
56+
return answer.replace(this.endMark, "");
57+
}
58+
59+
public async send(
60+
userMessage: string,
61+
modelOptions: LanguageModelChatRequestOptions = Copilot.DEFAULT_MODEL_OPTIONS,
62+
cancellationToken: CancellationToken = Copilot.NOT_CANCELLABEL
63+
): Promise<string> {
64+
return instrumentSimpleOperation("java.copilot.sendRequest", this.doSend.bind(this))(userMessage, modelOptions, cancellationToken);
65+
}
66+
}

src/copilot/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { LogOutputChannel, window } from "vscode";
2+
3+
export const logger: LogOutputChannel = window.createOutputChannel("Java Rewriting Suggestions", { log: true });

0 commit comments

Comments
 (0)