Spaces:
Configuration error
Configuration error
File size: 9,085 Bytes
76501b9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | 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();
}
});
});
}
} |