Spaces:
Configuration error
Configuration error
| import { writeFile, readFile, mkdirSync } from 'fs'; | |
| import { makeCompartment, lockdown } 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; | |
| this.file_counter = 0; | |
| this.fp = '/bots/'+agent.name+'/action-code/'; | |
| this.code_template = ''; | |
| this.code_lint_template = ''; | |
| readFile('./bots/execTemplate.js', 'utf8', (err, data) => { | |
| if (err) throw err; | |
| this.code_template = data; | |
| }); | |
| readFile('./bots/lintTemplate.js', 'utf8', (err, data) => { | |
| if (err) throw err; | |
| this.code_lint_template = data; | |
| }); | |
| mkdirSync('.' + this.fp, { recursive: true }); | |
| } | |
| async generateCode(agent_history) { | |
| this.agent.bot.modes.pause('unstuck'); | |
| lockdown(); | |
| // this message history is transient and only maintained in this function | |
| let messages = agent_history.getHistory(); | |
| messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'}); | |
| const MAX_ATTEMPTS = 5; | |
| const MAX_NO_CODE = 3; | |
| let code = null; | |
| let no_code_failures = 0; | |
| for (let i=0; i<MAX_ATTEMPTS; i++) { | |
| if (this.agent.bot.interrupt_code) | |
| return null; | |
| const messages_copy = JSON.parse(JSON.stringify(messages)); | |
| let res = await this.agent.prompter.promptCoding(messages_copy); | |
| if (this.agent.bot.interrupt_code) | |
| return null; | |
| let contains_code = res.indexOf('```') !== -1; | |
| if (!contains_code) { | |
| if (res.indexOf('!newAction') !== -1) { | |
| messages.push({ | |
| role: 'assistant', | |
| content: res.substring(0, res.indexOf('!newAction')) | |
| }); | |
| continue; // using newaction will continue the loop | |
| } | |
| if (no_code_failures >= MAX_NO_CODE) { | |
| console.warn("Action failed, agent would not write code."); | |
| return 'Action failed, agent would not write code.'; | |
| } | |
| messages.push({ | |
| role: 'system', | |
| content: 'Error: no code provided. Write code in codeblock in your response. ``` // example ```'} | |
| ); | |
| console.warn("No code block generated. Trying again."); | |
| no_code_failures++; | |
| continue; | |
| } | |
| code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```')); | |
| const result = await this._stageCode(code); | |
| const executionModule = result.func; | |
| const lintResult = await this._lintCode(result.src_lint_copy); | |
| if (lintResult) { | |
| const message = 'Error: Code lint error:'+'\n'+lintResult+'\nPlease try again.'; | |
| console.warn("Linting error:"+'\n'+lintResult+'\n'); | |
| messages.push({ role: 'system', content: message }); | |
| continue; | |
| } | |
| if (!executionModule) { | |
| console.warn("Failed to stage code, something is wrong."); | |
| return 'Failed to stage code, something is wrong.'; | |
| } | |
| try { | |
| console.log('Executing code...'); | |
| await executionModule.main(this.agent.bot); | |
| const code_output = this.agent.actions.getBotOutputSummary(); | |
| const summary = "Agent wrote this code: \n```" + this._sanitizeCode(code) + "```\nCode Output:\n" + code_output; | |
| return summary; | |
| } catch (e) { | |
| if (this.agent.bot.interrupt_code) | |
| return null; | |
| console.warn('Generated code threw error: ' + e.toString()); | |
| console.warn('trying again...'); | |
| const code_output = this.agent.actions.getBotOutputSummary(); | |
| messages.push({ | |
| role: 'assistant', | |
| content: res | |
| }); | |
| messages.push({ | |
| role: 'system', | |
| content: `Code Output:\n${code_output}\nCODE EXECUTION THREW ERROR: ${e.toString()}\n Please try again:` | |
| }); | |
| } | |
| } | |
| return `Code generation failed after ${MAX_ATTEMPTS} attempts.`; | |
| } | |
| async _lintCode(code) { | |
| let result = '#### CODE ERROR INFO ###\n'; | |
| // Extract everything in the code between the beginning of 'skills./world.' and the '(' | |
| const skillRegex = /(?:skills|world)\.(.*?)\(/g; | |
| const skills = []; | |
| let match; | |
| while ((match = skillRegex.exec(code)) !== null) { | |
| skills.push(match[1]); | |
| } | |
| const allDocs = await this.agent.prompter.skill_libary.getAllSkillDocs(); | |
| // check function exists | |
| const missingSkills = skills.filter(skill => !!allDocs[skill]); | |
| if (missingSkills.length > 0) { | |
| result += 'These functions do not exist.\n'; | |
| result += '### FUNCTIONS NOT FOUND ###\n'; | |
| result += missingSkills.join('\n'); | |
| console.log(result) | |
| return result; | |
| } | |
| const eslint = new ESLint(); | |
| const results = await eslint.lintText(code); | |
| const codeLines = code.split('\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`; | |
| } | |
| }); | |
| result += 'The code contains exceptions and cannot continue execution.'; | |
| } 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) { | |
| code = this._sanitizeCode(code); | |
| let src = ''; | |
| code = code.replaceAll('console.log(', 'log(bot,'); | |
| code = code.replaceAll('log("', 'log(bot,"'); | |
| 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`; | |
| } | |
| let src_lint_copy = this.code_lint_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; | |
| } | |
| return { func:{main: mainFn}, src_lint_copy: src_lint_copy }; | |
| } | |
| _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; | |
| } | |
| _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(); | |
| } | |
| }); | |
| }); | |
| } | |
| } |