mindcraft/src/agent/coder.js

214 lines
8.8 KiB
JavaScript
Raw Normal View History

2024-01-03 22:16:50 -08:00
import { writeFile, readFile, mkdirSync } from 'fs';
2024-06-01 16:05:59 -05:00
import settings from '../../settings.js';
import { makeCompartment } from './library/lockdown.js';
import * as skills from './library/skills.js';
import * as world from './library/world.js';
import { Vec3 } from 'vec3';
import {ESLint} from "eslint";
export class Coder {
constructor(agent) {
this.agent = agent;
2023-11-07 12:46:55 -06:00
this.file_counter = 0;
2023-12-21 15:11:38 -07:00
this.fp = '/bots/'+agent.name+'/action-code/';
this.generating = false;
this.code_template = '';
2024-11-03 03:52:00 +08:00
this.code_chack_template = '';
2023-12-21 15:11:38 -07:00
readFile('./bots/template.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_template = data;
});
2024-11-03 03:52:00 +08:00
readFile('./bots/codeChackTemplate.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_chack_template = data;
});
2023-12-21 20:42:01 -07:00
mkdirSync('.' + this.fp, { recursive: true });
}
async checkCode(code) {
const eslint = new ESLint();
const results = await eslint.lintText(code);
const codeLines = code.split('\n');
let result = '#### CODE ERROR INFO ###\n';
const exceptions = results.map(r => r.messages).flat();
if (exceptions.length > 0) {
exceptions.forEach((exc, index) => {
if (exc.line && exc.column ) {
const errorLine = codeLines[exc.line - 1]?.trim() || 'Unable to retrieve error line content';
result += `#ERROR ${index + 1}\n`;
result += `Message: ${exc.message}\n`;
result += `Location: Line ${exc.line}, Column ${exc.column}\n`;
result += `Related Code Line: ${errorLine}\n\n`;
}
});
result += 'The code contains exceptions and cannot continue execution.\n';
} else {
return null;//no error
}
return result ;
}
// write custom code to file and import it
// write custom code to file and prepare for evaluation
async stageCode(code) {
2024-02-16 11:57:48 -06:00
code = this.sanitizeCode(code);
let src = '';
code = code.replaceAll('console.log(', 'log(bot,');
code = code.replaceAll('log("', 'log(bot,"');
2024-04-13 22:56:18 -05:00
console.log(`Generated code: """${code}"""`);
// this may cause problems in callback functions
code = code.replaceAll(';\n', '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n');
for (let line of code.split('\n')) {
src += ` ${line}\n`;
}
2024-11-03 03:52:00 +08:00
let src_check_copy = this.code_chack_template.replace('/* CODE HERE */', src);
src = this.code_template.replace('/* CODE HERE */', src);
let filename = this.file_counter + '.js';
// if (this.file_counter > 0) {
// let prev_filename = this.fp + (this.file_counter-1) + '.js';
// unlink(prev_filename, (err) => {
// console.log("deleted file " + prev_filename);
// if (err) console.error(err);
// });
// } commented for now, useful to keep files for debugging
this.file_counter++;
let write_result = await this.writeFilePromise('.' + this.fp + filename, src);
// This is where we determine the environment the agent's code should be exposed to.
// It will only have access to these things, (in addition to basic javascript objects like Array, Object, etc.)
// Note that the code may be able to modify the exposed objects.
const compartment = makeCompartment({
skills,
log: skills.log,
world,
Vec3,
});
const mainFn = compartment.evaluate(src);
if (write_result) {
console.error('Error writing code execution file: ' + result);
return null;
}
2024-11-03 03:52:00 +08:00
return { func:{main: mainFn}, src_check_copy: src_check_copy };
}
2024-02-16 11:57:48 -06:00
sanitizeCode(code) {
code = code.trim();
const remove_strs = ['Javascript', 'javascript', 'js']
for (let r of remove_strs) {
if (code.startsWith(r)) {
code = code.slice(r.length);
return code;
}
}
return code;
}
2023-11-07 12:46:55 -06:00
writeFilePromise(filename, src) {
// makes it so we can await this function
return new Promise((resolve, reject) => {
writeFile(filename, src, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
2024-01-03 22:16:50 -08:00
async generateCode(agent_history) {
// wrapper to prevent overlapping code generation loops
2024-11-03 12:03:12 -05:00
await this.agent.actions.stop();
this.generating = true;
2024-01-26 14:10:09 -08:00
let res = await this.generateCodeLoop(agent_history);
this.generating = false;
2024-01-26 14:10:09 -08:00
if (!res.interrupted) this.agent.bot.emit('idle');
2024-02-16 11:57:48 -06:00
return res.message;
}
async generateCodeLoop(agent_history) {
2024-10-16 19:50:50 -05:00
this.agent.bot.modes.pause('unstuck');
let messages = agent_history.getHistory();
messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'});
2024-01-03 22:16:50 -08:00
2024-08-22 15:57:20 -05:00
let code = null;
let code_return = null;
let failures = 0;
2024-04-05 16:58:08 -05:00
const interrupt_return = {success: true, message: null, interrupted: true, timedout: false};
for (let i=0; i<5; i++) {
if (this.agent.bot.interrupt_code)
2024-04-05 16:58:08 -05:00
return interrupt_return;
console.log(messages)
let res = await this.agent.prompter.promptCoding(JSON.parse(JSON.stringify(messages)));
2024-04-05 16:58:08 -05:00
if (this.agent.bot.interrupt_code)
return interrupt_return;
let contains_code = res.indexOf('```') !== -1;
if (!contains_code) {
2024-03-23 11:15:53 -05:00
if (res.indexOf('!newAction') !== -1) {
messages.push({
role: 'assistant',
content: res.substring(0, res.indexOf('!newAction'))
});
continue; // using newaction will continue the loop
}
2024-08-22 15:57:20 -05:00
2024-10-12 20:39:00 -05:00
if (failures >= 3) {
return { success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false };
}
messages.push({
role: 'system',
content: 'Error: no code provided. Write code in codeblock in your response. ``` // example ```'}
);
failures++;
continue;
}
2024-08-22 15:57:20 -05:00
code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
const result = await this.stageCode(code);
const executionModuleExports = result.func;
if (!executionModuleExports) {
let src_check_copy = result.src_check_copy;
2024-11-03 03:52:00 +08:00
const analysisResult = await this.checkCode(src_check_copy);
if (analysisResult) {
const message = 'Error: Code syntax error. Please try again:'+'\n'+analysisResult+'\n'+await this.agent.prompter.getRelevantSkillDocs(analysisResult,3);
messages.push({ role: 'system', content: message });
continue;
}
agent_history.add('system', 'Failed to stage code, something is wrong.');
2024-01-26 14:10:09 -08:00
return {success: false, message: null, interrupted: false, timedout: false};
}
2024-10-12 20:39:00 -05:00
2024-11-03 12:03:12 -05:00
code_return = await this.agent.actions.runAction('newAction', async () => {
return await executionModuleExports.main(this.agent.bot);
}, { timeout: settings.code_timeout_mins });
if (code_return.interrupted && !code_return.timedout)
return { success: false, message: null, interrupted: true, timedout: false };
2024-02-16 11:57:48 -06:00
console.log("Code generation result:", code_return.success, code_return.message);
2024-01-03 22:16:50 -08:00
2024-08-22 15:57:20 -05:00
if (code_return.success) {
const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message;
return { success: true, message: summary, interrupted: false, timedout: false };
2024-08-22 15:57:20 -05:00
}
2024-01-03 22:16:50 -08:00
messages.push({
role: 'assistant',
content: res
});
messages.push({
role: 'system',
2024-08-22 15:57:20 -05:00
content: code_return.message + '\nCode failed. Please try again:'
2024-01-03 22:16:50 -08:00
});
}
return { success: false, message: null, interrupted: false, timedout: true };
2023-11-07 22:23:42 -06:00
}
//err = err.toString();
// let relevant_skill_docs = await this.agent.prompter.getRelevantSkillDocs(err,5);
// let message = this.formatOutput(this.agent.bot) + '!!Code threw exception!! Error: ' + err+'\n'+relevant_skill_docs;
//
}