2024-01-03 22:16:50 -08:00
import { writeFile , readFile , mkdirSync } from 'fs' ;
import { sendRequest , embed , cosineSimilarity } from '../utils/gpt.js' ;
import { stringifyTurns } from '../utils/text.js' ;
2023-11-07 12:00:55 -06:00
export class Coder {
constructor ( agent ) {
this . agent = agent ;
2023-11-15 17:00:02 -06:00
this . queued _code = '' ;
2023-11-07 12:00:55 -06:00
this . current _code = '' ;
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/' ;
2023-11-13 11:09:37 -06:00
this . agent . bot . interrupt _code = false ;
2023-11-13 00:57:20 -06:00
this . executing = false ;
2023-11-12 13:57:22 -06:00
this . agent . bot . output = '' ;
this . code _template = '' ;
2023-12-06 22:15:09 -06:00
this . timedout = false ;
2024-01-03 22:16:50 -08:00
this . fewshot = 3 ;
this . examples = [ ] ;
2023-11-12 13:57:22 -06:00
2023-12-21 15:11:38 -07:00
readFile ( './bots/template.js' , 'utf8' , ( err , data ) => {
2023-11-12 13:57:22 -06:00
if ( err ) throw err ;
this . code _template = data ;
} ) ;
2023-12-21 20:42:01 -07:00
mkdirSync ( '.' + this . fp , { recursive : true } ) ;
2023-11-07 12:00:55 -06:00
}
queueCode ( code ) {
2023-11-15 17:00:02 -06:00
this . queued _code = this . santitizeCode ( code ) ;
2023-11-08 19:24:24 -06:00
}
santitizeCode ( code ) {
const remove _strs = [ '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:00:55 -06:00
}
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 loadExamples ( ) {
let examples = [ ] ;
try {
const data = readFileSync ( './src/examples.json' , 'utf8' ) ;
examples = JSON . parse ( data ) ;
} catch ( err ) {
console . log ( 'No history examples found.' ) ;
}
this . examples = [ ] ;
for ( let example of examples ) {
let context = '' ;
for ( let turn of example . conversation ) {
context += turn . content + '\n' ;
}
context = context . trim ( ) ;
const embedding = await embed ( context ) ;
this . examples . push ( { 'embedding' : embedding , 'turns' : example } ) ;
}
await this . setExamples ( ) ;
}
async sortExamples ( messages ) {
let context = '' ;
for ( let turn of messages ) {
context += turn . content + '\n' ;
}
context = context . trim ( ) ;
const embedding = await embed ( context ) ;
this . examples . sort ( ( a , b ) => {
return cosineSimilarity ( a . embedding , embedding ) - cosineSimilarity ( b . embedding , embedding ) ;
} ) ;
}
async generateCode ( agent _history ) {
let system _message = "You are a minecraft bot that plays minecraft by writing javascript. Given the conversation between you and the user, use the provided skills and world queries to write your code. You will then be given a response to your code. If you are satisfied with the response, return output without writing any additional code. If you want to try again, output the code you want to try." ;
system _message += getSkillDocs ( ) ;
let messages = [ ] ;
this . sortExamples ( agent _history . turns ) ;
for ( let example of this . examples . slice ( - this . fewshot ) ) {
messages . push ( {
role : 'user' ,
content : stringifyTurns ( example . conversation )
} ) ;
for ( let i = 0 ; i < example . coder . length ; i ++ ) {
messages . push ( {
role : i % 2 == 0 ? 'assistant' : 'user' ,
content : example . coder [ i ]
} ) ;
}
}
messages . push ( {
role : 'user' ,
content : stringifyTurns ( agent _history . turns ) ,
} ) ;
let final _message = 'No code generated.' ;
for ( let i = 0 ; i < 5 ; i ++ ) {
let res = await sendRequest ( messages , system _message ) ;
let code = res . substring ( res . indexOf ( '```' ) + 3 , res . lastIndexOf ( '```' ) ) ;
if ( ! code )
break ;
agent . coder . queueCode ( code ) ;
let code _return = await agent . coder . execute ( ) ;
if ( code _return . interrupted && ! custom _return . timedout )
break ;
messages . push ( {
role : 'assistant' ,
content : res
} ) ;
messages . push ( {
role : 'user' ,
content : code _return . message
} ) ;
final _message = code _return . message ;
}
return final _message ;
}
2023-11-13 00:57:20 -06:00
2023-12-08 16:18:20 -06:00
// returns {success: bool, message: string, interrupted: bool, timedout: false}
2023-11-07 12:00:55 -06:00
async execute ( ) {
2023-12-06 22:15:09 -06:00
if ( ! this . queued _code ) return { success : false , message : "No code to execute." , interrupted : false , timedout : false } ;
if ( ! this . code _template ) return { success : false , message : "Code template not loaded." , interrupted : false , timedout : false } ;
2023-11-12 13:57:22 -06:00
let src = '' ;
2023-12-04 21:33:40 -06:00
let code = this . queued _code ;
code = code . replaceAll ( 'console.log(' , 'log(bot,' ) ;
code = code . replaceAll ( 'log("' , 'log(bot,"' ) ;
2023-11-15 17:00:02 -06:00
// this may cause problems in callback functions
2023-12-04 21:33:40 -06:00
code = code . replaceAll ( ';\n' , '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n' ) ;
2023-11-15 17:00:02 -06:00
for ( let line of code . split ( '\n' ) ) {
2023-11-07 12:00:55 -06:00
src += ` ${ line } \n ` ;
}
2023-11-12 13:57:22 -06:00
src = this . code _template . replace ( '/* CODE HERE */' , src ) ;
2023-11-07 12:00:55 -06:00
console . log ( "writing to file..." , src )
2023-11-07 12:46:55 -06:00
2023-12-21 15:11:38 -07:00
let filename = this . file _counter + '.js' ;
2023-11-08 19:24:24 -06:00
// 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
2023-11-07 12:46:55 -06:00
this . file _counter ++ ;
2023-12-21 15:11:38 -07:00
let write _result = await this . writeFilePromise ( '.' + this . fp + filename , src )
2023-11-07 12:00:55 -06:00
2023-11-08 19:24:24 -06:00
if ( write _result ) {
2023-11-07 12:46:55 -06:00
console . error ( 'Error writing code execution file: ' + result ) ;
2023-12-06 22:15:09 -06:00
return { success : false , message : result , interrupted : false , timedout : false } ;
2023-11-07 12:46:55 -06:00
}
2023-12-10 20:19:07 -06:00
let TIMEOUT ;
2023-11-07 12:46:55 -06:00
try {
console . log ( 'executing code...\n' ) ;
2023-12-21 15:11:38 -07:00
let execution _file = await import ( '../..' + this . fp + filename ) ;
2023-11-13 00:57:20 -06:00
await this . stop ( ) ;
2023-11-15 17:00:02 -06:00
this . current _code = this . queued _code ;
2023-11-08 21:05:18 -08:00
this . executing = true ;
2023-12-12 13:39:35 -06:00
TIMEOUT = this . _startTimeout ( 10 ) ;
2023-12-06 22:15:09 -06:00
await execution _file . main ( this . agent . bot ) ; // open fire
2023-11-08 21:05:18 -08:00
this . executing = false ;
2023-12-10 20:19:07 -06:00
clearTimeout ( TIMEOUT ) ;
2023-12-09 21:40:53 -06:00
2023-11-13 00:57:20 -06:00
let output = this . formatOutput ( this . agent . bot ) ;
2023-11-13 11:09:37 -06:00
let interrupted = this . agent . bot . interrupt _code ;
2023-12-06 22:15:09 -06:00
let timedout = this . timedout ;
2023-11-07 22:23:42 -06:00
this . clear ( ) ;
2023-12-20 16:30:05 -08:00
this . agent . bot . emit ( "code_terminated" ) ;
2023-12-06 22:15:09 -06:00
return { success : true , message : output , interrupted , timedout } ;
2023-11-07 12:46:55 -06:00
} catch ( err ) {
2023-11-08 21:05:18 -08:00
this . executing = false ;
2023-12-10 20:19:07 -06:00
clearTimeout ( TIMEOUT ) ;
console . error ( "Code execution triggered catch: " + err ) ;
2023-11-13 00:57:20 -06:00
let message = this . formatOutput ( this . agent . bot ) ;
2023-11-12 13:57:22 -06:00
message += '!!Code threw exception!! Error: ' + err ;
2023-11-13 11:09:37 -06:00
let interrupted = this . agent . bot . interrupt _code ;
2023-11-13 00:57:20 -06:00
await this . stop ( ) ;
2023-12-20 16:30:05 -08:00
this . agent . bot . emit ( "code_terminated" ) ;
2023-12-06 22:15:09 -06:00
return { success : false , message , interrupted , timedout : false } ;
2023-11-07 12:46:55 -06:00
}
2023-11-07 12:00:55 -06:00
}
2023-11-07 22:23:42 -06:00
2023-11-13 00:57:20 -06:00
formatOutput ( bot ) {
2023-12-08 16:18:20 -06:00
if ( bot . interrupt _code && ! this . timedout ) return '' ;
2023-11-13 00:57:20 -06:00
let output = bot . output ;
const MAX _OUT = 1000 ;
if ( output . length > MAX _OUT ) {
output = ` Code output is very long ( ${ output . length } chars) and has been shortened. \n
First outputs : \ n$ { output . substring ( 0 , MAX _OUT / 2 ) } \ n ... skipping many lines . \ nFinal outputs : \ n $ { output . substring ( output . length - MAX _OUT / 2 ) } ` ;
}
else {
output = 'Code output:\n' + output ;
}
return output ;
2023-11-12 13:57:22 -06:00
}
2023-11-13 00:57:20 -06:00
async stop ( ) {
2023-12-06 22:15:09 -06:00
if ( ! this . executing ) return ;
2023-11-08 21:05:18 -08:00
while ( this . executing ) {
2023-11-13 11:09:37 -06:00
this . agent . bot . interrupt _code = true ;
2023-11-08 21:05:18 -08:00
this . agent . bot . collectBlock . cancelTask ( ) ;
this . agent . bot . pathfinder . stop ( ) ;
2023-11-27 21:07:52 -06:00
this . agent . bot . pvp . stop ( ) ;
2023-11-13 11:09:37 -06:00
console . log ( 'waiting for code to finish executing... interrupt:' , this . agent . bot . interrupt _code ) ;
2023-11-12 17:41:01 -06:00
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
2023-11-08 21:05:18 -08:00
}
2023-11-12 13:57:22 -06:00
this . clear ( ) ;
2023-11-13 00:57:20 -06:00
}
clear ( ) {
2023-11-07 22:23:42 -06:00
this . current _code = '' ;
2023-11-13 00:57:20 -06:00
this . agent . bot . output = '' ;
2023-11-13 11:09:37 -06:00
this . agent . bot . interrupt _code = false ;
2023-12-06 22:15:09 -06:00
this . timedout = false ;
}
_startTimeout ( TIMEOUT _MINS = 10 ) {
return setTimeout ( async ( ) => {
console . warn ( ` Code execution timed out after ${ TIMEOUT _MINS } minutes. Attempting force stop. ` ) ;
this . timedout = true ;
2023-12-08 16:18:20 -06:00
this . agent . bot . output += ` \n Action performed for ${ TIMEOUT _MINS } minutes and then timed out and stopped. You may want to continue or do something else. ` ;
2023-12-06 22:15:09 -06:00
this . stop ( ) ; // last attempt to stop
2023-12-08 16:18:20 -06:00
await new Promise ( resolve => setTimeout ( resolve , 5 * 1000 ) ) ; // wait 5 seconds
2023-12-06 22:15:09 -06:00
if ( this . executing ) {
console . error ( ` Failed to stop. Killing process. Goodbye. ` ) ;
2023-12-09 21:40:53 -06:00
this . agent . bot . output += ` \n Force stop failed! Process was killed and will be restarted. Goodbye world. ` ;
2023-12-12 13:39:35 -06:00
this . agent . bot . chat ( 'Goodbye world.' ) ;
2023-12-08 16:18:20 -06:00
let output = this . formatOutput ( this . agent . bot ) ;
this . agent . history . add ( 'system' , output ) ;
this . agent . history . save ( ) ;
2023-12-06 22:15:09 -06:00
process . exit ( 1 ) ; // force exit program
}
console . log ( 'Code execution stopped successfully.' ) ;
} , TIMEOUT _MINS * 60 * 1000 ) ;
2023-11-07 22:23:42 -06:00
}
2023-11-07 12:00:55 -06:00
}