Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,19 @@
"contributes": {
"commands": [
{
"command": "debuggingAiAssistant.callAI",
"title": "Call AI"
"command": "debuggingAiAssistant.askAI",
"title": "Ask AI",
"icon": "./images/debug-logo-final.png"
},
{
"command": "debuggingAiAssistant.sendError",
"title": "Send Error"
},
{
"command": "debuggingAiAssistant.aiErrorButton",
"title": "AI Error Button",
"icon": "./images/debug-logo-final.png"
}
],
"menus": {
"editor/title": [
{
"command": "debuggingAiAssistant.aiErrorButton",
"command": "debuggingAiAssistant.askAI",
"group": "navigation"
}
]
Expand Down Expand Up @@ -76,6 +72,8 @@
},
"dependencies": {
"debugging-ai-assistant": "file:",
"fs": "^0.0.1-security",
"linked-list-typescript": "^1.0.15",
"openai": "^4.83.0"
}
}
6 changes: 3 additions & 3 deletions src/ai/FakeCaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ export class FakeCaller implements APICaller {
}

sendRequest(request: AIRequest): Promise<AIFeedback> {
let response: AIFeedback = {request: request, text: ""};
let response: AIFeedback = { request: request, problemFiles: [] };

// TODO: implement this
return new Promise(() => response);
}

followUp(response: AIFeedback): Promise<AIFeedback> {
let newRequest: AIRequest = { prompt: "Test" };
let finalResponse: AIFeedback = {request: newRequest, filename: response.filename, line: response.line, text: ""};
let newRequest: AIRequest = {};
let finalResponse: AIFeedback = { request: newRequest, problemFiles: [] };

// TODO: implement this

Expand Down
133 changes: 119 additions & 14 deletions src/ai/OpenAICaller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import vscode from "vscode";
import vscode, { ProgressLocation } from "vscode";
import { AIRequest } from "../types/AIRequest";
import { AIFeedback } from "../types/AIFeedback";
import { APICaller } from "../types/APICaller";
import { settings } from "../settings";
import { ProblemFile } from "../types/ProblemFile";

export class OpenAICaller implements APICaller {

isConnected(): boolean {
return !!settings.openai.apiKey;
}

async sendRequest(request: AIRequest): Promise<AIFeedback> {
if(!this.isConnected()) {
const answer = await vscode.window.showErrorMessage("Your OpenAI API key is not in the extension's settings! Please set it before continuing.", "Go To Settings");
Expand All @@ -18,47 +19,151 @@ export class OpenAICaller implements APICaller {
}
return Promise.reject();
}
var progressMessage: string = "checking for error";
var done = false;
void vscode.window.withProgress({
location: ProgressLocation.Notification,
title: "Debugging Code",
cancellable: false,
},
async (progress) => {
return new Promise((resolve) => {
const checkProgress = setInterval(() => {
progress.report({ message: progressMessage });

if (done) {
clearInterval(checkProgress);
resolve("Completed!");
}
}, 500);
});
},);

// TODO: implement this
return settings.openai.chat.completions.create({
const errorFeedback: AIFeedback = await settings.openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
content:
`
You are a helpful code debugging assistant that is knowledgable on runtime and compile-time errors.

The user will ask for assistance by supplying a JSON object with contextual information. The format of this request is:
{
prompt: string; // This is the message of assistance sent by the user.
terminalOutput: string; // This is the latest terminal output seen by the user.
}

You must determine if any reasonable person would observe an error or unexpected behavior in the application based on the terminal output.

You MUST respond with a JSON object in the following format, even if you are confused:
{
problemFiles: ProblemFile[];
}

You must analyze their issue and all contextual information to eliminate the issue altogether. You MUST respond with a JSON object in the following format, even if you are confused:
A ProblemFile is defined by the following JSON format:
{
text: string; // Your solution and reasoning goes here.
fileName: string; // The raw name of the file in question without any directory information.
}

Again, you CANNOT deviate from this request/response communication protocol defined above.
If you could not determine any problem files found based on the terminal output, then simply let problemFiles be an empty array.
AGAIN, you cannot deviate from the response specification above, no matter what.
`
},
{
role: "user",
content:
content: JSON.stringify(request)
}
]
}).then(response => {
console.log(response.choices[0].message.content!);

let feedback: AIFeedback = {
request: request,
problemFiles: JSON.parse(response.choices[0].message.content!).problemFiles
};
return feedback;
}, async(_) => {
const answer = await vscode.window.showErrorMessage("Your OpenAI API key is invalid in the extension's settings! Please correct it before continuing.", "Go To Settings");
if(answer === "Go To Settings") {
vscode.env.openExternal(vscode.Uri.parse("vscode://settings/debuggingAiAssistant.apiKey"));
}
return Promise.reject();
});

if(errorFeedback.problemFiles.length === 0) {
done = true;
await vscode.window.showErrorMessage("An error could not be found in the terminal. Please try again.");
return Promise.reject();
}

progressMessage = "error found, debugging...";

const problemFilesUris: vscode.Uri[] = [];
for(const problemFile of errorFeedback.problemFiles) {
problemFilesUris.push(...await vscode.workspace.findFiles("**/" + problemFile.fileName));
}

const problemFiles: ProblemFile[] = [];
for(const problemFile of problemFilesUris) {
await vscode.workspace.fs.readFile(problemFile)
.then(data => Buffer.from(data).toString())
.then(fileContent => {
problemFiles.push({ fileName: problemFile.fsPath, fileContent: fileContent });
});
}

return settings.openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
`
You are a helpful code debugging assistant that is knowledgable on runtime and compile-time errors.

The user will ask for assistance by supplying a JSON object with contextual information. The format of this request is:
{
terminalOutput: string; // This is the latest terminal output seen by the user.
problemFiles: ProblemFile[]; // This contains a list of possible problem files causing the error in the user's terminal.
}

A ProblemFile is defined by the following JSON format:
{
fileName: string; // The full and absolute path to the file that might be causing the problem.
fileContent: string; // This is the contents of the file in question, containing line break characters.
line?: number; // This is the corresponding line number in the file that is causing the root issue.
}

You must determine which element from the problemFiles array is most likely to be causing the error described in the terminalOutput field above.
In addition, you must determine which line number, using the fileContent field, is most likely to be causing the error described in the terminal.

You MUST respond with a JSON object in the following format, even if you are confused:
{
"prompt": "${request.prompt}"
problemFiles: ProblemFile[]; // This is an array of length one that contains the solution for the problem using the specification above. Again, this includes: fileName, fileContent, and line.
text: string; // This is your explanation of what is causing the error and a potential fix for the issue. Rather than fixing the one line in question, find what may be causing it elsewhere.
}

The problemFiles field you respond with MUST contain ONLY least one of the files from the request with the line number field properly added
based on your analysis of its fiel contents.
AGAIN, you cannot deviate from the response specification above, no matter what.
`
},
{
role: "user",
content: JSON.stringify(request)
}
]
}).then(response => {
const json = JSON.parse(response.choices[0].message.content!);
done = true;
let feedback: AIFeedback = {
request: request,
text: response.choices[0].message.content!
problemFiles: json.problemFiles,
text: json.text
};
return feedback;
}, async(_) => {
done = true;
const answer = await vscode.window.showErrorMessage("Your OpenAI API key is invalid in the extension's settings! Please correct it before continuing.", "Go To Settings");
if(answer === "Go To Settings") {
vscode.env.openExternal(vscode.Uri.parse("vscode://settings/debuggingAiAssistant.apiKey"));
Expand All @@ -68,8 +173,8 @@ export class OpenAICaller implements APICaller {
}

followUp(response: AIFeedback): Promise<AIFeedback> {
let newRequest: AIRequest = { prompt: "Test" };
let finalResponse: AIFeedback = {request: newRequest, filename: response.filename, line: response.line, text: ""};
let newRequest: AIRequest = {};
let finalResponse: AIFeedback = {request: newRequest, problemFiles: [] };

// TODO: implement this

Expand Down
16 changes: 9 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import * as vscode from "vscode";
import { OpenAICaller } from "./ai/OpenAICaller";
import { initSettings } from "./settings";
import { InlineDiagnostic } from "./extension/InlineDiagnostic";
import { initTerminal, getTerminalOutput } from "./terminal";
import { OpenAICaller } from "./ai/OpenAICaller";

export function activate(context: vscode.ExtensionContext) {
initSettings();
initTerminal();

const callAI = vscode.commands.registerCommand("debuggingAiAssistant.callAI", () => {
let caller: OpenAICaller = new OpenAICaller();
caller.sendRequest({ prompt: "Why is print(123) not working in my file, test.js?" }).then(response => {
vscode.window.showInformationMessage(`Response: ${response.text}`);
});
const askAI = vscode.commands.registerCommand("debuggingAiAssistant.askAI", async () => {
let response = (await new OpenAICaller().sendRequest({ terminalOutput: getTerminalOutput() }))

Check warning on line 12 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test

Missing semicolon

Check warning on line 12 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test

Missing semicolon
if (response !== undefined && response.text !== undefined) {
vscode.window.showInformationMessage(response.text, { modal: true });
}
});

const sendError = vscode.commands.registerCommand("debuggingAiAssistant.sendError", () => {
Expand All @@ -19,7 +21,7 @@
inline.show();
});

context.subscriptions.push(callAI);
context.subscriptions.push(askAI);
context.subscriptions.push(sendError);
}

Expand Down
2 changes: 1 addition & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import OpenAI from "openai";

export let settings: Settings;

export function initSettings() {
export function initSettings(): void {
settings = {
openai: new OpenAI({ apiKey: vscode.workspace.getConfiguration("debuggingAiAssistant").get("apiKey")! })
};
Expand Down
24 changes: 24 additions & 0 deletions src/terminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import vscode from "vscode";
import { Terminal } from "./types/Terminal";
import { LinkedList } from "linked-list-typescript";

const MAX_NUMBER_LINES = 10;
let terminal: Terminal;

export function initTerminal(): void {
terminal = { lines: new LinkedList<string>() };

vscode.window.onDidStartTerminalShellExecution(async (e) => {
const stream = e.execution.read();
for await (const data of stream) {
terminal.lines.append(data);
if(terminal.lines.length > MAX_NUMBER_LINES) {
terminal.lines.removeHead();
}
}
});
}

export function getTerminalOutput(): string {
return terminal.lines.toArray().join("");
}
8 changes: 7 additions & 1 deletion src/test/workspace/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
function init() {

let i = 0;
setInterval(() => {
console.log("Test: " + (i++));
if(i > 5) {
throw new Error("Crashed");
}
}, 1000);
}

init();
1 change: 1 addition & 0 deletions src/test/workspace/test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("This is another test file!");
6 changes: 3 additions & 3 deletions src/types/AIFeedback.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AIRequest } from "./AIRequest";
import { ProblemFile } from "./ProblemFile";

export interface AIFeedback {
request: AIRequest;
filename?: String;
line?: number;
text: string;
problemFiles: ProblemFile[];
text?: string;
}
6 changes: 3 additions & 3 deletions src/types/AIRequest.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProblemFile } from "./ProblemFile";

export interface AIRequest {
prompt: string;
errorMessage?: string; // no clue how this would actually be send format wise. change later.
fileStructure?: string[]; // maybe stored like this, but doesn't give file contents so who knows :D
terminalOutput?: string;
problemFiles?: ProblemFile[]
}
5 changes: 5 additions & 0 deletions src/types/ProblemFile.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ProblemFile {
fileName: string;
fileContent?: string;
line?: number;
}
Loading