nohandcoder
Version:
An AI agent for code editing, searching, and project analysis
185 lines (179 loc) • 8.04 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoHandCoderAgent = void 0;
const openai_1 = __importDefault(require("openai"));
const tools_1 = require("../tools");
class NoHandCoderAgent {
constructor(workspaceRoot) {
this.openai = new openai_1.default({
apiKey: process.env.OPENAI_API_KEY,
});
// Initialize tools
this.tools = [
new tools_1.ReadFileTool(workspaceRoot),
new tools_1.SearchFilesTool(workspaceRoot),
new tools_1.AnalyzeProjectTool(workspaceRoot),
new tools_1.ExecuteCommandTool(workspaceRoot),
new tools_1.EditFileTool(workspaceRoot),
].map((tool) => ({
instance: tool,
definition: tool.getDefinition(),
}));
// Initialize conversation history
this.conversationHistory = [];
}
getTools() {
return this.tools.map((tool) => tool.definition);
}
async getSystemPrompt() {
return `You are an AI coding assistant with access to various tools to help with coding tasks in the current workspace.
Your task is to help users by following these steps in order:
1. First, analyze the project structure and dependencies using the AnalyzeProjectTool to understand the codebase context
2. Search for relevant files and folders using SearchFilesTool based on the user's request
3. Read the identified files using ReadFileTool to understand the code that needs to be modified
4. Make necessary edits using EditFileTool according to user requirements
5. Execute any required commands using ExecuteCommandTool if needed
For each action you take:
1. Explain why you are using each tool and what information you expect to gather
2. Show the results and explain your findings
3. Describe your planned next steps based on the information gathered
4. Get user confirmation before making any file changes
Remember to:
- Always use the AnalyzeProjectTool first to understand the codebase context
- Keep track of all files you've examined and changes you've made
- Verify that proposed changes are consistent with the existing codebase
- Consider dependencies and potential side effects of changes
- Ask for clarification if the user's request is ambiguous
- Explain your reasoning for suggested changes
- Provide code examples to illustrate your suggestions
- Maintain context from the conversation history
Always be thorough in your analysis but concise in your responses. Focus on completing the task systematically while keeping the user informed of your progress.`;
}
async executeToolCall(toolCall) {
const tool = this.tools.find((t) => t.definition.function.name === toolCall.function.name);
if (!tool) {
throw new Error(`Tool ${toolCall.function.name} not found`);
}
const args = JSON.parse(toolCall.function.arguments);
return await tool.instance.execute(args);
}
async streamResponse(stream, onStream) {
let content = "";
const toolCalls = [];
for await (const chunk of stream) {
const deltaContent = chunk.choices[0]?.delta?.content || "";
const deltaToolCalls = chunk.choices[0]?.delta?.tool_calls || [];
if (deltaContent) {
content += deltaContent;
if (onStream) {
onStream(deltaContent);
}
else {
process.stdout.write(deltaContent);
}
}
// Handle tool calls
for (const toolCall of deltaToolCalls) {
if (toolCall.index !== undefined) {
if (!toolCalls[toolCall.index]) {
toolCalls[toolCall.index] = {
id: toolCall.id || "",
type: "function",
function: {
name: toolCall.function?.name || "",
arguments: toolCall.function?.arguments || "",
},
};
}
else if (toolCall.function?.arguments) {
toolCalls[toolCall.index].function.arguments +=
toolCall.function.arguments;
}
}
}
}
return { content, toolCalls };
}
async executeToolCalls(toolCalls) {
try {
return await Promise.all(toolCalls.map((toolCall) => this.executeToolCall(toolCall)));
}
catch (error) {
console.error("Error executing tool calls:", error);
throw new Error(`Failed to execute tool calls: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
async getModelResponse(messages, tools, onStream) {
const stream = await this.openai.chat.completions.create({
model: process.env.MODEL_NAME || "gpt-4o-mini",
messages: messages,
...(tools && { tools, tool_choice: "auto" }),
stream: true,
});
return this.streamResponse(stream, onStream);
}
async handleUserInput(userInput, onStream) {
try {
const systemPrompt = await this.getSystemPrompt();
const tools = this.getTools();
// Add user message to history
this.conversationHistory.push({ role: "user", content: userInput });
let fullResponse = "";
let currentMessages = [
{ role: "system", content: systemPrompt },
...this.conversationHistory,
];
while (true) {
// Get response from model
const { content: modelContent, toolCalls } = await this.getModelResponse(currentMessages, tools, onStream);
fullResponse += modelContent;
// If no tool calls, we're done
if (!toolCalls || toolCalls.length === 0) {
break;
}
// Execute tool calls
const toolResults = await this.executeToolCalls(toolCalls);
// Add tool calls and results to conversation history
this.conversationHistory.push({
role: "assistant",
content: modelContent,
tool_calls: toolCalls,
});
// Add responses for each tool call
toolCalls.forEach((toolCall, index) => {
this.conversationHistory.push({
role: "tool",
content: JSON.stringify(toolResults[index]),
tool_call_id: toolCall.id,
});
});
// Update current messages for next iteration
currentMessages = [
{ role: "system", content: systemPrompt },
...this.conversationHistory,
];
}
// Add final assistant's response to history
this.conversationHistory.push({
role: "assistant",
content: fullResponse,
});
// Limit conversation history to prevent memory issues
if (this.conversationHistory.length > 20) {
this.conversationHistory = [
{ role: "system", content: systemPrompt },
...this.conversationHistory.slice(-19),
];
}
return fullResponse;
}
catch (error) {
console.error("Error in handleUserInput:", error);
throw new Error(`Failed to handle user input: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
}
exports.NoHandCoderAgent = NoHandCoderAgent;
;