forked from EresDevOrg/personal-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0eebccc
commit a35576a
Showing
8 changed files
with
446 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import OpenAI from "openai"; | ||
import { Context } from "../../../types/context"; | ||
import { SuperOpenAi } from "./openai"; | ||
|
||
const sysMsg = `You are a capable AI assistant currently running on a GitHub bot. | ||
You are designed to assist with repository maintenance, code reviews, and issue resolution. | ||
You have access to the following tools: | ||
- read_file: Read contents of a file in the repository | ||
- write_file: Write/update contents to a file | ||
- terminal: Execute terminal commands | ||
- test_code: Run tests for the codebase | ||
Always be professional, concise, and follow these rules: | ||
1. Before making changes, understand the context fully | ||
2. When modifying code, ensure it maintains existing functionality | ||
3. Follow the project's coding style and conventions | ||
4. Document any significant changes | ||
5. Consider edge cases and error handling`; | ||
|
||
export class Completions extends SuperOpenAi { | ||
protected context: Context; | ||
protected model: string; | ||
protected maxTokens: number; | ||
|
||
constructor(client: OpenAI, context: Context) { | ||
super(client, context); | ||
this.context = context; | ||
this.model = "claude/sonnet"; | ||
this.maxTokens = 100; | ||
} | ||
|
||
async createCompletion(prompt: string) { | ||
const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ | ||
model: this.model, | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: [ | ||
{ | ||
type: "text", | ||
text: sysMsg, | ||
}, | ||
], | ||
}, | ||
{ | ||
role: "user", | ||
content: [ | ||
{ | ||
type: "text", | ||
text: prompt, | ||
}, | ||
], | ||
}, | ||
], | ||
temperature: 0.2, | ||
max_tokens: this.maxTokens, | ||
top_p: 0.5, | ||
frequency_penalty: 0, | ||
presence_penalty: 0, | ||
response_format: { | ||
type: "text", | ||
}, | ||
}); | ||
|
||
return res; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { OpenAI } from "openai"; | ||
import { Context } from "../../../types/context"; | ||
|
||
export class SuperOpenAi { | ||
protected client: OpenAI; | ||
protected context: Context; | ||
constructor(client: OpenAI, context: Context) { | ||
this.client = client; | ||
this.context = context; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,28 @@ | ||
import { ExploreDir } from "../tools/explore-dir"; | ||
import { Context } from "../types"; | ||
import { sayHello } from "./say-hello"; | ||
|
||
/** | ||
* NOTICE: Remove this file or use it as a template for your own plugins. | ||
* | ||
* This encapsulates the logic for a plugin if the only thing it does is say "Hello, world!". | ||
* | ||
* Try it out by running your local kernel worker and running the `yarn worker` command. | ||
* Comment on an issue in a repository where your GitHub App is installed and see the magic happen! | ||
* | ||
* Logger examples are provided to show how to log different types of data. | ||
*/ | ||
export async function delegate(context: Context) { | ||
const { logger, payload, octokit } = context; | ||
|
||
const sender = payload.comment.user?.login; | ||
const { logger, payload } = context; | ||
const body = payload.comment.body; | ||
const repo = payload.repository.name; | ||
const issueNumber = payload.issue.number; | ||
const owner = payload.repository.owner.login; | ||
const body = payload.comment.body; | ||
|
||
logger.debug(`Executing decideHandler:`, { sender, repo, issueNumber, owner }); | ||
|
||
const targetUser = body.match(/^\/\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))/i); | ||
if (!targetUser) { | ||
logger.error(`Missing target username from comment: ${body}`); | ||
return; | ||
} | ||
const personalAgentOwner = targetUser[0].replace("/@", ""); | ||
|
||
logger.info(`Comment received:`, { owner, personalAgentOwner, comment: body }); | ||
|
||
let reply; | ||
|
||
if (body.match(/^\/\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))\s+say\s+hello/i)) { | ||
reply = sayHello(); | ||
} else { | ||
reply = "I could not understand your comment to give you a quick response. I will get back to you later."; | ||
logger.error(`Invalid command.`, { body }); | ||
} | ||
|
||
const replyWithQuote = ["> ", `${body}`, "\n\n", reply].join(""); | ||
const issueNumber = payload.issue.number; | ||
|
||
try { | ||
await octokit.issues.createComment({ | ||
owner: payload.repository.owner.login, | ||
repo: payload.repository.name, | ||
issue_number: payload.issue.number, | ||
body: replyWithQuote, | ||
}); | ||
} catch (error) { | ||
/** | ||
* logger.fatal should not be used in 9/10 cases. Use logger.error instead. | ||
* | ||
* Below are examples of passing error objects to the logger, only one is needed. | ||
*/ | ||
if (error instanceof Error) { | ||
logger.error(`Error creating comment:`, { error: error, stack: error.stack }); | ||
throw error; | ||
} else { | ||
logger.error(`Error creating comment:`, { err: error, error: new Error() }); | ||
throw error; | ||
} | ||
// Check if the comment is requesting to solve the issue | ||
if (body.toLowerCase().includes("solve this issue")) { | ||
// Initialize LLM tools | ||
const explore = new ExploreDir(); | ||
// Get the current directory tree | ||
let tree = await explore.current_dir_tree(); | ||
// Log the tree | ||
logger.info(tree); | ||
// Clone the repository and get the file tree | ||
await explore.clone_repo(repo, owner, issueNumber); | ||
// Log the tree again | ||
tree = await explore.current_dir_tree(); | ||
logger.info(tree); | ||
} | ||
|
||
logger.ok(`Comment created: ${reply}`); | ||
logger.verbose(`Exiting decideHandler`); | ||
logger.ok(`Comment processed: ${body}`); | ||
logger.verbose(`Exiting delegate`); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { commonCallBack, Terminal } from "../terminal"; | ||
|
||
export class ExploreDir { | ||
private _shellInterface: Terminal; | ||
|
||
constructor() { | ||
this._shellInterface = new Terminal(commonCallBack("stdout"), commonCallBack("stderr"), commonCallBack("exit")); | ||
} | ||
|
||
current_dir_tree(): Promise<string> { | ||
return new Promise((resolve, reject) => { | ||
let output = ""; | ||
let isCompleted = false; | ||
|
||
this._shellInterface.runCommand("ls -R"); | ||
|
||
const stdout = this._shellInterface.outputOnStdout(); | ||
if (!stdout) { | ||
reject(new Error("No bash instance running")); | ||
return; | ||
} | ||
stdout.on("data", (data: string) => { | ||
output += data.toString(); | ||
if (isCompleted) { | ||
resolve(output); | ||
} | ||
}); | ||
|
||
this._shellInterface.hasCommandCompleted(); | ||
stdout.on("data", (data: string) => { | ||
const exitCode = parseInt(data.toString().trim()); | ||
if (exitCode === 0) { | ||
isCompleted = true; | ||
if (output) { | ||
resolve(output); | ||
} | ||
} else { | ||
reject(new Error(`Command failed with exit code ${exitCode}`)); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
clone_repo(repo: string, owner: string, issueNumber: number): Promise<string> { | ||
return new Promise((resolve, reject) => { | ||
let output = ""; | ||
let isCompleted = false; | ||
const tmpDir = `/tmp/repo-${owner}-${repo}-${issueNumber}`; | ||
|
||
this._shellInterface.runCommand(`git clone [email protected]:${owner}/${repo}.git ${tmpDir}`); | ||
|
||
const stdout = this._shellInterface.outputOnStdout(); | ||
if (!stdout) { | ||
reject(new Error("No bash instance running")); | ||
return; | ||
} | ||
stdout.on("data", (data: string) => { | ||
output += data.toString(); | ||
if (isCompleted) { | ||
resolve(output); | ||
} | ||
}); | ||
|
||
this._shellInterface.hasCommandCompleted(); | ||
stdout.on("data", (data: string) => { | ||
const exitCode = parseInt(data.toString().trim()); | ||
if (exitCode === 0) { | ||
isCompleted = true; | ||
if (output) { | ||
resolve(output); | ||
} | ||
} else { | ||
reject(new Error(`Git clone failed with exit code ${exitCode}`)); | ||
} | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { ChildProcessWithoutNullStreams, spawn } from "child_process"; | ||
|
||
const BASH_ERR_MSG = "No bash instance running."; | ||
|
||
export class Terminal { | ||
private _process: ChildProcessWithoutNullStreams | null; | ||
private _onStdout: (data: string) => void; | ||
private _onStderr: (data: string) => void; | ||
private _onClose: (code: string) => void; | ||
|
||
constructor(onStdout: (data: string) => void, onStderr: (data: string) => void, onClose: (code: string) => void) { | ||
this._process = null; | ||
this.start(); | ||
this._onStdout = onStdout; | ||
this._onStderr = onStderr; | ||
this._onClose = onClose; | ||
} | ||
|
||
// Method to start a bash instance | ||
start() { | ||
this._process = spawn("bash", [], { | ||
stdio: ["pipe", "pipe", "pipe"], | ||
}); | ||
|
||
this._process.stdout.on("data", (data: string) => { | ||
this._onStdout(data); | ||
}); | ||
|
||
this._process.stderr.on("data", (data: string) => { | ||
this._onStderr(data); | ||
}); | ||
|
||
this._process.on("close", (code: string) => { | ||
this._onClose(code); | ||
}); | ||
} | ||
|
||
// Method to run a command in the bash instance | ||
runCommand(command: string) { | ||
if (this._process) { | ||
this._process.stdin.write(`${command}\n`); | ||
} else { | ||
console.error(BASH_ERR_MSG); | ||
} | ||
} | ||
|
||
hasCommandCompleted() { | ||
if (this._process) { | ||
this._process.stdin.write("echo $?"); | ||
} else { | ||
console.error(BASH_ERR_MSG); | ||
} | ||
} | ||
|
||
outputOnStdout() { | ||
if (this._process) { | ||
return this._process.stdout; | ||
} else { | ||
console.error(BASH_ERR_MSG); | ||
} | ||
} | ||
|
||
// Method to kill the bash instance | ||
kill() { | ||
if (this._process) { | ||
this._process.kill(); | ||
this._process = null; | ||
console.log("Bash instance killed."); | ||
} else { | ||
console.error(BASH_ERR_MSG); | ||
} | ||
} | ||
} | ||
|
||
export function commonCallBack(id: string) { | ||
return (data: string) => { | ||
console.log(`Terminal ${id} stdout: ${data}`); | ||
}; | ||
} | ||
|
||
export class TerminalManager { | ||
private _terminals: Map<string, Terminal>; | ||
constructor() { | ||
this._terminals = new Map(); | ||
this.init(); | ||
} | ||
|
||
init() { | ||
console.log("TerminalManager initialized"); | ||
} | ||
|
||
// Method to create a new terminal instance | ||
createTerminal(id: string) { | ||
if (this._terminals.has(id)) { | ||
console.error(`Terminal with id ${id} already exists.`); | ||
return; | ||
} | ||
const terminal = new Terminal(commonCallBack(id), commonCallBack(id), commonCallBack(id)); | ||
this._terminals.set(id, terminal); | ||
terminal.start(); | ||
console.log(`Terminal with id ${id} created.`); | ||
} | ||
|
||
// Method to run a command in a specific terminal instance | ||
runCommandInTerminal(id: string, command: string) { | ||
const terminal = this._terminals.get(id); | ||
if (terminal) { | ||
terminal.runCommand(command); | ||
} else { | ||
console.error(`Terminal with id ${id} does not exist.`); | ||
} | ||
} | ||
|
||
// Method to kill a specific terminal instance | ||
killTerminal(id: string) { | ||
const terminal = this._terminals.get(id); | ||
if (terminal) { | ||
terminal.kill(); | ||
this._terminals.delete(id); | ||
console.log(`Terminal with id ${id} killed.`); | ||
} else { | ||
console.error(`Terminal with id ${id} does not exist.`); | ||
} | ||
} | ||
} |
Oops, something went wrong.