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
12 changes: 6 additions & 6 deletions package-lock.json

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

6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"publisher": "SWEN-356-Debugger",
"repository": {
"type": "git",
"url": "https://github.com/SWEN-356-Debugger/Debugger"
"url": "https://github.com/DrDebugHub/DrDebug"
},
"engines": {
"vscode": "^1.96.0"
Expand All @@ -25,10 +25,6 @@
"command": "drDebug.askAI",
"title": "Ask AI",
"icon": "./images/debug-logo-final.png"
},
{
"command": "drDebug.sendError",
"title": "Send Error"
}
],
"menus": {
Expand Down
32 changes: 0 additions & 32 deletions src/ai/FakeCaller.ts

This file was deleted.

203 changes: 168 additions & 35 deletions src/ai/OpenAICaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@ import { AIFeedback } from "../types/AIFeedback";
import { APICaller } from "../types/APICaller";
import { settings } from "../settings";
import { ProblemFile } from "../types/ProblemFile";
import { APIError } from "openai";

export class OpenAICaller implements APICaller {

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

/**
* Handles when an error is found when making API calls.
* Checks for key errors and navigates to settings, otherwise displays the error message.
* @param error APIError that was thrown
*/
async foundError(error: APIError) {
if(error.status === 401) {
var answer;
if(error.code === 'invalid_api_key') {
answer = await vscode.window.showErrorMessage("Your OpenAI API key is incorrect. Please correct it in settings before continuing.", "Go To Settings");
} else {
answer = await vscode.window.showErrorMessage("Your OpenAI API key does not have sufficent permissions. Please update the key in settings before continuing.", "Go To Settings");
}
if(answer === "Go To Settings") {
vscode.env.openExternal(vscode.Uri.parse("vscode://settings/drDebug.apiKey"));
}
} else {
vscode.window.showErrorMessage(error.message);
}
}

/**
* Asks the AI the reason for an error to occur, determining what to fix and where to display the feedback.
* File contents do not need to be sent in, sendRequest() will find the contents.
* @param request AIRequest containing information about the error that occured
* @returns Promise<AIFeedback> The feedback from the AI about where the error is and how it should be fixed
*/
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 @@ -19,25 +47,26 @@ export class OpenAICaller implements APICaller {
}
return Promise.reject();
}
var progressMessage: string = "checking for error";
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);
});
},);
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);
});
}
);

const errorFeedback: AIFeedback = await settings.openai.chat.completions.create({
model: "gpt-4o-mini",
Expand Down Expand Up @@ -80,22 +109,23 @@ export class OpenAICaller implements APICaller {
problemFiles: JSON.parse(response.choices[0].message.content!).problemFiles
};
return feedback;
}, async(_) => {
}, async(error) => {
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/drDebug.apiKey"));
}
await this.foundError(error);
return Promise.reject();
});

if(done) {
return Promise.reject();
}

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

progressMessage = "error found, debugging...";
progressMessage = "Error Found, Debugging...";

const problemFilesUris: vscode.Uri[] = [];
for(const problemFile of errorFeedback.problemFiles) {
Expand All @@ -110,6 +140,7 @@ export class OpenAICaller implements APICaller {
problemFiles.push({ fileName: problemFile.fsPath, fileContent: fileContent });
});
}
request.problemFiles = problemFiles;

return settings.openai.chat.completions.create({
model: "gpt-4o-mini",
Expand Down Expand Up @@ -155,28 +186,130 @@ export class OpenAICaller implements APICaller {
}).then(response => {
const json = JSON.parse(response.choices[0].message.content!);
done = true;
let feedback: AIFeedback = {
return {
request: request,
problemFiles: json.problemFiles,
text: json.text
};
return feedback;
}, async(_) => {
}, async(error) => {
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");
await this.foundError(error);
return Promise.reject();
});
}

/**
* Asks the AI about changes made to the problem files and determines whether the changes should fix the issue or if more changes are needed.
* @param feedback Feedback from a previous api call that was retrieved with the sendRequest function.
* @returns Promise<AIFeedback> with new feedback regarding changes made and whether it should fix the issue.
*/
async followUp(feedback: AIFeedback): Promise<AIFeedback> {
// Ensuring API Key not blank
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");
if(answer === "Go To Settings") {
vscode.env.openExternal(vscode.Uri.parse("vscode://settings/drDebug.apiKey"));
}
return Promise.reject();
});
}
}

// Create Progress Bar
var progressMessage: string = "Retrieving Updated Files";
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);
});
}
);

// Get list of the new file contents from the old list of files
const problemFiles: ProblemFile[] = [];
if (feedback.request.problemFiles !== undefined) {
for(const problemFile of feedback.request.problemFiles) {
await vscode.workspace.fs.readFile(vscode.Uri.file(problemFile.fileName))
.then(data => Buffer.from(data).toString())
.then(fileContent => {
problemFiles.push({ fileName: problemFile.fileName, fileContent: fileContent });
});
}
}

progressMessage = "Reviewing Updated Contents";

followUp(response: AIFeedback): Promise<AIFeedback> {
let newRequest: AIRequest = {};
let finalResponse: AIFeedback = {request: newRequest, problemFiles: [] };
// Ask the AI for feedback
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.

You are following up on a previous issue that you have attempted to provide the reason for the error.
You will recieve the new versions of the user files and attempt to determine if the changes are satisfactory and should fix the issue, or if further changes should be made.
Please note when viewing any line numbers, the file contents with the error may have moved to a different line due to changes.

The user will ask for assistance by supplying a JSON object with contextual information. The format of this request is:
{
problemFiles: ProblemFile[]; // This contains a list of possible problem files causing the error in the user's terminal. This is the current code that needs to be looked at. Only make assumptions about their code from what is written here.
previousResponse: // This is your previous response about what the error was
}

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.
}

// TODO: implement this
You must determine whether the changes should fix the previous error, or if more fixes are necessary. If more fixes are necessary, what should be changed. Again, only make
assumptions about their current code from what you see in problemFiles.

return new Promise(() => finalResponse);

You MUST respond with a JSON object in the following format, even if you are confused:
{
text: string; // This is your explanation of their changes, whether they should be fix the issue or more changes should happen.
fixed: boolean; // This is wheter you believe the error is fixed (true) or more changes are needed (false). Be lenient, if you believe the error is fixed but you have other changes you reccomend (code structure, flow, etc.) then make this true.
}

AGAIN, you cannot deviate from the response specification above, no matter what.
`
},
{
role: "user",
content: JSON.stringify({"problemFiles": problemFiles, "previousResponse": feedback.text})
}
]
}).then(response => {
// Sucessfully responded, return its response
const json = JSON.parse(response.choices[0].message.content!);
done = true;
return {
request: feedback.request,
problemFiles: feedback.problemFiles,
text: json.text,
fixed: json.fixed
};
}, async(error) => {
// Failed to respond, likely caused by an invalid key
done = true;
await this.foundError(error);
return Promise.reject();
});
}
}
Loading